Merge branch 'main' into i18n-de_DE-created
@@ -1,5 +1,94 @@
|
||||
# novelWriter Changelog
|
||||
|
||||
## Version 1.7 Beta 1 [2022-05-17]
|
||||
|
||||
### Release Notes
|
||||
|
||||
This is a beta release of the next release version, and is intended for testing purposes. Please be
|
||||
careful when using this version on live writing projects, and make sure you take frequent backups.
|
||||
|
||||
Please check the changelog for an overview of changes. The full release notes will be added to the
|
||||
final release.
|
||||
|
||||
### Detailed Changelog
|
||||
|
||||
**Features**
|
||||
|
||||
* A simple tool to add Lorem Ipsum placeholder text has been added to the Insert menu. PR #1028.
|
||||
* Status and Importance flags can now be reorganised in Project Settings. Issue #1035. PR #1040.
|
||||
* It is now possible to create multiple Root Folders of the same kind. This makes it possible to
|
||||
add multiple Novel root folders in a project, for instance. Issue #967. PR #1031.
|
||||
* All documents can now be dragged and dropped anywhere in the project tree. The document layout
|
||||
may be converted in the process. PR #1031.
|
||||
* Documents in the project tree can now have other documents as child documents. Issue #1002.
|
||||
PR #1047.
|
||||
* Folders in the project tree that are not empty, can now be moved to trash. PR #1048.
|
||||
* Empty folders are deleted on request, and not moved to trash. Issue #1052. PR #1055.
|
||||
|
||||
**User Interface**
|
||||
|
||||
* The tabs under the project tree and to the right of the main window have been replaced with a
|
||||
toolbar on the left hand side. The toolbar has a set of buttons to change view between Project,
|
||||
Novel and Outline. The three buttons that were available under the project tree have been moved
|
||||
to the bottom of the new toolbar. Issue #1056. PR #1057.
|
||||
* When a document changes from a project document to a note, and back again, the Status flag
|
||||
setting is preserved. Previously, the Importance setting would overwrite it during the
|
||||
conversion. PR #1030.
|
||||
* Item labels, Status labels, and other labels on the GUI are now run through a "simplify" function
|
||||
before being accepted. This functions strips out all whitespaces and consecutive whitespaces and
|
||||
replace them with single plain whitespaces. This is a safer format to store in XML, and also
|
||||
makes sure there aren't invisible characters floating around in the labels. PR #1038.
|
||||
* Due to the changes to how drag and drop works, there are no longer any restrictions on folders
|
||||
and documents. Only root folders remain restricted in terms of moving. Root folders can only be
|
||||
reordered with the Move Up and Move Down commands. PR #1047.
|
||||
* The label for the highlighting of redundant spaces in the Preferences dialog has been updated to
|
||||
better reflect what it does. Issue #1043. PR #1046.
|
||||
* The New Project Wizard will now try to check if the path selected for the new project can
|
||||
actually be used before letting the user proceed to the next page. Issue #1058. PR #1062.
|
||||
|
||||
**Internationalisation**
|
||||
|
||||
* Dutch translations have been added by Martijn van der Kleijn (@mvdkleijn). PR #1027.
|
||||
|
||||
**Functionality**
|
||||
|
||||
* Documents that are missing in the project index when a project is opened are automatically
|
||||
re-indexed. This also handles cases where the cached index is missing. PR #1039.
|
||||
|
||||
**Installation and Packaging**
|
||||
|
||||
* Python 3.6 is no longer supported. PR #1004.
|
||||
* Ubuntu 18.04 packages will no longer be released, due to dropping Python 3.6. Issue #1005.
|
||||
PR #1014.
|
||||
|
||||
**Project File Format**
|
||||
|
||||
* The item nodes in the content section of the main project XML file have been compacted. It now
|
||||
consists of a main item node and meta and a name node. All settings have been made attributes of
|
||||
one of these three nodes, except the item label which is the text value of the name node. The
|
||||
file format version has been bumped to 1.4. Issue #995. PR #993.
|
||||
* Both Importance and Status flag values are now saved to the project file. This means if a
|
||||
document changes layout, the value is no longer lost. PR #1030.
|
||||
|
||||
**Code Improvements**
|
||||
|
||||
* The linting settings have been updated to select between mutually exclusive options in
|
||||
pycodestyle. PR #1014.
|
||||
* The Tokenizer class has been converted to an abstract base class. PR #1026.
|
||||
* The class handling Status and Importance flags has been completely rewritten. The flags are now
|
||||
handled using a unique random key as reference rather than relying on the text of the label
|
||||
itself. This makes it a lot easier to rename them as there is no need to update project items.
|
||||
PR #1034.
|
||||
* Many of the decisions regarding where items are allowed to belong has been delegated to the
|
||||
NWItem class that holds the item. Some is also handled by the NWTree class that holds the project
|
||||
tree. A new maintenance function in the NWTree class will also ensure that the meta data of an
|
||||
item is correct and up to date. This is especially important after an item has been moved, but is
|
||||
also checked when items are initially loaded. PRs #1031 and #1054.
|
||||
* Item handles are now generated using the standard library random number generator. The new
|
||||
handles have the same format as the old algorithm, so they are compatible. PR #1044.
|
||||
|
||||
----
|
||||
|
||||
## Version 1.6.2 [2022-03-20]
|
||||
|
||||
### Release Notes
|
||||
|
||||
@@ -11,13 +11,15 @@
|
||||
|
||||
## Translations
|
||||
|
||||
The default language is English (UK) with English (US) as an option.
|
||||
|
||||
* Dutch: Martijn van der Kleijn (@mvdkleijn)
|
||||
* French: Jan Lüdke (@jyhelle)
|
||||
* German: Myian (@heymyian)
|
||||
* Latin American Spanish: Tommy Marplatt (@tmarplatt)
|
||||
* Norwegian: Veronica Berglyd Olsen (@vkbo)
|
||||
* Portuguese: Bruno Meneguello (@bkmeneguello)
|
||||
* Simplified Chinese: Qianzhi Long (@longqzh)
|
||||
* Latin American Spanish: Tommy Marplatt (@tmarplatt)
|
||||
* Dutch: Martijn van der Kleijn (@mvdkleijn)
|
||||
* German: Myian (@heymyian)
|
||||
|
||||
## Libraries
|
||||
|
||||
@@ -33,7 +35,7 @@ The following libraries are dependencies of novelWriter:
|
||||
|
||||
Some of the assets bundled with novelWriter were adapted from the following sources:
|
||||
|
||||
* Typicons icons by Stephen Hutchings (CC BY-SA 4.0)
|
||||
* Typicons icons by [Stephen Hutchings](https://github.com/stephenhutchings/typicons.font) (CC BY-SA 4.0)
|
||||
* Tomorrow syntax themes by Chris Kempson (MIT License)
|
||||
* Owl syntax themes by Sarah Drasner (MIT License)
|
||||
* Solarized themes by Ethan Schoonover, added by @nullbasis (MIT License)
|
||||
|
||||
@@ -29,6 +29,8 @@ The full documentation is available at
|
||||
The full credits are listed in
|
||||
[CREDITS.md](https://github.com/vkbo/novelWriter/blob/main/CREDITS.md).
|
||||
|
||||
You can also follow novelWriter on Mastodon at [fosstodon.org/@novelwriter](https://fosstodon.org/@novelwriter).
|
||||
|
||||
## Implementation
|
||||
|
||||
The application is written with Python 3 (3.7+) using Qt5 and PyQt5 (5.3+). It is developed on
|
||||
|
||||
@@ -39,11 +39,13 @@ too. novelWriter can be run directly from the Python source, installed from the
|
||||
|
||||
* Website: https://novelwriter.io
|
||||
* Documentation: https://novelwriter.readthedocs.io
|
||||
* Internationalisation: https://crowdin.com/project/novelwriter
|
||||
* Source Code: https://github.com/vkbo/novelWriter
|
||||
* Source Releases: https://github.com/vkbo/novelWriter/releases
|
||||
* Issue Tracker: https://github.com/vkbo/novelWriter/issues
|
||||
* Feature Discussions: https://github.com/vkbo/novelWriter/discussions
|
||||
* PyPi Project: https://pypi.org/project/novelWriter
|
||||
* Social Media: https://fosstodon.org/@novelwriter
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
@@ -59,7 +59,6 @@ The main shorcuts are as follows:
|
||||
":kbd:`Ctrl`:kbd:`Y`", "Redo latest undo."
|
||||
":kbd:`Ctrl`:kbd:`Z`", "Undo latest changes."
|
||||
":kbd:`Ctrl`:kbd:`F7`", "Toggle spell checking."
|
||||
":kbd:`Ctrl`:kbd:`F10`", "Toggle automatic updating of project outline."
|
||||
":kbd:`Ctrl`:kbd:`Up`", "Move item one step up in the project tree."
|
||||
":kbd:`Ctrl`:kbd:`Down`", "Move item one step down in the project tree."
|
||||
":kbd:`Ctrl`:kbd:`Del`", "Delete next word in editor."
|
||||
@@ -88,7 +87,6 @@ The main shorcuts are as follows:
|
||||
":kbd:`F7`", "Re-run spell checker."
|
||||
":kbd:`F8`", "Activate :guilabel:`Focus Mode`, hiding the project tree and document viewer."
|
||||
":kbd:`F9`", "Re-build the project index."
|
||||
":kbd:`F10`", "Re-build the project outline."
|
||||
":kbd:`F11`", "Activate full screen mode."
|
||||
":kbd:`Shift`:kbd:`F1`", "Open the local user manual (PDF) if it is available."
|
||||
":kbd:`Shift`:kbd:`F3`", "Find previous occurrence of search word in current document."
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
The maintenance of translations has been moved to the Crowdin service. The translation strings can
|
||||
be edited there at the [novelWriter project page](https://crowdin.com/project/novelwriter).
|
||||
However, please read the Translation Guidelines section below.
|
||||
|
||||
You can still use the manual approach listed below, and then upload the file through the website's
|
||||
interface. The translation strings for that language will then be updated and queued for approval.
|
||||
@@ -11,6 +12,22 @@ To verify a language file translated through the Crowdin tool, download and extr
|
||||
[Generate an Updated Translation File](#generate-an-updated-translation-file) below.
|
||||
|
||||
|
||||
# Translation Guidelines
|
||||
|
||||
When contributing translations, keep the following things in mind.
|
||||
|
||||
* For descriptive labels and dialog boxes, make sure you do _not_ change the meaning of the text
|
||||
when you translate it from English. The user must receive the same instructions or information
|
||||
regardless of language. This is improtant, otherwise the documentation will be inconsistent with
|
||||
the user interface and it will become a lot more difficult to handle user issues and questions.
|
||||
* If you think a label or description is misleading or incomplete, please file an issue report. The
|
||||
correct way to handle such changes is to change the text in the code first, which will then be
|
||||
forwarded to _all_ translators such that the GUI is consistent across all languages.
|
||||
* For very short labels, like button labels. it may be fine to replace the word with a similar
|
||||
word, but only as long as the user understands what it is supposed to do. Some buttons and tabs
|
||||
have limited space. If necessary, it is OK to use abbreviations.
|
||||
|
||||
|
||||
# Direct Approach Using Qt Linguist
|
||||
|
||||
Here you will find instructions for translating novelWriter to a new language directly using Qt
|
||||
|
||||
@@ -60,9 +60,9 @@ __license__ = "GPLv3"
|
||||
__author__ = "Veronica Berglyd Olsen"
|
||||
__maintainer__ = "Veronica Berglyd Olsen"
|
||||
__email__ = "code@vkbo.net"
|
||||
__version__ = "1.7-alpha0"
|
||||
__hexversion__ = "0x010700a0"
|
||||
__date__ = "2022-02-20"
|
||||
__version__ = "1.7-beta1"
|
||||
__hexversion__ = "0x010700b1"
|
||||
__date__ = "2022-05-17"
|
||||
__status__ = "Stable"
|
||||
__domain__ = "novelwriter.io"
|
||||
__url__ = "https://novelwriter.io"
|
||||
|
||||
@@ -2,104 +2,104 @@
|
||||
"Synopsis": "Synopsis",
|
||||
"Comment": "Opmerking",
|
||||
"Notes": "Notities",
|
||||
"0": "Nul",
|
||||
"1": "Één",
|
||||
"2": "Twee",
|
||||
"3": "Drie",
|
||||
"4": "Vier",
|
||||
"5": "Vijf",
|
||||
"6": "Zes",
|
||||
"7": "Zeven",
|
||||
"8": "Acht",
|
||||
"9": "Negen",
|
||||
"10": "Tien",
|
||||
"11": "Elf",
|
||||
"12": "Twaalf",
|
||||
"13": "Dertien",
|
||||
"14": "Veertien",
|
||||
"15": "Vijftien",
|
||||
"16": "Zestien",
|
||||
"17": "Zeventien",
|
||||
"18": "Achttien",
|
||||
"19": "Negentien",
|
||||
"20": "Twintig",
|
||||
"21": "Eenentwintig",
|
||||
"22": "Tweeentwintig",
|
||||
"23": "Drieentwintig",
|
||||
"24": "Vierentwintig",
|
||||
"25": "Vijfentwintig",
|
||||
"26": "Zesentwintig",
|
||||
"27": "Zevenentwintig",
|
||||
"28": "Achtentwintig",
|
||||
"29": "Negenentwintig",
|
||||
"30": "Dertig",
|
||||
"31": "Eenendertig",
|
||||
"32": "Tweeendertig",
|
||||
"33": "Drieendertig",
|
||||
"34": "Vierendertig",
|
||||
"35": "Vijfendertig",
|
||||
"36": "Zesendertig",
|
||||
"37": "Zevenendertig",
|
||||
"38": "Achtendertig",
|
||||
"39": "Negenendertig",
|
||||
"40": "Veertig",
|
||||
"41": "Eenenveertig",
|
||||
"42": "Tweeënveertig",
|
||||
"43": "Drieenveertig",
|
||||
"44": "Vierenveertig",
|
||||
"45": "Vijfenveertig",
|
||||
"46": "Zesenveertig",
|
||||
"47": "Zevenenveertig",
|
||||
"48": "Achtenveertig",
|
||||
"49": "Negenenveertig",
|
||||
"50": "Vijftig",
|
||||
"51": "Eenenvijftig",
|
||||
"52": "Tweeenvijftig",
|
||||
"53": "Drieenvijftig",
|
||||
"54": "Vierenvijftig",
|
||||
"55": "Vijfenvijftig",
|
||||
"56": "Zesenvijftig",
|
||||
"57": "Zevenenvijftig",
|
||||
"58": "Achtenvijftig",
|
||||
"59": "Negenenvijftig",
|
||||
"60": "Zestig",
|
||||
"61": "Eenenzestig",
|
||||
"62": "Tweeenzestig",
|
||||
"63": "Drieenzestig",
|
||||
"64": "Vierenzestig",
|
||||
"65": "Vijfenzestig",
|
||||
"66": "Zesenzestig",
|
||||
"67": "Zevenenzestig",
|
||||
"68": "Achtenzestig",
|
||||
"69": "Negenenzestig",
|
||||
"70": "Zeventig",
|
||||
"71": "Eenenzeventig",
|
||||
"72": "Tweeenzeventig",
|
||||
"73": "Drieenzeventig",
|
||||
"74": "Vierenzeventig",
|
||||
"75": "Vijfenzeventig",
|
||||
"76": "Zesenzeventig",
|
||||
"77": "Zevenenzeventig",
|
||||
"78": "Achtenzeventig",
|
||||
"79": "Negenenzeventig",
|
||||
"80": "Tachtig",
|
||||
"81": "Eenentachtig",
|
||||
"82": "Tweeentachtig",
|
||||
"83": "Drieentachtig",
|
||||
"84": "Vierentachtig",
|
||||
"85": "Vijfentachtig",
|
||||
"86": "Zesentachtig",
|
||||
"87": "Zevenentachtig",
|
||||
"88": "Achtentachtig",
|
||||
"89": "Negenentachtig",
|
||||
"90": "Negentig",
|
||||
"91": "Eenennegentig",
|
||||
"92": "Tweeennegentig",
|
||||
"93": "Drieennegentig",
|
||||
"94": "Vierennegentig",
|
||||
"95": "Vijfennegentig",
|
||||
"96": "Zesennegentig",
|
||||
"97": "Zevenennegentig",
|
||||
"98": "Achtennegentig",
|
||||
"99": "Negenennegentig"
|
||||
"0": "nul",
|
||||
"1": "één",
|
||||
"2": "twee",
|
||||
"3": "drie",
|
||||
"4": "vier",
|
||||
"5": "vijf",
|
||||
"6": "zes",
|
||||
"7": "zeven",
|
||||
"8": "acht",
|
||||
"9": "negen",
|
||||
"10": "tien",
|
||||
"11": "elf",
|
||||
"12": "twaalf",
|
||||
"13": "dertien",
|
||||
"14": "veertien",
|
||||
"15": "vijftien",
|
||||
"16": "zestien",
|
||||
"17": "zeventien",
|
||||
"18": "achttien",
|
||||
"19": "negentien",
|
||||
"20": "twintig",
|
||||
"21": "eenentwintig",
|
||||
"22": "tweeëntwintig",
|
||||
"23": "drieëntwintig",
|
||||
"24": "vierentwintig",
|
||||
"25": "vijfentwintig",
|
||||
"26": "zesentwintig",
|
||||
"27": "zevenentwintig",
|
||||
"28": "achtentwintig",
|
||||
"29": "negenentwintig",
|
||||
"30": "dertig",
|
||||
"31": "eenendertig",
|
||||
"32": "tweeëndertig",
|
||||
"33": "drieëndertig",
|
||||
"34": "vierendertig",
|
||||
"35": "vijfendertig",
|
||||
"36": "zesendertig",
|
||||
"37": "zevenendertig",
|
||||
"38": "achtendertig",
|
||||
"39": "negenendertig",
|
||||
"40": "veertig",
|
||||
"41": "eenenveertig",
|
||||
"42": "tweeënveertig",
|
||||
"43": "drieënveertig",
|
||||
"44": "vierenveertig",
|
||||
"45": "vijfenveertig",
|
||||
"46": "zesenveertig",
|
||||
"47": "zevenenveertig",
|
||||
"48": "achtenveertig",
|
||||
"49": "negenenveertig",
|
||||
"50": "vijftig",
|
||||
"51": "eenenvijftig",
|
||||
"52": "tweeënvijftig",
|
||||
"53": "drieënvijftig",
|
||||
"54": "vierenvijftig",
|
||||
"55": "vijfenvijftig",
|
||||
"56": "zesenvijftig",
|
||||
"57": "zevenenvijftig",
|
||||
"58": "achtenvijftig",
|
||||
"59": "negenenvijftig",
|
||||
"60": "zestig",
|
||||
"61": "eenenzestig",
|
||||
"62": "tweeënzestig",
|
||||
"63": "drieënzestig",
|
||||
"64": "vierenzestig",
|
||||
"65": "vijfenzestig",
|
||||
"66": "zesenzestig",
|
||||
"67": "zevenenzestig",
|
||||
"68": "achtenzestig",
|
||||
"69": "negenenzestig",
|
||||
"70": "zeventig",
|
||||
"71": "eenenzeventig",
|
||||
"72": "tweeënzeventig",
|
||||
"73": "drieënzeventig",
|
||||
"74": "vierenzeventig",
|
||||
"75": "vijfenzeventig",
|
||||
"76": "zesenzeventig",
|
||||
"77": "zevenenzeventig",
|
||||
"78": "achtenzeventig",
|
||||
"79": "negenenzeventig",
|
||||
"80": "tachtig",
|
||||
"81": "eenentachtig",
|
||||
"82": "tweeëntachtig",
|
||||
"83": "drieëntachtig",
|
||||
"84": "vierentachtig",
|
||||
"85": "vijfentachtig",
|
||||
"86": "zesentachtig",
|
||||
"87": "zevenentachtig",
|
||||
"88": "achtentachtig",
|
||||
"89": "negenentachtig",
|
||||
"90": "negentig",
|
||||
"91": "eenennegentig",
|
||||
"92": "tweeënnegentig",
|
||||
"93": "drieënnegentig",
|
||||
"94": "vierennegentig",
|
||||
"95": "vijfennegentig",
|
||||
"96": "zesennegentig",
|
||||
"97": "zevenennegentig",
|
||||
"98": "achtennegentig",
|
||||
"99": "negenennegentig"
|
||||
}
|
||||
|
||||
@@ -42,16 +42,20 @@ doc_h2 = mixed_heading2.svg
|
||||
doc_h3 = mixed_heading3.svg
|
||||
doc_h4 = mixed_heading4.svg
|
||||
done = typ_input-checked.svg
|
||||
down = typ_chevron-down.svg
|
||||
edit = typ_pencil.svg
|
||||
forward = typ_chevron-right.svg
|
||||
hash = typ_hash.svg
|
||||
maximise = typ_arrow-maximise.svg
|
||||
menu = typ_th-menu.svg
|
||||
minimise = typ_arrow-minimise.svg
|
||||
proj_chapter = mixed_document-chapter.svg
|
||||
proj_details = typ_th-list-grey.svg
|
||||
proj_document = typ_document-text.svg
|
||||
proj_folder = typ_folder.svg
|
||||
proj_note = mixed_document-note.svg
|
||||
proj_scene = mixed_document-scene.svg
|
||||
proj_stats = typ_chart-bar-grey.svg
|
||||
proj_title = mixed_document-title.svg
|
||||
reference = typ_at.svg
|
||||
refresh = typ_refresh.svg
|
||||
@@ -74,3 +78,8 @@ status_stats = typ_chart-bar-grey.svg
|
||||
status_time = typ_stopwatch-grey.svg
|
||||
sticky-off = typ_pin-outline.svg
|
||||
sticky-on = typ_pin.svg
|
||||
up = typ_chevron-up.svg
|
||||
view_build = typ_export.svg
|
||||
view_editor = mixed_edit.svg
|
||||
view_novel = typ_book-grey.svg
|
||||
view_outline = typ_puzzle-outline.svg
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg7536"
|
||||
sodipodi:docname="mixed_edit.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview1259"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="34.625"
|
||||
inkscape:cx="8.7364621"
|
||||
inkscape:cy="11.98556"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1330"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg7536" />
|
||||
<metadata
|
||||
id="metadata7542">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs7540" />
|
||||
<path
|
||||
id="path7534"
|
||||
style="display:inline;fill:#aeaeae;fill-opacity:1;stroke-width:1.10527"
|
||||
d="M 19.160156 0 C 18.73463 0 18.312122 0.160533 17.988281 0.484375 L 14.052734 4.421875 L 4.1054688 4.421875 C 3.4953635 4.421875 3 4.9141801 3 5.5253906 L 3 17.037109 L 3 18.429688 L 3 22.894531 C 3 23.505741 3.4953635 24 4.1054688 24 L 18.474609 24 C 19.084713 24 19.578125 23.505741 19.578125 22.894531 L 19.578125 18.429688 L 19.578125 17.037109 L 19.578125 9.9472656 L 23.515625 6.0117188 C 23.839466 5.6878766 24 5.2623116 24 4.8378906 C 24 4.4134695 23.839466 3.9918109 23.515625 3.6679688 L 20.332031 0.484375 C 20.008189 0.160533 19.584576 0 19.160156 0 z M 19.160156 2.4394531 L 21.560547 4.8398438 L 20.130859 6.2695312 L 17.730469 3.8691406 L 19.160156 2.4394531 z M 16.949219 4.6503906 L 19.349609 7.0507812 L 12.394531 14.005859 L 9.9941406 11.605469 L 16.949219 4.6503906 z M 5.2109375 6.6308594 L 11.841797 6.6308594 L 8.328125 10.146484 C 8.0042828 10.470327 7.7997073 11.043101 7.6328125 11.570312 C 7.4559704 12.122943 7.421875 12.737735 7.421875 13.195312 L 7.421875 16.578125 L 10.804688 16.578125 C 11.262267 16.578125 12.02928 16.467813 12.541016 16.257812 C 13.053858 16.047812 13.530778 15.874623 13.853516 15.550781 L 17.369141 12.158203 L 17.369141 17.037109 L 17.369141 18.429688 L 17.369141 21.789062 L 5.2109375 21.789062 L 5.2109375 18.429688 L 5.2109375 17.037109 L 5.2109375 6.6308594 z M 9.5644531 12.525391 L 11.505859 14.435547 L 9.6308594 14.369141 L 9.5644531 12.525391 z " />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg8707">
|
||||
<metadata
|
||||
id="metadata8713">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8711" />
|
||||
<path
|
||||
d="M 20,0 H 5.3333333 C 4.98,0 4.64,0.14 4.3906667,0.3906667 l -4,4 -0.076,0.082667 c -0.1853334,0.22 -0.3,0.4973334 -0.3133334,0.8 L 0,5.344 V 20 c 0,2.205333 1.7946667,4 4,4 h 12 c 1.738667,0 3.221333,-1.114667 3.772,-2.666667 h 0.894667 c 1.869333,0 3.333333,-1.756 3.333333,-4 V 4 C 24,1.7946667 22.205333,0 20,0 Z M 4,21.333333 C 3.2653333,21.333333 2.6666667,20.736 2.6666667,20 V 6.6666667 H 5.3333333 V 21.333333 Z M 17.333333,20 c 0,0.736 -0.598666,1.333333 -1.333333,1.333333 H 6.6666667 V 6.6666667 H 16 C 16.734667,6.6666667 17.333333,7.264 17.333333,8 Z m 4,-2.666667 c 0,0.826667 -0.432,1.333334 -0.666666,1.333334 H 20 V 8 C 20,5.7946667 18.205333,4 16,4 H 4.552 L 5.8853333,2.6666667 H 20 C 20.734667,2.6666667 21.333333,3.264 21.333333,4 Z"
|
||||
id="path8705"
|
||||
style="stroke-width:1.33333;fill:#aeaeae;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#6699cc;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 3.7531209,6.4345258 c -1.004161,1.002875 -1.004161,2.63319 0,3.6360652 l 8.2467181,8.248004 8.246719,-8.248004 C 20.749281,9.5691528 21,8.9108558 21,8.2525578 c 0,-0.658297 -0.250719,-1.316595 -0.753442,-1.818032 -1.004161,-1.004161 -2.631904,-1.004161 -3.636065,0 L 11.999839,11.043894 7.3891859,6.4345258 c -1.004161,-1.004161 -2.631905,-1.004161 -3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#6699cc;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 20.246879,17.565474 c 1.004161,-1.002875 1.004161,-2.63319 0,-3.636065 L 12.000161,5.6814052 3.7534421,13.929409 c -0.502723,0.501438 -0.753442,1.159735 -0.753442,1.818033 0,0.658297 0.250719,1.316595 0.753442,1.818032 1.004161,1.004161 2.631904,1.004161 3.636065,0 l 4.6106539,-4.609368 4.610653,4.609368 c 1.004161,1.004161 2.631905,1.004161 3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg1169"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1173" />
|
||||
<path
|
||||
d="M 6.8888889,16.888889 V 17.5 c 2.0667778,-3.150889 4.4000001,-4.831444 7.3333331,-4.888889 v 3.666666 c 0,0.673444 0.624556,1.222223 1.397,1.222223 0.444889,0 0.825,-0.193112 1.079223,-0.477889 C 19.061,14.542222 24,9.5555555 24,9.5555555 c 0,0 -4.939,-4.9891115 -7.302778,-7.5007781 -0.254222,-0.2505555 -0.633111,-0.4436667 -1.078,-0.4436667 -0.772444,0 -1.397,0.5463333 -1.397,1.2222222 v 3.6666667 c -5.6955553,0 -7.3333331,5.9534434 -7.3333331,10.3888894 z m -3.6666667,5.5 H 20.333333 c 0.675889,0 1.222222,-0.547557 1.222222,-1.222223 V 13.77711 c -0.811555,0.826222 -1.66711,1.702555 -2.444444,2.501889 v 3.665444 H 4.4444445 V 5.2777775 H 13 V 2.833333 H 3.2222222 C 2.5463333,2.833333 2,3.3808886 2,4.0555552 V 21.166666 c 0,0.674666 0.5463333,1.222223 1.2222222,1.222223 z"
|
||||
id="path1167"
|
||||
style="stroke-width:1.22222;fill:#aeaeae;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg11920">
|
||||
<metadata
|
||||
id="metadata11926">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11924" />
|
||||
<path
|
||||
d="M 22.461538,10.769231 V 8.615385 C 22.461538,6.24 20.52923,4.3076923 18.153846,4.3076923 H 17.219692 C 17.046153,1.8683077 14.816,0 12,0 9.1839995,0 6.9538461,1.8683077 6.7803077,4.3076923 H 5.8461538 C 3.4707692,4.3076923 1.5384615,6.24 1.5384615,8.615385 v 2.153846 c 0,1.245538 0.6326154,2.273231 1.5938462,2.764307 C 2.6916923,13.764923 2.3101538,14.110769 2.032,14.548923 1.7095384,15.058462 1.5384615,15.666462 1.5384615,16.307692 v 3.384616 C 1.5384615,22.067692 3.4707692,24 5.8461538,24 H 9.2307695 C 10.432,24 11.484308,23.388308 11.990154,22.401231 l 0.04431,0.08123 C 12.569847,23.432615 13.592616,24 14.76923,24 h 3.384616 c 2.375384,0 4.307692,-1.932308 4.307692,-4.307692 v -3.384616 c 0,-1.246769 -0.633845,-2.275692 -1.596308,-2.765538 0.955077,-0.505846 1.596308,-1.545846 1.596308,-2.772923 z m -2.461539,8.923077 c 0,1.015384 -0.830769,1.846154 -1.846153,1.846154 H 14.76923 c -0.438154,0 -0.891077,-0.265847 -0.481231,-0.94277 0.30277,-0.344615 0.481231,-0.761846 0.481231,-1.211077 0,-1.190153 -1.239384,-2.153846 -2.769231,-2.153846 -1.529846,0 -2.7692295,0.963693 -2.7692295,2.153846 0,0.369231 0.116923,0.708923 0.313846,1.012923 0.6239995,0.828308 0.167384,1.140924 -0.313846,1.140924 H 5.8461538 C 4.8307692,21.538462 4,20.707692 4,19.692308 v -3.384616 c 0,-0.317538 0.1390769,-0.64123 0.4726153,-0.64123 0.128,0 0.2818462,0.048 0.4701539,0.16 0.3446154,0.302769 0.7630769,0.48123 1.2110769,0.48123 1.1889231,0 2.1538462,-1.240615 2.1538462,-2.76923 0,-1.528616 -0.9649231,-2.769231 -2.1538462,-2.769231 -0.3692308,0 -0.7089231,0.116923 -1.0116923,0.313846 C 4.8504615,11.293538 4.6227692,11.382154 4.4504615,11.382154 4.1304615,11.382154 4,11.080615 4,10.769231 V 8.615385 C 4,7.6 4.8307692,6.7692308 5.8461538,6.7692308 h 3.3846157 c 0.48123,0 0.9378465,-0.3126154 0.299077,-1.1409231 -0.182154,-0.304 -0.299077,-0.6436923 -0.299077,-1.0129231 0,-1.1901538 1.2393835,-2.1538461 2.7692305,-2.1538461 1.529847,0 2.76923,0.9636923 2.76923,2.1538461 0,0.4492308 -0.17846,0.8664616 -0.48123,1.2110769 -0.409846,0.6769231 0.04308,0.9427693 0.48123,0.9427693 h 3.384616 C 19.16923,6.7692308 20,7.6 20,8.615385 v 2.153846 c 0,0.317538 -0.139076,0.641231 -0.472616,0.641231 -0.128,0 -0.281846,-0.048 -0.470154,-0.16 -0.344615,-0.30277 -0.763077,-0.481231 -1.211077,-0.481231 -1.188923,0 -2.153846,1.240615 -2.153846,2.769231 0,1.528615 0.964923,2.76923 2.153846,2.76923 0.369231,0 0.708923,-0.116923 1.011693,-0.313846 0.291693,-0.210461 0.519384,-0.300308 0.691692,-0.299077 0.318769,0 0.449232,0.301539 0.449232,0.612923 v 3.384616 z M 4,12.546462 c 0.1427692,0.03938 0.2904615,0.06646 0.4492307,0.06646 0.4209231,0 0.8406154,-0.146461 1.2775385,-0.448 l 0.084923,-0.05046 C 5.9310769,12.036923 6.0430769,12 6.1538461,12 c 0.4356923,0 0.9230769,0.658462 0.9230769,1.538462 0,0.88 -0.4873846,1.538461 -0.9230769,1.538461 -0.1329231,0 -0.2670769,-0.05908 -0.3987692,-0.174769 l -0.176,-0.128 C 5.2086153,14.548923 4.8356923,14.435692 4.4726153,14.435692 4.3076923,14.435692 4.1513846,14.464002 4,14.508312 Z M 19.550769,14.464 c -0.420922,0 -0.840616,0.146462 -1.277539,0.448 l -0.08492,0.05046 c -0.119384,0.07754 -0.231383,0.114461 -0.340923,0.114461 -0.435692,0 -0.923077,-0.658461 -0.923077,-1.538461 0,-0.88 0.487385,-1.538462 0.923077,-1.538462 0.132924,0 0.267077,0.05908 0.39877,0.174769 l 0.175999,0.128 c 0.371693,0.225231 0.743385,0.338462 1.106462,0.338462 0.167384,0 0.322461,-0.03077 0.472616,-0.07631 v 1.965539 c -0.144,-0.03938 -0.291693,-0.06646 -0.450462,-0.06646 z m -8.54523,7.074462 C 11.129847,21.112615 11.12,20.534154 10.630154,19.820308 l -0.05415,-0.09108 c -0.07631,-0.120616 -0.115692,-0.232616 -0.115692,-0.343385 0,-0.435692 0.657231,-0.923077 1.538462,-0.923077 0.88123,0 1.538461,0.487385 1.538461,0.923077 0,0.132923 -0.05908,0.267077 -0.176,0.4 l -0.128,0.174769 c -0.4,0.660923 -0.382769,1.204923 -0.270769,1.580308 h -1.956923 z"
|
||||
id="path11918"
|
||||
style="stroke-width:1.23077;fill:#aeaeae;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg4455"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs4459" />
|
||||
<path
|
||||
d="M19 17h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 10h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 3h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2z"
|
||||
id="path4453"
|
||||
style="fill:#6699cc;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@@ -42,16 +42,20 @@ doc_h2 = mixed_heading2.svg
|
||||
doc_h3 = mixed_heading3.svg
|
||||
doc_h4 = mixed_heading4.svg
|
||||
done = typ_input-checked.svg
|
||||
down = typ_chevron-down.svg
|
||||
edit = typ_pencil.svg
|
||||
forward = typ_chevron-right.svg
|
||||
hash = typ_hash.svg
|
||||
maximise = typ_arrow-maximise.svg
|
||||
menu = typ_th-menu.svg
|
||||
minimise = typ_arrow-minimise.svg
|
||||
proj_chapter = mixed_document-chapter.svg
|
||||
proj_details = typ_th-list-grey.svg
|
||||
proj_document = typ_document-text.svg
|
||||
proj_folder = typ_folder.svg
|
||||
proj_note = mixed_document-note.svg
|
||||
proj_scene = mixed_document-scene.svg
|
||||
proj_stats = typ_chart-bar-grey.svg
|
||||
proj_title = mixed_document-title.svg
|
||||
reference = typ_at.svg
|
||||
refresh = typ_refresh.svg
|
||||
@@ -74,3 +78,8 @@ status_stats = typ_chart-bar-grey.svg
|
||||
status_time = typ_stopwatch-grey.svg
|
||||
sticky-off = typ_pin-outline.svg
|
||||
sticky-on = typ_pin.svg
|
||||
up = typ_chevron-up.svg
|
||||
view_build = typ_export.svg
|
||||
view_editor = mixed_edit.svg
|
||||
view_novel = typ_book-grey.svg
|
||||
view_outline = typ_puzzle-outline.svg
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg7536"
|
||||
sodipodi:docname="mixed_edit.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/">
|
||||
<sodipodi:namedview
|
||||
id="namedview1259"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="34.625"
|
||||
inkscape:cx="3.1913357"
|
||||
inkscape:cy="11.98556"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1330"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg7536" />
|
||||
<metadata
|
||||
id="metadata7542">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs7540" />
|
||||
<path
|
||||
id="path7534"
|
||||
style="display:inline;fill:#000000;fill-opacity:0.72156864;stroke-width:1.10527"
|
||||
d="M 19.160156 0 C 18.73463 0 18.312122 0.160533 17.988281 0.484375 L 14.052734 4.421875 L 4.1054688 4.421875 C 3.4953635 4.421875 3 4.9141801 3 5.5253906 L 3 17.037109 L 3 18.429688 L 3 22.894531 C 3 23.505741 3.4953635 24 4.1054688 24 L 18.474609 24 C 19.084713 24 19.578125 23.505741 19.578125 22.894531 L 19.578125 18.429688 L 19.578125 17.037109 L 19.578125 9.9472656 L 23.515625 6.0117188 C 23.839466 5.6878766 24 5.2623116 24 4.8378906 C 24 4.4134695 23.839466 3.9918109 23.515625 3.6679688 L 20.332031 0.484375 C 20.008189 0.160533 19.584576 0 19.160156 0 z M 19.160156 2.4394531 L 21.560547 4.8398438 L 20.130859 6.2695312 L 17.730469 3.8691406 L 19.160156 2.4394531 z M 16.949219 4.6503906 L 19.349609 7.0507812 L 12.394531 14.005859 L 9.9941406 11.605469 L 16.949219 4.6503906 z M 5.2109375 6.6308594 L 11.841797 6.6308594 L 8.328125 10.146484 C 8.0042828 10.470327 7.7997073 11.043101 7.6328125 11.570312 C 7.4559704 12.122943 7.421875 12.737735 7.421875 13.195312 L 7.421875 16.578125 L 10.804688 16.578125 C 11.262267 16.578125 12.02928 16.467813 12.541016 16.257812 C 13.053858 16.047812 13.530778 15.874623 13.853516 15.550781 L 17.369141 12.158203 L 17.369141 17.037109 L 17.369141 18.429688 L 17.369141 21.789062 L 5.2109375 21.789062 L 5.2109375 18.429688 L 5.2109375 17.037109 L 5.2109375 6.6308594 z M 9.5644531 12.525391 L 11.505859 14.435547 L 9.6308594 14.369141 L 9.5644531 12.525391 z " />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 2.9 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg8707">
|
||||
<metadata
|
||||
id="metadata8713">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8711" />
|
||||
<path
|
||||
d="M 20,0 H 5.3333333 C 4.98,0 4.64,0.14 4.3906667,0.3906667 l -4,4 -0.076,0.082667 c -0.1853334,0.22 -0.3,0.4973334 -0.3133334,0.8 L 0,5.344 V 20 c 0,2.205333 1.7946667,4 4,4 h 12 c 1.738667,0 3.221333,-1.114667 3.772,-2.666667 h 0.894667 c 1.869333,0 3.333333,-1.756 3.333333,-4 V 4 C 24,1.7946667 22.205333,0 20,0 Z M 4,21.333333 C 3.2653333,21.333333 2.6666667,20.736 2.6666667,20 V 6.6666667 H 5.3333333 V 21.333333 Z M 17.333333,20 c 0,0.736 -0.598666,1.333333 -1.333333,1.333333 H 6.6666667 V 6.6666667 H 16 C 16.734667,6.6666667 17.333333,7.264 17.333333,8 Z m 4,-2.666667 c 0,0.826667 -0.432,1.333334 -0.666666,1.333334 H 20 V 8 C 20,5.7946667 18.205333,4 16,4 H 4.552 L 5.8853333,2.6666667 H 20 C 20.734667,2.6666667 21.333333,3.264 21.333333,4 Z"
|
||||
id="path8705"
|
||||
style="stroke-width:1.33333;fill:#000000;fill-opacity:0.72156864" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#4271ae;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 3.7531209,6.4345258 c -1.004161,1.002875 -1.004161,2.63319 0,3.6360652 l 8.2467181,8.248004 8.246719,-8.248004 C 20.749281,9.5691528 21,8.9108558 21,8.2525578 c 0,-0.658297 -0.250719,-1.316595 -0.753442,-1.818032 -1.004161,-1.004161 -2.631904,-1.004161 -3.636065,0 L 11.999839,11.043894 7.3891859,6.4345258 c -1.004161,-1.004161 -2.631905,-1.004161 -3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#4271ae;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 20.246879,17.565474 c 1.004161,-1.002875 1.004161,-2.63319 0,-3.636065 L 12.000161,5.6814052 3.7534421,13.929409 c -0.502723,0.501438 -0.753442,1.159735 -0.753442,1.818033 0,0.658297 0.250719,1.316595 0.753442,1.818032 1.004161,1.004161 2.631904,1.004161 3.636065,0 l 4.6106539,-4.609368 4.610653,4.609368 c 1.004161,1.004161 2.631905,1.004161 3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,38 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg1169"
|
||||
sodipodi:docname="typ_export.svg"
|
||||
inkscape:version="1.1.2 (0a00cf5339, 2022-02-04)"
|
||||
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<sodipodi:namedview
|
||||
id="namedview2676"
|
||||
pagecolor="#505050"
|
||||
bordercolor="#eeeeee"
|
||||
borderopacity="1"
|
||||
inkscape:pageshadow="0"
|
||||
inkscape:pageopacity="0"
|
||||
inkscape:pagecheckerboard="0"
|
||||
showgrid="false"
|
||||
inkscape:zoom="34.625"
|
||||
inkscape:cx="6.4259928"
|
||||
inkscape:cy="11.98556"
|
||||
inkscape:window-width="2560"
|
||||
inkscape:window-height="1330"
|
||||
inkscape:window-x="0"
|
||||
inkscape:window-y="0"
|
||||
inkscape:window-maximized="1"
|
||||
inkscape:current-layer="svg1169" />
|
||||
<defs
|
||||
id="defs1173" />
|
||||
<path
|
||||
d="M 6.8888889,16.888889 V 17.5 c 2.0667778,-3.150889 4.4000001,-4.831444 7.3333331,-4.888889 v 3.666666 c 0,0.673444 0.624556,1.222223 1.397,1.222223 0.444889,0 0.825,-0.193112 1.079223,-0.477889 C 19.061,14.542222 24,9.5555555 24,9.5555555 c 0,0 -4.939,-4.9891115 -7.302778,-7.5007781 -0.254222,-0.2505555 -0.633111,-0.4436667 -1.078,-0.4436667 -0.772444,0 -1.397,0.5463333 -1.397,1.2222222 v 3.6666667 c -5.6955553,0 -7.3333331,5.9534434 -7.3333331,10.3888894 z m -3.6666667,5.5 H 20.333333 c 0.675889,0 1.222222,-0.547557 1.222222,-1.222223 V 13.77711 c -0.811555,0.826222 -1.66711,1.702555 -2.444444,2.501889 v 3.665444 H 4.4444445 V 5.2777775 H 13 V 2.833333 H 3.2222222 C 2.5463333,2.833333 2,3.3808886 2,4.0555552 V 21.166666 c 0,0.674666 0.5463333,1.222223 1.2222222,1.222223 z"
|
||||
id="path1167"
|
||||
style="stroke-width:1.22222;fill:#000000;fill-opacity:0.72156864" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg11920">
|
||||
<metadata
|
||||
id="metadata11926">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11924" />
|
||||
<path
|
||||
d="M 22.461538,10.769231 V 8.615385 C 22.461538,6.24 20.52923,4.3076923 18.153846,4.3076923 H 17.219692 C 17.046153,1.8683077 14.816,0 12,0 9.1839995,0 6.9538461,1.8683077 6.7803077,4.3076923 H 5.8461538 C 3.4707692,4.3076923 1.5384615,6.24 1.5384615,8.615385 v 2.153846 c 0,1.245538 0.6326154,2.273231 1.5938462,2.764307 C 2.6916923,13.764923 2.3101538,14.110769 2.032,14.548923 1.7095384,15.058462 1.5384615,15.666462 1.5384615,16.307692 v 3.384616 C 1.5384615,22.067692 3.4707692,24 5.8461538,24 H 9.2307695 C 10.432,24 11.484308,23.388308 11.990154,22.401231 l 0.04431,0.08123 C 12.569847,23.432615 13.592616,24 14.76923,24 h 3.384616 c 2.375384,0 4.307692,-1.932308 4.307692,-4.307692 v -3.384616 c 0,-1.246769 -0.633845,-2.275692 -1.596308,-2.765538 0.955077,-0.505846 1.596308,-1.545846 1.596308,-2.772923 z m -2.461539,8.923077 c 0,1.015384 -0.830769,1.846154 -1.846153,1.846154 H 14.76923 c -0.438154,0 -0.891077,-0.265847 -0.481231,-0.94277 0.30277,-0.344615 0.481231,-0.761846 0.481231,-1.211077 0,-1.190153 -1.239384,-2.153846 -2.769231,-2.153846 -1.529846,0 -2.7692295,0.963693 -2.7692295,2.153846 0,0.369231 0.116923,0.708923 0.313846,1.012923 0.6239995,0.828308 0.167384,1.140924 -0.313846,1.140924 H 5.8461538 C 4.8307692,21.538462 4,20.707692 4,19.692308 v -3.384616 c 0,-0.317538 0.1390769,-0.64123 0.4726153,-0.64123 0.128,0 0.2818462,0.048 0.4701539,0.16 0.3446154,0.302769 0.7630769,0.48123 1.2110769,0.48123 1.1889231,0 2.1538462,-1.240615 2.1538462,-2.76923 0,-1.528616 -0.9649231,-2.769231 -2.1538462,-2.769231 -0.3692308,0 -0.7089231,0.116923 -1.0116923,0.313846 C 4.8504615,11.293538 4.6227692,11.382154 4.4504615,11.382154 4.1304615,11.382154 4,11.080615 4,10.769231 V 8.615385 C 4,7.6 4.8307692,6.7692308 5.8461538,6.7692308 h 3.3846157 c 0.48123,0 0.9378465,-0.3126154 0.299077,-1.1409231 -0.182154,-0.304 -0.299077,-0.6436923 -0.299077,-1.0129231 0,-1.1901538 1.2393835,-2.1538461 2.7692305,-2.1538461 1.529847,0 2.76923,0.9636923 2.76923,2.1538461 0,0.4492308 -0.17846,0.8664616 -0.48123,1.2110769 -0.409846,0.6769231 0.04308,0.9427693 0.48123,0.9427693 h 3.384616 C 19.16923,6.7692308 20,7.6 20,8.615385 v 2.153846 c 0,0.317538 -0.139076,0.641231 -0.472616,0.641231 -0.128,0 -0.281846,-0.048 -0.470154,-0.16 -0.344615,-0.30277 -0.763077,-0.481231 -1.211077,-0.481231 -1.188923,0 -2.153846,1.240615 -2.153846,2.769231 0,1.528615 0.964923,2.76923 2.153846,2.76923 0.369231,0 0.708923,-0.116923 1.011693,-0.313846 0.291693,-0.210461 0.519384,-0.300308 0.691692,-0.299077 0.318769,0 0.449232,0.301539 0.449232,0.612923 v 3.384616 z M 4,12.546462 c 0.1427692,0.03938 0.2904615,0.06646 0.4492307,0.06646 0.4209231,0 0.8406154,-0.146461 1.2775385,-0.448 l 0.084923,-0.05046 C 5.9310769,12.036923 6.0430769,12 6.1538461,12 c 0.4356923,0 0.9230769,0.658462 0.9230769,1.538462 0,0.88 -0.4873846,1.538461 -0.9230769,1.538461 -0.1329231,0 -0.2670769,-0.05908 -0.3987692,-0.174769 l -0.176,-0.128 C 5.2086153,14.548923 4.8356923,14.435692 4.4726153,14.435692 4.3076923,14.435692 4.1513846,14.464002 4,14.508312 Z M 19.550769,14.464 c -0.420922,0 -0.840616,0.146462 -1.277539,0.448 l -0.08492,0.05046 c -0.119384,0.07754 -0.231383,0.114461 -0.340923,0.114461 -0.435692,0 -0.923077,-0.658461 -0.923077,-1.538461 0,-0.88 0.487385,-1.538462 0.923077,-1.538462 0.132924,0 0.267077,0.05908 0.39877,0.174769 l 0.175999,0.128 c 0.371693,0.225231 0.743385,0.338462 1.106462,0.338462 0.167384,0 0.322461,-0.03077 0.472616,-0.07631 v 1.965539 c -0.144,-0.03938 -0.291693,-0.06646 -0.450462,-0.06646 z m -8.54523,7.074462 C 11.129847,21.112615 11.12,20.534154 10.630154,19.820308 l -0.05415,-0.09108 c -0.07631,-0.120616 -0.115692,-0.232616 -0.115692,-0.343385 0,-0.435692 0.657231,-0.923077 1.538462,-0.923077 0.88123,0 1.538461,0.487385 1.538461,0.923077 0,0.132923 -0.05908,0.267077 -0.176,0.4 l -0.128,0.174769 c -0.4,0.660923 -0.382769,1.204923 -0.270769,1.580308 h -1.956923 z"
|
||||
id="path11918"
|
||||
style="stroke-width:1.23077;fill:#000000;fill-opacity:0.72156864" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg4455"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs4459" />
|
||||
<path
|
||||
d="M19 17h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 10h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 3h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2z"
|
||||
id="path4453"
|
||||
style="fill:#4271ae;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@@ -2,56 +2,16 @@
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Release Notes for 1.6</h2>
|
||||
<p><i>Released on 20 February 2022</i></p>
|
||||
<h2>Release Notes for 1.7 Beta 1</h2>
|
||||
<p><i>Released on 17 May 2022</i></p>
|
||||
|
||||
<p>This release does not introduce any major new features, but is instead a collection of minor
|
||||
improvements and tweaks based on user requests. There are also a number of changes under the hood
|
||||
to improve the structure and performance of novelWriter.</p>
|
||||
<p><u>Some key improvements to the user interface are:</u></p>
|
||||
<p>✓ The max text width setting in Preferences now also applies to the document viewer, and
|
||||
the setting itself on the Preference dialog has been simplified a bit.</p>
|
||||
<p>✓ When text is selected in the document editor, the number of words selected is displayed
|
||||
in the editor's footer area.</p>
|
||||
<p>✓ The search tool in the document editor now shows the number of results in the
|
||||
document.</p>
|
||||
<p>✓ The Enter and Ctrl+O keyboard shortcuts should now work the same way in all tree
|
||||
views.</p>
|
||||
<p>✓ It is now possible to set a blank section title format on the Build Novel Project tool
|
||||
and get empty paragraphs in the output. Previously, a blank format would just remove the section
|
||||
break entirely. This change allows the user to define hard and soft scene breaks using level three
|
||||
and four headings. The scene and section titles can be hidden completely with two new switches
|
||||
added to the user interface.</p>
|
||||
<p><u>Other feature changes include:</u></p>
|
||||
<p>✓ The project index is now automatically rebuilt in the event it is empty or incomplete
|
||||
when the project is opened.</p>
|
||||
<p>✓ The user can now add their own syntax and GUI theme files in the app folder in their
|
||||
user area on the host operating system. Where the custom files must be added is described in the
|
||||
documentation.</p>
|
||||
<p>✓ A Windows installer is yet again provided for novelWriter. If you have novelWriter
|
||||
installed using another method, make sure you uninstall it properly first as the two methods are
|
||||
not compatible.</p>
|
||||
<p>✓ Release versions for Ubuntu 21.04 have been dropped, and added for the upcoming Ubuntu
|
||||
22.04.</p>
|
||||
<p>✓ Most translations have been updated. A Dutch translation is in the works.</p>
|
||||
<p>This is a beta release of the next release version, and is intended for testing purposes. Please
|
||||
be careful when using this version on live writing projects, and make sure you take frequent
|
||||
backups.</p>
|
||||
<p>Please check the changelog for an overview of changes. The full release notes will be added to
|
||||
the final release.</p>
|
||||
|
||||
<p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
|
||||
|
||||
<h2>Patch Notes</h2>
|
||||
|
||||
<h3>Patch 1.6.1 – 16 March 2022</h3>
|
||||
|
||||
<p>This is a bugfix and patch release that fixes two recursion/loop issues. One would potentially
|
||||
cause a crash if the window was resized rapidly, and one would cause a hang with certain search
|
||||
parameters in the editor's search box. The Latin American Spanish translation has also been
|
||||
updated.</p>
|
||||
|
||||
<h3>Patch 1.6.2 – 20 March 2022</h3>
|
||||
|
||||
<p>This is a bugfix release that fixes a couple of minor issues. Projects containing one or more
|
||||
empty documents would trigger a rebuild of the index each time the project was opened. This has now
|
||||
been fixed. Another fix resolves an error message being written to the console logging output when
|
||||
a new document was created. Both errors were harmless.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -184,6 +184,12 @@ def checkIntRange(value, first, last, default):
|
||||
return default
|
||||
|
||||
|
||||
def minmax(value, minVal, maxVal):
|
||||
"""Make sure an integer is between min and max value (inclusive).
|
||||
"""
|
||||
return min(maxVal, max(minVal, value))
|
||||
|
||||
|
||||
def checkIntTuple(value, valid, default):
|
||||
"""Check that an int is an element of a tuple. If it isn't, return
|
||||
the default value.
|
||||
@@ -245,6 +251,13 @@ def formatTime(tS):
|
||||
# String Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def simplified(string):
|
||||
"""Take a string an strip leading and trailing whitespaces, and
|
||||
replace all occurences of (multiple) whitespaces with a 0x20 space.
|
||||
"""
|
||||
return " ".join(str(string).strip().split())
|
||||
|
||||
|
||||
def splitVersionNumber(value):
|
||||
"""Split a version string on the form aa.bb.cc into major, minor
|
||||
and patch, and computes an integer value aabbcc.
|
||||
|
||||
@@ -978,12 +978,12 @@ class Config:
|
||||
"""
|
||||
try:
|
||||
import enchant # noqa: F401
|
||||
self.hasEnchant = True
|
||||
logger.debug("Checking package 'pyenchant': OK")
|
||||
except Exception:
|
||||
except ImportError:
|
||||
self.hasEnchant = False
|
||||
logger.debug("Checking package 'pyenchant': Missing")
|
||||
|
||||
else:
|
||||
self.hasEnchant = True
|
||||
logger.debug("Checking package 'pyenchant': OK")
|
||||
return
|
||||
|
||||
# END Class Config
|
||||
|
||||
@@ -25,7 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
|
||||
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwOutline
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwOutline
|
||||
|
||||
|
||||
def trConst(tString):
|
||||
@@ -34,7 +34,7 @@ def trConst(tString):
|
||||
return QCoreApplication.translate("Constant", tString)
|
||||
|
||||
|
||||
class nwConst():
|
||||
class nwConst:
|
||||
|
||||
# Date and Time Formats
|
||||
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
|
||||
@@ -42,32 +42,13 @@ class nwConst():
|
||||
FMT_DSTAMP = "%Y-%m-%d" # Date only format
|
||||
|
||||
# Various Hard Limits
|
||||
MAX_DEPTH = 30 # Maximum folder depth of a project
|
||||
MAX_DOCSIZE = 5000000 # Maxium size of a single document
|
||||
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
|
||||
|
||||
# END Class nwConst
|
||||
|
||||
|
||||
class nwLists():
|
||||
"""Lists used for grouping various other constants.
|
||||
"""
|
||||
# Regular user-accessible item types
|
||||
REG_TYPES = {nwItemType.ROOT, nwItemType.FOLDER, nwItemType.FILE}
|
||||
|
||||
# Item classes where the full list of novel layouts are allowed
|
||||
CLS_NOVEL = {nwItemClass.NOVEL, nwItemClass.ARCHIVE}
|
||||
|
||||
# Item classes which do not require items to have same class
|
||||
FREE_CLASS = {nwItemClass.ARCHIVE, nwItemClass.TRASH}
|
||||
|
||||
# Deprecated nwItemLayout entries
|
||||
DEP_LAYOUT = ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE")
|
||||
|
||||
# END Class nwLists
|
||||
|
||||
|
||||
class nwRegEx():
|
||||
class nwRegEx:
|
||||
|
||||
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
@@ -76,7 +57,7 @@ class nwRegEx():
|
||||
# END Class nwRegEx
|
||||
|
||||
|
||||
class nwFiles():
|
||||
class nwFiles:
|
||||
|
||||
PROJ_FILE = "nwProject.nwx"
|
||||
PROJ_DICT = "wordlist.txt"
|
||||
@@ -126,7 +107,7 @@ class nwKeyWords:
|
||||
# END Class nwKeyWords
|
||||
|
||||
|
||||
class nwLabels():
|
||||
class nwLabels:
|
||||
|
||||
CLASS_NAME = {
|
||||
nwItemClass.NO_CLASS: QT_TRANSLATE_NOOP("Constant", "None"),
|
||||
@@ -204,7 +185,7 @@ class nwLabels():
|
||||
# END Class nwLabels
|
||||
|
||||
|
||||
class nwQuotes():
|
||||
class nwQuotes:
|
||||
"""Allowed quotation marks.
|
||||
Source: https://en.wikipedia.org/wiki/Quotation_mark
|
||||
"""
|
||||
|
||||
@@ -20,7 +20,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from novelwriter.core.document import NWDoc
|
||||
from novelwriter.core.index import NWIndex, countWords
|
||||
from novelwriter.core.index import countWords
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.core.spellcheck import NWSpellEnchant
|
||||
from novelwriter.core.tohtml import ToHtml
|
||||
@@ -30,7 +30,6 @@ from novelwriter.core.tomd import ToMarkdown
|
||||
__all__ = [
|
||||
"countWords",
|
||||
"NWDoc",
|
||||
"NWIndex",
|
||||
"NWProject",
|
||||
"NWSpellEnchant",
|
||||
"ToHtml",
|
||||
|
||||
@@ -52,7 +52,7 @@ class NWDoc():
|
||||
self._docHandle = theHandle
|
||||
|
||||
if self._docHandle is not None:
|
||||
self._theItem = self.theProject.projTree[theHandle]
|
||||
self._theItem = self.theProject.tree[theHandle]
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -29,9 +29,9 @@ from lxml import etree
|
||||
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.common import (
|
||||
checkInt, isHandle, isItemClass, isItemLayout, isItemType
|
||||
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
|
||||
)
|
||||
from novelwriter.constants import nwLabels, nwLists, trConst
|
||||
from novelwriter.constants import nwLabels, trConst
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -45,6 +45,7 @@ class NWItem():
|
||||
self._name = ""
|
||||
self._handle = None
|
||||
self._parent = None
|
||||
self._root = None
|
||||
self._order = 0
|
||||
self._type = nwItemType.NO_TYPE
|
||||
self._class = nwItemClass.NO_CLASS
|
||||
@@ -85,6 +86,10 @@ class NWItem():
|
||||
def itemParent(self):
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
def itemRoot(self):
|
||||
return self._root
|
||||
|
||||
@property
|
||||
def itemOrder(self):
|
||||
return self._order
|
||||
@@ -147,6 +152,7 @@ class NWItem():
|
||||
itemAttrib = {}
|
||||
itemAttrib["handle"] = str(self._handle)
|
||||
itemAttrib["parent"] = str(self._parent)
|
||||
itemAttrib["root"] = str(self._root)
|
||||
itemAttrib["order"] = str(self._order)
|
||||
itemAttrib["type"] = str(self._type.name)
|
||||
itemAttrib["class"] = str(self._class.name)
|
||||
@@ -154,19 +160,18 @@ class NWItem():
|
||||
itemAttrib["layout"] = str(self._layout.name)
|
||||
|
||||
metaAttrib = {}
|
||||
metaAttrib["expanded"] = str(self._expanded)
|
||||
if self._type == nwItemType.FILE:
|
||||
metaAttrib["charCount"] = str(self._charCount)
|
||||
metaAttrib["wordCount"] = str(self._wordCount)
|
||||
metaAttrib["paraCount"] = str(self._paraCount)
|
||||
metaAttrib["cursorPos"] = str(self._cursorPos)
|
||||
else:
|
||||
metaAttrib["expanded"] = str(self._expanded)
|
||||
|
||||
nameAttrib = {}
|
||||
nameAttrib["status"] = str(self._status)
|
||||
nameAttrib["import"] = str(self._import)
|
||||
if self._type == nwItemType.FILE:
|
||||
nameAttrib["exported"] = str(self._exported)
|
||||
nameAttrib["exported"] = str(self._exported)
|
||||
|
||||
xPack = etree.SubElement(xParent, "item", attrib=itemAttrib)
|
||||
self._subPack(xPack, "meta", attrib=metaAttrib)
|
||||
@@ -188,6 +193,7 @@ class NWItem():
|
||||
return False
|
||||
|
||||
self.setParent(xItem.attrib.get("parent", None))
|
||||
self.setRoot(xItem.attrib.get("root", None))
|
||||
self.setOrder(xItem.attrib.get("order", 0))
|
||||
self.setType(xItem.attrib.get("type", nwItemType.NO_TYPE))
|
||||
self.setClass(xItem.attrib.get("class", nwItemClass.NO_CLASS))
|
||||
@@ -233,6 +239,17 @@ class NWItem():
|
||||
# version of novelWriter that doesn't know the tag
|
||||
logger.error("Unknown tag '%s'", xValue.tag)
|
||||
|
||||
# Make some checks to ensure consistency
|
||||
if self._type == nwItemType.ROOT:
|
||||
self._root = self._handle # Root items are their own ancestor
|
||||
self._parent = None # Root items cannot have a parent
|
||||
|
||||
if self._type != nwItemType.FILE:
|
||||
self._charCount = 0 # Only set for files
|
||||
self._wordCount = 0 # Only set for files
|
||||
self._paraCount = 0 # Only set for files
|
||||
self._cursorPos = 0 # Only set for files
|
||||
|
||||
return True
|
||||
|
||||
@staticmethod
|
||||
@@ -249,7 +266,7 @@ class NWItem():
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
# Lookup Methods
|
||||
##
|
||||
|
||||
def describeMe(self, hLevel=None):
|
||||
@@ -275,168 +292,226 @@ class NWItem():
|
||||
|
||||
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
|
||||
|
||||
def isNovelLike(self):
|
||||
"""Returns true if the item is of a novel-like class.
|
||||
"""
|
||||
return self._class in (nwItemClass.NOVEL, nwItemClass.ARCHIVE)
|
||||
|
||||
def documentAllowed(self):
|
||||
"""Returns true if the item is allowed to be of document layout.
|
||||
"""
|
||||
return self._class in (nwItemClass.NOVEL, nwItemClass.ARCHIVE, nwItemClass.TRASH)
|
||||
|
||||
def isInactive(self):
|
||||
"""Returns true if the item is in an inactive class.
|
||||
"""
|
||||
return self._class in (nwItemClass.NO_CLASS, nwItemClass.ARCHIVE, nwItemClass.TRASH)
|
||||
|
||||
def getImportStatus(self):
|
||||
"""Return the relevant importance or status label and icon for
|
||||
the current item based on its class.
|
||||
"""
|
||||
if self._class in nwLists.CLS_NOVEL:
|
||||
stName = self.theProject.statusItems.checkEntry(self._status)
|
||||
stIcon = self.theProject.statusItems.getIcon(stName)
|
||||
if self.isNovelLike():
|
||||
stName = self.theProject.statusItems.name(self._status)
|
||||
stIcon = self.theProject.statusItems.icon(self._status)
|
||||
else:
|
||||
stName = self.theProject.importItems.checkEntry(self._import)
|
||||
stIcon = self.theProject.importItems.getIcon(stName)
|
||||
stName = self.theProject.importItems.name(self._import)
|
||||
stIcon = self.theProject.importItems.icon(self._import)
|
||||
return stName, stIcon
|
||||
|
||||
def setImportStatus(self, theLabel):
|
||||
##
|
||||
# Special Setters
|
||||
##
|
||||
|
||||
def setImportStatus(self, value):
|
||||
"""Update the importance or status value based on class. This is
|
||||
a wrapper setter for setStatus and setImport.
|
||||
"""
|
||||
if self._class in nwLists.CLS_NOVEL:
|
||||
self.setStatus(theLabel)
|
||||
if self.isNovelLike():
|
||||
self.setStatus(value)
|
||||
else:
|
||||
self.setImport(theLabel)
|
||||
self.setImport(value)
|
||||
return
|
||||
|
||||
def setClassDefaults(self, itemClass):
|
||||
"""Set the default values based on the item's class and the
|
||||
project settings.
|
||||
"""
|
||||
if self._parent is not None:
|
||||
# Only update for child items
|
||||
self.setClass(itemClass)
|
||||
|
||||
if self._layout == nwItemLayout.NO_LAYOUT:
|
||||
# If no layout is set, pick one
|
||||
if self.isNovelLike():
|
||||
self._layout = nwItemLayout.DOCUMENT
|
||||
else:
|
||||
self._layout = nwItemLayout.NOTE
|
||||
elif not self.documentAllowed():
|
||||
# Change layout to note if it is not in an allowed folder
|
||||
self._layout = nwItemLayout.NOTE
|
||||
|
||||
if self._status is None:
|
||||
self.setStatus("New") # This forces a default value lookup
|
||||
|
||||
if self._import is None:
|
||||
self.setImport("New") # This forces a default value lookup
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Set Item Values
|
||||
##
|
||||
|
||||
def setName(self, theName):
|
||||
def setName(self, name):
|
||||
"""Set the item name.
|
||||
"""
|
||||
if isinstance(theName, str):
|
||||
self._name = theName.strip()
|
||||
if isinstance(name, str):
|
||||
self._name = simplified(name)
|
||||
else:
|
||||
self._name = ""
|
||||
return
|
||||
|
||||
def setHandle(self, theHandle):
|
||||
def setHandle(self, handle):
|
||||
"""Set the item handle, and ensure it is valid.
|
||||
"""
|
||||
if isHandle(theHandle):
|
||||
self._handle = theHandle
|
||||
if isHandle(handle):
|
||||
self._handle = handle
|
||||
else:
|
||||
self._handle = None
|
||||
return
|
||||
|
||||
def setParent(self, theParent):
|
||||
def setParent(self, handle):
|
||||
"""Set the parent handle, and ensure it is valid.
|
||||
"""
|
||||
if theParent is None:
|
||||
if handle is None:
|
||||
self._parent = None
|
||||
elif isHandle(theParent):
|
||||
self._parent = theParent
|
||||
elif isHandle(handle):
|
||||
self._parent = handle
|
||||
else:
|
||||
self._parent = None
|
||||
return
|
||||
|
||||
def setOrder(self, theOrder):
|
||||
def setRoot(self, handle):
|
||||
"""Set the root handle, and ensure it is valid.
|
||||
"""
|
||||
if handle is None:
|
||||
self._root = None
|
||||
elif isHandle(handle):
|
||||
self._root = handle
|
||||
else:
|
||||
self._root = None
|
||||
return
|
||||
|
||||
def setOrder(self, order):
|
||||
"""Set the item order, and ensure that it is valid. This value
|
||||
is purely a meta value, and not actually used by novelWriter at
|
||||
the moment.
|
||||
"""
|
||||
self._order = checkInt(theOrder, 0)
|
||||
self._order = checkInt(order, 0)
|
||||
return
|
||||
|
||||
def setType(self, theType):
|
||||
def setType(self, value):
|
||||
"""Set the item type from either a proper nwItemType, or set it
|
||||
from a string representing an nwItemType.
|
||||
"""
|
||||
if isinstance(theType, nwItemType):
|
||||
self._type = theType
|
||||
elif isItemType(theType):
|
||||
self._type = nwItemType[theType]
|
||||
if isinstance(value, nwItemType):
|
||||
self._type = value
|
||||
elif isItemType(value):
|
||||
self._type = nwItemType[value]
|
||||
elif value == "TRASH":
|
||||
self._type = nwItemType.ROOT
|
||||
else:
|
||||
logger.error("Unrecognised item type '%s'", theType)
|
||||
logger.error("Unrecognised item type '%s'", value)
|
||||
self._type = nwItemType.NO_TYPE
|
||||
return
|
||||
|
||||
def setClass(self, theClass):
|
||||
def setClass(self, value):
|
||||
"""Set the item class from either a proper nwItemClass, or set
|
||||
it from a string representing an nwItemClass.
|
||||
"""
|
||||
if isinstance(theClass, nwItemClass):
|
||||
self._class = theClass
|
||||
elif isItemClass(theClass):
|
||||
self._class = nwItemClass[theClass]
|
||||
if isinstance(value, nwItemClass):
|
||||
self._class = value
|
||||
elif isItemClass(value):
|
||||
self._class = nwItemClass[value]
|
||||
else:
|
||||
logger.error("Unrecognised item class '%s'", theClass)
|
||||
logger.error("Unrecognised item class '%s'", value)
|
||||
self._class = nwItemClass.NO_CLASS
|
||||
return
|
||||
|
||||
def setLayout(self, theLayout):
|
||||
def setLayout(self, value):
|
||||
"""Set the item layout from either a proper nwItemLayout, or set
|
||||
it from a string representing an nwItemLayout.
|
||||
"""
|
||||
if isinstance(theLayout, nwItemLayout):
|
||||
self._layout = theLayout
|
||||
elif isItemLayout(theLayout):
|
||||
self._layout = nwItemLayout[theLayout]
|
||||
elif theLayout in nwLists.DEP_LAYOUT:
|
||||
if isinstance(value, nwItemLayout):
|
||||
self._layout = value
|
||||
elif isItemLayout(value):
|
||||
self._layout = nwItemLayout[value]
|
||||
elif value in ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE"):
|
||||
self._layout = nwItemLayout.DOCUMENT
|
||||
else:
|
||||
logger.error("Unrecognised item layout '%s'", theLayout)
|
||||
logger.error("Unrecognised item layout '%s'", value)
|
||||
self._layout = nwItemLayout.NO_LAYOUT
|
||||
return
|
||||
|
||||
def setStatus(self, theStatus):
|
||||
def setStatus(self, value):
|
||||
"""Set the item status by looking it up in the valid status
|
||||
items of the current project.
|
||||
"""
|
||||
self._status = self.theProject.statusItems.checkEntry(theStatus)
|
||||
self._status = self.theProject.statusItems.check(value)
|
||||
return
|
||||
|
||||
def setImport(self, theImport):
|
||||
def setImport(self, value):
|
||||
"""Set the item importance by looking it up in the valid import
|
||||
items of the current project.
|
||||
"""
|
||||
self._import = self.theProject.importItems.checkEntry(theImport)
|
||||
self._import = self.theProject.importItems.check(value)
|
||||
return
|
||||
|
||||
def setExpanded(self, expState):
|
||||
def setExpanded(self, state):
|
||||
"""Set the expanded status of an item in the project tree.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self._expanded = (expState == str(True))
|
||||
if isinstance(state, str):
|
||||
self._expanded = (state == str(True))
|
||||
else:
|
||||
self._expanded = (expState is True)
|
||||
self._expanded = (state is True)
|
||||
return
|
||||
|
||||
def setExported(self, expState):
|
||||
def setExported(self, state):
|
||||
"""Set the export flag.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self._exported = (expState == str(True))
|
||||
if isinstance(state, str):
|
||||
self._exported = (state == str(True))
|
||||
else:
|
||||
self._exported = (expState is True)
|
||||
self._exported = (state is True)
|
||||
return
|
||||
|
||||
##
|
||||
# Set Document Meta Data
|
||||
##
|
||||
|
||||
def setCharCount(self, theCount):
|
||||
def setCharCount(self, count):
|
||||
"""Set the character count, and ensure that it is an integer.
|
||||
"""
|
||||
self._charCount = max(0, checkInt(theCount, 0))
|
||||
self._charCount = max(0, checkInt(count, 0))
|
||||
return
|
||||
|
||||
def setWordCount(self, theCount):
|
||||
def setWordCount(self, count):
|
||||
"""Set the word count, and ensure that it is an integer.
|
||||
"""
|
||||
self._wordCount = max(0, checkInt(theCount, 0))
|
||||
self._wordCount = max(0, checkInt(count, 0))
|
||||
return
|
||||
|
||||
def setParaCount(self, theCount):
|
||||
def setParaCount(self, count):
|
||||
"""Set the paragraph count, and ensure that it is an integer.
|
||||
"""
|
||||
self._paraCount = max(0, checkInt(theCount, 0))
|
||||
self._paraCount = max(0, checkInt(count, 0))
|
||||
return
|
||||
|
||||
def setCursorPos(self, thePosition):
|
||||
def setCursorPos(self, position):
|
||||
"""Set the cursor position, and ensure that it is an integer.
|
||||
"""
|
||||
self._cursorPos = max(0, checkInt(thePosition, 0))
|
||||
self._cursorPos = max(0, checkInt(position, 0))
|
||||
return
|
||||
|
||||
def saveInitialCount(self):
|
||||
|
||||
@@ -37,6 +37,7 @@ from PyQt5.QtCore import QCoreApplication
|
||||
|
||||
from novelwriter.core.tree import NWTree
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.index import NWIndex
|
||||
from novelwriter.core.status import NWStatus
|
||||
from novelwriter.core.options import OptionState
|
||||
from novelwriter.core.document import NWDoc
|
||||
@@ -44,16 +45,16 @@ from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import (
|
||||
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
|
||||
makeFileNameSafe, hexToInt
|
||||
makeFileNameSafe, hexToInt, simplified
|
||||
)
|
||||
from novelwriter.constants import nwLists, trConst, nwFiles, nwLabels
|
||||
from novelwriter.constants import trConst, nwFiles, nwLabels
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWProject():
|
||||
|
||||
FILE_VERSION = "1.4"
|
||||
FILE_VERSION = "1.4" # The current project file format version
|
||||
|
||||
def __init__(self, theParent):
|
||||
|
||||
@@ -62,9 +63,10 @@ class NWProject():
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
|
||||
# Core Elements
|
||||
self.optState = OptionState(self) # Project-specific GUI options
|
||||
self.projTree = NWTree(self) # The project tree
|
||||
self.langData = {} # Localisation data
|
||||
self._optState = OptionState(self) # Project-specific GUI options
|
||||
self._projTree = NWTree(self) # The project tree
|
||||
self._projIndex = NWIndex(self) # The projecty index
|
||||
self._langData = {} # Localisation data
|
||||
|
||||
# Project Status
|
||||
self.projOpened = 0 # The time stamp of when the project file was opened
|
||||
@@ -87,7 +89,7 @@ class NWProject():
|
||||
self.projFiles = [] # A list of all files in the content folder on load
|
||||
|
||||
# Project Meta
|
||||
self.projName = "" # Project name (working title)
|
||||
self.projName = "" # Project name
|
||||
self.bookTitle = "" # The final title; should only be used for exports
|
||||
self.bookAuthors = [] # A list of book authors
|
||||
|
||||
@@ -116,63 +118,70 @@ class NWProject():
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def index(self):
|
||||
return self._projIndex
|
||||
|
||||
@property
|
||||
def tree(self):
|
||||
return self._projTree
|
||||
|
||||
@property
|
||||
def options(self):
|
||||
return self._optState
|
||||
|
||||
##
|
||||
# Item Methods
|
||||
##
|
||||
|
||||
def newRoot(self, rootName, rootClass):
|
||||
"""Add a new root item. These items are unique, except for item class
|
||||
CUSTOM, and always have parent handle set to None.
|
||||
def newRoot(self, itemClass, label=None):
|
||||
"""Add a new root item. If label is None, use the class label.
|
||||
"""
|
||||
if not self.projTree.checkRootUnique(rootClass):
|
||||
self.theParent.makeAlert(self.tr("Duplicate root item detected."), nwAlert.ERROR)
|
||||
return None
|
||||
|
||||
if label is None:
|
||||
label = trConst(nwLabels.CLASS_NAME[itemClass])
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(rootName)
|
||||
newItem.setName(label)
|
||||
newItem.setType(nwItemType.ROOT)
|
||||
newItem.setClass(rootClass)
|
||||
newItem.setStatus(0)
|
||||
self.projTree.append(None, None, newItem)
|
||||
newItem.setClass(itemClass)
|
||||
self._projTree.append(None, None, newItem)
|
||||
self._projTree.updateItemData(newItem.itemHandle)
|
||||
return newItem.itemHandle
|
||||
|
||||
def newFolder(self, folderName, folderClass, pHandle):
|
||||
"""Add a new folder with a given name and class and parent item.
|
||||
def newFolder(self, label, pHandle):
|
||||
"""Add a new folder with a given label and parent item.
|
||||
"""
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(folderName)
|
||||
newItem.setName(label)
|
||||
newItem.setType(nwItemType.FOLDER)
|
||||
newItem.setClass(folderClass)
|
||||
newItem.setStatus(0)
|
||||
self.projTree.append(None, pHandle, newItem)
|
||||
self._projTree.append(None, pHandle, newItem)
|
||||
self._projTree.updateItemData(newItem.itemHandle)
|
||||
return newItem.itemHandle
|
||||
|
||||
def newFile(self, fileName, fileClass, pHandle):
|
||||
"""Add a new file with a given name and class, and set a layout
|
||||
based on the class. DOCUMENT for NOVEL, otherwise NOTE.
|
||||
def newFile(self, label, pHandle):
|
||||
"""Add a new file with a given label and parent item.
|
||||
"""
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(fileName)
|
||||
newItem.setName(label)
|
||||
newItem.setType(nwItemType.FILE)
|
||||
if fileClass == nwItemClass.NOVEL:
|
||||
newItem.setLayout(nwItemLayout.DOCUMENT)
|
||||
else:
|
||||
newItem.setLayout(nwItemLayout.NOTE)
|
||||
newItem.setClass(fileClass)
|
||||
newItem.setStatus(0)
|
||||
self.projTree.append(None, pHandle, newItem)
|
||||
self._projTree.append(None, pHandle, newItem)
|
||||
self._projTree.updateItemData(newItem.itemHandle)
|
||||
return newItem.itemHandle
|
||||
|
||||
def trashFolder(self):
|
||||
"""Add the special trash root folder to the project.
|
||||
"""
|
||||
trashHandle = self.projTree.trashRoot()
|
||||
trashHandle = self._projTree.trashRoot()
|
||||
if trashHandle is None:
|
||||
newItem = NWItem(self)
|
||||
newItem.setName(trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH]))
|
||||
newItem.setType(nwItemType.TRASH)
|
||||
newItem.setType(nwItemType.ROOT)
|
||||
newItem.setClass(nwItemClass.TRASH)
|
||||
self.projTree.append(None, None, newItem)
|
||||
self._projTree.append(None, None, newItem)
|
||||
self._projTree.updateItemData(newItem.itemHandle)
|
||||
return newItem.itemHandle
|
||||
|
||||
return trashHandle
|
||||
@@ -193,7 +202,7 @@ class NWProject():
|
||||
self.autoCount = 0
|
||||
|
||||
# Project Tree
|
||||
self.projTree.clear()
|
||||
self._projTree.clear()
|
||||
|
||||
# Project Settings
|
||||
self.projPath = None
|
||||
@@ -218,16 +227,16 @@ class NWProject():
|
||||
}
|
||||
self.spellCheck = False
|
||||
self.autoOutline = True
|
||||
self.statusItems = NWStatus()
|
||||
self.statusItems.addEntry(self.tr("New"), (100, 100, 100))
|
||||
self.statusItems.addEntry(self.tr("Note"), (200, 50, 0))
|
||||
self.statusItems.addEntry(self.tr("Draft"), (200, 150, 0))
|
||||
self.statusItems.addEntry(self.tr("Finished"), (50, 200, 0))
|
||||
self.importItems = NWStatus()
|
||||
self.importItems.addEntry(self.tr("New"), (100, 100, 100))
|
||||
self.importItems.addEntry(self.tr("Minor"), (200, 50, 0))
|
||||
self.importItems.addEntry(self.tr("Major"), (200, 150, 0))
|
||||
self.importItems.addEntry(self.tr("Main"), (50, 200, 0))
|
||||
self.statusItems = NWStatus(NWStatus.STATUS)
|
||||
self.statusItems.write(None, self.tr("New"), (100, 100, 100))
|
||||
self.statusItems.write(None, self.tr("Note"), (200, 50, 0))
|
||||
self.statusItems.write(None, self.tr("Draft"), (200, 150, 0))
|
||||
self.statusItems.write(None, self.tr("Finished"), (50, 200, 0))
|
||||
self.importItems = NWStatus(NWStatus.IMPORT)
|
||||
self.importItems.write(None, self.tr("New"), (100, 100, 100))
|
||||
self.importItems.write(None, self.tr("Minor"), (200, 50, 0))
|
||||
self.importItems.write(None, self.tr("Major"), (200, 150, 0))
|
||||
self.importItems.write(None, self.tr("Main"), (50, 200, 0))
|
||||
self.lastEdited = None
|
||||
self.lastViewed = None
|
||||
self.lastWCount = 0
|
||||
@@ -267,6 +276,7 @@ class NWProject():
|
||||
logger.error("No project path set for the new project")
|
||||
return False
|
||||
|
||||
self.clearProject()
|
||||
if not self.setProjectPath(projPath, newProject=True):
|
||||
return False
|
||||
|
||||
@@ -274,86 +284,88 @@ class NWProject():
|
||||
self.setBookTitle(projTitle)
|
||||
self.setBookAuthors(projAuthors)
|
||||
|
||||
hNovelRoot = self.newRoot(nwItemClass.NOVEL)
|
||||
hTitlePage = self.newFile(self.tr("Title Page"), hNovelRoot)
|
||||
|
||||
titlePage = "#! %s\n\n" % (self.bookTitle if self.bookTitle else self.projName)
|
||||
if self.bookAuthors:
|
||||
titlePage = "%s>> %s %s <<\n" % (titlePage, self.tr("By"), self.getAuthors())
|
||||
|
||||
aDoc = NWDoc(self, hTitlePage)
|
||||
aDoc.writeDocument(titlePage)
|
||||
|
||||
if popMinimal:
|
||||
# Creating a minimal project with a few root folders and a
|
||||
# single chapter folder with a single file.
|
||||
xHandle = {}
|
||||
xHandle[1] = self.newRoot(self.tr("Novel"), nwItemClass.NOVEL)
|
||||
xHandle[2] = self.newRoot(self.tr("Plot"), nwItemClass.PLOT)
|
||||
xHandle[3] = self.newRoot(self.tr("Characters"), nwItemClass.CHARACTER)
|
||||
xHandle[4] = self.newRoot(self.tr("World"), nwItemClass.WORLD)
|
||||
xHandle[5] = self.newFile(self.tr("Title Page"), nwItemClass.NOVEL, xHandle[1])
|
||||
xHandle[6] = self.newFolder(self.tr("New Chapter"), nwItemClass.NOVEL, xHandle[1])
|
||||
xHandle[7] = self.newFile(self.tr("New Chapter"), nwItemClass.NOVEL, xHandle[6])
|
||||
xHandle[8] = self.newFile(self.tr("New Scene"), nwItemClass.NOVEL, xHandle[6])
|
||||
|
||||
aDoc = NWDoc(self, xHandle[5])
|
||||
aDoc.writeDocument(titlePage)
|
||||
|
||||
aDoc = NWDoc(self, xHandle[7])
|
||||
# single chapter with a single scene.
|
||||
hChapter = self.newFile(self.tr("New Chapter"), hNovelRoot)
|
||||
aDoc = NWDoc(self, hChapter)
|
||||
aDoc.writeDocument("## %s\n\n" % self.tr("New Chapter"))
|
||||
|
||||
aDoc = NWDoc(self, xHandle[8])
|
||||
hScene = self.newFile(self.tr("New Scene"), hChapter)
|
||||
aDoc = NWDoc(self, hScene)
|
||||
aDoc.writeDocument("### %s\n\n" % self.tr("New Scene"))
|
||||
|
||||
self.newRoot(nwItemClass.PLOT)
|
||||
self.newRoot(nwItemClass.CHARACTER)
|
||||
self.newRoot(nwItemClass.WORLD)
|
||||
self.newRoot(nwItemClass.ARCHIVE)
|
||||
|
||||
elif popCustom:
|
||||
# Create a project structure based on selected root folders
|
||||
# and a number of chapters and scenes selected in the
|
||||
# wizard's custom page.
|
||||
|
||||
# Create root folders
|
||||
nHandle = self.newRoot(self.tr("Novel"), nwItemClass.NOVEL)
|
||||
for newRoot in projData.get("addRoots", []):
|
||||
if newRoot in nwItemClass:
|
||||
self.newRoot(trConst(nwLabels.CLASS_NAME[newRoot]), newRoot)
|
||||
|
||||
# Create a title page
|
||||
tHandle = self.newFile(self.tr("Title Page"), nwItemClass.NOVEL, nHandle)
|
||||
self.projTree.setFileItemLayout(tHandle, nwItemLayout.DOCUMENT)
|
||||
|
||||
aDoc = NWDoc(self, tHandle)
|
||||
aDoc.writeDocument(titlePage)
|
||||
|
||||
# Create chapters and scenes
|
||||
numChapters = projData.get("numChapters", 0)
|
||||
numScenes = projData.get("numScenes", 0)
|
||||
chFolders = projData.get("chFolders", False)
|
||||
|
||||
chSynop = self.tr("Summary of the chapter.")
|
||||
scSynop = self.tr("Summary of the scene.")
|
||||
|
||||
# Create chapters
|
||||
if numChapters > 0:
|
||||
for ch in range(numChapters):
|
||||
chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}")
|
||||
pHandle = nHandle
|
||||
if chFolders:
|
||||
pHandle = self.newFolder(chTitle, nwItemClass.NOVEL, nHandle)
|
||||
|
||||
cHandle = self.newFile(chTitle, nwItemClass.NOVEL, pHandle)
|
||||
self.projTree.setFileItemLayout(cHandle, nwItemLayout.DOCUMENT)
|
||||
|
||||
cHandle = self.newFile(chTitle, hNovelRoot)
|
||||
aDoc = NWDoc(self, cHandle)
|
||||
aDoc.writeDocument("## %s\n\n" % chTitle)
|
||||
aDoc.writeDocument(f"## {chTitle}\n\n% Synopsis: {chSynop}\n\n")
|
||||
|
||||
# Create chapter scenes
|
||||
if numScenes > 0:
|
||||
for sc in range(numScenes):
|
||||
scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}")
|
||||
sHandle = self.newFile(scTitle, nwItemClass.NOVEL, pHandle)
|
||||
|
||||
sHandle = self.newFile(scTitle, cHandle)
|
||||
aDoc = NWDoc(self, sHandle)
|
||||
aDoc.writeDocument("### %s\n\n" % scTitle)
|
||||
aDoc.writeDocument(f"### {scTitle}\n\n% Synopsis: {scSynop}\n\n")
|
||||
|
||||
# Create scenes (no chapters)
|
||||
elif numScenes > 0:
|
||||
for sc in range(numScenes):
|
||||
scTitle = self.tr("Scene {0}").format(f"{sc+1:d}")
|
||||
sHandle = self.newFile(scTitle, nwItemClass.NOVEL, nHandle)
|
||||
|
||||
sHandle = self.newFile(scTitle, hNovelRoot)
|
||||
aDoc = NWDoc(self, sHandle)
|
||||
aDoc.writeDocument("### %s\n\n" % scTitle)
|
||||
aDoc.writeDocument(f"### {scTitle}\n\n% Synopsis: {scSynop}\n\n")
|
||||
|
||||
# Create notes folders
|
||||
noteTitles = {
|
||||
nwItemClass.PLOT: self.tr("Main Plot"),
|
||||
nwItemClass.CHARACTER: self.tr("Protagonist"),
|
||||
nwItemClass.WORLD: self.tr("Main Location"),
|
||||
}
|
||||
|
||||
addNotes = projData.get("addNotes", False)
|
||||
for newRoot in projData.get("addRoots", []):
|
||||
if newRoot in nwItemClass:
|
||||
rHandle = self.newRoot(newRoot)
|
||||
if addNotes:
|
||||
aHandle = self.newFile(noteTitles[newRoot], rHandle)
|
||||
ntTag = simplified(noteTitles[newRoot]).replace(" ", "")
|
||||
aDoc = NWDoc(self, aHandle)
|
||||
aDoc.writeDocument(f"# {noteTitles[newRoot]}\n\n@tag: {ntTag}\n\n")
|
||||
|
||||
# Also add the archive and trash folders
|
||||
self.newRoot(nwItemClass.ARCHIVE)
|
||||
self.trashFolder()
|
||||
|
||||
# Finalise
|
||||
if popCustom or popMinimal:
|
||||
@@ -480,8 +492,9 @@ class NWProject():
|
||||
# documents and one for project notes. Introduced in
|
||||
# version 1.5.
|
||||
# 1.4 : Introduces a more compact format for storing items. All
|
||||
# settings aside from name are now attributes. Introduced
|
||||
# in version 1.7.
|
||||
# settings aside from name are now attributes. This format
|
||||
# also changes the way satus and importance labels are
|
||||
# stored and handled. Introduced in version 1.7.
|
||||
|
||||
if fileVersion not in ("1.0", "1.1", "1.2", "1.3", "1.4"):
|
||||
self.theParent.makeAlert(self.tr(
|
||||
@@ -533,14 +546,16 @@ class NWProject():
|
||||
if xItem.text is None:
|
||||
continue
|
||||
if xItem.tag == "name":
|
||||
logger.verbose("Working Title: '%s'", xItem.text)
|
||||
self.projName = xItem.text
|
||||
self.projName = checkString(simplified(xItem.text), "")
|
||||
logger.verbose("Working Title: '%s'", self.projName)
|
||||
elif xItem.tag == "title":
|
||||
logger.verbose("Title is '%s'", xItem.text)
|
||||
self.bookTitle = xItem.text
|
||||
self.bookTitle = checkString(simplified(xItem.text), "")
|
||||
logger.verbose("Title is '%s'", self.bookTitle)
|
||||
elif xItem.tag == "author":
|
||||
logger.verbose("Author: '%s'", xItem.text)
|
||||
self.bookAuthors.append(xItem.text)
|
||||
author = checkString(simplified(xItem.text), "")
|
||||
if author:
|
||||
self.bookAuthors.append(author)
|
||||
logger.verbose("Author: '%s'", author)
|
||||
elif xItem.tag == "saveCount":
|
||||
self.saveCount = checkInt(xItem.text, 0)
|
||||
elif xItem.tag == "autoCount":
|
||||
@@ -591,9 +606,9 @@ class NWProject():
|
||||
|
||||
elif xChild.tag == "content":
|
||||
logger.debug("Found project content")
|
||||
self.projTree.unpackXML(xChild)
|
||||
self._projTree.unpackXML(xChild)
|
||||
|
||||
self.optState.loadSettings()
|
||||
self._optState.loadSettings()
|
||||
|
||||
# Sort out old file locations
|
||||
if legacyList:
|
||||
@@ -610,7 +625,13 @@ class NWProject():
|
||||
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
|
||||
self.mainConf.saveRecentCache()
|
||||
|
||||
self.theParent.setStatus(self.tr("Opened Project: {0}").format(self.projName))
|
||||
# Check the project tree consistency
|
||||
for tItem in self._projTree:
|
||||
tHandle = tItem.itemHandle
|
||||
logger.verbose("Checking item '%s'", tHandle)
|
||||
if not self._projTree.updateItemData(tHandle):
|
||||
logger.error("There was a problem item '%s', and it has been removed", tHandle)
|
||||
del self._projTree[tHandle] # The file will be re-added as orphaned
|
||||
|
||||
self._scanProjectFolder()
|
||||
self._loadProjectLocalisation()
|
||||
@@ -621,6 +642,7 @@ class NWProject():
|
||||
|
||||
self._writeLockFile()
|
||||
self.setProjectChanged(False)
|
||||
self.theParent.setStatus(self.tr("Opened Project: {0}").format(self.projName))
|
||||
|
||||
return True
|
||||
|
||||
@@ -687,6 +709,8 @@ class NWProject():
|
||||
if len(aKey) > 0:
|
||||
self._packProjectValue(xTitleFmt, aKey, aValue)
|
||||
|
||||
# Save Status/Importance
|
||||
self.countStatus()
|
||||
xStatus = etree.SubElement(xSettings, "status")
|
||||
self.statusItems.packXML(xStatus)
|
||||
xStatus = etree.SubElement(xSettings, "importance")
|
||||
@@ -694,7 +718,7 @@ class NWProject():
|
||||
|
||||
# Save Tree Content
|
||||
logger.debug("Writing project content")
|
||||
self.projTree.packXML(nwXML)
|
||||
self._projTree.packXML(nwXML)
|
||||
|
||||
# Write the xml tree to file
|
||||
tempFile = os.path.join(self.projPath, self.projFile+"~")
|
||||
@@ -727,7 +751,7 @@ class NWProject():
|
||||
return False
|
||||
|
||||
# Save project GUI options
|
||||
self.optState.saveSettings()
|
||||
self._optState.saveSettings()
|
||||
|
||||
# Update recent projects
|
||||
self.mainConf.updateRecentCache(self.projPath, self.projName, self.currWCount, saveTime)
|
||||
@@ -743,8 +767,8 @@ class NWProject():
|
||||
"""Close the current project and clear all meta data.
|
||||
"""
|
||||
logger.info("Closing project: %s", self.projPath)
|
||||
self.optState.saveSettings()
|
||||
self.projTree.writeToCFile()
|
||||
self._optState.saveSettings()
|
||||
self._projTree.writeToCFile()
|
||||
self._appendSessionStats(idleTime)
|
||||
self._clearLockFile()
|
||||
self.clearProject()
|
||||
@@ -950,17 +974,17 @@ class NWProject():
|
||||
return True
|
||||
|
||||
def setProjectName(self, projName):
|
||||
"""Set the project name (working title), This is the the title
|
||||
used for backup files etc.
|
||||
"""Set the project name, This is the the name used for backup
|
||||
files etc.
|
||||
"""
|
||||
self.projName = projName.strip()
|
||||
self.projName = simplified(projName)
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
def setBookTitle(self, bookTitle):
|
||||
"""Set the book title, that is, the title to include in exports.
|
||||
"""
|
||||
self.bookTitle = bookTitle.strip()
|
||||
self.bookTitle = simplified(bookTitle)
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
@@ -972,7 +996,7 @@ class NWProject():
|
||||
|
||||
self.bookAuthors = []
|
||||
for bookAuthor in bookAuthors.splitlines():
|
||||
bookAuthor = bookAuthor.strip()
|
||||
bookAuthor = simplified(bookAuthor)
|
||||
if bookAuthor == "":
|
||||
continue
|
||||
self.bookAuthors.append(bookAuthor)
|
||||
@@ -1018,7 +1042,8 @@ class NWProject():
|
||||
if self.projSpell != theLang:
|
||||
self.projSpell = theLang
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
return True
|
||||
return False
|
||||
|
||||
def setProjectLang(self, theLang):
|
||||
"""Set the project-specific language.
|
||||
@@ -1043,9 +1068,9 @@ class NWProject():
|
||||
items in the GUI project tree. The user can rearrange the order
|
||||
by drag-and-drop. Forwarded to the NWTree class.
|
||||
"""
|
||||
if len(self.projTree) != len(newOrder):
|
||||
if len(self._projTree) != len(newOrder):
|
||||
logger.warning("Sizes of new and old tree order do not match")
|
||||
self.projTree.setOrder(newOrder)
|
||||
self._projTree.setOrder(newOrder)
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
@@ -1065,34 +1090,22 @@ class NWProject():
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
def setStatusColours(self, newCols):
|
||||
"""Update the list of novel file status flags. Also iterate
|
||||
through the project and replace keys that have been renamed.
|
||||
def setStatusColours(self, newCols, delCols):
|
||||
"""Update the list of novel file status flags.
|
||||
"""
|
||||
replaceMap = self.statusItems.setNewEntries(newCols)
|
||||
for nwItem in self.projTree:
|
||||
if nwItem.itemClass in nwLists.CLS_NOVEL:
|
||||
if nwItem.itemStatus in replaceMap:
|
||||
nwItem.setStatus(replaceMap[nwItem.itemStatus])
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
return self._setStatusImport(newCols, delCols, self.statusItems)
|
||||
|
||||
def setImportColours(self, newCols):
|
||||
"""Update the list of note file importance flags. Also iterate
|
||||
through the project and replace keys that have been renamed.
|
||||
def setImportColours(self, newCols, delCols):
|
||||
"""Update the list of note file importance flags.
|
||||
"""
|
||||
replaceMap = self.importItems.setNewEntries(newCols)
|
||||
for nwItem in self.projTree:
|
||||
if nwItem.itemClass not in nwLists.CLS_NOVEL:
|
||||
if nwItem.itemImport in replaceMap:
|
||||
nwItem.setImport(replaceMap[nwItem.itemImport])
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
return self._setStatusImport(newCols, delCols, self.importItems)
|
||||
|
||||
def setAutoReplace(self, autoReplace):
|
||||
"""Update the auto-replace dictionary.
|
||||
"""
|
||||
self.autoReplace = autoReplace
|
||||
self.autoReplace = {}
|
||||
for key, entry in autoReplace.items():
|
||||
self.autoReplace[key] = simplified(entry)
|
||||
self.setProjectChanged(True)
|
||||
return True
|
||||
|
||||
@@ -1101,7 +1114,9 @@ class NWProject():
|
||||
"""
|
||||
for valKey, valEntry in titleFormat.items():
|
||||
if valKey in self.titleFormat:
|
||||
self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey])
|
||||
self.titleFormat[valKey] = checkString(
|
||||
simplified(valEntry), self.titleFormat[valKey]
|
||||
)
|
||||
return True
|
||||
|
||||
def setProjectChanged(self, bValue):
|
||||
@@ -1149,16 +1164,16 @@ class NWProject():
|
||||
capable of handling it.
|
||||
"""
|
||||
sentItems = []
|
||||
iterItems = self.projTree.handles()
|
||||
iterItems = self._projTree.handles()
|
||||
n = 0
|
||||
nMax = min(len(iterItems), 10000)
|
||||
while n < nMax:
|
||||
tHandle = iterItems[n]
|
||||
tItem = self.projTree[tHandle]
|
||||
tItem = self._projTree[tHandle]
|
||||
n += 1
|
||||
if tItem is None:
|
||||
# Technically a bug since treeOrder is built from the
|
||||
# same data as projTree
|
||||
# same data as _projTree
|
||||
continue
|
||||
elif tItem.itemParent is None:
|
||||
# Item is a root, or already been identified as an
|
||||
@@ -1189,7 +1204,7 @@ class NWProject():
|
||||
def updateWordCounts(self):
|
||||
"""Update the total word count values.
|
||||
"""
|
||||
wcNovel, wcNotes = self.projTree.sumWords()
|
||||
wcNovel, wcNotes = self._projTree.sumWords()
|
||||
wcTotal = wcNovel + wcNotes
|
||||
if wcTotal != self.currWCount:
|
||||
self.currNovelWC = wcNovel
|
||||
@@ -1205,11 +1220,11 @@ class NWProject():
|
||||
"""
|
||||
self.statusItems.resetCounts()
|
||||
self.importItems.resetCounts()
|
||||
for nwItem in self.projTree:
|
||||
if nwItem.itemClass in nwLists.CLS_NOVEL:
|
||||
self.statusItems.countEntry(nwItem.itemStatus)
|
||||
for nwItem in self._projTree:
|
||||
if nwItem.isNovelLike():
|
||||
self.statusItems.increment(nwItem.itemStatus)
|
||||
else:
|
||||
self.importItems.countEntry(nwItem.itemImport)
|
||||
self.importItems.increment(nwItem.itemImport)
|
||||
return
|
||||
|
||||
def localLookup(self, theWord):
|
||||
@@ -1217,17 +1232,39 @@ class NWProject():
|
||||
return it. The variable is cast to a string before lookup. If
|
||||
the word does not exist, it returns itself.
|
||||
"""
|
||||
return self.langData.get(str(theWord), str(theWord))
|
||||
return self._langData.get(str(theWord), str(theWord))
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _setStatusImport(self, new, delete, target):
|
||||
"""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):
|
||||
"""Load the language data for the current project language.
|
||||
"""
|
||||
if self.projLang is None:
|
||||
self.langData = {}
|
||||
self._langData = {}
|
||||
return False
|
||||
|
||||
langFile = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % self.projLang)
|
||||
@@ -1236,7 +1273,7 @@ class NWProject():
|
||||
|
||||
try:
|
||||
with open(langFile, mode="r", encoding="utf-8") as inFile:
|
||||
self.langData = json.load(inFile)
|
||||
self._langData = json.load(inFile)
|
||||
logger.debug("Loaded project language file: %s", os.path.basename(langFile))
|
||||
|
||||
except Exception:
|
||||
@@ -1371,7 +1408,7 @@ class NWProject():
|
||||
logger.warning("Skipping file: %s", fileItem)
|
||||
continue
|
||||
|
||||
if fHandle in self.projTree:
|
||||
if fHandle in self._projTree:
|
||||
self.projFiles.append(fHandle)
|
||||
logger.debug("Checking file %s, handle '%s': OK", fileItem, fHandle)
|
||||
else:
|
||||
@@ -1418,10 +1455,10 @@ class NWProject():
|
||||
if oLayout is None:
|
||||
oLayout = nwItemLayout.NOTE
|
||||
|
||||
if oParent is None or oParent not in self.projTree:
|
||||
oParent = self.projTree.findRoot(oClass)
|
||||
if oParent is None or oParent not in self._projTree:
|
||||
oParent = self._projTree.findRoot(oClass)
|
||||
if oParent is None:
|
||||
oParent = self.projTree.findRoot(nwItemClass.NOVEL)
|
||||
oParent = self._projTree.findRoot(nwItemClass.NOVEL)
|
||||
|
||||
# If the file still has no parent item, skip it
|
||||
if oParent is None:
|
||||
@@ -1433,7 +1470,8 @@ class NWProject():
|
||||
orphItem.setType(nwItemType.FILE)
|
||||
orphItem.setClass(oClass)
|
||||
orphItem.setLayout(oLayout)
|
||||
self.projTree.append(oHandle, oParent, orphItem)
|
||||
self._projTree.append(oHandle, oParent, orphItem)
|
||||
self._projTree.updateItemData(orphItem.itemHandle)
|
||||
|
||||
if noWhere:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
|
||||
@@ -4,7 +4,8 @@ novelWriter – Project Item Status Class
|
||||
Data class for the status/importance settings of a project item
|
||||
|
||||
File History:
|
||||
Created: 2019-05-19 [0.1.3]
|
||||
Created: 2019-05-19 [0.1.3]
|
||||
Rewritten: 2022-04-05 [1.7a0]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
||||
@@ -23,6 +24,7 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import random
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
@@ -30,134 +32,208 @@ from lxml import etree
|
||||
|
||||
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
||||
|
||||
from novelwriter.common import checkInt
|
||||
from novelwriter.common import checkInt, minmax, simplified
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWStatus():
|
||||
|
||||
def __init__(self):
|
||||
STATUS = 1
|
||||
IMPORT = 2
|
||||
|
||||
def __init__(self, type):
|
||||
|
||||
self._type = type
|
||||
self._store = {}
|
||||
self._reverse = {}
|
||||
self._default = None
|
||||
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theIcons = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
self._iconSize = novelwriter.CONFIG.pxInt(32)
|
||||
pixmap = QPixmap(self._iconSize, self._iconSize)
|
||||
pixmap.fill(QColor(100, 100, 100))
|
||||
self._defaultIcon = QIcon(pixmap)
|
||||
|
||||
if self._type == self.STATUS:
|
||||
self._prefix = "s"
|
||||
elif self._type == self.IMPORT:
|
||||
self._prefix = "i"
|
||||
else:
|
||||
raise Exception("This is a bug!")
|
||||
|
||||
return
|
||||
|
||||
def addEntry(self, theLabel, theColours):
|
||||
"""Add a status entry to the status object, but ensure it isn't
|
||||
a duplicate.
|
||||
def write(self, key, name, cols, count=None):
|
||||
"""Add or update a status entry. If the key is invalid, a new
|
||||
key is generated.
|
||||
"""
|
||||
theLabel = theLabel.strip()
|
||||
if self._getIndex(theLabel) is None:
|
||||
theIcon = QPixmap(self._iconSize, self._iconSize)
|
||||
theIcon.fill(QColor(*theColours))
|
||||
self._theIcons.append(QIcon(theIcon))
|
||||
self._theLabels.append(theLabel)
|
||||
self._theColours.append(theColours)
|
||||
self._theCounts.append(0)
|
||||
self._theMap[theLabel] = self._theLength
|
||||
self._theLength += 1
|
||||
if not self._isKey(key):
|
||||
key = self._newKey()
|
||||
if not isinstance(cols, tuple):
|
||||
cols = (100, 100, 100)
|
||||
if len(cols) != 3:
|
||||
cols = (100, 100, 100)
|
||||
|
||||
pixmap = QPixmap(self._iconSize, self._iconSize)
|
||||
pixmap.fill(QColor(*cols))
|
||||
|
||||
name = simplified(name)
|
||||
if count is None:
|
||||
count = self._store[key]["count"] if key in self._store else 0
|
||||
|
||||
self._store[key] = {
|
||||
"name": name,
|
||||
"icon": QIcon(pixmap),
|
||||
"cols": cols,
|
||||
"count": count,
|
||||
}
|
||||
self._reverse[name] = key
|
||||
|
||||
if self._default is None:
|
||||
self._default = key
|
||||
|
||||
return key
|
||||
|
||||
def remove(self, key):
|
||||
"""Remove an entry in the list, but not if the count is larger
|
||||
than 0.
|
||||
"""
|
||||
if key not in self._store:
|
||||
return False
|
||||
if self._store[key]["count"] > 0:
|
||||
return False
|
||||
|
||||
del self._reverse[self._store[key]["name"]]
|
||||
del self._store[key]
|
||||
|
||||
keys = list(self._store.keys())
|
||||
if key == self._default:
|
||||
if len(keys) > 0:
|
||||
self._default = keys[0]
|
||||
else:
|
||||
self._default = None
|
||||
|
||||
return True
|
||||
|
||||
def checkEntry(self, theStatus):
|
||||
"""Check if a status value is valid, and returns the safe
|
||||
reference to be used internally.
|
||||
def check(self, value):
|
||||
"""Check the key against the stored status names.
|
||||
"""
|
||||
if isinstance(theStatus, str):
|
||||
if self._getIndex(theStatus) is not None:
|
||||
return theStatus.strip()
|
||||
return self._theLabels[0]
|
||||
if self._isKey(value) and value in self._store:
|
||||
return value
|
||||
elif value in self._reverse:
|
||||
return self._reverse[value]
|
||||
elif self._default is not None:
|
||||
return self._default
|
||||
else:
|
||||
return ""
|
||||
|
||||
def getIcon(self, theLabel):
|
||||
"""Return the icon for the given status item.
|
||||
def name(self, key):
|
||||
"""Return the name associated with a given key.
|
||||
"""
|
||||
theIndex = self._getIndex(theLabel)
|
||||
if theIndex is not None:
|
||||
return self._theIcons[theIndex]
|
||||
return QIcon()
|
||||
if key in self._store:
|
||||
return self._store[key]["name"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["name"]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def setNewEntries(self, newList):
|
||||
"""Update the list of entries after they have been modified by
|
||||
the GUI tool.
|
||||
def cols(self, key):
|
||||
"""Return the colours associated with a given key.
|
||||
"""
|
||||
replaceMap = {}
|
||||
if key in self._store:
|
||||
return self._store[key]["cols"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["cols"]
|
||||
else:
|
||||
return (100, 100, 100)
|
||||
|
||||
if newList is not None:
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theIcons = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
def count(self, key):
|
||||
"""Return the count associated with a given key.
|
||||
"""
|
||||
if key in self._store:
|
||||
return self._store[key]["count"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["count"]
|
||||
else:
|
||||
return 0
|
||||
|
||||
for nName, nR, nG, nB, oName in newList:
|
||||
self.addEntry(nName, (nR, nG, nB))
|
||||
if nName != oName and oName is not None:
|
||||
replaceMap[oName] = nName
|
||||
def icon(self, key):
|
||||
"""Return the icon associated with a given key.
|
||||
"""
|
||||
if key in self._store:
|
||||
return self._store[key]["icon"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["icon"]
|
||||
else:
|
||||
return self._defaultIcon
|
||||
|
||||
return replaceMap
|
||||
def reorder(self, order):
|
||||
"""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):
|
||||
"""Clear the counts of references to the status entries.
|
||||
"""
|
||||
self._theCounts = [0]*self._theLength
|
||||
for key in self._store:
|
||||
self._store[key]["count"] = 0
|
||||
return
|
||||
|
||||
def countEntry(self, theLabel):
|
||||
"""Increment the counter for a given label. This should be used
|
||||
together with resetCounts in a loop over project items.
|
||||
def increment(self, key):
|
||||
"""Increment the counter for a given entry.
|
||||
"""
|
||||
theIndex = self._getIndex(theLabel)
|
||||
if theIndex is not None:
|
||||
self._theCounts[theIndex] += 1
|
||||
if key in self._store:
|
||||
self._store[key]["count"] += 1
|
||||
return
|
||||
|
||||
def packXML(self, xParent):
|
||||
"""Pack the status entries into an XML object for saving to the
|
||||
main project file.
|
||||
"""
|
||||
for n in range(self._theLength):
|
||||
for key, data in self._store.items():
|
||||
xSub = etree.SubElement(xParent, "entry", attrib={
|
||||
"red": str(self._theColours[n][0]),
|
||||
"green": str(self._theColours[n][1]),
|
||||
"blue": str(self._theColours[n][2]),
|
||||
"key": key,
|
||||
"count": str(data["count"]),
|
||||
"red": str(data["cols"][0]),
|
||||
"green": str(data["cols"][1]),
|
||||
"blue": str(data["cols"][2]),
|
||||
})
|
||||
xSub.text = self._theLabels[n]
|
||||
xSub.text = data["name"]
|
||||
|
||||
return True
|
||||
|
||||
def unpackXML(self, xParent):
|
||||
"""Unpack an XML tree and set the class values.
|
||||
"""
|
||||
theLabels = []
|
||||
theColours = []
|
||||
self._store = {}
|
||||
self._reverse = {}
|
||||
self._default = None
|
||||
|
||||
for xChild in xParent:
|
||||
theLabels.append(xChild.text)
|
||||
cR = checkInt(xChild.attrib.get("red", 0), 0, False)
|
||||
cG = checkInt(xChild.attrib.get("green", 0), 0, False)
|
||||
cB = checkInt(xChild.attrib.get("blue", 0), 0, False)
|
||||
theColours.append((cR, cG, cB))
|
||||
|
||||
if len(theLabels) > 0:
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theIcons = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
|
||||
for n in range(len(theLabels)):
|
||||
self.addEntry(theLabels[n], theColours[n])
|
||||
key = xChild.attrib.get("key", None)
|
||||
name = xChild.text.strip()
|
||||
count = max(checkInt(xChild.attrib.get("count", 0), 0), 0)
|
||||
red = minmax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255)
|
||||
green = minmax(checkInt(xChild.attrib.get("green", 100), 100), 0, 255)
|
||||
blue = minmax(checkInt(xChild.attrib.get("blue", 100), 100), 0, 255)
|
||||
self.write(key, name, (red, green, blue), count)
|
||||
|
||||
return True
|
||||
|
||||
@@ -165,39 +241,52 @@ class NWStatus():
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _getIndex(self, theLabel):
|
||||
"""Look up a status entry in the object lists, and return it if
|
||||
it exists.
|
||||
def _newKey(self):
|
||||
"""Generate a new key for a status flag. This method is
|
||||
recursive, but should only fail if there is an issue with the
|
||||
random number generator or the user has added a lot of status
|
||||
flags. The Python recursion limit is given the job to handle
|
||||
the extreme case and will cause an app crash.
|
||||
"""
|
||||
if theLabel is None:
|
||||
return None
|
||||
return self._theMap.get(theLabel.strip(), None)
|
||||
key = f"{self._prefix}{random.getrandbits(24):06x}"
|
||||
if key in self._store:
|
||||
key = self._newKey()
|
||||
return key
|
||||
|
||||
def _isKey(self, value):
|
||||
"""Check if a value is a key or not.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
if len(value) != 7:
|
||||
return False
|
||||
if value[0] != self._prefix:
|
||||
return False
|
||||
for c in value[1:]:
|
||||
if c not in "0123456789abcdef":
|
||||
return False
|
||||
return True
|
||||
|
||||
##
|
||||
# Iterator Bits
|
||||
##
|
||||
|
||||
def __getitem__(self, n):
|
||||
"""Return an entry by its index.
|
||||
"""
|
||||
if n >= 0 and n < self._theLength:
|
||||
return self._theLabels[n], self._theColours[n], self._theCounts[n], self._theIcons[n]
|
||||
return None, None, None, QIcon()
|
||||
def __len__(self):
|
||||
return len(self._store)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._store[key]
|
||||
|
||||
def __iter__(self):
|
||||
"""Initialise the iterator.
|
||||
"""
|
||||
self._theIndex = 0
|
||||
return self
|
||||
return iter(self._store)
|
||||
|
||||
def __next__(self):
|
||||
"""Return the next entry for the iterator.
|
||||
"""
|
||||
if self._theIndex < self._theLength:
|
||||
theLabel, theColour, theCount, theIcon = self.__getitem__(self._theIndex)
|
||||
self._theIndex += 1
|
||||
return theLabel, theColour, theCount, theIcon
|
||||
else:
|
||||
raise StopIteration
|
||||
def keys(self):
|
||||
return self._store.keys()
|
||||
|
||||
def items(self):
|
||||
return self._store.items()
|
||||
|
||||
def values(self):
|
||||
return self._store.values()
|
||||
|
||||
# END Class NWStatus
|
||||
|
||||
@@ -451,7 +451,7 @@ class ToHtml(Tokenizer):
|
||||
def _formatKeywords(self, tText):
|
||||
"""Apply HTML formatting to keywords.
|
||||
"""
|
||||
isValid, theBits, _ = self.theParent.theIndex.scanThis("@"+tText)
|
||||
isValid, theBits, _ = self.theProject.index.scanThis("@"+tText)
|
||||
if not isValid or not theBits:
|
||||
return ""
|
||||
|
||||
|
||||
@@ -275,7 +275,7 @@ class Tokenizer(ABC):
|
||||
def addRootHeading(self, theHandle):
|
||||
"""Add a heading at the start of a new root folder.
|
||||
"""
|
||||
if not self.theProject.projTree.checkType(theHandle, nwItemType.ROOT):
|
||||
if not self.theProject.tree.checkType(theHandle, nwItemType.ROOT):
|
||||
return False
|
||||
|
||||
if self._isFirst:
|
||||
@@ -284,7 +284,7 @@ class Tokenizer(ABC):
|
||||
else:
|
||||
textAlign = self.A_PBB | self.A_CENTRE
|
||||
|
||||
theItem = self.theProject.projTree[theHandle]
|
||||
theItem = self.theProject.tree[theHandle]
|
||||
locNotes = self._localLookup("Notes")
|
||||
theTitle = f"{locNotes}: {theItem.itemName}"
|
||||
self._theTokens = []
|
||||
@@ -301,7 +301,7 @@ class Tokenizer(ABC):
|
||||
not set, load it from the file.
|
||||
"""
|
||||
self._theHandle = theHandle
|
||||
self._theItem = self.theProject.projTree[theHandle]
|
||||
self._theItem = self.theProject.tree[theHandle]
|
||||
if self._theItem is None:
|
||||
return False
|
||||
|
||||
|
||||
@@ -193,7 +193,7 @@ class ToMarkdown(Tokenizer):
|
||||
def _formatKeywords(self, tText, tStyle):
|
||||
"""Apply Markdown formatting to keywords.
|
||||
"""
|
||||
isValid, theBits, _ = self.theParent.theIndex.scanThis("@"+tText)
|
||||
isValid, theBits, _ = self.theProject.index.scanThis("@"+tText)
|
||||
if not isValid or not theBits:
|
||||
return ""
|
||||
|
||||
|
||||
@@ -550,7 +550,7 @@ class ToOdt(Tokenizer):
|
||||
def _formatKeywords(self, tText):
|
||||
"""Apply formatting to keywords.
|
||||
"""
|
||||
isValid, theBits, _ = self.theParent.theIndex.scanThis("@"+tText)
|
||||
isValid, theBits, _ = self.theProject.index.scanThis("@"+tText)
|
||||
if not isValid or not theBits:
|
||||
return ""
|
||||
|
||||
|
||||
@@ -24,16 +24,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import logging
|
||||
|
||||
from time import time
|
||||
from lxml import etree
|
||||
from hashlib import sha256
|
||||
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import checkHandle
|
||||
from novelwriter.constants import nwConst, nwFiles
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.core.item import NWItem
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -41,21 +40,20 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class NWTree():
|
||||
|
||||
MAX_DEPTH = 1000 # Cap of tree traversing for loops
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.theProject = theProject
|
||||
|
||||
self._projTree = {} # Holds all the items of the project
|
||||
self._treeOrder = [] # The order of the tree items on the tree view
|
||||
self._treeRoots = [] # The root items of the tree
|
||||
self._treeRoots = {} # The root items of the tree
|
||||
self._trashRoot = None # The handle of the trash root folder
|
||||
self._archRoot = None # The handle of the archive root folder
|
||||
self._theIndex = 0 # The current iterator index
|
||||
self._treeChanged = False # True if tree structure has changed
|
||||
|
||||
self._handleSeed = None # Used for generating handles for testing
|
||||
self._handleCount = 0 # A counter that is added to the handle generator
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
@@ -67,7 +65,7 @@ class NWTree():
|
||||
"""
|
||||
self._projTree = {}
|
||||
self._treeOrder = []
|
||||
self._treeRoots = []
|
||||
self._treeRoots = {}
|
||||
self._trashRoot = None
|
||||
self._archRoot = None
|
||||
self._theIndex = 0
|
||||
@@ -98,18 +96,17 @@ class NWTree():
|
||||
|
||||
if nwItem.itemType == nwItemType.ROOT:
|
||||
logger.verbose("Item '%s' is a root item", str(tHandle))
|
||||
self._treeRoots.append(tHandle)
|
||||
self._treeRoots[tHandle] = nwItem
|
||||
if nwItem.itemClass == nwItemClass.ARCHIVE:
|
||||
logger.verbose("Item '%s' is the archive folder", str(tHandle))
|
||||
self._archRoot = tHandle
|
||||
|
||||
if nwItem.itemType == nwItemType.TRASH:
|
||||
if self._trashRoot is None:
|
||||
logger.verbose("Item '%s' is the trash folder", str(tHandle))
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
return False
|
||||
elif nwItem.itemClass == nwItemClass.TRASH:
|
||||
if self._trashRoot is None:
|
||||
logger.verbose("Item '%s' is the trash folder", str(tHandle))
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
return False
|
||||
|
||||
self._projTree[tHandle] = nwItem
|
||||
self._treeOrder.append(tHandle)
|
||||
@@ -207,9 +204,30 @@ class NWTree():
|
||||
return novelWords, noteWords
|
||||
|
||||
##
|
||||
# Tree Structure Methods
|
||||
# Tree Item Methods
|
||||
##
|
||||
|
||||
def updateItemData(self, tHandle):
|
||||
"""Update the root item handle of a given item. Returns True if
|
||||
a root was found and data updated, otherwise False.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return False
|
||||
|
||||
iItem = tItem
|
||||
for _ in range(self.MAX_DEPTH):
|
||||
if iItem.itemParent is None:
|
||||
tItem.setRoot(iItem.itemHandle)
|
||||
tItem.setClassDefaults(iItem.itemClass)
|
||||
return True
|
||||
else:
|
||||
iItem = self.__getitem__(iItem.itemParent)
|
||||
if iItem is None:
|
||||
return False
|
||||
else:
|
||||
raise RecursionError("Critical internal error")
|
||||
|
||||
def checkType(self, tHandle, itemType):
|
||||
"""Return true of item exists and is of the specified item type.
|
||||
"""
|
||||
@@ -218,71 +236,6 @@ class NWTree():
|
||||
return False
|
||||
return tItem.itemType == itemType
|
||||
|
||||
def trashRoot(self):
|
||||
"""Returns the handle of the trash folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._trashRoot:
|
||||
return self._trashRoot
|
||||
return None
|
||||
|
||||
def isTrashRoot(self, tHandle):
|
||||
"""Check if a handle is the trash folder.
|
||||
"""
|
||||
if self._trashRoot is None:
|
||||
return False
|
||||
return tHandle == self._trashRoot
|
||||
|
||||
def archiveRoot(self):
|
||||
"""Returns the handle of the archive folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._archRoot:
|
||||
return self._archRoot
|
||||
return None
|
||||
|
||||
def findRoot(self, theClass):
|
||||
"""Find the root item for a given class.
|
||||
Note: This returns the first item for class CUSTOM.
|
||||
"""
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return tItem.itemHandle
|
||||
return None
|
||||
|
||||
def checkRootUnique(self, theClass):
|
||||
"""Checks if there already is a root entry of class 'theClass'
|
||||
in the root of the project tree. CUSTOM class is skipped as it
|
||||
is not required to be unique.
|
||||
"""
|
||||
if theClass == nwItemClass.CUSTOM:
|
||||
return True
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return False
|
||||
return True
|
||||
|
||||
def getRootItem(self, tHandle):
|
||||
"""Iterate upwards in the tree until we find the item with
|
||||
parent None, the root item. We do this with a for loop with a
|
||||
maximum depth to make infinite loops impossible.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
for i in range(nwConst.MAX_DEPTH + 1):
|
||||
if tItem.itemParent is None:
|
||||
return tItem
|
||||
else:
|
||||
tHandle = tItem.itemParent
|
||||
tItem = self.__getitem__(tHandle)
|
||||
return None
|
||||
|
||||
def getItemPath(self, tHandle):
|
||||
"""Iterate upwards in the tree until we find the item with
|
||||
parent None, the root item, and return the list of handles.
|
||||
@@ -293,7 +246,7 @@ class NWTree():
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
tTree.append(tHandle)
|
||||
for _ in range(nwConst.MAX_DEPTH + 1):
|
||||
for _ in range(self.MAX_DEPTH):
|
||||
if tItem.itemParent is None:
|
||||
return tTree
|
||||
else:
|
||||
@@ -303,8 +256,72 @@ class NWTree():
|
||||
return tTree
|
||||
else:
|
||||
tTree.append(tHandle)
|
||||
else:
|
||||
raise RecursionError("Critical internal error")
|
||||
|
||||
return tTree
|
||||
|
||||
##
|
||||
# Tree Root Methods
|
||||
##
|
||||
|
||||
def rootClasses(self):
|
||||
"""Return a set of all root classes in use by the project.
|
||||
"""
|
||||
rootClasses = set()
|
||||
for nwItem in self._treeRoots.values():
|
||||
rootClasses.add(nwItem.itemClass)
|
||||
return rootClasses
|
||||
|
||||
def iterRoots(self, itemClass):
|
||||
"""Iterate over all items of a given class.
|
||||
"""
|
||||
for tHandle, nwItem in self._treeRoots.items():
|
||||
if nwItem.itemClass == itemClass:
|
||||
yield tHandle, nwItem
|
||||
return
|
||||
|
||||
def isRoot(self, tHandle):
|
||||
"""Check if a handle is a root item.
|
||||
"""
|
||||
return tHandle in self._treeRoots
|
||||
|
||||
def isTrash(self, tHandle):
|
||||
"""Check if an item is in or is the trash folder.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return True
|
||||
if tItem.itemClass == nwItemClass.TRASH:
|
||||
return True
|
||||
if self._trashRoot is not None:
|
||||
if tHandle == self._trashRoot:
|
||||
return True
|
||||
elif tItem.itemParent == self._trashRoot:
|
||||
return True
|
||||
elif tItem.itemRoot == self._trashRoot:
|
||||
return True
|
||||
return False
|
||||
|
||||
def trashRoot(self):
|
||||
"""Returns the handle of the trash folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._trashRoot:
|
||||
return self._trashRoot
|
||||
return None
|
||||
|
||||
def findRoot(self, theClass):
|
||||
"""Find the first root item for a given class.
|
||||
"""
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return tItem.itemHandle
|
||||
return None
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
@@ -334,14 +351,6 @@ class NWTree():
|
||||
|
||||
return
|
||||
|
||||
def setSeed(self, theSeed):
|
||||
"""Used for debugging!
|
||||
Sets a seed for generating handles so that they always come out
|
||||
in a predictable order.
|
||||
"""
|
||||
self._handleSeed = theSeed
|
||||
return
|
||||
|
||||
def setFileItemLayout(self, tHandle, itemLayout):
|
||||
"""Set the nwItemLayout for a specific file.
|
||||
"""
|
||||
@@ -358,30 +367,6 @@ class NWTree():
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
|
||||
def countTypes(self):
|
||||
"""Count the number of files, folders and roots in the project.
|
||||
"""
|
||||
nRoot = 0
|
||||
nFolder = 0
|
||||
nFile = 0
|
||||
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
elif tItem.itemType == nwItemType.ROOT:
|
||||
nRoot += 1
|
||||
elif tItem.itemType == nwItemType.FOLDER:
|
||||
nFolder += 1
|
||||
elif tItem.itemType == nwItemType.FILE:
|
||||
nFile += 1
|
||||
|
||||
return nRoot, nFolder, nFile
|
||||
|
||||
##
|
||||
# Meta Methods
|
||||
##
|
||||
@@ -420,7 +405,7 @@ class NWTree():
|
||||
return
|
||||
|
||||
if tHandle in self._treeRoots:
|
||||
self._treeRoots.remove(tHandle)
|
||||
del self._treeRoots[tHandle]
|
||||
if tHandle == self._trashRoot:
|
||||
self._trashRoot = None
|
||||
if tHandle == self._archRoot:
|
||||
@@ -468,29 +453,16 @@ class NWTree():
|
||||
self.theProject.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def _makeHandle(self, addSeed=""):
|
||||
def _makeHandle(self):
|
||||
"""Generate a unique item handle. In the event that the key
|
||||
already exists, salt the seed and generate a new handle.
|
||||
A key collision is very unlikely to be caused by the truncation
|
||||
of the sha256 hash to 13 characters. Assuming it is near-random,
|
||||
it will on average happen every 4.5^15 times. However, the clock
|
||||
seed is likely to occasionally generate a collision if the
|
||||
handle requests come faster than the clock resolution.
|
||||
already exists, generate a new one.
|
||||
"""
|
||||
if self._handleSeed is None:
|
||||
newSeed = "%s_%d_%s" % (str(time()), self._handleCount, addSeed)
|
||||
self._handleCount += 1
|
||||
else:
|
||||
# This is used for debugging
|
||||
newSeed = str(self._handleSeed)
|
||||
self._handleSeed += 1
|
||||
|
||||
logger.verbose("Generating handle with seed '%s'", newSeed)
|
||||
itemHandle = sha256(newSeed.encode()).hexdigest()[0:13]
|
||||
if itemHandle in self._projTree:
|
||||
logger.verbose("Generating new handle")
|
||||
handle = f"{random.getrandbits(52):013x}"
|
||||
if handle in self._projTree:
|
||||
logger.warning("Duplicate handle encountered! Retrying ...")
|
||||
itemHandle = self._makeHandle(addSeed+"!")
|
||||
handle = self._makeHandle()
|
||||
|
||||
return itemHandle
|
||||
return handle
|
||||
|
||||
# END Class NWTree
|
||||
|
||||
@@ -181,14 +181,14 @@ class GuiAbout(QDialog):
|
||||
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
||||
self.tr("Translations"),
|
||||
self._wrapTable([
|
||||
("Deutsch", "Myian"),
|
||||
("English", "Veronica Berglyd Olsen"),
|
||||
("Español Latinoamericano", "Tommy Marplatt"),
|
||||
("Français", "Jan Lüdke (jyhelle)"),
|
||||
("Nederlands", "Martijn van der Kleijn"),
|
||||
("Norsk Bokmål", "Veronica Berglyd Olsen"),
|
||||
("Português", "Bruno Meneguello"),
|
||||
("简体中文", "Qianzhi Long"),
|
||||
("Español Latinoamericano", "Tommy Marplatt"),
|
||||
("Nederlands", "Martijn van der Kleijn"),
|
||||
("Deutsch", "Myian"),
|
||||
])
|
||||
)
|
||||
|
||||
|
||||
@@ -125,13 +125,13 @@ class GuiDocMerge(QDialog):
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
srcItem = self.theProject.projTree[self.sourceItem]
|
||||
srcItem = self.theProject.tree[self.sourceItem]
|
||||
if srcItem is None:
|
||||
self.theParent.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.itemParent)
|
||||
newItem = self.theProject.projTree[nHandle]
|
||||
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemParent)
|
||||
newItem = self.theProject.tree[nHandle]
|
||||
newItem.setStatus(srcItem.itemStatus)
|
||||
newItem.setImport(srcItem.itemImport)
|
||||
|
||||
@@ -170,7 +170,7 @@ class GuiDocMerge(QDialog):
|
||||
if tHandle is None:
|
||||
return False
|
||||
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem is None:
|
||||
return False
|
||||
|
||||
@@ -182,7 +182,7 @@ class GuiDocMerge(QDialog):
|
||||
|
||||
for sHandle in self.theParent.treeView.getTreeFromHandle(tHandle):
|
||||
newItem = QListWidgetItem()
|
||||
nwItem = self.theProject.projTree[sHandle]
|
||||
nwItem = self.theProject.tree[sHandle]
|
||||
if nwItem.itemType is not nwItemType.FILE:
|
||||
continue
|
||||
newItem.setText(nwItem.itemName)
|
||||
|
||||
@@ -33,8 +33,7 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from novelwriter.core import NWDoc
|
||||
from novelwriter.enum import nwAlert, nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.constants import nwConst
|
||||
from novelwriter.enum import nwAlert, nwItemType
|
||||
from novelwriter.gui.custom import QHelpLabel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -51,7 +50,6 @@ class GuiDocSplit(QDialog):
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
|
||||
self.sourceItem = None
|
||||
self.sourceText = []
|
||||
@@ -76,7 +74,7 @@ class GuiDocSplit(QDialog):
|
||||
self.splitLevel.addItem(self.tr("Split up to Header Level 3 (Scene)"), 3)
|
||||
self.splitLevel.addItem(self.tr("Split up to Header Level 4 (Section)"), 4)
|
||||
spIndex = self.splitLevel.findData(
|
||||
self.optState.getInt("GuiDocSplit", "spLevel", 3)
|
||||
self.theProject.options.getInt("GuiDocSplit", "spLevel", 3)
|
||||
)
|
||||
if spIndex != -1:
|
||||
self.splitLevel.setCurrentIndex(spIndex)
|
||||
@@ -122,7 +120,7 @@ class GuiDocSplit(QDialog):
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
srcItem = self.theProject.projTree[self.sourceItem]
|
||||
srcItem = self.theProject.tree[self.sourceItem]
|
||||
if srcItem is None:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Could not parse source document."
|
||||
@@ -160,16 +158,6 @@ class GuiDocSplit(QDialog):
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
# Check that another folder can be created
|
||||
parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
|
||||
if len(parTree) >= nwConst.MAX_DEPTH - 1:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Cannot add new folder for the document split. "
|
||||
"Maximum folder depth has been reached. "
|
||||
"Please move the file to another level in the project tree."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
msgYes = self.theParent.askQuestion(
|
||||
self.tr("Split Document"),
|
||||
"{0}<br><br>{1}".format(
|
||||
@@ -186,22 +174,16 @@ class GuiDocSplit(QDialog):
|
||||
return False
|
||||
|
||||
# Create the folder
|
||||
fHandle = self.theProject.newFolder(
|
||||
srcItem.itemName, srcItem.itemClass, srcItem.itemParent
|
||||
)
|
||||
fHandle = self.theProject.newFolder(srcItem.itemName, srcItem.itemParent)
|
||||
self.theParent.treeView.revealNewTreeItem(fHandle)
|
||||
logger.verbose("Creating folder '%s'", fHandle)
|
||||
|
||||
# Loop through, and create the files
|
||||
for wTitle, iStart, iEnd in finalOrder:
|
||||
|
||||
isNovel = srcItem.itemClass == nwItemClass.NOVEL
|
||||
itemLayout = nwItemLayout.DOCUMENT if isNovel else nwItemLayout.NOTE
|
||||
|
||||
wTitle = wTitle.lstrip("#").strip()
|
||||
nHandle = self.theProject.newFile(wTitle, srcItem.itemClass, fHandle)
|
||||
newItem = self.theProject.projTree[nHandle]
|
||||
newItem.setLayout(itemLayout)
|
||||
nHandle = self.theProject.newFile(wTitle, fHandle)
|
||||
newItem = self.theProject.tree[nHandle]
|
||||
newItem.setStatus(srcItem.itemStatus)
|
||||
newItem.setImport(srcItem.itemImport)
|
||||
logger.verbose(
|
||||
@@ -228,7 +210,7 @@ class GuiDocSplit(QDialog):
|
||||
def _doClose(self):
|
||||
"""Close the dialog window without doing anything.
|
||||
"""
|
||||
self.optState.saveSettings()
|
||||
self.theProject.options.saveSettings()
|
||||
self.close()
|
||||
return
|
||||
|
||||
@@ -249,7 +231,7 @@ class GuiDocSplit(QDialog):
|
||||
if self.sourceItem is None:
|
||||
return False
|
||||
|
||||
nwItem = self.theProject.projTree[self.sourceItem]
|
||||
nwItem = self.theProject.tree[self.sourceItem]
|
||||
if nwItem is None:
|
||||
return False
|
||||
|
||||
@@ -266,7 +248,7 @@ class GuiDocSplit(QDialog):
|
||||
return False
|
||||
|
||||
spLevel = self.splitLevel.currentData()
|
||||
self.optState.setValue("GuiDocSplit", "spLevel", spLevel)
|
||||
self.theProject.options.setValue("GuiDocSplit", "spLevel", spLevel)
|
||||
logger.debug(
|
||||
"Scanning document '%s' for headings level <= %d",
|
||||
self.sourceItem, spLevel
|
||||
|
||||
@@ -33,7 +33,7 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwItemLayout, nwItemType
|
||||
from novelwriter.constants import trConst, nwLists, nwLabels
|
||||
from novelwriter.constants import trConst, nwLabels
|
||||
from novelwriter.gui.custom import QSwitch
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -55,7 +55,7 @@ class GuiItemEditor(QDialog):
|
||||
# Build GUI
|
||||
##
|
||||
|
||||
self.theItem = self.theProject.projTree[tHandle]
|
||||
self.theItem = self.theProject.tree[tHandle]
|
||||
if self.theItem is None:
|
||||
self.close()
|
||||
return
|
||||
@@ -74,19 +74,28 @@ class GuiItemEditor(QDialog):
|
||||
# Item Status
|
||||
self.editStatus = QComboBox()
|
||||
self.editStatus.setMinimumWidth(mVd)
|
||||
if self.theItem.itemClass in nwLists.CLS_NOVEL:
|
||||
for sLabel, _, _, sIcon in self.theProject.statusItems:
|
||||
self.editStatus.addItem(sIcon, sLabel, sLabel)
|
||||
if self.theItem.isNovelLike():
|
||||
for key, entry in self.theProject.statusItems.items():
|
||||
self.editStatus.addItem(entry["icon"], entry["name"], key)
|
||||
|
||||
index = self.editStatus.findData(self.theItem.itemStatus)
|
||||
if index != -1:
|
||||
self.editStatus.setCurrentIndex(index)
|
||||
|
||||
else:
|
||||
for sLabel, _, _, sIcon in self.theProject.importItems:
|
||||
self.editStatus.addItem(sIcon, sLabel, sLabel)
|
||||
for key, entry in self.theProject.importItems.items():
|
||||
self.editStatus.addItem(entry["icon"], entry["name"], key)
|
||||
|
||||
index = self.editStatus.findData(self.theItem.itemImport)
|
||||
if index != -1:
|
||||
self.editStatus.setCurrentIndex(index)
|
||||
|
||||
# Item Layout
|
||||
self.editLayout = QComboBox()
|
||||
self.editLayout.setMinimumWidth(mVd)
|
||||
validLayouts = []
|
||||
if self.theItem.itemType == nwItemType.FILE:
|
||||
if self.theItem.itemClass in nwLists.CLS_NOVEL:
|
||||
if self.theItem.documentAllowed():
|
||||
validLayouts.append(nwItemLayout.DOCUMENT)
|
||||
validLayouts.append(nwItemLayout.NOTE)
|
||||
else:
|
||||
@@ -97,6 +106,10 @@ class GuiItemEditor(QDialog):
|
||||
if itemLayout in validLayouts:
|
||||
self.editLayout.addItem(trConst(nwLabels.LAYOUT_NAME[itemLayout]), itemLayout)
|
||||
|
||||
index = self.editLayout.findData(self.theItem.itemLayout)
|
||||
if index != -1:
|
||||
self.editLayout.setCurrentIndex(index)
|
||||
|
||||
# Export Switch
|
||||
self.textExport = QLabel(self.tr("Include when building project"))
|
||||
self.editExport = QSwitch()
|
||||
@@ -116,15 +129,6 @@ class GuiItemEditor(QDialog):
|
||||
self.editName.setText(self.theItem.itemName)
|
||||
self.editName.selectAll()
|
||||
|
||||
currStatus, _ = self.theItem.getImportStatus()
|
||||
statusIdx = self.editStatus.findData(currStatus)
|
||||
if statusIdx != -1:
|
||||
self.editStatus.setCurrentIndex(statusIdx)
|
||||
|
||||
layoutIdx = self.editLayout.findData(self.theItem.itemLayout)
|
||||
if layoutIdx != -1:
|
||||
self.editLayout.setCurrentIndex(layoutIdx)
|
||||
|
||||
##
|
||||
# Assemble
|
||||
##
|
||||
|
||||
@@ -203,6 +203,22 @@ class GuiPreferencesGeneral(QWidget):
|
||||
self.tr("Requires restart.")
|
||||
)
|
||||
|
||||
# Editor Theme
|
||||
self.guiSyntax = QComboBox()
|
||||
self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200))
|
||||
self.theSyntaxes = self.theTheme.listSyntax()
|
||||
for syntaxFile, syntaxName in self.theSyntaxes:
|
||||
self.guiSyntax.addItem(syntaxName, syntaxFile)
|
||||
syntaxIdx = self.guiSyntax.findData(self.mainConf.guiSyntax)
|
||||
if syntaxIdx != -1:
|
||||
self.guiSyntax.setCurrentIndex(syntaxIdx)
|
||||
|
||||
self.mainForm.addRow(
|
||||
self.tr("Editor theme"),
|
||||
self.guiSyntax,
|
||||
self.tr("Colour theme for the editor and viewer.")
|
||||
)
|
||||
|
||||
# Font Family
|
||||
self.guiFont = QLineEdit()
|
||||
self.guiFont.setReadOnly(True)
|
||||
@@ -275,6 +291,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
guiLang = self.guiLang.currentData()
|
||||
guiTheme = self.guiTheme.currentData()
|
||||
guiIcons = self.guiIcons.currentData()
|
||||
guiSyntax = self.guiSyntax.currentData()
|
||||
guiFont = self.guiFont.text()
|
||||
guiFontSize = self.guiFontSize.value()
|
||||
emphLabels = self.emphLabels.isChecked()
|
||||
@@ -294,6 +311,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
self.mainConf.guiLang = guiLang
|
||||
self.mainConf.guiTheme = guiTheme
|
||||
self.mainConf.guiIcons = guiIcons
|
||||
self.mainConf.guiSyntax = guiSyntax
|
||||
self.mainConf.guiFont = guiFont
|
||||
self.mainConf.guiFontSize = guiFontSize
|
||||
self.mainConf.emphLabels = emphLabels
|
||||
@@ -834,25 +852,6 @@ class GuiPreferencesSyntax(QWidget):
|
||||
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Highlighting Theme
|
||||
# ==================
|
||||
self.mainForm.addGroupLabel(self.tr("Highlighting Theme"))
|
||||
|
||||
self.guiSyntax = QComboBox()
|
||||
self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200))
|
||||
self.theSyntaxes = self.theTheme.listSyntax()
|
||||
for syntaxFile, syntaxName in self.theSyntaxes:
|
||||
self.guiSyntax.addItem(syntaxName, syntaxFile)
|
||||
syntaxIdx = self.guiSyntax.findData(self.mainConf.guiSyntax)
|
||||
if syntaxIdx != -1:
|
||||
self.guiSyntax.setCurrentIndex(syntaxIdx)
|
||||
|
||||
self.mainForm.addRow(
|
||||
self.tr("Highlighting theme"),
|
||||
self.guiSyntax,
|
||||
self.tr("Colour theme for the editor and viewer.")
|
||||
)
|
||||
|
||||
# Quotes & Dialogue
|
||||
# =================
|
||||
self.mainForm.addGroupLabel(self.tr("Quotes & Dialogue"))
|
||||
@@ -902,7 +901,7 @@ class GuiPreferencesSyntax(QWidget):
|
||||
self.showMultiSpaces = QSwitch()
|
||||
self.showMultiSpaces.setChecked(self.mainConf.showMultiSpaces)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Highlight multiple spaces"),
|
||||
self.tr("Highlight multiple or trailing spaces"),
|
||||
self.showMultiSpaces,
|
||||
self.tr("Applies to the document editor only.")
|
||||
)
|
||||
@@ -912,9 +911,6 @@ class GuiPreferencesSyntax(QWidget):
|
||||
def saveValues(self):
|
||||
"""Save the values set for this tab.
|
||||
"""
|
||||
# Highlighting Theme
|
||||
self.mainConf.guiSyntax = self.guiSyntax.currentData()
|
||||
|
||||
# Quotes & Dialogue
|
||||
self.mainConf.highlightQuotes = self.highlightQuotes.isChecked()
|
||||
self.mainConf.allowOpenSQuote = self.allowOpenSQuote.isChecked()
|
||||
|
||||
@@ -52,18 +52,18 @@ class GuiProjectDetails(PagedDialog):
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
|
||||
self.setWindowTitle(self.tr("Project Details"))
|
||||
|
||||
wW = self.mainConf.pxInt(600)
|
||||
wH = self.mainConf.pxInt(400)
|
||||
pOptions = self.theProject.options
|
||||
|
||||
self.setMinimumWidth(wW)
|
||||
self.setMinimumHeight(wH)
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "winWidth", wW)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "winHeight", wH))
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "winWidth", wW)),
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "winHeight", wH))
|
||||
)
|
||||
|
||||
self.tabMain = GuiProjectDetailsMain(self.theParent, self.theProject)
|
||||
@@ -120,16 +120,17 @@ class GuiProjectDetails(PagedDialog):
|
||||
countFrom = self.tabContents.poValue.value()
|
||||
clearDouble = self.tabContents.dblValue.isChecked()
|
||||
|
||||
self.optState.setValue("GuiProjectDetails", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiProjectDetails", "winHeight", winHeight)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol0", widthCol0)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol1", widthCol1)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol2", widthCol2)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol3", widthCol3)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol4", widthCol4)
|
||||
self.optState.setValue("GuiProjectDetails", "wordsPerPage", wordsPerPage)
|
||||
self.optState.setValue("GuiProjectDetails", "countFrom", countFrom)
|
||||
self.optState.setValue("GuiProjectDetails", "clearDouble", clearDouble)
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiProjectDetails", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiProjectDetails", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol0", widthCol0)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol1", widthCol1)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol2", widthCol2)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol3", widthCol3)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol4", widthCol4)
|
||||
pOptions.setValue("GuiProjectDetails", "wordsPerPage", wordsPerPage)
|
||||
pOptions.setValue("GuiProjectDetails", "countFrom", countFrom)
|
||||
pOptions.setValue("GuiProjectDetails", "clearDouble", clearDouble)
|
||||
|
||||
return
|
||||
|
||||
@@ -145,7 +146,6 @@ class GuiProjectDetailsMain(QWidget):
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
|
||||
fPx = self.theTheme.fontPixelSize
|
||||
fPt = self.theTheme.fontPointSize
|
||||
@@ -245,8 +245,9 @@ class GuiProjectDetailsMain(QWidget):
|
||||
def updateValues(self):
|
||||
"""Set all the values.
|
||||
"""
|
||||
hCounts = self.theIndex.getNovelTitleCounts()
|
||||
nwCount = self.theIndex.getNovelWordCount()
|
||||
pIndex = self.theProject.index
|
||||
hCounts = pIndex.getNovelTitleCounts()
|
||||
nwCount = pIndex.getNovelWordCount()
|
||||
edTime = self.theProject.getCurrentEditTime()
|
||||
|
||||
self.wordCountVal.setText(f"{nwCount:n}")
|
||||
@@ -277,8 +278,6 @@ class GuiProjectDetailsContents(QWidget):
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.optState = theProject.optState
|
||||
|
||||
# Internal
|
||||
self._theToC = []
|
||||
@@ -286,6 +285,7 @@ class GuiProjectDetailsContents(QWidget):
|
||||
iPx = self.theTheme.baseIconSize
|
||||
hPx = self.mainConf.pxInt(12)
|
||||
vPx = self.mainConf.pxInt(4)
|
||||
pOptions = self.theProject.options
|
||||
|
||||
# Contents Tree
|
||||
# =============
|
||||
@@ -314,11 +314,11 @@ class GuiProjectDetailsContents(QWidget):
|
||||
treeHeader.setStretchLastSection(True)
|
||||
treeHeader.setMinimumSectionSize(hPx)
|
||||
|
||||
wCol0 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol0", 200))
|
||||
wCol1 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol1", 60))
|
||||
wCol2 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol2", 60))
|
||||
wCol3 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol3", 60))
|
||||
wCol4 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol4", 90))
|
||||
wCol0 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol0", 200))
|
||||
wCol1 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol1", 60))
|
||||
wCol2 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol2", 60))
|
||||
wCol3 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol3", 60))
|
||||
wCol4 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol4", 90))
|
||||
|
||||
self.tocTree.setColumnWidth(0, wCol0)
|
||||
self.tocTree.setColumnWidth(1, wCol1)
|
||||
@@ -330,9 +330,9 @@ class GuiProjectDetailsContents(QWidget):
|
||||
# Options
|
||||
# =======
|
||||
|
||||
wordsPerPage = self.optState.getInt("GuiProjectDetails", "wordsPerPage", 350)
|
||||
countFrom = self.optState.getInt("GuiProjectDetails", "countFrom", 1)
|
||||
clearDouble = self.optState.getInt("GuiProjectDetails", "clearDouble", True)
|
||||
wordsPerPage = pOptions.getInt("GuiProjectDetails", "wordsPerPage", 350)
|
||||
countFrom = pOptions.getInt("GuiProjectDetails", "countFrom", 1)
|
||||
clearDouble = pOptions.getInt("GuiProjectDetails", "clearDouble", True)
|
||||
|
||||
wordsHelp = (
|
||||
self.tr("Typical word count for a 5 by 8 inch book page with 11 pt font is 350.")
|
||||
@@ -424,7 +424,7 @@ class GuiProjectDetailsContents(QWidget):
|
||||
"""Extract the data for the tree.
|
||||
"""
|
||||
self._theToC = []
|
||||
self._theToC = self.theIndex.getTableOfContents(2)
|
||||
self._theToC = self.theProject.index.getTableOfContents(2)
|
||||
self._theToC.append(("", 0, self.tr("END"), 0))
|
||||
return
|
||||
|
||||
|
||||
@@ -35,6 +35,7 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwAlert
|
||||
from novelwriter.common import simplified
|
||||
from novelwriter.gui.custom import QSwitch, PagedDialog, QConfigLayout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -51,19 +52,19 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
|
||||
self.theProject.countStatus()
|
||||
self.setWindowTitle(self.tr("Project Settings"))
|
||||
|
||||
wW = self.mainConf.pxInt(570)
|
||||
wH = self.mainConf.pxInt(375)
|
||||
pOptions = self.theProject.options
|
||||
|
||||
self.setMinimumWidth(wW)
|
||||
self.setMinimumHeight(wH)
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winWidth", wW)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winHeight", wH))
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiProjectSettings", "winWidth", wW)),
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiProjectSettings", "winHeight", wH))
|
||||
)
|
||||
|
||||
self.tabMain = GuiProjectEditMain(self.theParent, self.theProject)
|
||||
@@ -81,6 +82,9 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
self.addControls(self.buttonBox)
|
||||
|
||||
# Flags
|
||||
self.spellChanged = False
|
||||
|
||||
logger.debug("GuiProjectSettings initialisation complete")
|
||||
|
||||
return
|
||||
@@ -103,16 +107,18 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.theProject.setProjectName(projName)
|
||||
self.theProject.setBookTitle(bookTitle)
|
||||
self.theProject.setBookAuthors(bookAuthors)
|
||||
self.theProject.setSpellLang(spellLang)
|
||||
self.theProject.setProjBackup(doBackup)
|
||||
|
||||
# Remember this as updating spell dictionary can be expensive
|
||||
self.spellChanged = self.theProject.setSpellLang(spellLang)
|
||||
|
||||
if self.tabStatus.colChanged:
|
||||
statusCol = self.tabStatus.getNewList()
|
||||
self.theProject.setStatusColours(statusCol)
|
||||
newList, delList = self.tabStatus.getNewList()
|
||||
self.theProject.setStatusColours(newList, delList)
|
||||
|
||||
if self.tabImport.colChanged:
|
||||
importCol = self.tabImport.getNewList()
|
||||
self.theProject.setImportColours(importCol)
|
||||
newList, delList = self.tabImport.getNewList()
|
||||
self.theProject.setImportColours(newList, delList)
|
||||
|
||||
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
||||
self.theParent.rebuildTrees()
|
||||
@@ -146,11 +152,12 @@ class GuiProjectSettings(PagedDialog):
|
||||
statusColW = self.mainConf.rpxInt(self.tabStatus.listBox.columnWidth(0))
|
||||
importColW = self.mainConf.rpxInt(self.tabImport.listBox.columnWidth(0))
|
||||
|
||||
self.optState.setValue("GuiProjectSettings", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiProjectSettings", "winHeight", winHeight)
|
||||
self.optState.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||
self.optState.setValue("GuiProjectSettings", "statusColW", statusColW)
|
||||
self.optState.setValue("GuiProjectSettings", "importColW", importColW)
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiProjectSettings", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiProjectSettings", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||
pOptions.setValue("GuiProjectSettings", "statusColW", statusColW)
|
||||
pOptions.setValue("GuiProjectSettings", "importColW", importColW)
|
||||
|
||||
return
|
||||
|
||||
@@ -181,7 +188,7 @@ class GuiProjectEditMain(QWidget):
|
||||
self.editName.setMaximumWidth(xW)
|
||||
self.editName.setText(self.theProject.projName)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Working title"),
|
||||
self.tr("Project name"),
|
||||
self.editName,
|
||||
self.tr("Should be set only once.")
|
||||
)
|
||||
@@ -245,13 +252,16 @@ class GuiProjectEditStatus(QWidget):
|
||||
COL_LABEL = 0
|
||||
COL_USAGE = 1
|
||||
|
||||
KEY_ROLE = Qt.UserRole
|
||||
COL_ROLE = Qt.UserRole + 1
|
||||
NUM_ROLE = Qt.UserRole + 2
|
||||
|
||||
def __init__(self, theParent, theProject, isStatus):
|
||||
QWidget.__init__(self, theParent)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.optState = theProject.optState
|
||||
self.theTheme = theParent.theTheme
|
||||
|
||||
if isStatus:
|
||||
@@ -264,13 +274,12 @@ class GuiProjectEditStatus(QWidget):
|
||||
colSetting = "importColW"
|
||||
|
||||
wCol0 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiProjectSettings", colSetting, 130)
|
||||
self.theProject.options.getInt("GuiProjectSettings", colSetting, 130)
|
||||
)
|
||||
|
||||
self.colData = []
|
||||
self.colCounts = []
|
||||
self.colDeleted = []
|
||||
self.colChanged = False
|
||||
self.selColour = None
|
||||
self.selColour = QColor(100, 100, 100)
|
||||
|
||||
self.iPx = self.theTheme.baseIconSize
|
||||
|
||||
@@ -285,8 +294,8 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
|
||||
self.listBox.setIndentation(0)
|
||||
|
||||
for iName, iCol, nUse, _ in self.theStatus:
|
||||
self._addItem(iName, iCol, iName, nUse)
|
||||
for key, entry in self.theStatus.items():
|
||||
self._addItem(key, entry["name"], entry["cols"], entry["count"])
|
||||
|
||||
# List Controls
|
||||
# =============
|
||||
@@ -297,6 +306,12 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
|
||||
self.delButton.clicked.connect(self._delItem)
|
||||
|
||||
self.upButton = QPushButton(self.theTheme.getIcon("up"), "")
|
||||
self.upButton.clicked.connect(lambda: self._moveItem(-1))
|
||||
|
||||
self.dnButton = QPushButton(self.theTheme.getIcon("down"), "")
|
||||
self.dnButton.clicked.connect(lambda: self._moveItem(1))
|
||||
|
||||
# Edit Form
|
||||
# =========
|
||||
|
||||
@@ -306,7 +321,7 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.editName.setPlaceholderText(self.tr("Select item to edit"))
|
||||
|
||||
self.colPixmap = QPixmap(self.iPx, self.iPx)
|
||||
self.colPixmap.fill(QColor(120, 120, 120))
|
||||
self.colPixmap.fill(QColor(100, 100, 100))
|
||||
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
|
||||
self.colButton.setIconSize(self.colPixmap.rect().size())
|
||||
self.colButton.clicked.connect(self._selectColour)
|
||||
@@ -320,6 +335,8 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.listControls = QVBoxLayout()
|
||||
self.listControls.addWidget(self.addButton)
|
||||
self.listControls.addWidget(self.delButton)
|
||||
self.listControls.addWidget(self.upButton)
|
||||
self.listControls.addWidget(self.dnButton)
|
||||
self.listControls.addStretch(1)
|
||||
|
||||
self.editBox = QHBoxLayout()
|
||||
@@ -349,12 +366,15 @@ class GuiProjectEditStatus(QWidget):
|
||||
if self.colChanged:
|
||||
newList = []
|
||||
for n in range(self.listBox.topLevelItemCount()):
|
||||
nItem = self.listBox.topLevelItem(n)
|
||||
nIdx = nItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
newList.append(self.colData[nIdx])
|
||||
return newList
|
||||
item = self.listBox.topLevelItem(n)
|
||||
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 None
|
||||
return [], []
|
||||
|
||||
##
|
||||
# User Actions
|
||||
@@ -369,16 +389,16 @@ class GuiProjectEditStatus(QWidget):
|
||||
)
|
||||
if newCol.isValid():
|
||||
self.selColour = newCol
|
||||
colPixmap = QPixmap(self.iPx, self.iPx)
|
||||
colPixmap.fill(newCol)
|
||||
self.colButton.setIcon(QIcon(colPixmap))
|
||||
self.colButton.setIconSize(colPixmap.rect().size())
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(newCol)
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
self.colButton.setIconSize(pixmap.rect().size())
|
||||
return
|
||||
|
||||
def _newItem(self):
|
||||
"""Create a new status item.
|
||||
"""
|
||||
newItem = self._addItem(self.tr("New Item"), (0, 0, 0), None, 0)
|
||||
newItem = self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
|
||||
newItem.setBackground(self.COL_LABEL, QBrush(QColor(0, 255, 0, 70)))
|
||||
newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70)))
|
||||
self.colChanged = True
|
||||
@@ -390,14 +410,14 @@ class GuiProjectEditStatus(QWidget):
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is not None:
|
||||
iRow = self.listBox.indexOfTopLevelItem(selItem)
|
||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
if self.colCounts[selIdx] == 0:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self.colChanged = True
|
||||
else:
|
||||
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Cannot delete a status item that is in use."
|
||||
), nwAlert.ERROR)
|
||||
else:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
||||
self.colChanged = True
|
||||
return
|
||||
|
||||
def _saveItem(self):
|
||||
@@ -405,53 +425,75 @@ class GuiProjectEditStatus(QWidget):
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is not None:
|
||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
self.colData[selIdx] = (
|
||||
self.editName.text().strip(),
|
||||
self.selColour.red(),
|
||||
self.selColour.green(),
|
||||
self.selColour.blue(),
|
||||
self.colData[selIdx][4]
|
||||
)
|
||||
selItem.setText(self.COL_LABEL, self.colData[selIdx][0])
|
||||
selItem.setText(self.COL_USAGE, self._usageString(self.colCounts[selIdx]))
|
||||
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()
|
||||
))
|
||||
self.editName.setEnabled(False)
|
||||
self.colChanged = True
|
||||
|
||||
return
|
||||
|
||||
def _addItem(self, iName, iCol, oName, nUse):
|
||||
def _addItem(self, key, name, cols, count):
|
||||
"""Add a status item to the list.
|
||||
"""
|
||||
newIcon = QPixmap(self.iPx, self.iPx)
|
||||
newIcon.fill(QColor(*iCol))
|
||||
newItem = QTreeWidgetItem()
|
||||
newItem.setText(self.COL_LABEL, iName)
|
||||
newItem.setText(self.COL_USAGE, self._usageString(nUse))
|
||||
newItem.setIcon(self.COL_LABEL, QIcon(newIcon))
|
||||
newItem.setData(self.COL_LABEL, Qt.UserRole, len(self.colData))
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
self.colData.append((iName, iCol[0], iCol[1], iCol[2], oName))
|
||||
self.colCounts.append(nUse)
|
||||
return newItem
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(*cols))
|
||||
|
||||
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, cols)
|
||||
item.setData(self.COL_LABEL, self.NUM_ROLE, count)
|
||||
item.setText(self.COL_USAGE, self._usageString(count))
|
||||
|
||||
self.listBox.addTopLevelItem(item)
|
||||
|
||||
return item
|
||||
|
||||
def _moveItem(self, step):
|
||||
"""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()
|
||||
|
||||
cItem.setSelected(True)
|
||||
self.colChanged = True
|
||||
|
||||
return
|
||||
|
||||
def _selectedItem(self):
|
||||
"""Extract the info of a selected item and populate the settings
|
||||
boxes and button.
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is not None:
|
||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
selVal = self.colData[selIdx]
|
||||
self.selColour = QColor(selVal[1], selVal[2], selVal[3])
|
||||
newIcon = QPixmap(self.iPx, self.iPx)
|
||||
newIcon.fill(self.selColour)
|
||||
self.editName.setText(selVal[0])
|
||||
self.colButton.setIcon(QIcon(newIcon))
|
||||
self.editName.setEnabled(True)
|
||||
self.editName.selectAll()
|
||||
self.editName.setFocus()
|
||||
if selItem is None:
|
||||
return
|
||||
|
||||
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))
|
||||
self.editName.setEnabled(True)
|
||||
self.editName.selectAll()
|
||||
self.editName.setFocus()
|
||||
|
||||
return
|
||||
|
||||
@@ -467,12 +509,6 @@ class GuiProjectEditStatus(QWidget):
|
||||
return selItem[0]
|
||||
return None
|
||||
|
||||
def _rowsMoved(self):
|
||||
"""A row has been moved, so set the changed flag.
|
||||
"""
|
||||
self.colChanged = True
|
||||
return
|
||||
|
||||
def _usageString(self, nUse):
|
||||
"""Generate usage string.
|
||||
"""
|
||||
@@ -498,11 +534,10 @@ class GuiProjectEditReplace(QWidget):
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theProject
|
||||
self.optState = theProject.optState
|
||||
self.arChanged = False
|
||||
|
||||
wCol0 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiProjectSettings", "replaceColW", 130)
|
||||
self.theProject.options.getInt("GuiProjectSettings", "replaceColW", 130)
|
||||
)
|
||||
pageLabel = self.tr("Text Replace List for Preview and Export")
|
||||
|
||||
|
||||
@@ -52,19 +52,19 @@ class GuiWordList(QDialog):
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
|
||||
self.setWindowTitle(self.tr("Project Word List"))
|
||||
|
||||
mS = self.mainConf.pxInt(250)
|
||||
wW = self.mainConf.pxInt(320)
|
||||
wH = self.mainConf.pxInt(340)
|
||||
pOptions = self.theProject.options
|
||||
|
||||
self.setMinimumWidth(mS)
|
||||
self.setMinimumHeight(mS)
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winWidth", wW)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winHeight", wH))
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiWordList", "winWidth", wW)),
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiWordList", "winHeight", wH))
|
||||
)
|
||||
|
||||
# Main Widgets
|
||||
@@ -207,8 +207,9 @@ class GuiWordList(QDialog):
|
||||
winWidth = self.mainConf.rpxInt(self.width())
|
||||
winHeight = self.mainConf.rpxInt(self.height())
|
||||
|
||||
self.optState.setValue("GuiWordList", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiWordList", "winHeight", winHeight)
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiWordList", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiWordList", "winHeight", winHeight)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ class nwItemType(Enum):
|
||||
ROOT = 1
|
||||
FOLDER = 2
|
||||
FILE = 3
|
||||
TRASH = 4
|
||||
|
||||
# END Enum nwItemType
|
||||
|
||||
@@ -63,6 +62,14 @@ class nwItemLayout(Enum):
|
||||
# END Enum nwItemLayout
|
||||
|
||||
|
||||
class nwDocMode(Enum):
|
||||
|
||||
VIEW = 0
|
||||
EDIT = 1
|
||||
|
||||
# END Enum nwDocMode
|
||||
|
||||
|
||||
class nwDocAction(Enum):
|
||||
|
||||
NO_ACTION = 0
|
||||
@@ -131,6 +138,16 @@ class nwState(Enum):
|
||||
# END Enum nwState
|
||||
|
||||
|
||||
class nwView(Enum):
|
||||
|
||||
EDITOR = 0
|
||||
PROJECT = 1
|
||||
NOVEL = 2
|
||||
OUTLINE = 3
|
||||
|
||||
# END Enum nwView
|
||||
|
||||
|
||||
class nwWidget(Enum):
|
||||
|
||||
TREE = 1
|
||||
|
||||
@@ -87,6 +87,8 @@ class NWErrorMessage(QDialog):
|
||||
self.mainBox.addWidget(self.btnBox, 2, 0, 1, 2)
|
||||
self.mainBox.setSpacing(16)
|
||||
|
||||
# Pick a random window title from a set of error messages by
|
||||
# Hex, the computer, from Discworld
|
||||
self.setWindowTitle([
|
||||
"+++ Out of Cheese Error +++",
|
||||
"+++ Divide by Cucumber Error +++",
|
||||
|
||||
@@ -25,10 +25,10 @@ from novelwriter.gui.itemdetails import GuiItemDetails
|
||||
from novelwriter.gui.mainmenu import GuiMainMenu
|
||||
from novelwriter.gui.noveltree import GuiNovelTree
|
||||
from novelwriter.gui.outline import GuiOutline
|
||||
from novelwriter.gui.outlinedetails import GuiOutlineDetails
|
||||
from novelwriter.gui.projtree import GuiProjectTree
|
||||
from novelwriter.gui.statusbar import GuiMainStatus
|
||||
from novelwriter.gui.theme import GuiTheme
|
||||
from novelwriter.gui.viewsbar import GuiViewsBar
|
||||
|
||||
__all__ = [
|
||||
"GuiDocEditor",
|
||||
@@ -39,7 +39,7 @@ __all__ = [
|
||||
"GuiMainStatus",
|
||||
"GuiNovelTree",
|
||||
"GuiOutline",
|
||||
"GuiOutlineDetails",
|
||||
"GuiProjectTree",
|
||||
"GuiTheme",
|
||||
"GuiViewsBar",
|
||||
]
|
||||
|
||||
@@ -409,10 +409,10 @@ class PagedDialog(QDialog):
|
||||
|
||||
return
|
||||
|
||||
def addTab(self, tabWidget, tabLabel):
|
||||
def addTab(self, widget, label):
|
||||
"""Forwards the adding of tabs to the QTabWidget.
|
||||
"""
|
||||
self._tabBox.addTab(tabWidget, tabLabel)
|
||||
self._tabBox.addTab(widget, label)
|
||||
return
|
||||
|
||||
def addControls(self, buttonBar):
|
||||
@@ -431,15 +431,15 @@ class VerticalTabBar(QTabBar):
|
||||
self._mW = novelwriter.CONFIG.pxInt(150)
|
||||
return
|
||||
|
||||
def tabSizeHint(self, theIndex):
|
||||
def tabSizeHint(self, index):
|
||||
"""Returns a transposed size hint for the rotated bar.
|
||||
"""
|
||||
tSize = QTabBar.tabSizeHint(self, theIndex)
|
||||
tSize = QTabBar.tabSizeHint(self, index)
|
||||
tSize.transpose()
|
||||
tSize.setWidth(min(tSize.width(), self._mW))
|
||||
return tSize
|
||||
|
||||
def paintEvent(self, theEvent):
|
||||
def paintEvent(self, event):
|
||||
"""Custom implementation of the label painter that rotates the
|
||||
label 90 degrees.
|
||||
"""
|
||||
|
||||
@@ -33,6 +33,7 @@ import bisect
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from enum import Enum
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
@@ -50,7 +51,7 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from novelwriter.core import NWDoc, NWSpellEnchant, countWords
|
||||
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert
|
||||
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode
|
||||
from novelwriter.common import transferCase
|
||||
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode
|
||||
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
||||
@@ -69,6 +70,7 @@ class GuiDocEditor(QTextEdit):
|
||||
spellDictionaryChanged = pyqtSignal(str, str)
|
||||
docEditedStatusChanged = pyqtSignal(bool)
|
||||
docCountsChanged = pyqtSignal(str, int, int, int)
|
||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||
|
||||
def __init__(self, theParent):
|
||||
QTextEdit.__init__(self, theParent)
|
||||
@@ -79,7 +81,6 @@ class GuiDocEditor(QTextEdit):
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.theProject = theParent.theProject
|
||||
|
||||
self._nwDocument = None
|
||||
@@ -131,8 +132,9 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
# Editor Settings
|
||||
self.setMinimumWidth(self.mainConf.pxInt(300))
|
||||
self.setAutoFillBackground(True)
|
||||
self.setAcceptRichText(False)
|
||||
self.setAutoFillBackground(True)
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
|
||||
# Custom Shortcuts
|
||||
QShortcut(
|
||||
@@ -400,7 +402,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self.document().rootFrame().setFrameFormat(docFrame)
|
||||
|
||||
self.docFooter.updateLineCount()
|
||||
self._docHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
||||
self._docHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
|
||||
|
||||
qApp.processEvents()
|
||||
self.document().clearUndoRedoStacks()
|
||||
@@ -505,9 +507,9 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
self.setDocumentChanged(False)
|
||||
|
||||
oldHeader = self.theIndex.getHandleHeaderLevel(tHandle)
|
||||
self.theIndex.scanText(tHandle, docText)
|
||||
newHeader = self.theIndex.getHandleHeaderLevel(tHandle)
|
||||
oldHeader = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
self.theProject.index.scanText(tHandle, docText)
|
||||
newHeader = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
|
||||
if self._updateHeaders(checkLevel=True):
|
||||
self.theParent.requestNovelTreeRefresh()
|
||||
@@ -567,16 +569,6 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
return
|
||||
|
||||
def updateDocInfo(self, tHandle):
|
||||
"""Called when an item label is changed to check if the document
|
||||
title bar needs updating,
|
||||
"""
|
||||
if tHandle == self._docHandle:
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
self.docFooter.updateInfo()
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -1066,7 +1058,22 @@ class GuiDocEditor(QTextEdit):
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateDocInfo(self, tHandle):
|
||||
"""Called when an item label is changed to check if the document
|
||||
title bar needs updating,
|
||||
"""
|
||||
if tHandle == self._docHandle:
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
self.docFooter.updateInfo()
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(int, int, int)
|
||||
@@ -1894,7 +1901,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
if loadTag:
|
||||
logger.verbose("Attempting to follow tag '%s'", theWord)
|
||||
self.theParent.docViewer.loadFromTag(theWord)
|
||||
self.loadDocumentTagRequest.emit(theWord, nwDocMode.VIEW)
|
||||
else:
|
||||
logger.verbose("Potential tag '%s'", theWord)
|
||||
|
||||
@@ -2002,7 +2009,7 @@ class GuiDocEditor(QTextEdit):
|
||||
if self._docHandle is None:
|
||||
return False
|
||||
|
||||
newHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
||||
newHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
|
||||
if checkPos:
|
||||
newPos = [x[0] for x in newHeaders]
|
||||
oldPos = [x[0] for x in self._docHeaders]
|
||||
@@ -2701,15 +2708,15 @@ class GuiDocEditHeader(QWidget):
|
||||
|
||||
if self.mainConf.showFullPath:
|
||||
tTitle = []
|
||||
tTree = self.theProject.projTree.getItemPath(tHandle)
|
||||
tTree = self.theProject.tree.getItemPath(tHandle)
|
||||
for aHandle in reversed(tTree):
|
||||
nwItem = self.theProject.projTree[aHandle]
|
||||
nwItem = self.theProject.tree[aHandle]
|
||||
if nwItem is not None:
|
||||
tTitle.append(nwItem.itemName)
|
||||
sSep = " %s " % nwUnicode.U_RSAQUO
|
||||
self.theTitle.setText(sSep.join(tTitle))
|
||||
else:
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem is None:
|
||||
return False
|
||||
self.theTitle.setText(nwItem.itemName)
|
||||
@@ -2795,7 +2802,6 @@ class GuiDocEditFooter(QWidget):
|
||||
self.theParent = docEditor.theParent
|
||||
self.theProject = docEditor.theProject
|
||||
self.theTheme = docEditor.theTheme
|
||||
self.optState = docEditor.theProject.optState
|
||||
|
||||
self._theItem = None
|
||||
self._docHandle = None
|
||||
@@ -2918,7 +2924,7 @@ class GuiDocEditFooter(QWidget):
|
||||
logger.verbose("No handle set, so clearing the editor footer")
|
||||
self._theItem = None
|
||||
else:
|
||||
self._theItem = self.theProject.projTree[self._docHandle]
|
||||
self._theItem = self.theProject.tree[self._docHandle]
|
||||
|
||||
self.setHasSelection(False)
|
||||
self.updateInfo()
|
||||
@@ -2942,7 +2948,7 @@ class GuiDocEditFooter(QWidget):
|
||||
else:
|
||||
theStatus, theIcon = self._theItem.getImportStatus()
|
||||
sIcon = theIcon.pixmap(self.sPx, self.sPx)
|
||||
hLevel = self.theParent.theIndex.getHandleHeaderLevel(self._docHandle)
|
||||
hLevel = self.theProject.index.getHandleHeaderLevel(self._docHandle)
|
||||
sText = f"{theStatus} / {self._theItem.describeMe(hLevel)}"
|
||||
|
||||
self.statusIcon.setPixmap(sIcon)
|
||||
|
||||
@@ -55,7 +55,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
self.spEnchant = spEnchant
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.theProject = theParent.theProject
|
||||
self.theHandle = None
|
||||
self.spellCheck = False
|
||||
self.spellRx = None
|
||||
@@ -287,9 +287,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
if theText.startswith("@"): # Keywords and commands
|
||||
self.setCurrentBlockState(self.BLOCK_META)
|
||||
tItem = self.theParent.theProject.projTree[self.theHandle]
|
||||
isValid, theBits, thePos = self.theIndex.scanThis(theText)
|
||||
isGood = self.theIndex.checkThese(theBits, tItem)
|
||||
pIndex = self.theProject.index
|
||||
tItem = self.theParent.theProject.tree[self.theHandle]
|
||||
isValid, theBits, thePos = pIndex.scanThis(theText)
|
||||
isGood = pIndex.checkThese(theBits, tItem)
|
||||
if isValid:
|
||||
for n, theBit in enumerate(theBits):
|
||||
xPos = thePos[n]
|
||||
|
||||
@@ -30,17 +30,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot
|
||||
from enum import Enum
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtGui import (
|
||||
QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon, QCursor
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QTextBrowser, QWidget, QScrollArea, QLabel, QHBoxLayout, QToolButton,
|
||||
QAction, QMenu
|
||||
QAction, QMenu, QFrame
|
||||
)
|
||||
|
||||
from novelwriter.core import ToHtml
|
||||
from novelwriter.enum import nwAlert, nwItemType, nwDocAction
|
||||
from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.constants import nwUnicode
|
||||
|
||||
@@ -49,6 +51,8 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiDocViewer(QTextBrowser):
|
||||
|
||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||
|
||||
def __init__(self, theParent):
|
||||
QTextBrowser.__init__(self, theParent)
|
||||
|
||||
@@ -68,6 +72,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.setAutoFillBackground(True)
|
||||
self.setOpenExternalLinks(False)
|
||||
self.setFocusPolicy(Qt.StrongFocus)
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
|
||||
# Document Header and Footer
|
||||
self.docHeader = GuiDocViewHeader(self)
|
||||
@@ -159,7 +164,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
def loadText(self, tHandle, updateHistory=True):
|
||||
"""Load text into the viewer from an item handle.
|
||||
"""
|
||||
if not self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
|
||||
if not self.theProject.tree.checkType(tHandle, nwItemType.FILE):
|
||||
logger.warning("Item not found")
|
||||
return False
|
||||
|
||||
@@ -238,30 +243,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
def loadFromTag(self, theTag):
|
||||
"""Load text in the document from a reference given by a meta
|
||||
tag rather than a known handle. This function depends on the
|
||||
index being up to date.
|
||||
"""
|
||||
logger.debug("Loading document from tag '%s'", theTag)
|
||||
tHandle, _, sTitle = self.theParent.theIndex.getTagSource(theTag)
|
||||
if tHandle is None:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Could not find the reference for tag '{0}'. It either doesn't "
|
||||
"exist, or the index is out of date. The index can be updated "
|
||||
"from the Tools menu, or by pressing {1}."
|
||||
).format(
|
||||
theTag, "F9"
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
else:
|
||||
# Let the parent handle the opening as it also ensures that
|
||||
# the doc view panel is visible in case this request comes
|
||||
# from outside this class.
|
||||
logger.verbose("Tag points to '%s#%s'", tHandle, sTitle)
|
||||
self.theParent.viewDocument(tHandle, "#%s" % sTitle)
|
||||
return True
|
||||
|
||||
def docAction(self, theAction):
|
||||
"""Wrapper function for various document actions on the current
|
||||
document.
|
||||
@@ -341,15 +322,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
return
|
||||
|
||||
def updateDocInfo(self, tHandle):
|
||||
"""Called when an item label is changed to check if the document
|
||||
title bar needs updating,
|
||||
"""
|
||||
if tHandle == self._docHandle:
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -408,19 +380,33 @@ class GuiDocViewer(QTextBrowser):
|
||||
return 0
|
||||
|
||||
##
|
||||
# Slots
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateDocInfo(self, tHandle):
|
||||
"""Called when an item label is changed to check if the document
|
||||
title bar needs updating,
|
||||
"""
|
||||
if tHandle == self._docHandle:
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot("QUrl")
|
||||
def _linkClicked(self, theURL):
|
||||
"""Slot for a link in the document being clicked.
|
||||
"""Process a clicked link internally in the document.
|
||||
"""
|
||||
theLink = theURL.url()
|
||||
logger.verbose("Clicked link: '%s'", theLink)
|
||||
if len(theLink) > 0:
|
||||
theBits = theLink.split("=")
|
||||
if len(theBits) == 2:
|
||||
self.loadFromTag(theBits[1])
|
||||
self.loadDocumentTagRequest.emit(theBits[1], nwDocMode.VIEW)
|
||||
return
|
||||
|
||||
@pyqtSlot("QPoint")
|
||||
@@ -862,15 +848,15 @@ class GuiDocViewHeader(QWidget):
|
||||
|
||||
if self.mainConf.showFullPath:
|
||||
tTitle = []
|
||||
tTree = self.theProject.projTree.getItemPath(tHandle)
|
||||
tTree = self.theProject.tree.getItemPath(tHandle)
|
||||
for aHandle in reversed(tTree):
|
||||
nwItem = self.theProject.projTree[aHandle]
|
||||
nwItem = self.theProject.tree[aHandle]
|
||||
if nwItem is not None:
|
||||
tTitle.append(nwItem.itemName)
|
||||
sSep = " %s " % nwUnicode.U_RSAQUO
|
||||
self.theTitle.setText(sSep.join(tTitle))
|
||||
else:
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem is None:
|
||||
return False
|
||||
self.theTitle.setText(nwItem.itemName)
|
||||
@@ -1185,6 +1171,7 @@ class GuiDocViewDetails(QScrollArea):
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setWidgetResizable(True)
|
||||
self.setMinimumHeight(self.mainConf.pxInt(50))
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
|
||||
logger.debug("GuiDocViewDetails initialisation complete")
|
||||
|
||||
@@ -1197,10 +1184,10 @@ class GuiDocViewDetails(QScrollArea):
|
||||
if self.theParent.docViewer.stickyRef:
|
||||
return
|
||||
|
||||
theRefs = self.theParent.theIndex.getBackReferenceList(tHandle)
|
||||
theRefs = self.theProject.index.getBackReferenceList(tHandle)
|
||||
theList = []
|
||||
for tHandle in theRefs:
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
if tItem is not None:
|
||||
theList.append("<a href='%s#%s' %s>%s</a>" % (
|
||||
tHandle, theRefs[tHandle], self.linkStyle, tItem.itemName
|
||||
|
||||
@@ -115,6 +115,7 @@ class GuiItemDetails(QWidget):
|
||||
self.usageData = QLabel("")
|
||||
self.usageData.setFont(fntValue)
|
||||
self.usageData.setAlignment(Qt.AlignLeft)
|
||||
self.usageData.setWordWrap(True)
|
||||
|
||||
# Character Count
|
||||
self.cCountName = QLabel(" "+self.tr("Characters"))
|
||||
@@ -214,6 +215,16 @@ class GuiItemDetails(QWidget):
|
||||
|
||||
return
|
||||
|
||||
def refreshDetails(self):
|
||||
"""Reload the content of the details panel.
|
||||
"""
|
||||
self.updateViewBox(self._itemHandle)
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateViewBox(self, tHandle):
|
||||
"""Populate the details box from a given handle.
|
||||
"""
|
||||
@@ -221,7 +232,7 @@ class GuiItemDetails(QWidget):
|
||||
self.clearDetails()
|
||||
return
|
||||
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem is None:
|
||||
self.clearDetails()
|
||||
return
|
||||
@@ -263,7 +274,7 @@ class GuiItemDetails(QWidget):
|
||||
# Layout
|
||||
# ======
|
||||
|
||||
hLevel = self.theParent.theIndex.getHandleHeaderLevel(tHandle)
|
||||
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
usageIcon = self.theTheme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||||
)
|
||||
@@ -284,10 +295,6 @@ class GuiItemDetails(QWidget):
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str, int, int, int)
|
||||
def doUpdateCounts(self, tHandle, cC, wC, pC):
|
||||
"""Update the counts if the handle is the same as the one we're
|
||||
|
||||
@@ -71,24 +71,6 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def setAvailableRoot(self):
|
||||
"""Update the list of available root folders and set the ones
|
||||
that are active.
|
||||
"""
|
||||
for itemClass in nwItemClass:
|
||||
if itemClass == nwItemClass.NO_CLASS:
|
||||
continue
|
||||
if itemClass == nwItemClass.TRASH:
|
||||
continue
|
||||
self.rootItems[itemClass].setVisible(
|
||||
self.theProject.projTree.checkRootUnique(itemClass)
|
||||
)
|
||||
return
|
||||
|
||||
##
|
||||
# Update Menu on Settings Changed
|
||||
##
|
||||
@@ -99,12 +81,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aSpellCheck.setChecked(theMode)
|
||||
return
|
||||
|
||||
def setAutoOutline(self, theMode):
|
||||
"""Forward auto outline check state to its action.
|
||||
"""
|
||||
self.aAutoOutline.setChecked(theMode)
|
||||
return
|
||||
|
||||
def setFocusMode(self, theMode):
|
||||
"""Forward focus mode check state to its action.
|
||||
"""
|
||||
@@ -123,12 +99,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.theParent.docEditor.toggleSpellCheck(None)
|
||||
return True
|
||||
|
||||
def _toggleAutoOutline(self, theMode):
|
||||
"""Toggle auto outline when the menu entry is checked.
|
||||
"""
|
||||
self.theProject.setAutoOutline(theMode)
|
||||
return True
|
||||
|
||||
def _openWebsite(self, theUrl):
|
||||
"""Open a URL in the system's default browser.
|
||||
"""
|
||||
@@ -215,7 +185,7 @@ class GuiMainMenu(QMenuBar):
|
||||
# Project > New Folder
|
||||
self.aCreateFolder = QAction(self.tr("Create Folder"), self)
|
||||
self.aCreateFolder.setShortcut("Ctrl+Shift+N")
|
||||
self.aCreateFolder.triggered.connect(lambda: self._newTreeItem(nwItemType.FOLDER, None))
|
||||
self.aCreateFolder.triggered.connect(lambda: self._newTreeItem(nwItemType.FOLDER))
|
||||
self.projMenu.addAction(self.aCreateFolder)
|
||||
|
||||
# Project > Separator
|
||||
@@ -277,7 +247,7 @@ class GuiMainMenu(QMenuBar):
|
||||
# Document > New
|
||||
self.aNewDoc = QAction(self.tr("New Document"), self)
|
||||
self.aNewDoc.setShortcut("Ctrl+N")
|
||||
self.aNewDoc.triggered.connect(lambda: self._newTreeItem(nwItemType.FILE, None))
|
||||
self.aNewDoc.triggered.connect(lambda: self._newTreeItem(nwItemType.FILE))
|
||||
self.docuMenu.addAction(self.aNewDoc)
|
||||
|
||||
# Document > Open
|
||||
@@ -907,19 +877,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aRebuildIndex.triggered.connect(lambda: self.theParent.rebuildIndex())
|
||||
self.toolsMenu.addAction(self.aRebuildIndex)
|
||||
|
||||
# Tools > Rebuild Outline
|
||||
self.aRebuildOutline = QAction(self.tr("Rebuild Outline"), self)
|
||||
self.aRebuildOutline.setShortcut("F10")
|
||||
self.aRebuildOutline.triggered.connect(lambda: self.theParent.rebuildOutline())
|
||||
self.toolsMenu.addAction(self.aRebuildOutline)
|
||||
|
||||
# Tools > Toggle Auto Build Outline
|
||||
self.aAutoOutline = QAction(self.tr("Auto-Update Outline"), self)
|
||||
self.aAutoOutline.setCheckable(True)
|
||||
self.aAutoOutline.toggled.connect(self._toggleAutoOutline)
|
||||
self.aAutoOutline.setShortcut("Ctrl+F10")
|
||||
self.toolsMenu.addAction(self.aAutoOutline)
|
||||
|
||||
# Tools > Separator
|
||||
self.toolsMenu.addSeparator()
|
||||
|
||||
|
||||
@@ -29,7 +29,9 @@ import novelwriter
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView
|
||||
from PyQt5.QtWidgets import (
|
||||
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QFrame
|
||||
)
|
||||
|
||||
from novelwriter.common import checkInt
|
||||
from novelwriter.constants import nwKeyWords
|
||||
@@ -52,7 +54,6 @@ class GuiNovelTree(QTreeWidget):
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.theIndex = theParent.theIndex
|
||||
|
||||
# Internal Variables
|
||||
self._treeMap = {}
|
||||
@@ -60,6 +61,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
|
||||
# Build GUI
|
||||
iPx = self.theTheme.baseIconSize
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setIndentation(iPx)
|
||||
self.setColumnCount(3)
|
||||
@@ -134,7 +136,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
"""
|
||||
logger.verbose("Requesting refresh of the novel tree")
|
||||
treeChanged = self.theParent.treeView.changedSince(self._lastBuild)
|
||||
indexChanged = self.theIndex.novelChangedSince(self._lastBuild)
|
||||
indexChanged = self.theProject.index.indexChangedSince(self._lastBuild)
|
||||
if not (treeChanged or indexChanged or overRide):
|
||||
logger.verbose("No changes have been made to the novel index")
|
||||
return
|
||||
@@ -144,7 +146,6 @@ class GuiNovelTree(QTreeWidget):
|
||||
if selItem:
|
||||
titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2]
|
||||
|
||||
self.theParent.treeView.flushTreeOrder()
|
||||
self._populateTree()
|
||||
|
||||
if titleKey is not None and titleKey in self._treeMap:
|
||||
@@ -155,7 +156,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
def updateWordCounts(self, tHandle):
|
||||
"""Update the word count for a given handle.
|
||||
"""
|
||||
tHeaders = self.theIndex.getHandleWordCounts(tHandle)
|
||||
tHeaders = self.theProject.index.getHandleWordCounts(tHandle)
|
||||
for titleKey, wCount in tHeaders:
|
||||
if titleKey in self._treeMap:
|
||||
self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}")
|
||||
@@ -249,12 +250,12 @@ class GuiNovelTree(QTreeWidget):
|
||||
currChapter = None
|
||||
currScene = None
|
||||
|
||||
for tKey, tHandle, sTitle, novIdx in self.theIndex.novelStructure(skipExcluded=True):
|
||||
for tKey, tHandle, sTitle, novIdx in self.theProject.index.novelStructure(skipExcl=True):
|
||||
|
||||
tItem = self._createTreeItem(tHandle, sTitle, tKey, novIdx)
|
||||
self._treeMap[tKey] = tItem
|
||||
|
||||
tLevel = novIdx["level"]
|
||||
tLevel = novIdx.level
|
||||
if tLevel == "H1":
|
||||
self.addTopLevelItem(tItem)
|
||||
currTitle = tItem
|
||||
@@ -301,18 +302,18 @@ class GuiNovelTree(QTreeWidget):
|
||||
"""Populate a tree item with all the column values.
|
||||
"""
|
||||
newItem = QTreeWidgetItem()
|
||||
hIcon = "doc_%s" % novIdx["level"].lower()
|
||||
hIcon = "doc_%s" % novIdx.level.lower()
|
||||
theData = (tHandle, sTitle[1:].lstrip("0"), titleKey)
|
||||
|
||||
wC = int(novIdx["wCount"])
|
||||
wC = int(novIdx.wordCount)
|
||||
|
||||
newItem.setText(self.C_TITLE, novIdx["title"])
|
||||
newItem.setText(self.C_TITLE, novIdx.title)
|
||||
newItem.setData(self.C_TITLE, Qt.UserRole, theData)
|
||||
newItem.setIcon(self.C_TITLE, self.theTheme.getIcon(hIcon))
|
||||
newItem.setText(self.C_WORDS, f"{wC:n}")
|
||||
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
|
||||
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
||||
theRefs = self.theProject.index.getReferences(tHandle, sTitle)
|
||||
newItem.setText(self.C_POV, ", ".join(theRefs[nwKeyWords.POV_KEY]))
|
||||
|
||||
return newItem
|
||||
|
||||
@@ -4,7 +4,11 @@ novelWriter – GUI Project Outline
|
||||
GUI class for the project outline view
|
||||
|
||||
File History:
|
||||
Created: 2019-11-16 [0.4.1]
|
||||
Created: 2022-05-15 [1.7b1] GuiOutline
|
||||
Created: 2022-05-22 [1.7b1] GuiOutlineToolBar
|
||||
Created: 2019-11-16 [0.4.1] GuiOutlineView
|
||||
Created: 2019-11-16 [0.4.1] GuiOutlineHeaderMenu
|
||||
Created: 2020-06-02 [0.7.0] GuiOutlineDetails
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
||||
@@ -27,20 +31,244 @@ import logging
|
||||
import novelwriter
|
||||
|
||||
from time import time
|
||||
from enum import Enum
|
||||
|
||||
from PyQt5.QtCore import Qt, QSize, pyqtSlot
|
||||
from PyQt5.QtCore import (
|
||||
Qt, pyqtSignal, pyqtSlot, QSize, QT_TRANSLATE_NOOP
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
QTreeWidget, QTreeWidgetItem, QMenu, QAction, QAbstractItemView
|
||||
QAbstractItemView, QAction, QGridLayout, QGroupBox, QHBoxLayout, QLabel,
|
||||
QMenu, QScrollArea, QSplitter, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
|
||||
QWidget, QFrame, QToolBar, QSizePolicy, QComboBox, QToolButton
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwItemLayout, nwItemType, nwOutline
|
||||
from novelwriter.enum import (
|
||||
nwDocMode, nwItemClass, nwItemLayout, nwItemType, nwOutline
|
||||
)
|
||||
from novelwriter.common import checkInt
|
||||
from novelwriter.constants import trConst, nwKeyWords, nwLabels
|
||||
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiOutline(QTreeWidget):
|
||||
class GuiOutline(QWidget):
|
||||
|
||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
|
||||
self.outlineBar = GuiOutlineToolBar(self)
|
||||
self.outlineView = GuiOutlineView(self)
|
||||
self.outlineData = GuiOutlineDetails(self)
|
||||
|
||||
self.splitOutline = QSplitter(Qt.Vertical)
|
||||
self.splitOutline.addWidget(self.outlineView)
|
||||
self.splitOutline.addWidget(self.outlineData)
|
||||
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos())
|
||||
|
||||
# Assemble
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||||
self.outerBox.addWidget(self.outlineBar)
|
||||
self.outerBox.addWidget(self.splitOutline)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
# Connect Signals
|
||||
self.outlineView.hiddenStateChanged.connect(self._updateMenuColumns)
|
||||
self.outlineView.activeItemChanged.connect(self.outlineData.showItem)
|
||||
self.outlineData.itemTagClicked.connect(self._tagClicked)
|
||||
self.outlineBar.loadNovelRootRequest.connect(self._rootItemChanged)
|
||||
self.outlineBar.viewColumnToggled.connect(self.outlineView.menuColumnToggled)
|
||||
|
||||
# Function Mappings
|
||||
self.getSelectedHandle = self.outlineView.getSelectedHandle
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def splitSizes(self):
|
||||
return self.splitOutline.sizes()
|
||||
|
||||
def clearOutline(self):
|
||||
self.outlineData.clearDetails()
|
||||
return
|
||||
|
||||
def initOutline(self):
|
||||
self.outlineView.initOutline()
|
||||
self.outlineData.initDetails()
|
||||
return
|
||||
|
||||
def closeOutline(self):
|
||||
self.outlineView.closeOutline()
|
||||
self.outlineData.updateClasses()
|
||||
return
|
||||
|
||||
def refreshView(self, overRide=False, novelChanged=False):
|
||||
self.outlineView.refreshTree(overRide=overRide, novelChanged=novelChanged)
|
||||
return
|
||||
|
||||
def treeFocus(self):
|
||||
return self.outlineView.hasFocus()
|
||||
|
||||
def setTreeFocus(self):
|
||||
return self.outlineView.setFocus()
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateRootItem(self, tHandle):
|
||||
"""Should be called whenever a root folders changes.
|
||||
"""
|
||||
self.outlineBar.populateNovelList()
|
||||
self.outlineData.updateClasses()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _updateMenuColumns(self):
|
||||
"""Trigger an update of the toggled state of the column menu
|
||||
checkboxes whenever a signal is received that the hidden state
|
||||
of columns has changed.
|
||||
"""
|
||||
self.outlineBar.setColumnHiddenState(self.outlineView.hiddenColumns)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _tagClicked(self, link):
|
||||
"""Capture the click of a tag in the details panel.
|
||||
"""
|
||||
if link:
|
||||
self.loadDocumentTagRequest.emit(link, nwDocMode.VIEW)
|
||||
return
|
||||
|
||||
@pyqtSlot(str)
|
||||
def _rootItemChanged(self, handle):
|
||||
"""The root novel handle has changed or needs to be refreshed.
|
||||
"""
|
||||
self.outlineView.refreshTree(rootHandle=(handle or None), overRide=True)
|
||||
return
|
||||
|
||||
# END Class GuiOutline
|
||||
|
||||
|
||||
class GuiOutlineToolBar(QToolBar):
|
||||
|
||||
loadNovelRootRequest = pyqtSignal(str)
|
||||
viewColumnToggled = pyqtSignal(bool, Enum)
|
||||
|
||||
def __init__(self, theOutline):
|
||||
QTreeWidget.__init__(self, theOutline)
|
||||
|
||||
logger.debug("Initialising GuiOutlineToolBar ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theOutline.theParent
|
||||
self.theProject = theOutline.theParent.theProject
|
||||
self.theTheme = theOutline.theParent.theTheme
|
||||
|
||||
iPx = self.mainConf.pxInt(22)
|
||||
mPx = self.mainConf.pxInt(12)
|
||||
|
||||
self.setMovable(False)
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||
|
||||
stretch = QWidget(self)
|
||||
stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
# Novel Selector
|
||||
self.novelLabel = QLabel(self.tr("Outline of"))
|
||||
self.novelLabel.setContentsMargins(0, 0, mPx, 0)
|
||||
|
||||
self.novelValue = QComboBox(self)
|
||||
self.novelValue.setMinimumWidth(self.mainConf.pxInt(200))
|
||||
self.novelValue.currentIndexChanged.connect(self._novelValueChanged)
|
||||
|
||||
# Actions
|
||||
self.aRefresh = QAction(self.tr("Refresh"), self)
|
||||
self.aRefresh.setIcon(self.theTheme.getIcon("refresh"))
|
||||
self.aRefresh.triggered.connect(self._refreshRequested)
|
||||
|
||||
# Column Menu
|
||||
self.mColumns = GuiOutlineHeaderMenu(self)
|
||||
self.mColumns.columnToggled.connect(
|
||||
lambda isChecked, tItem: self.viewColumnToggled.emit(isChecked, tItem)
|
||||
)
|
||||
|
||||
self.tbColumns = QToolButton(self)
|
||||
self.tbColumns.setIcon(self.theTheme.getIcon("menu"))
|
||||
self.tbColumns.setMenu(self.mColumns)
|
||||
self.tbColumns.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
# Assemble
|
||||
self.addWidget(self.novelLabel)
|
||||
self.addWidget(self.novelValue)
|
||||
self.addSeparator()
|
||||
self.addAction(self.aRefresh)
|
||||
self.addWidget(self.tbColumns)
|
||||
self.addWidget(stretch)
|
||||
|
||||
logger.debug("GuiOutlineToolBar initialisation complete")
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def populateNovelList(self):
|
||||
"""Fill the novel combo box with a list of all novel folders.
|
||||
"""
|
||||
self.novelValue.clear()
|
||||
tIcon = self.theTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
|
||||
for tHandle, nwItem in self.theProject.tree.iterRoots(nwItemClass.NOVEL):
|
||||
self.novelValue.addItem(tIcon, nwItem.itemName, tHandle)
|
||||
self.novelValue.insertSeparator(self.novelValue.count())
|
||||
self.novelValue.addItem(tIcon, self.tr("All Novel Folders"), "")
|
||||
return
|
||||
|
||||
def setColumnHiddenState(self, hiddenState):
|
||||
"""Forward the change of column hidden states to the menu.
|
||||
"""
|
||||
self.mColumns.setHiddenState(hiddenState)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _novelValueChanged(self, index):
|
||||
"""Emit a signal containing the handle of the selected item.
|
||||
"""
|
||||
if index >= 0:
|
||||
self.loadNovelRootRequest.emit(self.novelValue.currentData())
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _refreshRequested(self):
|
||||
"""Emit a signal containing the handle of the selected item.
|
||||
"""
|
||||
self.loadNovelRootRequest.emit(self.novelValue.currentData())
|
||||
return
|
||||
|
||||
# END Class GuiOutlineToolBar
|
||||
|
||||
|
||||
class GuiOutlineView(QTreeWidget):
|
||||
|
||||
DEF_WIDTH = {
|
||||
nwOutline.TITLE: 200,
|
||||
@@ -82,19 +310,20 @@ class GuiOutline(QTreeWidget):
|
||||
nwOutline.SYNOP: False,
|
||||
}
|
||||
|
||||
def __init__(self, theParent):
|
||||
QTreeWidget.__init__(self, theParent)
|
||||
hiddenStateChanged = pyqtSignal()
|
||||
activeItemChanged = pyqtSignal(str, str)
|
||||
|
||||
logger.debug("Initialising GuiOutline ...")
|
||||
def __init__(self, theOutline):
|
||||
QTreeWidget.__init__(self, theOutline)
|
||||
|
||||
logger.debug("Initialising GuiOutlineView ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.optState = theParent.theProject.optState
|
||||
self.headerMenu = GuiOutlineHeaderMenu(self)
|
||||
self.theParent = theOutline.theParent
|
||||
self.theProject = theOutline.theParent.theProject
|
||||
self.theTheme = theOutline.theParent.theTheme
|
||||
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
@@ -107,8 +336,6 @@ class GuiOutline(QTreeWidget):
|
||||
self.setIndentation(iPx)
|
||||
|
||||
self.treeHead = self.header()
|
||||
self.treeHead.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
self.treeHead.customContextMenuRequested.connect(self._headerRightClick)
|
||||
self.treeHead.sectionMoved.connect(self._columnMoved)
|
||||
|
||||
# Internals
|
||||
@@ -122,12 +349,25 @@ class GuiOutline(QTreeWidget):
|
||||
|
||||
self.initOutline()
|
||||
self.clearOutline()
|
||||
self.headerMenu.setHiddenState(self._colHidden)
|
||||
|
||||
logger.debug("GuiOutline initialisation complete")
|
||||
self.hiddenStateChanged.emit()
|
||||
|
||||
logger.debug("GuiOutlineView initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def hiddenColumns(self):
|
||||
return self._colHidden
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def initOutline(self):
|
||||
"""Set or update outline settings.
|
||||
"""
|
||||
@@ -167,7 +407,7 @@ class GuiOutline(QTreeWidget):
|
||||
|
||||
return
|
||||
|
||||
def refreshTree(self, overRide=False, novelChanged=False):
|
||||
def refreshTree(self, rootHandle=None, overRide=False, novelChanged=False):
|
||||
"""Called whenever the Outline tab is activated and controls
|
||||
what data to load, and if necessary, force a rebuild of the
|
||||
tree.
|
||||
@@ -175,17 +415,17 @@ class GuiOutline(QTreeWidget):
|
||||
# If it's the first time, we always build
|
||||
if self._firstView or self._firstView and overRide:
|
||||
self._loadHeaderState()
|
||||
self._populateTree()
|
||||
self._populateTree(rootHandle)
|
||||
self._firstView = False
|
||||
return
|
||||
|
||||
# If the novel index or novel tree has changed since the tree
|
||||
# was last built, we rebuild the tree from the updated index.
|
||||
indexChanged = self.theIndex.novelChangedSince(self._lastBuild)
|
||||
indexChanged = self.theProject.index.rootChangedSince(rootHandle, self._lastBuild)
|
||||
doBuild = (novelChanged or indexChanged) and self.theProject.autoOutline
|
||||
if doBuild or overRide:
|
||||
logger.debug("Rebuilding Project Outline")
|
||||
self._populateTree()
|
||||
self._populateTree(rootHandle)
|
||||
|
||||
return
|
||||
|
||||
@@ -221,7 +461,7 @@ class GuiOutline(QTreeWidget):
|
||||
document editor.
|
||||
"""
|
||||
tHandle, tLine = self.getSelectedHandle()
|
||||
self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True)
|
||||
self.theParent.openDocument(tHandle, tLine=tLine - 1, doScroll=True)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -233,18 +473,10 @@ class GuiOutline(QTreeWidget):
|
||||
if selItems:
|
||||
tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], Qt.UserRole)
|
||||
sTitle = selItems[0].data(self._colIdx[nwOutline.LINE], Qt.UserRole)
|
||||
self.theParent.projMeta.showItem(tHandle, sTitle)
|
||||
self.theParent.treeView.setSelectedHandle(tHandle)
|
||||
self.activeItemChanged.emit(tHandle, sTitle)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot("QPoint")
|
||||
def _headerRightClick(self, clickPos):
|
||||
"""Show the header column menu.
|
||||
"""
|
||||
self.headerMenu.exec_(self.mapToGlobal(clickPos))
|
||||
return
|
||||
|
||||
@pyqtSlot(int, int, int)
|
||||
def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx):
|
||||
"""Make sure the order array is up to date with the actual order
|
||||
@@ -254,9 +486,10 @@ class GuiOutline(QTreeWidget):
|
||||
self._saveHeaderState()
|
||||
return
|
||||
|
||||
def _menuColumnToggled(self, isChecked, theItem):
|
||||
@pyqtSlot(bool, Enum)
|
||||
def menuColumnToggled(self, isChecked, theItem):
|
||||
"""Receive the changes to column visibility forwarded by the
|
||||
header context menu.
|
||||
column selection menu.
|
||||
"""
|
||||
logger.verbose("User toggled Outline column '%s'", theItem.name)
|
||||
if theItem in self._colIdx:
|
||||
@@ -273,10 +506,12 @@ class GuiOutline(QTreeWidget):
|
||||
"""Load the state of the main tree header, that is, column order
|
||||
and column width.
|
||||
"""
|
||||
pOptions = self.theProject.options
|
||||
|
||||
# Load whatever we saved last time, regardless of wether it
|
||||
# contains the correct names or number of columns. The names
|
||||
# must be valid though.
|
||||
tempOrder = self.optState.getValue("GuiOutline", "headerOrder", [])
|
||||
tempOrder = pOptions.getValue("GuiOutline", "headerOrder", [])
|
||||
treeOrder = []
|
||||
for hName in tempOrder:
|
||||
try:
|
||||
@@ -299,21 +534,21 @@ class GuiOutline(QTreeWidget):
|
||||
|
||||
# We load whatever column widths and hidden states we find in
|
||||
# the file, and leave the rest in their default state.
|
||||
tmpWidth = self.optState.getValue("GuiOutline", "columnWidth", {})
|
||||
tmpWidth = pOptions.getValue("GuiOutline", "columnWidth", {})
|
||||
for hName in tmpWidth:
|
||||
try:
|
||||
self._colWidth[nwOutline[hName]] = self.mainConf.pxInt(tmpWidth[hName])
|
||||
except Exception:
|
||||
logger.warning("Ignored unknown outline column '%s'", str(hName))
|
||||
|
||||
tmpHidden = self.optState.getValue("GuiOutline", "columnHidden", {})
|
||||
tmpHidden = pOptions.getValue("GuiOutline", "columnHidden", {})
|
||||
for hName in tmpHidden:
|
||||
try:
|
||||
self._colHidden[nwOutline[hName]] = tmpHidden[hName]
|
||||
except Exception:
|
||||
logger.warning("Ignored unknown outline column '%s'", str(hName))
|
||||
|
||||
self.headerMenu.setHiddenState(self._colHidden)
|
||||
self.hiddenStateChanged.emit()
|
||||
|
||||
return
|
||||
|
||||
@@ -347,14 +582,15 @@ class GuiOutline(QTreeWidget):
|
||||
if not logHidden and logWidth > 0:
|
||||
colWidth[hName] = logWidth
|
||||
|
||||
self.optState.setValue("GuiOutline", "headerOrder", treeOrder)
|
||||
self.optState.setValue("GuiOutline", "columnWidth", colWidth)
|
||||
self.optState.setValue("GuiOutline", "columnHidden", colHidden)
|
||||
self.optState.saveSettings()
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiOutline", "headerOrder", treeOrder)
|
||||
pOptions.setValue("GuiOutline", "columnWidth", colWidth)
|
||||
pOptions.setValue("GuiOutline", "columnHidden", colHidden)
|
||||
pOptions.saveSettings()
|
||||
|
||||
return
|
||||
|
||||
def _populateTree(self):
|
||||
def _populateTree(self, rootHandle):
|
||||
"""Build the tree based on the project index, and the header
|
||||
based on the defined constants, default values and user selected
|
||||
width, order and hidden state. All columns are populated, even
|
||||
@@ -387,11 +623,12 @@ class GuiOutline(QTreeWidget):
|
||||
currChapter = None
|
||||
currScene = None
|
||||
|
||||
for tKey, tHandle, sTitle, novIdx in self.theIndex.novelStructure(skipExcluded=True):
|
||||
novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||
for _, tHandle, sTitle, novIdx in novStruct:
|
||||
|
||||
tItem = self._createTreeItem(tHandle, sTitle, novIdx)
|
||||
|
||||
tLevel = novIdx["level"]
|
||||
tLevel = novIdx.level
|
||||
if tLevel == "H1":
|
||||
self.addTopLevelItem(tItem)
|
||||
currTitle = tItem
|
||||
@@ -437,26 +674,26 @@ class GuiOutline(QTreeWidget):
|
||||
def _createTreeItem(self, tHandle, sTitle, novIdx):
|
||||
"""Populate a tree item with all the column values.
|
||||
"""
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
newItem = QTreeWidgetItem()
|
||||
hIcon = "doc_%s" % novIdx["level"].lower()
|
||||
hIcon = "doc_%s" % novIdx.level.lower()
|
||||
|
||||
hLevel = self.theIndex.getHandleHeaderLevel(tHandle)
|
||||
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
dIcon = self.theTheme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, hLevel)
|
||||
|
||||
cC = int(novIdx["cCount"])
|
||||
wC = int(novIdx["wCount"])
|
||||
pC = int(novIdx["pCount"])
|
||||
cC = int(novIdx.charCount)
|
||||
wC = int(novIdx.wordCount)
|
||||
pC = int(novIdx.paraCount)
|
||||
|
||||
newItem.setText(self._colIdx[nwOutline.TITLE], novIdx["title"])
|
||||
newItem.setText(self._colIdx[nwOutline.TITLE], novIdx.title)
|
||||
newItem.setData(self._colIdx[nwOutline.TITLE], Qt.UserRole, tHandle)
|
||||
newItem.setIcon(self._colIdx[nwOutline.TITLE], self.theTheme.getIcon(hIcon))
|
||||
newItem.setText(self._colIdx[nwOutline.LEVEL], novIdx["level"])
|
||||
newItem.setText(self._colIdx[nwOutline.LEVEL], novIdx.level)
|
||||
newItem.setText(self._colIdx[nwOutline.LABEL], nwItem.itemName)
|
||||
newItem.setIcon(self._colIdx[nwOutline.LABEL], dIcon)
|
||||
newItem.setText(self._colIdx[nwOutline.LINE], sTitle[1:].lstrip("0"))
|
||||
newItem.setData(self._colIdx[nwOutline.LINE], Qt.UserRole, sTitle)
|
||||
newItem.setText(self._colIdx[nwOutline.SYNOP], novIdx["synopsis"])
|
||||
newItem.setText(self._colIdx[nwOutline.SYNOP], novIdx.synopsis)
|
||||
newItem.setText(self._colIdx[nwOutline.CCOUNT], f"{cC:n}")
|
||||
newItem.setText(self._colIdx[nwOutline.WCOUNT], f"{wC:n}")
|
||||
newItem.setText(self._colIdx[nwOutline.PCOUNT], f"{pC:n}")
|
||||
@@ -464,7 +701,7 @@ class GuiOutline(QTreeWidget):
|
||||
newItem.setTextAlignment(self._colIdx[nwOutline.WCOUNT], Qt.AlignRight)
|
||||
newItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], Qt.AlignRight)
|
||||
|
||||
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
||||
theRefs = self.theProject.index.getReferences(tHandle, sTitle)
|
||||
newItem.setText(self._colIdx[nwOutline.POV], ", ".join(theRefs[nwKeyWords.POV_KEY]))
|
||||
newItem.setText(self._colIdx[nwOutline.FOCUS], ", ".join(theRefs[nwKeyWords.FOCUS_KEY]))
|
||||
newItem.setText(self._colIdx[nwOutline.CHAR], ", ".join(theRefs[nwKeyWords.CHAR_KEY]))
|
||||
@@ -477,15 +714,16 @@ class GuiOutline(QTreeWidget):
|
||||
|
||||
return newItem
|
||||
|
||||
# END Class GuiOutline
|
||||
# END Class GuiOutlineView
|
||||
|
||||
|
||||
class GuiOutlineHeaderMenu(QMenu):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QMenu.__init__(self, theParent)
|
||||
columnToggled = pyqtSignal(bool, Enum)
|
||||
|
||||
def __init__(self, theOutline):
|
||||
QMenu.__init__(self, theOutline)
|
||||
|
||||
self.theParent = theParent
|
||||
self.acceptToggle = True
|
||||
|
||||
mnuHead = QAction(self.tr("Select Columns"), self)
|
||||
@@ -499,7 +737,7 @@ class GuiOutlineHeaderMenu(QMenu):
|
||||
self.actionMap[hItem] = QAction(trConst(nwLabels.OUTLINE_COLS[hItem]), self)
|
||||
self.actionMap[hItem].setCheckable(True)
|
||||
self.actionMap[hItem].toggled.connect(
|
||||
lambda isChecked, tItem=hItem: self._columnToggled(isChecked, tItem)
|
||||
lambda isChecked, tItem=hItem: self.columnToggled.emit(isChecked, tItem)
|
||||
)
|
||||
self.addAction(self.actionMap[hItem])
|
||||
|
||||
@@ -520,16 +758,338 @@ class GuiOutlineHeaderMenu(QMenu):
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiOutlineHeaderMenu
|
||||
|
||||
|
||||
class GuiOutlineDetails(QScrollArea):
|
||||
|
||||
LVL_MAP = {
|
||||
"H1": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Title"),
|
||||
"H2": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Chapter"),
|
||||
"H3": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Scene"),
|
||||
"H4": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Section"),
|
||||
}
|
||||
|
||||
itemTagClicked = pyqtSignal(str)
|
||||
|
||||
def __init__(self, theOutline):
|
||||
QScrollArea.__init__(self, theOutline)
|
||||
|
||||
logger.debug("Initialising GuiOutlineDetails ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theOutline = theOutline
|
||||
self.theParent = theOutline.theParent
|
||||
self.theProject = theOutline.theParent.theProject
|
||||
self.theTheme = theOutline.theParent.theTheme
|
||||
|
||||
# Sizes
|
||||
minTitle = 30*self.theTheme.textNWidth
|
||||
maxTitle = 40*self.theTheme.textNWidth
|
||||
wCount = self.theTheme.getTextWidth("999,999")
|
||||
hSpace = int(self.mainConf.pxInt(10))
|
||||
vSpace = int(self.mainConf.pxInt(4))
|
||||
|
||||
# Details Area
|
||||
self.titleLabel = QLabel("<b>%s</b>" % self.tr("Title"))
|
||||
self.fileLabel = QLabel("<b>%s</b>" % self.tr("Document"))
|
||||
self.itemLabel = QLabel("<b>%s</b>" % self.tr("Status"))
|
||||
self.titleValue = QLabel("")
|
||||
self.fileValue = QLabel("")
|
||||
self.itemValue = QLabel("")
|
||||
|
||||
self.titleValue.setMinimumWidth(minTitle)
|
||||
self.titleValue.setMaximumWidth(maxTitle)
|
||||
self.fileValue.setMinimumWidth(minTitle)
|
||||
self.fileValue.setMaximumWidth(maxTitle)
|
||||
self.itemValue.setMinimumWidth(minTitle)
|
||||
self.itemValue.setMaximumWidth(maxTitle)
|
||||
|
||||
# Stats Area
|
||||
self.cCLabel = QLabel("<b>%s</b>" % self.tr("Characters"))
|
||||
self.wCLabel = QLabel("<b>%s</b>" % self.tr("Words"))
|
||||
self.pCLabel = QLabel("<b>%s</b>" % self.tr("Paragraphs"))
|
||||
self.cCValue = QLabel("")
|
||||
self.wCValue = QLabel("")
|
||||
self.pCValue = QLabel("")
|
||||
|
||||
self.cCValue.setMinimumWidth(wCount)
|
||||
self.wCValue.setMinimumWidth(wCount)
|
||||
self.pCValue.setMinimumWidth(wCount)
|
||||
self.cCValue.setAlignment(Qt.AlignRight)
|
||||
self.wCValue.setAlignment(Qt.AlignRight)
|
||||
self.pCValue.setAlignment(Qt.AlignRight)
|
||||
|
||||
# Synopsis
|
||||
self.synopLabel = QLabel("<b>%s</b>" % self.tr("Synopsis"))
|
||||
self.synopValue = QLabel("")
|
||||
self.synopLWrap = QHBoxLayout()
|
||||
self.synopValue.setWordWrap(True)
|
||||
self.synopValue.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
||||
self.synopLWrap.addWidget(self.synopValue, 1)
|
||||
|
||||
# Tags
|
||||
self.povKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]))
|
||||
self.focKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]))
|
||||
self.chrKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY]))
|
||||
self.pltKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]))
|
||||
self.timKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.TIME_KEY]))
|
||||
self.wldKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY]))
|
||||
self.objKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY]))
|
||||
self.entKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY]))
|
||||
self.cstKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY]))
|
||||
|
||||
self.povKeyLWrap = QHBoxLayout()
|
||||
self.focKeyLWrap = QHBoxLayout()
|
||||
self.chrKeyLWrap = QHBoxLayout()
|
||||
self.pltKeyLWrap = QHBoxLayout()
|
||||
self.timKeyLWrap = QHBoxLayout()
|
||||
self.wldKeyLWrap = QHBoxLayout()
|
||||
self.objKeyLWrap = QHBoxLayout()
|
||||
self.entKeyLWrap = QHBoxLayout()
|
||||
self.cstKeyLWrap = QHBoxLayout()
|
||||
|
||||
self.povKeyValue = QLabel("")
|
||||
self.focKeyValue = QLabel("")
|
||||
self.chrKeyValue = QLabel("")
|
||||
self.pltKeyValue = QLabel("")
|
||||
self.timKeyValue = QLabel("")
|
||||
self.wldKeyValue = QLabel("")
|
||||
self.objKeyValue = QLabel("")
|
||||
self.entKeyValue = QLabel("")
|
||||
self.cstKeyValue = QLabel("")
|
||||
|
||||
self.povKeyValue.setWordWrap(True)
|
||||
self.focKeyValue.setWordWrap(True)
|
||||
self.chrKeyValue.setWordWrap(True)
|
||||
self.pltKeyValue.setWordWrap(True)
|
||||
self.timKeyValue.setWordWrap(True)
|
||||
self.wldKeyValue.setWordWrap(True)
|
||||
self.objKeyValue.setWordWrap(True)
|
||||
self.entKeyValue.setWordWrap(True)
|
||||
self.cstKeyValue.setWordWrap(True)
|
||||
|
||||
def tagClicked(link):
|
||||
self.itemTagClicked.emit(link)
|
||||
|
||||
self.povKeyValue.linkActivated.connect(tagClicked)
|
||||
self.focKeyValue.linkActivated.connect(tagClicked)
|
||||
self.chrKeyValue.linkActivated.connect(tagClicked)
|
||||
self.pltKeyValue.linkActivated.connect(tagClicked)
|
||||
self.timKeyValue.linkActivated.connect(tagClicked)
|
||||
self.wldKeyValue.linkActivated.connect(tagClicked)
|
||||
self.objKeyValue.linkActivated.connect(tagClicked)
|
||||
self.entKeyValue.linkActivated.connect(tagClicked)
|
||||
self.cstKeyValue.linkActivated.connect(tagClicked)
|
||||
|
||||
self.povKeyLWrap.addWidget(self.povKeyValue, 1)
|
||||
self.focKeyLWrap.addWidget(self.focKeyValue, 1)
|
||||
self.chrKeyLWrap.addWidget(self.chrKeyValue, 1)
|
||||
self.pltKeyLWrap.addWidget(self.pltKeyValue, 1)
|
||||
self.timKeyLWrap.addWidget(self.timKeyValue, 1)
|
||||
self.wldKeyLWrap.addWidget(self.wldKeyValue, 1)
|
||||
self.objKeyLWrap.addWidget(self.objKeyValue, 1)
|
||||
self.entKeyLWrap.addWidget(self.entKeyValue, 1)
|
||||
self.cstKeyLWrap.addWidget(self.cstKeyValue, 1)
|
||||
|
||||
# Selected Item Details
|
||||
self.mainGroup = QGroupBox(self.tr("Title Details"), self)
|
||||
self.mainForm = QGridLayout()
|
||||
self.mainGroup.setLayout(self.mainForm)
|
||||
|
||||
self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
||||
self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
||||
self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
||||
self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft)
|
||||
|
||||
self.mainForm.setColumnStretch(1, 1)
|
||||
self.mainForm.setRowStretch(4, 1)
|
||||
self.mainForm.setHorizontalSpacing(hSpace)
|
||||
self.mainForm.setVerticalSpacing(vSpace)
|
||||
|
||||
# Selected Item Tags
|
||||
self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self)
|
||||
self.tagsForm = QGridLayout()
|
||||
self.tagsGroup.setLayout(self.tagsForm)
|
||||
|
||||
self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.focKeyLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.focKeyLWrap, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.chrKeyLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.chrKeyLWrap, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.pltKeyLabel, 3, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.pltKeyLWrap, 3, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.timKeyLabel, 4, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.timKeyLWrap, 4, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.wldKeyLabel, 5, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.wldKeyLWrap, 5, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.objKeyLabel, 6, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.objKeyLWrap, 6, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.entKeyLabel, 7, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.entKeyLWrap, 7, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.cstKeyLabel, 8, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.cstKeyLWrap, 8, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
|
||||
self.tagsForm.setColumnStretch(1, 1)
|
||||
self.tagsForm.setRowStretch(8, 1)
|
||||
self.tagsForm.setHorizontalSpacing(hSpace)
|
||||
self.tagsForm.setVerticalSpacing(vSpace)
|
||||
|
||||
# Assemble
|
||||
self.outerWidget = QWidget()
|
||||
self.outerBox = QHBoxLayout()
|
||||
self.outerBox.addWidget(self.mainGroup, 0)
|
||||
self.outerBox.addWidget(self.tagsGroup, 1)
|
||||
|
||||
self.outerWidget.setLayout(self.outerBox)
|
||||
self.setWidget(self.outerWidget)
|
||||
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setWidgetResizable(True)
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
|
||||
self.initDetails()
|
||||
|
||||
logger.debug("GuiOutlineDetails initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
def initDetails(self):
|
||||
"""Set or update outline settings.
|
||||
"""
|
||||
# Scroll bars
|
||||
if self.mainConf.hideVScroll:
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
else:
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
|
||||
if self.mainConf.hideHScroll:
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
else:
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
|
||||
self.updateClasses()
|
||||
|
||||
return
|
||||
|
||||
def clearDetails(self):
|
||||
"""Clear all the data labels.
|
||||
"""
|
||||
self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
|
||||
self.titleValue.setText("")
|
||||
self.fileValue.setText("")
|
||||
self.itemValue.setText("")
|
||||
self.cCValue.setText("")
|
||||
self.wCValue.setText("")
|
||||
self.pCValue.setText("")
|
||||
self.synopValue.setText("")
|
||||
self.povKeyValue.setText("")
|
||||
self.focKeyValue.setText("")
|
||||
self.chrKeyValue.setText("")
|
||||
self.pltKeyValue.setText("")
|
||||
self.timKeyValue.setText("")
|
||||
self.wldKeyValue.setText("")
|
||||
self.objKeyValue.setText("")
|
||||
self.entKeyValue.setText("")
|
||||
self.cstKeyValue.setText("")
|
||||
self.updateClasses()
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
def _columnToggled(self, isChecked, theItem):
|
||||
"""The user has toggled the visibility of a column. Forward the
|
||||
event to the parent class only if we're accepting changes.
|
||||
@pyqtSlot(str, str)
|
||||
def showItem(self, tHandle, sTitle):
|
||||
"""Update the content of the tree with the given handle and line
|
||||
number pointing to a header.
|
||||
"""
|
||||
if self.acceptToggle:
|
||||
self.theParent._menuColumnToggled(isChecked, theItem)
|
||||
pIndex = self.theProject.index
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
novIdx = pIndex.getNovelData(tHandle, sTitle)
|
||||
theRefs = pIndex.getReferences(tHandle, sTitle)
|
||||
if nwItem is None or novIdx is None:
|
||||
return False
|
||||
|
||||
if novIdx.level in self.LVL_MAP:
|
||||
self.titleLabel.setText("<b>%s</b>" % self.tr(self.LVL_MAP[novIdx.level]))
|
||||
else:
|
||||
self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
|
||||
self.titleValue.setText(novIdx.title)
|
||||
|
||||
itemStatus, _ = nwItem.getImportStatus()
|
||||
|
||||
self.fileValue.setText(nwItem.itemName)
|
||||
self.itemValue.setText(itemStatus)
|
||||
|
||||
cC = checkInt(novIdx.charCount, 0)
|
||||
wC = checkInt(novIdx.wordCount, 0)
|
||||
pC = checkInt(novIdx.paraCount, 0)
|
||||
|
||||
self.cCValue.setText(f"{cC:n}")
|
||||
self.wCValue.setText(f"{wC:n}")
|
||||
self.pCValue.setText(f"{pC:n}")
|
||||
|
||||
self.synopValue.setText(novIdx.synopsis)
|
||||
|
||||
self.povKeyValue.setText(self._formatTags(theRefs, nwKeyWords.POV_KEY))
|
||||
self.focKeyValue.setText(self._formatTags(theRefs, nwKeyWords.FOCUS_KEY))
|
||||
self.chrKeyValue.setText(self._formatTags(theRefs, nwKeyWords.CHAR_KEY))
|
||||
self.pltKeyValue.setText(self._formatTags(theRefs, nwKeyWords.PLOT_KEY))
|
||||
self.timKeyValue.setText(self._formatTags(theRefs, nwKeyWords.TIME_KEY))
|
||||
self.wldKeyValue.setText(self._formatTags(theRefs, nwKeyWords.WORLD_KEY))
|
||||
self.objKeyValue.setText(self._formatTags(theRefs, nwKeyWords.OBJECT_KEY))
|
||||
self.entKeyValue.setText(self._formatTags(theRefs, nwKeyWords.ENTITY_KEY))
|
||||
self.cstKeyValue.setText(self._formatTags(theRefs, nwKeyWords.CUSTOM_KEY))
|
||||
|
||||
return True
|
||||
|
||||
@pyqtSlot()
|
||||
def updateClasses(self):
|
||||
"""Update the visibility status of class details.
|
||||
"""
|
||||
usedClasses = self.theProject.tree.rootClasses()
|
||||
|
||||
pltVisible = nwItemClass.PLOT in usedClasses
|
||||
timVisible = nwItemClass.TIMELINE in usedClasses
|
||||
wldVisible = nwItemClass.WORLD in usedClasses
|
||||
objVisible = nwItemClass.OBJECT in usedClasses
|
||||
entVisible = nwItemClass.ENTITY in usedClasses
|
||||
cstVisible = nwItemClass.CUSTOM in usedClasses
|
||||
|
||||
self.pltKeyLabel.setVisible(pltVisible)
|
||||
self.pltKeyValue.setVisible(pltVisible)
|
||||
self.timKeyLabel.setVisible(timVisible)
|
||||
self.timKeyValue.setVisible(timVisible)
|
||||
self.wldKeyLabel.setVisible(wldVisible)
|
||||
self.wldKeyValue.setVisible(wldVisible)
|
||||
self.objKeyLabel.setVisible(objVisible)
|
||||
self.objKeyValue.setVisible(objVisible)
|
||||
self.entKeyLabel.setVisible(entVisible)
|
||||
self.entKeyValue.setVisible(entVisible)
|
||||
self.cstKeyLabel.setVisible(cstVisible)
|
||||
self.cstKeyValue.setVisible(cstVisible)
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiOutlineHeaderMenu
|
||||
@staticmethod
|
||||
def _formatTags(refs, key):
|
||||
"""Convert a list of tags into a list of clickable tag links.
|
||||
"""
|
||||
return ", ".join(
|
||||
[f"<a href='{tag}'>{tag}</a>" for tag in refs.get(key, [])]
|
||||
)
|
||||
|
||||
# END Class GuiOutlineDetails
|
||||
|
||||
@@ -1,349 +0,0 @@
|
||||
"""
|
||||
novelWriter – GUI Project Outline Details
|
||||
=========================================
|
||||
GUI class for the project outline details panel
|
||||
|
||||
File History:
|
||||
Created: 2020-06-02 [0.7.0]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from PyQt5.QtCore import Qt, QT_TRANSLATE_NOOP
|
||||
from PyQt5.QtWidgets import (
|
||||
QScrollArea, QWidget, QGridLayout, QHBoxLayout, QGroupBox, QLabel
|
||||
)
|
||||
|
||||
from novelwriter.common import checkInt
|
||||
from novelwriter.constants import trConst, nwKeyWords, nwLabels
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiOutlineDetails(QScrollArea):
|
||||
|
||||
LVL_MAP = {
|
||||
"H1": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Title"),
|
||||
"H2": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Chapter"),
|
||||
"H3": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Scene"),
|
||||
"H4": QT_TRANSLATE_NOOP("GuiOutlineDetails", "Section"),
|
||||
}
|
||||
|
||||
def __init__(self, theParent):
|
||||
QScrollArea.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising GuiOutlineDetails ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.optState = theParent.theProject.optState
|
||||
|
||||
# Sizes
|
||||
minTitle = 30*self.theTheme.textNWidth
|
||||
maxTitle = 40*self.theTheme.textNWidth
|
||||
wCount = self.theTheme.getTextWidth("999,999")
|
||||
hSpace = int(self.mainConf.pxInt(10))
|
||||
vSpace = int(self.mainConf.pxInt(4))
|
||||
|
||||
# Details Area
|
||||
self.titleLabel = QLabel("<b>%s</b>" % self.tr("Title"))
|
||||
self.fileLabel = QLabel("<b>%s</b>" % self.tr("Document"))
|
||||
self.itemLabel = QLabel("<b>%s</b>" % self.tr("Status"))
|
||||
self.titleValue = QLabel("")
|
||||
self.fileValue = QLabel("")
|
||||
self.itemValue = QLabel("")
|
||||
|
||||
self.titleValue.setMinimumWidth(minTitle)
|
||||
self.titleValue.setMaximumWidth(maxTitle)
|
||||
self.fileValue.setMinimumWidth(minTitle)
|
||||
self.fileValue.setMaximumWidth(maxTitle)
|
||||
self.itemValue.setMinimumWidth(minTitle)
|
||||
self.itemValue.setMaximumWidth(maxTitle)
|
||||
|
||||
# Stats Area
|
||||
self.cCLabel = QLabel("<b>%s</b>" % self.tr("Characters"))
|
||||
self.wCLabel = QLabel("<b>%s</b>" % self.tr("Words"))
|
||||
self.pCLabel = QLabel("<b>%s</b>" % self.tr("Paragraphs"))
|
||||
self.cCValue = QLabel("")
|
||||
self.wCValue = QLabel("")
|
||||
self.pCValue = QLabel("")
|
||||
|
||||
self.cCValue.setMinimumWidth(wCount)
|
||||
self.wCValue.setMinimumWidth(wCount)
|
||||
self.pCValue.setMinimumWidth(wCount)
|
||||
self.cCValue.setAlignment(Qt.AlignRight)
|
||||
self.wCValue.setAlignment(Qt.AlignRight)
|
||||
self.pCValue.setAlignment(Qt.AlignRight)
|
||||
|
||||
# Synopsis
|
||||
self.synopLabel = QLabel("<b>%s</b>" % self.tr("Synopsis"))
|
||||
self.synopValue = QLabel("")
|
||||
self.synopLWrap = QHBoxLayout()
|
||||
self.synopValue.setWordWrap(True)
|
||||
self.synopValue.setAlignment(Qt.AlignTop | Qt.AlignLeft)
|
||||
self.synopLWrap.addWidget(self.synopValue, 1)
|
||||
|
||||
# Tags
|
||||
self.povKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY]))
|
||||
self.focKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY]))
|
||||
self.chrKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.CHAR_KEY]))
|
||||
self.pltKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY]))
|
||||
self.timKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.TIME_KEY]))
|
||||
self.wldKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.WORLD_KEY]))
|
||||
self.objKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.OBJECT_KEY]))
|
||||
self.entKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.ENTITY_KEY]))
|
||||
self.cstKeyLabel = QLabel("<b>%s</b>" % trConst(nwLabels.KEY_NAME[nwKeyWords.CUSTOM_KEY]))
|
||||
|
||||
self.povKeyLWrap = QHBoxLayout()
|
||||
self.focKeyLWrap = QHBoxLayout()
|
||||
self.chrKeyLWrap = QHBoxLayout()
|
||||
self.pltKeyLWrap = QHBoxLayout()
|
||||
self.timKeyLWrap = QHBoxLayout()
|
||||
self.wldKeyLWrap = QHBoxLayout()
|
||||
self.objKeyLWrap = QHBoxLayout()
|
||||
self.entKeyLWrap = QHBoxLayout()
|
||||
self.cstKeyLWrap = QHBoxLayout()
|
||||
|
||||
self.povKeyValue = QLabel("")
|
||||
self.focKeyValue = QLabel("")
|
||||
self.chrKeyValue = QLabel("")
|
||||
self.pltKeyValue = QLabel("")
|
||||
self.timKeyValue = QLabel("")
|
||||
self.wldKeyValue = QLabel("")
|
||||
self.objKeyValue = QLabel("")
|
||||
self.entKeyValue = QLabel("")
|
||||
self.cstKeyValue = QLabel("")
|
||||
|
||||
self.povKeyValue.setWordWrap(True)
|
||||
self.focKeyValue.setWordWrap(True)
|
||||
self.chrKeyValue.setWordWrap(True)
|
||||
self.pltKeyValue.setWordWrap(True)
|
||||
self.timKeyValue.setWordWrap(True)
|
||||
self.wldKeyValue.setWordWrap(True)
|
||||
self.objKeyValue.setWordWrap(True)
|
||||
self.entKeyValue.setWordWrap(True)
|
||||
self.cstKeyValue.setWordWrap(True)
|
||||
|
||||
self.povKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.focKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.chrKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.pltKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.timKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.wldKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.objKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.entKeyValue.linkActivated.connect(self._tagClicked)
|
||||
self.cstKeyValue.linkActivated.connect(self._tagClicked)
|
||||
|
||||
self.povKeyLWrap.addWidget(self.povKeyValue, 1)
|
||||
self.focKeyLWrap.addWidget(self.focKeyValue, 1)
|
||||
self.chrKeyLWrap.addWidget(self.chrKeyValue, 1)
|
||||
self.pltKeyLWrap.addWidget(self.pltKeyValue, 1)
|
||||
self.timKeyLWrap.addWidget(self.timKeyValue, 1)
|
||||
self.wldKeyLWrap.addWidget(self.wldKeyValue, 1)
|
||||
self.objKeyLWrap.addWidget(self.objKeyValue, 1)
|
||||
self.entKeyLWrap.addWidget(self.entKeyValue, 1)
|
||||
self.cstKeyLWrap.addWidget(self.cstKeyValue, 1)
|
||||
|
||||
# Selected Item Details
|
||||
self.mainGroup = QGroupBox(self.tr("Title Details"), self)
|
||||
self.mainForm = QGridLayout()
|
||||
self.mainGroup.setLayout(self.mainForm)
|
||||
|
||||
self.mainForm.addWidget(self.titleLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.titleValue, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.cCLabel, 0, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.cCValue, 0, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
||||
self.mainForm.addWidget(self.fileLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.fileValue, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.wCLabel, 1, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.wCValue, 1, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
||||
self.mainForm.addWidget(self.itemLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.itemValue, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.pCLabel, 2, 2, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addWidget(self.pCValue, 2, 3, 1, 1, Qt.AlignTop | Qt.AlignRight)
|
||||
self.mainForm.addWidget(self.synopLabel, 3, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.mainForm.addLayout(self.synopLWrap, 4, 0, 1, 4, Qt.AlignTop | Qt.AlignLeft)
|
||||
|
||||
self.mainForm.setColumnStretch(1, 1)
|
||||
self.mainForm.setRowStretch(4, 1)
|
||||
self.mainForm.setHorizontalSpacing(hSpace)
|
||||
self.mainForm.setVerticalSpacing(vSpace)
|
||||
|
||||
# Selected Item Tags
|
||||
self.tagsGroup = QGroupBox(self.tr("Reference Tags"), self)
|
||||
self.tagsForm = QGridLayout()
|
||||
self.tagsGroup.setLayout(self.tagsForm)
|
||||
|
||||
self.tagsForm.addWidget(self.povKeyLabel, 0, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.povKeyLWrap, 0, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.focKeyLabel, 1, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.focKeyLWrap, 1, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.chrKeyLabel, 2, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.chrKeyLWrap, 2, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.pltKeyLabel, 3, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.pltKeyLWrap, 3, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.timKeyLabel, 4, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.timKeyLWrap, 4, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.wldKeyLabel, 5, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.wldKeyLWrap, 5, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.objKeyLabel, 6, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.objKeyLWrap, 6, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.entKeyLabel, 7, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.entKeyLWrap, 7, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addWidget(self.cstKeyLabel, 8, 0, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
self.tagsForm.addLayout(self.cstKeyLWrap, 8, 1, 1, 1, Qt.AlignTop | Qt.AlignLeft)
|
||||
|
||||
self.tagsForm.setColumnStretch(1, 1)
|
||||
self.tagsForm.setRowStretch(8, 1)
|
||||
self.tagsForm.setHorizontalSpacing(hSpace)
|
||||
self.tagsForm.setVerticalSpacing(vSpace)
|
||||
|
||||
# Assemble
|
||||
self.outerWidget = QWidget()
|
||||
self.outerBox = QHBoxLayout()
|
||||
self.outerBox.addWidget(self.mainGroup, 0)
|
||||
self.outerBox.addWidget(self.tagsGroup, 1)
|
||||
|
||||
self.outerWidget.setLayout(self.outerBox)
|
||||
self.setWidget(self.outerWidget)
|
||||
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setWidgetResizable(True)
|
||||
|
||||
self.initDetails()
|
||||
|
||||
logger.debug("GuiOutlineDetails initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
def initDetails(self):
|
||||
"""Set or update outline settings.
|
||||
"""
|
||||
# Scroll bars
|
||||
if self.mainConf.hideVScroll:
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
else:
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
|
||||
if self.mainConf.hideHScroll:
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
|
||||
else:
|
||||
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
|
||||
return
|
||||
|
||||
def clearDetails(self):
|
||||
"""Clear all the data labels.
|
||||
"""
|
||||
self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
|
||||
self.titleValue.setText("")
|
||||
self.fileValue.setText("")
|
||||
self.itemValue.setText("")
|
||||
self.cCValue.setText("")
|
||||
self.wCValue.setText("")
|
||||
self.pCValue.setText("")
|
||||
self.synopValue.setText("")
|
||||
self.povKeyValue.setText("")
|
||||
self.focKeyValue.setText("")
|
||||
self.chrKeyValue.setText("")
|
||||
self.pltKeyValue.setText("")
|
||||
self.timKeyValue.setText("")
|
||||
self.wldKeyValue.setText("")
|
||||
self.objKeyValue.setText("")
|
||||
self.entKeyValue.setText("")
|
||||
self.cstKeyValue.setText("")
|
||||
return
|
||||
|
||||
def showItem(self, tHandle, sTitle):
|
||||
"""Update the content of the tree with the given handle and line
|
||||
number pointing to a header.
|
||||
"""
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
novIdx = self.theIndex.getNovelData(tHandle, sTitle)
|
||||
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
||||
if nwItem is None or novIdx is None:
|
||||
return False
|
||||
|
||||
if novIdx["level"] in self.LVL_MAP:
|
||||
self.titleLabel.setText("<b>%s</b>" % self.tr(self.LVL_MAP[novIdx["level"]]))
|
||||
else:
|
||||
self.titleLabel.setText("<b>%s</b>" % self.tr("Title"))
|
||||
self.titleValue.setText(novIdx["title"])
|
||||
|
||||
self.fileValue.setText(nwItem.itemName)
|
||||
self.itemValue.setText(nwItem.itemStatus)
|
||||
|
||||
cC = checkInt(novIdx["cCount"], 0)
|
||||
wC = checkInt(novIdx["wCount"], 0)
|
||||
pC = checkInt(novIdx["pCount"], 0)
|
||||
|
||||
self.cCValue.setText(f"{cC:n}")
|
||||
self.wCValue.setText(f"{wC:n}")
|
||||
self.pCValue.setText(f"{pC:n}")
|
||||
|
||||
self.synopValue.setText(novIdx["synopsis"])
|
||||
|
||||
self.povKeyValue.setText(self._formatTags(theRefs, nwKeyWords.POV_KEY))
|
||||
self.focKeyValue.setText(self._formatTags(theRefs, nwKeyWords.FOCUS_KEY))
|
||||
self.chrKeyValue.setText(self._formatTags(theRefs, nwKeyWords.CHAR_KEY))
|
||||
self.pltKeyValue.setText(self._formatTags(theRefs, nwKeyWords.PLOT_KEY))
|
||||
self.timKeyValue.setText(self._formatTags(theRefs, nwKeyWords.TIME_KEY))
|
||||
self.wldKeyValue.setText(self._formatTags(theRefs, nwKeyWords.WORLD_KEY))
|
||||
self.objKeyValue.setText(self._formatTags(theRefs, nwKeyWords.OBJECT_KEY))
|
||||
self.entKeyValue.setText(self._formatTags(theRefs, nwKeyWords.ENTITY_KEY))
|
||||
self.cstKeyValue.setText(self._formatTags(theRefs, nwKeyWords.CUSTOM_KEY))
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
def _tagClicked(self, theLink):
|
||||
"""Capture the click of a tag in the right-most column.
|
||||
"""
|
||||
logger.verbose("Clicked link: '%s'", theLink)
|
||||
if len(theLink) > 0:
|
||||
theBits = theLink.split("=")
|
||||
if len(theBits) == 2:
|
||||
self.theParent.docViewer.loadFromTag(theBits[1])
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _formatTags(self, theRefs, theKey):
|
||||
"""Format the tags as clickable links.
|
||||
"""
|
||||
if theKey not in theRefs:
|
||||
return ""
|
||||
refTags = []
|
||||
for tTag in theRefs[theKey]:
|
||||
refTags.append("<a href='#%s=%s'>%s</a>" % (
|
||||
theKey[1:], tTag, tTag
|
||||
))
|
||||
return ", ".join(refTags)
|
||||
|
||||
# END Class GuiOutlineDetails
|
||||
@@ -32,12 +32,14 @@ from time import time
|
||||
from PyQt5.QtCore import Qt, QSize, pyqtSignal, pyqtSlot
|
||||
from PyQt5.QtGui import QIcon
|
||||
from PyQt5.QtWidgets import (
|
||||
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction
|
||||
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction, QFrame,
|
||||
QDialog
|
||||
)
|
||||
|
||||
from novelwriter.core import NWDoc
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
|
||||
from novelwriter.constants import nwConst, trConst, nwLists, nwLabels
|
||||
from novelwriter.common import minmax
|
||||
from novelwriter.dialogs.itemeditor import GuiItemEditor
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -49,8 +51,9 @@ class GuiProjectTree(QTreeWidget):
|
||||
C_EXPORT = 2
|
||||
C_STATUS = 3
|
||||
|
||||
novelItemChanged = pyqtSignal()
|
||||
noteItemChanged = pyqtSignal()
|
||||
treeItemChanged = pyqtSignal(str)
|
||||
novelItemChanged = pyqtSignal(str)
|
||||
rootFolderChanged = pyqtSignal(str)
|
||||
wordCountsChanged = pyqtSignal()
|
||||
|
||||
def __init__(self, theParent):
|
||||
@@ -62,13 +65,11 @@ class GuiProjectTree(QTreeWidget):
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.theIndex = theParent.theIndex
|
||||
|
||||
# Internal Variables
|
||||
self._treeMap = {}
|
||||
self._treeChanged = False
|
||||
self._treeMap = {}
|
||||
self._lastMove = {}
|
||||
self._timeChanged = 0
|
||||
self._lastMove = {}
|
||||
|
||||
##
|
||||
# Build GUI
|
||||
@@ -82,7 +83,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
# Tree Settings
|
||||
iPx = self.theTheme.baseIconSize
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setExpandsOnDoubleClick(True)
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
self.setIndentation(iPx)
|
||||
self.setColumnCount(4)
|
||||
self.setHeaderLabels([
|
||||
@@ -157,147 +159,97 @@ class GuiProjectTree(QTreeWidget):
|
||||
"""
|
||||
self.clear()
|
||||
self._treeMap = {}
|
||||
self._treeChanged = False
|
||||
self._lastMove = {}
|
||||
self._timeChanged = 0
|
||||
return
|
||||
|
||||
def newTreeItem(self, itemType, itemClass):
|
||||
"""Add new item to the tree, with a given itemType and
|
||||
itemClass, and attach it to the selected handle. Also make sure
|
||||
the item is added in a place it can be added, and that other
|
||||
meta data is set correctly to ensure a valid project tree.
|
||||
def newTreeItem(self, itemType, itemClass=None):
|
||||
"""Add new item to the tree, with a given itemType (and
|
||||
itemClass if Root), and attach it to the selected handle. Also
|
||||
make sure the item is added in a place it can be added, and that
|
||||
other meta data is set correctly to ensure a valid project tree.
|
||||
"""
|
||||
pHandle = self.getSelectedHandle()
|
||||
nHandle = None
|
||||
|
||||
if not self.theParent.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
if not isinstance(itemType, nwItemType):
|
||||
# This would indicate an internal bug
|
||||
logger.error("No itemType provided")
|
||||
return False
|
||||
nHandle = None
|
||||
tHandle = None
|
||||
|
||||
# The item needs to be assigned an item class, so one must be
|
||||
# provided, or it must be possible to extract it from the parent
|
||||
# item of the new item.
|
||||
if itemClass is None and pHandle is not None:
|
||||
pItem = self.theProject.projTree[pHandle]
|
||||
if pItem is not None:
|
||||
itemClass = pItem.itemClass
|
||||
if itemType == nwItemType.ROOT and isinstance(itemClass, nwItemClass):
|
||||
|
||||
# If class is still not set, alert the user and exit
|
||||
if itemClass is None:
|
||||
if itemType == nwItemType.FILE:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Please select a valid location in the tree to add the document."
|
||||
), nwAlert.ERROR)
|
||||
else:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Please select a valid location in the tree to add the folder."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
tHandle = self.theProject.newRoot(itemClass)
|
||||
|
||||
# Everything is fine, we have what we need, so we proceed
|
||||
logger.verbose(
|
||||
"Adding new item of type '%s' and class '%s' to handle '%s'",
|
||||
itemType.name, itemClass.name, str(pHandle)
|
||||
)
|
||||
elif itemType in (nwItemType.FILE, nwItemType.FOLDER):
|
||||
|
||||
if itemType == nwItemType.ROOT:
|
||||
tHandle = self.theProject.newRoot(
|
||||
trConst(nwLabels.CLASS_NAME[itemClass]), itemClass
|
||||
)
|
||||
if tHandle is None:
|
||||
logger.error("No root item added")
|
||||
return False
|
||||
|
||||
else:
|
||||
# If no parent has been selected, make the new file under
|
||||
# the root NOVEL item.
|
||||
if pHandle is None:
|
||||
pHandle = self.theProject.projTree.findRoot(nwItemClass.NOVEL)
|
||||
|
||||
# If still nothing, give up
|
||||
if pHandle is None:
|
||||
sHandle = self.getSelectedHandle()
|
||||
if sHandle is None or sHandle not in self.theProject.tree:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Did not find anywhere to add the file or folder!"
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
# Now check if the selected item is a file, in which case
|
||||
# the new file will be a sibling
|
||||
pItem = self.theProject.projTree[pHandle]
|
||||
if pItem.itemType == nwItemType.FILE:
|
||||
nHandle = pHandle
|
||||
pHandle = pItem.itemParent
|
||||
# If the selected item is a file, the new item will be a
|
||||
# sibling if the file has no children, otherwise a child
|
||||
pItem = self.theProject.tree[sHandle]
|
||||
qItem = self._getTreeItem(sHandle)
|
||||
if pItem.itemType == nwItemType.FILE and qItem.childCount() == 0:
|
||||
nHandle = sHandle
|
||||
sHandle = pItem.itemParent
|
||||
if sHandle is None:
|
||||
logger.error("Internal error") # Bug
|
||||
return False
|
||||
|
||||
# If we again have no home, give up
|
||||
if pHandle is None:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Did not find anywhere to add the file or folder!"
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
if self.theProject.projTree.isTrashRoot(pHandle):
|
||||
if self.theProject.tree.isTrash(sHandle):
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Cannot add new files or folders to the Trash folder."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
parTree = self.theProject.projTree.getItemPath(pHandle)
|
||||
|
||||
# If we're still here, add the file or folder
|
||||
# Add the file or folder
|
||||
if itemType == nwItemType.FILE:
|
||||
tHandle = self.theProject.newFile(self.tr("New File"), itemClass, pHandle)
|
||||
|
||||
if pItem.isNovelLike():
|
||||
tHandle = self.theProject.newFile(self.tr("New Document"), sHandle)
|
||||
else:
|
||||
tHandle = self.theProject.newFile(self.tr("New Note"), sHandle)
|
||||
elif itemType == nwItemType.FOLDER:
|
||||
if len(parTree) >= nwConst.MAX_DEPTH - 1:
|
||||
# Folders cannot be deeper than MAX_DEPTH - 1, leaving room
|
||||
# for one more level of files.
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Cannot add new folder to this item. "
|
||||
"Maximum folder depth has been reached."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
tHandle = self.theProject.newFolder(self.tr("New Folder"), itemClass, pHandle)
|
||||
tHandle = self.theProject.newFolder(self.tr("New Folder"), sHandle)
|
||||
|
||||
else:
|
||||
logger.error("Failed to add new item")
|
||||
return False
|
||||
else:
|
||||
logger.error("Failed to add new item")
|
||||
return False
|
||||
|
||||
# If there is no handle set, return here
|
||||
if tHandle is None:
|
||||
# If there is no handle set, return here. This is a bug
|
||||
if tHandle is None: # pragma: no cover
|
||||
return True
|
||||
|
||||
# Add the new item to the tree
|
||||
self.revealNewTreeItem(tHandle, nHandle)
|
||||
self.theParent.editItem(tHandle)
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
|
||||
# If this is a folder, return here
|
||||
# Handle new file creation
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem.itemType != nwItemType.FILE:
|
||||
return True
|
||||
|
||||
# This is a new files, so let's add some content
|
||||
# This is a new file, so let's add some content
|
||||
newDoc = NWDoc(self.theProject, tHandle)
|
||||
curTxt = newDoc.readDocument()
|
||||
if curTxt is None:
|
||||
curTxt = ""
|
||||
|
||||
if curTxt == "":
|
||||
if not newDoc.readDocument():
|
||||
if nwItem.itemLayout == nwItemLayout.DOCUMENT:
|
||||
newText = f"### {nwItem.itemName}\n\n"
|
||||
iLvl = self.theProject.index.getHandleHeaderIntLevel(sHandle)
|
||||
hLvl = "#"*minmax(iLvl + 1, 2, 4)
|
||||
newText = f"{hLvl} {nwItem.itemName}\n\n"
|
||||
else:
|
||||
newText = f"# {nwItem.itemName}\n\n"
|
||||
|
||||
pIndex = self.theProject.index
|
||||
|
||||
# Save the text and index it
|
||||
newDoc.writeDocument(newText)
|
||||
self.theIndex.scanText(tHandle, newText)
|
||||
pIndex.scanText(tHandle, newText)
|
||||
|
||||
# Get Word Counts
|
||||
cC, wC, pC = self.theIndex.getCounts(tHandle)
|
||||
cC, wC, pC = pIndex.getCounts(tHandle)
|
||||
nwItem.setCharCount(cC)
|
||||
nwItem.setWordCount(wC)
|
||||
nwItem.setParaCount(pC)
|
||||
@@ -309,7 +261,10 @@ class GuiProjectTree(QTreeWidget):
|
||||
def revealNewTreeItem(self, tHandle, nHandle=None):
|
||||
"""Reveal a newly added project item in the project tree.
|
||||
"""
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem is None:
|
||||
return False
|
||||
|
||||
trItem = self._addTreeItem(nwItem, nHandle)
|
||||
if trItem is None:
|
||||
return False
|
||||
@@ -318,7 +273,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
if pHandle is not None and pHandle in self._treeMap:
|
||||
self._treeMap[pHandle].setExpanded(True)
|
||||
|
||||
self._emitItemChange(tHandle)
|
||||
self._alertTreeChange(tHandle=tHandle, flush=True)
|
||||
self.clearSelection()
|
||||
trItem.setSelected(True)
|
||||
|
||||
@@ -360,10 +315,31 @@ class GuiProjectTree(QTreeWidget):
|
||||
pItem.insertChild(nIndex, cItem)
|
||||
self._recordLastMove(cItem, pItem, tIndex)
|
||||
|
||||
self._alertTreeChange(tHandle=tHandle, flush=True)
|
||||
self.clearSelection()
|
||||
cItem.setSelected(True)
|
||||
self._setTreeChanged(True)
|
||||
self._emitItemChange(tHandle)
|
||||
|
||||
return True
|
||||
|
||||
def editTreeItem(self, tHandle=None):
|
||||
"""Open the edit item dialog.
|
||||
"""
|
||||
if tHandle is None:
|
||||
logger.warning("No item selected")
|
||||
return False
|
||||
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
if tItem is None:
|
||||
return False
|
||||
if tItem.itemType == nwItemType.NO_TYPE:
|
||||
return False
|
||||
|
||||
logger.verbose("Requesting change to item '%s'", tHandle)
|
||||
dlgProj = GuiItemEditor(self, tHandle)
|
||||
dlgProj.exec_()
|
||||
if dlgProj.result() == QDialog.Accepted:
|
||||
self.setTreeItemValues(tHandle)
|
||||
self._alertTreeChange(tHandle=tHandle, flush=False)
|
||||
|
||||
return True
|
||||
|
||||
@@ -380,16 +356,6 @@ class GuiProjectTree(QTreeWidget):
|
||||
self.theProject.setTreeOrder(theList)
|
||||
return True
|
||||
|
||||
def flushTreeOrder(self):
|
||||
"""Calls saveTreeOrder if there are unsaved changes, otherwise
|
||||
does nothing.
|
||||
"""
|
||||
if self._treeChanged:
|
||||
logger.verbose("Flushing project tree to project class")
|
||||
self.saveTreeOrder()
|
||||
self._setTreeChanged(False)
|
||||
return
|
||||
|
||||
def getTreeFromHandle(self, tHandle):
|
||||
"""Recursively return all the children items starting from a
|
||||
given item handle.
|
||||
@@ -400,6 +366,14 @@ class GuiProjectTree(QTreeWidget):
|
||||
theList = self._scanChildren(theList, theItem, 0)
|
||||
return theList
|
||||
|
||||
def toggleExpanded(self, tHandle):
|
||||
"""Expand an item based on its handle.
|
||||
"""
|
||||
trItem = self._getTreeItem(tHandle)
|
||||
if trItem is not None:
|
||||
trItem.setExpanded(not trItem.isExpanded())
|
||||
return
|
||||
|
||||
def getColumnSizes(self):
|
||||
"""Return the column widths for the tree columns.
|
||||
"""
|
||||
@@ -419,7 +393,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
trashHandle = self.theProject.projTree.trashRoot()
|
||||
trashHandle = self.theProject.tree.trashRoot()
|
||||
|
||||
logger.debug("Emptying Trash folder")
|
||||
if trashHandle is None:
|
||||
@@ -447,13 +421,13 @@ class GuiProjectTree(QTreeWidget):
|
||||
return False
|
||||
|
||||
logger.verbose("Deleting %d file(s) from Trash", nTrash)
|
||||
for tHandle in self.getTreeFromHandle(trashHandle):
|
||||
for tHandle in reversed(self.getTreeFromHandle(trashHandle)):
|
||||
if tHandle == trashHandle:
|
||||
continue
|
||||
self.deleteItem(tHandle, alreadyAsked=True, bulkAction=True)
|
||||
|
||||
if nTrash > 0:
|
||||
self._setTreeChanged(True)
|
||||
self._alertTreeChange(tHandle=trashHandle, flush=True)
|
||||
|
||||
return True
|
||||
|
||||
@@ -461,8 +435,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
"""Delete an item from the project tree. As a first step, files are
|
||||
moved to the Trash folder. Permanent deletion is a second step. This
|
||||
second step also deletes the item from the project object as well as
|
||||
delete the files on disk. Folders are deleted if they're empty only,
|
||||
and the deletion is always permanent.
|
||||
delete the files on disk. Root folders are deleted if they're empty
|
||||
only, and the deletion is always permanent.
|
||||
"""
|
||||
if not self.theParent.hasProject:
|
||||
logger.error("No project open")
|
||||
@@ -480,109 +454,22 @@ class GuiProjectTree(QTreeWidget):
|
||||
return False
|
||||
|
||||
trItemS = self._getTreeItem(tHandle)
|
||||
nwItemS = self.theProject.projTree[tHandle]
|
||||
nwItemS = self.theProject.tree[tHandle]
|
||||
|
||||
if trItemS is None or nwItemS is None:
|
||||
logger.error("Could not find tree item for deletion")
|
||||
return False
|
||||
|
||||
wCount = int(trItemS.data(self.C_COUNT, Qt.UserRole))
|
||||
if nwItemS.itemType == nwItemType.FILE:
|
||||
logger.debug("User requested file '%s' deleted", tHandle)
|
||||
trItemP = trItemS.parent()
|
||||
trItemT = self._addTrashRoot()
|
||||
if trItemP is None or trItemT is None:
|
||||
logger.error("Could not delete item")
|
||||
return False
|
||||
|
||||
pHandle = nwItemS.itemParent
|
||||
if self.theProject.projTree.isTrashRoot(pHandle):
|
||||
# If the file is in the trash folder already, as the
|
||||
# user if they want to permanently delete the file.
|
||||
doPermanent = False
|
||||
if not alreadyAsked:
|
||||
msgYes = self.theParent.askQuestion(
|
||||
self.tr("Delete File"),
|
||||
self.tr("Permanently delete file '{0}'?").format(nwItemS.itemName)
|
||||
)
|
||||
if msgYes:
|
||||
doPermanent = True
|
||||
else:
|
||||
doPermanent = True
|
||||
|
||||
if doPermanent:
|
||||
logger.debug("Permanently deleting file with handle '%s'", tHandle)
|
||||
|
||||
self.propagateCount(tHandle, 0)
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
trItemC = trItemP.takeChild(tIndex)
|
||||
|
||||
if self.theParent.docEditor.docHandle() == tHandle:
|
||||
self.theParent.closeDocument()
|
||||
|
||||
delDoc = NWDoc(self.theProject, tHandle)
|
||||
if not delDoc.deleteDocument():
|
||||
self.theParent.makeAlert([
|
||||
self.tr("Could not delete document file."), delDoc.getError()
|
||||
], nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
self.theIndex.deleteHandle(tHandle)
|
||||
self._deleteTreeItem(tHandle)
|
||||
self._setTreeChanged(True)
|
||||
self.wordCountsChanged.emit()
|
||||
|
||||
else:
|
||||
# The file is not already in the trash folder, so we
|
||||
# move it there.
|
||||
msgYes = self.theParent.askQuestion(
|
||||
self.tr("Delete File"),
|
||||
self.tr("Move file '{0}' to Trash?").format(nwItemS.itemName),
|
||||
)
|
||||
if msgYes:
|
||||
if pHandle is None:
|
||||
logger.warning("File has no parent item")
|
||||
|
||||
logger.debug("Moving file '%s' to trash", tHandle)
|
||||
|
||||
self.propagateCount(tHandle, 0)
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
trItemC = trItemP.takeChild(tIndex)
|
||||
trItemT.addChild(trItemC)
|
||||
self._updateItemParent(tHandle)
|
||||
self.propagateCount(tHandle, wCount)
|
||||
|
||||
self.theIndex.deleteHandle(tHandle)
|
||||
self._recordLastMove(trItemS, trItemP, tIndex)
|
||||
self._setTreeChanged(True)
|
||||
|
||||
elif nwItemS.itemType == nwItemType.FOLDER:
|
||||
logger.debug("User requested folder '%s' deleted", tHandle)
|
||||
trItemP = trItemS.parent()
|
||||
if trItemP is None:
|
||||
logger.error("Could not delete folder")
|
||||
return False
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
if trItemS.childCount() == 0:
|
||||
trItemP.takeChild(tIndex)
|
||||
self._deleteTreeItem(tHandle)
|
||||
self._setTreeChanged(True)
|
||||
else:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Cannot delete folder. It is not empty. "
|
||||
"Recursive deletion is not supported. "
|
||||
"Please delete the content first."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
elif nwItemS.itemType == nwItemType.ROOT:
|
||||
logger.debug("User requested root folder '%s' deleted", tHandle)
|
||||
wCount = self._getItemWordCount(tHandle)
|
||||
autoFlush = not bulkAction
|
||||
if nwItemS.itemType == nwItemType.ROOT:
|
||||
# Only an empty ROOT folder can be deleted
|
||||
logger.debug("User requested a root folder '%s' deleted", tHandle)
|
||||
tIndex = self.indexOfTopLevelItem(trItemS)
|
||||
if trItemS.childCount() == 0:
|
||||
self.takeTopLevelItem(tIndex)
|
||||
self._deleteTreeItem(tHandle)
|
||||
self.theParent.mainMenu.setAvailableRoot()
|
||||
self._setTreeChanged(True)
|
||||
self._alertTreeChange(tHandle=tHandle, flush=True)
|
||||
else:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Cannot delete root folder. It is not empty. "
|
||||
@@ -591,6 +478,70 @@ class GuiProjectTree(QTreeWidget):
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
elif nwItemS.itemType == nwItemType.FOLDER and trItemS.childCount() == 0:
|
||||
# An empty FOLDER is just deleted without any further checks
|
||||
logger.debug("User requested an empty folder '%s' deleted", tHandle)
|
||||
trItemP = trItemS.parent()
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
trItemP.takeChild(tIndex)
|
||||
self._deleteTreeItem(tHandle)
|
||||
self._alertTreeChange(tHandle=tHandle, flush=autoFlush)
|
||||
|
||||
else:
|
||||
# A populated FOLDER or a FILE requires confirmtation
|
||||
logger.debug("User requested a file or folder '%s' deleted", tHandle)
|
||||
trItemP = trItemS.parent()
|
||||
trItemT = self._addTrashRoot()
|
||||
if trItemP is None or trItemT is None:
|
||||
logger.error("Could not delete item")
|
||||
return False
|
||||
|
||||
if self.theProject.tree.isTrash(tHandle):
|
||||
# If the file is in the trash folder already, as the
|
||||
# user if they want to permanently delete the file.
|
||||
doPermanent = False
|
||||
if not alreadyAsked:
|
||||
msgYes = self.theParent.askQuestion(
|
||||
self.tr("Delete"),
|
||||
self.tr("Permanently delete '{0}'?").format(nwItemS.itemName)
|
||||
)
|
||||
if msgYes:
|
||||
doPermanent = True
|
||||
else:
|
||||
doPermanent = True
|
||||
|
||||
if doPermanent:
|
||||
logger.debug("Permanently deleting item with handle '%s'", tHandle)
|
||||
|
||||
self.propagateCount(tHandle, 0)
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
trItemC = trItemP.takeChild(tIndex)
|
||||
for dHandle in reversed(self.getTreeFromHandle(tHandle)):
|
||||
if self.theParent.docEditor.docHandle() == dHandle:
|
||||
self.theParent.closeDocument()
|
||||
self._deleteTreeItem(dHandle)
|
||||
|
||||
self._alertTreeChange(tHandle=tHandle, flush=autoFlush)
|
||||
self.wordCountsChanged.emit()
|
||||
|
||||
else:
|
||||
# The item is not already in the trash folder, so we
|
||||
# move it there.
|
||||
msgYes = self.theParent.askQuestion(
|
||||
self.tr("Delete"),
|
||||
self.tr("Move '{0}' to Trash?").format(nwItemS.itemName),
|
||||
)
|
||||
if msgYes:
|
||||
logger.debug("Moving item '%s' to trash", tHandle)
|
||||
|
||||
self.propagateCount(tHandle, 0)
|
||||
tIndex = trItemP.indexOfChild(trItemS)
|
||||
trItemC = trItemP.takeChild(tIndex)
|
||||
trItemT.addChild(trItemC)
|
||||
self._postItemMove(tHandle, wCount)
|
||||
self._recordLastMove(trItemS, trItemP, tIndex)
|
||||
self._alertTreeChange(tHandle=tHandle, flush=autoFlush)
|
||||
|
||||
return True
|
||||
|
||||
def setTreeItemValues(self, tHandle):
|
||||
@@ -599,7 +550,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
already coming from the project tree.
|
||||
"""
|
||||
trItem = self._getTreeItem(tHandle)
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if trItem is None or nwItem is None:
|
||||
return
|
||||
|
||||
@@ -610,8 +561,8 @@ class GuiProjectTree(QTreeWidget):
|
||||
else:
|
||||
expIcon = self.theTheme.getIcon("cross")
|
||||
|
||||
itempStatus, statusIcon = nwItem.getImportStatus()
|
||||
hLevel = self.theIndex.getHandleHeaderLevel(tHandle)
|
||||
itemStatus, statusIcon = nwItem.getImportStatus()
|
||||
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
itemIcon = self.theTheme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||||
)
|
||||
@@ -620,7 +571,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
trItem.setText(self.C_NAME, nwItem.itemName)
|
||||
trItem.setIcon(self.C_EXPORT, expIcon)
|
||||
trItem.setIcon(self.C_STATUS, statusIcon)
|
||||
trItem.setToolTip(self.C_STATUS, itempStatus)
|
||||
trItem.setToolTip(self.C_STATUS, itemStatus)
|
||||
|
||||
if self.mainConf.emphLabels and nwItem.itemLayout == nwItemLayout.DOCUMENT:
|
||||
trFont = trItem.font(self.C_NAME)
|
||||
@@ -634,7 +585,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
return
|
||||
|
||||
def propagateCount(self, tHandle, theCount, nDepth=0):
|
||||
def propagateCount(self, tHandle, newCount, countChildren=False):
|
||||
"""Recursive function setting the word count for a given item,
|
||||
and propagating that count upwards in the tree until reaching a
|
||||
root item. This function is more efficient than recalculating
|
||||
@@ -646,20 +597,30 @@ class GuiProjectTree(QTreeWidget):
|
||||
if tItem is None:
|
||||
return
|
||||
|
||||
tItem.setText(self.C_COUNT, f"{theCount:n}")
|
||||
tItem.setData(self.C_COUNT, Qt.UserRole, int(theCount))
|
||||
if countChildren:
|
||||
for i in range(tItem.childCount()):
|
||||
newCount += int(tItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
||||
|
||||
tItem.setText(self.C_COUNT, f"{newCount:n}")
|
||||
tItem.setData(self.C_COUNT, Qt.UserRole, int(newCount))
|
||||
|
||||
pItem = tItem.parent()
|
||||
if pItem is None:
|
||||
return
|
||||
|
||||
pCount = 0
|
||||
pHandle = None
|
||||
for i in range(pItem.childCount()):
|
||||
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
||||
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
||||
|
||||
if not nDepth > nwConst.MAX_DEPTH + 1 and pHandle != "":
|
||||
self.propagateCount(pHandle, pCount, nDepth+1)
|
||||
if pHandle:
|
||||
if self.theProject.tree.checkType(pHandle, nwItemType.FILE):
|
||||
# A file has an internal word count we need to account
|
||||
# for, but a folder always has 0 words on its own.
|
||||
pCount += self.theProject.index.getCounts(pHandle)[1]
|
||||
|
||||
self.propagateCount(pHandle, pCount, countChildren=False)
|
||||
|
||||
return
|
||||
|
||||
@@ -703,20 +664,19 @@ class GuiProjectTree(QTreeWidget):
|
||||
return False
|
||||
|
||||
dstIndex = min(max(0, dstIndex), dstItem.childCount())
|
||||
wCount = int(srcItem.data(self.C_COUNT, Qt.UserRole))
|
||||
sHandle = srcItem.data(self.C_NAME, Qt.UserRole)
|
||||
dHandle = dstItem.data(self.C_NAME, Qt.UserRole)
|
||||
logger.debug("Moving item '%s' back to '%s', index %d", sHandle, dHandle, dstIndex)
|
||||
|
||||
wCount = self._getItemWordCount(sHandle)
|
||||
self.propagateCount(sHandle, 0)
|
||||
parItem = srcItem.parent()
|
||||
srcIndex = parItem.indexOfChild(srcItem)
|
||||
movItem = parItem.takeChild(srcIndex)
|
||||
dstItem.insertChild(dstIndex, movItem)
|
||||
|
||||
snItem = self.theProject.projTree[sHandle]
|
||||
dnItem = self.theProject.projTree[dHandle]
|
||||
self._postItemMove(sHandle, snItem, dnItem, wCount)
|
||||
self._postItemMove(sHandle, wCount)
|
||||
self._alertTreeChange(tHandle=sHandle, flush=True)
|
||||
|
||||
self.clearSelection()
|
||||
movItem.setSelected(True)
|
||||
@@ -771,7 +731,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
if isinstance(selItem, QTreeWidgetItem):
|
||||
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
||||
self.setSelectedHandle(tHandle) # Just to be safe
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
if tItem is not None:
|
||||
if self.ctxMenu.filterActions(tItem):
|
||||
# Only open menu if any actions remain after filter
|
||||
@@ -783,7 +743,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
def doUpdateCounts(self, tHandle, cCount, wCount, pCount):
|
||||
"""Slot for updating the word count of a specific item.
|
||||
"""
|
||||
self.propagateCount(tHandle, wCount)
|
||||
self.propagateCount(tHandle, wCount, countChildren=True)
|
||||
self.wordCountsChanged.emit()
|
||||
return
|
||||
|
||||
@@ -809,7 +769,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
return
|
||||
|
||||
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
if tItem is None:
|
||||
return
|
||||
|
||||
@@ -819,61 +779,34 @@ class GuiProjectTree(QTreeWidget):
|
||||
return
|
||||
|
||||
def dropEvent(self, theEvent):
|
||||
"""Overload the drop of dragged item event to check whether the
|
||||
drop is allowed or not. Disallowed drops are cancelled.
|
||||
"""Overload the drop item event to ensure relevant data has been
|
||||
updated.
|
||||
"""
|
||||
sHandle = self.getSelectedHandle()
|
||||
if sHandle is None:
|
||||
logger.error("No handle selected")
|
||||
logger.error("Invalid drag and drop event")
|
||||
return
|
||||
|
||||
dIndex = self.indexAt(theEvent.pos())
|
||||
if not dIndex.isValid():
|
||||
logger.error("Invalid drop index")
|
||||
return
|
||||
logger.debug("Drag'n'drop of item '%s' accepted", sHandle)
|
||||
|
||||
sItem = self._getTreeItem(sHandle)
|
||||
dItem = self.itemFromIndex(dIndex)
|
||||
dHandle = dItem.data(self.C_NAME, Qt.UserRole)
|
||||
snItem = self.theProject.projTree[sHandle]
|
||||
dnItem = self.theProject.projTree[dHandle]
|
||||
if dnItem is None:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"The item cannot be moved to that location."
|
||||
), nwAlert.ERROR)
|
||||
return
|
||||
isExpanded = False
|
||||
if sItem is not None:
|
||||
isExpanded = sItem.isExpanded()
|
||||
|
||||
pItem = sItem.parent()
|
||||
pIndex = 0
|
||||
if pItem is not None:
|
||||
pIndex = pItem.indexOfChild(sItem)
|
||||
|
||||
wCount = int(sItem.data(self.C_COUNT, Qt.UserRole))
|
||||
isFile = snItem.itemType == nwItemType.FILE
|
||||
isRoot = snItem.itemType == nwItemType.ROOT
|
||||
onFile = dnItem.itemType == nwItemType.FILE
|
||||
wCount = self._getItemWordCount(sHandle)
|
||||
self.propagateCount(sHandle, 0)
|
||||
|
||||
isSame = snItem.itemClass == dnItem.itemClass
|
||||
isNone = snItem.itemClass == nwItemClass.NO_CLASS
|
||||
isNote = snItem.itemLayout == nwItemLayout.NOTE
|
||||
onFree = dnItem.itemClass in nwLists.FREE_CLASS and isFile
|
||||
|
||||
allowDrop = isSame or isNone or isNote or onFree
|
||||
allowDrop &= not (self.dropIndicatorPosition() == QAbstractItemView.OnItem and onFile)
|
||||
|
||||
if allowDrop and not isRoot:
|
||||
logger.debug("Drag'n'drop of item '%s' accepted", sHandle)
|
||||
self.propagateCount(sHandle, 0)
|
||||
QTreeWidget.dropEvent(self, theEvent)
|
||||
self._postItemMove(sHandle, snItem, dnItem, wCount)
|
||||
self._recordLastMove(sItem, pItem, pIndex)
|
||||
|
||||
else:
|
||||
theEvent.ignore()
|
||||
logger.debug("Drag'n'drop of item '%s' not accepted", sHandle)
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"The item cannot be moved to that location."
|
||||
), nwAlert.ERROR)
|
||||
QTreeWidget.dropEvent(self, theEvent)
|
||||
self._postItemMove(sHandle, wCount)
|
||||
self._recordLastMove(sItem, pItem, pIndex)
|
||||
self._alertTreeChange(tHandle=sHandle, flush=True)
|
||||
sItem.setExpanded(isExpanded)
|
||||
|
||||
return
|
||||
|
||||
@@ -881,64 +814,88 @@ class GuiProjectTree(QTreeWidget):
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _postItemMove(self, sHandle, snItem, dnItem, wCount):
|
||||
def _postItemMove(self, tHandle, wCount):
|
||||
"""Run various maintenance tasks for a moved item.
|
||||
"""
|
||||
isFile = snItem.itemType == nwItemType.FILE
|
||||
isSame = snItem.itemClass == dnItem.itemClass
|
||||
onFree = dnItem.itemClass in nwLists.FREE_CLASS and isFile
|
||||
trItemS = self._getTreeItem(tHandle)
|
||||
nwItemS = self.theProject.tree[tHandle]
|
||||
trItemP = trItemS.parent()
|
||||
if trItemP is None:
|
||||
logger.error("Failed to find new parent item of '%s'", tHandle)
|
||||
return False
|
||||
|
||||
self._updateItemParent(sHandle)
|
||||
# Update item parent handle in the project, make sure meta data
|
||||
# is updated accordingly, and update word count
|
||||
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
||||
nwItemS.setParent(pHandle)
|
||||
trItemP.setExpanded(True)
|
||||
logger.debug("The parent of item '%s' has been changed to '%s'", tHandle, pHandle)
|
||||
|
||||
# If the item does not have the same class as the target,
|
||||
# and the target is not a free root folder, update its class
|
||||
if not (isSame or onFree):
|
||||
logger.debug(
|
||||
"Item '%s' class has been changed from '%s' to '%s'",
|
||||
sHandle, snItem.itemClass.name, dnItem.itemClass.name
|
||||
)
|
||||
snItem.setClass(dnItem.itemClass)
|
||||
self.setTreeItemValues(sHandle)
|
||||
mHandles = self.getTreeFromHandle(tHandle)
|
||||
logger.debug("A total of %d item(s) were moved", len(mHandles))
|
||||
for mHandle in mHandles:
|
||||
logger.debug("Updating item '%s'", mHandle)
|
||||
self.theProject.tree.updateItemData(mHandle)
|
||||
|
||||
self.propagateCount(sHandle, wCount)
|
||||
# Update the index
|
||||
if nwItemS.isInactive():
|
||||
self.theProject.index.deleteHandle(mHandle)
|
||||
else:
|
||||
self.theProject.index.reIndexHandle(mHandle)
|
||||
|
||||
# The items dropped into archive or trash should be removed
|
||||
# from the project index, for all other items, we rescan the
|
||||
# file to ensure the index is up to date.
|
||||
if onFree:
|
||||
self.theIndex.deleteHandle(sHandle)
|
||||
else:
|
||||
self.theIndex.reIndexHandle(sHandle)
|
||||
self.setTreeItemValues(mHandle)
|
||||
|
||||
# Trigger dependent updates
|
||||
self._setTreeChanged(True)
|
||||
self._emitItemChange(sHandle)
|
||||
self.propagateCount(tHandle, wCount)
|
||||
|
||||
return
|
||||
return True
|
||||
|
||||
def _getItemWordCount(self, tHandle):
|
||||
"""Retrun the word count of a given item handle.
|
||||
"""
|
||||
tItem = self._getTreeItem(tHandle)
|
||||
if tItem is None:
|
||||
return 0
|
||||
return int(tItem.data(self.C_COUNT, Qt.UserRole))
|
||||
|
||||
def _getTreeItem(self, tHandle):
|
||||
"""Returns the QTreeWidgetItem of a given item handle.
|
||||
"""Return the QTreeWidgetItem of a given item handle.
|
||||
"""
|
||||
return self._treeMap.get(tHandle, None)
|
||||
|
||||
def _deleteTreeItem(self, tHandle):
|
||||
"""Delete a tree item from the project and the map.
|
||||
"""Permanently delete a tree item from the project and the map.
|
||||
"""
|
||||
del self.theProject.projTree[tHandle]
|
||||
if self.theProject.tree.checkType(tHandle, nwItemType.FILE):
|
||||
delDoc = NWDoc(self.theProject, tHandle)
|
||||
if not delDoc.deleteDocument():
|
||||
self.theParent.makeAlert([
|
||||
self.tr("Could not delete document file."), delDoc.getError()
|
||||
], nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
self.theProject.index.deleteHandle(tHandle)
|
||||
del self.theProject.tree[tHandle]
|
||||
self._treeMap.pop(tHandle, None)
|
||||
return
|
||||
|
||||
return True
|
||||
|
||||
def _scanChildren(self, theList, tItem, tIndex):
|
||||
"""This is a recursive function returning all items in a tree
|
||||
starting at a given QTreeWidgetItem.
|
||||
"""
|
||||
tHandle = tItem.data(self.C_NAME, Qt.UserRole)
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem.setExpanded(tItem.isExpanded())
|
||||
cCount = tItem.childCount()
|
||||
|
||||
# Update tree-related meta data
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
nwItem.setExpanded(tItem.isExpanded() and cCount > 0)
|
||||
nwItem.setOrder(tIndex)
|
||||
|
||||
theList.append(tHandle)
|
||||
for i in range(tItem.childCount()):
|
||||
for i in range(cCount):
|
||||
self._scanChildren(theList, tItem.child(i), i)
|
||||
|
||||
return theList
|
||||
|
||||
def _addTreeItem(self, nwItem, nHandle=None):
|
||||
@@ -965,9 +922,7 @@ class GuiProjectTree(QTreeWidget):
|
||||
self._treeMap[tHandle] = newItem
|
||||
if pHandle is None:
|
||||
if nwItem.itemType == nwItemType.ROOT:
|
||||
self.addTopLevelItem(newItem)
|
||||
self.theParent.mainMenu.setAvailableRoot()
|
||||
elif nwItem.itemType == nwItemType.TRASH:
|
||||
newItem.setFlags(newItem.flags() ^ Qt.ItemIsDragEnabled)
|
||||
self.addTopLevelItem(newItem)
|
||||
else:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
@@ -984,16 +939,14 @@ class GuiProjectTree(QTreeWidget):
|
||||
except Exception:
|
||||
logger.error("Failed to get index of item with handle '%s'", nHandle)
|
||||
if byIndex >= 0:
|
||||
self._treeMap[pHandle].insertChild(byIndex+1, newItem)
|
||||
self._treeMap[pHandle].insertChild(byIndex + 1, newItem)
|
||||
else:
|
||||
self._treeMap[pHandle].addChild(newItem)
|
||||
self.propagateCount(tHandle, nwItem.wordCount)
|
||||
self.propagateCount(tHandle, nwItem.wordCount, countChildren=True)
|
||||
|
||||
self.setTreeItemValues(tHandle)
|
||||
newItem.setExpanded(nwItem.isExpanded)
|
||||
|
||||
self._setTreeChanged(True)
|
||||
|
||||
return newItem
|
||||
|
||||
def _addTrashRoot(self):
|
||||
@@ -1006,52 +959,34 @@ class GuiProjectTree(QTreeWidget):
|
||||
|
||||
trItem = self._getTreeItem(trashHandle)
|
||||
if trItem is None:
|
||||
trItem = self._addTreeItem(
|
||||
self.theProject.projTree[trashHandle]
|
||||
)
|
||||
trItem = self._addTreeItem(self.theProject.tree[trashHandle])
|
||||
if trItem is not None:
|
||||
trItem.setExpanded(True)
|
||||
self._setTreeChanged(True)
|
||||
self._alertTreeChange(tHandle=trashHandle, flush=True)
|
||||
|
||||
return trItem
|
||||
|
||||
def _updateItemParent(self, tHandle):
|
||||
"""Update the parent handle of an item so that the information
|
||||
in the project is consistent with the treeView.
|
||||
def _alertTreeChange(self, tHandle=None, flush=True):
|
||||
"""Update information on tree change state, and emit necessary
|
||||
signals.
|
||||
"""
|
||||
trItemS = self._getTreeItem(tHandle)
|
||||
nwItemS = self.theProject.projTree[tHandle]
|
||||
trItemP = trItemS.parent()
|
||||
if trItemP is None:
|
||||
logger.error("Failed to find new parent item of '%s'", tHandle)
|
||||
return False
|
||||
self._timeChanged = time()
|
||||
self.theProject.setProjectChanged(True)
|
||||
if flush:
|
||||
self.saveTreeOrder()
|
||||
|
||||
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
||||
nwItemS.setParent(pHandle)
|
||||
self.setTreeItemValues(tHandle)
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
if tItem is None:
|
||||
return
|
||||
|
||||
logger.debug("The parent of item '%s' has been changed to '%s'", tHandle, pHandle)
|
||||
itemType = tItem.itemType
|
||||
if itemType == nwItemType.ROOT:
|
||||
self.rootFolderChanged.emit(tHandle)
|
||||
elif itemType == nwItemType.FILE and tItem.isNovelLike():
|
||||
self.novelItemChanged.emit(tHandle)
|
||||
|
||||
return True
|
||||
self.treeItemChanged.emit(tHandle)
|
||||
|
||||
def _setTreeChanged(self, theState):
|
||||
"""Set the tree change flag, and propagate to the project.
|
||||
"""
|
||||
self._treeChanged = theState
|
||||
if theState:
|
||||
self._timeChanged = time()
|
||||
self.theProject.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def _emitItemChange(self, tHandle):
|
||||
"""Emit an item change signal for a given handle.
|
||||
"""
|
||||
if self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
if nwItem.itemClass == nwItemClass.NOVEL:
|
||||
self.novelItemChanged.emit()
|
||||
else:
|
||||
self.noteItemChanged.emit()
|
||||
return
|
||||
|
||||
def _recordLastMove(self, srcItem, parItem, parIndex):
|
||||
@@ -1130,9 +1065,9 @@ class GuiProjectTreeMenu(QMenu):
|
||||
logger.error("Failed to extract information to build tree context menu")
|
||||
return False
|
||||
|
||||
trashHandle = self.theTree.theProject.projTree.trashRoot()
|
||||
trashHandle = self.theTree.theProject.tree.trashRoot()
|
||||
|
||||
inTrash = theItem.itemParent == trashHandle and trashHandle is not None
|
||||
inTrash = self.theTree.theProject.tree.isTrash(theItem.itemHandle)
|
||||
isTrash = theItem.itemHandle == trashHandle and trashHandle is not None
|
||||
isFile = theItem.itemType == nwItemType.FILE
|
||||
|
||||
@@ -1182,7 +1117,7 @@ class GuiProjectTreeMenu(QMenu):
|
||||
"""Forward the new file call to the project tree.
|
||||
"""
|
||||
if self.theItem is not None:
|
||||
self.theTree.newTreeItem(nwItemType.FILE, None)
|
||||
self.theTree.newTreeItem(nwItemType.FILE)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -1190,7 +1125,7 @@ class GuiProjectTreeMenu(QMenu):
|
||||
"""Forward the new folder call to the project tree.
|
||||
"""
|
||||
if self.theItem is not None:
|
||||
self.theTree.newTreeItem(nwItemType.FOLDER, None)
|
||||
self.theTree.newTreeItem(nwItemType.FOLDER)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
|
||||
@@ -456,20 +456,18 @@ class GuiIcons:
|
||||
|
||||
ICON_KEYS = {
|
||||
# Project and GUI icons
|
||||
"novelwriter", "proj_nwx",
|
||||
"cls_none", "cls_novel", "cls_plot", "cls_character", "cls_world",
|
||||
"cls_timeline", "cls_object", "cls_entity", "cls_custom", "cls_archive", "cls_trash",
|
||||
"proj_document", "proj_title", "proj_chapter", "proj_scene", "proj_note", "proj_folder",
|
||||
"status_lang", "status_time", "status_idle", "status_stats", "status_lines",
|
||||
"doc_h0", "doc_h1", "doc_h2", "doc_h3", "doc_h4",
|
||||
"search_case", "search_regex", "search_word", "search_loop", "search_project",
|
||||
"search_cancel", "search_preserve",
|
||||
"novelwriter", "cls_archive", "cls_character", "cls_custom", "cls_entity", "cls_none",
|
||||
"cls_novel", "cls_object", "cls_plot", "cls_timeline", "cls_trash", "cls_world", "doc_h0",
|
||||
"doc_h1", "doc_h2", "doc_h3", "doc_h4", "proj_chapter", "proj_details", "proj_document",
|
||||
"proj_folder", "proj_note", "proj_nwx", "proj_scene", "proj_stats", "proj_title",
|
||||
"search_cancel", "search_case", "search_loop", "search_preserve", "search_project",
|
||||
"search_regex", "search_word", "status_idle", "status_lang", "status_lines",
|
||||
"status_stats", "status_time", "view_build", "view_editor", "view_novel", "view_outline",
|
||||
|
||||
# General Button Icons
|
||||
"delete", "close", "done", "clear", "save", "add", "remove",
|
||||
"search", "search_replace", "edit", "check", "cross", "hash",
|
||||
"maximise", "minimise", "refresh", "reference", "backward",
|
||||
"forward", "settings",
|
||||
"add", "backward", "check", "clear", "close", "cross", "delete", "done", "down", "edit",
|
||||
"forward", "hash", "maximise", "menu", "minimise", "reference", "refresh", "remove",
|
||||
"save", "search_replace", "search", "settings", "up",
|
||||
|
||||
# Switches
|
||||
"sticky-on", "sticky-off",
|
||||
@@ -639,9 +637,6 @@ class GuiIcons:
|
||||
iconName = "proj_scene"
|
||||
elif tLayout == nwItemLayout.NOTE:
|
||||
iconName = "proj_note"
|
||||
elif tType == nwItemType.TRASH:
|
||||
iconName = nwLabels.CLASS_ICON[tClass]
|
||||
|
||||
if iconName is None:
|
||||
return QIcon()
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
novelWriter – GUI Main Window Views ToolBar
|
||||
===========================================
|
||||
GUI class for the main window "Views" toolbar
|
||||
|
||||
File History:
|
||||
Created: 2022-05-10 [1.7b1]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from PyQt5.QtCore import Qt, QSize, pyqtSignal
|
||||
from PyQt5.QtWidgets import (
|
||||
QToolBar, QWidget, QSizePolicy, QAction, QMenu, QToolButton
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiViewsBar(QToolBar):
|
||||
|
||||
viewChangeRequested = pyqtSignal(nwView)
|
||||
|
||||
def __init__(self, theParent):
|
||||
QToolBar.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising GuiViewsBar ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
|
||||
# Style
|
||||
iPx = self.mainConf.pxInt(22)
|
||||
mPx = self.mainConf.pxInt(60)
|
||||
|
||||
lblFont = self.theTheme.guiFont
|
||||
lblFont.setPointSizeF(0.65*self.theTheme.fontPointSize)
|
||||
|
||||
self.setMovable(False)
|
||||
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setMaximumWidth(mPx)
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setStyleSheet("QToolBar {border: 0px;}")
|
||||
|
||||
stretch = QWidget(self)
|
||||
stretch.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
# Actions
|
||||
self.aProject = QAction(self.tr("Project"))
|
||||
self.aProject.setFont(lblFont)
|
||||
self.aProject.setToolTip(self.tr("Show project tree and editor"))
|
||||
self.aProject.setIcon(self.theTheme.getIcon("view_editor"))
|
||||
self.aProject.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.PROJECT))
|
||||
|
||||
self.aNovel = QAction(self.tr("Novel"))
|
||||
self.aNovel.setFont(lblFont)
|
||||
self.aNovel.setToolTip(self.tr("Show novel tree and editor"))
|
||||
self.aNovel.setIcon(self.theTheme.getIcon("view_novel"))
|
||||
self.aNovel.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.NOVEL))
|
||||
|
||||
self.aOutline = QAction(self.tr("Outline"))
|
||||
self.aOutline.setFont(lblFont)
|
||||
self.aOutline.setToolTip(self.tr("Show novel outline"))
|
||||
self.aOutline.setIcon(self.theTheme.getIcon("view_outline"))
|
||||
self.aOutline.triggered.connect(lambda: self.viewChangeRequested.emit(nwView.OUTLINE))
|
||||
|
||||
self.aBuild = QAction(self.tr("Build"))
|
||||
self.aBuild.setFont(lblFont)
|
||||
self.aBuild.setToolTip(self.tr("Build novel project"))
|
||||
self.aBuild.setIcon(self.theTheme.getIcon("view_build"))
|
||||
self.aBuild.triggered.connect(lambda: self.theParent.showBuildProjectDialog())
|
||||
|
||||
self.aDetails = QAction(self.tr("Details"))
|
||||
self.aDetails.setFont(lblFont)
|
||||
self.aDetails.setToolTip(self.tr("Show project details"))
|
||||
self.aDetails.setIcon(self.theTheme.getIcon("proj_details"))
|
||||
self.aDetails.triggered.connect(lambda: self.theParent.showProjectDetailsDialog())
|
||||
|
||||
self.aStats = QAction(self.tr("Stats"))
|
||||
self.aStats.setFont(lblFont)
|
||||
self.aStats.setToolTip(self.tr("Show project statistics"))
|
||||
self.aStats.setIcon(self.theTheme.getIcon("proj_stats"))
|
||||
self.aStats.triggered.connect(lambda: self.theParent.showWritingStatsDialog())
|
||||
|
||||
# Settings Menu
|
||||
self.mSettings = QMenu()
|
||||
|
||||
self.aPrjSettings = QAction(self.tr("Project Settings"))
|
||||
self.aPrjSettings.triggered.connect(lambda: self.theParent.showProjectSettingsDialog())
|
||||
self.mSettings.addAction(self.aPrjSettings)
|
||||
|
||||
self.aPreferences = QAction(self.tr("Preferences"))
|
||||
self.aPreferences.triggered.connect(lambda: self.theParent.showPreferencesDialog())
|
||||
self.mSettings.addAction(self.aPreferences)
|
||||
|
||||
self.tbSettings = QToolButton(self)
|
||||
self.tbSettings.setFont(lblFont)
|
||||
self.tbSettings.setText(self.tr("Settings"))
|
||||
self.tbSettings.setIcon(self.theTheme.getIcon("settings"))
|
||||
self.tbSettings.setMenu(self.mSettings)
|
||||
self.tbSettings.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
|
||||
self.tbSettings.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
# Assemble
|
||||
self.addAction(self.aProject)
|
||||
self.addAction(self.aNovel)
|
||||
self.addAction(self.aOutline)
|
||||
self.addAction(self.aBuild)
|
||||
self.addWidget(stretch)
|
||||
self.addAction(self.aDetails)
|
||||
self.addAction(self.aStats)
|
||||
self.addWidget(self.tbSettings)
|
||||
|
||||
logger.debug("GuiViewsBar initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
# END Class GuiViewsBar
|
||||
@@ -27,35 +27,34 @@ import os
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from enum import Enum
|
||||
from time import time
|
||||
from datetime import datetime
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer, QSize, QThreadPool, pyqtSlot
|
||||
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
|
||||
from PyQt5.QtGui import QIcon, QKeySequence, QCursor
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
|
||||
QMessageBox, QDialog, QTabWidget, QToolBar, QAction
|
||||
QMessageBox, QDialog, QStackedWidget
|
||||
)
|
||||
|
||||
from novelwriter.gui import (
|
||||
GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu,
|
||||
GuiMainStatus, GuiNovelTree, GuiOutline, GuiOutlineDetails, GuiProjectTree,
|
||||
GuiTheme
|
||||
GuiMainStatus, GuiNovelTree, GuiOutline, GuiProjectTree, GuiTheme,
|
||||
GuiViewsBar
|
||||
)
|
||||
from novelwriter.dialogs import (
|
||||
GuiAbout, GuiDocMerge, GuiDocSplit, GuiItemEditor, GuiPreferences,
|
||||
GuiProjectDetails, GuiProjectLoad, GuiProjectSettings, GuiUpdates,
|
||||
GuiWordList
|
||||
GuiAbout, GuiDocMerge, GuiDocSplit, GuiPreferences, GuiProjectDetails,
|
||||
GuiProjectLoad, GuiProjectSettings, GuiUpdates, GuiWordList
|
||||
)
|
||||
from novelwriter.tools import (
|
||||
GuiBuildNovel, GuiLipsum, GuiProjectWizard, GuiWritingStats
|
||||
)
|
||||
from novelwriter.core import NWProject, NWIndex
|
||||
from novelwriter.core import NWProject
|
||||
from novelwriter.enum import (
|
||||
nwItemType, nwItemClass, nwAlert, nwWidget, nwState
|
||||
nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwState, nwView
|
||||
)
|
||||
from novelwriter.common import getGuiItem, hexToInt
|
||||
from novelwriter.constants import nwLists
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -87,7 +86,6 @@ class GuiMain(QMainWindow):
|
||||
# Core Classes and Settings
|
||||
self.theTheme = GuiTheme()
|
||||
self.theProject = NWProject(self)
|
||||
self.theIndex = NWIndex(self.theProject)
|
||||
self.hasProject = False
|
||||
self.isFocusMode = False
|
||||
self.idleRefTime = time()
|
||||
@@ -103,8 +101,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
# Sizes
|
||||
mPx = self.mainConf.pxInt(4)
|
||||
fPx = self.theTheme.fontPixelSize
|
||||
fPt = self.theTheme.fontPointSize
|
||||
hWd = self.mainConf.pxInt(4)
|
||||
|
||||
# Main GUI Elements
|
||||
self.statusBar = GuiMainStatus(self)
|
||||
@@ -115,62 +112,43 @@ class GuiMain(QMainWindow):
|
||||
self.docViewer = GuiDocViewer(self)
|
||||
self.treeMeta = GuiItemDetails(self)
|
||||
self.projView = GuiOutline(self)
|
||||
self.projMeta = GuiOutlineDetails(self)
|
||||
self.mainMenu = GuiMainMenu(self)
|
||||
self.viewsBar = GuiViewsBar(self)
|
||||
|
||||
# Connect Signals Between Main Elements
|
||||
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
|
||||
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
|
||||
self.docEditor.docCountsChanged.connect(self.treeMeta.doUpdateCounts)
|
||||
self.docEditor.docCountsChanged.connect(self.treeView.doUpdateCounts)
|
||||
self.viewsBar.viewChangeRequested.connect(self._changeView)
|
||||
|
||||
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
|
||||
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
|
||||
self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
|
||||
self.treeView.treeItemChanged.connect(self.docEditor.updateDocInfo)
|
||||
self.treeView.treeItemChanged.connect(self.docViewer.updateDocInfo)
|
||||
self.treeView.treeItemChanged.connect(self.treeMeta.updateViewBox)
|
||||
self.treeView.rootFolderChanged.connect(self.projView.updateRootItem)
|
||||
|
||||
# Project Tree Tabs
|
||||
self.projTabs = QTabWidget()
|
||||
self.projTabs.setTabPosition(QTabWidget.South)
|
||||
self.projTabs.setStyleSheet("QTabWidget::pane {border: 0;};")
|
||||
self.projTabs.addTab(self.treeView, self.tr("Project"))
|
||||
self.projTabs.addTab(self.novelView, self.tr("Novel"))
|
||||
self.projTabs.currentChanged.connect(self._projTabsChanged)
|
||||
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
|
||||
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
|
||||
self.docEditor.docCountsChanged.connect(self.treeMeta.doUpdateCounts)
|
||||
self.docEditor.docCountsChanged.connect(self.treeView.doUpdateCounts)
|
||||
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
|
||||
|
||||
tabFont = self.projTabs.tabBar().font()
|
||||
tabFont.setPointSizeF(0.9*fPt)
|
||||
self.projTabs.tabBar().setFont(tabFont)
|
||||
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
|
||||
|
||||
# Project Tree Action Buttons
|
||||
btnSize = int(round(0.7*fPx))
|
||||
self.treeButtons = QToolBar()
|
||||
self.treeButtons.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.treeButtons.setIconSize(QSize(btnSize, btnSize))
|
||||
self.treeButtons.setContentsMargins(0, 0, 0, 0)
|
||||
self.treeButtons.setStyleSheet("QToolBar {padding: 0;}")
|
||||
self.projTabs.setCornerWidget(self.treeButtons, Qt.BottomRightCorner)
|
||||
self.projView.loadDocumentTagRequest.connect(self._followTag)
|
||||
|
||||
self.projDetailsBtn = QAction(self.tr("Project Details"))
|
||||
self.projDetailsBtn.setIcon(self.theTheme.getIcon("status_lines"))
|
||||
self.projDetailsBtn.triggered.connect(lambda: self.showProjectDetailsDialog())
|
||||
self.treeButtons.addAction(self.projDetailsBtn)
|
||||
|
||||
self.projStatsBtn = QAction(self.tr("Writing Statistics"))
|
||||
self.projStatsBtn.setIcon(self.theTheme.getIcon("status_stats"))
|
||||
self.projStatsBtn.triggered.connect(lambda: self.showWritingStatsDialog())
|
||||
self.treeButtons.addAction(self.projStatsBtn)
|
||||
|
||||
self.projSettingsBtn = QAction(self.tr("Project Settings"))
|
||||
self.projSettingsBtn.setIcon(self.theTheme.getIcon("settings"))
|
||||
self.projSettingsBtn.triggered.connect(lambda: self.showProjectSettingsDialog())
|
||||
self.treeButtons.addAction(self.projSettingsBtn)
|
||||
# Project Tree Stack
|
||||
self.projStack = QStackedWidget()
|
||||
self.projStack.addWidget(self.treeView)
|
||||
self.projStack.addWidget(self.novelView)
|
||||
self.projStack.currentChanged.connect(self._projStackChanged)
|
||||
|
||||
# Project Tree View
|
||||
self.treePane = QWidget()
|
||||
self.treeBox = QVBoxLayout()
|
||||
self.treeBox.setContentsMargins(0, 0, 0, 0)
|
||||
self.treeBox.setSpacing(mPx)
|
||||
self.treeBox.addWidget(self.projTabs)
|
||||
self.treeBox.addWidget(self.projStack)
|
||||
self.treeBox.addWidget(self.treeMeta)
|
||||
self.treePane.setLayout(self.treeBox)
|
||||
|
||||
@@ -178,47 +156,42 @@ class GuiMain(QMainWindow):
|
||||
self.splitView = QSplitter(Qt.Vertical)
|
||||
self.splitView.addWidget(self.docViewer)
|
||||
self.splitView.addWidget(self.viewMeta)
|
||||
self.splitView.setHandleWidth(hWd)
|
||||
self.splitView.setSizes(self.mainConf.getViewPanePos())
|
||||
|
||||
# Splitter : Document Editor / Document Viewer
|
||||
self.splitDocs = QSplitter(Qt.Horizontal)
|
||||
self.splitDocs.addWidget(self.docEditor)
|
||||
self.splitDocs.addWidget(self.splitView)
|
||||
|
||||
# Splitter : Project Outlie / Outline Details
|
||||
self.splitOutline = QSplitter(Qt.Vertical)
|
||||
self.splitOutline.addWidget(self.projView)
|
||||
self.splitOutline.addWidget(self.projMeta)
|
||||
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos())
|
||||
|
||||
# Main Tabs : Editor / Outline
|
||||
self.mainTabs = QTabWidget()
|
||||
self.mainTabs.setTabPosition(QTabWidget.East)
|
||||
self.mainTabs.setStyleSheet("QTabWidget::pane {border: 0;}")
|
||||
self.mainTabs.addTab(self.splitDocs, self.tr("Editor"))
|
||||
self.mainTabs.addTab(self.splitOutline, self.tr("Outline"))
|
||||
self.mainTabs.currentChanged.connect(self._mainTabChanged)
|
||||
self.splitDocs.setHandleWidth(hWd)
|
||||
|
||||
# Splitter : Project Tree / Main Tabs
|
||||
self.splitMain = QSplitter(Qt.Horizontal)
|
||||
self.splitMain.setContentsMargins(mPx, mPx, mPx, mPx)
|
||||
self.splitMain.setContentsMargins(0, 0, 0, 0)
|
||||
self.splitMain.addWidget(self.treePane)
|
||||
self.splitMain.addWidget(self.mainTabs)
|
||||
self.splitMain.addWidget(self.splitDocs)
|
||||
self.splitMain.setHandleWidth(hWd)
|
||||
self.splitMain.setSizes(self.mainConf.getMainPanePos())
|
||||
|
||||
# Main Stack : Editor / Outline
|
||||
self.mainStack = QStackedWidget()
|
||||
self.mainStack.addWidget(self.splitMain)
|
||||
self.mainStack.addWidget(self.projView)
|
||||
self.mainStack.currentChanged.connect(self._mainStackChanged)
|
||||
|
||||
# Indices of Splitter Widgets
|
||||
self.idxTree = self.splitMain.indexOf(self.treePane)
|
||||
self.idxMain = self.splitMain.indexOf(self.mainTabs)
|
||||
self.idxMain = self.splitMain.indexOf(self.splitDocs)
|
||||
self.idxEditor = self.splitDocs.indexOf(self.docEditor)
|
||||
self.idxViewer = self.splitDocs.indexOf(self.splitView)
|
||||
self.idxViewDoc = self.splitView.indexOf(self.docViewer)
|
||||
self.idxViewMeta = self.splitView.indexOf(self.viewMeta)
|
||||
|
||||
# Indices of Tab Widgets
|
||||
self.idxTabEdit = self.mainTabs.indexOf(self.splitDocs)
|
||||
self.idxTabProj = self.mainTabs.indexOf(self.splitOutline)
|
||||
self.idxTreeView = self.projTabs.indexOf(self.treeView)
|
||||
self.idxNovelView = self.projTabs.indexOf(self.novelView)
|
||||
self.idxEditorView = self.mainStack.indexOf(self.splitMain)
|
||||
self.idxOutlineView = self.mainStack.indexOf(self.projView)
|
||||
self.idxTreeView = self.projStack.indexOf(self.treeView)
|
||||
self.idxNovelView = self.projStack.indexOf(self.novelView)
|
||||
|
||||
# Splitter Behaviour
|
||||
self.splitMain.setCollapsible(self.idxTree, False)
|
||||
@@ -237,8 +210,9 @@ class GuiMain(QMainWindow):
|
||||
|
||||
# Set Main Window Elements
|
||||
self.setMenuBar(self.mainMenu)
|
||||
self.setCentralWidget(self.splitMain)
|
||||
self.setCentralWidget(self.mainStack)
|
||||
self.setStatusBar(self.statusBar)
|
||||
self.addToolBar(Qt.LeftToolBarArea, self.viewsBar)
|
||||
|
||||
# Finalise Initialisation
|
||||
# =======================
|
||||
@@ -323,7 +297,7 @@ class GuiMain(QMainWindow):
|
||||
self.docEditor.clearEditor()
|
||||
self.docEditor.setDictionaries()
|
||||
self.closeDocViewer()
|
||||
self.projMeta.clearDetails()
|
||||
self.projView.clearOutline()
|
||||
|
||||
# General
|
||||
self.statusBar.clearStatus()
|
||||
@@ -385,6 +359,7 @@ class GuiMain(QMainWindow):
|
||||
self.rebuildTrees()
|
||||
self.saveProject()
|
||||
self.docEditor.setDictionaries()
|
||||
self.projView.updateRootItem(None)
|
||||
self.rebuildIndex(beQuiet=True)
|
||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||
self.statusBar.setProjectStatus(nwState.GOOD)
|
||||
@@ -446,10 +421,10 @@ class GuiMain(QMainWindow):
|
||||
self.idleRefTime = time()
|
||||
self.idleTime = 0.0
|
||||
|
||||
self.theIndex.clearIndex()
|
||||
self.theProject.index.clearIndex()
|
||||
self.clearGUI()
|
||||
self.hasProject = False
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.PROJECT)
|
||||
|
||||
return saveOK
|
||||
|
||||
@@ -465,7 +440,7 @@ class GuiMain(QMainWindow):
|
||||
return False
|
||||
|
||||
# Switch main tab to editor view
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.PROJECT)
|
||||
|
||||
# Try to open the project
|
||||
if not self.theProject.openProject(projFile):
|
||||
@@ -523,15 +498,15 @@ class GuiMain(QMainWindow):
|
||||
self.idleTime = 0.0
|
||||
|
||||
# Load the tag index
|
||||
self.theIndex.loadIndex()
|
||||
self.theProject.index.loadIndex()
|
||||
|
||||
# Update GUI
|
||||
self._updateWindowTitle(self.theProject.projName)
|
||||
self.rebuildTrees()
|
||||
self.docEditor.setDictionaries()
|
||||
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
|
||||
self.mainMenu.setAutoOutline(self.theProject.autoOutline)
|
||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||
self.projView.updateRootItem(None)
|
||||
self._updateStatusWordCount()
|
||||
|
||||
# Restore previously open documents, if any
|
||||
@@ -542,10 +517,10 @@ class GuiMain(QMainWindow):
|
||||
self.viewDocument(self.theProject.lastViewed)
|
||||
|
||||
# Check if we need to rebuild the index
|
||||
if self.theIndex.indexBroken:
|
||||
if self.theProject.index.indexBroken:
|
||||
self.makeAlert(self.tr(
|
||||
"The project index is outdated or broken. Rebuilding index."
|
||||
), nwAlert.WARN)
|
||||
), nwAlert.INFO)
|
||||
self.rebuildIndex()
|
||||
|
||||
# Make sure the changed status is set to false on things opened
|
||||
@@ -566,7 +541,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
self.treeView.saveTreeOrder()
|
||||
if self.theProject.saveProject(autoSave=autoSave):
|
||||
self.theIndex.saveIndex()
|
||||
self.theProject.index.saveIndex()
|
||||
|
||||
return True
|
||||
|
||||
@@ -599,12 +574,12 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
if not self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
|
||||
if not self.theProject.tree.checkType(tHandle, nwItemType.FILE):
|
||||
logger.debug("Requested item '%s' is not a document", tHandle)
|
||||
return False
|
||||
|
||||
self.closeDocument()
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.EDITOR)
|
||||
if self.docEditor.loadText(tHandle, tLine):
|
||||
if changeFocus:
|
||||
self.docEditor.setFocus()
|
||||
@@ -623,12 +598,11 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
self.treeView.flushTreeOrder()
|
||||
nHandle = None # The next handle after tHandle
|
||||
fHandle = None # The first file handle we encounter
|
||||
foundIt = False # We've found tHandle, pick the next we see
|
||||
for tItem in self.theProject.projTree:
|
||||
if not self.theProject.projTree.checkType(tItem.itemHandle, nwItemType.FILE):
|
||||
for tItem in self.theProject.tree:
|
||||
if not self.theProject.tree.checkType(tItem.itemHandle, nwItemType.FILE):
|
||||
continue
|
||||
if fHandle is None:
|
||||
fHandle = tItem.itemHandle
|
||||
@@ -687,7 +661,7 @@ class GuiMain(QMainWindow):
|
||||
return False
|
||||
|
||||
# Make sure main tab is in Editor view
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.EDITOR)
|
||||
|
||||
logger.debug("Viewing document with handle '%s'", tHandle)
|
||||
if self.docViewer.loadText(tHandle):
|
||||
@@ -817,7 +791,7 @@ class GuiMain(QMainWindow):
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
elif self.novelView.hasFocus():
|
||||
tHandle, tLine = self.novelView.getSelectedHandle()
|
||||
elif self.projView.hasFocus():
|
||||
elif self.projView.treeFocus():
|
||||
tHandle, tLine = self.projView.getSelectedHandle()
|
||||
else:
|
||||
logger.warning("No item selected")
|
||||
@@ -840,27 +814,10 @@ class GuiMain(QMainWindow):
|
||||
tHandle = self.docEditor.docHandle()
|
||||
else:
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
if tHandle:
|
||||
return self.treeView.editTreeItem(tHandle)
|
||||
|
||||
if tHandle is None:
|
||||
logger.warning("No item selected")
|
||||
return False
|
||||
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
if tItem is None:
|
||||
return False
|
||||
if tItem.itemType not in nwLists.REG_TYPES:
|
||||
return False
|
||||
|
||||
logger.verbose("Requesting change to item '%s'", tHandle)
|
||||
dlgProj = GuiItemEditor(self, tHandle)
|
||||
dlgProj.exec_()
|
||||
if dlgProj.result() == QDialog.Accepted:
|
||||
self.treeView.setTreeItemValues(tHandle)
|
||||
self.treeMeta.updateViewBox(tHandle)
|
||||
self.docEditor.updateDocInfo(tHandle)
|
||||
self.docViewer.updateDocInfo(tHandle)
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
def rebuildTrees(self):
|
||||
"""Rebuild the project tree.
|
||||
@@ -872,7 +829,7 @@ class GuiMain(QMainWindow):
|
||||
def requestNovelTreeRefresh(self):
|
||||
"""Update the novel tree, but only if it is visible.
|
||||
"""
|
||||
if self.projTabs.currentIndex() == self.idxNovelView and self.hasProject:
|
||||
if self.projStack.currentIndex() == self.idxNovelView and self.hasProject:
|
||||
self.novelView.refreshTree()
|
||||
return True
|
||||
return False
|
||||
@@ -889,25 +846,16 @@ class GuiMain(QMainWindow):
|
||||
tStart = time()
|
||||
|
||||
self.treeView.saveTreeOrder()
|
||||
self.theIndex.clearIndex()
|
||||
self.theProject.index.clearIndex()
|
||||
|
||||
for tItem in self.theProject.projTree:
|
||||
for tItem in self.theProject.tree:
|
||||
if tItem is None: # pragma: no cover
|
||||
continue # This is a bug trap
|
||||
|
||||
if tItem is not None:
|
||||
self.setStatus(self.tr("Indexing: '{0}'").format(tItem.itemName))
|
||||
else:
|
||||
self.setStatus(self.tr("Indexing: '{0}'").format(self.tr("Unknown item")))
|
||||
|
||||
if tItem is not None and tItem.itemType == nwItemType.FILE:
|
||||
logger.verbose("Scanning '%s'", tItem.itemName)
|
||||
self.theIndex.reIndexHandle(tItem.itemHandle)
|
||||
|
||||
# Get Word Counts
|
||||
cC, wC, pC = self.theIndex.getCounts(tItem.itemHandle)
|
||||
tItem.setCharCount(cC)
|
||||
tItem.setWordCount(wC)
|
||||
tItem.setParaCount(pC)
|
||||
self.treeView.propagateCount(tItem.itemHandle, wC)
|
||||
logger.verbose("Indexing '%s'", tItem.itemName)
|
||||
if self.theProject.index.reIndexHandle(tItem.itemHandle):
|
||||
# Update Word Counts
|
||||
self.treeView.propagateCount(tItem.itemHandle, tItem.wordCount, countChildren=True)
|
||||
self.treeView.setTreeItemValues(tItem.itemHandle)
|
||||
|
||||
tEnd = time()
|
||||
@@ -925,19 +873,6 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return True
|
||||
|
||||
def rebuildOutline(self):
|
||||
"""Force a rebuild of the Outline view.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
logger.verbose("Forcing a rebuild of the Project Outline")
|
||||
self.mainTabs.setCurrentWidget(self.splitOutline)
|
||||
self.projView.refreshTree(overRide=True)
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Main Dialogs
|
||||
##
|
||||
@@ -985,7 +920,6 @@ class GuiMain(QMainWindow):
|
||||
self.treeView.initTree()
|
||||
self.novelView.initTree()
|
||||
self.projView.initOutline()
|
||||
self.projMeta.initDetails()
|
||||
self._updateStatusWordCount()
|
||||
|
||||
return
|
||||
@@ -1002,7 +936,9 @@ class GuiMain(QMainWindow):
|
||||
|
||||
if dlgProj.result() == QDialog.Accepted:
|
||||
logger.debug("Applying new project settings")
|
||||
self.docEditor.setDictionaries()
|
||||
if dlgProj.spellChanged:
|
||||
self.docEditor.setDictionaries()
|
||||
self.treeMeta.refreshDetails()
|
||||
self._updateWindowTitle(self.theProject.projName)
|
||||
|
||||
return True
|
||||
@@ -1014,8 +950,6 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
self.treeView.flushTreeOrder()
|
||||
|
||||
dlgDetails = getGuiItem("GuiProjectDetails")
|
||||
if dlgDetails is None:
|
||||
dlgDetails = GuiProjectDetails(self)
|
||||
@@ -1220,7 +1154,7 @@ class GuiMain(QMainWindow):
|
||||
if not self.isFocusMode:
|
||||
self.mainConf.setMainPanePos(self.splitMain.sizes())
|
||||
self.mainConf.setDocPanePos(self.splitDocs.sizes())
|
||||
self.mainConf.setOutlinePanePos(self.splitOutline.sizes())
|
||||
self.mainConf.setOutlinePanePos(self.projView.splitSizes())
|
||||
if self.viewMeta.isVisible():
|
||||
self.mainConf.setViewPanePos(self.splitView.sizes())
|
||||
|
||||
@@ -1244,20 +1178,20 @@ class GuiMain(QMainWindow):
|
||||
"""Switch focus between main GUI views.
|
||||
"""
|
||||
if paneNo == nwWidget.TREE:
|
||||
tabIdx = self.projTabs.currentIndex()
|
||||
tabIdx = self.projStack.currentIndex()
|
||||
if tabIdx == self.idxTreeView:
|
||||
self.treeView.setFocus()
|
||||
elif tabIdx == self.idxNovelView:
|
||||
self.novelView.setFocus()
|
||||
elif paneNo == nwWidget.EDITOR:
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.EDITOR)
|
||||
self.docEditor.setFocus()
|
||||
elif paneNo == nwWidget.VIEWER:
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.EDITOR)
|
||||
self.docViewer.setFocus()
|
||||
elif paneNo == nwWidget.OUTLINE:
|
||||
self.mainTabs.setCurrentWidget(self.splitOutline)
|
||||
self.projView.setFocus()
|
||||
self._changeView(nwView.OUTLINE)
|
||||
self.projView.setTreeFocus()
|
||||
return
|
||||
|
||||
def closeDocEditor(self):
|
||||
@@ -1289,7 +1223,6 @@ class GuiMain(QMainWindow):
|
||||
self.mainMenu.setFocusMode(self.isFocusMode)
|
||||
if self.isFocusMode:
|
||||
logger.debug("Activating Focus Mode")
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self.switchFocus(nwWidget.EDITOR)
|
||||
else:
|
||||
logger.debug("Deactivating Focus Mode")
|
||||
@@ -1298,7 +1231,7 @@ class GuiMain(QMainWindow):
|
||||
self.treePane.setVisible(isVisible)
|
||||
self.statusBar.setVisible(isVisible)
|
||||
self.mainMenu.setVisible(isVisible)
|
||||
self.mainTabs.tabBar().setVisible(isVisible)
|
||||
self.viewsBar.setVisible(isVisible)
|
||||
|
||||
hideDocFooter = self.isFocusMode and self.mainConf.hideFocusFooter
|
||||
self.docEditor.docFooter.setVisible(not hideDocFooter)
|
||||
@@ -1426,7 +1359,7 @@ class GuiMain(QMainWindow):
|
||||
return True
|
||||
|
||||
def _updateWindowTitle(self, projName=None):
|
||||
"""Set the window title and add the project's working title.
|
||||
"""Set the window title and add the project's name.
|
||||
"""
|
||||
winTitle = self.mainConf.appName
|
||||
if projName is not None:
|
||||
@@ -1469,9 +1402,9 @@ class GuiMain(QMainWindow):
|
||||
"popMinimal": newProj.field("popMinimal"),
|
||||
"popCustom": newProj.field("popCustom"),
|
||||
"addRoots": [],
|
||||
"addNotes": False,
|
||||
"numChapters": 0,
|
||||
"numScenes": 0,
|
||||
"chFolders": False,
|
||||
}
|
||||
if newProj.field("popCustom"):
|
||||
addRoots = []
|
||||
@@ -1481,19 +1414,30 @@ class GuiMain(QMainWindow):
|
||||
addRoots.append(nwItemClass.CHARACTER)
|
||||
if newProj.field("addWorld"):
|
||||
addRoots.append(nwItemClass.WORLD)
|
||||
if newProj.field("addTime"):
|
||||
addRoots.append(nwItemClass.TIMELINE)
|
||||
if newProj.field("addObject"):
|
||||
addRoots.append(nwItemClass.OBJECT)
|
||||
if newProj.field("addEntity"):
|
||||
addRoots.append(nwItemClass.ENTITY)
|
||||
projData["addRoots"] = addRoots
|
||||
projData["addNotes"] = newProj.field("addNotes")
|
||||
projData["numChapters"] = newProj.field("numChapters")
|
||||
projData["numScenes"] = newProj.field("numScenes")
|
||||
projData["chFolders"] = newProj.field("chFolders")
|
||||
|
||||
return projData
|
||||
|
||||
def _getTagSource(self, tTag):
|
||||
"""A wrapper function for the index lookup of a tag that will
|
||||
display an alert if the tag cannot be found.
|
||||
"""
|
||||
tHandle, sTitle = self.theProject.index.getTagSource(tTag)
|
||||
if tHandle is None:
|
||||
self.makeAlert(self.tr(
|
||||
"Could not find the reference for tag '{0}'. It either doesn't "
|
||||
"exist, or the index is out of date. The index can be updated "
|
||||
"from the Tools menu, or by pressing {1}."
|
||||
).format(
|
||||
tTag, "F9"
|
||||
), nwAlert.ERROR)
|
||||
return None, None
|
||||
|
||||
return tHandle, sTitle
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -1509,9 +1453,42 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str, Enum)
|
||||
def _followTag(self, tTag, tMode):
|
||||
"""Follow a tag after user interaction with a link.
|
||||
"""
|
||||
tHandle, sTitle = self._getTagSource(tTag)
|
||||
if tHandle is not None:
|
||||
if tMode == nwDocMode.EDIT:
|
||||
self.openDocument(tHandle)
|
||||
elif tMode == nwDocMode.VIEW:
|
||||
self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}")
|
||||
return
|
||||
|
||||
@pyqtSlot(nwView)
|
||||
def _changeView(self, view):
|
||||
"""Handle the requested change of view from the GuiViewBar.
|
||||
"""
|
||||
if view == nwView.EDITOR:
|
||||
# Only change the main stack, but not the project stack
|
||||
self.mainStack.setCurrentWidget(self.splitMain)
|
||||
|
||||
elif view == nwView.PROJECT:
|
||||
self.mainStack.setCurrentWidget(self.splitMain)
|
||||
self.projStack.setCurrentWidget(self.treeView)
|
||||
|
||||
elif view == nwView.NOVEL:
|
||||
self.mainStack.setCurrentWidget(self.splitMain)
|
||||
self.projStack.setCurrentWidget(self.novelView)
|
||||
|
||||
elif view == nwView.OUTLINE:
|
||||
self.mainStack.setCurrentWidget(self.projView)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _timeTick(self):
|
||||
"""Triggered on every tick of the main timer.
|
||||
@@ -1567,11 +1544,17 @@ class GuiMain(QMainWindow):
|
||||
@pyqtSlot("QTreeWidgetItem*", int)
|
||||
def _treeDoubleClick(self, tItem, colNo):
|
||||
"""The user double-clicked an item in the tree. If it is a file,
|
||||
we open it. Otherwise, we do nothing.
|
||||
we open it. Otherwise, we toggle the expanded status.
|
||||
"""
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
if tHandle is not None:
|
||||
self.openDocument(tHandle, changeFocus=False, doScroll=False)
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
if tItem is None:
|
||||
return
|
||||
if tItem.itemType == nwItemType.FILE:
|
||||
self.openDocument(tHandle, changeFocus=False, doScroll=False)
|
||||
else:
|
||||
self.treeView.toggleExpanded(tHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
@@ -1579,11 +1562,10 @@ class GuiMain(QMainWindow):
|
||||
"""Triggered when there is a change to a novel item in the
|
||||
project tree.
|
||||
"""
|
||||
if self.mainTabs.currentIndex() == self.idxTabProj:
|
||||
if self.mainStack.currentIndex() == self.idxOutlineView:
|
||||
logger.verbose("Novel tree changed while Outline tab active")
|
||||
if self.hasProject:
|
||||
self.treeView.flushTreeOrder()
|
||||
self.projView.refreshTree(novelChanged=True)
|
||||
self.projView.refreshView(novelChanged=True)
|
||||
|
||||
return
|
||||
|
||||
@@ -1608,20 +1590,20 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _mainTabChanged(self, tabIndex):
|
||||
def _mainStackChanged(self, tabIndex):
|
||||
"""Activated when the main window tab is changed.
|
||||
"""
|
||||
if tabIndex == self.idxTabEdit:
|
||||
if tabIndex == self.idxEditorView:
|
||||
logger.verbose("Editor tab activated")
|
||||
elif tabIndex == self.idxTabProj:
|
||||
elif tabIndex == self.idxOutlineView:
|
||||
logger.verbose("Project outline tab activated")
|
||||
if self.hasProject:
|
||||
self.projView.refreshTree()
|
||||
self.projView.refreshView()
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _projTabsChanged(self, tabIndex):
|
||||
def _projStackChanged(self, tabIndex):
|
||||
"""Activated when the project view tab is changed.
|
||||
"""
|
||||
sHandle = None
|
||||
|
||||
@@ -75,7 +75,6 @@ class GuiBuildNovel(QDialog):
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
|
||||
self.htmlText = [] # List of html documents
|
||||
self.htmlStyle = [] # List of html styles
|
||||
@@ -86,9 +85,10 @@ class GuiBuildNovel(QDialog):
|
||||
self.setMinimumWidth(self.mainConf.pxInt(700))
|
||||
self.setMinimumHeight(self.mainConf.pxInt(600))
|
||||
|
||||
pOptions = self.theProject.options
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winWidth", 900)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winHeight", 800))
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winWidth", 900)),
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winHeight", 800))
|
||||
)
|
||||
|
||||
self.docView = GuiBuildNovelDocView(self, self.theProject)
|
||||
@@ -174,12 +174,12 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.hideScene = QSwitch(width=wS, height=hS)
|
||||
self.hideScene.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "hideScene", False)
|
||||
pOptions.getBool("GuiBuildNovel", "hideScene", False)
|
||||
)
|
||||
|
||||
self.hideSection = QSwitch(width=wS, height=hS)
|
||||
self.hideSection.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "hideSection", True)
|
||||
pOptions.getBool("GuiBuildNovel", "hideSection", True)
|
||||
)
|
||||
|
||||
# Wrapper boxes due to QGridView and QLineEdit expand bug
|
||||
@@ -235,7 +235,7 @@ class GuiBuildNovel(QDialog):
|
||||
self.textFont.setReadOnly(True)
|
||||
self.textFont.setMinimumWidth(xFmt)
|
||||
self.textFont.setText(
|
||||
self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
|
||||
pOptions.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
|
||||
)
|
||||
self.fontButton = QPushButton("...")
|
||||
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||
@@ -247,7 +247,7 @@ class GuiBuildNovel(QDialog):
|
||||
self.textSize.setMaximum(72)
|
||||
self.textSize.setSingleStep(1)
|
||||
self.textSize.setValue(
|
||||
self.optState.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
|
||||
pOptions.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
|
||||
)
|
||||
|
||||
self.lineHeight = QDoubleSpinBox(self)
|
||||
@@ -257,7 +257,7 @@ class GuiBuildNovel(QDialog):
|
||||
self.lineHeight.setSingleStep(0.05)
|
||||
self.lineHeight.setDecimals(2)
|
||||
self.lineHeight.setValue(
|
||||
self.optState.getFloat("GuiBuildNovel", "lineHeight", 1.15)
|
||||
pOptions.getFloat("GuiBuildNovel", "lineHeight", 1.15)
|
||||
)
|
||||
|
||||
# Wrapper box due to QGridView and QLineEdit expand bug
|
||||
@@ -291,12 +291,12 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.justifyText = QSwitch(width=wS, height=hS)
|
||||
self.justifyText.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "justifyText", False)
|
||||
pOptions.getBool("GuiBuildNovel", "justifyText", False)
|
||||
)
|
||||
|
||||
self.noStyling = QSwitch(width=wS, height=hS)
|
||||
self.noStyling.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "noStyling", False)
|
||||
pOptions.getBool("GuiBuildNovel", "noStyling", False)
|
||||
)
|
||||
|
||||
self.styleForm.addWidget(justifyLabel, 1, 0, 1, 1, Qt.AlignLeft)
|
||||
@@ -316,22 +316,22 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.includeSynopsis = QSwitch(width=wS, height=hS)
|
||||
self.includeSynopsis.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incSynopsis", False)
|
||||
pOptions.getBool("GuiBuildNovel", "incSynopsis", False)
|
||||
)
|
||||
|
||||
self.includeComments = QSwitch(width=wS, height=hS)
|
||||
self.includeComments.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incComments", False)
|
||||
pOptions.getBool("GuiBuildNovel", "incComments", False)
|
||||
)
|
||||
|
||||
self.includeKeywords = QSwitch(width=wS, height=hS)
|
||||
self.includeKeywords.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incKeywords", False)
|
||||
pOptions.getBool("GuiBuildNovel", "incKeywords", False)
|
||||
)
|
||||
|
||||
self.includeBody = QSwitch(width=wS, height=hS)
|
||||
self.includeBody.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incBodyText", True)
|
||||
pOptions.getBool("GuiBuildNovel", "incBodyText", True)
|
||||
)
|
||||
|
||||
synopsisLabel = QLabel(self.tr("Include synopsis"))
|
||||
@@ -360,17 +360,17 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.novelFiles = QSwitch(width=wS, height=hS)
|
||||
self.novelFiles.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "addNovel", True)
|
||||
pOptions.getBool("GuiBuildNovel", "addNovel", True)
|
||||
)
|
||||
|
||||
self.noteFiles = QSwitch(width=wS, height=hS)
|
||||
self.noteFiles.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "addNotes", False)
|
||||
pOptions.getBool("GuiBuildNovel", "addNotes", False)
|
||||
)
|
||||
|
||||
self.ignoreFlag = QSwitch(width=wS, height=hS)
|
||||
self.ignoreFlag.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "ignoreFlag", False)
|
||||
pOptions.getBool("GuiBuildNovel", "ignoreFlag", False)
|
||||
)
|
||||
|
||||
novelLabel = QLabel(self.tr("Include novel files"))
|
||||
@@ -396,12 +396,12 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.replaceTabs = QSwitch(width=wS, height=hS)
|
||||
self.replaceTabs.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "replaceTabs", False)
|
||||
pOptions.getBool("GuiBuildNovel", "replaceTabs", False)
|
||||
)
|
||||
|
||||
self.replaceUCode = QSwitch(width=wS, height=hS)
|
||||
self.replaceUCode.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "replaceUCode", False)
|
||||
pOptions.getBool("GuiBuildNovel", "replaceUCode", False)
|
||||
)
|
||||
|
||||
tabsLabel = QLabel(self.tr("Replace tabs with spaces"))
|
||||
@@ -493,9 +493,9 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
# Splitter Position
|
||||
boxWidth = self.mainConf.pxInt(350)
|
||||
boxWidth = self.optState.getInt("GuiBuildNovel", "boxWidth", boxWidth)
|
||||
boxWidth = pOptions.getInt("GuiBuildNovel", "boxWidth", boxWidth)
|
||||
docWidth = max(self.width() - boxWidth, 100)
|
||||
docWidth = self.optState.getInt("GuiBuildNovel", "docWidth", docWidth)
|
||||
docWidth = pOptions.getInt("GuiBuildNovel", "docWidth", docWidth)
|
||||
|
||||
# The Tool Box
|
||||
self.toolsBox = QVBoxLayout()
|
||||
@@ -659,6 +659,7 @@ class GuiBuildNovel(QDialog):
|
||||
fmtUnnumbered = self.fmtUnnumbered.text()
|
||||
fmtScene = self.fmtScene.text()
|
||||
fmtSection = self.fmtSection.text()
|
||||
buildLang = self.buildLang.currentData()
|
||||
hideScene = self.hideScene.isChecked()
|
||||
hideSection = self.hideSection.isChecked()
|
||||
textFont = self.textFont.text()
|
||||
@@ -676,7 +677,7 @@ class GuiBuildNovel(QDialog):
|
||||
replaceUCode = self.replaceUCode.isChecked()
|
||||
|
||||
# The language lookup dict is reloaded if needed
|
||||
self.theProject.setProjectLang(self.buildLang.currentData())
|
||||
self.theProject.setProjectLang(buildLang)
|
||||
|
||||
# Get font information
|
||||
fontInfo = QFontInfo(QFont(textFont, textSize))
|
||||
@@ -706,16 +707,16 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
if isOdt:
|
||||
bldObj.setColourHeaders(not noStyling)
|
||||
bldObj.setLanguage(buildLang)
|
||||
bldObj.initDocument()
|
||||
|
||||
# Make sure the project and document is up to date
|
||||
self.theParent.treeView.flushTreeOrder()
|
||||
self.theParent.saveDocument()
|
||||
|
||||
self.buildProgress.setMaximum(len(self.theProject.projTree))
|
||||
self.buildProgress.setMaximum(len(self.theProject.tree))
|
||||
self.buildProgress.setValue(0)
|
||||
|
||||
for nItt, tItem in enumerate(self.theProject.projTree):
|
||||
for nItt, tItem in enumerate(self.theProject.tree):
|
||||
|
||||
noteRoot = noteFiles
|
||||
noteRoot &= tItem.itemType == nwItemType.ROOT
|
||||
@@ -780,14 +781,12 @@ class GuiBuildNovel(QDialog):
|
||||
if theItem is None:
|
||||
return False
|
||||
|
||||
if not theItem.isExported and not ignoreFlag:
|
||||
if not (theItem.isExported or ignoreFlag):
|
||||
return False
|
||||
|
||||
isNone = theItem.itemType != nwItemType.FILE
|
||||
isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
||||
isNone |= theItem.itemClass == nwItemClass.NO_CLASS
|
||||
isNone |= theItem.itemClass == nwItemClass.TRASH
|
||||
isNone |= theItem.itemParent == self.theProject.projTree.trashRoot()
|
||||
isNone |= theItem.isInactive()
|
||||
isNone |= theItem.itemParent is None
|
||||
isNote = theItem.itemLayout == nwItemLayout.NOTE
|
||||
isNovel = not isNone and not isNote
|
||||
@@ -799,10 +798,6 @@ class GuiBuildNovel(QDialog):
|
||||
if isNovel and not novelFiles:
|
||||
return False
|
||||
|
||||
rootItem = self.theProject.projTree.getRootItem(theItem.itemHandle)
|
||||
if rootItem.itemClass == nwItemClass.ARCHIVE:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _saveDocument(self, theFmt):
|
||||
@@ -1159,28 +1154,28 @@ class GuiBuildNovel(QDialog):
|
||||
self.theProject.setProjectLang(buildLang)
|
||||
|
||||
# GUI Settings
|
||||
self.optState.setValue("GuiBuildNovel", "hideScene", hideScene)
|
||||
self.optState.setValue("GuiBuildNovel", "hideSection", hideSection)
|
||||
self.optState.setValue("GuiBuildNovel", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiBuildNovel", "winHeight", winHeight)
|
||||
self.optState.setValue("GuiBuildNovel", "boxWidth", boxWidth)
|
||||
self.optState.setValue("GuiBuildNovel", "docWidth", docWidth)
|
||||
self.optState.setValue("GuiBuildNovel", "justifyText", justifyText)
|
||||
self.optState.setValue("GuiBuildNovel", "noStyling", noStyling)
|
||||
self.optState.setValue("GuiBuildNovel", "textFont", textFont)
|
||||
self.optState.setValue("GuiBuildNovel", "textSize", textSize)
|
||||
self.optState.setValue("GuiBuildNovel", "lineHeight", lineHeight)
|
||||
self.optState.setValue("GuiBuildNovel", "addNovel", novelFiles)
|
||||
self.optState.setValue("GuiBuildNovel", "addNotes", noteFiles)
|
||||
self.optState.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag)
|
||||
self.optState.setValue("GuiBuildNovel", "incSynopsis", incSynopsis)
|
||||
self.optState.setValue("GuiBuildNovel", "incComments", incComments)
|
||||
self.optState.setValue("GuiBuildNovel", "incKeywords", incKeywords)
|
||||
self.optState.setValue("GuiBuildNovel", "incBodyText", incBodyText)
|
||||
self.optState.setValue("GuiBuildNovel", "replaceTabs", replaceTabs)
|
||||
self.optState.setValue("GuiBuildNovel", "replaceUCode", replaceUCode)
|
||||
|
||||
self.optState.saveSettings()
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiBuildNovel", "hideScene", hideScene)
|
||||
pOptions.setValue("GuiBuildNovel", "hideSection", hideSection)
|
||||
pOptions.setValue("GuiBuildNovel", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiBuildNovel", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiBuildNovel", "boxWidth", boxWidth)
|
||||
pOptions.setValue("GuiBuildNovel", "docWidth", docWidth)
|
||||
pOptions.setValue("GuiBuildNovel", "justifyText", justifyText)
|
||||
pOptions.setValue("GuiBuildNovel", "noStyling", noStyling)
|
||||
pOptions.setValue("GuiBuildNovel", "textFont", textFont)
|
||||
pOptions.setValue("GuiBuildNovel", "textSize", textSize)
|
||||
pOptions.setValue("GuiBuildNovel", "lineHeight", lineHeight)
|
||||
pOptions.setValue("GuiBuildNovel", "addNovel", novelFiles)
|
||||
pOptions.setValue("GuiBuildNovel", "addNotes", noteFiles)
|
||||
pOptions.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag)
|
||||
pOptions.setValue("GuiBuildNovel", "incSynopsis", incSynopsis)
|
||||
pOptions.setValue("GuiBuildNovel", "incComments", incComments)
|
||||
pOptions.setValue("GuiBuildNovel", "incKeywords", incKeywords)
|
||||
pOptions.setValue("GuiBuildNovel", "incBodyText", incBodyText)
|
||||
pOptions.setValue("GuiBuildNovel", "replaceTabs", replaceTabs)
|
||||
pOptions.setValue("GuiBuildNovel", "replaceUCode", replaceUCode)
|
||||
pOptions.saveSettings()
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -72,7 +72,7 @@ class GuiLipsum(QDialog):
|
||||
# Form
|
||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Insert Lorem Ipsum Text")))
|
||||
|
||||
self.paraLabel = QLabel(self.tr("Number of pragraphs"))
|
||||
self.paraLabel = QLabel(self.tr("Number of paragraphs"))
|
||||
self.paraCount = QSpinBox()
|
||||
self.paraCount.setMinimum(1)
|
||||
self.paraCount.setMaximum(100)
|
||||
|
||||
@@ -31,12 +31,10 @@ from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtWidgets import (
|
||||
QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit,
|
||||
QPushButton, QFileDialog, QHBoxLayout, QRadioButton, QFormLayout,
|
||||
QGroupBox, QGridLayout, QSpinBox
|
||||
QGridLayout, QSpinBox
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwItemClass
|
||||
from novelwriter.common import makeFileNameSafe
|
||||
from novelwriter.constants import trConst, nwLabels
|
||||
from novelwriter.gui.custom import QSwitch
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -98,10 +96,10 @@ class ProjWizardIntroPage(QWizardPage):
|
||||
|
||||
self.setTitle(self.tr("Create New Project"))
|
||||
self.theText = QLabel(self.tr(
|
||||
"Provide at least a working title. The working title should not "
|
||||
"be change beyond this point as it is used by the application for "
|
||||
"generating file names for for instance backups. The other fields "
|
||||
"are optional and can be changed at any time in Project Settings."
|
||||
"Provide at least a project name. The project name should not "
|
||||
"be changed beyond this point as it is used for generating file "
|
||||
"names for for instance backups. The other fields are optional "
|
||||
"and can be changed at any time in Project Settings."
|
||||
))
|
||||
self.theText.setWordWrap(True)
|
||||
|
||||
@@ -134,7 +132,7 @@ class ProjWizardIntroPage(QWizardPage):
|
||||
self.projAuthors.setPlaceholderText(self.tr("Optional. One name per line."))
|
||||
|
||||
self.mainForm = QFormLayout()
|
||||
self.mainForm.addRow(self.tr("Working Title"), self.projName)
|
||||
self.mainForm.addRow(self.tr("Project Name"), self.projName)
|
||||
self.mainForm.addRow(self.tr("Novel Title"), self.projTitle)
|
||||
self.mainForm.addRow(self.tr("Author(s)"), self.projAuthors)
|
||||
self.mainForm.setVerticalSpacing(fS)
|
||||
@@ -185,6 +183,9 @@ class ProjWizardFolderPage(QWizardPage):
|
||||
self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||
self.browseButton.clicked.connect(self._doBrowse)
|
||||
|
||||
self.errLabel = QLabel("")
|
||||
self.errLabel.setWordWrap(True)
|
||||
|
||||
self.mainForm = QHBoxLayout()
|
||||
self.mainForm.addWidget(QLabel(self.tr("Project Path")), 0)
|
||||
self.mainForm.addWidget(self.projPath, 1)
|
||||
@@ -198,11 +199,36 @@ class ProjWizardFolderPage(QWizardPage):
|
||||
self.outerBox.setSpacing(vS)
|
||||
self.outerBox.addWidget(self.theText)
|
||||
self.outerBox.addLayout(self.mainForm)
|
||||
self.outerBox.addWidget(self.errLabel)
|
||||
self.outerBox.addStretch(1)
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
def isComplete(self):
|
||||
"""Check that the selected path isn't already being used.
|
||||
"""
|
||||
self.errLabel.setText("")
|
||||
if not QWizardPage.isComplete(self):
|
||||
return False
|
||||
|
||||
setPath = os.path.abspath(os.path.expanduser(self.projPath.text()))
|
||||
parPath = os.path.dirname(setPath)
|
||||
logger.verbose("Path is: %s", setPath)
|
||||
if parPath and not os.path.isdir(parPath):
|
||||
self.errLabel.setText(self.tr(
|
||||
"Error: A project folder cannot be created using this path."
|
||||
))
|
||||
return False
|
||||
|
||||
if os.path.exists(setPath):
|
||||
self.errLabel.setText(self.tr(
|
||||
"Error: The selected path already exists."
|
||||
))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
@@ -296,68 +322,28 @@ class ProjWizardCustomPage(QWizardPage):
|
||||
|
||||
self.setTitle(self.tr("Custom Project Options"))
|
||||
self.theText = QLabel(self.tr(
|
||||
"Select which additional root folders to make, and how to populate "
|
||||
"the Novel folder. If you don't want to add chapters or scenes, set "
|
||||
"the values to 0. You can add scenes without chapters."
|
||||
"Select which additional elements to populate the project with. "
|
||||
"You can skip making chapters and add only scenes by setting the "
|
||||
"number of chapters to 0."
|
||||
))
|
||||
self.theText.setWordWrap(True)
|
||||
|
||||
vS = self.mainConf.pxInt(12)
|
||||
cM = self.mainConf.pxInt(12)
|
||||
mH = self.mainConf.pxInt(26)
|
||||
fS = self.mainConf.pxInt(4)
|
||||
|
||||
# Root Folders
|
||||
self.rootGroup = QGroupBox(self.tr("Additional Root Folders"))
|
||||
self.rootForm = QGridLayout()
|
||||
self.rootGroup.setLayout(self.rootForm)
|
||||
|
||||
self.lblPlot = QLabel(self.tr("{0} folder").format(
|
||||
trConst(nwLabels.CLASS_NAME[nwItemClass.PLOT]))
|
||||
)
|
||||
self.lblChar = QLabel(self.tr("{0} folder").format(
|
||||
trConst(nwLabels.CLASS_NAME[nwItemClass.CHARACTER]))
|
||||
)
|
||||
self.lblWorld = QLabel(self.tr("{0} folder").format(
|
||||
trConst(nwLabels.CLASS_NAME[nwItemClass.WORLD]))
|
||||
)
|
||||
self.lblTime = QLabel(self.tr("{0} folder").format(
|
||||
trConst(nwLabels.CLASS_NAME[nwItemClass.TIMELINE]))
|
||||
)
|
||||
self.lblObject = QLabel(self.tr("{0} folder").format(
|
||||
trConst(nwLabels.CLASS_NAME[nwItemClass.OBJECT]))
|
||||
)
|
||||
self.lblEntity = QLabel(self.tr("{0} folder").format(
|
||||
trConst(nwLabels.CLASS_NAME[nwItemClass.ENTITY]))
|
||||
)
|
||||
|
||||
self.addPlot = QSwitch()
|
||||
self.addChar = QSwitch()
|
||||
self.addWorld = QSwitch()
|
||||
self.addTime = QSwitch()
|
||||
self.addObject = QSwitch()
|
||||
self.addEntity = QSwitch()
|
||||
self.addPlot = QSwitch()
|
||||
self.addChar = QSwitch()
|
||||
self.addWorld = QSwitch()
|
||||
self.addNotes = QSwitch()
|
||||
|
||||
self.addPlot.setChecked(True)
|
||||
self.addChar.setChecked(True)
|
||||
self.addWorld.setChecked(True)
|
||||
|
||||
self.rootForm.addWidget(self.lblPlot, 0, 0)
|
||||
self.rootForm.addWidget(self.lblChar, 1, 0)
|
||||
self.rootForm.addWidget(self.lblWorld, 2, 0)
|
||||
self.rootForm.addWidget(self.lblTime, 3, 0)
|
||||
self.rootForm.addWidget(self.lblObject, 4, 0)
|
||||
self.rootForm.addWidget(self.lblEntity, 5, 0)
|
||||
self.rootForm.addWidget(self.addPlot, 0, 1, 1, 1, Qt.AlignRight)
|
||||
self.rootForm.addWidget(self.addChar, 1, 1, 1, 1, Qt.AlignRight)
|
||||
self.rootForm.addWidget(self.addWorld, 2, 1, 1, 1, Qt.AlignRight)
|
||||
self.rootForm.addWidget(self.addTime, 3, 1, 1, 1, Qt.AlignRight)
|
||||
self.rootForm.addWidget(self.addObject, 4, 1, 1, 1, Qt.AlignRight)
|
||||
self.rootForm.addWidget(self.addEntity, 5, 1, 1, 1, Qt.AlignRight)
|
||||
self.rootForm.setRowStretch(6, 1)
|
||||
|
||||
# Novel Options
|
||||
self.novelGroup = QGroupBox(self.tr("Populate Novel Folder"))
|
||||
self.novelForm = QGridLayout()
|
||||
self.novelGroup.setLayout(self.novelForm)
|
||||
self.addWorld.setChecked(False)
|
||||
self.addNotes.setChecked(False)
|
||||
|
||||
# Generate Content
|
||||
self.numChapters = QSpinBox()
|
||||
self.numChapters.setRange(0, 100)
|
||||
self.numChapters.setValue(5)
|
||||
@@ -366,37 +352,40 @@ class ProjWizardCustomPage(QWizardPage):
|
||||
self.numScenes.setRange(0, 200)
|
||||
self.numScenes.setValue(5)
|
||||
|
||||
self.chFolders = QSwitch()
|
||||
self.chFolders.setChecked(True)
|
||||
|
||||
self.novelForm.addWidget(QLabel(self.tr("Add chapters")), 0, 0)
|
||||
self.novelForm.addWidget(QLabel(self.tr("Scenes (per chapter)")), 1, 0)
|
||||
self.novelForm.addWidget(QLabel(self.tr("Add chapter folders")), 2, 0)
|
||||
self.novelForm.addWidget(self.numChapters, 0, 1, 1, 1, Qt.AlignRight)
|
||||
self.novelForm.addWidget(self.numScenes, 1, 1, 1, 1, Qt.AlignRight)
|
||||
self.novelForm.addWidget(self.chFolders, 2, 1, 1, 1, Qt.AlignRight)
|
||||
self.novelForm.setRowStretch(3, 1)
|
||||
# Grid Form
|
||||
self.addBox = QGridLayout()
|
||||
self.addBox.addWidget(QLabel(self.tr("Add a folder for plot notes")), 0, 0)
|
||||
self.addBox.addWidget(QLabel(self.tr("Add a folder for character notes")), 1, 0)
|
||||
self.addBox.addWidget(QLabel(self.tr("Add a folder for location notes")), 2, 0)
|
||||
self.addBox.addWidget(QLabel(self.tr("Add example notes to the above")), 3, 0)
|
||||
self.addBox.addWidget(QLabel(self.tr("Add chapters to the novel folder")), 4, 0)
|
||||
self.addBox.addWidget(QLabel(self.tr("Add scenes to each chapter")), 5, 0)
|
||||
self.addBox.addWidget(self.addPlot, 0, 1, 1, 1, Qt.AlignRight)
|
||||
self.addBox.addWidget(self.addChar, 1, 1, 1, 1, Qt.AlignRight)
|
||||
self.addBox.addWidget(self.addWorld, 2, 1, 1, 1, Qt.AlignRight)
|
||||
self.addBox.addWidget(self.addNotes, 3, 1, 1, 1, Qt.AlignRight)
|
||||
self.addBox.addWidget(self.numChapters, 4, 1, 1, 1, Qt.AlignRight)
|
||||
self.addBox.addWidget(self.numScenes, 5, 1, 1, 1, Qt.AlignRight)
|
||||
self.addBox.setVerticalSpacing(fS)
|
||||
self.addBox.setHorizontalSpacing(cM)
|
||||
self.addBox.setContentsMargins(cM, 0, cM, 0)
|
||||
self.addBox.setColumnStretch(2, 1)
|
||||
for i in range(6):
|
||||
self.addBox.setRowMinimumHeight(i, mH)
|
||||
|
||||
# Wizard Fields
|
||||
self.registerField("addPlot", self.addPlot)
|
||||
self.registerField("addChar", self.addChar)
|
||||
self.registerField("addWorld", self.addWorld)
|
||||
self.registerField("addTime", self.addTime)
|
||||
self.registerField("addObject", self.addObject)
|
||||
self.registerField("addEntity", self.addEntity)
|
||||
self.registerField("addNotes", self.addNotes)
|
||||
self.registerField("numChapters", self.numChapters)
|
||||
self.registerField("numScenes", self.numScenes)
|
||||
self.registerField("chFolders", self.chFolders)
|
||||
|
||||
# Assemble
|
||||
self.innerBox = QHBoxLayout()
|
||||
self.innerBox.addWidget(self.rootGroup)
|
||||
self.innerBox.addWidget(self.novelGroup)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.setSpacing(vS)
|
||||
self.outerBox.setSpacing(cM)
|
||||
self.outerBox.addWidget(self.theText)
|
||||
self.outerBox.addLayout(self.innerBox)
|
||||
self.outerBox.addLayout(self.addBox)
|
||||
self.outerBox.addStretch(1)
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
@@ -413,15 +402,8 @@ class ProjWizardFinalPage(QWizardPage):
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theWizard = theWizard
|
||||
|
||||
self.setTitle(self.tr("Finished"))
|
||||
self.theText = QLabel(
|
||||
"<p>%s</p><p>%s</p>" % (
|
||||
self.tr("All done."),
|
||||
self.tr("Press '{0}' to create the new project.").format(
|
||||
self.tr("Done") if self.mainConf.osDarwin else self.tr("Finish")
|
||||
)
|
||||
)
|
||||
)
|
||||
self.setTitle(self.tr("Summary"))
|
||||
self.theText = QLabel("")
|
||||
self.theText.setWordWrap(True)
|
||||
|
||||
# Assemble
|
||||
@@ -433,4 +415,51 @@ class ProjWizardFinalPage(QWizardPage):
|
||||
|
||||
return
|
||||
|
||||
def initializePage(self):
|
||||
"""Update the summary information on the final page.
|
||||
"""
|
||||
QWizardPage.initializePage(self)
|
||||
|
||||
sumList = []
|
||||
sumList.append(self.tr("Project Name: {0}").format(self.field("projName")))
|
||||
sumList.append(self.tr("Project Path: {0}").format(self.field("projPath")))
|
||||
|
||||
if self.field("popMinimal"):
|
||||
sumList.append(self.tr("Fill the project with a minimal set of items"))
|
||||
elif self.field("popSample"):
|
||||
sumList.append(self.tr("Fill the project with example files"))
|
||||
elif self.field("popCustom"):
|
||||
if self.field("addPlot"):
|
||||
sumList.append(self.tr("Add a folder for plot notes"))
|
||||
if self.field("addChar"):
|
||||
sumList.append(self.tr("Add a folder for character notes"))
|
||||
if self.field("addWorld"):
|
||||
sumList.append(self.tr("Add a folder for location notes"))
|
||||
if self.field("addNotes"):
|
||||
sumList.append(self.tr("Add example notes to the above"))
|
||||
if self.field("numChapters") > 0:
|
||||
sumList.append(self.tr("Add {0} chapters to the novel folder").format(
|
||||
self.field("numChapters")
|
||||
))
|
||||
if self.field("numScenes") > 0:
|
||||
sumList.append(self.tr("Add {0} scenes to each chapter").format(
|
||||
self.field("numScenes")
|
||||
))
|
||||
else:
|
||||
if self.field("numScenes") > 0:
|
||||
sumList.append(self.tr("Add {0} scenes").format(
|
||||
self.field("numScenes")
|
||||
))
|
||||
|
||||
self.theText.setText(
|
||||
"<p>%s</p><p> • %s</p><p>%s</p>" % (
|
||||
self.tr("You have selected the following:"),
|
||||
"<br> • ".join(sumList),
|
||||
self.tr("Press '{0}' to create the new project.").format(
|
||||
self.tr("Done") if self.mainConf.osDarwin else self.tr("Finish")
|
||||
)
|
||||
)
|
||||
)
|
||||
return
|
||||
|
||||
# END Class ProjWizardFinalPage
|
||||
|
||||
@@ -67,33 +67,34 @@ class GuiWritingStats(QDialog):
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
|
||||
self.logData = []
|
||||
self.filterData = []
|
||||
self.timeFilter = 0.0
|
||||
self.wordOffset = 0
|
||||
|
||||
pOptions = self.theProject.options
|
||||
|
||||
self.setWindowTitle(self.tr("Writing Statistics"))
|
||||
self.setMinimumWidth(self.mainConf.pxInt(420))
|
||||
self.setMinimumHeight(self.mainConf.pxInt(400))
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiWritingStats", "winWidth", 550)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiWritingStats", "winHeight", 500))
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiWritingStats", "winWidth", 550)),
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiWritingStats", "winHeight", 500))
|
||||
)
|
||||
|
||||
# List Box
|
||||
wCol0 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiWritingStats", "widthCol0", 180)
|
||||
pOptions.getInt("GuiWritingStats", "widthCol0", 180)
|
||||
)
|
||||
wCol1 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiWritingStats", "widthCol1", 80)
|
||||
pOptions.getInt("GuiWritingStats", "widthCol1", 80)
|
||||
)
|
||||
wCol2 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiWritingStats", "widthCol2", 80)
|
||||
pOptions.getInt("GuiWritingStats", "widthCol2", 80)
|
||||
)
|
||||
wCol3 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiWritingStats", "widthCol3", 80)
|
||||
pOptions.getInt("GuiWritingStats", "widthCol3", 80)
|
||||
)
|
||||
|
||||
self.listBox = QTreeWidget()
|
||||
@@ -115,9 +116,9 @@ class GuiWritingStats(QDialog):
|
||||
hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
|
||||
hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)
|
||||
|
||||
sortCol = checkIntRange(self.optState.getInt("GuiWritingStats", "sortCol", 0), 0, 2, 0)
|
||||
sortCol = checkIntRange(pOptions.getInt("GuiWritingStats", "sortCol", 0), 0, 2, 0)
|
||||
sortOrder = checkIntTuple(
|
||||
self.optState.getInt("GuiWritingStats", "sortOrder", Qt.DescendingOrder),
|
||||
pOptions.getInt("GuiWritingStats", "sortOrder", Qt.DescendingOrder),
|
||||
(Qt.AscendingOrder, Qt.DescendingOrder), Qt.DescendingOrder
|
||||
)
|
||||
self.listBox.sortByColumn(sortCol, sortOrder)
|
||||
@@ -190,37 +191,37 @@ class GuiWritingStats(QDialog):
|
||||
|
||||
self.incNovel = QSwitch(width=2*sPx, height=sPx)
|
||||
self.incNovel.setChecked(
|
||||
self.optState.getBool("GuiWritingStats", "incNovel", True)
|
||||
pOptions.getBool("GuiWritingStats", "incNovel", True)
|
||||
)
|
||||
self.incNovel.clicked.connect(self._updateListBox)
|
||||
|
||||
self.incNotes = QSwitch(width=2*sPx, height=sPx)
|
||||
self.incNotes.setChecked(
|
||||
self.optState.getBool("GuiWritingStats", "incNotes", True)
|
||||
pOptions.getBool("GuiWritingStats", "incNotes", True)
|
||||
)
|
||||
self.incNotes.clicked.connect(self._updateListBox)
|
||||
|
||||
self.hideZeros = QSwitch(width=2*sPx, height=sPx)
|
||||
self.hideZeros.setChecked(
|
||||
self.optState.getBool("GuiWritingStats", "hideZeros", True)
|
||||
pOptions.getBool("GuiWritingStats", "hideZeros", True)
|
||||
)
|
||||
self.hideZeros.clicked.connect(self._updateListBox)
|
||||
|
||||
self.hideNegative = QSwitch(width=2*sPx, height=sPx)
|
||||
self.hideNegative.setChecked(
|
||||
self.optState.getBool("GuiWritingStats", "hideNegative", False)
|
||||
pOptions.getBool("GuiWritingStats", "hideNegative", False)
|
||||
)
|
||||
self.hideNegative.clicked.connect(self._updateListBox)
|
||||
|
||||
self.groupByDay = QSwitch(width=2*sPx, height=sPx)
|
||||
self.groupByDay.setChecked(
|
||||
self.optState.getBool("GuiWritingStats", "groupByDay", False)
|
||||
pOptions.getBool("GuiWritingStats", "groupByDay", False)
|
||||
)
|
||||
self.groupByDay.clicked.connect(self._updateListBox)
|
||||
|
||||
self.showIdleTime = QSwitch(width=2*sPx, height=sPx)
|
||||
self.showIdleTime.setChecked(
|
||||
self.optState.getBool("GuiWritingStats", "showIdleTime", False)
|
||||
pOptions.getBool("GuiWritingStats", "showIdleTime", False)
|
||||
)
|
||||
self.showIdleTime.clicked.connect(self._updateListBox)
|
||||
|
||||
@@ -244,7 +245,7 @@ class GuiWritingStats(QDialog):
|
||||
self.histMax.setMaximum(100000)
|
||||
self.histMax.setSingleStep(100)
|
||||
self.histMax.setValue(
|
||||
self.optState.getInt("GuiWritingStats", "histMax", 2000)
|
||||
pOptions.getInt("GuiWritingStats", "histMax", 2000)
|
||||
)
|
||||
self.histMax.valueChanged.connect(self._updateListBox)
|
||||
|
||||
@@ -323,23 +324,23 @@ class GuiWritingStats(QDialog):
|
||||
showIdleTime = self.showIdleTime.isChecked()
|
||||
histMax = self.histMax.value()
|
||||
|
||||
self.optState.setValue("GuiWritingStats", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiWritingStats", "winHeight", winHeight)
|
||||
self.optState.setValue("GuiWritingStats", "widthCol0", widthCol0)
|
||||
self.optState.setValue("GuiWritingStats", "widthCol1", widthCol1)
|
||||
self.optState.setValue("GuiWritingStats", "widthCol2", widthCol2)
|
||||
self.optState.setValue("GuiWritingStats", "widthCol3", widthCol3)
|
||||
self.optState.setValue("GuiWritingStats", "sortCol", sortCol)
|
||||
self.optState.setValue("GuiWritingStats", "sortOrder", sortOrder)
|
||||
self.optState.setValue("GuiWritingStats", "incNovel", incNovel)
|
||||
self.optState.setValue("GuiWritingStats", "incNotes", incNotes)
|
||||
self.optState.setValue("GuiWritingStats", "hideZeros", hideZeros)
|
||||
self.optState.setValue("GuiWritingStats", "hideNegative", hideNegative)
|
||||
self.optState.setValue("GuiWritingStats", "groupByDay", groupByDay)
|
||||
self.optState.setValue("GuiWritingStats", "showIdleTime", showIdleTime)
|
||||
self.optState.setValue("GuiWritingStats", "histMax", histMax)
|
||||
|
||||
self.optState.saveSettings()
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiWritingStats", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiWritingStats", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiWritingStats", "widthCol0", widthCol0)
|
||||
pOptions.setValue("GuiWritingStats", "widthCol1", widthCol1)
|
||||
pOptions.setValue("GuiWritingStats", "widthCol2", widthCol2)
|
||||
pOptions.setValue("GuiWritingStats", "widthCol3", widthCol3)
|
||||
pOptions.setValue("GuiWritingStats", "sortCol", sortCol)
|
||||
pOptions.setValue("GuiWritingStats", "sortOrder", sortOrder)
|
||||
pOptions.setValue("GuiWritingStats", "incNovel", incNovel)
|
||||
pOptions.setValue("GuiWritingStats", "incNotes", incNotes)
|
||||
pOptions.setValue("GuiWritingStats", "hideZeros", hideZeros)
|
||||
pOptions.setValue("GuiWritingStats", "hideNegative", hideNegative)
|
||||
pOptions.setValue("GuiWritingStats", "groupByDay", groupByDay)
|
||||
pOptions.setValue("GuiWritingStats", "showIdleTime", showIdleTime)
|
||||
pOptions.setValue("GuiWritingStats", "histMax", histMax)
|
||||
pOptions.saveSettings()
|
||||
self.close()
|
||||
|
||||
return
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
# Mars
|
||||
|
||||
@tag: Mars
|
||||
@location: Space
|
||||
|
||||
It’s red. Dusty and red.
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
%%~name: Making a Scene
|
||||
%%~path: e7ded148d6e4a/636b6aa9b697b
|
||||
%%~path: 6a2d6d5f4f401/636b6aa9b697b
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
### Making a Scene
|
||||
|
||||
@pov: Jane
|
||||
@char: John
|
||||
@char: John, Jane
|
||||
@location: Earth
|
||||
|
||||
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree.
|
||||
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree. The scene document can be sorted after the chapter document, or as a child of the chapter. Both result in the same output in the end, so it is a matter of preference.
|
||||
|
||||
Each paragraph in the scene is separated by a blank line. The text supports minimal formatting, like **bold**, _italic_ and **_bold italic_**. You can also ~~strike through~~ text. There is **some support for _nested_ emphasis**, but there are some known limitations. If the syntax highlighter doesn’t show it correctly, the export tool will not either.
|
||||
|
||||
In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.”
|
||||
In addition, the editor supports automatic formatting of “quotes”, both double and ‘single’. Depending on the syntax highlighter settings and colour theme, these can be in different colours. “You can of course use **bold** and _italic_ text inside of quotes too.”
|
||||
|
||||
If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, let’s auto-replace this A with <A>, and this C with <C>. While <E> is just <E>. Press Ctrl+R to see what this looks like in the view pane.
|
||||
If you have the need for it, you can also add text that can be automatically replaced by other text when you generate a preview or export the project. Now, let’s auto-replace this A with <A>, and this C with <C>. While <E> is just <E>. Press Ctrl+R to see what this looks like in the view pane. The list of auto-replaced text is sett in Project Settings.
|
||||
|
||||
The editor also supports non breaking spaces, and the spell checker accepts long dashes—like this—as valid word separators. Regular dashes are also supported – and can be automatically inserted when typing two hyphens.
|
||||
|
||||
@@ -25,7 +25,7 @@ If you need to split a scene file up into further pieces, you can do so with the
|
||||
|
||||
Both scene and section titles can be left out of the final exported document. The formatting of titles can be selected from the Build Novel Project dialog. You can also have them replaced with scene separators like “* * *”.
|
||||
|
||||
#### Text Alignment
|
||||
#### Text Alignment and Indentation
|
||||
|
||||
The text by default will have the left or justified alignment in the main text files in your project. You can also specify alignment for a specific paragraph by “pushing” it away from an edge with a set of ‘>>’ or ‘<<’ symbols, like so:
|
||||
|
||||
@@ -35,8 +35,6 @@ This text is left-aligned. <<
|
||||
|
||||
>> This text is centred. <<
|
||||
|
||||
#### Text Indent
|
||||
|
||||
You can indent a paragraph from both the left and right margin with ‘>’ and ‘<’ symbols.
|
||||
|
||||
> This paragraph is indented from both the left margin and the right margin. This is useful for when you want to quote a large chunk of text for instance. <
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
%%~name: Chapter One
|
||||
%%~path: e7ded148d6e4a/6a2d6d5f4f401
|
||||
%%~path: 7031beac91f75/6a2d6d5f4f401
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
## So it Begins
|
||||
|
||||
@pov: Jane
|
||||
@location: Earth
|
||||
|
||||
% Synopsis: We can add a chapter file, but keep the scene files separate. In the chapter file we can set the meta data that applies to the whole chapter if we wish to.
|
||||
% Synopsis: We can add a chapter document, but keep the scene files separate. In the chapter document we can set the meta data that applies to the whole chapter if we wish to. You can add the scenes as child documents directly under the chapter.
|
||||
|
||||
A chapter can also contain leading text before the first scene.
|
||||
A chapter can contain leading text before the first scene, like this piece of text.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%~name: Chapter Two
|
||||
%%~path: e7ded148d6e4a/88706ddc78b1b
|
||||
%%~path: 7031beac91f75/88706ddc78b1b
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
## Where has John Gone?
|
||||
|
||||
@@ -11,6 +11,7 @@
|
||||
### Jane Cannot Find John
|
||||
|
||||
@pov: Jane
|
||||
@focus: John
|
||||
@location: Space
|
||||
|
||||
Jane has been looking all over for John. He’s nowhere to be found on Earth, so Jane goes to space.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%~name: Old File
|
||||
%%~path: ae9bf3c3ea159/8a5deb88c0e97
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~kind: ARCHIVE/DOCUMENT
|
||||
### Discarded Scene
|
||||
|
||||
If you have files you no longer want in your main project, you can move them to the “Archive” folder. This is equivalent to turning off the “Include when building project” switch, just that you also put the file away, although the switch can be ignored when building the project, this folder cannot.
|
||||
If you have files you no longer want in your main project, you can move them to the “Archive” folder. This is equivalent to turning off the “Include when building project” switch, just that you also put the file away.
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
%%~name: A Note on Structure
|
||||
%%~path: e7ded148d6e4a/96b68994dfa3d
|
||||
%%~path: 7031beac91f75/96b68994dfa3d
|
||||
%%~kind: NOVEL/NOTE
|
||||
# A Note on Structure
|
||||
|
||||
This file is just a note. You can save notes anywhere you like in the project tree. Notes can be filtered out when you export the project.
|
||||
|
||||
In root folders that isn’t the Novel root folder, you can _only_ add notes. In the Novel root folder, you can choose between a number of layouts. Some of them are just to let yourself know what each file is for.
|
||||
In root folders that aren’t the Novel or Archive root folders, you can _only_ add notes. In the Novel and Archive folder you can also add Project Documents, which are the documents that make up your actual story.
|
||||
|
||||
## Headers in Notes
|
||||
|
||||
@@ -15,8 +15,10 @@ Unlike in novel files, headers in notes have no particular meaning other than vi
|
||||
|
||||
The folders in the tree view have no structural meaning other than they’re a way for you to organise your files into groups in whatever way suits you. They are not intended to represent chapters, but you can of course use them for that. If you do, know that you still need to define chapter headers in your structure so novelWriter knows where you want them.
|
||||
|
||||
If you do have separate chapter documents, you can always add scene documents as child document of the chapter instead of using folders.
|
||||
|
||||
## Linking Files and Notes
|
||||
|
||||
You can link files and notes together by assigning tags to them, and then reference them from other files. The file class of a file determines which reference keywords apply to each file. For instance a file in the Characters root folder can be referenced using either the @char keyword or the @pov keyword.
|
||||
You can link project documents and notes together by assigning tags to the notes with the @tag keyword, and then reference them from other files using one of the many reference keywords. The file class of a file determines which reference keywords apply to each file. For instance a file in the Characters root folder can be referenced using either the @char keyword or the @pov keyword.
|
||||
|
||||
If you want to see the content of the file the reference points to, you can click Ctrl+Enter with the cursor on top of the reference, and the view pane will show you the file. In the view pane, all references are clickable, so you can navigate further. At the bottom of the view pane, a list of files referencing the one your viewing will appear. This panel updates when you navigate, unless you make it sticky by clicking the sticky checkbox.
|
||||
@@ -6,4 +6,4 @@
|
||||
|
||||
This is a plain page with some text on it.
|
||||
|
||||
If you want the text to start on a fresh page, add the [NEW PAGE] code above the text. You can also add empty paragraphs with the [VSPACE] code.
|
||||
If you want the text to start on a fresh page, add the [NEW PAGE] code above the text. You can also add empty paragraphs with the [VSPACE] code. The above code adds two empty paragraphs before the text starts.
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
%%~name: Chapter One
|
||||
%%~path: e5e47ebf63b1c/a520879ca0b45
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
### Chapter One
|
||||
|
||||
@pov: Jane
|
||||
|
||||
% Synopsis: Remember Jane and John?
|
||||
|
||||
### Scene One
|
||||
|
||||
@pov: Jane
|
||||
@focus: John
|
||||
|
||||
A project can have multiple novel root folders for multiple novels. This is the first scene of a sequel to the first novel.
|
||||
|
||||
In this way, the writer can keep the same notes for multiple novels. This can be especially useful if the writer is planning a multi-novel story in advance.
|
||||
@@ -1,9 +1,10 @@
|
||||
%%~name: We Found John!
|
||||
%%~path: e7ded148d6e4a/ae7339df26ded
|
||||
%%~path: 88706ddc78b1b/ae7339df26ded
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
### We Found John!
|
||||
|
||||
@pov: John
|
||||
@focus: John
|
||||
@location: Mars
|
||||
|
||||
Jane has been searching for a while, and she finally found John on Mars. He was indeed in space! What was he doing on Mars anyway? Well, it turns out, he was farming potatoes.
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
# Earth
|
||||
|
||||
@tag: Earth
|
||||
@location: Space
|
||||
|
||||
Third planet from the sun, fairly dense, and with lots of people on it.
|
||||
Third planet from the sun, fairly dense, and with lots of people on it.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
%%~name: Delete Me!
|
||||
%%~path: 98acd8c76c93a/b8136a5a774a0
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~kind: TRASH/DOCUMENT
|
||||
### Delete Me!
|
||||
|
||||
This scene is trash.
|
||||
@@ -1,9 +1,9 @@
|
||||
%%~name: Interlude
|
||||
%%~path: e7ded148d6e4a/ba8a28a246524
|
||||
%%~path: 7031beac91f75/ba8a28a246524
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
##! Interlude
|
||||
|
||||
% Notice that this is a file with the flag ‘N.Un’. The ‘N’ means it’s a novel file, and the ‘Un’ means it’s an unnumbered chapter. Unnumbered chapters can be treated separately from numbered chapters during export. Perfect for when you want to add an interlude, or for a prologue or epilogue.
|
||||
% Notice that this document has a title with a ‘!’ in it in addition to the two hash symbols. This means it is an unnumbered chapter. Unnumbered chapters can be treated separately from numbered chapters during export. Perfect for when you want to add an interlude, or for a prologue or epilogue.
|
||||
|
||||
I am the very model of a modern Major-General
|
||||
I've information vegetable, animal, and mineral
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
%%~name: Title Page
|
||||
%%~path: e5e47ebf63b1c/bacb7059e3083
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
#! Sequel Novel
|
||||
|
||||
>> **By Jane Doh** <<
|
||||
|
||||
% Synopsis: Jane and John are back in a sequel to My Novel!
|
||||
@@ -1,5 +1,5 @@
|
||||
%%~name: Another Scene
|
||||
%%~path: e7ded148d6e4a/bc0cbd2a407f3
|
||||
%%~path: 6a2d6d5f4f401/bc0cbd2a407f3
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
### Another Scene
|
||||
|
||||
@@ -7,9 +7,9 @@
|
||||
@focus: Jane
|
||||
@location: Earth
|
||||
|
||||
Adding more scenes to a chapter is as easy as adding more scene files, with a level three heading, or just adding another level three heading in the same file if that works for the way you want to structure your files.
|
||||
Adding more scenes to a chapter is as easy as adding more scene files with a level three heading. You can of course also just add another level three heading in the same file if that works for the way you want to structure your files.
|
||||
|
||||
In fact, if you wish, you can add all the scenes in the chapter file too. All novelWriter cares about is the level of the headings.
|
||||
In fact, if you wish, you can add all the scenes in the chapter file too. All novelWriter cares about is the level of the headings and the order in which they appear.
|
||||
|
||||
### More Scenes
|
||||
|
||||
@@ -17,4 +17,4 @@ In fact, if you wish, you can add all the scenes in the chapter file too. All no
|
||||
@focus: John
|
||||
@location: Earth
|
||||
|
||||
This is a second scene in the same file as the previous scene. You can always split the files up later.
|
||||
This is a second scene in the same file as the previous scene. You can always split the files up later using the split tool.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 22:32:30">
|
||||
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-05 15:03:00">
|
||||
<project>
|
||||
<name>Sample Project</name>
|
||||
<title>Sample Project</title>
|
||||
<author>Jane Smith</author>
|
||||
<author>Jay Doh</author>
|
||||
<saveCount>1303</saveCount>
|
||||
<autoCount>199</autoCount>
|
||||
<editTime>65005</editTime>
|
||||
<saveCount>1334</saveCount>
|
||||
<autoCount>225</autoCount>
|
||||
<editTime>67746</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>False</doBackup>
|
||||
@@ -15,11 +15,11 @@
|
||||
<spellCheck>True</spellCheck>
|
||||
<spellLang>None</spellLang>
|
||||
<autoOutline>True</autoOutline>
|
||||
<lastEdited>636b6aa9b697b</lastEdited>
|
||||
<lastEdited>a520879ca0b45</lastEdited>
|
||||
<lastViewed>636b6aa9b697b</lastViewed>
|
||||
<lastWordCount>1206</lastWordCount>
|
||||
<novelWordCount>830</novelWordCount>
|
||||
<notesWordCount>376</notesWordCount>
|
||||
<lastWordCount>1363</lastWordCount>
|
||||
<novelWordCount>954</novelWordCount>
|
||||
<notesWordCount>409</notesWordCount>
|
||||
<autoReplace>
|
||||
<entry key="A">B</entry>
|
||||
<entry key="B">E</entry>
|
||||
@@ -33,121 +33,129 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Notes</entry>
|
||||
<entry red="182" green="60" blue="0">Started</entry>
|
||||
<entry red="193" green="129" blue="0">1st Draft</entry>
|
||||
<entry red="193" green="129" blue="0">2nd Draft</entry>
|
||||
<entry red="193" green="129" blue="0">3rd Draft</entry>
|
||||
<entry red="58" green="180" blue="58">Finished</entry>
|
||||
<entry key="sf12341" count="7" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="sf24ce6" count="1" red="200" green="50" blue="0">Notes</entry>
|
||||
<entry key="sc24b8f" count="2" red="182" green="60" blue="0">Started</entry>
|
||||
<entry key="s90e6c9" count="6" red="193" green="129" blue="0">1st Draft</entry>
|
||||
<entry key="sd51c5b" count="1" red="193" green="129" blue="0">2nd Draft</entry>
|
||||
<entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry>
|
||||
<entry key="s78ea90" count="0" red="58" green="180" blue="58">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry red="100" green="100" blue="100">None</entry>
|
||||
<entry red="0" green="122" blue="188">Minor</entry>
|
||||
<entry red="21" green="0" blue="180">Major</entry>
|
||||
<entry red="117" green="0" blue="175">Main</entry>
|
||||
<entry key="ia857f0" count="5" red="100" green="100" blue="100">None</entry>
|
||||
<entry key="icfb3a5" count="2" red="0" green="122" blue="188">Minor</entry>
|
||||
<entry key="i2d7a54" count="2" red="21" green="0" blue="180">Major</entry>
|
||||
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="25">
|
||||
<item handle="7031beac91f75" parent="None" order="0" type="ROOT" class="NOVEL">
|
||||
<content count="27">
|
||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="True"/>
|
||||
<name status="Started" import="None">Novel</name>
|
||||
<name status="sc24b8f" import="ia857f0">Novel</name>
|
||||
</item>
|
||||
<item handle="53b69b83cdafc" parent="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
|
||||
<name status="Started" import="None" exported="True">Title Page</name>
|
||||
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
|
||||
<name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="974e400180a99" parent="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="186" wordCount="39" paraCount="2" cursorPos="212"/>
|
||||
<name status="New" import="None" exported="True">Page</name>
|
||||
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
|
||||
<name status="sf12341" import="ia857f0" exported="True">Page</name>
|
||||
</item>
|
||||
<item handle="edca4be2fcaf8" parent="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="26" wordCount="6" paraCount="1" cursorPos="33"/>
|
||||
<name status="New" import="None" exported="True">Part One</name>
|
||||
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
|
||||
<name status="sf12341" import="ia857f0" exported="True">Part One</name>
|
||||
</item>
|
||||
<item handle="e7ded148d6e4a" parent="7031beac91f75" order="3" type="FOLDER" class="NOVEL">
|
||||
<item handle="6a2d6d5f4f401" parent="7031beac91f75" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="True" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
|
||||
<name status="sf24ce6" import="ia857f0" exported="True">Chapter One</name>
|
||||
</item>
|
||||
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="2687" wordCount="479" paraCount="14" cursorPos="61"/>
|
||||
<name status="s90e6c9" import="ia857f0" exported="True">Making a Scene</name>
|
||||
</item>
|
||||
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="548" wordCount="108" paraCount="3" cursorPos="649"/>
|
||||
<name status="s90e6c9" import="ia857f0" exported="True">Another Scene</name>
|
||||
</item>
|
||||
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
|
||||
<name status="sf12341" import="ia857f0" exported="True">Interlude</name>
|
||||
</item>
|
||||
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
|
||||
<meta expanded="False" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
|
||||
<name status="sd51c5b" import="ia857f0" exported="False">A Note on Structure</name>
|
||||
</item>
|
||||
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="True" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
|
||||
<name status="s90e6c9" import="ia857f0" exported="True">Chapter Two</name>
|
||||
</item>
|
||||
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
|
||||
<name status="s90e6c9" import="ia857f0" exported="True">We Found John!</name>
|
||||
</item>
|
||||
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
|
||||
<meta expanded="True"/>
|
||||
<name status="1st Draft" import="None">A Folder</name>
|
||||
<name status="sf12341" import="ia857f0">Sequel</name>
|
||||
</item>
|
||||
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
|
||||
<name status="Notes" import="None" exported="True">Chapter One</name>
|
||||
<item handle="bacb7059e3083" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
|
||||
<name status="sf12341" import="ia857f0" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="2429" wordCount="432" paraCount="14" cursorPos="219"/>
|
||||
<name status="1st Draft" import="None" exported="True">Making a Scene</name>
|
||||
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
|
||||
<name status="s90e6c9" import="ia857f0" exported="True">Chapter One</name>
|
||||
</item>
|
||||
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="476" wordCount="93" paraCount="3" cursorPos="577"/>
|
||||
<name status="1st Draft" import="None" exported="True">Another Scene</name>
|
||||
</item>
|
||||
<item handle="ba8a28a246524" parent="e7ded148d6e4a" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="617" wordCount="101" paraCount="3" cursorPos="4"/>
|
||||
<name status="New" import="None" exported="True">Interlude</name>
|
||||
</item>
|
||||
<item handle="96b68994dfa3d" parent="e7ded148d6e4a" order="4" type="FILE" class="NOVEL" layout="NOTE">
|
||||
<meta charCount="1692" wordCount="313" paraCount="6" cursorPos="1110"/>
|
||||
<name status="2nd Draft" import="None" exported="False">A Note on Structure</name>
|
||||
</item>
|
||||
<item handle="88706ddc78b1b" parent="e7ded148d6e4a" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
|
||||
<name status="1st Draft" import="None" exported="True">Chapter Two</name>
|
||||
</item>
|
||||
<item handle="ae7339df26ded" parent="e7ded148d6e4a" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
|
||||
<name status="1st Draft" import="None" exported="True">We Found John!</name>
|
||||
</item>
|
||||
<item handle="f6622b4617424" parent="None" order="1" type="ROOT" class="CHARACTER">
|
||||
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="None">Characters</name>
|
||||
<name status="sf12341" import="ia857f0">Characters</name>
|
||||
</item>
|
||||
<item handle="f7e2d9f330615" parent="f6622b4617424" order="0" type="FOLDER" class="CHARACTER">
|
||||
<item handle="f7e2d9f330615" parent="f6622b4617424" root="f6622b4617424" order="0" type="FOLDER" class="CHARACTER">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="None">Main Characters</name>
|
||||
<name status="sf12341" import="ia857f0">Main Characters</name>
|
||||
</item>
|
||||
<item handle="14298de4d9524" parent="f7e2d9f330615" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
|
||||
<name status="New" import="Minor" exported="True">John Smith</name>
|
||||
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta expanded="False" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
|
||||
<name status="sf12341" import="icfb3a5" exported="True">John Smith</name>
|
||||
</item>
|
||||
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
|
||||
<name status="New" import="Major" exported="True">Jane Smith</name>
|
||||
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta expanded="False" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
|
||||
<name status="sf12341" import="i2d7a54" exported="True">Jane Smith</name>
|
||||
</item>
|
||||
<item handle="15c4492bd5107" parent="None" order="2" type="ROOT" class="WORLD">
|
||||
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="None">Locations</name>
|
||||
<name status="sf12341" import="ia857f0">Locations</name>
|
||||
</item>
|
||||
<item handle="b3e74dbc1f584" parent="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
|
||||
<name status="New" import="Main" exported="True">Earth</name>
|
||||
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="False" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
|
||||
<name status="sf12341" import="i56be10" exported="True">Earth</name>
|
||||
</item>
|
||||
<item handle="f1471bef9f2ae" parent="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
|
||||
<name status="New" import="Minor" exported="True">Space</name>
|
||||
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="False" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
|
||||
<name status="sf12341" import="icfb3a5" exported="True">Space</name>
|
||||
</item>
|
||||
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
|
||||
<name status="New" import="Major" exported="True">Mars</name>
|
||||
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="False" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
|
||||
<name status="sf12341" import="i2d7a54" exported="True">Mars</name>
|
||||
</item>
|
||||
<item handle="6827118336ac1" parent="None" order="3" type="ROOT" class="ARCHIVE">
|
||||
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="None">Archive</name>
|
||||
<name status="sf12341" import="ia857f0">Archive</name>
|
||||
</item>
|
||||
<item handle="ae9bf3c3ea159" parent="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE">
|
||||
<item handle="ae9bf3c3ea159" parent="6827118336ac1" root="6827118336ac1" order="0" type="FOLDER" class="ARCHIVE">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="None">Scenes</name>
|
||||
<name status="sf12341" import="ia857f0">Scenes</name>
|
||||
</item>
|
||||
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="315" wordCount="55" paraCount="1" cursorPos="322"/>
|
||||
<name status="1st Draft" import="None" exported="True">Old File</name>
|
||||
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="6827118336ac1" order="0" type="FILE" class="ARCHIVE" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
|
||||
<name status="s90e6c9" import="ia857f0" exported="True">Old File</name>
|
||||
</item>
|
||||
<item handle="98acd8c76c93a" parent="None" order="4" type="TRASH" class="TRASH">
|
||||
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="None">Trash</name>
|
||||
<name status="sf12341" import="ia857f0">Trash</name>
|
||||
</item>
|
||||
<item handle="b8136a5a774a0" parent="98acd8c76c93a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
|
||||
<name status="New" import="None" exported="True">Delete Me!</name>
|
||||
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="98acd8c76c93a" order="0" type="FILE" class="TRASH" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
|
||||
<name status="sf12341" import="ia857f0" exported="True">Delete Me!</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -4,5 +4,6 @@
|
||||
<comment>novelWriter Project</comment>
|
||||
<sub-class-of type="application/xml"/>
|
||||
<glob pattern="*.nwx"/>
|
||||
<icon name="application-x-novelwriter-project"/>
|
||||
</mime-type>
|
||||
</mime-info>
|
||||
|
||||
@@ -180,6 +180,26 @@ def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
|
||||
return
|
||||
|
||||
|
||||
##
|
||||
# Python Objects
|
||||
##
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mockRnd(monkeypatch):
|
||||
"""Create a mock random number generator that just counts upwards
|
||||
from 0. This one will generate status/importance flags and handles
|
||||
in a predictable sequence.
|
||||
"""
|
||||
def rnd(n):
|
||||
for x in range(n):
|
||||
yield x
|
||||
|
||||
gen = rnd(1000)
|
||||
monkeypatch.setattr("random.getrandbits", lambda *a: next(gen))
|
||||
|
||||
return
|
||||
|
||||
|
||||
##
|
||||
# Temp Project Folders
|
||||
##
|
||||
@@ -248,10 +268,6 @@ def nwOldProj(tmpDir):
|
||||
return
|
||||
|
||||
|
||||
##
|
||||
# Useful Fixtures
|
||||
##
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def ipsumText():
|
||||
"""Return five paragraphs of Lorem Ipsum text.
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:36:12">
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-23 15:34:13">
|
||||
<project>
|
||||
<name>Lorem Ipsum</name>
|
||||
<title>Lorem Ipsum</title>
|
||||
<author>lipsum.com</author>
|
||||
<saveCount>23</saveCount>
|
||||
<saveCount>26</saveCount>
|
||||
<autoCount>24</autoCount>
|
||||
<editTime>1854</editTime>
|
||||
<editTime>1863</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>False</doBackup>
|
||||
@@ -31,102 +31,102 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Note</entry>
|
||||
<entry red="200" green="150" blue="0">Draft</entry>
|
||||
<entry red="50" green="200" blue="0">Finished</entry>
|
||||
<entry key="sbaa94f" count="3" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="s27bf7c" count="0" red="200" green="50" blue="0">Note</entry>
|
||||
<entry key="s92a87b" count="5" red="200" green="150" blue="0">Draft</entry>
|
||||
<entry key="sedd043" count="7" red="50" green="200" blue="0">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Minor</entry>
|
||||
<entry red="200" green="150" blue="0">Major</entry>
|
||||
<entry red="50" green="200" blue="0">Main</entry>
|
||||
<entry key="i613591" count="6" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="i560cbf" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||
<entry key="i37861c" count="0" red="200" green="150" blue="0">Major</entry>
|
||||
<entry key="id6b1d0" count="0" red="50" green="200" blue="0">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="21">
|
||||
<item handle="b3643d0f92e32" parent="None" order="0" type="ROOT" class="NOVEL">
|
||||
<item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="New">Novel</name>
|
||||
<name status="sbaa94f" import="i613591">Novel</name>
|
||||
</item>
|
||||
<item handle="7a992350f3eb6" parent="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
|
||||
<name status="Finished" import="New" exported="True">Lorem Ipsum</name>
|
||||
<item handle="7a992350f3eb6" parent="b3643d0f92e32" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
|
||||
<name status="sedd043" import="i613591" exported="True">Lorem Ipsum</name>
|
||||
</item>
|
||||
<item handle="8c58a65414c23" parent="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
|
||||
<name status="Finished" import="New" exported="True">Front Matter</name>
|
||||
<item handle="8c58a65414c23" parent="b3643d0f92e32" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
|
||||
<name status="sedd043" import="i613591" exported="True">Front Matter</name>
|
||||
</item>
|
||||
<item handle="88d59a277361b" parent="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
|
||||
<name status="Draft" import="New" exported="True">Prologue</name>
|
||||
<item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
|
||||
<name status="s92a87b" import="i613591" exported="True">Prologue</name>
|
||||
</item>
|
||||
<item handle="db7e733775d4d" parent="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
|
||||
<name status="New" import="New" exported="True">Act One</name>
|
||||
<item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
|
||||
<name status="sbaa94f" import="i613591" exported="True">Act One</name>
|
||||
</item>
|
||||
<item handle="45e6b01ca35c1" parent="b3643d0f92e32" order="4" type="FOLDER" class="NOVEL">
|
||||
<item handle="45e6b01ca35c1" parent="b3643d0f92e32" root="b3643d0f92e32" order="4" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="True"/>
|
||||
<name status="Draft" import="New">Chapter One</name>
|
||||
<name status="s92a87b" import="i613591">Chapter One</name>
|
||||
</item>
|
||||
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
|
||||
<name status="Draft" import="New" exported="True">Chapter One</name>
|
||||
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
|
||||
<name status="s92a87b" import="i613591" exported="True">Chapter One</name>
|
||||
</item>
|
||||
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
|
||||
<name status="Finished" import="New" exported="True">Scene One</name>
|
||||
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
|
||||
<name status="sedd043" import="i613591" exported="True">Scene One</name>
|
||||
</item>
|
||||
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
|
||||
<name status="Finished" import="New" exported="True">Scene Two</name>
|
||||
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
|
||||
<name status="sedd043" import="i613591" exported="True">Scene Two</name>
|
||||
</item>
|
||||
<item handle="846352075de7d" parent="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
|
||||
<name status="New" import="New" exported="False">Interlude</name>
|
||||
<item handle="846352075de7d" parent="b3643d0f92e32" root="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
|
||||
<name status="sbaa94f" import="i613591" exported="False">Interlude</name>
|
||||
</item>
|
||||
<item handle="6bd935d2490cd" parent="b3643d0f92e32" order="6" type="FOLDER" class="NOVEL">
|
||||
<item handle="6bd935d2490cd" parent="b3643d0f92e32" root="b3643d0f92e32" order="6" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="True"/>
|
||||
<name status="Draft" import="New">Chapter Two</name>
|
||||
<name status="s92a87b" import="i613591">Chapter Two</name>
|
||||
</item>
|
||||
<item handle="441420a886d82" parent="6bd935d2490cd" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
|
||||
<name status="Draft" import="New" exported="True">Chapter Two</name>
|
||||
<item handle="441420a886d82" parent="6bd935d2490cd" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
|
||||
<name status="s92a87b" import="i613591" exported="True">Chapter Two</name>
|
||||
</item>
|
||||
<item handle="eb103bc70c90c" parent="6bd935d2490cd" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
|
||||
<name status="Finished" import="New" exported="True">Scene Three</name>
|
||||
<item handle="eb103bc70c90c" parent="6bd935d2490cd" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
|
||||
<name status="sedd043" import="i613591" exported="True">Scene Three</name>
|
||||
</item>
|
||||
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
|
||||
<name status="Finished" import="New" exported="True">Scene Four</name>
|
||||
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
|
||||
<name status="sedd043" import="i613591" exported="True">Scene Four</name>
|
||||
</item>
|
||||
<item handle="47666c91c7ccf" parent="6bd935d2490cd" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
|
||||
<name status="Finished" import="New" exported="True">Scene Five</name>
|
||||
<item handle="47666c91c7ccf" parent="6bd935d2490cd" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
|
||||
<name status="sedd043" import="i613591" exported="True">Scene Five</name>
|
||||
</item>
|
||||
<item handle="67a8707f2f249" parent="None" order="1" type="ROOT" class="CHARACTER">
|
||||
<item handle="67a8707f2f249" parent="None" root="67a8707f2f249" order="1" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="New">Characters</name>
|
||||
<name status="sbaa94f" import="i613591">Characters</name>
|
||||
</item>
|
||||
<item handle="4c4f28287af27" parent="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
|
||||
<name status="New" import="New" exported="True">Mr. Nobody</name>
|
||||
<item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta expanded="False" charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
|
||||
<name status="sbaa94f" import="i613591" exported="True">Mr. Nobody</name>
|
||||
</item>
|
||||
<item handle="6c6afb1247750" parent="None" order="2" type="ROOT" class="PLOT">
|
||||
<item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="New">Plot</name>
|
||||
<name status="sbaa94f" import="i613591">Plot</name>
|
||||
</item>
|
||||
<item handle="2426c6f0ca922" parent="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE">
|
||||
<meta charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
|
||||
<name status="New" import="New" exported="True">Main</name>
|
||||
<item handle="2426c6f0ca922" parent="6c6afb1247750" root="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE">
|
||||
<meta expanded="False" charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
|
||||
<name status="sbaa94f" import="i613591" exported="True">Main</name>
|
||||
</item>
|
||||
<item handle="60bdf227455cc" parent="None" order="3" type="ROOT" class="WORLD">
|
||||
<item handle="60bdf227455cc" parent="None" root="60bdf227455cc" order="3" type="ROOT" class="WORLD">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="New">World</name>
|
||||
<name status="sbaa94f" import="i613591">World</name>
|
||||
</item>
|
||||
<item handle="04468803b92e1" parent="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
|
||||
<name status="New" import="New" exported="True">Ancient Europe</name>
|
||||
<item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="False" charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
|
||||
<name status="sbaa94f" import="i613591" exported="True">Ancient Europe</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:36:18">
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-23 15:34:21">
|
||||
<project>
|
||||
<name>Test Minimal</name>
|
||||
<title>Minimal</title>
|
||||
<author>Jane Doe</author>
|
||||
<author>John Doh</author>
|
||||
<saveCount>14</saveCount>
|
||||
<saveCount>17</saveCount>
|
||||
<autoCount>2</autoCount>
|
||||
<editTime>135</editTime>
|
||||
<editTime>150</editTime>
|
||||
</project>
|
||||
<settings>
|
||||
<doBackup>True</doBackup>
|
||||
@@ -29,50 +29,50 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Note</entry>
|
||||
<entry red="200" green="150" blue="0">Draft</entry>
|
||||
<entry red="50" green="200" blue="0">Finished</entry>
|
||||
<entry key="s72322d" count="5" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="sb6adc7" count="0" red="200" green="50" blue="0">Note</entry>
|
||||
<entry key="s2ae76d" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||
<entry key="s541953" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Minor</entry>
|
||||
<entry red="200" green="150" blue="0">Major</entry>
|
||||
<entry red="50" green="200" blue="0">Main</entry>
|
||||
<entry key="iffcacb" count="3" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="i6b130b" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||
<entry key="iece803" count="0" red="200" green="150" blue="0">Major</entry>
|
||||
<entry key="i5ba06e" count="0" red="50" green="200" blue="0">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="8">
|
||||
<item handle="a508bb932959c" parent="None" order="0" type="ROOT" class="NOVEL">
|
||||
<item handle="a508bb932959c" parent="None" root="a508bb932959c" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="New">Novel</name>
|
||||
<name status="s72322d" import="iffcacb">Novel</name>
|
||||
</item>
|
||||
<item handle="a35baf2e93843" parent="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="33"/>
|
||||
<name status="New" import="New" exported="True">Title Page</name>
|
||||
<item handle="a35baf2e93843" parent="a508bb932959c" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="28" wordCount="6" paraCount="1" cursorPos="33"/>
|
||||
<name status="s72322d" import="iffcacb" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="a6d311a93600a" parent="a508bb932959c" order="1" type="FOLDER" class="NOVEL">
|
||||
<item handle="a6d311a93600a" parent="a508bb932959c" root="a508bb932959c" order="1" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="True"/>
|
||||
<name status="New" import="New">New Chapter</name>
|
||||
<name status="s72322d" import="iffcacb">New Chapter</name>
|
||||
</item>
|
||||
<item handle="f5ab3e30151e1" parent="a6d311a93600a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="16"/>
|
||||
<name status="New" import="New" exported="True">New Chapter</name>
|
||||
<item handle="f5ab3e30151e1" parent="a6d311a93600a" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="11" wordCount="2" paraCount="0" cursorPos="16"/>
|
||||
<name status="s72322d" import="iffcacb" exported="True">New Chapter</name>
|
||||
</item>
|
||||
<item handle="8c659a11cd429" parent="a6d311a93600a" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="15"/>
|
||||
<name status="New" import="New" exported="True">New Scene</name>
|
||||
<item handle="8c659a11cd429" parent="a6d311a93600a" root="a508bb932959c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="9" wordCount="2" paraCount="0" cursorPos="15"/>
|
||||
<name status="s72322d" import="iffcacb" exported="True">New Scene</name>
|
||||
</item>
|
||||
<item handle="7695ce551d265" parent="None" order="1" type="ROOT" class="PLOT">
|
||||
<item handle="7695ce551d265" parent="None" root="7695ce551d265" order="1" type="ROOT" class="PLOT">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Plot</name>
|
||||
<name status="s72322d" import="iffcacb">Plot</name>
|
||||
</item>
|
||||
<item handle="afb3043c7b2b3" parent="None" order="2" type="ROOT" class="CHARACTER">
|
||||
<item handle="afb3043c7b2b3" parent="None" root="afb3043c7b2b3" order="2" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Characters</name>
|
||||
<name status="s72322d" import="iffcacb">Characters</name>
|
||||
</item>
|
||||
<item handle="9d5247ab588e0" parent="None" order="3" type="ROOT" class="WORLD">
|
||||
<item handle="9d5247ab588e0" parent="None" root="9d5247ab588e0" order="3" type="ROOT" class="WORLD">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">World</name>
|
||||
<name status="s72322d" import="iffcacb">World</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -29,7 +29,6 @@ class MockGuiMain():
|
||||
def __init__(self):
|
||||
self.mainConf = None
|
||||
self.hasProject = True
|
||||
self.theIndex = None
|
||||
self.theProject = None
|
||||
self.statusBar = MockStatusBar()
|
||||
|
||||
|
||||
@@ -1,99 +1,125 @@
|
||||
{
|
||||
"tagIndex": {
|
||||
"Bod": [3, "4c4f28287af27", "CHARACTER", "T000001"],
|
||||
"Main": [3, "2426c6f0ca922", "PLOT", "T000001"],
|
||||
"Europe": [3, "04468803b92e1", "WORLD", "T000001"]
|
||||
},
|
||||
"refIndex": {
|
||||
"fb609cd8319dc": {
|
||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
||||
"tagsIndex": {
|
||||
"Bod": {"handle": "4c4f28287af27", "heading": "T000001", "class": "CHARACTER"},
|
||||
"Main": {"handle": "2426c6f0ca922", "heading": "T000001", "class": "PLOT"},
|
||||
"Europe": {"handle": "04468803b92e1", "heading": "T000001", "class": "WORLD"}
|
||||
},
|
||||
"88243afbe5ed8": {
|
||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
||||
},
|
||||
"f96ec11c6a3da": {
|
||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
||||
},
|
||||
"441420a886d82": {
|
||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
||||
},
|
||||
"eb103bc70c90c": {
|
||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
||||
},
|
||||
"f8c0562e50f1b": {
|
||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
||||
},
|
||||
"47666c91c7ccf": {
|
||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
||||
},
|
||||
"4c4f28287af27": {
|
||||
"T000001": [[4, "@plot", "Main"]]
|
||||
"itemIndex": {
|
||||
"7a992350f3eb6": {
|
||||
"level": "H1",
|
||||
"headings": {
|
||||
"T000001": {"level": "H1", "title": "Lorem Ipsum", "tag": "", "cCount": 230, "wCount": 40, "pCount": 3, "synopsis": ""}
|
||||
}
|
||||
},
|
||||
"8c58a65414c23": {
|
||||
"level": "H0",
|
||||
"headings": {
|
||||
"T000000": {"level": "H0", "title": "", "tag": "", "cCount": 1058, "wCount": 176, "pCount": 2, "synopsis": ""}
|
||||
}
|
||||
},
|
||||
"88d59a277361b": {
|
||||
"level": "H2",
|
||||
"headings": {
|
||||
"T000001": {"level": "H2", "title": "Prologue", "tag": "", "cCount": 584, "wCount": 92, "pCount": 1, "synopsis": "Explanation from the lipsum.com website."}
|
||||
}
|
||||
},
|
||||
"db7e733775d4d": {
|
||||
"level": "H1",
|
||||
"headings": {
|
||||
"T000001": {"level": "H1", "title": "Act One", "tag": "", "cCount": 35, "wCount": 6, "pCount": 1, "synopsis": ""}
|
||||
}
|
||||
},
|
||||
"fb609cd8319dc": {
|
||||
"level": "H2",
|
||||
"headings": {
|
||||
"T000001": {"level": "H2", "title": "Chapter One", "tag": "", "cCount": 419, "wCount": 67, "pCount": 1, "synopsis": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."}
|
||||
},
|
||||
"references": {
|
||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||
}
|
||||
},
|
||||
"88243afbe5ed8": {
|
||||
"level": "H3",
|
||||
"headings": {
|
||||
"T000001": {"level": "H3", "title": "Scene One", "tag": "", "cCount": 1197, "wCount": 174, "pCount": 2, "synopsis": "Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur."},
|
||||
"T000013": {"level": "H4", "title": "Scene One, Section Two", "tag": "", "cCount": 1561, "wCount": 230, "pCount": 2, "synopsis": ""}
|
||||
},
|
||||
"references": {
|
||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||
}
|
||||
},
|
||||
"f96ec11c6a3da": {
|
||||
"level": "H3",
|
||||
"headings": {
|
||||
"T000001": {"level": "H3", "title": "Scene Two", "tag": "", "cCount": 2034, "wCount": 299, "pCount": 3, "synopsis": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci."},
|
||||
"T000015": {"level": "H4", "title": "Scene Two, Section Two", "tag": "", "cCount": 2009, "wCount": 301, "pCount": 3, "synopsis": ""}
|
||||
},
|
||||
"references": {
|
||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||
}
|
||||
},
|
||||
"846352075de7d": {
|
||||
"level": "H2",
|
||||
"headings": {
|
||||
"T000001": {"level": "H2", "title": "Why do we use it?", "tag": "", "cCount": 631, "wCount": 109, "pCount": 3, "synopsis": ""}
|
||||
}
|
||||
},
|
||||
"441420a886d82": {
|
||||
"level": "H2",
|
||||
"headings": {
|
||||
"T000001": {"level": "H2", "title": "Chapter Two", "tag": "", "cCount": 477, "wCount": 70, "pCount": 1, "synopsis": "Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue."}
|
||||
},
|
||||
"references": {
|
||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||
}
|
||||
},
|
||||
"eb103bc70c90c": {
|
||||
"level": "H3",
|
||||
"headings": {
|
||||
"T000001": {"level": "H3", "title": "Scene Three", "tag": "", "cCount": 3006, "wCount": 439, "pCount": 4, "synopsis": "Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."}
|
||||
},
|
||||
"references": {
|
||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||
}
|
||||
},
|
||||
"f8c0562e50f1b": {
|
||||
"level": "H3",
|
||||
"headings": {
|
||||
"T000001": {"level": "H3", "title": "Scene Four", "tag": "", "cCount": 3839, "wCount": 563, "pCount": 6, "synopsis": "Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo."}
|
||||
},
|
||||
"references": {
|
||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||
}
|
||||
},
|
||||
"47666c91c7ccf": {
|
||||
"level": "H3",
|
||||
"headings": {
|
||||
"T000001": {"level": "H3", "title": "Scene Five", "tag": "", "cCount": 3644, "wCount": 543, "pCount": 5, "synopsis": "Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."}
|
||||
},
|
||||
"references": {
|
||||
"T000001": {"Bod": "@pov", "Main": "@plot", "Europe": "@location"}
|
||||
}
|
||||
},
|
||||
"4c4f28287af27": {
|
||||
"level": "H1",
|
||||
"headings": {
|
||||
"T000001": {"level": "H1", "title": "Nobody Owens", "tag": "Bod", "cCount": 1864, "wCount": 284, "pCount": 3, "synopsis": ""}
|
||||
},
|
||||
"references": {
|
||||
"T000001": {"Main": "@plot"}
|
||||
}
|
||||
},
|
||||
"2426c6f0ca922": {
|
||||
"level": "H1",
|
||||
"headings": {
|
||||
"T000001": {"level": "H1", "title": "Main Plot", "tag": "Main", "cCount": 1369, "wCount": 195, "pCount": 2, "synopsis": ""}
|
||||
}
|
||||
},
|
||||
"04468803b92e1": {
|
||||
"level": "H1",
|
||||
"headings": {
|
||||
"T000001": {"level": "H1", "title": "Ancient Europe", "tag": "Europe", "cCount": 1770, "wCount": 259, "pCount": 3, "synopsis": ""}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"fileIndex": {
|
||||
"7a992350f3eb6": {
|
||||
"T000001": {"level": "H1", "title": "Lorem Ipsum", "layout": "DOCUMENT", "cCount": 230, "wCount": 40, "pCount": 3, "synopsis": ""}
|
||||
},
|
||||
"8c58a65414c23": {
|
||||
"T000000": {"level": "H0", "title": "", "layout": "DOCUMENT", "cCount": 1058, "wCount": 176, "pCount": 2, "synopsis": ""}
|
||||
},
|
||||
"88d59a277361b": {
|
||||
"T000001": {"level": "H2", "title": "Prologue", "layout": "DOCUMENT", "cCount": 584, "wCount": 92, "pCount": 1, "synopsis": "Explanation from the lipsum.com website."}
|
||||
},
|
||||
"db7e733775d4d": {
|
||||
"T000001": {"level": "H1", "title": "Act One", "layout": "DOCUMENT", "cCount": 35, "wCount": 6, "pCount": 1, "synopsis": ""}
|
||||
},
|
||||
"fb609cd8319dc": {
|
||||
"T000001": {"level": "H2", "title": "Chapter One", "layout": "DOCUMENT", "cCount": 419, "wCount": 67, "pCount": 1, "synopsis": "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."}
|
||||
},
|
||||
"88243afbe5ed8": {
|
||||
"T000001": {"level": "H3", "title": "Scene One", "layout": "DOCUMENT", "cCount": 1197, "wCount": 174, "pCount": 2, "synopsis": "Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur."},
|
||||
"T000013": {"level": "H4", "title": "Scene One, Section Two", "layout": "DOCUMENT", "cCount": 1561, "wCount": 230, "pCount": 2, "synopsis": ""}
|
||||
},
|
||||
"f96ec11c6a3da": {
|
||||
"T000001": {"level": "H3", "title": "Scene Two", "layout": "DOCUMENT", "cCount": 2034, "wCount": 299, "pCount": 3, "synopsis": "Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci."},
|
||||
"T000015": {"level": "H4", "title": "Scene Two, Section Two", "layout": "DOCUMENT", "cCount": 2009, "wCount": 301, "pCount": 3, "synopsis": ""}
|
||||
},
|
||||
"846352075de7d": {
|
||||
"T000001": {"level": "H2", "title": "Why do we use it?", "layout": "DOCUMENT", "cCount": 631, "wCount": 109, "pCount": 3, "synopsis": ""}
|
||||
},
|
||||
"441420a886d82": {
|
||||
"T000001": {"level": "H2", "title": "Chapter Two", "layout": "DOCUMENT", "cCount": 477, "wCount": 70, "pCount": 1, "synopsis": "Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue."}
|
||||
},
|
||||
"eb103bc70c90c": {
|
||||
"T000001": {"level": "H3", "title": "Scene Three", "layout": "DOCUMENT", "cCount": 3006, "wCount": 439, "pCount": 4, "synopsis": "Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."}
|
||||
},
|
||||
"f8c0562e50f1b": {
|
||||
"T000001": {"level": "H3", "title": "Scene Four", "layout": "DOCUMENT", "cCount": 3839, "wCount": 563, "pCount": 6, "synopsis": "Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo."}
|
||||
},
|
||||
"47666c91c7ccf": {
|
||||
"T000001": {"level": "H3", "title": "Scene Five", "layout": "DOCUMENT", "cCount": 3644, "wCount": 543, "pCount": 5, "synopsis": "Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."}
|
||||
},
|
||||
"4c4f28287af27": {
|
||||
"T000001": {"level": "H1", "title": "Nobody Owens", "layout": "NOTE", "cCount": 1864, "wCount": 284, "pCount": 3, "synopsis": ""}
|
||||
},
|
||||
"2426c6f0ca922": {
|
||||
"T000001": {"level": "H1", "title": "Main Plot", "layout": "NOTE", "cCount": 1369, "wCount": 195, "pCount": 2, "synopsis": ""}
|
||||
},
|
||||
"04468803b92e1": {
|
||||
"T000001": {"level": "H1", "title": "Ancient Europe", "layout": "NOTE", "cCount": 1770, "wCount": 259, "pCount": 3, "synopsis": ""}
|
||||
}
|
||||
},
|
||||
"fileMeta": {
|
||||
"7a992350f3eb6": ["H1", 230, 40, 3],
|
||||
"8c58a65414c23": ["H0", 1058, 176, 2],
|
||||
"88d59a277361b": ["H2", 584, 92, 1],
|
||||
"db7e733775d4d": ["H1", 35, 6, 1],
|
||||
"fb609cd8319dc": ["H2", 419, 67, 1],
|
||||
"88243afbe5ed8": ["H3", 2758, 404, 4],
|
||||
"f96ec11c6a3da": ["H3", 4043, 600, 6],
|
||||
"846352075de7d": ["H2", 631, 109, 3],
|
||||
"441420a886d82": ["H2", 477, 70, 1],
|
||||
"eb103bc70c90c": ["H3", 3006, 439, 4],
|
||||
"f8c0562e50f1b": ["H3", 3839, 563, 6],
|
||||
"47666c91c7ccf": ["H3", 3644, 543, 5],
|
||||
"4c4f28287af27": ["H1", 1864, 284, 3],
|
||||
"2426c6f0ca922": ["H1", 1369, 195, 2],
|
||||
"04468803b92e1": ["H1", 1770, 259, 3]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:38:23">
|
||||
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:49:23">
|
||||
<project>
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
@@ -29,110 +29,106 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Note</entry>
|
||||
<entry red="200" green="150" blue="0">Draft</entry>
|
||||
<entry red="50" green="200" blue="0">Finished</entry>
|
||||
<entry key="s000008" count="15" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Minor</entry>
|
||||
<entry red="200" green="150" blue="0">Major</entry>
|
||||
<entry red="50" green="200" blue="0">Main</entry>
|
||||
<entry key="i00000c" count="7" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="23">
|
||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
||||
<content count="22">
|
||||
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Novel</name>
|
||||
<name status="s000008" import="i00000c">Novel</name>
|
||||
</item>
|
||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
||||
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="0000000000012" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Chapter 1</name>
|
||||
</item>
|
||||
<item handle="0000000000013" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 1.1</name>
|
||||
</item>
|
||||
<item handle="0000000000014" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 1.2</name>
|
||||
</item>
|
||||
<item handle="0000000000015" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 1.3</name>
|
||||
</item>
|
||||
<item handle="0000000000016" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Chapter 2</name>
|
||||
</item>
|
||||
<item handle="0000000000017" parent="0000000000016" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 2.1</name>
|
||||
</item>
|
||||
<item handle="0000000000018" parent="0000000000016" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 2.2</name>
|
||||
</item>
|
||||
<item handle="0000000000019" parent="0000000000016" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 2.3</name>
|
||||
</item>
|
||||
<item handle="000000000001a" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Chapter 3</name>
|
||||
</item>
|
||||
<item handle="000000000001b" parent="000000000001a" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 3.1</name>
|
||||
</item>
|
||||
<item handle="000000000001c" parent="000000000001a" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 3.2</name>
|
||||
</item>
|
||||
<item handle="000000000001d" parent="000000000001a" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 3.3</name>
|
||||
</item>
|
||||
<item handle="000000000001e" parent="None" root="000000000001e" order="0" type="ROOT" class="PLOT">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Plot</name>
|
||||
<name status="s000008" import="i00000c">Plot</name>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
||||
<item handle="000000000001f" parent="000000000001e" root="000000000001e" order="0" type="FILE" class="PLOT" layout="NOTE">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Main Plot</name>
|
||||
</item>
|
||||
<item handle="0000000000020" parent="None" root="0000000000020" order="0" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Characters</name>
|
||||
<name status="s000008" import="i00000c">Characters</name>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
||||
<item handle="0000000000021" parent="0000000000020" root="0000000000020" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Protagonist</name>
|
||||
</item>
|
||||
<item handle="0000000000022" parent="None" root="0000000000022" order="0" type="ROOT" class="WORLD">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Locations</name>
|
||||
<name status="s000008" import="i00000c">Locations</name>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" parent="None" order="0" type="ROOT" class="TIMELINE">
|
||||
<item handle="0000000000023" parent="0000000000022" root="0000000000022" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Main Location</name>
|
||||
</item>
|
||||
<item handle="0000000000024" parent="None" root="0000000000024" order="0" type="ROOT" class="ARCHIVE">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Timeline</name>
|
||||
<name status="s000008" import="i00000c">Archive</name>
|
||||
</item>
|
||||
<item handle="31489056e0916" parent="None" order="0" type="ROOT" class="OBJECT">
|
||||
<item handle="0000000000025" parent="None" root="0000000000025" order="0" type="ROOT" class="TRASH">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Objects</name>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" parent="None" order="0" type="ROOT" class="ENTITY">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Entities</name>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Chapter 1</name>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" parent="1a6562590ef19" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Chapter 1</name>
|
||||
</item>
|
||||
<item handle="41cfc0d1f2d12" parent="1a6562590ef19" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 1.1</name>
|
||||
</item>
|
||||
<item handle="2858dcd1057d3" parent="1a6562590ef19" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 1.2</name>
|
||||
</item>
|
||||
<item handle="2fca346db6561" parent="1a6562590ef19" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 1.3</name>
|
||||
</item>
|
||||
<item handle="02d20bbd7e394" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Chapter 2</name>
|
||||
</item>
|
||||
<item handle="7688b6ef52555" parent="02d20bbd7e394" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Chapter 2</name>
|
||||
</item>
|
||||
<item handle="c837649cce43f" parent="02d20bbd7e394" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 2.1</name>
|
||||
</item>
|
||||
<item handle="6208ef0f7750c" parent="02d20bbd7e394" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 2.2</name>
|
||||
</item>
|
||||
<item handle="3e1e967e9b793" parent="02d20bbd7e394" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 2.3</name>
|
||||
</item>
|
||||
<item handle="39fa9ec190eee" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Chapter 3</name>
|
||||
</item>
|
||||
<item handle="d029fa3a95e17" parent="39fa9ec190eee" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Chapter 3</name>
|
||||
</item>
|
||||
<item handle="81b8a03f97e87" parent="39fa9ec190eee" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 3.1</name>
|
||||
</item>
|
||||
<item handle="da4ea2a5506f2" parent="39fa9ec190eee" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 3.2</name>
|
||||
</item>
|
||||
<item handle="a68b412c42825" parent="39fa9ec190eee" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 3.3</name>
|
||||
<name status="s000008" import="i00000c">Trash</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:38:23">
|
||||
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:48:40">
|
||||
<project>
|
||||
<name>Test Custom</name>
|
||||
<title>Test Novel</title>
|
||||
@@ -29,74 +29,82 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Note</entry>
|
||||
<entry red="200" green="150" blue="0">Draft</entry>
|
||||
<entry red="50" green="200" blue="0">Finished</entry>
|
||||
<entry key="s000008" count="9" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Minor</entry>
|
||||
<entry red="200" green="150" blue="0">Major</entry>
|
||||
<entry red="50" green="200" blue="0">Main</entry>
|
||||
<entry key="i00000c" count="7" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="14">
|
||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
||||
<content count="16">
|
||||
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Novel</name>
|
||||
<name status="s000008" import="i00000c">Novel</name>
|
||||
</item>
|
||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
||||
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="0000000000012" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 1</name>
|
||||
</item>
|
||||
<item handle="0000000000013" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 2</name>
|
||||
</item>
|
||||
<item handle="0000000000014" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 3</name>
|
||||
</item>
|
||||
<item handle="0000000000015" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 4</name>
|
||||
</item>
|
||||
<item handle="0000000000016" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 5</name>
|
||||
</item>
|
||||
<item handle="0000000000017" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Scene 6</name>
|
||||
</item>
|
||||
<item handle="0000000000018" parent="None" root="0000000000018" order="0" type="ROOT" class="PLOT">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Plot</name>
|
||||
<name status="s000008" import="i00000c">Plot</name>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
||||
<item handle="0000000000019" parent="0000000000018" root="0000000000018" order="0" type="FILE" class="PLOT" layout="NOTE">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Main Plot</name>
|
||||
</item>
|
||||
<item handle="000000000001a" parent="None" root="000000000001a" order="0" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Characters</name>
|
||||
<name status="s000008" import="i00000c">Characters</name>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
||||
<item handle="000000000001b" parent="000000000001a" root="000000000001a" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Protagonist</name>
|
||||
</item>
|
||||
<item handle="000000000001c" parent="None" root="000000000001c" order="0" type="ROOT" class="WORLD">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Locations</name>
|
||||
<name status="s000008" import="i00000c">Locations</name>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" parent="None" order="0" type="ROOT" class="TIMELINE">
|
||||
<item handle="000000000001d" parent="000000000001c" root="000000000001c" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Main Location</name>
|
||||
</item>
|
||||
<item handle="000000000001e" parent="None" root="000000000001e" order="0" type="ROOT" class="ARCHIVE">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Timeline</name>
|
||||
<name status="s000008" import="i00000c">Archive</name>
|
||||
</item>
|
||||
<item handle="31489056e0916" parent="None" order="0" type="ROOT" class="OBJECT">
|
||||
<item handle="000000000001f" parent="None" root="000000000001f" order="0" type="ROOT" class="TRASH">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Objects</name>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" parent="None" order="0" type="ROOT" class="ENTITY">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Entities</name>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 1</name>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 2</name>
|
||||
</item>
|
||||
<item handle="41cfc0d1f2d12" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 3</name>
|
||||
</item>
|
||||
<item handle="2858dcd1057d3" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 4</name>
|
||||
</item>
|
||||
<item handle="2fca346db6561" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 5</name>
|
||||
</item>
|
||||
<item handle="02d20bbd7e394" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Scene 6</name>
|
||||
<name status="s000008" import="i00000c">Trash</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:38:23">
|
||||
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:08:21">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<title>New Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
<saveCount>2</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
@@ -27,58 +28,58 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Note</entry>
|
||||
<entry red="200" green="150" blue="0">Draft</entry>
|
||||
<entry red="50" green="200" blue="0">Finished</entry>
|
||||
<entry key="s000008" count="5" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Minor</entry>
|
||||
<entry red="200" green="150" blue="0">Major</entry>
|
||||
<entry red="50" green="200" blue="0">Main</entry>
|
||||
<entry key="i00000c" count="3" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="10">
|
||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
||||
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Novel</name>
|
||||
<name status="s000008" import="i00000c">Novel</name>
|
||||
</item>
|
||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
||||
<item handle="0000000000011" parent="None" root="0000000000011" order="0" type="ROOT" class="PLOT">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Plot</name>
|
||||
<name status="s000008" import="i00000c">Plot</name>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
||||
<item handle="0000000000012" parent="None" root="0000000000012" order="0" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Characters</name>
|
||||
<name status="s000008" import="i00000c">Characters</name>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
||||
<item handle="0000000000013" parent="None" root="0000000000013" order="0" type="ROOT" class="WORLD">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">World</name>
|
||||
<name status="s000008" import="i00000c">World</name>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="New" exported="True">Title Page</name>
|
||||
<item handle="0000000000014" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="31489056e0916" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||
<item handle="0000000000015" parent="0000000000010" root="0000000000010" order="0" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">New Chapter</name>
|
||||
<name status="s000008" import="i00000c">New Chapter</name>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="New" exported="True">New Chapter</name>
|
||||
<item handle="0000000000016" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">New Chapter</name>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="New" exported="True">New Scene</name>
|
||||
<item handle="0000000000017" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">New Scene</name>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Hello</name>
|
||||
<item handle="0000000000028" parent="31489056e0916" root="None" order="0" type="FILE" class="NO_CLASS" layout="NO_LAYOUT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="None" import="None" exported="True">Hello</name>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" parent="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="None" exported="True">Jane</name>
|
||||
<item handle="0000000000029" parent="71ee45a3c0db9" root="None" order="0" type="FILE" class="NO_CLASS" layout="NO_LAYOUT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="None" import="None" exported="True">Jane</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:54:40">
|
||||
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:50:26">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<saveCount>1</saveCount>
|
||||
<saveCount>2</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
</project>
|
||||
@@ -27,50 +27,50 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Note</entry>
|
||||
<entry red="200" green="150" blue="0">Draft</entry>
|
||||
<entry red="50" green="200" blue="0">Finished</entry>
|
||||
<entry key="s000008" count="5" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Minor</entry>
|
||||
<entry red="200" green="150" blue="0">Major</entry>
|
||||
<entry red="50" green="200" blue="0">Main</entry>
|
||||
<entry key="i00000c" count="3" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="8">
|
||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
||||
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Novel</name>
|
||||
<name status="s000008" import="i00000c">Novel</name>
|
||||
</item>
|
||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
||||
<item handle="0000000000011" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="0000000000012" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">New Chapter</name>
|
||||
</item>
|
||||
<item handle="0000000000013" parent="0000000000012" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">New Scene</name>
|
||||
</item>
|
||||
<item handle="0000000000014" parent="None" root="0000000000014" order="0" type="ROOT" class="PLOT">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Plot</name>
|
||||
<name status="s000008" import="i00000c">Plot</name>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
||||
<item handle="0000000000015" parent="None" root="0000000000015" order="0" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Characters</name>
|
||||
<name status="s000008" import="i00000c">Characters</name>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
||||
<item handle="0000000000016" parent="None" root="0000000000016" order="0" type="ROOT" class="WORLD">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">World</name>
|
||||
<name status="s000008" import="i00000c">Locations</name>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="New" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="31489056e0916" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||
<item handle="0000000000017" parent="None" root="0000000000017" order="0" type="ROOT" class="ARCHIVE">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">New Chapter</name>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="New" exported="True">New Chapter</name>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="New" exported="True">New Scene</name>
|
||||
<name status="s000008" import="i00000c">Archive</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="1.7-alpha0" hexVersion="0x010700a0" fileVersion="1.4" timeStamp="2022-04-03 21:38:23">
|
||||
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 22:07:28">
|
||||
<project>
|
||||
<name>New Project</name>
|
||||
<title></title>
|
||||
<title>New Novel</title>
|
||||
<author>Jane Doe</author>
|
||||
<saveCount>2</saveCount>
|
||||
<autoCount>1</autoCount>
|
||||
<editTime>0</editTime>
|
||||
@@ -27,66 +28,82 @@
|
||||
<section></section>
|
||||
</titleFormat>
|
||||
<status>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Note</entry>
|
||||
<entry red="200" green="150" blue="0">Draft</entry>
|
||||
<entry red="50" green="200" blue="0">Finished</entry>
|
||||
<entry key="s000008" count="6" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry red="100" green="100" blue="100">New</entry>
|
||||
<entry red="200" green="50" blue="0">Minor</entry>
|
||||
<entry red="200" green="150" blue="0">Major</entry>
|
||||
<entry red="50" green="200" blue="0">Main</entry>
|
||||
<entry key="i00000c" count="10" red="100" green="100" blue="100">New</entry>
|
||||
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content count="12">
|
||||
<item handle="73475cb40a568" parent="None" order="0" type="ROOT" class="NOVEL">
|
||||
<content count="16">
|
||||
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Novel</name>
|
||||
<name status="s000008" import="i00000c">Novel</name>
|
||||
</item>
|
||||
<item handle="44cb730c42048" parent="None" order="0" type="ROOT" class="PLOT">
|
||||
<item handle="0000000000011" parent="None" root="0000000000011" order="0" type="ROOT" class="PLOT">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Plot</name>
|
||||
<name status="s000008" import="i00000c">Plot</name>
|
||||
</item>
|
||||
<item handle="71ee45a3c0db9" parent="None" order="0" type="ROOT" class="CHARACTER">
|
||||
<item handle="0000000000012" parent="None" root="0000000000012" order="0" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">Characters</name>
|
||||
<name status="s000008" import="i00000c">Characters</name>
|
||||
</item>
|
||||
<item handle="811786ad1ae74" parent="None" order="0" type="ROOT" class="WORLD">
|
||||
<item handle="0000000000013" parent="None" root="0000000000013" order="0" type="ROOT" class="WORLD">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">World</name>
|
||||
<name status="s000008" import="i00000c">World</name>
|
||||
</item>
|
||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="New" exported="True">Title Page</name>
|
||||
<item handle="0000000000014" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">Title Page</name>
|
||||
</item>
|
||||
<item handle="31489056e0916" parent="73475cb40a568" order="0" type="FOLDER" class="NOVEL">
|
||||
<item handle="0000000000015" parent="0000000000010" root="0000000000010" order="0" type="FOLDER" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="New">New Chapter</name>
|
||||
<name status="s000008" import="i00000c">New Chapter</name>
|
||||
</item>
|
||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="New" exported="True">New Chapter</name>
|
||||
<item handle="0000000000016" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">New Chapter</name>
|
||||
</item>
|
||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="New" import="New" exported="True">New Scene</name>
|
||||
<item handle="0000000000017" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||
<name status="s000008" import="i00000c" exported="True">New Scene</name>
|
||||
</item>
|
||||
<item handle="1a6562590ef19" parent="None" order="0" type="ROOT" class="TIMELINE">
|
||||
<item handle="0000000000028" parent="None" root="0000000000028" order="0" type="ROOT" class="NOVEL">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Timeline</name>
|
||||
<name status="s000008" import="i00000c">Novel</name>
|
||||
</item>
|
||||
<item handle="031b4af5197ec" parent="None" order="0" type="ROOT" class="OBJECT">
|
||||
<item handle="0000000000029" parent="None" root="0000000000029" order="0" type="ROOT" class="PLOT">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Object</name>
|
||||
<name status="s000008" import="i00000c">Plot</name>
|
||||
</item>
|
||||
<item handle="41cfc0d1f2d12" parent="None" order="0" type="ROOT" class="CUSTOM">
|
||||
<item handle="000000000002a" parent="None" root="000000000002a" order="0" type="ROOT" class="CHARACTER">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Custom1</name>
|
||||
<name status="s000008" import="i00000c">Characters</name>
|
||||
</item>
|
||||
<item handle="2858dcd1057d3" parent="None" order="0" type="ROOT" class="CUSTOM">
|
||||
<item handle="000000000002b" parent="None" root="000000000002b" order="0" type="ROOT" class="WORLD">
|
||||
<meta expanded="False"/>
|
||||
<name status="New" import="None">Custom2</name>
|
||||
<name status="s000008" import="i00000c">Locations</name>
|
||||
</item>
|
||||
<item handle="000000000002c" parent="None" root="000000000002c" order="0" type="ROOT" class="TIMELINE">
|
||||
<meta expanded="False"/>
|
||||
<name status="s000008" import="i00000c">Timeline</name>
|
||||
</item>
|
||||
<item handle="000000000002d" parent="None" root="000000000002d" order="0" type="ROOT" class="OBJECT">
|
||||
<meta expanded="False"/>
|
||||
<name status="s000008" import="i00000c">Objects</name>
|
||||
</item>
|
||||
<item handle="000000000002e" parent="None" root="000000000002e" order="0" type="ROOT" class="CUSTOM">
|
||||
<meta expanded="False"/>
|
||||
<name status="s000008" import="i00000c">Custom</name>
|
||||
</item>
|
||||
<item handle="000000000002f" parent="None" root="000000000002f" order="0" type="ROOT" class="CUSTOM">
|
||||
<meta expanded="False"/>
|
||||
<name status="s000008" import="i00000c">Custom</name>
|
||||
</item>
|
||||
</content>
|
||||
</novelWriterXML>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%~name: New Scene
|
||||
%%~path: 31489056e0916/0e17daca5f3e1
|
||||
%%~path: 000000000000d/000000000000f
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
# Novel
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%~name: New File
|
||||
%%~path: 71ee45a3c0db9/1a6562590ef19
|
||||
%%~name: New Note
|
||||
%%~path: 000000000000a/0000000000020
|
||||
%%~kind: CHARACTER/NOTE
|
||||
# Jane Doe
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%~name: New File
|
||||
%%~path: 44cb730c42048/031b4af5197ec
|
||||
%%~name: New Note
|
||||
%%~path: 0000000000009/0000000000021
|
||||
%%~kind: PLOT/NOTE
|
||||
# Main Plot
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
%%~name: New File
|
||||
%%~path: 811786ad1ae74/41cfc0d1f2d12
|
||||
%%~name: New Note
|
||||
%%~path: 000000000000b/0000000000022
|
||||
%%~kind: WORLD/NOTE
|
||||
# Main Location
|
||||
|
||||