Merge branch 'main' into i18n-de_DE-created
@@ -1,5 +1,94 @@
|
|||||||
# novelWriter Changelog
|
# 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]
|
## Version 1.6.2 [2022-03-20]
|
||||||
|
|
||||||
### Release Notes
|
### Release Notes
|
||||||
|
|||||||
@@ -11,13 +11,15 @@
|
|||||||
|
|
||||||
## Translations
|
## Translations
|
||||||
|
|
||||||
|
The default language is English (UK) with English (US) as an option.
|
||||||
|
|
||||||
|
* Dutch: Martijn van der Kleijn (@mvdkleijn)
|
||||||
* French: Jan Lüdke (@jyhelle)
|
* French: Jan Lüdke (@jyhelle)
|
||||||
|
* German: Myian (@heymyian)
|
||||||
|
* Latin American Spanish: Tommy Marplatt (@tmarplatt)
|
||||||
* Norwegian: Veronica Berglyd Olsen (@vkbo)
|
* Norwegian: Veronica Berglyd Olsen (@vkbo)
|
||||||
* Portuguese: Bruno Meneguello (@bkmeneguello)
|
* Portuguese: Bruno Meneguello (@bkmeneguello)
|
||||||
* Simplified Chinese: Qianzhi Long (@longqzh)
|
* Simplified Chinese: Qianzhi Long (@longqzh)
|
||||||
* Latin American Spanish: Tommy Marplatt (@tmarplatt)
|
|
||||||
* Dutch: Martijn van der Kleijn (@mvdkleijn)
|
|
||||||
* German: Myian (@heymyian)
|
|
||||||
|
|
||||||
## Libraries
|
## 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:
|
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)
|
* Tomorrow syntax themes by Chris Kempson (MIT License)
|
||||||
* Owl syntax themes by Sarah Drasner (MIT License)
|
* Owl syntax themes by Sarah Drasner (MIT License)
|
||||||
* Solarized themes by Ethan Schoonover, added by @nullbasis (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
|
The full credits are listed in
|
||||||
[CREDITS.md](https://github.com/vkbo/novelWriter/blob/main/CREDITS.md).
|
[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
|
## Implementation
|
||||||
|
|
||||||
The application is written with Python 3 (3.7+) using Qt5 and PyQt5 (5.3+). It is developed on
|
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
|
* Website: https://novelwriter.io
|
||||||
* Documentation: https://novelwriter.readthedocs.io
|
* Documentation: https://novelwriter.readthedocs.io
|
||||||
|
* Internationalisation: https://crowdin.com/project/novelwriter
|
||||||
* Source Code: https://github.com/vkbo/novelWriter
|
* Source Code: https://github.com/vkbo/novelWriter
|
||||||
* Source Releases: https://github.com/vkbo/novelWriter/releases
|
* Source Releases: https://github.com/vkbo/novelWriter/releases
|
||||||
* Issue Tracker: https://github.com/vkbo/novelWriter/issues
|
* Issue Tracker: https://github.com/vkbo/novelWriter/issues
|
||||||
* Feature Discussions: https://github.com/vkbo/novelWriter/discussions
|
* Feature Discussions: https://github.com/vkbo/novelWriter/discussions
|
||||||
* PyPi Project: https://pypi.org/project/novelWriter
|
* PyPi Project: https://pypi.org/project/novelWriter
|
||||||
|
* Social Media: https://fosstodon.org/@novelwriter
|
||||||
|
|
||||||
.. toctree::
|
.. toctree::
|
||||||
:maxdepth: 1
|
:maxdepth: 1
|
||||||
|
|||||||
@@ -59,7 +59,6 @@ The main shorcuts are as follows:
|
|||||||
":kbd:`Ctrl`:kbd:`Y`", "Redo latest undo."
|
":kbd:`Ctrl`:kbd:`Y`", "Redo latest undo."
|
||||||
":kbd:`Ctrl`:kbd:`Z`", "Undo latest changes."
|
":kbd:`Ctrl`:kbd:`Z`", "Undo latest changes."
|
||||||
":kbd:`Ctrl`:kbd:`F7`", "Toggle spell checking."
|
":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:`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:`Down`", "Move item one step down in the project tree."
|
||||||
":kbd:`Ctrl`:kbd:`Del`", "Delete next word in editor."
|
":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:`F7`", "Re-run spell checker."
|
||||||
":kbd:`F8`", "Activate :guilabel:`Focus Mode`, hiding the project tree and document viewer."
|
":kbd:`F8`", "Activate :guilabel:`Focus Mode`, hiding the project tree and document viewer."
|
||||||
":kbd:`F9`", "Re-build the project index."
|
":kbd:`F9`", "Re-build the project index."
|
||||||
":kbd:`F10`", "Re-build the project outline."
|
|
||||||
":kbd:`F11`", "Activate full screen mode."
|
":kbd:`F11`", "Activate full screen mode."
|
||||||
":kbd:`Shift`:kbd:`F1`", "Open the local user manual (PDF) if it is available."
|
":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."
|
":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
|
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).
|
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
|
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.
|
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.
|
[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
|
# Direct Approach Using Qt Linguist
|
||||||
|
|
||||||
Here you will find instructions for translating novelWriter to a new language directly using Qt
|
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"
|
__author__ = "Veronica Berglyd Olsen"
|
||||||
__maintainer__ = "Veronica Berglyd Olsen"
|
__maintainer__ = "Veronica Berglyd Olsen"
|
||||||
__email__ = "code@vkbo.net"
|
__email__ = "code@vkbo.net"
|
||||||
__version__ = "1.7-alpha0"
|
__version__ = "1.7-beta1"
|
||||||
__hexversion__ = "0x010700a0"
|
__hexversion__ = "0x010700b1"
|
||||||
__date__ = "2022-02-20"
|
__date__ = "2022-05-17"
|
||||||
__status__ = "Stable"
|
__status__ = "Stable"
|
||||||
__domain__ = "novelwriter.io"
|
__domain__ = "novelwriter.io"
|
||||||
__url__ = "https://novelwriter.io"
|
__url__ = "https://novelwriter.io"
|
||||||
|
|||||||
@@ -2,104 +2,104 @@
|
|||||||
"Synopsis": "Synopsis",
|
"Synopsis": "Synopsis",
|
||||||
"Comment": "Opmerking",
|
"Comment": "Opmerking",
|
||||||
"Notes": "Notities",
|
"Notes": "Notities",
|
||||||
"0": "Nul",
|
"0": "nul",
|
||||||
"1": "Één",
|
"1": "één",
|
||||||
"2": "Twee",
|
"2": "twee",
|
||||||
"3": "Drie",
|
"3": "drie",
|
||||||
"4": "Vier",
|
"4": "vier",
|
||||||
"5": "Vijf",
|
"5": "vijf",
|
||||||
"6": "Zes",
|
"6": "zes",
|
||||||
"7": "Zeven",
|
"7": "zeven",
|
||||||
"8": "Acht",
|
"8": "acht",
|
||||||
"9": "Negen",
|
"9": "negen",
|
||||||
"10": "Tien",
|
"10": "tien",
|
||||||
"11": "Elf",
|
"11": "elf",
|
||||||
"12": "Twaalf",
|
"12": "twaalf",
|
||||||
"13": "Dertien",
|
"13": "dertien",
|
||||||
"14": "Veertien",
|
"14": "veertien",
|
||||||
"15": "Vijftien",
|
"15": "vijftien",
|
||||||
"16": "Zestien",
|
"16": "zestien",
|
||||||
"17": "Zeventien",
|
"17": "zeventien",
|
||||||
"18": "Achttien",
|
"18": "achttien",
|
||||||
"19": "Negentien",
|
"19": "negentien",
|
||||||
"20": "Twintig",
|
"20": "twintig",
|
||||||
"21": "Eenentwintig",
|
"21": "eenentwintig",
|
||||||
"22": "Tweeentwintig",
|
"22": "tweeëntwintig",
|
||||||
"23": "Drieentwintig",
|
"23": "drieëntwintig",
|
||||||
"24": "Vierentwintig",
|
"24": "vierentwintig",
|
||||||
"25": "Vijfentwintig",
|
"25": "vijfentwintig",
|
||||||
"26": "Zesentwintig",
|
"26": "zesentwintig",
|
||||||
"27": "Zevenentwintig",
|
"27": "zevenentwintig",
|
||||||
"28": "Achtentwintig",
|
"28": "achtentwintig",
|
||||||
"29": "Negenentwintig",
|
"29": "negenentwintig",
|
||||||
"30": "Dertig",
|
"30": "dertig",
|
||||||
"31": "Eenendertig",
|
"31": "eenendertig",
|
||||||
"32": "Tweeendertig",
|
"32": "tweeëndertig",
|
||||||
"33": "Drieendertig",
|
"33": "drieëndertig",
|
||||||
"34": "Vierendertig",
|
"34": "vierendertig",
|
||||||
"35": "Vijfendertig",
|
"35": "vijfendertig",
|
||||||
"36": "Zesendertig",
|
"36": "zesendertig",
|
||||||
"37": "Zevenendertig",
|
"37": "zevenendertig",
|
||||||
"38": "Achtendertig",
|
"38": "achtendertig",
|
||||||
"39": "Negenendertig",
|
"39": "negenendertig",
|
||||||
"40": "Veertig",
|
"40": "veertig",
|
||||||
"41": "Eenenveertig",
|
"41": "eenenveertig",
|
||||||
"42": "Tweeënveertig",
|
"42": "tweeënveertig",
|
||||||
"43": "Drieenveertig",
|
"43": "drieënveertig",
|
||||||
"44": "Vierenveertig",
|
"44": "vierenveertig",
|
||||||
"45": "Vijfenveertig",
|
"45": "vijfenveertig",
|
||||||
"46": "Zesenveertig",
|
"46": "zesenveertig",
|
||||||
"47": "Zevenenveertig",
|
"47": "zevenenveertig",
|
||||||
"48": "Achtenveertig",
|
"48": "achtenveertig",
|
||||||
"49": "Negenenveertig",
|
"49": "negenenveertig",
|
||||||
"50": "Vijftig",
|
"50": "vijftig",
|
||||||
"51": "Eenenvijftig",
|
"51": "eenenvijftig",
|
||||||
"52": "Tweeenvijftig",
|
"52": "tweeënvijftig",
|
||||||
"53": "Drieenvijftig",
|
"53": "drieënvijftig",
|
||||||
"54": "Vierenvijftig",
|
"54": "vierenvijftig",
|
||||||
"55": "Vijfenvijftig",
|
"55": "vijfenvijftig",
|
||||||
"56": "Zesenvijftig",
|
"56": "zesenvijftig",
|
||||||
"57": "Zevenenvijftig",
|
"57": "zevenenvijftig",
|
||||||
"58": "Achtenvijftig",
|
"58": "achtenvijftig",
|
||||||
"59": "Negenenvijftig",
|
"59": "negenenvijftig",
|
||||||
"60": "Zestig",
|
"60": "zestig",
|
||||||
"61": "Eenenzestig",
|
"61": "eenenzestig",
|
||||||
"62": "Tweeenzestig",
|
"62": "tweeënzestig",
|
||||||
"63": "Drieenzestig",
|
"63": "drieënzestig",
|
||||||
"64": "Vierenzestig",
|
"64": "vierenzestig",
|
||||||
"65": "Vijfenzestig",
|
"65": "vijfenzestig",
|
||||||
"66": "Zesenzestig",
|
"66": "zesenzestig",
|
||||||
"67": "Zevenenzestig",
|
"67": "zevenenzestig",
|
||||||
"68": "Achtenzestig",
|
"68": "achtenzestig",
|
||||||
"69": "Negenenzestig",
|
"69": "negenenzestig",
|
||||||
"70": "Zeventig",
|
"70": "zeventig",
|
||||||
"71": "Eenenzeventig",
|
"71": "eenenzeventig",
|
||||||
"72": "Tweeenzeventig",
|
"72": "tweeënzeventig",
|
||||||
"73": "Drieenzeventig",
|
"73": "drieënzeventig",
|
||||||
"74": "Vierenzeventig",
|
"74": "vierenzeventig",
|
||||||
"75": "Vijfenzeventig",
|
"75": "vijfenzeventig",
|
||||||
"76": "Zesenzeventig",
|
"76": "zesenzeventig",
|
||||||
"77": "Zevenenzeventig",
|
"77": "zevenenzeventig",
|
||||||
"78": "Achtenzeventig",
|
"78": "achtenzeventig",
|
||||||
"79": "Negenenzeventig",
|
"79": "negenenzeventig",
|
||||||
"80": "Tachtig",
|
"80": "tachtig",
|
||||||
"81": "Eenentachtig",
|
"81": "eenentachtig",
|
||||||
"82": "Tweeentachtig",
|
"82": "tweeëntachtig",
|
||||||
"83": "Drieentachtig",
|
"83": "drieëntachtig",
|
||||||
"84": "Vierentachtig",
|
"84": "vierentachtig",
|
||||||
"85": "Vijfentachtig",
|
"85": "vijfentachtig",
|
||||||
"86": "Zesentachtig",
|
"86": "zesentachtig",
|
||||||
"87": "Zevenentachtig",
|
"87": "zevenentachtig",
|
||||||
"88": "Achtentachtig",
|
"88": "achtentachtig",
|
||||||
"89": "Negenentachtig",
|
"89": "negenentachtig",
|
||||||
"90": "Negentig",
|
"90": "negentig",
|
||||||
"91": "Eenennegentig",
|
"91": "eenennegentig",
|
||||||
"92": "Tweeennegentig",
|
"92": "tweeënnegentig",
|
||||||
"93": "Drieennegentig",
|
"93": "drieënnegentig",
|
||||||
"94": "Vierennegentig",
|
"94": "vierennegentig",
|
||||||
"95": "Vijfennegentig",
|
"95": "vijfennegentig",
|
||||||
"96": "Zesennegentig",
|
"96": "zesennegentig",
|
||||||
"97": "Zevenennegentig",
|
"97": "zevenennegentig",
|
||||||
"98": "Achtennegentig",
|
"98": "achtennegentig",
|
||||||
"99": "Negenennegentig"
|
"99": "negenennegentig"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -42,16 +42,20 @@ doc_h2 = mixed_heading2.svg
|
|||||||
doc_h3 = mixed_heading3.svg
|
doc_h3 = mixed_heading3.svg
|
||||||
doc_h4 = mixed_heading4.svg
|
doc_h4 = mixed_heading4.svg
|
||||||
done = typ_input-checked.svg
|
done = typ_input-checked.svg
|
||||||
|
down = typ_chevron-down.svg
|
||||||
edit = typ_pencil.svg
|
edit = typ_pencil.svg
|
||||||
forward = typ_chevron-right.svg
|
forward = typ_chevron-right.svg
|
||||||
hash = typ_hash.svg
|
hash = typ_hash.svg
|
||||||
maximise = typ_arrow-maximise.svg
|
maximise = typ_arrow-maximise.svg
|
||||||
|
menu = typ_th-menu.svg
|
||||||
minimise = typ_arrow-minimise.svg
|
minimise = typ_arrow-minimise.svg
|
||||||
proj_chapter = mixed_document-chapter.svg
|
proj_chapter = mixed_document-chapter.svg
|
||||||
|
proj_details = typ_th-list-grey.svg
|
||||||
proj_document = typ_document-text.svg
|
proj_document = typ_document-text.svg
|
||||||
proj_folder = typ_folder.svg
|
proj_folder = typ_folder.svg
|
||||||
proj_note = mixed_document-note.svg
|
proj_note = mixed_document-note.svg
|
||||||
proj_scene = mixed_document-scene.svg
|
proj_scene = mixed_document-scene.svg
|
||||||
|
proj_stats = typ_chart-bar-grey.svg
|
||||||
proj_title = mixed_document-title.svg
|
proj_title = mixed_document-title.svg
|
||||||
reference = typ_at.svg
|
reference = typ_at.svg
|
||||||
refresh = typ_refresh.svg
|
refresh = typ_refresh.svg
|
||||||
@@ -74,3 +78,8 @@ status_stats = typ_chart-bar-grey.svg
|
|||||||
status_time = typ_stopwatch-grey.svg
|
status_time = typ_stopwatch-grey.svg
|
||||||
sticky-off = typ_pin-outline.svg
|
sticky-off = typ_pin-outline.svg
|
||||||
sticky-on = typ_pin.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_h3 = mixed_heading3.svg
|
||||||
doc_h4 = mixed_heading4.svg
|
doc_h4 = mixed_heading4.svg
|
||||||
done = typ_input-checked.svg
|
done = typ_input-checked.svg
|
||||||
|
down = typ_chevron-down.svg
|
||||||
edit = typ_pencil.svg
|
edit = typ_pencil.svg
|
||||||
forward = typ_chevron-right.svg
|
forward = typ_chevron-right.svg
|
||||||
hash = typ_hash.svg
|
hash = typ_hash.svg
|
||||||
maximise = typ_arrow-maximise.svg
|
maximise = typ_arrow-maximise.svg
|
||||||
|
menu = typ_th-menu.svg
|
||||||
minimise = typ_arrow-minimise.svg
|
minimise = typ_arrow-minimise.svg
|
||||||
proj_chapter = mixed_document-chapter.svg
|
proj_chapter = mixed_document-chapter.svg
|
||||||
|
proj_details = typ_th-list-grey.svg
|
||||||
proj_document = typ_document-text.svg
|
proj_document = typ_document-text.svg
|
||||||
proj_folder = typ_folder.svg
|
proj_folder = typ_folder.svg
|
||||||
proj_note = mixed_document-note.svg
|
proj_note = mixed_document-note.svg
|
||||||
proj_scene = mixed_document-scene.svg
|
proj_scene = mixed_document-scene.svg
|
||||||
|
proj_stats = typ_chart-bar-grey.svg
|
||||||
proj_title = mixed_document-title.svg
|
proj_title = mixed_document-title.svg
|
||||||
reference = typ_at.svg
|
reference = typ_at.svg
|
||||||
refresh = typ_refresh.svg
|
refresh = typ_refresh.svg
|
||||||
@@ -74,3 +78,8 @@ status_stats = typ_chart-bar-grey.svg
|
|||||||
status_time = typ_stopwatch-grey.svg
|
status_time = typ_stopwatch-grey.svg
|
||||||
sticky-off = typ_pin-outline.svg
|
sticky-off = typ_pin-outline.svg
|
||||||
sticky-on = typ_pin.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>
|
<html>
|
||||||
<body>
|
<body>
|
||||||
|
|
||||||
<h2>Release Notes for 1.6</h2>
|
<h2>Release Notes for 1.7 Beta 1</h2>
|
||||||
<p><i>Released on 20 February 2022</i></p>
|
<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
|
<p>This is a beta release of the next release version, and is intended for testing purposes. Please
|
||||||
improvements and tweaks based on user requests. There are also a number of changes under the hood
|
be careful when using this version on live writing projects, and make sure you take frequent
|
||||||
to improve the structure and performance of novelWriter.</p>
|
backups.</p>
|
||||||
<p><u>Some key improvements to the user interface are:</u></p>
|
<p>Please check the changelog for an overview of changes. The full release notes will be added to
|
||||||
<p>✓ The max text width setting in Preferences now also applies to the document viewer, and
|
the final release.</p>
|
||||||
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><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
|
<p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
|
||||||
|
|
||||||
<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>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -184,6 +184,12 @@ def checkIntRange(value, first, last, default):
|
|||||||
return 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):
|
def checkIntTuple(value, valid, default):
|
||||||
"""Check that an int is an element of a tuple. If it isn't, return
|
"""Check that an int is an element of a tuple. If it isn't, return
|
||||||
the default value.
|
the default value.
|
||||||
@@ -245,6 +251,13 @@ def formatTime(tS):
|
|||||||
# String Functions
|
# 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):
|
def splitVersionNumber(value):
|
||||||
"""Split a version string on the form aa.bb.cc into major, minor
|
"""Split a version string on the form aa.bb.cc into major, minor
|
||||||
and patch, and computes an integer value aabbcc.
|
and patch, and computes an integer value aabbcc.
|
||||||
|
|||||||
@@ -978,12 +978,12 @@ class Config:
|
|||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
import enchant # noqa: F401
|
import enchant # noqa: F401
|
||||||
self.hasEnchant = True
|
except ImportError:
|
||||||
logger.debug("Checking package 'pyenchant': OK")
|
|
||||||
except Exception:
|
|
||||||
self.hasEnchant = False
|
self.hasEnchant = False
|
||||||
logger.debug("Checking package 'pyenchant': Missing")
|
logger.debug("Checking package 'pyenchant': Missing")
|
||||||
|
else:
|
||||||
|
self.hasEnchant = True
|
||||||
|
logger.debug("Checking package 'pyenchant': OK")
|
||||||
return
|
return
|
||||||
|
|
||||||
# END Class Config
|
# 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 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):
|
def trConst(tString):
|
||||||
@@ -34,7 +34,7 @@ def trConst(tString):
|
|||||||
return QCoreApplication.translate("Constant", tString)
|
return QCoreApplication.translate("Constant", tString)
|
||||||
|
|
||||||
|
|
||||||
class nwConst():
|
class nwConst:
|
||||||
|
|
||||||
# Date and Time Formats
|
# Date and Time Formats
|
||||||
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
|
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
|
||||||
@@ -42,32 +42,13 @@ class nwConst():
|
|||||||
FMT_DSTAMP = "%Y-%m-%d" # Date only format
|
FMT_DSTAMP = "%Y-%m-%d" # Date only format
|
||||||
|
|
||||||
# Various Hard Limits
|
# Various Hard Limits
|
||||||
MAX_DEPTH = 30 # Maximum folder depth of a project
|
|
||||||
MAX_DOCSIZE = 5000000 # Maxium size of a single document
|
MAX_DOCSIZE = 5000000 # Maxium size of a single document
|
||||||
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
|
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
|
||||||
|
|
||||||
# END Class nwConst
|
# END Class nwConst
|
||||||
|
|
||||||
|
|
||||||
class nwLists():
|
class nwRegEx:
|
||||||
"""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():
|
|
||||||
|
|
||||||
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||||
FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
|
FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||||
@@ -76,7 +57,7 @@ class nwRegEx():
|
|||||||
# END Class nwRegEx
|
# END Class nwRegEx
|
||||||
|
|
||||||
|
|
||||||
class nwFiles():
|
class nwFiles:
|
||||||
|
|
||||||
PROJ_FILE = "nwProject.nwx"
|
PROJ_FILE = "nwProject.nwx"
|
||||||
PROJ_DICT = "wordlist.txt"
|
PROJ_DICT = "wordlist.txt"
|
||||||
@@ -126,7 +107,7 @@ class nwKeyWords:
|
|||||||
# END Class nwKeyWords
|
# END Class nwKeyWords
|
||||||
|
|
||||||
|
|
||||||
class nwLabels():
|
class nwLabels:
|
||||||
|
|
||||||
CLASS_NAME = {
|
CLASS_NAME = {
|
||||||
nwItemClass.NO_CLASS: QT_TRANSLATE_NOOP("Constant", "None"),
|
nwItemClass.NO_CLASS: QT_TRANSLATE_NOOP("Constant", "None"),
|
||||||
@@ -204,7 +185,7 @@ class nwLabels():
|
|||||||
# END Class nwLabels
|
# END Class nwLabels
|
||||||
|
|
||||||
|
|
||||||
class nwQuotes():
|
class nwQuotes:
|
||||||
"""Allowed quotation marks.
|
"""Allowed quotation marks.
|
||||||
Source: https://en.wikipedia.org/wiki/Quotation_mark
|
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.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.project import NWProject
|
||||||
from novelwriter.core.spellcheck import NWSpellEnchant
|
from novelwriter.core.spellcheck import NWSpellEnchant
|
||||||
from novelwriter.core.tohtml import ToHtml
|
from novelwriter.core.tohtml import ToHtml
|
||||||
@@ -30,7 +30,6 @@ from novelwriter.core.tomd import ToMarkdown
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"countWords",
|
"countWords",
|
||||||
"NWDoc",
|
"NWDoc",
|
||||||
"NWIndex",
|
|
||||||
"NWProject",
|
"NWProject",
|
||||||
"NWSpellEnchant",
|
"NWSpellEnchant",
|
||||||
"ToHtml",
|
"ToHtml",
|
||||||
|
|||||||
@@ -52,7 +52,7 @@ class NWDoc():
|
|||||||
self._docHandle = theHandle
|
self._docHandle = theHandle
|
||||||
|
|
||||||
if self._docHandle is not None:
|
if self._docHandle is not None:
|
||||||
self._theItem = self.theProject.projTree[theHandle]
|
self._theItem = self.theProject.tree[theHandle]
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ from lxml import etree
|
|||||||
|
|
||||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||||
from novelwriter.common import (
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -45,6 +45,7 @@ class NWItem():
|
|||||||
self._name = ""
|
self._name = ""
|
||||||
self._handle = None
|
self._handle = None
|
||||||
self._parent = None
|
self._parent = None
|
||||||
|
self._root = None
|
||||||
self._order = 0
|
self._order = 0
|
||||||
self._type = nwItemType.NO_TYPE
|
self._type = nwItemType.NO_TYPE
|
||||||
self._class = nwItemClass.NO_CLASS
|
self._class = nwItemClass.NO_CLASS
|
||||||
@@ -85,6 +86,10 @@ class NWItem():
|
|||||||
def itemParent(self):
|
def itemParent(self):
|
||||||
return self._parent
|
return self._parent
|
||||||
|
|
||||||
|
@property
|
||||||
|
def itemRoot(self):
|
||||||
|
return self._root
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def itemOrder(self):
|
def itemOrder(self):
|
||||||
return self._order
|
return self._order
|
||||||
@@ -147,6 +152,7 @@ class NWItem():
|
|||||||
itemAttrib = {}
|
itemAttrib = {}
|
||||||
itemAttrib["handle"] = str(self._handle)
|
itemAttrib["handle"] = str(self._handle)
|
||||||
itemAttrib["parent"] = str(self._parent)
|
itemAttrib["parent"] = str(self._parent)
|
||||||
|
itemAttrib["root"] = str(self._root)
|
||||||
itemAttrib["order"] = str(self._order)
|
itemAttrib["order"] = str(self._order)
|
||||||
itemAttrib["type"] = str(self._type.name)
|
itemAttrib["type"] = str(self._type.name)
|
||||||
itemAttrib["class"] = str(self._class.name)
|
itemAttrib["class"] = str(self._class.name)
|
||||||
@@ -154,19 +160,18 @@ class NWItem():
|
|||||||
itemAttrib["layout"] = str(self._layout.name)
|
itemAttrib["layout"] = str(self._layout.name)
|
||||||
|
|
||||||
metaAttrib = {}
|
metaAttrib = {}
|
||||||
|
metaAttrib["expanded"] = str(self._expanded)
|
||||||
if self._type == nwItemType.FILE:
|
if self._type == nwItemType.FILE:
|
||||||
metaAttrib["charCount"] = str(self._charCount)
|
metaAttrib["charCount"] = str(self._charCount)
|
||||||
metaAttrib["wordCount"] = str(self._wordCount)
|
metaAttrib["wordCount"] = str(self._wordCount)
|
||||||
metaAttrib["paraCount"] = str(self._paraCount)
|
metaAttrib["paraCount"] = str(self._paraCount)
|
||||||
metaAttrib["cursorPos"] = str(self._cursorPos)
|
metaAttrib["cursorPos"] = str(self._cursorPos)
|
||||||
else:
|
|
||||||
metaAttrib["expanded"] = str(self._expanded)
|
|
||||||
|
|
||||||
nameAttrib = {}
|
nameAttrib = {}
|
||||||
nameAttrib["status"] = str(self._status)
|
nameAttrib["status"] = str(self._status)
|
||||||
nameAttrib["import"] = str(self._import)
|
nameAttrib["import"] = str(self._import)
|
||||||
if self._type == nwItemType.FILE:
|
if self._type == nwItemType.FILE:
|
||||||
nameAttrib["exported"] = str(self._exported)
|
nameAttrib["exported"] = str(self._exported)
|
||||||
|
|
||||||
xPack = etree.SubElement(xParent, "item", attrib=itemAttrib)
|
xPack = etree.SubElement(xParent, "item", attrib=itemAttrib)
|
||||||
self._subPack(xPack, "meta", attrib=metaAttrib)
|
self._subPack(xPack, "meta", attrib=metaAttrib)
|
||||||
@@ -188,6 +193,7 @@ class NWItem():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
self.setParent(xItem.attrib.get("parent", None))
|
self.setParent(xItem.attrib.get("parent", None))
|
||||||
|
self.setRoot(xItem.attrib.get("root", None))
|
||||||
self.setOrder(xItem.attrib.get("order", 0))
|
self.setOrder(xItem.attrib.get("order", 0))
|
||||||
self.setType(xItem.attrib.get("type", nwItemType.NO_TYPE))
|
self.setType(xItem.attrib.get("type", nwItemType.NO_TYPE))
|
||||||
self.setClass(xItem.attrib.get("class", nwItemClass.NO_CLASS))
|
self.setClass(xItem.attrib.get("class", nwItemClass.NO_CLASS))
|
||||||
@@ -233,6 +239,17 @@ class NWItem():
|
|||||||
# version of novelWriter that doesn't know the tag
|
# version of novelWriter that doesn't know the tag
|
||||||
logger.error("Unknown tag '%s'", xValue.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
|
return True
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -249,7 +266,7 @@ class NWItem():
|
|||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Methods
|
# Lookup Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def describeMe(self, hLevel=None):
|
def describeMe(self, hLevel=None):
|
||||||
@@ -275,168 +292,226 @@ class NWItem():
|
|||||||
|
|
||||||
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
|
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):
|
def getImportStatus(self):
|
||||||
"""Return the relevant importance or status label and icon for
|
"""Return the relevant importance or status label and icon for
|
||||||
the current item based on its class.
|
the current item based on its class.
|
||||||
"""
|
"""
|
||||||
if self._class in nwLists.CLS_NOVEL:
|
if self.isNovelLike():
|
||||||
stName = self.theProject.statusItems.checkEntry(self._status)
|
stName = self.theProject.statusItems.name(self._status)
|
||||||
stIcon = self.theProject.statusItems.getIcon(stName)
|
stIcon = self.theProject.statusItems.icon(self._status)
|
||||||
else:
|
else:
|
||||||
stName = self.theProject.importItems.checkEntry(self._import)
|
stName = self.theProject.importItems.name(self._import)
|
||||||
stIcon = self.theProject.importItems.getIcon(stName)
|
stIcon = self.theProject.importItems.icon(self._import)
|
||||||
return stName, stIcon
|
return stName, stIcon
|
||||||
|
|
||||||
def setImportStatus(self, theLabel):
|
##
|
||||||
|
# Special Setters
|
||||||
|
##
|
||||||
|
|
||||||
|
def setImportStatus(self, value):
|
||||||
"""Update the importance or status value based on class. This is
|
"""Update the importance or status value based on class. This is
|
||||||
a wrapper setter for setStatus and setImport.
|
a wrapper setter for setStatus and setImport.
|
||||||
"""
|
"""
|
||||||
if self._class in nwLists.CLS_NOVEL:
|
if self.isNovelLike():
|
||||||
self.setStatus(theLabel)
|
self.setStatus(value)
|
||||||
else:
|
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
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Set Item Values
|
# Set Item Values
|
||||||
##
|
##
|
||||||
|
|
||||||
def setName(self, theName):
|
def setName(self, name):
|
||||||
"""Set the item name.
|
"""Set the item name.
|
||||||
"""
|
"""
|
||||||
if isinstance(theName, str):
|
if isinstance(name, str):
|
||||||
self._name = theName.strip()
|
self._name = simplified(name)
|
||||||
else:
|
else:
|
||||||
self._name = ""
|
self._name = ""
|
||||||
return
|
return
|
||||||
|
|
||||||
def setHandle(self, theHandle):
|
def setHandle(self, handle):
|
||||||
"""Set the item handle, and ensure it is valid.
|
"""Set the item handle, and ensure it is valid.
|
||||||
"""
|
"""
|
||||||
if isHandle(theHandle):
|
if isHandle(handle):
|
||||||
self._handle = theHandle
|
self._handle = handle
|
||||||
else:
|
else:
|
||||||
self._handle = None
|
self._handle = None
|
||||||
return
|
return
|
||||||
|
|
||||||
def setParent(self, theParent):
|
def setParent(self, handle):
|
||||||
"""Set the parent handle, and ensure it is valid.
|
"""Set the parent handle, and ensure it is valid.
|
||||||
"""
|
"""
|
||||||
if theParent is None:
|
if handle is None:
|
||||||
self._parent = None
|
self._parent = None
|
||||||
elif isHandle(theParent):
|
elif isHandle(handle):
|
||||||
self._parent = theParent
|
self._parent = handle
|
||||||
else:
|
else:
|
||||||
self._parent = None
|
self._parent = None
|
||||||
return
|
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
|
"""Set the item order, and ensure that it is valid. This value
|
||||||
is purely a meta value, and not actually used by novelWriter at
|
is purely a meta value, and not actually used by novelWriter at
|
||||||
the moment.
|
the moment.
|
||||||
"""
|
"""
|
||||||
self._order = checkInt(theOrder, 0)
|
self._order = checkInt(order, 0)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setType(self, theType):
|
def setType(self, value):
|
||||||
"""Set the item type from either a proper nwItemType, or set it
|
"""Set the item type from either a proper nwItemType, or set it
|
||||||
from a string representing an nwItemType.
|
from a string representing an nwItemType.
|
||||||
"""
|
"""
|
||||||
if isinstance(theType, nwItemType):
|
if isinstance(value, nwItemType):
|
||||||
self._type = theType
|
self._type = value
|
||||||
elif isItemType(theType):
|
elif isItemType(value):
|
||||||
self._type = nwItemType[theType]
|
self._type = nwItemType[value]
|
||||||
|
elif value == "TRASH":
|
||||||
|
self._type = nwItemType.ROOT
|
||||||
else:
|
else:
|
||||||
logger.error("Unrecognised item type '%s'", theType)
|
logger.error("Unrecognised item type '%s'", value)
|
||||||
self._type = nwItemType.NO_TYPE
|
self._type = nwItemType.NO_TYPE
|
||||||
return
|
return
|
||||||
|
|
||||||
def setClass(self, theClass):
|
def setClass(self, value):
|
||||||
"""Set the item class from either a proper nwItemClass, or set
|
"""Set the item class from either a proper nwItemClass, or set
|
||||||
it from a string representing an nwItemClass.
|
it from a string representing an nwItemClass.
|
||||||
"""
|
"""
|
||||||
if isinstance(theClass, nwItemClass):
|
if isinstance(value, nwItemClass):
|
||||||
self._class = theClass
|
self._class = value
|
||||||
elif isItemClass(theClass):
|
elif isItemClass(value):
|
||||||
self._class = nwItemClass[theClass]
|
self._class = nwItemClass[value]
|
||||||
else:
|
else:
|
||||||
logger.error("Unrecognised item class '%s'", theClass)
|
logger.error("Unrecognised item class '%s'", value)
|
||||||
self._class = nwItemClass.NO_CLASS
|
self._class = nwItemClass.NO_CLASS
|
||||||
return
|
return
|
||||||
|
|
||||||
def setLayout(self, theLayout):
|
def setLayout(self, value):
|
||||||
"""Set the item layout from either a proper nwItemLayout, or set
|
"""Set the item layout from either a proper nwItemLayout, or set
|
||||||
it from a string representing an nwItemLayout.
|
it from a string representing an nwItemLayout.
|
||||||
"""
|
"""
|
||||||
if isinstance(theLayout, nwItemLayout):
|
if isinstance(value, nwItemLayout):
|
||||||
self._layout = theLayout
|
self._layout = value
|
||||||
elif isItemLayout(theLayout):
|
elif isItemLayout(value):
|
||||||
self._layout = nwItemLayout[theLayout]
|
self._layout = nwItemLayout[value]
|
||||||
elif theLayout in nwLists.DEP_LAYOUT:
|
elif value in ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE"):
|
||||||
self._layout = nwItemLayout.DOCUMENT
|
self._layout = nwItemLayout.DOCUMENT
|
||||||
else:
|
else:
|
||||||
logger.error("Unrecognised item layout '%s'", theLayout)
|
logger.error("Unrecognised item layout '%s'", value)
|
||||||
self._layout = nwItemLayout.NO_LAYOUT
|
self._layout = nwItemLayout.NO_LAYOUT
|
||||||
return
|
return
|
||||||
|
|
||||||
def setStatus(self, theStatus):
|
def setStatus(self, value):
|
||||||
"""Set the item status by looking it up in the valid status
|
"""Set the item status by looking it up in the valid status
|
||||||
items of the current project.
|
items of the current project.
|
||||||
"""
|
"""
|
||||||
self._status = self.theProject.statusItems.checkEntry(theStatus)
|
self._status = self.theProject.statusItems.check(value)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setImport(self, theImport):
|
def setImport(self, value):
|
||||||
"""Set the item importance by looking it up in the valid import
|
"""Set the item importance by looking it up in the valid import
|
||||||
items of the current project.
|
items of the current project.
|
||||||
"""
|
"""
|
||||||
self._import = self.theProject.importItems.checkEntry(theImport)
|
self._import = self.theProject.importItems.check(value)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setExpanded(self, expState):
|
def setExpanded(self, state):
|
||||||
"""Set the expanded status of an item in the project tree.
|
"""Set the expanded status of an item in the project tree.
|
||||||
"""
|
"""
|
||||||
if isinstance(expState, str):
|
if isinstance(state, str):
|
||||||
self._expanded = (expState == str(True))
|
self._expanded = (state == str(True))
|
||||||
else:
|
else:
|
||||||
self._expanded = (expState is True)
|
self._expanded = (state is True)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setExported(self, expState):
|
def setExported(self, state):
|
||||||
"""Set the export flag.
|
"""Set the export flag.
|
||||||
"""
|
"""
|
||||||
if isinstance(expState, str):
|
if isinstance(state, str):
|
||||||
self._exported = (expState == str(True))
|
self._exported = (state == str(True))
|
||||||
else:
|
else:
|
||||||
self._exported = (expState is True)
|
self._exported = (state is True)
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Set Document Meta Data
|
# Set Document Meta Data
|
||||||
##
|
##
|
||||||
|
|
||||||
def setCharCount(self, theCount):
|
def setCharCount(self, count):
|
||||||
"""Set the character count, and ensure that it is an integer.
|
"""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
|
return
|
||||||
|
|
||||||
def setWordCount(self, theCount):
|
def setWordCount(self, count):
|
||||||
"""Set the word count, and ensure that it is an integer.
|
"""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
|
return
|
||||||
|
|
||||||
def setParaCount(self, theCount):
|
def setParaCount(self, count):
|
||||||
"""Set the paragraph count, and ensure that it is an integer.
|
"""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
|
return
|
||||||
|
|
||||||
def setCursorPos(self, thePosition):
|
def setCursorPos(self, position):
|
||||||
"""Set the cursor position, and ensure that it is an integer.
|
"""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
|
return
|
||||||
|
|
||||||
def saveInitialCount(self):
|
def saveInitialCount(self):
|
||||||
|
|||||||
@@ -37,6 +37,7 @@ from PyQt5.QtCore import QCoreApplication
|
|||||||
|
|
||||||
from novelwriter.core.tree import NWTree
|
from novelwriter.core.tree import NWTree
|
||||||
from novelwriter.core.item import NWItem
|
from novelwriter.core.item import NWItem
|
||||||
|
from novelwriter.core.index import NWIndex
|
||||||
from novelwriter.core.status import NWStatus
|
from novelwriter.core.status import NWStatus
|
||||||
from novelwriter.core.options import OptionState
|
from novelwriter.core.options import OptionState
|
||||||
from novelwriter.core.document import NWDoc
|
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.error import logException
|
||||||
from novelwriter.common import (
|
from novelwriter.common import (
|
||||||
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NWProject():
|
class NWProject():
|
||||||
|
|
||||||
FILE_VERSION = "1.4"
|
FILE_VERSION = "1.4" # The current project file format version
|
||||||
|
|
||||||
def __init__(self, theParent):
|
def __init__(self, theParent):
|
||||||
|
|
||||||
@@ -62,9 +63,10 @@ class NWProject():
|
|||||||
self.mainConf = novelwriter.CONFIG
|
self.mainConf = novelwriter.CONFIG
|
||||||
|
|
||||||
# Core Elements
|
# Core Elements
|
||||||
self.optState = OptionState(self) # Project-specific GUI options
|
self._optState = OptionState(self) # Project-specific GUI options
|
||||||
self.projTree = NWTree(self) # The project tree
|
self._projTree = NWTree(self) # The project tree
|
||||||
self.langData = {} # Localisation data
|
self._projIndex = NWIndex(self) # The projecty index
|
||||||
|
self._langData = {} # Localisation data
|
||||||
|
|
||||||
# Project Status
|
# Project Status
|
||||||
self.projOpened = 0 # The time stamp of when the project file was opened
|
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
|
self.projFiles = [] # A list of all files in the content folder on load
|
||||||
|
|
||||||
# Project Meta
|
# Project Meta
|
||||||
self.projName = "" # Project name (working title)
|
self.projName = "" # Project name
|
||||||
self.bookTitle = "" # The final title; should only be used for exports
|
self.bookTitle = "" # The final title; should only be used for exports
|
||||||
self.bookAuthors = [] # A list of book authors
|
self.bookAuthors = [] # A list of book authors
|
||||||
|
|
||||||
@@ -116,63 +118,70 @@ class NWProject():
|
|||||||
|
|
||||||
return
|
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
|
# Item Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def newRoot(self, rootName, rootClass):
|
def newRoot(self, itemClass, label=None):
|
||||||
"""Add a new root item. These items are unique, except for item class
|
"""Add a new root item. If label is None, use the class label.
|
||||||
CUSTOM, and always have parent handle set to None.
|
|
||||||
"""
|
"""
|
||||||
if not self.projTree.checkRootUnique(rootClass):
|
if label is None:
|
||||||
self.theParent.makeAlert(self.tr("Duplicate root item detected."), nwAlert.ERROR)
|
label = trConst(nwLabels.CLASS_NAME[itemClass])
|
||||||
return None
|
|
||||||
|
|
||||||
newItem = NWItem(self)
|
newItem = NWItem(self)
|
||||||
newItem.setName(rootName)
|
newItem.setName(label)
|
||||||
newItem.setType(nwItemType.ROOT)
|
newItem.setType(nwItemType.ROOT)
|
||||||
newItem.setClass(rootClass)
|
newItem.setClass(itemClass)
|
||||||
newItem.setStatus(0)
|
self._projTree.append(None, None, newItem)
|
||||||
self.projTree.append(None, None, newItem)
|
self._projTree.updateItemData(newItem.itemHandle)
|
||||||
return newItem.itemHandle
|
return newItem.itemHandle
|
||||||
|
|
||||||
def newFolder(self, folderName, folderClass, pHandle):
|
def newFolder(self, label, pHandle):
|
||||||
"""Add a new folder with a given name and class and parent item.
|
"""Add a new folder with a given label and parent item.
|
||||||
"""
|
"""
|
||||||
newItem = NWItem(self)
|
newItem = NWItem(self)
|
||||||
newItem.setName(folderName)
|
newItem.setName(label)
|
||||||
newItem.setType(nwItemType.FOLDER)
|
newItem.setType(nwItemType.FOLDER)
|
||||||
newItem.setClass(folderClass)
|
self._projTree.append(None, pHandle, newItem)
|
||||||
newItem.setStatus(0)
|
self._projTree.updateItemData(newItem.itemHandle)
|
||||||
self.projTree.append(None, pHandle, newItem)
|
|
||||||
return newItem.itemHandle
|
return newItem.itemHandle
|
||||||
|
|
||||||
def newFile(self, fileName, fileClass, pHandle):
|
def newFile(self, label, pHandle):
|
||||||
"""Add a new file with a given name and class, and set a layout
|
"""Add a new file with a given label and parent item.
|
||||||
based on the class. DOCUMENT for NOVEL, otherwise NOTE.
|
|
||||||
"""
|
"""
|
||||||
newItem = NWItem(self)
|
newItem = NWItem(self)
|
||||||
newItem.setName(fileName)
|
newItem.setName(label)
|
||||||
newItem.setType(nwItemType.FILE)
|
newItem.setType(nwItemType.FILE)
|
||||||
if fileClass == nwItemClass.NOVEL:
|
self._projTree.append(None, pHandle, newItem)
|
||||||
newItem.setLayout(nwItemLayout.DOCUMENT)
|
self._projTree.updateItemData(newItem.itemHandle)
|
||||||
else:
|
|
||||||
newItem.setLayout(nwItemLayout.NOTE)
|
|
||||||
newItem.setClass(fileClass)
|
|
||||||
newItem.setStatus(0)
|
|
||||||
self.projTree.append(None, pHandle, newItem)
|
|
||||||
return newItem.itemHandle
|
return newItem.itemHandle
|
||||||
|
|
||||||
def trashFolder(self):
|
def trashFolder(self):
|
||||||
"""Add the special trash root folder to the project.
|
"""Add the special trash root folder to the project.
|
||||||
"""
|
"""
|
||||||
trashHandle = self.projTree.trashRoot()
|
trashHandle = self._projTree.trashRoot()
|
||||||
if trashHandle is None:
|
if trashHandle is None:
|
||||||
newItem = NWItem(self)
|
newItem = NWItem(self)
|
||||||
newItem.setName(trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH]))
|
newItem.setName(trConst(nwLabels.CLASS_NAME[nwItemClass.TRASH]))
|
||||||
newItem.setType(nwItemType.TRASH)
|
newItem.setType(nwItemType.ROOT)
|
||||||
newItem.setClass(nwItemClass.TRASH)
|
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 newItem.itemHandle
|
||||||
|
|
||||||
return trashHandle
|
return trashHandle
|
||||||
@@ -193,7 +202,7 @@ class NWProject():
|
|||||||
self.autoCount = 0
|
self.autoCount = 0
|
||||||
|
|
||||||
# Project Tree
|
# Project Tree
|
||||||
self.projTree.clear()
|
self._projTree.clear()
|
||||||
|
|
||||||
# Project Settings
|
# Project Settings
|
||||||
self.projPath = None
|
self.projPath = None
|
||||||
@@ -218,16 +227,16 @@ class NWProject():
|
|||||||
}
|
}
|
||||||
self.spellCheck = False
|
self.spellCheck = False
|
||||||
self.autoOutline = True
|
self.autoOutline = True
|
||||||
self.statusItems = NWStatus()
|
self.statusItems = NWStatus(NWStatus.STATUS)
|
||||||
self.statusItems.addEntry(self.tr("New"), (100, 100, 100))
|
self.statusItems.write(None, self.tr("New"), (100, 100, 100))
|
||||||
self.statusItems.addEntry(self.tr("Note"), (200, 50, 0))
|
self.statusItems.write(None, self.tr("Note"), (200, 50, 0))
|
||||||
self.statusItems.addEntry(self.tr("Draft"), (200, 150, 0))
|
self.statusItems.write(None, self.tr("Draft"), (200, 150, 0))
|
||||||
self.statusItems.addEntry(self.tr("Finished"), (50, 200, 0))
|
self.statusItems.write(None, self.tr("Finished"), (50, 200, 0))
|
||||||
self.importItems = NWStatus()
|
self.importItems = NWStatus(NWStatus.IMPORT)
|
||||||
self.importItems.addEntry(self.tr("New"), (100, 100, 100))
|
self.importItems.write(None, self.tr("New"), (100, 100, 100))
|
||||||
self.importItems.addEntry(self.tr("Minor"), (200, 50, 0))
|
self.importItems.write(None, self.tr("Minor"), (200, 50, 0))
|
||||||
self.importItems.addEntry(self.tr("Major"), (200, 150, 0))
|
self.importItems.write(None, self.tr("Major"), (200, 150, 0))
|
||||||
self.importItems.addEntry(self.tr("Main"), (50, 200, 0))
|
self.importItems.write(None, self.tr("Main"), (50, 200, 0))
|
||||||
self.lastEdited = None
|
self.lastEdited = None
|
||||||
self.lastViewed = None
|
self.lastViewed = None
|
||||||
self.lastWCount = 0
|
self.lastWCount = 0
|
||||||
@@ -267,6 +276,7 @@ class NWProject():
|
|||||||
logger.error("No project path set for the new project")
|
logger.error("No project path set for the new project")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
self.clearProject()
|
||||||
if not self.setProjectPath(projPath, newProject=True):
|
if not self.setProjectPath(projPath, newProject=True):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -274,86 +284,88 @@ class NWProject():
|
|||||||
self.setBookTitle(projTitle)
|
self.setBookTitle(projTitle)
|
||||||
self.setBookAuthors(projAuthors)
|
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)
|
titlePage = "#! %s\n\n" % (self.bookTitle if self.bookTitle else self.projName)
|
||||||
if self.bookAuthors:
|
if self.bookAuthors:
|
||||||
titlePage = "%s>> %s %s <<\n" % (titlePage, self.tr("By"), self.getAuthors())
|
titlePage = "%s>> %s %s <<\n" % (titlePage, self.tr("By"), self.getAuthors())
|
||||||
|
|
||||||
|
aDoc = NWDoc(self, hTitlePage)
|
||||||
|
aDoc.writeDocument(titlePage)
|
||||||
|
|
||||||
if popMinimal:
|
if popMinimal:
|
||||||
# Creating a minimal project with a few root folders and a
|
# Creating a minimal project with a few root folders and a
|
||||||
# single chapter folder with a single file.
|
# single chapter with a single scene.
|
||||||
xHandle = {}
|
hChapter = self.newFile(self.tr("New Chapter"), hNovelRoot)
|
||||||
xHandle[1] = self.newRoot(self.tr("Novel"), nwItemClass.NOVEL)
|
aDoc = NWDoc(self, hChapter)
|
||||||
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])
|
|
||||||
aDoc.writeDocument("## %s\n\n" % self.tr("New Chapter"))
|
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"))
|
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:
|
elif popCustom:
|
||||||
# Create a project structure based on selected root folders
|
# Create a project structure based on selected root folders
|
||||||
# and a number of chapters and scenes selected in the
|
# and a number of chapters and scenes selected in the
|
||||||
# wizard's custom page.
|
# 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
|
# Create chapters and scenes
|
||||||
numChapters = projData.get("numChapters", 0)
|
numChapters = projData.get("numChapters", 0)
|
||||||
numScenes = projData.get("numScenes", 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
|
# Create chapters
|
||||||
if numChapters > 0:
|
if numChapters > 0:
|
||||||
for ch in range(numChapters):
|
for ch in range(numChapters):
|
||||||
chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}")
|
chTitle = self.tr("Chapter {0}").format(f"{ch+1:d}")
|
||||||
pHandle = nHandle
|
cHandle = self.newFile(chTitle, hNovelRoot)
|
||||||
if chFolders:
|
|
||||||
pHandle = self.newFolder(chTitle, nwItemClass.NOVEL, nHandle)
|
|
||||||
|
|
||||||
cHandle = self.newFile(chTitle, nwItemClass.NOVEL, pHandle)
|
|
||||||
self.projTree.setFileItemLayout(cHandle, nwItemLayout.DOCUMENT)
|
|
||||||
|
|
||||||
aDoc = NWDoc(self, cHandle)
|
aDoc = NWDoc(self, cHandle)
|
||||||
aDoc.writeDocument("## %s\n\n" % chTitle)
|
aDoc.writeDocument(f"## {chTitle}\n\n% Synopsis: {chSynop}\n\n")
|
||||||
|
|
||||||
# Create chapter scenes
|
# Create chapter scenes
|
||||||
if numScenes > 0:
|
if numScenes > 0:
|
||||||
for sc in range(numScenes):
|
for sc in range(numScenes):
|
||||||
scTitle = self.tr("Scene {0}").format(f"{ch+1:d}.{sc+1:d}")
|
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 = NWDoc(self, sHandle)
|
||||||
aDoc.writeDocument("### %s\n\n" % scTitle)
|
aDoc.writeDocument(f"### {scTitle}\n\n% Synopsis: {scSynop}\n\n")
|
||||||
|
|
||||||
# Create scenes (no chapters)
|
# Create scenes (no chapters)
|
||||||
elif numScenes > 0:
|
elif numScenes > 0:
|
||||||
for sc in range(numScenes):
|
for sc in range(numScenes):
|
||||||
scTitle = self.tr("Scene {0}").format(f"{sc+1:d}")
|
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 = 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
|
# Finalise
|
||||||
if popCustom or popMinimal:
|
if popCustom or popMinimal:
|
||||||
@@ -480,8 +492,9 @@ class NWProject():
|
|||||||
# documents and one for project notes. Introduced in
|
# documents and one for project notes. Introduced in
|
||||||
# version 1.5.
|
# version 1.5.
|
||||||
# 1.4 : Introduces a more compact format for storing items. All
|
# 1.4 : Introduces a more compact format for storing items. All
|
||||||
# settings aside from name are now attributes. Introduced
|
# settings aside from name are now attributes. This format
|
||||||
# in version 1.7.
|
# 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"):
|
if fileVersion not in ("1.0", "1.1", "1.2", "1.3", "1.4"):
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
@@ -533,14 +546,16 @@ class NWProject():
|
|||||||
if xItem.text is None:
|
if xItem.text is None:
|
||||||
continue
|
continue
|
||||||
if xItem.tag == "name":
|
if xItem.tag == "name":
|
||||||
logger.verbose("Working Title: '%s'", xItem.text)
|
self.projName = checkString(simplified(xItem.text), "")
|
||||||
self.projName = xItem.text
|
logger.verbose("Working Title: '%s'", self.projName)
|
||||||
elif xItem.tag == "title":
|
elif xItem.tag == "title":
|
||||||
logger.verbose("Title is '%s'", xItem.text)
|
self.bookTitle = checkString(simplified(xItem.text), "")
|
||||||
self.bookTitle = xItem.text
|
logger.verbose("Title is '%s'", self.bookTitle)
|
||||||
elif xItem.tag == "author":
|
elif xItem.tag == "author":
|
||||||
logger.verbose("Author: '%s'", xItem.text)
|
author = checkString(simplified(xItem.text), "")
|
||||||
self.bookAuthors.append(xItem.text)
|
if author:
|
||||||
|
self.bookAuthors.append(author)
|
||||||
|
logger.verbose("Author: '%s'", author)
|
||||||
elif xItem.tag == "saveCount":
|
elif xItem.tag == "saveCount":
|
||||||
self.saveCount = checkInt(xItem.text, 0)
|
self.saveCount = checkInt(xItem.text, 0)
|
||||||
elif xItem.tag == "autoCount":
|
elif xItem.tag == "autoCount":
|
||||||
@@ -591,9 +606,9 @@ class NWProject():
|
|||||||
|
|
||||||
elif xChild.tag == "content":
|
elif xChild.tag == "content":
|
||||||
logger.debug("Found project 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
|
# Sort out old file locations
|
||||||
if legacyList:
|
if legacyList:
|
||||||
@@ -610,7 +625,13 @@ class NWProject():
|
|||||||
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
|
self.mainConf.updateRecentCache(self.projPath, self.projName, self.lastWCount, time())
|
||||||
self.mainConf.saveRecentCache()
|
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._scanProjectFolder()
|
||||||
self._loadProjectLocalisation()
|
self._loadProjectLocalisation()
|
||||||
@@ -621,6 +642,7 @@ class NWProject():
|
|||||||
|
|
||||||
self._writeLockFile()
|
self._writeLockFile()
|
||||||
self.setProjectChanged(False)
|
self.setProjectChanged(False)
|
||||||
|
self.theParent.setStatus(self.tr("Opened Project: {0}").format(self.projName))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -687,6 +709,8 @@ class NWProject():
|
|||||||
if len(aKey) > 0:
|
if len(aKey) > 0:
|
||||||
self._packProjectValue(xTitleFmt, aKey, aValue)
|
self._packProjectValue(xTitleFmt, aKey, aValue)
|
||||||
|
|
||||||
|
# Save Status/Importance
|
||||||
|
self.countStatus()
|
||||||
xStatus = etree.SubElement(xSettings, "status")
|
xStatus = etree.SubElement(xSettings, "status")
|
||||||
self.statusItems.packXML(xStatus)
|
self.statusItems.packXML(xStatus)
|
||||||
xStatus = etree.SubElement(xSettings, "importance")
|
xStatus = etree.SubElement(xSettings, "importance")
|
||||||
@@ -694,7 +718,7 @@ class NWProject():
|
|||||||
|
|
||||||
# Save Tree Content
|
# Save Tree Content
|
||||||
logger.debug("Writing project content")
|
logger.debug("Writing project content")
|
||||||
self.projTree.packXML(nwXML)
|
self._projTree.packXML(nwXML)
|
||||||
|
|
||||||
# Write the xml tree to file
|
# Write the xml tree to file
|
||||||
tempFile = os.path.join(self.projPath, self.projFile+"~")
|
tempFile = os.path.join(self.projPath, self.projFile+"~")
|
||||||
@@ -727,7 +751,7 @@ class NWProject():
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Save project GUI options
|
# Save project GUI options
|
||||||
self.optState.saveSettings()
|
self._optState.saveSettings()
|
||||||
|
|
||||||
# Update recent projects
|
# Update recent projects
|
||||||
self.mainConf.updateRecentCache(self.projPath, self.projName, self.currWCount, saveTime)
|
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.
|
"""Close the current project and clear all meta data.
|
||||||
"""
|
"""
|
||||||
logger.info("Closing project: %s", self.projPath)
|
logger.info("Closing project: %s", self.projPath)
|
||||||
self.optState.saveSettings()
|
self._optState.saveSettings()
|
||||||
self.projTree.writeToCFile()
|
self._projTree.writeToCFile()
|
||||||
self._appendSessionStats(idleTime)
|
self._appendSessionStats(idleTime)
|
||||||
self._clearLockFile()
|
self._clearLockFile()
|
||||||
self.clearProject()
|
self.clearProject()
|
||||||
@@ -950,17 +974,17 @@ class NWProject():
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def setProjectName(self, projName):
|
def setProjectName(self, projName):
|
||||||
"""Set the project name (working title), This is the the title
|
"""Set the project name, This is the the name used for backup
|
||||||
used for backup files etc.
|
files etc.
|
||||||
"""
|
"""
|
||||||
self.projName = projName.strip()
|
self.projName = simplified(projName)
|
||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setBookTitle(self, bookTitle):
|
def setBookTitle(self, bookTitle):
|
||||||
"""Set the book title, that is, the title to include in exports.
|
"""Set the book title, that is, the title to include in exports.
|
||||||
"""
|
"""
|
||||||
self.bookTitle = bookTitle.strip()
|
self.bookTitle = simplified(bookTitle)
|
||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -972,7 +996,7 @@ class NWProject():
|
|||||||
|
|
||||||
self.bookAuthors = []
|
self.bookAuthors = []
|
||||||
for bookAuthor in bookAuthors.splitlines():
|
for bookAuthor in bookAuthors.splitlines():
|
||||||
bookAuthor = bookAuthor.strip()
|
bookAuthor = simplified(bookAuthor)
|
||||||
if bookAuthor == "":
|
if bookAuthor == "":
|
||||||
continue
|
continue
|
||||||
self.bookAuthors.append(bookAuthor)
|
self.bookAuthors.append(bookAuthor)
|
||||||
@@ -1018,7 +1042,8 @@ class NWProject():
|
|||||||
if self.projSpell != theLang:
|
if self.projSpell != theLang:
|
||||||
self.projSpell = theLang
|
self.projSpell = theLang
|
||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
def setProjectLang(self, theLang):
|
def setProjectLang(self, theLang):
|
||||||
"""Set the project-specific language.
|
"""Set the project-specific language.
|
||||||
@@ -1043,9 +1068,9 @@ class NWProject():
|
|||||||
items in the GUI project tree. The user can rearrange the order
|
items in the GUI project tree. The user can rearrange the order
|
||||||
by drag-and-drop. Forwarded to the NWTree class.
|
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")
|
logger.warning("Sizes of new and old tree order do not match")
|
||||||
self.projTree.setOrder(newOrder)
|
self._projTree.setOrder(newOrder)
|
||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1065,34 +1090,22 @@ class NWProject():
|
|||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setStatusColours(self, newCols):
|
def setStatusColours(self, newCols, delCols):
|
||||||
"""Update the list of novel file status flags. Also iterate
|
"""Update the list of novel file status flags.
|
||||||
through the project and replace keys that have been renamed.
|
|
||||||
"""
|
"""
|
||||||
replaceMap = self.statusItems.setNewEntries(newCols)
|
return self._setStatusImport(newCols, delCols, self.statusItems)
|
||||||
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
|
|
||||||
|
|
||||||
def setImportColours(self, newCols):
|
def setImportColours(self, newCols, delCols):
|
||||||
"""Update the list of note file importance flags. Also iterate
|
"""Update the list of note file importance flags.
|
||||||
through the project and replace keys that have been renamed.
|
|
||||||
"""
|
"""
|
||||||
replaceMap = self.importItems.setNewEntries(newCols)
|
return self._setStatusImport(newCols, delCols, self.importItems)
|
||||||
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
|
|
||||||
|
|
||||||
def setAutoReplace(self, autoReplace):
|
def setAutoReplace(self, autoReplace):
|
||||||
"""Update the auto-replace dictionary.
|
"""Update the auto-replace dictionary.
|
||||||
"""
|
"""
|
||||||
self.autoReplace = autoReplace
|
self.autoReplace = {}
|
||||||
|
for key, entry in autoReplace.items():
|
||||||
|
self.autoReplace[key] = simplified(entry)
|
||||||
self.setProjectChanged(True)
|
self.setProjectChanged(True)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -1101,7 +1114,9 @@ class NWProject():
|
|||||||
"""
|
"""
|
||||||
for valKey, valEntry in titleFormat.items():
|
for valKey, valEntry in titleFormat.items():
|
||||||
if valKey in self.titleFormat:
|
if valKey in self.titleFormat:
|
||||||
self.titleFormat[valKey] = checkString(valEntry, self.titleFormat[valKey])
|
self.titleFormat[valKey] = checkString(
|
||||||
|
simplified(valEntry), self.titleFormat[valKey]
|
||||||
|
)
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def setProjectChanged(self, bValue):
|
def setProjectChanged(self, bValue):
|
||||||
@@ -1149,16 +1164,16 @@ class NWProject():
|
|||||||
capable of handling it.
|
capable of handling it.
|
||||||
"""
|
"""
|
||||||
sentItems = []
|
sentItems = []
|
||||||
iterItems = self.projTree.handles()
|
iterItems = self._projTree.handles()
|
||||||
n = 0
|
n = 0
|
||||||
nMax = min(len(iterItems), 10000)
|
nMax = min(len(iterItems), 10000)
|
||||||
while n < nMax:
|
while n < nMax:
|
||||||
tHandle = iterItems[n]
|
tHandle = iterItems[n]
|
||||||
tItem = self.projTree[tHandle]
|
tItem = self._projTree[tHandle]
|
||||||
n += 1
|
n += 1
|
||||||
if tItem is None:
|
if tItem is None:
|
||||||
# Technically a bug since treeOrder is built from the
|
# Technically a bug since treeOrder is built from the
|
||||||
# same data as projTree
|
# same data as _projTree
|
||||||
continue
|
continue
|
||||||
elif tItem.itemParent is None:
|
elif tItem.itemParent is None:
|
||||||
# Item is a root, or already been identified as an
|
# Item is a root, or already been identified as an
|
||||||
@@ -1189,7 +1204,7 @@ class NWProject():
|
|||||||
def updateWordCounts(self):
|
def updateWordCounts(self):
|
||||||
"""Update the total word count values.
|
"""Update the total word count values.
|
||||||
"""
|
"""
|
||||||
wcNovel, wcNotes = self.projTree.sumWords()
|
wcNovel, wcNotes = self._projTree.sumWords()
|
||||||
wcTotal = wcNovel + wcNotes
|
wcTotal = wcNovel + wcNotes
|
||||||
if wcTotal != self.currWCount:
|
if wcTotal != self.currWCount:
|
||||||
self.currNovelWC = wcNovel
|
self.currNovelWC = wcNovel
|
||||||
@@ -1205,11 +1220,11 @@ class NWProject():
|
|||||||
"""
|
"""
|
||||||
self.statusItems.resetCounts()
|
self.statusItems.resetCounts()
|
||||||
self.importItems.resetCounts()
|
self.importItems.resetCounts()
|
||||||
for nwItem in self.projTree:
|
for nwItem in self._projTree:
|
||||||
if nwItem.itemClass in nwLists.CLS_NOVEL:
|
if nwItem.isNovelLike():
|
||||||
self.statusItems.countEntry(nwItem.itemStatus)
|
self.statusItems.increment(nwItem.itemStatus)
|
||||||
else:
|
else:
|
||||||
self.importItems.countEntry(nwItem.itemImport)
|
self.importItems.increment(nwItem.itemImport)
|
||||||
return
|
return
|
||||||
|
|
||||||
def localLookup(self, theWord):
|
def localLookup(self, theWord):
|
||||||
@@ -1217,17 +1232,39 @@ class NWProject():
|
|||||||
return it. The variable is cast to a string before lookup. If
|
return it. The variable is cast to a string before lookup. If
|
||||||
the word does not exist, it returns itself.
|
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
|
# 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):
|
def _loadProjectLocalisation(self):
|
||||||
"""Load the language data for the current project language.
|
"""Load the language data for the current project language.
|
||||||
"""
|
"""
|
||||||
if self.projLang is None:
|
if self.projLang is None:
|
||||||
self.langData = {}
|
self._langData = {}
|
||||||
return False
|
return False
|
||||||
|
|
||||||
langFile = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % self.projLang)
|
langFile = os.path.join(self.mainConf.nwLangPath, "project_%s.json" % self.projLang)
|
||||||
@@ -1236,7 +1273,7 @@ class NWProject():
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
with open(langFile, mode="r", encoding="utf-8") as inFile:
|
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))
|
logger.debug("Loaded project language file: %s", os.path.basename(langFile))
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -1371,7 +1408,7 @@ class NWProject():
|
|||||||
logger.warning("Skipping file: %s", fileItem)
|
logger.warning("Skipping file: %s", fileItem)
|
||||||
continue
|
continue
|
||||||
|
|
||||||
if fHandle in self.projTree:
|
if fHandle in self._projTree:
|
||||||
self.projFiles.append(fHandle)
|
self.projFiles.append(fHandle)
|
||||||
logger.debug("Checking file %s, handle '%s': OK", fileItem, fHandle)
|
logger.debug("Checking file %s, handle '%s': OK", fileItem, fHandle)
|
||||||
else:
|
else:
|
||||||
@@ -1418,10 +1455,10 @@ class NWProject():
|
|||||||
if oLayout is None:
|
if oLayout is None:
|
||||||
oLayout = nwItemLayout.NOTE
|
oLayout = nwItemLayout.NOTE
|
||||||
|
|
||||||
if oParent is None or oParent not in self.projTree:
|
if oParent is None or oParent not in self._projTree:
|
||||||
oParent = self.projTree.findRoot(oClass)
|
oParent = self._projTree.findRoot(oClass)
|
||||||
if oParent is None:
|
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 the file still has no parent item, skip it
|
||||||
if oParent is None:
|
if oParent is None:
|
||||||
@@ -1433,7 +1470,8 @@ class NWProject():
|
|||||||
orphItem.setType(nwItemType.FILE)
|
orphItem.setType(nwItemType.FILE)
|
||||||
orphItem.setClass(oClass)
|
orphItem.setClass(oClass)
|
||||||
orphItem.setLayout(oLayout)
|
orphItem.setLayout(oLayout)
|
||||||
self.projTree.append(oHandle, oParent, orphItem)
|
self._projTree.append(oHandle, oParent, orphItem)
|
||||||
|
self._projTree.updateItemData(orphItem.itemHandle)
|
||||||
|
|
||||||
if noWhere:
|
if noWhere:
|
||||||
self.theParent.makeAlert(self.tr(
|
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
|
Data class for the status/importance settings of a project item
|
||||||
|
|
||||||
File History:
|
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
|
This file is a part of novelWriter
|
||||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
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/>.
|
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import random
|
||||||
import logging
|
import logging
|
||||||
import novelwriter
|
import novelwriter
|
||||||
|
|
||||||
@@ -30,134 +32,208 @@ from lxml import etree
|
|||||||
|
|
||||||
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
||||||
|
|
||||||
from novelwriter.common import checkInt
|
from novelwriter.common import checkInt, minmax, simplified
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class NWStatus():
|
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)
|
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
|
return
|
||||||
|
|
||||||
def addEntry(self, theLabel, theColours):
|
def write(self, key, name, cols, count=None):
|
||||||
"""Add a status entry to the status object, but ensure it isn't
|
"""Add or update a status entry. If the key is invalid, a new
|
||||||
a duplicate.
|
key is generated.
|
||||||
"""
|
"""
|
||||||
theLabel = theLabel.strip()
|
if not self._isKey(key):
|
||||||
if self._getIndex(theLabel) is None:
|
key = self._newKey()
|
||||||
theIcon = QPixmap(self._iconSize, self._iconSize)
|
if not isinstance(cols, tuple):
|
||||||
theIcon.fill(QColor(*theColours))
|
cols = (100, 100, 100)
|
||||||
self._theIcons.append(QIcon(theIcon))
|
if len(cols) != 3:
|
||||||
self._theLabels.append(theLabel)
|
cols = (100, 100, 100)
|
||||||
self._theColours.append(theColours)
|
|
||||||
self._theCounts.append(0)
|
pixmap = QPixmap(self._iconSize, self._iconSize)
|
||||||
self._theMap[theLabel] = self._theLength
|
pixmap.fill(QColor(*cols))
|
||||||
self._theLength += 1
|
|
||||||
|
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
|
return True
|
||||||
|
|
||||||
def checkEntry(self, theStatus):
|
def check(self, value):
|
||||||
"""Check if a status value is valid, and returns the safe
|
"""Check the key against the stored status names.
|
||||||
reference to be used internally.
|
|
||||||
"""
|
"""
|
||||||
if isinstance(theStatus, str):
|
if self._isKey(value) and value in self._store:
|
||||||
if self._getIndex(theStatus) is not None:
|
return value
|
||||||
return theStatus.strip()
|
elif value in self._reverse:
|
||||||
return self._theLabels[0]
|
return self._reverse[value]
|
||||||
|
elif self._default is not None:
|
||||||
|
return self._default
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
def getIcon(self, theLabel):
|
def name(self, key):
|
||||||
"""Return the icon for the given status item.
|
"""Return the name associated with a given key.
|
||||||
"""
|
"""
|
||||||
theIndex = self._getIndex(theLabel)
|
if key in self._store:
|
||||||
if theIndex is not None:
|
return self._store[key]["name"]
|
||||||
return self._theIcons[theIndex]
|
elif self._default is not None:
|
||||||
return QIcon()
|
return self._store[self._default]["name"]
|
||||||
|
else:
|
||||||
|
return ""
|
||||||
|
|
||||||
def setNewEntries(self, newList):
|
def cols(self, key):
|
||||||
"""Update the list of entries after they have been modified by
|
"""Return the colours associated with a given key.
|
||||||
the GUI tool.
|
|
||||||
"""
|
"""
|
||||||
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:
|
def count(self, key):
|
||||||
self._theLabels = []
|
"""Return the count associated with a given key.
|
||||||
self._theColours = []
|
"""
|
||||||
self._theCounts = []
|
if key in self._store:
|
||||||
self._theIcons = []
|
return self._store[key]["count"]
|
||||||
self._theMap = {}
|
elif self._default is not None:
|
||||||
self._theLength = 0
|
return self._store[self._default]["count"]
|
||||||
self._theIndex = 0
|
else:
|
||||||
|
return 0
|
||||||
|
|
||||||
for nName, nR, nG, nB, oName in newList:
|
def icon(self, key):
|
||||||
self.addEntry(nName, (nR, nG, nB))
|
"""Return the icon associated with a given key.
|
||||||
if nName != oName and oName is not None:
|
"""
|
||||||
replaceMap[oName] = nName
|
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):
|
def resetCounts(self):
|
||||||
"""Clear the counts of references to the status entries.
|
"""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
|
return
|
||||||
|
|
||||||
def countEntry(self, theLabel):
|
def increment(self, key):
|
||||||
"""Increment the counter for a given label. This should be used
|
"""Increment the counter for a given entry.
|
||||||
together with resetCounts in a loop over project items.
|
|
||||||
"""
|
"""
|
||||||
theIndex = self._getIndex(theLabel)
|
if key in self._store:
|
||||||
if theIndex is not None:
|
self._store[key]["count"] += 1
|
||||||
self._theCounts[theIndex] += 1
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def packXML(self, xParent):
|
def packXML(self, xParent):
|
||||||
"""Pack the status entries into an XML object for saving to the
|
"""Pack the status entries into an XML object for saving to the
|
||||||
main project file.
|
main project file.
|
||||||
"""
|
"""
|
||||||
for n in range(self._theLength):
|
for key, data in self._store.items():
|
||||||
xSub = etree.SubElement(xParent, "entry", attrib={
|
xSub = etree.SubElement(xParent, "entry", attrib={
|
||||||
"red": str(self._theColours[n][0]),
|
"key": key,
|
||||||
"green": str(self._theColours[n][1]),
|
"count": str(data["count"]),
|
||||||
"blue": str(self._theColours[n][2]),
|
"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
|
return True
|
||||||
|
|
||||||
def unpackXML(self, xParent):
|
def unpackXML(self, xParent):
|
||||||
"""Unpack an XML tree and set the class values.
|
"""Unpack an XML tree and set the class values.
|
||||||
"""
|
"""
|
||||||
theLabels = []
|
self._store = {}
|
||||||
theColours = []
|
self._reverse = {}
|
||||||
|
self._default = None
|
||||||
|
|
||||||
for xChild in xParent:
|
for xChild in xParent:
|
||||||
theLabels.append(xChild.text)
|
key = xChild.attrib.get("key", None)
|
||||||
cR = checkInt(xChild.attrib.get("red", 0), 0, False)
|
name = xChild.text.strip()
|
||||||
cG = checkInt(xChild.attrib.get("green", 0), 0, False)
|
count = max(checkInt(xChild.attrib.get("count", 0), 0), 0)
|
||||||
cB = checkInt(xChild.attrib.get("blue", 0), 0, False)
|
red = minmax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255)
|
||||||
theColours.append((cR, cG, cB))
|
green = minmax(checkInt(xChild.attrib.get("green", 100), 100), 0, 255)
|
||||||
|
blue = minmax(checkInt(xChild.attrib.get("blue", 100), 100), 0, 255)
|
||||||
if len(theLabels) > 0:
|
self.write(key, name, (red, green, blue), count)
|
||||||
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])
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -165,39 +241,52 @@ class NWStatus():
|
|||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|
||||||
def _getIndex(self, theLabel):
|
def _newKey(self):
|
||||||
"""Look up a status entry in the object lists, and return it if
|
"""Generate a new key for a status flag. This method is
|
||||||
it exists.
|
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:
|
key = f"{self._prefix}{random.getrandbits(24):06x}"
|
||||||
return None
|
if key in self._store:
|
||||||
return self._theMap.get(theLabel.strip(), None)
|
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
|
# Iterator Bits
|
||||||
##
|
##
|
||||||
|
|
||||||
def __getitem__(self, n):
|
def __len__(self):
|
||||||
"""Return an entry by its index.
|
return len(self._store)
|
||||||
"""
|
|
||||||
if n >= 0 and n < self._theLength:
|
def __getitem__(self, key):
|
||||||
return self._theLabels[n], self._theColours[n], self._theCounts[n], self._theIcons[n]
|
return self._store[key]
|
||||||
return None, None, None, QIcon()
|
|
||||||
|
|
||||||
def __iter__(self):
|
def __iter__(self):
|
||||||
"""Initialise the iterator.
|
return iter(self._store)
|
||||||
"""
|
|
||||||
self._theIndex = 0
|
|
||||||
return self
|
|
||||||
|
|
||||||
def __next__(self):
|
def keys(self):
|
||||||
"""Return the next entry for the iterator.
|
return self._store.keys()
|
||||||
"""
|
|
||||||
if self._theIndex < self._theLength:
|
def items(self):
|
||||||
theLabel, theColour, theCount, theIcon = self.__getitem__(self._theIndex)
|
return self._store.items()
|
||||||
self._theIndex += 1
|
|
||||||
return theLabel, theColour, theCount, theIcon
|
def values(self):
|
||||||
else:
|
return self._store.values()
|
||||||
raise StopIteration
|
|
||||||
|
|
||||||
# END Class NWStatus
|
# END Class NWStatus
|
||||||
|
|||||||
@@ -451,7 +451,7 @@ class ToHtml(Tokenizer):
|
|||||||
def _formatKeywords(self, tText):
|
def _formatKeywords(self, tText):
|
||||||
"""Apply HTML formatting to keywords.
|
"""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:
|
if not isValid or not theBits:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|||||||
@@ -275,7 +275,7 @@ class Tokenizer(ABC):
|
|||||||
def addRootHeading(self, theHandle):
|
def addRootHeading(self, theHandle):
|
||||||
"""Add a heading at the start of a new root folder.
|
"""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
|
return False
|
||||||
|
|
||||||
if self._isFirst:
|
if self._isFirst:
|
||||||
@@ -284,7 +284,7 @@ class Tokenizer(ABC):
|
|||||||
else:
|
else:
|
||||||
textAlign = self.A_PBB | self.A_CENTRE
|
textAlign = self.A_PBB | self.A_CENTRE
|
||||||
|
|
||||||
theItem = self.theProject.projTree[theHandle]
|
theItem = self.theProject.tree[theHandle]
|
||||||
locNotes = self._localLookup("Notes")
|
locNotes = self._localLookup("Notes")
|
||||||
theTitle = f"{locNotes}: {theItem.itemName}"
|
theTitle = f"{locNotes}: {theItem.itemName}"
|
||||||
self._theTokens = []
|
self._theTokens = []
|
||||||
@@ -301,7 +301,7 @@ class Tokenizer(ABC):
|
|||||||
not set, load it from the file.
|
not set, load it from the file.
|
||||||
"""
|
"""
|
||||||
self._theHandle = theHandle
|
self._theHandle = theHandle
|
||||||
self._theItem = self.theProject.projTree[theHandle]
|
self._theItem = self.theProject.tree[theHandle]
|
||||||
if self._theItem is None:
|
if self._theItem is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|||||||
@@ -193,7 +193,7 @@ class ToMarkdown(Tokenizer):
|
|||||||
def _formatKeywords(self, tText, tStyle):
|
def _formatKeywords(self, tText, tStyle):
|
||||||
"""Apply Markdown formatting to keywords.
|
"""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:
|
if not isValid or not theBits:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|||||||
@@ -550,7 +550,7 @@ class ToOdt(Tokenizer):
|
|||||||
def _formatKeywords(self, tText):
|
def _formatKeywords(self, tText):
|
||||||
"""Apply formatting to keywords.
|
"""Apply formatting to keywords.
|
||||||
"""
|
"""
|
||||||
isValid, theBits, _ = self.theParent.theIndex.scanThis("@"+tText)
|
isValid, theBits, _ = self.theProject.index.scanThis("@"+tText)
|
||||||
if not isValid or not theBits:
|
if not isValid or not theBits:
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
|
|||||||
@@ -24,16 +24,15 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
import os
|
||||||
|
import random
|
||||||
import logging
|
import logging
|
||||||
|
|
||||||
from time import time
|
|
||||||
from lxml import etree
|
from lxml import etree
|
||||||
from hashlib import sha256
|
|
||||||
|
|
||||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||||
from novelwriter.error import logException
|
from novelwriter.error import logException
|
||||||
from novelwriter.common import checkHandle
|
from novelwriter.common import checkHandle
|
||||||
from novelwriter.constants import nwConst, nwFiles
|
from novelwriter.constants import nwFiles
|
||||||
from novelwriter.core.item import NWItem
|
from novelwriter.core.item import NWItem
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -41,21 +40,20 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class NWTree():
|
class NWTree():
|
||||||
|
|
||||||
|
MAX_DEPTH = 1000 # Cap of tree traversing for loops
|
||||||
|
|
||||||
def __init__(self, theProject):
|
def __init__(self, theProject):
|
||||||
|
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
|
|
||||||
self._projTree = {} # Holds all the items of the project
|
self._projTree = {} # Holds all the items of the project
|
||||||
self._treeOrder = [] # The order of the tree items on the tree view
|
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._trashRoot = None # The handle of the trash root folder
|
||||||
self._archRoot = None # The handle of the archive root folder
|
self._archRoot = None # The handle of the archive root folder
|
||||||
self._theIndex = 0 # The current iterator index
|
self._theIndex = 0 # The current iterator index
|
||||||
self._treeChanged = False # True if tree structure has changed
|
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
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -67,7 +65,7 @@ class NWTree():
|
|||||||
"""
|
"""
|
||||||
self._projTree = {}
|
self._projTree = {}
|
||||||
self._treeOrder = []
|
self._treeOrder = []
|
||||||
self._treeRoots = []
|
self._treeRoots = {}
|
||||||
self._trashRoot = None
|
self._trashRoot = None
|
||||||
self._archRoot = None
|
self._archRoot = None
|
||||||
self._theIndex = 0
|
self._theIndex = 0
|
||||||
@@ -98,18 +96,17 @@ class NWTree():
|
|||||||
|
|
||||||
if nwItem.itemType == nwItemType.ROOT:
|
if nwItem.itemType == nwItemType.ROOT:
|
||||||
logger.verbose("Item '%s' is a root item", str(tHandle))
|
logger.verbose("Item '%s' is a root item", str(tHandle))
|
||||||
self._treeRoots.append(tHandle)
|
self._treeRoots[tHandle] = nwItem
|
||||||
if nwItem.itemClass == nwItemClass.ARCHIVE:
|
if nwItem.itemClass == nwItemClass.ARCHIVE:
|
||||||
logger.verbose("Item '%s' is the archive folder", str(tHandle))
|
logger.verbose("Item '%s' is the archive folder", str(tHandle))
|
||||||
self._archRoot = tHandle
|
self._archRoot = tHandle
|
||||||
|
elif nwItem.itemClass == nwItemClass.TRASH:
|
||||||
if nwItem.itemType == nwItemType.TRASH:
|
if self._trashRoot is None:
|
||||||
if self._trashRoot is None:
|
logger.verbose("Item '%s' is the trash folder", str(tHandle))
|
||||||
logger.verbose("Item '%s' is the trash folder", str(tHandle))
|
self._trashRoot = tHandle
|
||||||
self._trashRoot = tHandle
|
else:
|
||||||
else:
|
logger.error("Only one trash folder allowed")
|
||||||
logger.error("Only one trash folder allowed")
|
return False
|
||||||
return False
|
|
||||||
|
|
||||||
self._projTree[tHandle] = nwItem
|
self._projTree[tHandle] = nwItem
|
||||||
self._treeOrder.append(tHandle)
|
self._treeOrder.append(tHandle)
|
||||||
@@ -207,9 +204,30 @@ class NWTree():
|
|||||||
return novelWords, noteWords
|
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):
|
def checkType(self, tHandle, itemType):
|
||||||
"""Return true of item exists and is of the specified item type.
|
"""Return true of item exists and is of the specified item type.
|
||||||
"""
|
"""
|
||||||
@@ -218,71 +236,6 @@ class NWTree():
|
|||||||
return False
|
return False
|
||||||
return tItem.itemType == itemType
|
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):
|
def getItemPath(self, tHandle):
|
||||||
"""Iterate upwards in the tree until we find the item with
|
"""Iterate upwards in the tree until we find the item with
|
||||||
parent None, the root item, and return the list of handles.
|
parent None, the root item, and return the list of handles.
|
||||||
@@ -293,7 +246,7 @@ class NWTree():
|
|||||||
tItem = self.__getitem__(tHandle)
|
tItem = self.__getitem__(tHandle)
|
||||||
if tItem is not None:
|
if tItem is not None:
|
||||||
tTree.append(tHandle)
|
tTree.append(tHandle)
|
||||||
for _ in range(nwConst.MAX_DEPTH + 1):
|
for _ in range(self.MAX_DEPTH):
|
||||||
if tItem.itemParent is None:
|
if tItem.itemParent is None:
|
||||||
return tTree
|
return tTree
|
||||||
else:
|
else:
|
||||||
@@ -303,8 +256,72 @@ class NWTree():
|
|||||||
return tTree
|
return tTree
|
||||||
else:
|
else:
|
||||||
tTree.append(tHandle)
|
tTree.append(tHandle)
|
||||||
|
else:
|
||||||
|
raise RecursionError("Critical internal error")
|
||||||
|
|
||||||
return tTree
|
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
|
# Setters
|
||||||
##
|
##
|
||||||
@@ -334,14 +351,6 @@ class NWTree():
|
|||||||
|
|
||||||
return
|
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):
|
def setFileItemLayout(self, tHandle, itemLayout):
|
||||||
"""Set the nwItemLayout for a specific file.
|
"""Set the nwItemLayout for a specific file.
|
||||||
"""
|
"""
|
||||||
@@ -358,30 +367,6 @@ class NWTree():
|
|||||||
|
|
||||||
return True
|
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
|
# Meta Methods
|
||||||
##
|
##
|
||||||
@@ -420,7 +405,7 @@ class NWTree():
|
|||||||
return
|
return
|
||||||
|
|
||||||
if tHandle in self._treeRoots:
|
if tHandle in self._treeRoots:
|
||||||
self._treeRoots.remove(tHandle)
|
del self._treeRoots[tHandle]
|
||||||
if tHandle == self._trashRoot:
|
if tHandle == self._trashRoot:
|
||||||
self._trashRoot = None
|
self._trashRoot = None
|
||||||
if tHandle == self._archRoot:
|
if tHandle == self._archRoot:
|
||||||
@@ -468,29 +453,16 @@ class NWTree():
|
|||||||
self.theProject.setProjectChanged(True)
|
self.theProject.setProjectChanged(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
def _makeHandle(self, addSeed=""):
|
def _makeHandle(self):
|
||||||
"""Generate a unique item handle. In the event that the key
|
"""Generate a unique item handle. In the event that the key
|
||||||
already exists, salt the seed and generate a new handle.
|
already exists, generate a new one.
|
||||||
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.
|
|
||||||
"""
|
"""
|
||||||
if self._handleSeed is None:
|
logger.verbose("Generating new handle")
|
||||||
newSeed = "%s_%d_%s" % (str(time()), self._handleCount, addSeed)
|
handle = f"{random.getrandbits(52):013x}"
|
||||||
self._handleCount += 1
|
if handle in self._projTree:
|
||||||
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.warning("Duplicate handle encountered! Retrying ...")
|
logger.warning("Duplicate handle encountered! Retrying ...")
|
||||||
itemHandle = self._makeHandle(addSeed+"!")
|
handle = self._makeHandle()
|
||||||
|
|
||||||
return itemHandle
|
return handle
|
||||||
|
|
||||||
# END Class NWTree
|
# END Class NWTree
|
||||||
|
|||||||
@@ -181,14 +181,14 @@ class GuiAbout(QDialog):
|
|||||||
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
||||||
self.tr("Translations"),
|
self.tr("Translations"),
|
||||||
self._wrapTable([
|
self._wrapTable([
|
||||||
|
("Deutsch", "Myian"),
|
||||||
("English", "Veronica Berglyd Olsen"),
|
("English", "Veronica Berglyd Olsen"),
|
||||||
|
("Español Latinoamericano", "Tommy Marplatt"),
|
||||||
("Français", "Jan Lüdke (jyhelle)"),
|
("Français", "Jan Lüdke (jyhelle)"),
|
||||||
|
("Nederlands", "Martijn van der Kleijn"),
|
||||||
("Norsk Bokmål", "Veronica Berglyd Olsen"),
|
("Norsk Bokmål", "Veronica Berglyd Olsen"),
|
||||||
("Português", "Bruno Meneguello"),
|
("Português", "Bruno Meneguello"),
|
||||||
("简体中文", "Qianzhi Long"),
|
("简体中文", "Qianzhi Long"),
|
||||||
("Español Latinoamericano", "Tommy Marplatt"),
|
|
||||||
("Nederlands", "Martijn van der Kleijn"),
|
|
||||||
("Deutsch", "Myian"),
|
|
||||||
])
|
])
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|||||||
@@ -125,13 +125,13 @@ class GuiDocMerge(QDialog):
|
|||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
srcItem = self.theProject.projTree[self.sourceItem]
|
srcItem = self.theProject.tree[self.sourceItem]
|
||||||
if srcItem is None:
|
if srcItem is None:
|
||||||
self.theParent.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
|
self.theParent.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.itemParent)
|
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemParent)
|
||||||
newItem = self.theProject.projTree[nHandle]
|
newItem = self.theProject.tree[nHandle]
|
||||||
newItem.setStatus(srcItem.itemStatus)
|
newItem.setStatus(srcItem.itemStatus)
|
||||||
newItem.setImport(srcItem.itemImport)
|
newItem.setImport(srcItem.itemImport)
|
||||||
|
|
||||||
@@ -170,7 +170,7 @@ class GuiDocMerge(QDialog):
|
|||||||
if tHandle is None:
|
if tHandle is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.tree[tHandle]
|
||||||
if nwItem is None:
|
if nwItem is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -182,7 +182,7 @@ class GuiDocMerge(QDialog):
|
|||||||
|
|
||||||
for sHandle in self.theParent.treeView.getTreeFromHandle(tHandle):
|
for sHandle in self.theParent.treeView.getTreeFromHandle(tHandle):
|
||||||
newItem = QListWidgetItem()
|
newItem = QListWidgetItem()
|
||||||
nwItem = self.theProject.projTree[sHandle]
|
nwItem = self.theProject.tree[sHandle]
|
||||||
if nwItem.itemType is not nwItemType.FILE:
|
if nwItem.itemType is not nwItemType.FILE:
|
||||||
continue
|
continue
|
||||||
newItem.setText(nwItem.itemName)
|
newItem.setText(nwItem.itemName)
|
||||||
|
|||||||
@@ -33,8 +33,7 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.core import NWDoc
|
from novelwriter.core import NWDoc
|
||||||
from novelwriter.enum import nwAlert, nwItemType, nwItemClass, nwItemLayout
|
from novelwriter.enum import nwAlert, nwItemType
|
||||||
from novelwriter.constants import nwConst
|
|
||||||
from novelwriter.gui.custom import QHelpLabel
|
from novelwriter.gui.custom import QHelpLabel
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -51,7 +50,6 @@ class GuiDocSplit(QDialog):
|
|||||||
self.mainConf = novelwriter.CONFIG
|
self.mainConf = novelwriter.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.optState = theParent.theProject.optState
|
|
||||||
|
|
||||||
self.sourceItem = None
|
self.sourceItem = None
|
||||||
self.sourceText = []
|
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 3 (Scene)"), 3)
|
||||||
self.splitLevel.addItem(self.tr("Split up to Header Level 4 (Section)"), 4)
|
self.splitLevel.addItem(self.tr("Split up to Header Level 4 (Section)"), 4)
|
||||||
spIndex = self.splitLevel.findData(
|
spIndex = self.splitLevel.findData(
|
||||||
self.optState.getInt("GuiDocSplit", "spLevel", 3)
|
self.theProject.options.getInt("GuiDocSplit", "spLevel", 3)
|
||||||
)
|
)
|
||||||
if spIndex != -1:
|
if spIndex != -1:
|
||||||
self.splitLevel.setCurrentIndex(spIndex)
|
self.splitLevel.setCurrentIndex(spIndex)
|
||||||
@@ -122,7 +120,7 @@ class GuiDocSplit(QDialog):
|
|||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
srcItem = self.theProject.projTree[self.sourceItem]
|
srcItem = self.theProject.tree[self.sourceItem]
|
||||||
if srcItem is None:
|
if srcItem is None:
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
"Could not parse source document."
|
"Could not parse source document."
|
||||||
@@ -160,16 +158,6 @@ class GuiDocSplit(QDialog):
|
|||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
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(
|
msgYes = self.theParent.askQuestion(
|
||||||
self.tr("Split Document"),
|
self.tr("Split Document"),
|
||||||
"{0}<br><br>{1}".format(
|
"{0}<br><br>{1}".format(
|
||||||
@@ -186,22 +174,16 @@ class GuiDocSplit(QDialog):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Create the folder
|
# Create the folder
|
||||||
fHandle = self.theProject.newFolder(
|
fHandle = self.theProject.newFolder(srcItem.itemName, srcItem.itemParent)
|
||||||
srcItem.itemName, srcItem.itemClass, srcItem.itemParent
|
|
||||||
)
|
|
||||||
self.theParent.treeView.revealNewTreeItem(fHandle)
|
self.theParent.treeView.revealNewTreeItem(fHandle)
|
||||||
logger.verbose("Creating folder '%s'", fHandle)
|
logger.verbose("Creating folder '%s'", fHandle)
|
||||||
|
|
||||||
# Loop through, and create the files
|
# Loop through, and create the files
|
||||||
for wTitle, iStart, iEnd in finalOrder:
|
for wTitle, iStart, iEnd in finalOrder:
|
||||||
|
|
||||||
isNovel = srcItem.itemClass == nwItemClass.NOVEL
|
|
||||||
itemLayout = nwItemLayout.DOCUMENT if isNovel else nwItemLayout.NOTE
|
|
||||||
|
|
||||||
wTitle = wTitle.lstrip("#").strip()
|
wTitle = wTitle.lstrip("#").strip()
|
||||||
nHandle = self.theProject.newFile(wTitle, srcItem.itemClass, fHandle)
|
nHandle = self.theProject.newFile(wTitle, fHandle)
|
||||||
newItem = self.theProject.projTree[nHandle]
|
newItem = self.theProject.tree[nHandle]
|
||||||
newItem.setLayout(itemLayout)
|
|
||||||
newItem.setStatus(srcItem.itemStatus)
|
newItem.setStatus(srcItem.itemStatus)
|
||||||
newItem.setImport(srcItem.itemImport)
|
newItem.setImport(srcItem.itemImport)
|
||||||
logger.verbose(
|
logger.verbose(
|
||||||
@@ -228,7 +210,7 @@ class GuiDocSplit(QDialog):
|
|||||||
def _doClose(self):
|
def _doClose(self):
|
||||||
"""Close the dialog window without doing anything.
|
"""Close the dialog window without doing anything.
|
||||||
"""
|
"""
|
||||||
self.optState.saveSettings()
|
self.theProject.options.saveSettings()
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -249,7 +231,7 @@ class GuiDocSplit(QDialog):
|
|||||||
if self.sourceItem is None:
|
if self.sourceItem is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
nwItem = self.theProject.projTree[self.sourceItem]
|
nwItem = self.theProject.tree[self.sourceItem]
|
||||||
if nwItem is None:
|
if nwItem is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -266,7 +248,7 @@ class GuiDocSplit(QDialog):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
spLevel = self.splitLevel.currentData()
|
spLevel = self.splitLevel.currentData()
|
||||||
self.optState.setValue("GuiDocSplit", "spLevel", spLevel)
|
self.theProject.options.setValue("GuiDocSplit", "spLevel", spLevel)
|
||||||
logger.debug(
|
logger.debug(
|
||||||
"Scanning document '%s' for headings level <= %d",
|
"Scanning document '%s' for headings level <= %d",
|
||||||
self.sourceItem, spLevel
|
self.sourceItem, spLevel
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.enum import nwItemLayout, nwItemType
|
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
|
from novelwriter.gui.custom import QSwitch
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -55,7 +55,7 @@ class GuiItemEditor(QDialog):
|
|||||||
# Build GUI
|
# Build GUI
|
||||||
##
|
##
|
||||||
|
|
||||||
self.theItem = self.theProject.projTree[tHandle]
|
self.theItem = self.theProject.tree[tHandle]
|
||||||
if self.theItem is None:
|
if self.theItem is None:
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
@@ -74,19 +74,28 @@ class GuiItemEditor(QDialog):
|
|||||||
# Item Status
|
# Item Status
|
||||||
self.editStatus = QComboBox()
|
self.editStatus = QComboBox()
|
||||||
self.editStatus.setMinimumWidth(mVd)
|
self.editStatus.setMinimumWidth(mVd)
|
||||||
if self.theItem.itemClass in nwLists.CLS_NOVEL:
|
if self.theItem.isNovelLike():
|
||||||
for sLabel, _, _, sIcon in self.theProject.statusItems:
|
for key, entry in self.theProject.statusItems.items():
|
||||||
self.editStatus.addItem(sIcon, sLabel, sLabel)
|
self.editStatus.addItem(entry["icon"], entry["name"], key)
|
||||||
|
|
||||||
|
index = self.editStatus.findData(self.theItem.itemStatus)
|
||||||
|
if index != -1:
|
||||||
|
self.editStatus.setCurrentIndex(index)
|
||||||
|
|
||||||
else:
|
else:
|
||||||
for sLabel, _, _, sIcon in self.theProject.importItems:
|
for key, entry in self.theProject.importItems.items():
|
||||||
self.editStatus.addItem(sIcon, sLabel, sLabel)
|
self.editStatus.addItem(entry["icon"], entry["name"], key)
|
||||||
|
|
||||||
|
index = self.editStatus.findData(self.theItem.itemImport)
|
||||||
|
if index != -1:
|
||||||
|
self.editStatus.setCurrentIndex(index)
|
||||||
|
|
||||||
# Item Layout
|
# Item Layout
|
||||||
self.editLayout = QComboBox()
|
self.editLayout = QComboBox()
|
||||||
self.editLayout.setMinimumWidth(mVd)
|
self.editLayout.setMinimumWidth(mVd)
|
||||||
validLayouts = []
|
validLayouts = []
|
||||||
if self.theItem.itemType == nwItemType.FILE:
|
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.DOCUMENT)
|
||||||
validLayouts.append(nwItemLayout.NOTE)
|
validLayouts.append(nwItemLayout.NOTE)
|
||||||
else:
|
else:
|
||||||
@@ -97,6 +106,10 @@ class GuiItemEditor(QDialog):
|
|||||||
if itemLayout in validLayouts:
|
if itemLayout in validLayouts:
|
||||||
self.editLayout.addItem(trConst(nwLabels.LAYOUT_NAME[itemLayout]), itemLayout)
|
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
|
# Export Switch
|
||||||
self.textExport = QLabel(self.tr("Include when building project"))
|
self.textExport = QLabel(self.tr("Include when building project"))
|
||||||
self.editExport = QSwitch()
|
self.editExport = QSwitch()
|
||||||
@@ -116,15 +129,6 @@ class GuiItemEditor(QDialog):
|
|||||||
self.editName.setText(self.theItem.itemName)
|
self.editName.setText(self.theItem.itemName)
|
||||||
self.editName.selectAll()
|
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
|
# Assemble
|
||||||
##
|
##
|
||||||
|
|||||||
@@ -203,6 +203,22 @@ class GuiPreferencesGeneral(QWidget):
|
|||||||
self.tr("Requires restart.")
|
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
|
# Font Family
|
||||||
self.guiFont = QLineEdit()
|
self.guiFont = QLineEdit()
|
||||||
self.guiFont.setReadOnly(True)
|
self.guiFont.setReadOnly(True)
|
||||||
@@ -275,6 +291,7 @@ class GuiPreferencesGeneral(QWidget):
|
|||||||
guiLang = self.guiLang.currentData()
|
guiLang = self.guiLang.currentData()
|
||||||
guiTheme = self.guiTheme.currentData()
|
guiTheme = self.guiTheme.currentData()
|
||||||
guiIcons = self.guiIcons.currentData()
|
guiIcons = self.guiIcons.currentData()
|
||||||
|
guiSyntax = self.guiSyntax.currentData()
|
||||||
guiFont = self.guiFont.text()
|
guiFont = self.guiFont.text()
|
||||||
guiFontSize = self.guiFontSize.value()
|
guiFontSize = self.guiFontSize.value()
|
||||||
emphLabels = self.emphLabels.isChecked()
|
emphLabels = self.emphLabels.isChecked()
|
||||||
@@ -294,6 +311,7 @@ class GuiPreferencesGeneral(QWidget):
|
|||||||
self.mainConf.guiLang = guiLang
|
self.mainConf.guiLang = guiLang
|
||||||
self.mainConf.guiTheme = guiTheme
|
self.mainConf.guiTheme = guiTheme
|
||||||
self.mainConf.guiIcons = guiIcons
|
self.mainConf.guiIcons = guiIcons
|
||||||
|
self.mainConf.guiSyntax = guiSyntax
|
||||||
self.mainConf.guiFont = guiFont
|
self.mainConf.guiFont = guiFont
|
||||||
self.mainConf.guiFontSize = guiFontSize
|
self.mainConf.guiFontSize = guiFontSize
|
||||||
self.mainConf.emphLabels = emphLabels
|
self.mainConf.emphLabels = emphLabels
|
||||||
@@ -834,25 +852,6 @@ class GuiPreferencesSyntax(QWidget):
|
|||||||
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
||||||
self.setLayout(self.mainForm)
|
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
|
# Quotes & Dialogue
|
||||||
# =================
|
# =================
|
||||||
self.mainForm.addGroupLabel(self.tr("Quotes & Dialogue"))
|
self.mainForm.addGroupLabel(self.tr("Quotes & Dialogue"))
|
||||||
@@ -902,7 +901,7 @@ class GuiPreferencesSyntax(QWidget):
|
|||||||
self.showMultiSpaces = QSwitch()
|
self.showMultiSpaces = QSwitch()
|
||||||
self.showMultiSpaces.setChecked(self.mainConf.showMultiSpaces)
|
self.showMultiSpaces.setChecked(self.mainConf.showMultiSpaces)
|
||||||
self.mainForm.addRow(
|
self.mainForm.addRow(
|
||||||
self.tr("Highlight multiple spaces"),
|
self.tr("Highlight multiple or trailing spaces"),
|
||||||
self.showMultiSpaces,
|
self.showMultiSpaces,
|
||||||
self.tr("Applies to the document editor only.")
|
self.tr("Applies to the document editor only.")
|
||||||
)
|
)
|
||||||
@@ -912,9 +911,6 @@ class GuiPreferencesSyntax(QWidget):
|
|||||||
def saveValues(self):
|
def saveValues(self):
|
||||||
"""Save the values set for this tab.
|
"""Save the values set for this tab.
|
||||||
"""
|
"""
|
||||||
# Highlighting Theme
|
|
||||||
self.mainConf.guiSyntax = self.guiSyntax.currentData()
|
|
||||||
|
|
||||||
# Quotes & Dialogue
|
# Quotes & Dialogue
|
||||||
self.mainConf.highlightQuotes = self.highlightQuotes.isChecked()
|
self.mainConf.highlightQuotes = self.highlightQuotes.isChecked()
|
||||||
self.mainConf.allowOpenSQuote = self.allowOpenSQuote.isChecked()
|
self.mainConf.allowOpenSQuote = self.allowOpenSQuote.isChecked()
|
||||||
|
|||||||
@@ -52,18 +52,18 @@ class GuiProjectDetails(PagedDialog):
|
|||||||
self.mainConf = novelwriter.CONFIG
|
self.mainConf = novelwriter.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.optState = theParent.theProject.optState
|
|
||||||
|
|
||||||
self.setWindowTitle(self.tr("Project Details"))
|
self.setWindowTitle(self.tr("Project Details"))
|
||||||
|
|
||||||
wW = self.mainConf.pxInt(600)
|
wW = self.mainConf.pxInt(600)
|
||||||
wH = self.mainConf.pxInt(400)
|
wH = self.mainConf.pxInt(400)
|
||||||
|
pOptions = self.theProject.options
|
||||||
|
|
||||||
self.setMinimumWidth(wW)
|
self.setMinimumWidth(wW)
|
||||||
self.setMinimumHeight(wH)
|
self.setMinimumHeight(wH)
|
||||||
self.resize(
|
self.resize(
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "winWidth", wW)),
|
self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "winWidth", wW)),
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "winHeight", wH))
|
self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "winHeight", wH))
|
||||||
)
|
)
|
||||||
|
|
||||||
self.tabMain = GuiProjectDetailsMain(self.theParent, self.theProject)
|
self.tabMain = GuiProjectDetailsMain(self.theParent, self.theProject)
|
||||||
@@ -120,16 +120,17 @@ class GuiProjectDetails(PagedDialog):
|
|||||||
countFrom = self.tabContents.poValue.value()
|
countFrom = self.tabContents.poValue.value()
|
||||||
clearDouble = self.tabContents.dblValue.isChecked()
|
clearDouble = self.tabContents.dblValue.isChecked()
|
||||||
|
|
||||||
self.optState.setValue("GuiProjectDetails", "winWidth", winWidth)
|
pOptions = self.theProject.options
|
||||||
self.optState.setValue("GuiProjectDetails", "winHeight", winHeight)
|
pOptions.setValue("GuiProjectDetails", "winWidth", winWidth)
|
||||||
self.optState.setValue("GuiProjectDetails", "widthCol0", widthCol0)
|
pOptions.setValue("GuiProjectDetails", "winHeight", winHeight)
|
||||||
self.optState.setValue("GuiProjectDetails", "widthCol1", widthCol1)
|
pOptions.setValue("GuiProjectDetails", "widthCol0", widthCol0)
|
||||||
self.optState.setValue("GuiProjectDetails", "widthCol2", widthCol2)
|
pOptions.setValue("GuiProjectDetails", "widthCol1", widthCol1)
|
||||||
self.optState.setValue("GuiProjectDetails", "widthCol3", widthCol3)
|
pOptions.setValue("GuiProjectDetails", "widthCol2", widthCol2)
|
||||||
self.optState.setValue("GuiProjectDetails", "widthCol4", widthCol4)
|
pOptions.setValue("GuiProjectDetails", "widthCol3", widthCol3)
|
||||||
self.optState.setValue("GuiProjectDetails", "wordsPerPage", wordsPerPage)
|
pOptions.setValue("GuiProjectDetails", "widthCol4", widthCol4)
|
||||||
self.optState.setValue("GuiProjectDetails", "countFrom", countFrom)
|
pOptions.setValue("GuiProjectDetails", "wordsPerPage", wordsPerPage)
|
||||||
self.optState.setValue("GuiProjectDetails", "clearDouble", clearDouble)
|
pOptions.setValue("GuiProjectDetails", "countFrom", countFrom)
|
||||||
|
pOptions.setValue("GuiProjectDetails", "clearDouble", clearDouble)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -145,7 +146,6 @@ class GuiProjectDetailsMain(QWidget):
|
|||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theIndex = theParent.theIndex
|
|
||||||
|
|
||||||
fPx = self.theTheme.fontPixelSize
|
fPx = self.theTheme.fontPixelSize
|
||||||
fPt = self.theTheme.fontPointSize
|
fPt = self.theTheme.fontPointSize
|
||||||
@@ -245,8 +245,9 @@ class GuiProjectDetailsMain(QWidget):
|
|||||||
def updateValues(self):
|
def updateValues(self):
|
||||||
"""Set all the values.
|
"""Set all the values.
|
||||||
"""
|
"""
|
||||||
hCounts = self.theIndex.getNovelTitleCounts()
|
pIndex = self.theProject.index
|
||||||
nwCount = self.theIndex.getNovelWordCount()
|
hCounts = pIndex.getNovelTitleCounts()
|
||||||
|
nwCount = pIndex.getNovelWordCount()
|
||||||
edTime = self.theProject.getCurrentEditTime()
|
edTime = self.theProject.getCurrentEditTime()
|
||||||
|
|
||||||
self.wordCountVal.setText(f"{nwCount:n}")
|
self.wordCountVal.setText(f"{nwCount:n}")
|
||||||
@@ -277,8 +278,6 @@ class GuiProjectDetailsContents(QWidget):
|
|||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theIndex = theParent.theIndex
|
|
||||||
self.optState = theProject.optState
|
|
||||||
|
|
||||||
# Internal
|
# Internal
|
||||||
self._theToC = []
|
self._theToC = []
|
||||||
@@ -286,6 +285,7 @@ class GuiProjectDetailsContents(QWidget):
|
|||||||
iPx = self.theTheme.baseIconSize
|
iPx = self.theTheme.baseIconSize
|
||||||
hPx = self.mainConf.pxInt(12)
|
hPx = self.mainConf.pxInt(12)
|
||||||
vPx = self.mainConf.pxInt(4)
|
vPx = self.mainConf.pxInt(4)
|
||||||
|
pOptions = self.theProject.options
|
||||||
|
|
||||||
# Contents Tree
|
# Contents Tree
|
||||||
# =============
|
# =============
|
||||||
@@ -314,11 +314,11 @@ class GuiProjectDetailsContents(QWidget):
|
|||||||
treeHeader.setStretchLastSection(True)
|
treeHeader.setStretchLastSection(True)
|
||||||
treeHeader.setMinimumSectionSize(hPx)
|
treeHeader.setMinimumSectionSize(hPx)
|
||||||
|
|
||||||
wCol0 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol0", 200))
|
wCol0 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol0", 200))
|
||||||
wCol1 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol1", 60))
|
wCol1 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol1", 60))
|
||||||
wCol2 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol2", 60))
|
wCol2 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol2", 60))
|
||||||
wCol3 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol3", 60))
|
wCol3 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol3", 60))
|
||||||
wCol4 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol4", 90))
|
wCol4 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol4", 90))
|
||||||
|
|
||||||
self.tocTree.setColumnWidth(0, wCol0)
|
self.tocTree.setColumnWidth(0, wCol0)
|
||||||
self.tocTree.setColumnWidth(1, wCol1)
|
self.tocTree.setColumnWidth(1, wCol1)
|
||||||
@@ -330,9 +330,9 @@ class GuiProjectDetailsContents(QWidget):
|
|||||||
# Options
|
# Options
|
||||||
# =======
|
# =======
|
||||||
|
|
||||||
wordsPerPage = self.optState.getInt("GuiProjectDetails", "wordsPerPage", 350)
|
wordsPerPage = pOptions.getInt("GuiProjectDetails", "wordsPerPage", 350)
|
||||||
countFrom = self.optState.getInt("GuiProjectDetails", "countFrom", 1)
|
countFrom = pOptions.getInt("GuiProjectDetails", "countFrom", 1)
|
||||||
clearDouble = self.optState.getInt("GuiProjectDetails", "clearDouble", True)
|
clearDouble = pOptions.getInt("GuiProjectDetails", "clearDouble", True)
|
||||||
|
|
||||||
wordsHelp = (
|
wordsHelp = (
|
||||||
self.tr("Typical word count for a 5 by 8 inch book page with 11 pt font is 350.")
|
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.
|
"""Extract the data for the tree.
|
||||||
"""
|
"""
|
||||||
self._theToC = []
|
self._theToC = []
|
||||||
self._theToC = self.theIndex.getTableOfContents(2)
|
self._theToC = self.theProject.index.getTableOfContents(2)
|
||||||
self._theToC.append(("", 0, self.tr("END"), 0))
|
self._theToC.append(("", 0, self.tr("END"), 0))
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.enum import nwAlert
|
from novelwriter.enum import nwAlert
|
||||||
|
from novelwriter.common import simplified
|
||||||
from novelwriter.gui.custom import QSwitch, PagedDialog, QConfigLayout
|
from novelwriter.gui.custom import QSwitch, PagedDialog, QConfigLayout
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -51,19 +52,19 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
self.mainConf = novelwriter.CONFIG
|
self.mainConf = novelwriter.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.optState = theParent.theProject.optState
|
|
||||||
|
|
||||||
self.theProject.countStatus()
|
self.theProject.countStatus()
|
||||||
self.setWindowTitle(self.tr("Project Settings"))
|
self.setWindowTitle(self.tr("Project Settings"))
|
||||||
|
|
||||||
wW = self.mainConf.pxInt(570)
|
wW = self.mainConf.pxInt(570)
|
||||||
wH = self.mainConf.pxInt(375)
|
wH = self.mainConf.pxInt(375)
|
||||||
|
pOptions = self.theProject.options
|
||||||
|
|
||||||
self.setMinimumWidth(wW)
|
self.setMinimumWidth(wW)
|
||||||
self.setMinimumHeight(wH)
|
self.setMinimumHeight(wH)
|
||||||
self.resize(
|
self.resize(
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winWidth", wW)),
|
self.mainConf.pxInt(pOptions.getInt("GuiProjectSettings", "winWidth", wW)),
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winHeight", wH))
|
self.mainConf.pxInt(pOptions.getInt("GuiProjectSettings", "winHeight", wH))
|
||||||
)
|
)
|
||||||
|
|
||||||
self.tabMain = GuiProjectEditMain(self.theParent, self.theProject)
|
self.tabMain = GuiProjectEditMain(self.theParent, self.theProject)
|
||||||
@@ -81,6 +82,9 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
self.buttonBox.rejected.connect(self._doClose)
|
self.buttonBox.rejected.connect(self._doClose)
|
||||||
self.addControls(self.buttonBox)
|
self.addControls(self.buttonBox)
|
||||||
|
|
||||||
|
# Flags
|
||||||
|
self.spellChanged = False
|
||||||
|
|
||||||
logger.debug("GuiProjectSettings initialisation complete")
|
logger.debug("GuiProjectSettings initialisation complete")
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -103,16 +107,18 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
self.theProject.setProjectName(projName)
|
self.theProject.setProjectName(projName)
|
||||||
self.theProject.setBookTitle(bookTitle)
|
self.theProject.setBookTitle(bookTitle)
|
||||||
self.theProject.setBookAuthors(bookAuthors)
|
self.theProject.setBookAuthors(bookAuthors)
|
||||||
self.theProject.setSpellLang(spellLang)
|
|
||||||
self.theProject.setProjBackup(doBackup)
|
self.theProject.setProjBackup(doBackup)
|
||||||
|
|
||||||
|
# Remember this as updating spell dictionary can be expensive
|
||||||
|
self.spellChanged = self.theProject.setSpellLang(spellLang)
|
||||||
|
|
||||||
if self.tabStatus.colChanged:
|
if self.tabStatus.colChanged:
|
||||||
statusCol = self.tabStatus.getNewList()
|
newList, delList = self.tabStatus.getNewList()
|
||||||
self.theProject.setStatusColours(statusCol)
|
self.theProject.setStatusColours(newList, delList)
|
||||||
|
|
||||||
if self.tabImport.colChanged:
|
if self.tabImport.colChanged:
|
||||||
importCol = self.tabImport.getNewList()
|
newList, delList = self.tabImport.getNewList()
|
||||||
self.theProject.setImportColours(importCol)
|
self.theProject.setImportColours(newList, delList)
|
||||||
|
|
||||||
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
||||||
self.theParent.rebuildTrees()
|
self.theParent.rebuildTrees()
|
||||||
@@ -146,11 +152,12 @@ class GuiProjectSettings(PagedDialog):
|
|||||||
statusColW = self.mainConf.rpxInt(self.tabStatus.listBox.columnWidth(0))
|
statusColW = self.mainConf.rpxInt(self.tabStatus.listBox.columnWidth(0))
|
||||||
importColW = self.mainConf.rpxInt(self.tabImport.listBox.columnWidth(0))
|
importColW = self.mainConf.rpxInt(self.tabImport.listBox.columnWidth(0))
|
||||||
|
|
||||||
self.optState.setValue("GuiProjectSettings", "winWidth", winWidth)
|
pOptions = self.theProject.options
|
||||||
self.optState.setValue("GuiProjectSettings", "winHeight", winHeight)
|
pOptions.setValue("GuiProjectSettings", "winWidth", winWidth)
|
||||||
self.optState.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
pOptions.setValue("GuiProjectSettings", "winHeight", winHeight)
|
||||||
self.optState.setValue("GuiProjectSettings", "statusColW", statusColW)
|
pOptions.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||||
self.optState.setValue("GuiProjectSettings", "importColW", importColW)
|
pOptions.setValue("GuiProjectSettings", "statusColW", statusColW)
|
||||||
|
pOptions.setValue("GuiProjectSettings", "importColW", importColW)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -181,7 +188,7 @@ class GuiProjectEditMain(QWidget):
|
|||||||
self.editName.setMaximumWidth(xW)
|
self.editName.setMaximumWidth(xW)
|
||||||
self.editName.setText(self.theProject.projName)
|
self.editName.setText(self.theProject.projName)
|
||||||
self.mainForm.addRow(
|
self.mainForm.addRow(
|
||||||
self.tr("Working title"),
|
self.tr("Project name"),
|
||||||
self.editName,
|
self.editName,
|
||||||
self.tr("Should be set only once.")
|
self.tr("Should be set only once.")
|
||||||
)
|
)
|
||||||
@@ -245,13 +252,16 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
COL_LABEL = 0
|
COL_LABEL = 0
|
||||||
COL_USAGE = 1
|
COL_USAGE = 1
|
||||||
|
|
||||||
|
KEY_ROLE = Qt.UserRole
|
||||||
|
COL_ROLE = Qt.UserRole + 1
|
||||||
|
NUM_ROLE = Qt.UserRole + 2
|
||||||
|
|
||||||
def __init__(self, theParent, theProject, isStatus):
|
def __init__(self, theParent, theProject, isStatus):
|
||||||
QWidget.__init__(self, theParent)
|
QWidget.__init__(self, theParent)
|
||||||
|
|
||||||
self.mainConf = novelwriter.CONFIG
|
self.mainConf = novelwriter.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
self.optState = theProject.optState
|
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
|
|
||||||
if isStatus:
|
if isStatus:
|
||||||
@@ -264,13 +274,12 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
colSetting = "importColW"
|
colSetting = "importColW"
|
||||||
|
|
||||||
wCol0 = self.mainConf.pxInt(
|
wCol0 = self.mainConf.pxInt(
|
||||||
self.optState.getInt("GuiProjectSettings", colSetting, 130)
|
self.theProject.options.getInt("GuiProjectSettings", colSetting, 130)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.colData = []
|
self.colDeleted = []
|
||||||
self.colCounts = []
|
|
||||||
self.colChanged = False
|
self.colChanged = False
|
||||||
self.selColour = None
|
self.selColour = QColor(100, 100, 100)
|
||||||
|
|
||||||
self.iPx = self.theTheme.baseIconSize
|
self.iPx = self.theTheme.baseIconSize
|
||||||
|
|
||||||
@@ -285,8 +294,8 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
|
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
|
||||||
self.listBox.setIndentation(0)
|
self.listBox.setIndentation(0)
|
||||||
|
|
||||||
for iName, iCol, nUse, _ in self.theStatus:
|
for key, entry in self.theStatus.items():
|
||||||
self._addItem(iName, iCol, iName, nUse)
|
self._addItem(key, entry["name"], entry["cols"], entry["count"])
|
||||||
|
|
||||||
# List Controls
|
# List Controls
|
||||||
# =============
|
# =============
|
||||||
@@ -297,6 +306,12 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
|
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
|
||||||
self.delButton.clicked.connect(self._delItem)
|
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
|
# Edit Form
|
||||||
# =========
|
# =========
|
||||||
|
|
||||||
@@ -306,7 +321,7 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
self.editName.setPlaceholderText(self.tr("Select item to edit"))
|
self.editName.setPlaceholderText(self.tr("Select item to edit"))
|
||||||
|
|
||||||
self.colPixmap = QPixmap(self.iPx, self.iPx)
|
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 = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
|
||||||
self.colButton.setIconSize(self.colPixmap.rect().size())
|
self.colButton.setIconSize(self.colPixmap.rect().size())
|
||||||
self.colButton.clicked.connect(self._selectColour)
|
self.colButton.clicked.connect(self._selectColour)
|
||||||
@@ -320,6 +335,8 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
self.listControls = QVBoxLayout()
|
self.listControls = QVBoxLayout()
|
||||||
self.listControls.addWidget(self.addButton)
|
self.listControls.addWidget(self.addButton)
|
||||||
self.listControls.addWidget(self.delButton)
|
self.listControls.addWidget(self.delButton)
|
||||||
|
self.listControls.addWidget(self.upButton)
|
||||||
|
self.listControls.addWidget(self.dnButton)
|
||||||
self.listControls.addStretch(1)
|
self.listControls.addStretch(1)
|
||||||
|
|
||||||
self.editBox = QHBoxLayout()
|
self.editBox = QHBoxLayout()
|
||||||
@@ -349,12 +366,15 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
if self.colChanged:
|
if self.colChanged:
|
||||||
newList = []
|
newList = []
|
||||||
for n in range(self.listBox.topLevelItemCount()):
|
for n in range(self.listBox.topLevelItemCount()):
|
||||||
nItem = self.listBox.topLevelItem(n)
|
item = self.listBox.topLevelItem(n)
|
||||||
nIdx = nItem.data(self.COL_LABEL, Qt.UserRole)
|
newList.append({
|
||||||
newList.append(self.colData[nIdx])
|
"key": item.data(self.COL_LABEL, self.KEY_ROLE),
|
||||||
return newList
|
"name": item.text(self.COL_LABEL),
|
||||||
|
"cols": item.data(self.COL_LABEL, self.COL_ROLE),
|
||||||
|
})
|
||||||
|
return newList, self.colDeleted
|
||||||
|
|
||||||
return None
|
return [], []
|
||||||
|
|
||||||
##
|
##
|
||||||
# User Actions
|
# User Actions
|
||||||
@@ -369,16 +389,16 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
)
|
)
|
||||||
if newCol.isValid():
|
if newCol.isValid():
|
||||||
self.selColour = newCol
|
self.selColour = newCol
|
||||||
colPixmap = QPixmap(self.iPx, self.iPx)
|
pixmap = QPixmap(self.iPx, self.iPx)
|
||||||
colPixmap.fill(newCol)
|
pixmap.fill(newCol)
|
||||||
self.colButton.setIcon(QIcon(colPixmap))
|
self.colButton.setIcon(QIcon(pixmap))
|
||||||
self.colButton.setIconSize(colPixmap.rect().size())
|
self.colButton.setIconSize(pixmap.rect().size())
|
||||||
return
|
return
|
||||||
|
|
||||||
def _newItem(self):
|
def _newItem(self):
|
||||||
"""Create a new status item.
|
"""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_LABEL, QBrush(QColor(0, 255, 0, 70)))
|
||||||
newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70)))
|
newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70)))
|
||||||
self.colChanged = True
|
self.colChanged = True
|
||||||
@@ -390,14 +410,14 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
selItem = self._getSelectedItem()
|
selItem = self._getSelectedItem()
|
||||||
if selItem is not None:
|
if selItem is not None:
|
||||||
iRow = self.listBox.indexOfTopLevelItem(selItem)
|
iRow = self.listBox.indexOfTopLevelItem(selItem)
|
||||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
|
||||||
if self.colCounts[selIdx] == 0:
|
|
||||||
self.listBox.takeTopLevelItem(iRow)
|
|
||||||
self.colChanged = True
|
|
||||||
else:
|
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
"Cannot delete a status item that is in use."
|
"Cannot delete a status item that is in use."
|
||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
|
else:
|
||||||
|
self.listBox.takeTopLevelItem(iRow)
|
||||||
|
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
||||||
|
self.colChanged = True
|
||||||
return
|
return
|
||||||
|
|
||||||
def _saveItem(self):
|
def _saveItem(self):
|
||||||
@@ -405,53 +425,75 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
"""
|
"""
|
||||||
selItem = self._getSelectedItem()
|
selItem = self._getSelectedItem()
|
||||||
if selItem is not None:
|
if selItem is not None:
|
||||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
|
||||||
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.setIcon(self.COL_LABEL, self.colButton.icon())
|
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.editName.setEnabled(False)
|
||||||
self.colChanged = True
|
self.colChanged = True
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _addItem(self, iName, iCol, oName, nUse):
|
def _addItem(self, key, name, cols, count):
|
||||||
"""Add a status item to the list.
|
"""Add a status item to the list.
|
||||||
"""
|
"""
|
||||||
newIcon = QPixmap(self.iPx, self.iPx)
|
pixmap = QPixmap(self.iPx, self.iPx)
|
||||||
newIcon.fill(QColor(*iCol))
|
pixmap.fill(QColor(*cols))
|
||||||
newItem = QTreeWidgetItem()
|
|
||||||
newItem.setText(self.COL_LABEL, iName)
|
item = QTreeWidgetItem()
|
||||||
newItem.setText(self.COL_USAGE, self._usageString(nUse))
|
item.setText(self.COL_LABEL, name)
|
||||||
newItem.setIcon(self.COL_LABEL, QIcon(newIcon))
|
item.setIcon(self.COL_LABEL, QIcon(pixmap))
|
||||||
newItem.setData(self.COL_LABEL, Qt.UserRole, len(self.colData))
|
item.setData(self.COL_LABEL, self.KEY_ROLE, key)
|
||||||
self.listBox.addTopLevelItem(newItem)
|
item.setData(self.COL_LABEL, self.COL_ROLE, cols)
|
||||||
self.colData.append((iName, iCol[0], iCol[1], iCol[2], oName))
|
item.setData(self.COL_LABEL, self.NUM_ROLE, count)
|
||||||
self.colCounts.append(nUse)
|
item.setText(self.COL_USAGE, self._usageString(count))
|
||||||
return newItem
|
|
||||||
|
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):
|
def _selectedItem(self):
|
||||||
"""Extract the info of a selected item and populate the settings
|
"""Extract the info of a selected item and populate the settings
|
||||||
boxes and button.
|
boxes and button.
|
||||||
"""
|
"""
|
||||||
selItem = self._getSelectedItem()
|
selItem = self._getSelectedItem()
|
||||||
if selItem is not None:
|
if selItem is None:
|
||||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
return
|
||||||
selVal = self.colData[selIdx]
|
|
||||||
self.selColour = QColor(selVal[1], selVal[2], selVal[3])
|
cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
|
||||||
newIcon = QPixmap(self.iPx, self.iPx)
|
name = selItem.text(self.COL_LABEL)
|
||||||
newIcon.fill(self.selColour)
|
|
||||||
self.editName.setText(selVal[0])
|
pixmap = QPixmap(self.iPx, self.iPx)
|
||||||
self.colButton.setIcon(QIcon(newIcon))
|
pixmap.fill(QColor(*cols))
|
||||||
self.editName.setEnabled(True)
|
self.selColour = QColor(*cols)
|
||||||
self.editName.selectAll()
|
self.editName.setText(name)
|
||||||
self.editName.setFocus()
|
self.colButton.setIcon(QIcon(pixmap))
|
||||||
|
self.editName.setEnabled(True)
|
||||||
|
self.editName.selectAll()
|
||||||
|
self.editName.setFocus()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -467,12 +509,6 @@ class GuiProjectEditStatus(QWidget):
|
|||||||
return selItem[0]
|
return selItem[0]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def _rowsMoved(self):
|
|
||||||
"""A row has been moved, so set the changed flag.
|
|
||||||
"""
|
|
||||||
self.colChanged = True
|
|
||||||
return
|
|
||||||
|
|
||||||
def _usageString(self, nUse):
|
def _usageString(self, nUse):
|
||||||
"""Generate usage string.
|
"""Generate usage string.
|
||||||
"""
|
"""
|
||||||
@@ -498,11 +534,10 @@ class GuiProjectEditReplace(QWidget):
|
|||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theProject = theProject
|
self.theProject = theProject
|
||||||
self.optState = theProject.optState
|
|
||||||
self.arChanged = False
|
self.arChanged = False
|
||||||
|
|
||||||
wCol0 = self.mainConf.pxInt(
|
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")
|
pageLabel = self.tr("Text Replace List for Preview and Export")
|
||||||
|
|
||||||
|
|||||||
@@ -52,19 +52,19 @@ class GuiWordList(QDialog):
|
|||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.optState = theParent.theProject.optState
|
|
||||||
|
|
||||||
self.setWindowTitle(self.tr("Project Word List"))
|
self.setWindowTitle(self.tr("Project Word List"))
|
||||||
|
|
||||||
mS = self.mainConf.pxInt(250)
|
mS = self.mainConf.pxInt(250)
|
||||||
wW = self.mainConf.pxInt(320)
|
wW = self.mainConf.pxInt(320)
|
||||||
wH = self.mainConf.pxInt(340)
|
wH = self.mainConf.pxInt(340)
|
||||||
|
pOptions = self.theProject.options
|
||||||
|
|
||||||
self.setMinimumWidth(mS)
|
self.setMinimumWidth(mS)
|
||||||
self.setMinimumHeight(mS)
|
self.setMinimumHeight(mS)
|
||||||
self.resize(
|
self.resize(
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winWidth", wW)),
|
self.mainConf.pxInt(pOptions.getInt("GuiWordList", "winWidth", wW)),
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winHeight", wH))
|
self.mainConf.pxInt(pOptions.getInt("GuiWordList", "winHeight", wH))
|
||||||
)
|
)
|
||||||
|
|
||||||
# Main Widgets
|
# Main Widgets
|
||||||
@@ -207,8 +207,9 @@ class GuiWordList(QDialog):
|
|||||||
winWidth = self.mainConf.rpxInt(self.width())
|
winWidth = self.mainConf.rpxInt(self.width())
|
||||||
winHeight = self.mainConf.rpxInt(self.height())
|
winHeight = self.mainConf.rpxInt(self.height())
|
||||||
|
|
||||||
self.optState.setValue("GuiWordList", "winWidth", winWidth)
|
pOptions = self.theProject.options
|
||||||
self.optState.setValue("GuiWordList", "winHeight", winHeight)
|
pOptions.setValue("GuiWordList", "winWidth", winWidth)
|
||||||
|
pOptions.setValue("GuiWordList", "winHeight", winHeight)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ class nwItemType(Enum):
|
|||||||
ROOT = 1
|
ROOT = 1
|
||||||
FOLDER = 2
|
FOLDER = 2
|
||||||
FILE = 3
|
FILE = 3
|
||||||
TRASH = 4
|
|
||||||
|
|
||||||
# END Enum nwItemType
|
# END Enum nwItemType
|
||||||
|
|
||||||
@@ -63,6 +62,14 @@ class nwItemLayout(Enum):
|
|||||||
# END Enum nwItemLayout
|
# END Enum nwItemLayout
|
||||||
|
|
||||||
|
|
||||||
|
class nwDocMode(Enum):
|
||||||
|
|
||||||
|
VIEW = 0
|
||||||
|
EDIT = 1
|
||||||
|
|
||||||
|
# END Enum nwDocMode
|
||||||
|
|
||||||
|
|
||||||
class nwDocAction(Enum):
|
class nwDocAction(Enum):
|
||||||
|
|
||||||
NO_ACTION = 0
|
NO_ACTION = 0
|
||||||
@@ -131,6 +138,16 @@ class nwState(Enum):
|
|||||||
# END Enum nwState
|
# END Enum nwState
|
||||||
|
|
||||||
|
|
||||||
|
class nwView(Enum):
|
||||||
|
|
||||||
|
EDITOR = 0
|
||||||
|
PROJECT = 1
|
||||||
|
NOVEL = 2
|
||||||
|
OUTLINE = 3
|
||||||
|
|
||||||
|
# END Enum nwView
|
||||||
|
|
||||||
|
|
||||||
class nwWidget(Enum):
|
class nwWidget(Enum):
|
||||||
|
|
||||||
TREE = 1
|
TREE = 1
|
||||||
|
|||||||
@@ -87,6 +87,8 @@ class NWErrorMessage(QDialog):
|
|||||||
self.mainBox.addWidget(self.btnBox, 2, 0, 1, 2)
|
self.mainBox.addWidget(self.btnBox, 2, 0, 1, 2)
|
||||||
self.mainBox.setSpacing(16)
|
self.mainBox.setSpacing(16)
|
||||||
|
|
||||||
|
# Pick a random window title from a set of error messages by
|
||||||
|
# Hex, the computer, from Discworld
|
||||||
self.setWindowTitle([
|
self.setWindowTitle([
|
||||||
"+++ Out of Cheese Error +++",
|
"+++ Out of Cheese Error +++",
|
||||||
"+++ Divide by Cucumber Error +++",
|
"+++ Divide by Cucumber Error +++",
|
||||||
|
|||||||
@@ -25,10 +25,10 @@ from novelwriter.gui.itemdetails import GuiItemDetails
|
|||||||
from novelwriter.gui.mainmenu import GuiMainMenu
|
from novelwriter.gui.mainmenu import GuiMainMenu
|
||||||
from novelwriter.gui.noveltree import GuiNovelTree
|
from novelwriter.gui.noveltree import GuiNovelTree
|
||||||
from novelwriter.gui.outline import GuiOutline
|
from novelwriter.gui.outline import GuiOutline
|
||||||
from novelwriter.gui.outlinedetails import GuiOutlineDetails
|
|
||||||
from novelwriter.gui.projtree import GuiProjectTree
|
from novelwriter.gui.projtree import GuiProjectTree
|
||||||
from novelwriter.gui.statusbar import GuiMainStatus
|
from novelwriter.gui.statusbar import GuiMainStatus
|
||||||
from novelwriter.gui.theme import GuiTheme
|
from novelwriter.gui.theme import GuiTheme
|
||||||
|
from novelwriter.gui.viewsbar import GuiViewsBar
|
||||||
|
|
||||||
__all__ = [
|
__all__ = [
|
||||||
"GuiDocEditor",
|
"GuiDocEditor",
|
||||||
@@ -39,7 +39,7 @@ __all__ = [
|
|||||||
"GuiMainStatus",
|
"GuiMainStatus",
|
||||||
"GuiNovelTree",
|
"GuiNovelTree",
|
||||||
"GuiOutline",
|
"GuiOutline",
|
||||||
"GuiOutlineDetails",
|
|
||||||
"GuiProjectTree",
|
"GuiProjectTree",
|
||||||
"GuiTheme",
|
"GuiTheme",
|
||||||
|
"GuiViewsBar",
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -409,10 +409,10 @@ class PagedDialog(QDialog):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def addTab(self, tabWidget, tabLabel):
|
def addTab(self, widget, label):
|
||||||
"""Forwards the adding of tabs to the QTabWidget.
|
"""Forwards the adding of tabs to the QTabWidget.
|
||||||
"""
|
"""
|
||||||
self._tabBox.addTab(tabWidget, tabLabel)
|
self._tabBox.addTab(widget, label)
|
||||||
return
|
return
|
||||||
|
|
||||||
def addControls(self, buttonBar):
|
def addControls(self, buttonBar):
|
||||||
@@ -431,15 +431,15 @@ class VerticalTabBar(QTabBar):
|
|||||||
self._mW = novelwriter.CONFIG.pxInt(150)
|
self._mW = novelwriter.CONFIG.pxInt(150)
|
||||||
return
|
return
|
||||||
|
|
||||||
def tabSizeHint(self, theIndex):
|
def tabSizeHint(self, index):
|
||||||
"""Returns a transposed size hint for the rotated bar.
|
"""Returns a transposed size hint for the rotated bar.
|
||||||
"""
|
"""
|
||||||
tSize = QTabBar.tabSizeHint(self, theIndex)
|
tSize = QTabBar.tabSizeHint(self, index)
|
||||||
tSize.transpose()
|
tSize.transpose()
|
||||||
tSize.setWidth(min(tSize.width(), self._mW))
|
tSize.setWidth(min(tSize.width(), self._mW))
|
||||||
return tSize
|
return tSize
|
||||||
|
|
||||||
def paintEvent(self, theEvent):
|
def paintEvent(self, event):
|
||||||
"""Custom implementation of the label painter that rotates the
|
"""Custom implementation of the label painter that rotates the
|
||||||
label 90 degrees.
|
label 90 degrees.
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -33,6 +33,7 @@ import bisect
|
|||||||
import logging
|
import logging
|
||||||
import novelwriter
|
import novelwriter
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
@@ -50,7 +51,7 @@ from PyQt5.QtWidgets import (
|
|||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.core import NWDoc, NWSpellEnchant, countWords
|
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.common import transferCase
|
||||||
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode
|
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode
|
||||||
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
||||||
@@ -69,6 +70,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
spellDictionaryChanged = pyqtSignal(str, str)
|
spellDictionaryChanged = pyqtSignal(str, str)
|
||||||
docEditedStatusChanged = pyqtSignal(bool)
|
docEditedStatusChanged = pyqtSignal(bool)
|
||||||
docCountsChanged = pyqtSignal(str, int, int, int)
|
docCountsChanged = pyqtSignal(str, int, int, int)
|
||||||
|
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||||
|
|
||||||
def __init__(self, theParent):
|
def __init__(self, theParent):
|
||||||
QTextEdit.__init__(self, theParent)
|
QTextEdit.__init__(self, theParent)
|
||||||
@@ -79,7 +81,6 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.mainConf = novelwriter.CONFIG
|
self.mainConf = novelwriter.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theIndex = theParent.theIndex
|
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
|
|
||||||
self._nwDocument = None
|
self._nwDocument = None
|
||||||
@@ -131,8 +132,9 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
# Editor Settings
|
# Editor Settings
|
||||||
self.setMinimumWidth(self.mainConf.pxInt(300))
|
self.setMinimumWidth(self.mainConf.pxInt(300))
|
||||||
self.setAutoFillBackground(True)
|
|
||||||
self.setAcceptRichText(False)
|
self.setAcceptRichText(False)
|
||||||
|
self.setAutoFillBackground(True)
|
||||||
|
self.setFrameStyle(QFrame.NoFrame)
|
||||||
|
|
||||||
# Custom Shortcuts
|
# Custom Shortcuts
|
||||||
QShortcut(
|
QShortcut(
|
||||||
@@ -400,7 +402,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
self.document().rootFrame().setFrameFormat(docFrame)
|
self.document().rootFrame().setFrameFormat(docFrame)
|
||||||
|
|
||||||
self.docFooter.updateLineCount()
|
self.docFooter.updateLineCount()
|
||||||
self._docHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
self._docHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
|
||||||
|
|
||||||
qApp.processEvents()
|
qApp.processEvents()
|
||||||
self.document().clearUndoRedoStacks()
|
self.document().clearUndoRedoStacks()
|
||||||
@@ -505,9 +507,9 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
self.setDocumentChanged(False)
|
self.setDocumentChanged(False)
|
||||||
|
|
||||||
oldHeader = self.theIndex.getHandleHeaderLevel(tHandle)
|
oldHeader = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||||
self.theIndex.scanText(tHandle, docText)
|
self.theProject.index.scanText(tHandle, docText)
|
||||||
newHeader = self.theIndex.getHandleHeaderLevel(tHandle)
|
newHeader = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||||
|
|
||||||
if self._updateHeaders(checkLevel=True):
|
if self._updateHeaders(checkLevel=True):
|
||||||
self.theParent.requestNovelTreeRefresh()
|
self.theParent.requestNovelTreeRefresh()
|
||||||
@@ -567,16 +569,6 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
return
|
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
|
# Properties
|
||||||
##
|
##
|
||||||
@@ -1066,7 +1058,22 @@ class GuiDocEditor(QTextEdit):
|
|||||||
return
|
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)
|
@pyqtSlot(int, int, int)
|
||||||
@@ -1894,7 +1901,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
if loadTag:
|
if loadTag:
|
||||||
logger.verbose("Attempting to follow tag '%s'", theWord)
|
logger.verbose("Attempting to follow tag '%s'", theWord)
|
||||||
self.theParent.docViewer.loadFromTag(theWord)
|
self.loadDocumentTagRequest.emit(theWord, nwDocMode.VIEW)
|
||||||
else:
|
else:
|
||||||
logger.verbose("Potential tag '%s'", theWord)
|
logger.verbose("Potential tag '%s'", theWord)
|
||||||
|
|
||||||
@@ -2002,7 +2009,7 @@ class GuiDocEditor(QTextEdit):
|
|||||||
if self._docHandle is None:
|
if self._docHandle is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
newHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
newHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
|
||||||
if checkPos:
|
if checkPos:
|
||||||
newPos = [x[0] for x in newHeaders]
|
newPos = [x[0] for x in newHeaders]
|
||||||
oldPos = [x[0] for x in self._docHeaders]
|
oldPos = [x[0] for x in self._docHeaders]
|
||||||
@@ -2701,15 +2708,15 @@ class GuiDocEditHeader(QWidget):
|
|||||||
|
|
||||||
if self.mainConf.showFullPath:
|
if self.mainConf.showFullPath:
|
||||||
tTitle = []
|
tTitle = []
|
||||||
tTree = self.theProject.projTree.getItemPath(tHandle)
|
tTree = self.theProject.tree.getItemPath(tHandle)
|
||||||
for aHandle in reversed(tTree):
|
for aHandle in reversed(tTree):
|
||||||
nwItem = self.theProject.projTree[aHandle]
|
nwItem = self.theProject.tree[aHandle]
|
||||||
if nwItem is not None:
|
if nwItem is not None:
|
||||||
tTitle.append(nwItem.itemName)
|
tTitle.append(nwItem.itemName)
|
||||||
sSep = " %s " % nwUnicode.U_RSAQUO
|
sSep = " %s " % nwUnicode.U_RSAQUO
|
||||||
self.theTitle.setText(sSep.join(tTitle))
|
self.theTitle.setText(sSep.join(tTitle))
|
||||||
else:
|
else:
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.tree[tHandle]
|
||||||
if nwItem is None:
|
if nwItem is None:
|
||||||
return False
|
return False
|
||||||
self.theTitle.setText(nwItem.itemName)
|
self.theTitle.setText(nwItem.itemName)
|
||||||
@@ -2795,7 +2802,6 @@ class GuiDocEditFooter(QWidget):
|
|||||||
self.theParent = docEditor.theParent
|
self.theParent = docEditor.theParent
|
||||||
self.theProject = docEditor.theProject
|
self.theProject = docEditor.theProject
|
||||||
self.theTheme = docEditor.theTheme
|
self.theTheme = docEditor.theTheme
|
||||||
self.optState = docEditor.theProject.optState
|
|
||||||
|
|
||||||
self._theItem = None
|
self._theItem = None
|
||||||
self._docHandle = None
|
self._docHandle = None
|
||||||
@@ -2918,7 +2924,7 @@ class GuiDocEditFooter(QWidget):
|
|||||||
logger.verbose("No handle set, so clearing the editor footer")
|
logger.verbose("No handle set, so clearing the editor footer")
|
||||||
self._theItem = None
|
self._theItem = None
|
||||||
else:
|
else:
|
||||||
self._theItem = self.theProject.projTree[self._docHandle]
|
self._theItem = self.theProject.tree[self._docHandle]
|
||||||
|
|
||||||
self.setHasSelection(False)
|
self.setHasSelection(False)
|
||||||
self.updateInfo()
|
self.updateInfo()
|
||||||
@@ -2942,7 +2948,7 @@ class GuiDocEditFooter(QWidget):
|
|||||||
else:
|
else:
|
||||||
theStatus, theIcon = self._theItem.getImportStatus()
|
theStatus, theIcon = self._theItem.getImportStatus()
|
||||||
sIcon = theIcon.pixmap(self.sPx, self.sPx)
|
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)}"
|
sText = f"{theStatus} / {self._theItem.describeMe(hLevel)}"
|
||||||
|
|
||||||
self.statusIcon.setPixmap(sIcon)
|
self.statusIcon.setPixmap(sIcon)
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
self.spEnchant = spEnchant
|
self.spEnchant = spEnchant
|
||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theIndex = theParent.theIndex
|
self.theProject = theParent.theProject
|
||||||
self.theHandle = None
|
self.theHandle = None
|
||||||
self.spellCheck = False
|
self.spellCheck = False
|
||||||
self.spellRx = None
|
self.spellRx = None
|
||||||
@@ -287,9 +287,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
|||||||
|
|
||||||
if theText.startswith("@"): # Keywords and commands
|
if theText.startswith("@"): # Keywords and commands
|
||||||
self.setCurrentBlockState(self.BLOCK_META)
|
self.setCurrentBlockState(self.BLOCK_META)
|
||||||
tItem = self.theParent.theProject.projTree[self.theHandle]
|
pIndex = self.theProject.index
|
||||||
isValid, theBits, thePos = self.theIndex.scanThis(theText)
|
tItem = self.theParent.theProject.tree[self.theHandle]
|
||||||
isGood = self.theIndex.checkThese(theBits, tItem)
|
isValid, theBits, thePos = pIndex.scanThis(theText)
|
||||||
|
isGood = pIndex.checkThese(theBits, tItem)
|
||||||
if isValid:
|
if isValid:
|
||||||
for n, theBit in enumerate(theBits):
|
for n, theBit in enumerate(theBits):
|
||||||
xPos = thePos[n]
|
xPos = thePos[n]
|
||||||
|
|||||||
@@ -30,17 +30,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|||||||
import logging
|
import logging
|
||||||
import novelwriter
|
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 (
|
from PyQt5.QtGui import (
|
||||||
QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon, QCursor
|
QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon, QCursor
|
||||||
)
|
)
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QTextBrowser, QWidget, QScrollArea, QLabel, QHBoxLayout, QToolButton,
|
qApp, QTextBrowser, QWidget, QScrollArea, QLabel, QHBoxLayout, QToolButton,
|
||||||
QAction, QMenu
|
QAction, QMenu, QFrame
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.core import ToHtml
|
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.error import logException
|
||||||
from novelwriter.constants import nwUnicode
|
from novelwriter.constants import nwUnicode
|
||||||
|
|
||||||
@@ -49,6 +51,8 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
class GuiDocViewer(QTextBrowser):
|
class GuiDocViewer(QTextBrowser):
|
||||||
|
|
||||||
|
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||||
|
|
||||||
def __init__(self, theParent):
|
def __init__(self, theParent):
|
||||||
QTextBrowser.__init__(self, theParent)
|
QTextBrowser.__init__(self, theParent)
|
||||||
|
|
||||||
@@ -68,6 +72,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
self.setAutoFillBackground(True)
|
self.setAutoFillBackground(True)
|
||||||
self.setOpenExternalLinks(False)
|
self.setOpenExternalLinks(False)
|
||||||
self.setFocusPolicy(Qt.StrongFocus)
|
self.setFocusPolicy(Qt.StrongFocus)
|
||||||
|
self.setFrameStyle(QFrame.NoFrame)
|
||||||
|
|
||||||
# Document Header and Footer
|
# Document Header and Footer
|
||||||
self.docHeader = GuiDocViewHeader(self)
|
self.docHeader = GuiDocViewHeader(self)
|
||||||
@@ -159,7 +164,7 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
def loadText(self, tHandle, updateHistory=True):
|
def loadText(self, tHandle, updateHistory=True):
|
||||||
"""Load text into the viewer from an item handle.
|
"""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")
|
logger.warning("Item not found")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -238,30 +243,6 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
self.updateDocMargins()
|
self.updateDocMargins()
|
||||||
return
|
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):
|
def docAction(self, theAction):
|
||||||
"""Wrapper function for various document actions on the current
|
"""Wrapper function for various document actions on the current
|
||||||
document.
|
document.
|
||||||
@@ -341,15 +322,6 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
|
|
||||||
return
|
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
|
# Properties
|
||||||
##
|
##
|
||||||
@@ -408,19 +380,33 @@ class GuiDocViewer(QTextBrowser):
|
|||||||
return 0
|
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")
|
@pyqtSlot("QUrl")
|
||||||
def _linkClicked(self, theURL):
|
def _linkClicked(self, theURL):
|
||||||
"""Slot for a link in the document being clicked.
|
"""Process a clicked link internally in the document.
|
||||||
"""
|
"""
|
||||||
theLink = theURL.url()
|
theLink = theURL.url()
|
||||||
logger.verbose("Clicked link: '%s'", theLink)
|
logger.verbose("Clicked link: '%s'", theLink)
|
||||||
if len(theLink) > 0:
|
if len(theLink) > 0:
|
||||||
theBits = theLink.split("=")
|
theBits = theLink.split("=")
|
||||||
if len(theBits) == 2:
|
if len(theBits) == 2:
|
||||||
self.loadFromTag(theBits[1])
|
self.loadDocumentTagRequest.emit(theBits[1], nwDocMode.VIEW)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot("QPoint")
|
@pyqtSlot("QPoint")
|
||||||
@@ -862,15 +848,15 @@ class GuiDocViewHeader(QWidget):
|
|||||||
|
|
||||||
if self.mainConf.showFullPath:
|
if self.mainConf.showFullPath:
|
||||||
tTitle = []
|
tTitle = []
|
||||||
tTree = self.theProject.projTree.getItemPath(tHandle)
|
tTree = self.theProject.tree.getItemPath(tHandle)
|
||||||
for aHandle in reversed(tTree):
|
for aHandle in reversed(tTree):
|
||||||
nwItem = self.theProject.projTree[aHandle]
|
nwItem = self.theProject.tree[aHandle]
|
||||||
if nwItem is not None:
|
if nwItem is not None:
|
||||||
tTitle.append(nwItem.itemName)
|
tTitle.append(nwItem.itemName)
|
||||||
sSep = " %s " % nwUnicode.U_RSAQUO
|
sSep = " %s " % nwUnicode.U_RSAQUO
|
||||||
self.theTitle.setText(sSep.join(tTitle))
|
self.theTitle.setText(sSep.join(tTitle))
|
||||||
else:
|
else:
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.tree[tHandle]
|
||||||
if nwItem is None:
|
if nwItem is None:
|
||||||
return False
|
return False
|
||||||
self.theTitle.setText(nwItem.itemName)
|
self.theTitle.setText(nwItem.itemName)
|
||||||
@@ -1185,6 +1171,7 @@ class GuiDocViewDetails(QScrollArea):
|
|||||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||||
self.setWidgetResizable(True)
|
self.setWidgetResizable(True)
|
||||||
self.setMinimumHeight(self.mainConf.pxInt(50))
|
self.setMinimumHeight(self.mainConf.pxInt(50))
|
||||||
|
self.setFrameStyle(QFrame.NoFrame)
|
||||||
|
|
||||||
logger.debug("GuiDocViewDetails initialisation complete")
|
logger.debug("GuiDocViewDetails initialisation complete")
|
||||||
|
|
||||||
@@ -1197,10 +1184,10 @@ class GuiDocViewDetails(QScrollArea):
|
|||||||
if self.theParent.docViewer.stickyRef:
|
if self.theParent.docViewer.stickyRef:
|
||||||
return
|
return
|
||||||
|
|
||||||
theRefs = self.theParent.theIndex.getBackReferenceList(tHandle)
|
theRefs = self.theProject.index.getBackReferenceList(tHandle)
|
||||||
theList = []
|
theList = []
|
||||||
for tHandle in theRefs:
|
for tHandle in theRefs:
|
||||||
tItem = self.theProject.projTree[tHandle]
|
tItem = self.theProject.tree[tHandle]
|
||||||
if tItem is not None:
|
if tItem is not None:
|
||||||
theList.append("<a href='%s#%s' %s>%s</a>" % (
|
theList.append("<a href='%s#%s' %s>%s</a>" % (
|
||||||
tHandle, theRefs[tHandle], self.linkStyle, tItem.itemName
|
tHandle, theRefs[tHandle], self.linkStyle, tItem.itemName
|
||||||
|
|||||||
@@ -115,6 +115,7 @@ class GuiItemDetails(QWidget):
|
|||||||
self.usageData = QLabel("")
|
self.usageData = QLabel("")
|
||||||
self.usageData.setFont(fntValue)
|
self.usageData.setFont(fntValue)
|
||||||
self.usageData.setAlignment(Qt.AlignLeft)
|
self.usageData.setAlignment(Qt.AlignLeft)
|
||||||
|
self.usageData.setWordWrap(True)
|
||||||
|
|
||||||
# Character Count
|
# Character Count
|
||||||
self.cCountName = QLabel(" "+self.tr("Characters"))
|
self.cCountName = QLabel(" "+self.tr("Characters"))
|
||||||
@@ -214,6 +215,16 @@ class GuiItemDetails(QWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def refreshDetails(self):
|
||||||
|
"""Reload the content of the details panel.
|
||||||
|
"""
|
||||||
|
self.updateViewBox(self._itemHandle)
|
||||||
|
|
||||||
|
##
|
||||||
|
# Public Slots
|
||||||
|
##
|
||||||
|
|
||||||
|
@pyqtSlot(str)
|
||||||
def updateViewBox(self, tHandle):
|
def updateViewBox(self, tHandle):
|
||||||
"""Populate the details box from a given handle.
|
"""Populate the details box from a given handle.
|
||||||
"""
|
"""
|
||||||
@@ -221,7 +232,7 @@ class GuiItemDetails(QWidget):
|
|||||||
self.clearDetails()
|
self.clearDetails()
|
||||||
return
|
return
|
||||||
|
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.tree[tHandle]
|
||||||
if nwItem is None:
|
if nwItem is None:
|
||||||
self.clearDetails()
|
self.clearDetails()
|
||||||
return
|
return
|
||||||
@@ -263,7 +274,7 @@ class GuiItemDetails(QWidget):
|
|||||||
# Layout
|
# Layout
|
||||||
# ======
|
# ======
|
||||||
|
|
||||||
hLevel = self.theParent.theIndex.getHandleHeaderLevel(tHandle)
|
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||||
usageIcon = self.theTheme.getItemIcon(
|
usageIcon = self.theTheme.getItemIcon(
|
||||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||||||
)
|
)
|
||||||
@@ -284,10 +295,6 @@ class GuiItemDetails(QWidget):
|
|||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
##
|
|
||||||
# Slots
|
|
||||||
##
|
|
||||||
|
|
||||||
@pyqtSlot(str, int, int, int)
|
@pyqtSlot(str, int, int, int)
|
||||||
def doUpdateCounts(self, tHandle, cC, wC, pC):
|
def doUpdateCounts(self, tHandle, cC, wC, pC):
|
||||||
"""Update the counts if the handle is the same as the one we're
|
"""Update the counts if the handle is the same as the one we're
|
||||||
|
|||||||
@@ -71,24 +71,6 @@ class GuiMainMenu(QMenuBar):
|
|||||||
|
|
||||||
return
|
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
|
# Update Menu on Settings Changed
|
||||||
##
|
##
|
||||||
@@ -99,12 +81,6 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aSpellCheck.setChecked(theMode)
|
self.aSpellCheck.setChecked(theMode)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setAutoOutline(self, theMode):
|
|
||||||
"""Forward auto outline check state to its action.
|
|
||||||
"""
|
|
||||||
self.aAutoOutline.setChecked(theMode)
|
|
||||||
return
|
|
||||||
|
|
||||||
def setFocusMode(self, theMode):
|
def setFocusMode(self, theMode):
|
||||||
"""Forward focus mode check state to its action.
|
"""Forward focus mode check state to its action.
|
||||||
"""
|
"""
|
||||||
@@ -123,12 +99,6 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.theParent.docEditor.toggleSpellCheck(None)
|
self.theParent.docEditor.toggleSpellCheck(None)
|
||||||
return True
|
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):
|
def _openWebsite(self, theUrl):
|
||||||
"""Open a URL in the system's default browser.
|
"""Open a URL in the system's default browser.
|
||||||
"""
|
"""
|
||||||
@@ -215,7 +185,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
# Project > New Folder
|
# Project > New Folder
|
||||||
self.aCreateFolder = QAction(self.tr("Create Folder"), self)
|
self.aCreateFolder = QAction(self.tr("Create Folder"), self)
|
||||||
self.aCreateFolder.setShortcut("Ctrl+Shift+N")
|
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)
|
self.projMenu.addAction(self.aCreateFolder)
|
||||||
|
|
||||||
# Project > Separator
|
# Project > Separator
|
||||||
@@ -277,7 +247,7 @@ class GuiMainMenu(QMenuBar):
|
|||||||
# Document > New
|
# Document > New
|
||||||
self.aNewDoc = QAction(self.tr("New Document"), self)
|
self.aNewDoc = QAction(self.tr("New Document"), self)
|
||||||
self.aNewDoc.setShortcut("Ctrl+N")
|
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)
|
self.docuMenu.addAction(self.aNewDoc)
|
||||||
|
|
||||||
# Document > Open
|
# Document > Open
|
||||||
@@ -907,19 +877,6 @@ class GuiMainMenu(QMenuBar):
|
|||||||
self.aRebuildIndex.triggered.connect(lambda: self.theParent.rebuildIndex())
|
self.aRebuildIndex.triggered.connect(lambda: self.theParent.rebuildIndex())
|
||||||
self.toolsMenu.addAction(self.aRebuildIndex)
|
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
|
# Tools > Separator
|
||||||
self.toolsMenu.addSeparator()
|
self.toolsMenu.addSeparator()
|
||||||
|
|
||||||
|
|||||||
@@ -29,7 +29,9 @@ import novelwriter
|
|||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QSize
|
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.common import checkInt
|
||||||
from novelwriter.constants import nwKeyWords
|
from novelwriter.constants import nwKeyWords
|
||||||
@@ -52,7 +54,6 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.theIndex = theParent.theIndex
|
|
||||||
|
|
||||||
# Internal Variables
|
# Internal Variables
|
||||||
self._treeMap = {}
|
self._treeMap = {}
|
||||||
@@ -60,6 +61,7 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
|
|
||||||
# Build GUI
|
# Build GUI
|
||||||
iPx = self.theTheme.baseIconSize
|
iPx = self.theTheme.baseIconSize
|
||||||
|
self.setFrameStyle(QFrame.NoFrame)
|
||||||
self.setIconSize(QSize(iPx, iPx))
|
self.setIconSize(QSize(iPx, iPx))
|
||||||
self.setIndentation(iPx)
|
self.setIndentation(iPx)
|
||||||
self.setColumnCount(3)
|
self.setColumnCount(3)
|
||||||
@@ -134,7 +136,7 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
"""
|
"""
|
||||||
logger.verbose("Requesting refresh of the novel tree")
|
logger.verbose("Requesting refresh of the novel tree")
|
||||||
treeChanged = self.theParent.treeView.changedSince(self._lastBuild)
|
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):
|
if not (treeChanged or indexChanged or overRide):
|
||||||
logger.verbose("No changes have been made to the novel index")
|
logger.verbose("No changes have been made to the novel index")
|
||||||
return
|
return
|
||||||
@@ -144,7 +146,6 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
if selItem:
|
if selItem:
|
||||||
titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2]
|
titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2]
|
||||||
|
|
||||||
self.theParent.treeView.flushTreeOrder()
|
|
||||||
self._populateTree()
|
self._populateTree()
|
||||||
|
|
||||||
if titleKey is not None and titleKey in self._treeMap:
|
if titleKey is not None and titleKey in self._treeMap:
|
||||||
@@ -155,7 +156,7 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
def updateWordCounts(self, tHandle):
|
def updateWordCounts(self, tHandle):
|
||||||
"""Update the word count for a given handle.
|
"""Update the word count for a given handle.
|
||||||
"""
|
"""
|
||||||
tHeaders = self.theIndex.getHandleWordCounts(tHandle)
|
tHeaders = self.theProject.index.getHandleWordCounts(tHandle)
|
||||||
for titleKey, wCount in tHeaders:
|
for titleKey, wCount in tHeaders:
|
||||||
if titleKey in self._treeMap:
|
if titleKey in self._treeMap:
|
||||||
self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}")
|
self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}")
|
||||||
@@ -249,12 +250,12 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
currChapter = None
|
currChapter = None
|
||||||
currScene = 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)
|
tItem = self._createTreeItem(tHandle, sTitle, tKey, novIdx)
|
||||||
self._treeMap[tKey] = tItem
|
self._treeMap[tKey] = tItem
|
||||||
|
|
||||||
tLevel = novIdx["level"]
|
tLevel = novIdx.level
|
||||||
if tLevel == "H1":
|
if tLevel == "H1":
|
||||||
self.addTopLevelItem(tItem)
|
self.addTopLevelItem(tItem)
|
||||||
currTitle = tItem
|
currTitle = tItem
|
||||||
@@ -301,18 +302,18 @@ class GuiNovelTree(QTreeWidget):
|
|||||||
"""Populate a tree item with all the column values.
|
"""Populate a tree item with all the column values.
|
||||||
"""
|
"""
|
||||||
newItem = QTreeWidgetItem()
|
newItem = QTreeWidgetItem()
|
||||||
hIcon = "doc_%s" % novIdx["level"].lower()
|
hIcon = "doc_%s" % novIdx.level.lower()
|
||||||
theData = (tHandle, sTitle[1:].lstrip("0"), titleKey)
|
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.setData(self.C_TITLE, Qt.UserRole, theData)
|
||||||
newItem.setIcon(self.C_TITLE, self.theTheme.getIcon(hIcon))
|
newItem.setIcon(self.C_TITLE, self.theTheme.getIcon(hIcon))
|
||||||
newItem.setText(self.C_WORDS, f"{wC:n}")
|
newItem.setText(self.C_WORDS, f"{wC:n}")
|
||||||
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
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]))
|
newItem.setText(self.C_POV, ", ".join(theRefs[nwKeyWords.POV_KEY]))
|
||||||
|
|
||||||
return newItem
|
return newItem
|
||||||
|
|||||||
@@ -4,7 +4,11 @@ novelWriter – GUI Project Outline
|
|||||||
GUI class for the project outline view
|
GUI class for the project outline view
|
||||||
|
|
||||||
File History:
|
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
|
This file is a part of novelWriter
|
||||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
Copyright 2018–2022, Veronica Berglyd Olsen
|
||||||
@@ -27,20 +31,244 @@ import logging
|
|||||||
import novelwriter
|
import novelwriter
|
||||||
|
|
||||||
from time import time
|
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 (
|
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.common import checkInt
|
||||||
from novelwriter.constants import trConst, nwKeyWords, nwLabels
|
from novelwriter.constants import trConst, nwKeyWords, nwLabels
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
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 = {
|
DEF_WIDTH = {
|
||||||
nwOutline.TITLE: 200,
|
nwOutline.TITLE: 200,
|
||||||
@@ -82,19 +310,20 @@ class GuiOutline(QTreeWidget):
|
|||||||
nwOutline.SYNOP: False,
|
nwOutline.SYNOP: False,
|
||||||
}
|
}
|
||||||
|
|
||||||
def __init__(self, theParent):
|
hiddenStateChanged = pyqtSignal()
|
||||||
QTreeWidget.__init__(self, theParent)
|
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.mainConf = novelwriter.CONFIG
|
||||||
self.theParent = theParent
|
self.theParent = theOutline.theParent
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theOutline.theParent.theProject
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theOutline.theParent.theTheme
|
||||||
self.theIndex = theParent.theIndex
|
|
||||||
self.optState = theParent.theProject.optState
|
|
||||||
self.headerMenu = GuiOutlineHeaderMenu(self)
|
|
||||||
|
|
||||||
|
self.setFrameStyle(QFrame.NoFrame)
|
||||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||||
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||||
self.setExpandsOnDoubleClick(False)
|
self.setExpandsOnDoubleClick(False)
|
||||||
@@ -107,8 +336,6 @@ class GuiOutline(QTreeWidget):
|
|||||||
self.setIndentation(iPx)
|
self.setIndentation(iPx)
|
||||||
|
|
||||||
self.treeHead = self.header()
|
self.treeHead = self.header()
|
||||||
self.treeHead.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
||||||
self.treeHead.customContextMenuRequested.connect(self._headerRightClick)
|
|
||||||
self.treeHead.sectionMoved.connect(self._columnMoved)
|
self.treeHead.sectionMoved.connect(self._columnMoved)
|
||||||
|
|
||||||
# Internals
|
# Internals
|
||||||
@@ -122,12 +349,25 @@ class GuiOutline(QTreeWidget):
|
|||||||
|
|
||||||
self.initOutline()
|
self.initOutline()
|
||||||
self.clearOutline()
|
self.clearOutline()
|
||||||
self.headerMenu.setHiddenState(self._colHidden)
|
|
||||||
|
|
||||||
logger.debug("GuiOutline initialisation complete")
|
self.hiddenStateChanged.emit()
|
||||||
|
|
||||||
|
logger.debug("GuiOutlineView initialisation complete")
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Properties
|
||||||
|
##
|
||||||
|
|
||||||
|
@property
|
||||||
|
def hiddenColumns(self):
|
||||||
|
return self._colHidden
|
||||||
|
|
||||||
|
##
|
||||||
|
# Methods
|
||||||
|
##
|
||||||
|
|
||||||
def initOutline(self):
|
def initOutline(self):
|
||||||
"""Set or update outline settings.
|
"""Set or update outline settings.
|
||||||
"""
|
"""
|
||||||
@@ -167,7 +407,7 @@ class GuiOutline(QTreeWidget):
|
|||||||
|
|
||||||
return
|
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
|
"""Called whenever the Outline tab is activated and controls
|
||||||
what data to load, and if necessary, force a rebuild of the
|
what data to load, and if necessary, force a rebuild of the
|
||||||
tree.
|
tree.
|
||||||
@@ -175,17 +415,17 @@ class GuiOutline(QTreeWidget):
|
|||||||
# If it's the first time, we always build
|
# If it's the first time, we always build
|
||||||
if self._firstView or self._firstView and overRide:
|
if self._firstView or self._firstView and overRide:
|
||||||
self._loadHeaderState()
|
self._loadHeaderState()
|
||||||
self._populateTree()
|
self._populateTree(rootHandle)
|
||||||
self._firstView = False
|
self._firstView = False
|
||||||
return
|
return
|
||||||
|
|
||||||
# If the novel index or novel tree has changed since the tree
|
# If the novel index or novel tree has changed since the tree
|
||||||
# was last built, we rebuild the tree from the updated index.
|
# 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
|
doBuild = (novelChanged or indexChanged) and self.theProject.autoOutline
|
||||||
if doBuild or overRide:
|
if doBuild or overRide:
|
||||||
logger.debug("Rebuilding Project Outline")
|
logger.debug("Rebuilding Project Outline")
|
||||||
self._populateTree()
|
self._populateTree(rootHandle)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -221,7 +461,7 @@ class GuiOutline(QTreeWidget):
|
|||||||
document editor.
|
document editor.
|
||||||
"""
|
"""
|
||||||
tHandle, tLine = self.getSelectedHandle()
|
tHandle, tLine = self.getSelectedHandle()
|
||||||
self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True)
|
self.theParent.openDocument(tHandle, tLine=tLine - 1, doScroll=True)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -233,18 +473,10 @@ class GuiOutline(QTreeWidget):
|
|||||||
if selItems:
|
if selItems:
|
||||||
tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], Qt.UserRole)
|
tHandle = selItems[0].data(self._colIdx[nwOutline.TITLE], Qt.UserRole)
|
||||||
sTitle = selItems[0].data(self._colIdx[nwOutline.LINE], Qt.UserRole)
|
sTitle = selItems[0].data(self._colIdx[nwOutline.LINE], Qt.UserRole)
|
||||||
self.theParent.projMeta.showItem(tHandle, sTitle)
|
self.activeItemChanged.emit(tHandle, sTitle)
|
||||||
self.theParent.treeView.setSelectedHandle(tHandle)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot("QPoint")
|
|
||||||
def _headerRightClick(self, clickPos):
|
|
||||||
"""Show the header column menu.
|
|
||||||
"""
|
|
||||||
self.headerMenu.exec_(self.mapToGlobal(clickPos))
|
|
||||||
return
|
|
||||||
|
|
||||||
@pyqtSlot(int, int, int)
|
@pyqtSlot(int, int, int)
|
||||||
def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx):
|
def _columnMoved(self, logIdx, oldVisualIdx, newVisualIdx):
|
||||||
"""Make sure the order array is up to date with the actual order
|
"""Make sure the order array is up to date with the actual order
|
||||||
@@ -254,9 +486,10 @@ class GuiOutline(QTreeWidget):
|
|||||||
self._saveHeaderState()
|
self._saveHeaderState()
|
||||||
return
|
return
|
||||||
|
|
||||||
def _menuColumnToggled(self, isChecked, theItem):
|
@pyqtSlot(bool, Enum)
|
||||||
|
def menuColumnToggled(self, isChecked, theItem):
|
||||||
"""Receive the changes to column visibility forwarded by the
|
"""Receive the changes to column visibility forwarded by the
|
||||||
header context menu.
|
column selection menu.
|
||||||
"""
|
"""
|
||||||
logger.verbose("User toggled Outline column '%s'", theItem.name)
|
logger.verbose("User toggled Outline column '%s'", theItem.name)
|
||||||
if theItem in self._colIdx:
|
if theItem in self._colIdx:
|
||||||
@@ -273,10 +506,12 @@ class GuiOutline(QTreeWidget):
|
|||||||
"""Load the state of the main tree header, that is, column order
|
"""Load the state of the main tree header, that is, column order
|
||||||
and column width.
|
and column width.
|
||||||
"""
|
"""
|
||||||
|
pOptions = self.theProject.options
|
||||||
|
|
||||||
# Load whatever we saved last time, regardless of wether it
|
# Load whatever we saved last time, regardless of wether it
|
||||||
# contains the correct names or number of columns. The names
|
# contains the correct names or number of columns. The names
|
||||||
# must be valid though.
|
# must be valid though.
|
||||||
tempOrder = self.optState.getValue("GuiOutline", "headerOrder", [])
|
tempOrder = pOptions.getValue("GuiOutline", "headerOrder", [])
|
||||||
treeOrder = []
|
treeOrder = []
|
||||||
for hName in tempOrder:
|
for hName in tempOrder:
|
||||||
try:
|
try:
|
||||||
@@ -299,21 +534,21 @@ class GuiOutline(QTreeWidget):
|
|||||||
|
|
||||||
# We load whatever column widths and hidden states we find in
|
# We load whatever column widths and hidden states we find in
|
||||||
# the file, and leave the rest in their default state.
|
# 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:
|
for hName in tmpWidth:
|
||||||
try:
|
try:
|
||||||
self._colWidth[nwOutline[hName]] = self.mainConf.pxInt(tmpWidth[hName])
|
self._colWidth[nwOutline[hName]] = self.mainConf.pxInt(tmpWidth[hName])
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("Ignored unknown outline column '%s'", str(hName))
|
logger.warning("Ignored unknown outline column '%s'", str(hName))
|
||||||
|
|
||||||
tmpHidden = self.optState.getValue("GuiOutline", "columnHidden", {})
|
tmpHidden = pOptions.getValue("GuiOutline", "columnHidden", {})
|
||||||
for hName in tmpHidden:
|
for hName in tmpHidden:
|
||||||
try:
|
try:
|
||||||
self._colHidden[nwOutline[hName]] = tmpHidden[hName]
|
self._colHidden[nwOutline[hName]] = tmpHidden[hName]
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.warning("Ignored unknown outline column '%s'", str(hName))
|
logger.warning("Ignored unknown outline column '%s'", str(hName))
|
||||||
|
|
||||||
self.headerMenu.setHiddenState(self._colHidden)
|
self.hiddenStateChanged.emit()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -347,14 +582,15 @@ class GuiOutline(QTreeWidget):
|
|||||||
if not logHidden and logWidth > 0:
|
if not logHidden and logWidth > 0:
|
||||||
colWidth[hName] = logWidth
|
colWidth[hName] = logWidth
|
||||||
|
|
||||||
self.optState.setValue("GuiOutline", "headerOrder", treeOrder)
|
pOptions = self.theProject.options
|
||||||
self.optState.setValue("GuiOutline", "columnWidth", colWidth)
|
pOptions.setValue("GuiOutline", "headerOrder", treeOrder)
|
||||||
self.optState.setValue("GuiOutline", "columnHidden", colHidden)
|
pOptions.setValue("GuiOutline", "columnWidth", colWidth)
|
||||||
self.optState.saveSettings()
|
pOptions.setValue("GuiOutline", "columnHidden", colHidden)
|
||||||
|
pOptions.saveSettings()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def _populateTree(self):
|
def _populateTree(self, rootHandle):
|
||||||
"""Build the tree based on the project index, and the header
|
"""Build the tree based on the project index, and the header
|
||||||
based on the defined constants, default values and user selected
|
based on the defined constants, default values and user selected
|
||||||
width, order and hidden state. All columns are populated, even
|
width, order and hidden state. All columns are populated, even
|
||||||
@@ -387,11 +623,12 @@ class GuiOutline(QTreeWidget):
|
|||||||
currChapter = None
|
currChapter = None
|
||||||
currScene = 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)
|
tItem = self._createTreeItem(tHandle, sTitle, novIdx)
|
||||||
|
|
||||||
tLevel = novIdx["level"]
|
tLevel = novIdx.level
|
||||||
if tLevel == "H1":
|
if tLevel == "H1":
|
||||||
self.addTopLevelItem(tItem)
|
self.addTopLevelItem(tItem)
|
||||||
currTitle = tItem
|
currTitle = tItem
|
||||||
@@ -437,26 +674,26 @@ class GuiOutline(QTreeWidget):
|
|||||||
def _createTreeItem(self, tHandle, sTitle, novIdx):
|
def _createTreeItem(self, tHandle, sTitle, novIdx):
|
||||||
"""Populate a tree item with all the column values.
|
"""Populate a tree item with all the column values.
|
||||||
"""
|
"""
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.tree[tHandle]
|
||||||
newItem = QTreeWidgetItem()
|
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)
|
dIcon = self.theTheme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, hLevel)
|
||||||
|
|
||||||
cC = int(novIdx["cCount"])
|
cC = int(novIdx.charCount)
|
||||||
wC = int(novIdx["wCount"])
|
wC = int(novIdx.wordCount)
|
||||||
pC = int(novIdx["pCount"])
|
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.setData(self._colIdx[nwOutline.TITLE], Qt.UserRole, tHandle)
|
||||||
newItem.setIcon(self._colIdx[nwOutline.TITLE], self.theTheme.getIcon(hIcon))
|
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.setText(self._colIdx[nwOutline.LABEL], nwItem.itemName)
|
||||||
newItem.setIcon(self._colIdx[nwOutline.LABEL], dIcon)
|
newItem.setIcon(self._colIdx[nwOutline.LABEL], dIcon)
|
||||||
newItem.setText(self._colIdx[nwOutline.LINE], sTitle[1:].lstrip("0"))
|
newItem.setText(self._colIdx[nwOutline.LINE], sTitle[1:].lstrip("0"))
|
||||||
newItem.setData(self._colIdx[nwOutline.LINE], Qt.UserRole, sTitle)
|
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.CCOUNT], f"{cC:n}")
|
||||||
newItem.setText(self._colIdx[nwOutline.WCOUNT], f"{wC:n}")
|
newItem.setText(self._colIdx[nwOutline.WCOUNT], f"{wC:n}")
|
||||||
newItem.setText(self._colIdx[nwOutline.PCOUNT], f"{pC: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.WCOUNT], Qt.AlignRight)
|
||||||
newItem.setTextAlignment(self._colIdx[nwOutline.PCOUNT], 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.POV], ", ".join(theRefs[nwKeyWords.POV_KEY]))
|
||||||
newItem.setText(self._colIdx[nwOutline.FOCUS], ", ".join(theRefs[nwKeyWords.FOCUS_KEY]))
|
newItem.setText(self._colIdx[nwOutline.FOCUS], ", ".join(theRefs[nwKeyWords.FOCUS_KEY]))
|
||||||
newItem.setText(self._colIdx[nwOutline.CHAR], ", ".join(theRefs[nwKeyWords.CHAR_KEY]))
|
newItem.setText(self._colIdx[nwOutline.CHAR], ", ".join(theRefs[nwKeyWords.CHAR_KEY]))
|
||||||
@@ -477,15 +714,16 @@ class GuiOutline(QTreeWidget):
|
|||||||
|
|
||||||
return newItem
|
return newItem
|
||||||
|
|
||||||
# END Class GuiOutline
|
# END Class GuiOutlineView
|
||||||
|
|
||||||
|
|
||||||
class GuiOutlineHeaderMenu(QMenu):
|
class GuiOutlineHeaderMenu(QMenu):
|
||||||
|
|
||||||
def __init__(self, theParent):
|
columnToggled = pyqtSignal(bool, Enum)
|
||||||
QMenu.__init__(self, theParent)
|
|
||||||
|
def __init__(self, theOutline):
|
||||||
|
QMenu.__init__(self, theOutline)
|
||||||
|
|
||||||
self.theParent = theParent
|
|
||||||
self.acceptToggle = True
|
self.acceptToggle = True
|
||||||
|
|
||||||
mnuHead = QAction(self.tr("Select Columns"), self)
|
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] = QAction(trConst(nwLabels.OUTLINE_COLS[hItem]), self)
|
||||||
self.actionMap[hItem].setCheckable(True)
|
self.actionMap[hItem].setCheckable(True)
|
||||||
self.actionMap[hItem].toggled.connect(
|
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])
|
self.addAction(self.actionMap[hItem])
|
||||||
|
|
||||||
@@ -520,16 +758,338 @@ class GuiOutlineHeaderMenu(QMenu):
|
|||||||
|
|
||||||
return
|
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
|
# Slots
|
||||||
##
|
##
|
||||||
|
|
||||||
def _columnToggled(self, isChecked, theItem):
|
@pyqtSlot(str, str)
|
||||||
"""The user has toggled the visibility of a column. Forward the
|
def showItem(self, tHandle, sTitle):
|
||||||
event to the parent class only if we're accepting changes.
|
"""Update the content of the tree with the given handle and line
|
||||||
|
number pointing to a header.
|
||||||
"""
|
"""
|
||||||
if self.acceptToggle:
|
pIndex = self.theProject.index
|
||||||
self.theParent._menuColumnToggled(isChecked, theItem)
|
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
|
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.QtCore import Qt, QSize, pyqtSignal, pyqtSlot
|
||||||
from PyQt5.QtGui import QIcon
|
from PyQt5.QtGui import QIcon
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction
|
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QMenu, QAction, QFrame,
|
||||||
|
QDialog
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.core import NWDoc
|
from novelwriter.core import NWDoc
|
||||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
|
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__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -49,8 +51,9 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
C_EXPORT = 2
|
C_EXPORT = 2
|
||||||
C_STATUS = 3
|
C_STATUS = 3
|
||||||
|
|
||||||
novelItemChanged = pyqtSignal()
|
treeItemChanged = pyqtSignal(str)
|
||||||
noteItemChanged = pyqtSignal()
|
novelItemChanged = pyqtSignal(str)
|
||||||
|
rootFolderChanged = pyqtSignal(str)
|
||||||
wordCountsChanged = pyqtSignal()
|
wordCountsChanged = pyqtSignal()
|
||||||
|
|
||||||
def __init__(self, theParent):
|
def __init__(self, theParent):
|
||||||
@@ -62,13 +65,11 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.theIndex = theParent.theIndex
|
|
||||||
|
|
||||||
# Internal Variables
|
# Internal Variables
|
||||||
self._treeMap = {}
|
self._treeMap = {}
|
||||||
self._treeChanged = False
|
self._lastMove = {}
|
||||||
self._timeChanged = 0
|
self._timeChanged = 0
|
||||||
self._lastMove = {}
|
|
||||||
|
|
||||||
##
|
##
|
||||||
# Build GUI
|
# Build GUI
|
||||||
@@ -82,7 +83,8 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
# Tree Settings
|
# Tree Settings
|
||||||
iPx = self.theTheme.baseIconSize
|
iPx = self.theTheme.baseIconSize
|
||||||
self.setIconSize(QSize(iPx, iPx))
|
self.setIconSize(QSize(iPx, iPx))
|
||||||
self.setExpandsOnDoubleClick(True)
|
self.setFrameStyle(QFrame.NoFrame)
|
||||||
|
self.setExpandsOnDoubleClick(False)
|
||||||
self.setIndentation(iPx)
|
self.setIndentation(iPx)
|
||||||
self.setColumnCount(4)
|
self.setColumnCount(4)
|
||||||
self.setHeaderLabels([
|
self.setHeaderLabels([
|
||||||
@@ -157,147 +159,97 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
"""
|
"""
|
||||||
self.clear()
|
self.clear()
|
||||||
self._treeMap = {}
|
self._treeMap = {}
|
||||||
self._treeChanged = False
|
self._lastMove = {}
|
||||||
self._timeChanged = 0
|
self._timeChanged = 0
|
||||||
return
|
return
|
||||||
|
|
||||||
def newTreeItem(self, itemType, itemClass):
|
def newTreeItem(self, itemType, itemClass=None):
|
||||||
"""Add new item to the tree, with a given itemType and
|
"""Add new item to the tree, with a given itemType (and
|
||||||
itemClass, and attach it to the selected handle. Also make sure
|
itemClass if Root), and attach it to the selected handle. Also
|
||||||
the item is added in a place it can be added, and that other
|
make sure the item is added in a place it can be added, and that
|
||||||
meta data is set correctly to ensure a valid project tree.
|
other meta data is set correctly to ensure a valid project tree.
|
||||||
"""
|
"""
|
||||||
pHandle = self.getSelectedHandle()
|
|
||||||
nHandle = None
|
|
||||||
|
|
||||||
if not self.theParent.hasProject:
|
if not self.theParent.hasProject:
|
||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not isinstance(itemType, nwItemType):
|
nHandle = None
|
||||||
# This would indicate an internal bug
|
tHandle = None
|
||||||
logger.error("No itemType provided")
|
|
||||||
return False
|
|
||||||
|
|
||||||
# The item needs to be assigned an item class, so one must be
|
if itemType == nwItemType.ROOT and isinstance(itemClass, nwItemClass):
|
||||||
# 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 class is still not set, alert the user and exit
|
tHandle = self.theProject.newRoot(itemClass)
|
||||||
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
|
|
||||||
|
|
||||||
# Everything is fine, we have what we need, so we proceed
|
elif itemType in (nwItemType.FILE, nwItemType.FOLDER):
|
||||||
logger.verbose(
|
|
||||||
"Adding new item of type '%s' and class '%s' to handle '%s'",
|
|
||||||
itemType.name, itemClass.name, str(pHandle)
|
|
||||||
)
|
|
||||||
|
|
||||||
if itemType == nwItemType.ROOT:
|
sHandle = self.getSelectedHandle()
|
||||||
tHandle = self.theProject.newRoot(
|
if sHandle is None or sHandle not in self.theProject.tree:
|
||||||
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:
|
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
"Did not find anywhere to add the file or folder!"
|
"Did not find anywhere to add the file or folder!"
|
||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# Now check if the selected item is a file, in which case
|
# If the selected item is a file, the new item will be a
|
||||||
# the new file will be a sibling
|
# sibling if the file has no children, otherwise a child
|
||||||
pItem = self.theProject.projTree[pHandle]
|
pItem = self.theProject.tree[sHandle]
|
||||||
if pItem.itemType == nwItemType.FILE:
|
qItem = self._getTreeItem(sHandle)
|
||||||
nHandle = pHandle
|
if pItem.itemType == nwItemType.FILE and qItem.childCount() == 0:
|
||||||
pHandle = pItem.itemParent
|
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 self.theProject.tree.isTrash(sHandle):
|
||||||
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):
|
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
"Cannot add new files or folders to the Trash folder."
|
"Cannot add new files or folders to the Trash folder."
|
||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
parTree = self.theProject.projTree.getItemPath(pHandle)
|
# Add the file or folder
|
||||||
|
|
||||||
# If we're still here, add the file or folder
|
|
||||||
if itemType == nwItemType.FILE:
|
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:
|
elif itemType == nwItemType.FOLDER:
|
||||||
if len(parTree) >= nwConst.MAX_DEPTH - 1:
|
tHandle = self.theProject.newFolder(self.tr("New Folder"), sHandle)
|
||||||
# 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)
|
|
||||||
|
|
||||||
else:
|
else:
|
||||||
logger.error("Failed to add new item")
|
logger.error("Failed to add new item")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# If there is no handle set, return here
|
# If there is no handle set, return here. This is a bug
|
||||||
if tHandle is None:
|
if tHandle is None: # pragma: no cover
|
||||||
return True
|
return True
|
||||||
|
|
||||||
# Add the new item to the tree
|
# Add the new item to the tree
|
||||||
self.revealNewTreeItem(tHandle, nHandle)
|
self.revealNewTreeItem(tHandle, nHandle)
|
||||||
self.theParent.editItem(tHandle)
|
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:
|
if nwItem.itemType != nwItemType.FILE:
|
||||||
return True
|
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)
|
newDoc = NWDoc(self.theProject, tHandle)
|
||||||
curTxt = newDoc.readDocument()
|
if not newDoc.readDocument():
|
||||||
if curTxt is None:
|
|
||||||
curTxt = ""
|
|
||||||
|
|
||||||
if curTxt == "":
|
|
||||||
if nwItem.itemLayout == nwItemLayout.DOCUMENT:
|
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:
|
else:
|
||||||
newText = f"# {nwItem.itemName}\n\n"
|
newText = f"# {nwItem.itemName}\n\n"
|
||||||
|
|
||||||
|
pIndex = self.theProject.index
|
||||||
|
|
||||||
# Save the text and index it
|
# Save the text and index it
|
||||||
newDoc.writeDocument(newText)
|
newDoc.writeDocument(newText)
|
||||||
self.theIndex.scanText(tHandle, newText)
|
pIndex.scanText(tHandle, newText)
|
||||||
|
|
||||||
# Get Word Counts
|
# Get Word Counts
|
||||||
cC, wC, pC = self.theIndex.getCounts(tHandle)
|
cC, wC, pC = pIndex.getCounts(tHandle)
|
||||||
nwItem.setCharCount(cC)
|
nwItem.setCharCount(cC)
|
||||||
nwItem.setWordCount(wC)
|
nwItem.setWordCount(wC)
|
||||||
nwItem.setParaCount(pC)
|
nwItem.setParaCount(pC)
|
||||||
@@ -309,7 +261,10 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
def revealNewTreeItem(self, tHandle, nHandle=None):
|
def revealNewTreeItem(self, tHandle, nHandle=None):
|
||||||
"""Reveal a newly added project item in the project tree.
|
"""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)
|
trItem = self._addTreeItem(nwItem, nHandle)
|
||||||
if trItem is None:
|
if trItem is None:
|
||||||
return False
|
return False
|
||||||
@@ -318,7 +273,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if pHandle is not None and pHandle in self._treeMap:
|
if pHandle is not None and pHandle in self._treeMap:
|
||||||
self._treeMap[pHandle].setExpanded(True)
|
self._treeMap[pHandle].setExpanded(True)
|
||||||
|
|
||||||
self._emitItemChange(tHandle)
|
self._alertTreeChange(tHandle=tHandle, flush=True)
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
trItem.setSelected(True)
|
trItem.setSelected(True)
|
||||||
|
|
||||||
@@ -360,10 +315,31 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
pItem.insertChild(nIndex, cItem)
|
pItem.insertChild(nIndex, cItem)
|
||||||
self._recordLastMove(cItem, pItem, tIndex)
|
self._recordLastMove(cItem, pItem, tIndex)
|
||||||
|
|
||||||
|
self._alertTreeChange(tHandle=tHandle, flush=True)
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
cItem.setSelected(True)
|
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
|
return True
|
||||||
|
|
||||||
@@ -380,16 +356,6 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self.theProject.setTreeOrder(theList)
|
self.theProject.setTreeOrder(theList)
|
||||||
return True
|
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):
|
def getTreeFromHandle(self, tHandle):
|
||||||
"""Recursively return all the children items starting from a
|
"""Recursively return all the children items starting from a
|
||||||
given item handle.
|
given item handle.
|
||||||
@@ -400,6 +366,14 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
theList = self._scanChildren(theList, theItem, 0)
|
theList = self._scanChildren(theList, theItem, 0)
|
||||||
return theList
|
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):
|
def getColumnSizes(self):
|
||||||
"""Return the column widths for the tree columns.
|
"""Return the column widths for the tree columns.
|
||||||
"""
|
"""
|
||||||
@@ -419,7 +393,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
trashHandle = self.theProject.projTree.trashRoot()
|
trashHandle = self.theProject.tree.trashRoot()
|
||||||
|
|
||||||
logger.debug("Emptying Trash folder")
|
logger.debug("Emptying Trash folder")
|
||||||
if trashHandle is None:
|
if trashHandle is None:
|
||||||
@@ -447,13 +421,13 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
logger.verbose("Deleting %d file(s) from Trash", nTrash)
|
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:
|
if tHandle == trashHandle:
|
||||||
continue
|
continue
|
||||||
self.deleteItem(tHandle, alreadyAsked=True, bulkAction=True)
|
self.deleteItem(tHandle, alreadyAsked=True, bulkAction=True)
|
||||||
|
|
||||||
if nTrash > 0:
|
if nTrash > 0:
|
||||||
self._setTreeChanged(True)
|
self._alertTreeChange(tHandle=trashHandle, flush=True)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -461,8 +435,8 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
"""Delete an item from the project tree. As a first step, files are
|
"""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
|
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
|
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,
|
delete the files on disk. Root folders are deleted if they're empty
|
||||||
and the deletion is always permanent.
|
only, and the deletion is always permanent.
|
||||||
"""
|
"""
|
||||||
if not self.theParent.hasProject:
|
if not self.theParent.hasProject:
|
||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
@@ -480,109 +454,22 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
trItemS = self._getTreeItem(tHandle)
|
trItemS = self._getTreeItem(tHandle)
|
||||||
nwItemS = self.theProject.projTree[tHandle]
|
nwItemS = self.theProject.tree[tHandle]
|
||||||
|
|
||||||
if trItemS is None or nwItemS is None:
|
if trItemS is None or nwItemS is None:
|
||||||
logger.error("Could not find tree item for deletion")
|
logger.error("Could not find tree item for deletion")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
wCount = int(trItemS.data(self.C_COUNT, Qt.UserRole))
|
wCount = self._getItemWordCount(tHandle)
|
||||||
if nwItemS.itemType == nwItemType.FILE:
|
autoFlush = not bulkAction
|
||||||
logger.debug("User requested file '%s' deleted", tHandle)
|
if nwItemS.itemType == nwItemType.ROOT:
|
||||||
trItemP = trItemS.parent()
|
# Only an empty ROOT folder can be deleted
|
||||||
trItemT = self._addTrashRoot()
|
logger.debug("User requested a root folder '%s' deleted", tHandle)
|
||||||
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)
|
|
||||||
tIndex = self.indexOfTopLevelItem(trItemS)
|
tIndex = self.indexOfTopLevelItem(trItemS)
|
||||||
if trItemS.childCount() == 0:
|
if trItemS.childCount() == 0:
|
||||||
self.takeTopLevelItem(tIndex)
|
self.takeTopLevelItem(tIndex)
|
||||||
self._deleteTreeItem(tHandle)
|
self._deleteTreeItem(tHandle)
|
||||||
self.theParent.mainMenu.setAvailableRoot()
|
self._alertTreeChange(tHandle=tHandle, flush=True)
|
||||||
self._setTreeChanged(True)
|
|
||||||
else:
|
else:
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
"Cannot delete root folder. It is not empty. "
|
"Cannot delete root folder. It is not empty. "
|
||||||
@@ -591,6 +478,70 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
), nwAlert.ERROR)
|
), nwAlert.ERROR)
|
||||||
return False
|
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
|
return True
|
||||||
|
|
||||||
def setTreeItemValues(self, tHandle):
|
def setTreeItemValues(self, tHandle):
|
||||||
@@ -599,7 +550,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
already coming from the project tree.
|
already coming from the project tree.
|
||||||
"""
|
"""
|
||||||
trItem = self._getTreeItem(tHandle)
|
trItem = self._getTreeItem(tHandle)
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
nwItem = self.theProject.tree[tHandle]
|
||||||
if trItem is None or nwItem is None:
|
if trItem is None or nwItem is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -610,8 +561,8 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
else:
|
else:
|
||||||
expIcon = self.theTheme.getIcon("cross")
|
expIcon = self.theTheme.getIcon("cross")
|
||||||
|
|
||||||
itempStatus, statusIcon = nwItem.getImportStatus()
|
itemStatus, statusIcon = nwItem.getImportStatus()
|
||||||
hLevel = self.theIndex.getHandleHeaderLevel(tHandle)
|
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||||
itemIcon = self.theTheme.getItemIcon(
|
itemIcon = self.theTheme.getItemIcon(
|
||||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||||||
)
|
)
|
||||||
@@ -620,7 +571,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
trItem.setText(self.C_NAME, nwItem.itemName)
|
trItem.setText(self.C_NAME, nwItem.itemName)
|
||||||
trItem.setIcon(self.C_EXPORT, expIcon)
|
trItem.setIcon(self.C_EXPORT, expIcon)
|
||||||
trItem.setIcon(self.C_STATUS, statusIcon)
|
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:
|
if self.mainConf.emphLabels and nwItem.itemLayout == nwItemLayout.DOCUMENT:
|
||||||
trFont = trItem.font(self.C_NAME)
|
trFont = trItem.font(self.C_NAME)
|
||||||
@@ -634,7 +585,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
return
|
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,
|
"""Recursive function setting the word count for a given item,
|
||||||
and propagating that count upwards in the tree until reaching a
|
and propagating that count upwards in the tree until reaching a
|
||||||
root item. This function is more efficient than recalculating
|
root item. This function is more efficient than recalculating
|
||||||
@@ -646,20 +597,30 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if tItem is None:
|
if tItem is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
tItem.setText(self.C_COUNT, f"{theCount:n}")
|
if countChildren:
|
||||||
tItem.setData(self.C_COUNT, Qt.UserRole, int(theCount))
|
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()
|
pItem = tItem.parent()
|
||||||
if pItem is None:
|
if pItem is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
pCount = 0
|
pCount = 0
|
||||||
|
pHandle = None
|
||||||
for i in range(pItem.childCount()):
|
for i in range(pItem.childCount()):
|
||||||
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
pCount += int(pItem.child(i).data(self.C_COUNT, Qt.UserRole))
|
||||||
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
pHandle = pItem.data(self.C_NAME, Qt.UserRole)
|
||||||
|
|
||||||
if not nDepth > nwConst.MAX_DEPTH + 1 and pHandle != "":
|
if pHandle:
|
||||||
self.propagateCount(pHandle, pCount, nDepth+1)
|
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
|
return
|
||||||
|
|
||||||
@@ -703,20 +664,19 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
dstIndex = min(max(0, dstIndex), dstItem.childCount())
|
dstIndex = min(max(0, dstIndex), dstItem.childCount())
|
||||||
wCount = int(srcItem.data(self.C_COUNT, Qt.UserRole))
|
|
||||||
sHandle = srcItem.data(self.C_NAME, Qt.UserRole)
|
sHandle = srcItem.data(self.C_NAME, Qt.UserRole)
|
||||||
dHandle = dstItem.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)
|
logger.debug("Moving item '%s' back to '%s', index %d", sHandle, dHandle, dstIndex)
|
||||||
|
|
||||||
|
wCount = self._getItemWordCount(sHandle)
|
||||||
self.propagateCount(sHandle, 0)
|
self.propagateCount(sHandle, 0)
|
||||||
parItem = srcItem.parent()
|
parItem = srcItem.parent()
|
||||||
srcIndex = parItem.indexOfChild(srcItem)
|
srcIndex = parItem.indexOfChild(srcItem)
|
||||||
movItem = parItem.takeChild(srcIndex)
|
movItem = parItem.takeChild(srcIndex)
|
||||||
dstItem.insertChild(dstIndex, movItem)
|
dstItem.insertChild(dstIndex, movItem)
|
||||||
|
|
||||||
snItem = self.theProject.projTree[sHandle]
|
self._postItemMove(sHandle, wCount)
|
||||||
dnItem = self.theProject.projTree[dHandle]
|
self._alertTreeChange(tHandle=sHandle, flush=True)
|
||||||
self._postItemMove(sHandle, snItem, dnItem, wCount)
|
|
||||||
|
|
||||||
self.clearSelection()
|
self.clearSelection()
|
||||||
movItem.setSelected(True)
|
movItem.setSelected(True)
|
||||||
@@ -771,7 +731,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
if isinstance(selItem, QTreeWidgetItem):
|
if isinstance(selItem, QTreeWidgetItem):
|
||||||
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
||||||
self.setSelectedHandle(tHandle) # Just to be safe
|
self.setSelectedHandle(tHandle) # Just to be safe
|
||||||
tItem = self.theProject.projTree[tHandle]
|
tItem = self.theProject.tree[tHandle]
|
||||||
if tItem is not None:
|
if tItem is not None:
|
||||||
if self.ctxMenu.filterActions(tItem):
|
if self.ctxMenu.filterActions(tItem):
|
||||||
# Only open menu if any actions remain after filter
|
# Only open menu if any actions remain after filter
|
||||||
@@ -783,7 +743,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
def doUpdateCounts(self, tHandle, cCount, wCount, pCount):
|
def doUpdateCounts(self, tHandle, cCount, wCount, pCount):
|
||||||
"""Slot for updating the word count of a specific item.
|
"""Slot for updating the word count of a specific item.
|
||||||
"""
|
"""
|
||||||
self.propagateCount(tHandle, wCount)
|
self.propagateCount(tHandle, wCount, countChildren=True)
|
||||||
self.wordCountsChanged.emit()
|
self.wordCountsChanged.emit()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -809,7 +769,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
tHandle = selItem.data(self.C_NAME, Qt.UserRole)
|
||||||
tItem = self.theProject.projTree[tHandle]
|
tItem = self.theProject.tree[tHandle]
|
||||||
if tItem is None:
|
if tItem is None:
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -819,61 +779,34 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
def dropEvent(self, theEvent):
|
def dropEvent(self, theEvent):
|
||||||
"""Overload the drop of dragged item event to check whether the
|
"""Overload the drop item event to ensure relevant data has been
|
||||||
drop is allowed or not. Disallowed drops are cancelled.
|
updated.
|
||||||
"""
|
"""
|
||||||
sHandle = self.getSelectedHandle()
|
sHandle = self.getSelectedHandle()
|
||||||
if sHandle is None:
|
if sHandle is None:
|
||||||
logger.error("No handle selected")
|
logger.error("Invalid drag and drop event")
|
||||||
return
|
return
|
||||||
|
|
||||||
dIndex = self.indexAt(theEvent.pos())
|
logger.debug("Drag'n'drop of item '%s' accepted", sHandle)
|
||||||
if not dIndex.isValid():
|
|
||||||
logger.error("Invalid drop index")
|
|
||||||
return
|
|
||||||
|
|
||||||
sItem = self._getTreeItem(sHandle)
|
sItem = self._getTreeItem(sHandle)
|
||||||
dItem = self.itemFromIndex(dIndex)
|
isExpanded = False
|
||||||
dHandle = dItem.data(self.C_NAME, Qt.UserRole)
|
if sItem is not None:
|
||||||
snItem = self.theProject.projTree[sHandle]
|
isExpanded = sItem.isExpanded()
|
||||||
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
|
|
||||||
|
|
||||||
pItem = sItem.parent()
|
pItem = sItem.parent()
|
||||||
pIndex = 0
|
pIndex = 0
|
||||||
if pItem is not None:
|
if pItem is not None:
|
||||||
pIndex = pItem.indexOfChild(sItem)
|
pIndex = pItem.indexOfChild(sItem)
|
||||||
|
|
||||||
wCount = int(sItem.data(self.C_COUNT, Qt.UserRole))
|
wCount = self._getItemWordCount(sHandle)
|
||||||
isFile = snItem.itemType == nwItemType.FILE
|
self.propagateCount(sHandle, 0)
|
||||||
isRoot = snItem.itemType == nwItemType.ROOT
|
|
||||||
onFile = dnItem.itemType == nwItemType.FILE
|
|
||||||
|
|
||||||
isSame = snItem.itemClass == dnItem.itemClass
|
QTreeWidget.dropEvent(self, theEvent)
|
||||||
isNone = snItem.itemClass == nwItemClass.NO_CLASS
|
self._postItemMove(sHandle, wCount)
|
||||||
isNote = snItem.itemLayout == nwItemLayout.NOTE
|
self._recordLastMove(sItem, pItem, pIndex)
|
||||||
onFree = dnItem.itemClass in nwLists.FREE_CLASS and isFile
|
self._alertTreeChange(tHandle=sHandle, flush=True)
|
||||||
|
sItem.setExpanded(isExpanded)
|
||||||
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)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -881,64 +814,88 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
# Internal Functions
|
# Internal Functions
|
||||||
##
|
##
|
||||||
|
|
||||||
def _postItemMove(self, sHandle, snItem, dnItem, wCount):
|
def _postItemMove(self, tHandle, wCount):
|
||||||
"""Run various maintenance tasks for a moved item.
|
"""Run various maintenance tasks for a moved item.
|
||||||
"""
|
"""
|
||||||
isFile = snItem.itemType == nwItemType.FILE
|
trItemS = self._getTreeItem(tHandle)
|
||||||
isSame = snItem.itemClass == dnItem.itemClass
|
nwItemS = self.theProject.tree[tHandle]
|
||||||
onFree = dnItem.itemClass in nwLists.FREE_CLASS and isFile
|
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,
|
mHandles = self.getTreeFromHandle(tHandle)
|
||||||
# and the target is not a free root folder, update its class
|
logger.debug("A total of %d item(s) were moved", len(mHandles))
|
||||||
if not (isSame or onFree):
|
for mHandle in mHandles:
|
||||||
logger.debug(
|
logger.debug("Updating item '%s'", mHandle)
|
||||||
"Item '%s' class has been changed from '%s' to '%s'",
|
self.theProject.tree.updateItemData(mHandle)
|
||||||
sHandle, snItem.itemClass.name, dnItem.itemClass.name
|
|
||||||
)
|
|
||||||
snItem.setClass(dnItem.itemClass)
|
|
||||||
self.setTreeItemValues(sHandle)
|
|
||||||
|
|
||||||
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
|
self.setTreeItemValues(mHandle)
|
||||||
# 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)
|
|
||||||
|
|
||||||
# Trigger dependent updates
|
# Trigger dependent updates
|
||||||
self._setTreeChanged(True)
|
self.propagateCount(tHandle, wCount)
|
||||||
self._emitItemChange(sHandle)
|
|
||||||
|
|
||||||
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):
|
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)
|
return self._treeMap.get(tHandle, None)
|
||||||
|
|
||||||
def _deleteTreeItem(self, tHandle):
|
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)
|
self._treeMap.pop(tHandle, None)
|
||||||
return
|
|
||||||
|
return True
|
||||||
|
|
||||||
def _scanChildren(self, theList, tItem, tIndex):
|
def _scanChildren(self, theList, tItem, tIndex):
|
||||||
"""This is a recursive function returning all items in a tree
|
"""This is a recursive function returning all items in a tree
|
||||||
starting at a given QTreeWidgetItem.
|
starting at a given QTreeWidgetItem.
|
||||||
"""
|
"""
|
||||||
tHandle = tItem.data(self.C_NAME, Qt.UserRole)
|
tHandle = tItem.data(self.C_NAME, Qt.UserRole)
|
||||||
nwItem = self.theProject.projTree[tHandle]
|
cCount = tItem.childCount()
|
||||||
nwItem.setExpanded(tItem.isExpanded())
|
|
||||||
|
# Update tree-related meta data
|
||||||
|
nwItem = self.theProject.tree[tHandle]
|
||||||
|
nwItem.setExpanded(tItem.isExpanded() and cCount > 0)
|
||||||
nwItem.setOrder(tIndex)
|
nwItem.setOrder(tIndex)
|
||||||
|
|
||||||
theList.append(tHandle)
|
theList.append(tHandle)
|
||||||
for i in range(tItem.childCount()):
|
for i in range(cCount):
|
||||||
self._scanChildren(theList, tItem.child(i), i)
|
self._scanChildren(theList, tItem.child(i), i)
|
||||||
|
|
||||||
return theList
|
return theList
|
||||||
|
|
||||||
def _addTreeItem(self, nwItem, nHandle=None):
|
def _addTreeItem(self, nwItem, nHandle=None):
|
||||||
@@ -965,9 +922,7 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
self._treeMap[tHandle] = newItem
|
self._treeMap[tHandle] = newItem
|
||||||
if pHandle is None:
|
if pHandle is None:
|
||||||
if nwItem.itemType == nwItemType.ROOT:
|
if nwItem.itemType == nwItemType.ROOT:
|
||||||
self.addTopLevelItem(newItem)
|
newItem.setFlags(newItem.flags() ^ Qt.ItemIsDragEnabled)
|
||||||
self.theParent.mainMenu.setAvailableRoot()
|
|
||||||
elif nwItem.itemType == nwItemType.TRASH:
|
|
||||||
self.addTopLevelItem(newItem)
|
self.addTopLevelItem(newItem)
|
||||||
else:
|
else:
|
||||||
self.theParent.makeAlert(self.tr(
|
self.theParent.makeAlert(self.tr(
|
||||||
@@ -984,16 +939,14 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
except Exception:
|
except Exception:
|
||||||
logger.error("Failed to get index of item with handle '%s'", nHandle)
|
logger.error("Failed to get index of item with handle '%s'", nHandle)
|
||||||
if byIndex >= 0:
|
if byIndex >= 0:
|
||||||
self._treeMap[pHandle].insertChild(byIndex+1, newItem)
|
self._treeMap[pHandle].insertChild(byIndex + 1, newItem)
|
||||||
else:
|
else:
|
||||||
self._treeMap[pHandle].addChild(newItem)
|
self._treeMap[pHandle].addChild(newItem)
|
||||||
self.propagateCount(tHandle, nwItem.wordCount)
|
self.propagateCount(tHandle, nwItem.wordCount, countChildren=True)
|
||||||
|
|
||||||
self.setTreeItemValues(tHandle)
|
self.setTreeItemValues(tHandle)
|
||||||
newItem.setExpanded(nwItem.isExpanded)
|
newItem.setExpanded(nwItem.isExpanded)
|
||||||
|
|
||||||
self._setTreeChanged(True)
|
|
||||||
|
|
||||||
return newItem
|
return newItem
|
||||||
|
|
||||||
def _addTrashRoot(self):
|
def _addTrashRoot(self):
|
||||||
@@ -1006,52 +959,34 @@ class GuiProjectTree(QTreeWidget):
|
|||||||
|
|
||||||
trItem = self._getTreeItem(trashHandle)
|
trItem = self._getTreeItem(trashHandle)
|
||||||
if trItem is None:
|
if trItem is None:
|
||||||
trItem = self._addTreeItem(
|
trItem = self._addTreeItem(self.theProject.tree[trashHandle])
|
||||||
self.theProject.projTree[trashHandle]
|
|
||||||
)
|
|
||||||
if trItem is not None:
|
if trItem is not None:
|
||||||
trItem.setExpanded(True)
|
trItem.setExpanded(True)
|
||||||
self._setTreeChanged(True)
|
self._alertTreeChange(tHandle=trashHandle, flush=True)
|
||||||
|
|
||||||
return trItem
|
return trItem
|
||||||
|
|
||||||
def _updateItemParent(self, tHandle):
|
def _alertTreeChange(self, tHandle=None, flush=True):
|
||||||
"""Update the parent handle of an item so that the information
|
"""Update information on tree change state, and emit necessary
|
||||||
in the project is consistent with the treeView.
|
signals.
|
||||||
"""
|
"""
|
||||||
trItemS = self._getTreeItem(tHandle)
|
self._timeChanged = time()
|
||||||
nwItemS = self.theProject.projTree[tHandle]
|
self.theProject.setProjectChanged(True)
|
||||||
trItemP = trItemS.parent()
|
if flush:
|
||||||
if trItemP is None:
|
self.saveTreeOrder()
|
||||||
logger.error("Failed to find new parent item of '%s'", tHandle)
|
|
||||||
return False
|
|
||||||
|
|
||||||
pHandle = trItemP.data(self.C_NAME, Qt.UserRole)
|
tItem = self.theProject.tree[tHandle]
|
||||||
nwItemS.setParent(pHandle)
|
if tItem is None:
|
||||||
self.setTreeItemValues(tHandle)
|
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
|
return
|
||||||
|
|
||||||
def _recordLastMove(self, srcItem, parItem, parIndex):
|
def _recordLastMove(self, srcItem, parItem, parIndex):
|
||||||
@@ -1130,9 +1065,9 @@ class GuiProjectTreeMenu(QMenu):
|
|||||||
logger.error("Failed to extract information to build tree context menu")
|
logger.error("Failed to extract information to build tree context menu")
|
||||||
return False
|
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
|
isTrash = theItem.itemHandle == trashHandle and trashHandle is not None
|
||||||
isFile = theItem.itemType == nwItemType.FILE
|
isFile = theItem.itemType == nwItemType.FILE
|
||||||
|
|
||||||
@@ -1182,7 +1117,7 @@ class GuiProjectTreeMenu(QMenu):
|
|||||||
"""Forward the new file call to the project tree.
|
"""Forward the new file call to the project tree.
|
||||||
"""
|
"""
|
||||||
if self.theItem is not None:
|
if self.theItem is not None:
|
||||||
self.theTree.newTreeItem(nwItemType.FILE, None)
|
self.theTree.newTreeItem(nwItemType.FILE)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -1190,7 +1125,7 @@ class GuiProjectTreeMenu(QMenu):
|
|||||||
"""Forward the new folder call to the project tree.
|
"""Forward the new folder call to the project tree.
|
||||||
"""
|
"""
|
||||||
if self.theItem is not None:
|
if self.theItem is not None:
|
||||||
self.theTree.newTreeItem(nwItemType.FOLDER, None)
|
self.theTree.newTreeItem(nwItemType.FOLDER)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
|
|||||||
@@ -456,20 +456,18 @@ class GuiIcons:
|
|||||||
|
|
||||||
ICON_KEYS = {
|
ICON_KEYS = {
|
||||||
# Project and GUI icons
|
# Project and GUI icons
|
||||||
"novelwriter", "proj_nwx",
|
"novelwriter", "cls_archive", "cls_character", "cls_custom", "cls_entity", "cls_none",
|
||||||
"cls_none", "cls_novel", "cls_plot", "cls_character", "cls_world",
|
"cls_novel", "cls_object", "cls_plot", "cls_timeline", "cls_trash", "cls_world", "doc_h0",
|
||||||
"cls_timeline", "cls_object", "cls_entity", "cls_custom", "cls_archive", "cls_trash",
|
"doc_h1", "doc_h2", "doc_h3", "doc_h4", "proj_chapter", "proj_details", "proj_document",
|
||||||
"proj_document", "proj_title", "proj_chapter", "proj_scene", "proj_note", "proj_folder",
|
"proj_folder", "proj_note", "proj_nwx", "proj_scene", "proj_stats", "proj_title",
|
||||||
"status_lang", "status_time", "status_idle", "status_stats", "status_lines",
|
"search_cancel", "search_case", "search_loop", "search_preserve", "search_project",
|
||||||
"doc_h0", "doc_h1", "doc_h2", "doc_h3", "doc_h4",
|
"search_regex", "search_word", "status_idle", "status_lang", "status_lines",
|
||||||
"search_case", "search_regex", "search_word", "search_loop", "search_project",
|
"status_stats", "status_time", "view_build", "view_editor", "view_novel", "view_outline",
|
||||||
"search_cancel", "search_preserve",
|
|
||||||
|
|
||||||
# General Button Icons
|
# General Button Icons
|
||||||
"delete", "close", "done", "clear", "save", "add", "remove",
|
"add", "backward", "check", "clear", "close", "cross", "delete", "done", "down", "edit",
|
||||||
"search", "search_replace", "edit", "check", "cross", "hash",
|
"forward", "hash", "maximise", "menu", "minimise", "reference", "refresh", "remove",
|
||||||
"maximise", "minimise", "refresh", "reference", "backward",
|
"save", "search_replace", "search", "settings", "up",
|
||||||
"forward", "settings",
|
|
||||||
|
|
||||||
# Switches
|
# Switches
|
||||||
"sticky-on", "sticky-off",
|
"sticky-on", "sticky-off",
|
||||||
@@ -639,9 +637,6 @@ class GuiIcons:
|
|||||||
iconName = "proj_scene"
|
iconName = "proj_scene"
|
||||||
elif tLayout == nwItemLayout.NOTE:
|
elif tLayout == nwItemLayout.NOTE:
|
||||||
iconName = "proj_note"
|
iconName = "proj_note"
|
||||||
elif tType == nwItemType.TRASH:
|
|
||||||
iconName = nwLabels.CLASS_ICON[tClass]
|
|
||||||
|
|
||||||
if iconName is None:
|
if iconName is None:
|
||||||
return QIcon()
|
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 logging
|
||||||
import novelwriter
|
import novelwriter
|
||||||
|
|
||||||
|
from enum import Enum
|
||||||
from time import time
|
from time import time
|
||||||
from datetime import datetime
|
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.QtGui import QIcon, QKeySequence, QCursor
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
|
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
|
||||||
QMessageBox, QDialog, QTabWidget, QToolBar, QAction
|
QMessageBox, QDialog, QStackedWidget
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.gui import (
|
from novelwriter.gui import (
|
||||||
GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu,
|
GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu,
|
||||||
GuiMainStatus, GuiNovelTree, GuiOutline, GuiOutlineDetails, GuiProjectTree,
|
GuiMainStatus, GuiNovelTree, GuiOutline, GuiProjectTree, GuiTheme,
|
||||||
GuiTheme
|
GuiViewsBar
|
||||||
)
|
)
|
||||||
from novelwriter.dialogs import (
|
from novelwriter.dialogs import (
|
||||||
GuiAbout, GuiDocMerge, GuiDocSplit, GuiItemEditor, GuiPreferences,
|
GuiAbout, GuiDocMerge, GuiDocSplit, GuiPreferences, GuiProjectDetails,
|
||||||
GuiProjectDetails, GuiProjectLoad, GuiProjectSettings, GuiUpdates,
|
GuiProjectLoad, GuiProjectSettings, GuiUpdates, GuiWordList
|
||||||
GuiWordList
|
|
||||||
)
|
)
|
||||||
from novelwriter.tools import (
|
from novelwriter.tools import (
|
||||||
GuiBuildNovel, GuiLipsum, GuiProjectWizard, GuiWritingStats
|
GuiBuildNovel, GuiLipsum, GuiProjectWizard, GuiWritingStats
|
||||||
)
|
)
|
||||||
from novelwriter.core import NWProject, NWIndex
|
from novelwriter.core import NWProject
|
||||||
from novelwriter.enum import (
|
from novelwriter.enum import (
|
||||||
nwItemType, nwItemClass, nwAlert, nwWidget, nwState
|
nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwState, nwView
|
||||||
)
|
)
|
||||||
from novelwriter.common import getGuiItem, hexToInt
|
from novelwriter.common import getGuiItem, hexToInt
|
||||||
from novelwriter.constants import nwLists
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@@ -87,7 +86,6 @@ class GuiMain(QMainWindow):
|
|||||||
# Core Classes and Settings
|
# Core Classes and Settings
|
||||||
self.theTheme = GuiTheme()
|
self.theTheme = GuiTheme()
|
||||||
self.theProject = NWProject(self)
|
self.theProject = NWProject(self)
|
||||||
self.theIndex = NWIndex(self.theProject)
|
|
||||||
self.hasProject = False
|
self.hasProject = False
|
||||||
self.isFocusMode = False
|
self.isFocusMode = False
|
||||||
self.idleRefTime = time()
|
self.idleRefTime = time()
|
||||||
@@ -103,8 +101,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
# Sizes
|
# Sizes
|
||||||
mPx = self.mainConf.pxInt(4)
|
mPx = self.mainConf.pxInt(4)
|
||||||
fPx = self.theTheme.fontPixelSize
|
hWd = self.mainConf.pxInt(4)
|
||||||
fPt = self.theTheme.fontPointSize
|
|
||||||
|
|
||||||
# Main GUI Elements
|
# Main GUI Elements
|
||||||
self.statusBar = GuiMainStatus(self)
|
self.statusBar = GuiMainStatus(self)
|
||||||
@@ -115,62 +112,43 @@ class GuiMain(QMainWindow):
|
|||||||
self.docViewer = GuiDocViewer(self)
|
self.docViewer = GuiDocViewer(self)
|
||||||
self.treeMeta = GuiItemDetails(self)
|
self.treeMeta = GuiItemDetails(self)
|
||||||
self.projView = GuiOutline(self)
|
self.projView = GuiOutline(self)
|
||||||
self.projMeta = GuiOutlineDetails(self)
|
|
||||||
self.mainMenu = GuiMainMenu(self)
|
self.mainMenu = GuiMainMenu(self)
|
||||||
|
self.viewsBar = GuiViewsBar(self)
|
||||||
|
|
||||||
# Connect Signals Between Main Elements
|
# Connect Signals Between Main Elements
|
||||||
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
|
self.viewsBar.viewChangeRequested.connect(self._changeView)
|
||||||
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
|
|
||||||
self.docEditor.docCountsChanged.connect(self.treeMeta.doUpdateCounts)
|
|
||||||
self.docEditor.docCountsChanged.connect(self.treeView.doUpdateCounts)
|
|
||||||
|
|
||||||
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
|
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
|
||||||
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
|
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||||
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
|
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
|
||||||
self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
|
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.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
|
||||||
self.projTabs = QTabWidget()
|
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
|
||||||
self.projTabs.setTabPosition(QTabWidget.South)
|
self.docEditor.docCountsChanged.connect(self.treeMeta.doUpdateCounts)
|
||||||
self.projTabs.setStyleSheet("QTabWidget::pane {border: 0;};")
|
self.docEditor.docCountsChanged.connect(self.treeView.doUpdateCounts)
|
||||||
self.projTabs.addTab(self.treeView, self.tr("Project"))
|
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
|
||||||
self.projTabs.addTab(self.novelView, self.tr("Novel"))
|
|
||||||
self.projTabs.currentChanged.connect(self._projTabsChanged)
|
|
||||||
|
|
||||||
tabFont = self.projTabs.tabBar().font()
|
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
|
||||||
tabFont.setPointSizeF(0.9*fPt)
|
|
||||||
self.projTabs.tabBar().setFont(tabFont)
|
|
||||||
|
|
||||||
# Project Tree Action Buttons
|
self.projView.loadDocumentTagRequest.connect(self._followTag)
|
||||||
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.projDetailsBtn = QAction(self.tr("Project Details"))
|
# Project Tree Stack
|
||||||
self.projDetailsBtn.setIcon(self.theTheme.getIcon("status_lines"))
|
self.projStack = QStackedWidget()
|
||||||
self.projDetailsBtn.triggered.connect(lambda: self.showProjectDetailsDialog())
|
self.projStack.addWidget(self.treeView)
|
||||||
self.treeButtons.addAction(self.projDetailsBtn)
|
self.projStack.addWidget(self.novelView)
|
||||||
|
self.projStack.currentChanged.connect(self._projStackChanged)
|
||||||
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 View
|
# Project Tree View
|
||||||
self.treePane = QWidget()
|
self.treePane = QWidget()
|
||||||
self.treeBox = QVBoxLayout()
|
self.treeBox = QVBoxLayout()
|
||||||
self.treeBox.setContentsMargins(0, 0, 0, 0)
|
self.treeBox.setContentsMargins(0, 0, 0, 0)
|
||||||
self.treeBox.setSpacing(mPx)
|
self.treeBox.setSpacing(mPx)
|
||||||
self.treeBox.addWidget(self.projTabs)
|
self.treeBox.addWidget(self.projStack)
|
||||||
self.treeBox.addWidget(self.treeMeta)
|
self.treeBox.addWidget(self.treeMeta)
|
||||||
self.treePane.setLayout(self.treeBox)
|
self.treePane.setLayout(self.treeBox)
|
||||||
|
|
||||||
@@ -178,47 +156,42 @@ class GuiMain(QMainWindow):
|
|||||||
self.splitView = QSplitter(Qt.Vertical)
|
self.splitView = QSplitter(Qt.Vertical)
|
||||||
self.splitView.addWidget(self.docViewer)
|
self.splitView.addWidget(self.docViewer)
|
||||||
self.splitView.addWidget(self.viewMeta)
|
self.splitView.addWidget(self.viewMeta)
|
||||||
|
self.splitView.setHandleWidth(hWd)
|
||||||
self.splitView.setSizes(self.mainConf.getViewPanePos())
|
self.splitView.setSizes(self.mainConf.getViewPanePos())
|
||||||
|
|
||||||
# Splitter : Document Editor / Document Viewer
|
# Splitter : Document Editor / Document Viewer
|
||||||
self.splitDocs = QSplitter(Qt.Horizontal)
|
self.splitDocs = QSplitter(Qt.Horizontal)
|
||||||
self.splitDocs.addWidget(self.docEditor)
|
self.splitDocs.addWidget(self.docEditor)
|
||||||
self.splitDocs.addWidget(self.splitView)
|
self.splitDocs.addWidget(self.splitView)
|
||||||
|
self.splitDocs.setHandleWidth(hWd)
|
||||||
# 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)
|
|
||||||
|
|
||||||
# Splitter : Project Tree / Main Tabs
|
# Splitter : Project Tree / Main Tabs
|
||||||
self.splitMain = QSplitter(Qt.Horizontal)
|
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.treePane)
|
||||||
self.splitMain.addWidget(self.mainTabs)
|
self.splitMain.addWidget(self.splitDocs)
|
||||||
|
self.splitMain.setHandleWidth(hWd)
|
||||||
self.splitMain.setSizes(self.mainConf.getMainPanePos())
|
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
|
# Indices of Splitter Widgets
|
||||||
self.idxTree = self.splitMain.indexOf(self.treePane)
|
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.idxEditor = self.splitDocs.indexOf(self.docEditor)
|
||||||
self.idxViewer = self.splitDocs.indexOf(self.splitView)
|
self.idxViewer = self.splitDocs.indexOf(self.splitView)
|
||||||
self.idxViewDoc = self.splitView.indexOf(self.docViewer)
|
self.idxViewDoc = self.splitView.indexOf(self.docViewer)
|
||||||
self.idxViewMeta = self.splitView.indexOf(self.viewMeta)
|
self.idxViewMeta = self.splitView.indexOf(self.viewMeta)
|
||||||
|
|
||||||
# Indices of Tab Widgets
|
# Indices of Tab Widgets
|
||||||
self.idxTabEdit = self.mainTabs.indexOf(self.splitDocs)
|
self.idxEditorView = self.mainStack.indexOf(self.splitMain)
|
||||||
self.idxTabProj = self.mainTabs.indexOf(self.splitOutline)
|
self.idxOutlineView = self.mainStack.indexOf(self.projView)
|
||||||
self.idxTreeView = self.projTabs.indexOf(self.treeView)
|
self.idxTreeView = self.projStack.indexOf(self.treeView)
|
||||||
self.idxNovelView = self.projTabs.indexOf(self.novelView)
|
self.idxNovelView = self.projStack.indexOf(self.novelView)
|
||||||
|
|
||||||
# Splitter Behaviour
|
# Splitter Behaviour
|
||||||
self.splitMain.setCollapsible(self.idxTree, False)
|
self.splitMain.setCollapsible(self.idxTree, False)
|
||||||
@@ -237,8 +210,9 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
# Set Main Window Elements
|
# Set Main Window Elements
|
||||||
self.setMenuBar(self.mainMenu)
|
self.setMenuBar(self.mainMenu)
|
||||||
self.setCentralWidget(self.splitMain)
|
self.setCentralWidget(self.mainStack)
|
||||||
self.setStatusBar(self.statusBar)
|
self.setStatusBar(self.statusBar)
|
||||||
|
self.addToolBar(Qt.LeftToolBarArea, self.viewsBar)
|
||||||
|
|
||||||
# Finalise Initialisation
|
# Finalise Initialisation
|
||||||
# =======================
|
# =======================
|
||||||
@@ -323,7 +297,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.docEditor.clearEditor()
|
self.docEditor.clearEditor()
|
||||||
self.docEditor.setDictionaries()
|
self.docEditor.setDictionaries()
|
||||||
self.closeDocViewer()
|
self.closeDocViewer()
|
||||||
self.projMeta.clearDetails()
|
self.projView.clearOutline()
|
||||||
|
|
||||||
# General
|
# General
|
||||||
self.statusBar.clearStatus()
|
self.statusBar.clearStatus()
|
||||||
@@ -385,6 +359,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.rebuildTrees()
|
self.rebuildTrees()
|
||||||
self.saveProject()
|
self.saveProject()
|
||||||
self.docEditor.setDictionaries()
|
self.docEditor.setDictionaries()
|
||||||
|
self.projView.updateRootItem(None)
|
||||||
self.rebuildIndex(beQuiet=True)
|
self.rebuildIndex(beQuiet=True)
|
||||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||||
self.statusBar.setProjectStatus(nwState.GOOD)
|
self.statusBar.setProjectStatus(nwState.GOOD)
|
||||||
@@ -446,10 +421,10 @@ class GuiMain(QMainWindow):
|
|||||||
self.idleRefTime = time()
|
self.idleRefTime = time()
|
||||||
self.idleTime = 0.0
|
self.idleTime = 0.0
|
||||||
|
|
||||||
self.theIndex.clearIndex()
|
self.theProject.index.clearIndex()
|
||||||
self.clearGUI()
|
self.clearGUI()
|
||||||
self.hasProject = False
|
self.hasProject = False
|
||||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
self._changeView(nwView.PROJECT)
|
||||||
|
|
||||||
return saveOK
|
return saveOK
|
||||||
|
|
||||||
@@ -465,7 +440,7 @@ class GuiMain(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Switch main tab to editor view
|
# Switch main tab to editor view
|
||||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
self._changeView(nwView.PROJECT)
|
||||||
|
|
||||||
# Try to open the project
|
# Try to open the project
|
||||||
if not self.theProject.openProject(projFile):
|
if not self.theProject.openProject(projFile):
|
||||||
@@ -523,15 +498,15 @@ class GuiMain(QMainWindow):
|
|||||||
self.idleTime = 0.0
|
self.idleTime = 0.0
|
||||||
|
|
||||||
# Load the tag index
|
# Load the tag index
|
||||||
self.theIndex.loadIndex()
|
self.theProject.index.loadIndex()
|
||||||
|
|
||||||
# Update GUI
|
# Update GUI
|
||||||
self._updateWindowTitle(self.theProject.projName)
|
self._updateWindowTitle(self.theProject.projName)
|
||||||
self.rebuildTrees()
|
self.rebuildTrees()
|
||||||
self.docEditor.setDictionaries()
|
self.docEditor.setDictionaries()
|
||||||
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
|
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
|
||||||
self.mainMenu.setAutoOutline(self.theProject.autoOutline)
|
|
||||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||||
|
self.projView.updateRootItem(None)
|
||||||
self._updateStatusWordCount()
|
self._updateStatusWordCount()
|
||||||
|
|
||||||
# Restore previously open documents, if any
|
# Restore previously open documents, if any
|
||||||
@@ -542,10 +517,10 @@ class GuiMain(QMainWindow):
|
|||||||
self.viewDocument(self.theProject.lastViewed)
|
self.viewDocument(self.theProject.lastViewed)
|
||||||
|
|
||||||
# Check if we need to rebuild the index
|
# Check if we need to rebuild the index
|
||||||
if self.theIndex.indexBroken:
|
if self.theProject.index.indexBroken:
|
||||||
self.makeAlert(self.tr(
|
self.makeAlert(self.tr(
|
||||||
"The project index is outdated or broken. Rebuilding index."
|
"The project index is outdated or broken. Rebuilding index."
|
||||||
), nwAlert.WARN)
|
), nwAlert.INFO)
|
||||||
self.rebuildIndex()
|
self.rebuildIndex()
|
||||||
|
|
||||||
# Make sure the changed status is set to false on things opened
|
# Make sure the changed status is set to false on things opened
|
||||||
@@ -566,7 +541,7 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
self.treeView.saveTreeOrder()
|
self.treeView.saveTreeOrder()
|
||||||
if self.theProject.saveProject(autoSave=autoSave):
|
if self.theProject.saveProject(autoSave=autoSave):
|
||||||
self.theIndex.saveIndex()
|
self.theProject.index.saveIndex()
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -599,12 +574,12 @@ class GuiMain(QMainWindow):
|
|||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
return False
|
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)
|
logger.debug("Requested item '%s' is not a document", tHandle)
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.closeDocument()
|
self.closeDocument()
|
||||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
self._changeView(nwView.EDITOR)
|
||||||
if self.docEditor.loadText(tHandle, tLine):
|
if self.docEditor.loadText(tHandle, tLine):
|
||||||
if changeFocus:
|
if changeFocus:
|
||||||
self.docEditor.setFocus()
|
self.docEditor.setFocus()
|
||||||
@@ -623,12 +598,11 @@ class GuiMain(QMainWindow):
|
|||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.treeView.flushTreeOrder()
|
|
||||||
nHandle = None # The next handle after tHandle
|
nHandle = None # The next handle after tHandle
|
||||||
fHandle = None # The first file handle we encounter
|
fHandle = None # The first file handle we encounter
|
||||||
foundIt = False # We've found tHandle, pick the next we see
|
foundIt = False # We've found tHandle, pick the next we see
|
||||||
for tItem in self.theProject.projTree:
|
for tItem in self.theProject.tree:
|
||||||
if not self.theProject.projTree.checkType(tItem.itemHandle, nwItemType.FILE):
|
if not self.theProject.tree.checkType(tItem.itemHandle, nwItemType.FILE):
|
||||||
continue
|
continue
|
||||||
if fHandle is None:
|
if fHandle is None:
|
||||||
fHandle = tItem.itemHandle
|
fHandle = tItem.itemHandle
|
||||||
@@ -687,7 +661,7 @@ class GuiMain(QMainWindow):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# Make sure main tab is in Editor view
|
# 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)
|
logger.debug("Viewing document with handle '%s'", tHandle)
|
||||||
if self.docViewer.loadText(tHandle):
|
if self.docViewer.loadText(tHandle):
|
||||||
@@ -817,7 +791,7 @@ class GuiMain(QMainWindow):
|
|||||||
tHandle = self.treeView.getSelectedHandle()
|
tHandle = self.treeView.getSelectedHandle()
|
||||||
elif self.novelView.hasFocus():
|
elif self.novelView.hasFocus():
|
||||||
tHandle, tLine = self.novelView.getSelectedHandle()
|
tHandle, tLine = self.novelView.getSelectedHandle()
|
||||||
elif self.projView.hasFocus():
|
elif self.projView.treeFocus():
|
||||||
tHandle, tLine = self.projView.getSelectedHandle()
|
tHandle, tLine = self.projView.getSelectedHandle()
|
||||||
else:
|
else:
|
||||||
logger.warning("No item selected")
|
logger.warning("No item selected")
|
||||||
@@ -840,27 +814,10 @@ class GuiMain(QMainWindow):
|
|||||||
tHandle = self.docEditor.docHandle()
|
tHandle = self.docEditor.docHandle()
|
||||||
else:
|
else:
|
||||||
tHandle = self.treeView.getSelectedHandle()
|
tHandle = self.treeView.getSelectedHandle()
|
||||||
|
if tHandle:
|
||||||
|
return self.treeView.editTreeItem(tHandle)
|
||||||
|
|
||||||
if tHandle is None:
|
return False
|
||||||
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
|
|
||||||
|
|
||||||
def rebuildTrees(self):
|
def rebuildTrees(self):
|
||||||
"""Rebuild the project tree.
|
"""Rebuild the project tree.
|
||||||
@@ -872,7 +829,7 @@ class GuiMain(QMainWindow):
|
|||||||
def requestNovelTreeRefresh(self):
|
def requestNovelTreeRefresh(self):
|
||||||
"""Update the novel tree, but only if it is visible.
|
"""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()
|
self.novelView.refreshTree()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
@@ -889,25 +846,16 @@ class GuiMain(QMainWindow):
|
|||||||
tStart = time()
|
tStart = time()
|
||||||
|
|
||||||
self.treeView.saveTreeOrder()
|
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:
|
logger.verbose("Indexing '%s'", tItem.itemName)
|
||||||
self.setStatus(self.tr("Indexing: '{0}'").format(tItem.itemName))
|
if self.theProject.index.reIndexHandle(tItem.itemHandle):
|
||||||
else:
|
# Update Word Counts
|
||||||
self.setStatus(self.tr("Indexing: '{0}'").format(self.tr("Unknown item")))
|
self.treeView.propagateCount(tItem.itemHandle, tItem.wordCount, countChildren=True)
|
||||||
|
|
||||||
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)
|
|
||||||
self.treeView.setTreeItemValues(tItem.itemHandle)
|
self.treeView.setTreeItemValues(tItem.itemHandle)
|
||||||
|
|
||||||
tEnd = time()
|
tEnd = time()
|
||||||
@@ -925,19 +873,6 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
return True
|
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
|
# Main Dialogs
|
||||||
##
|
##
|
||||||
@@ -985,7 +920,6 @@ class GuiMain(QMainWindow):
|
|||||||
self.treeView.initTree()
|
self.treeView.initTree()
|
||||||
self.novelView.initTree()
|
self.novelView.initTree()
|
||||||
self.projView.initOutline()
|
self.projView.initOutline()
|
||||||
self.projMeta.initDetails()
|
|
||||||
self._updateStatusWordCount()
|
self._updateStatusWordCount()
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1002,7 +936,9 @@ class GuiMain(QMainWindow):
|
|||||||
|
|
||||||
if dlgProj.result() == QDialog.Accepted:
|
if dlgProj.result() == QDialog.Accepted:
|
||||||
logger.debug("Applying new project settings")
|
logger.debug("Applying new project settings")
|
||||||
self.docEditor.setDictionaries()
|
if dlgProj.spellChanged:
|
||||||
|
self.docEditor.setDictionaries()
|
||||||
|
self.treeMeta.refreshDetails()
|
||||||
self._updateWindowTitle(self.theProject.projName)
|
self._updateWindowTitle(self.theProject.projName)
|
||||||
|
|
||||||
return True
|
return True
|
||||||
@@ -1014,8 +950,6 @@ class GuiMain(QMainWindow):
|
|||||||
logger.error("No project open")
|
logger.error("No project open")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
self.treeView.flushTreeOrder()
|
|
||||||
|
|
||||||
dlgDetails = getGuiItem("GuiProjectDetails")
|
dlgDetails = getGuiItem("GuiProjectDetails")
|
||||||
if dlgDetails is None:
|
if dlgDetails is None:
|
||||||
dlgDetails = GuiProjectDetails(self)
|
dlgDetails = GuiProjectDetails(self)
|
||||||
@@ -1220,7 +1154,7 @@ class GuiMain(QMainWindow):
|
|||||||
if not self.isFocusMode:
|
if not self.isFocusMode:
|
||||||
self.mainConf.setMainPanePos(self.splitMain.sizes())
|
self.mainConf.setMainPanePos(self.splitMain.sizes())
|
||||||
self.mainConf.setDocPanePos(self.splitDocs.sizes())
|
self.mainConf.setDocPanePos(self.splitDocs.sizes())
|
||||||
self.mainConf.setOutlinePanePos(self.splitOutline.sizes())
|
self.mainConf.setOutlinePanePos(self.projView.splitSizes())
|
||||||
if self.viewMeta.isVisible():
|
if self.viewMeta.isVisible():
|
||||||
self.mainConf.setViewPanePos(self.splitView.sizes())
|
self.mainConf.setViewPanePos(self.splitView.sizes())
|
||||||
|
|
||||||
@@ -1244,20 +1178,20 @@ class GuiMain(QMainWindow):
|
|||||||
"""Switch focus between main GUI views.
|
"""Switch focus between main GUI views.
|
||||||
"""
|
"""
|
||||||
if paneNo == nwWidget.TREE:
|
if paneNo == nwWidget.TREE:
|
||||||
tabIdx = self.projTabs.currentIndex()
|
tabIdx = self.projStack.currentIndex()
|
||||||
if tabIdx == self.idxTreeView:
|
if tabIdx == self.idxTreeView:
|
||||||
self.treeView.setFocus()
|
self.treeView.setFocus()
|
||||||
elif tabIdx == self.idxNovelView:
|
elif tabIdx == self.idxNovelView:
|
||||||
self.novelView.setFocus()
|
self.novelView.setFocus()
|
||||||
elif paneNo == nwWidget.EDITOR:
|
elif paneNo == nwWidget.EDITOR:
|
||||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
self._changeView(nwView.EDITOR)
|
||||||
self.docEditor.setFocus()
|
self.docEditor.setFocus()
|
||||||
elif paneNo == nwWidget.VIEWER:
|
elif paneNo == nwWidget.VIEWER:
|
||||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
self._changeView(nwView.EDITOR)
|
||||||
self.docViewer.setFocus()
|
self.docViewer.setFocus()
|
||||||
elif paneNo == nwWidget.OUTLINE:
|
elif paneNo == nwWidget.OUTLINE:
|
||||||
self.mainTabs.setCurrentWidget(self.splitOutline)
|
self._changeView(nwView.OUTLINE)
|
||||||
self.projView.setFocus()
|
self.projView.setTreeFocus()
|
||||||
return
|
return
|
||||||
|
|
||||||
def closeDocEditor(self):
|
def closeDocEditor(self):
|
||||||
@@ -1289,7 +1223,6 @@ class GuiMain(QMainWindow):
|
|||||||
self.mainMenu.setFocusMode(self.isFocusMode)
|
self.mainMenu.setFocusMode(self.isFocusMode)
|
||||||
if self.isFocusMode:
|
if self.isFocusMode:
|
||||||
logger.debug("Activating Focus Mode")
|
logger.debug("Activating Focus Mode")
|
||||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
|
||||||
self.switchFocus(nwWidget.EDITOR)
|
self.switchFocus(nwWidget.EDITOR)
|
||||||
else:
|
else:
|
||||||
logger.debug("Deactivating Focus Mode")
|
logger.debug("Deactivating Focus Mode")
|
||||||
@@ -1298,7 +1231,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.treePane.setVisible(isVisible)
|
self.treePane.setVisible(isVisible)
|
||||||
self.statusBar.setVisible(isVisible)
|
self.statusBar.setVisible(isVisible)
|
||||||
self.mainMenu.setVisible(isVisible)
|
self.mainMenu.setVisible(isVisible)
|
||||||
self.mainTabs.tabBar().setVisible(isVisible)
|
self.viewsBar.setVisible(isVisible)
|
||||||
|
|
||||||
hideDocFooter = self.isFocusMode and self.mainConf.hideFocusFooter
|
hideDocFooter = self.isFocusMode and self.mainConf.hideFocusFooter
|
||||||
self.docEditor.docFooter.setVisible(not hideDocFooter)
|
self.docEditor.docFooter.setVisible(not hideDocFooter)
|
||||||
@@ -1426,7 +1359,7 @@ class GuiMain(QMainWindow):
|
|||||||
return True
|
return True
|
||||||
|
|
||||||
def _updateWindowTitle(self, projName=None):
|
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
|
winTitle = self.mainConf.appName
|
||||||
if projName is not None:
|
if projName is not None:
|
||||||
@@ -1469,9 +1402,9 @@ class GuiMain(QMainWindow):
|
|||||||
"popMinimal": newProj.field("popMinimal"),
|
"popMinimal": newProj.field("popMinimal"),
|
||||||
"popCustom": newProj.field("popCustom"),
|
"popCustom": newProj.field("popCustom"),
|
||||||
"addRoots": [],
|
"addRoots": [],
|
||||||
|
"addNotes": False,
|
||||||
"numChapters": 0,
|
"numChapters": 0,
|
||||||
"numScenes": 0,
|
"numScenes": 0,
|
||||||
"chFolders": False,
|
|
||||||
}
|
}
|
||||||
if newProj.field("popCustom"):
|
if newProj.field("popCustom"):
|
||||||
addRoots = []
|
addRoots = []
|
||||||
@@ -1481,19 +1414,30 @@ class GuiMain(QMainWindow):
|
|||||||
addRoots.append(nwItemClass.CHARACTER)
|
addRoots.append(nwItemClass.CHARACTER)
|
||||||
if newProj.field("addWorld"):
|
if newProj.field("addWorld"):
|
||||||
addRoots.append(nwItemClass.WORLD)
|
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["addRoots"] = addRoots
|
||||||
|
projData["addNotes"] = newProj.field("addNotes")
|
||||||
projData["numChapters"] = newProj.field("numChapters")
|
projData["numChapters"] = newProj.field("numChapters")
|
||||||
projData["numScenes"] = newProj.field("numScenes")
|
projData["numScenes"] = newProj.field("numScenes")
|
||||||
projData["chFolders"] = newProj.field("chFolders")
|
|
||||||
|
|
||||||
return projData
|
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
|
# Events
|
||||||
##
|
##
|
||||||
@@ -1509,9 +1453,42 @@ class GuiMain(QMainWindow):
|
|||||||
return
|
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()
|
@pyqtSlot()
|
||||||
def _timeTick(self):
|
def _timeTick(self):
|
||||||
"""Triggered on every tick of the main timer.
|
"""Triggered on every tick of the main timer.
|
||||||
@@ -1567,11 +1544,17 @@ class GuiMain(QMainWindow):
|
|||||||
@pyqtSlot("QTreeWidgetItem*", int)
|
@pyqtSlot("QTreeWidgetItem*", int)
|
||||||
def _treeDoubleClick(self, tItem, colNo):
|
def _treeDoubleClick(self, tItem, colNo):
|
||||||
"""The user double-clicked an item in the tree. If it is a file,
|
"""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()
|
tHandle = self.treeView.getSelectedHandle()
|
||||||
if tHandle is not None:
|
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
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -1579,11 +1562,10 @@ class GuiMain(QMainWindow):
|
|||||||
"""Triggered when there is a change to a novel item in the
|
"""Triggered when there is a change to a novel item in the
|
||||||
project tree.
|
project tree.
|
||||||
"""
|
"""
|
||||||
if self.mainTabs.currentIndex() == self.idxTabProj:
|
if self.mainStack.currentIndex() == self.idxOutlineView:
|
||||||
logger.verbose("Novel tree changed while Outline tab active")
|
logger.verbose("Novel tree changed while Outline tab active")
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
self.treeView.flushTreeOrder()
|
self.projView.refreshView(novelChanged=True)
|
||||||
self.projView.refreshTree(novelChanged=True)
|
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -1608,20 +1590,20 @@ class GuiMain(QMainWindow):
|
|||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def _mainTabChanged(self, tabIndex):
|
def _mainStackChanged(self, tabIndex):
|
||||||
"""Activated when the main window tab is changed.
|
"""Activated when the main window tab is changed.
|
||||||
"""
|
"""
|
||||||
if tabIndex == self.idxTabEdit:
|
if tabIndex == self.idxEditorView:
|
||||||
logger.verbose("Editor tab activated")
|
logger.verbose("Editor tab activated")
|
||||||
elif tabIndex == self.idxTabProj:
|
elif tabIndex == self.idxOutlineView:
|
||||||
logger.verbose("Project outline tab activated")
|
logger.verbose("Project outline tab activated")
|
||||||
if self.hasProject:
|
if self.hasProject:
|
||||||
self.projView.refreshTree()
|
self.projView.refreshView()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot(int)
|
@pyqtSlot(int)
|
||||||
def _projTabsChanged(self, tabIndex):
|
def _projStackChanged(self, tabIndex):
|
||||||
"""Activated when the project view tab is changed.
|
"""Activated when the project view tab is changed.
|
||||||
"""
|
"""
|
||||||
sHandle = None
|
sHandle = None
|
||||||
|
|||||||
@@ -75,7 +75,6 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.optState = theParent.theProject.optState
|
|
||||||
|
|
||||||
self.htmlText = [] # List of html documents
|
self.htmlText = [] # List of html documents
|
||||||
self.htmlStyle = [] # List of html styles
|
self.htmlStyle = [] # List of html styles
|
||||||
@@ -86,9 +85,10 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.setMinimumWidth(self.mainConf.pxInt(700))
|
self.setMinimumWidth(self.mainConf.pxInt(700))
|
||||||
self.setMinimumHeight(self.mainConf.pxInt(600))
|
self.setMinimumHeight(self.mainConf.pxInt(600))
|
||||||
|
|
||||||
|
pOptions = self.theProject.options
|
||||||
self.resize(
|
self.resize(
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winWidth", 900)),
|
self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winWidth", 900)),
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winHeight", 800))
|
self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winHeight", 800))
|
||||||
)
|
)
|
||||||
|
|
||||||
self.docView = GuiBuildNovelDocView(self, self.theProject)
|
self.docView = GuiBuildNovelDocView(self, self.theProject)
|
||||||
@@ -174,12 +174,12 @@ class GuiBuildNovel(QDialog):
|
|||||||
|
|
||||||
self.hideScene = QSwitch(width=wS, height=hS)
|
self.hideScene = QSwitch(width=wS, height=hS)
|
||||||
self.hideScene.setChecked(
|
self.hideScene.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "hideScene", False)
|
pOptions.getBool("GuiBuildNovel", "hideScene", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.hideSection = QSwitch(width=wS, height=hS)
|
self.hideSection = QSwitch(width=wS, height=hS)
|
||||||
self.hideSection.setChecked(
|
self.hideSection.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "hideSection", True)
|
pOptions.getBool("GuiBuildNovel", "hideSection", True)
|
||||||
)
|
)
|
||||||
|
|
||||||
# Wrapper boxes due to QGridView and QLineEdit expand bug
|
# Wrapper boxes due to QGridView and QLineEdit expand bug
|
||||||
@@ -235,7 +235,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.textFont.setReadOnly(True)
|
self.textFont.setReadOnly(True)
|
||||||
self.textFont.setMinimumWidth(xFmt)
|
self.textFont.setMinimumWidth(xFmt)
|
||||||
self.textFont.setText(
|
self.textFont.setText(
|
||||||
self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
|
pOptions.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
|
||||||
)
|
)
|
||||||
self.fontButton = QPushButton("...")
|
self.fontButton = QPushButton("...")
|
||||||
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||||
@@ -247,7 +247,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.textSize.setMaximum(72)
|
self.textSize.setMaximum(72)
|
||||||
self.textSize.setSingleStep(1)
|
self.textSize.setSingleStep(1)
|
||||||
self.textSize.setValue(
|
self.textSize.setValue(
|
||||||
self.optState.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
|
pOptions.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.lineHeight = QDoubleSpinBox(self)
|
self.lineHeight = QDoubleSpinBox(self)
|
||||||
@@ -257,7 +257,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.lineHeight.setSingleStep(0.05)
|
self.lineHeight.setSingleStep(0.05)
|
||||||
self.lineHeight.setDecimals(2)
|
self.lineHeight.setDecimals(2)
|
||||||
self.lineHeight.setValue(
|
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
|
# 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 = QSwitch(width=wS, height=hS)
|
||||||
self.justifyText.setChecked(
|
self.justifyText.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "justifyText", False)
|
pOptions.getBool("GuiBuildNovel", "justifyText", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.noStyling = QSwitch(width=wS, height=hS)
|
self.noStyling = QSwitch(width=wS, height=hS)
|
||||||
self.noStyling.setChecked(
|
self.noStyling.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "noStyling", False)
|
pOptions.getBool("GuiBuildNovel", "noStyling", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.styleForm.addWidget(justifyLabel, 1, 0, 1, 1, Qt.AlignLeft)
|
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 = QSwitch(width=wS, height=hS)
|
||||||
self.includeSynopsis.setChecked(
|
self.includeSynopsis.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "incSynopsis", False)
|
pOptions.getBool("GuiBuildNovel", "incSynopsis", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.includeComments = QSwitch(width=wS, height=hS)
|
self.includeComments = QSwitch(width=wS, height=hS)
|
||||||
self.includeComments.setChecked(
|
self.includeComments.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "incComments", False)
|
pOptions.getBool("GuiBuildNovel", "incComments", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.includeKeywords = QSwitch(width=wS, height=hS)
|
self.includeKeywords = QSwitch(width=wS, height=hS)
|
||||||
self.includeKeywords.setChecked(
|
self.includeKeywords.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "incKeywords", False)
|
pOptions.getBool("GuiBuildNovel", "incKeywords", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.includeBody = QSwitch(width=wS, height=hS)
|
self.includeBody = QSwitch(width=wS, height=hS)
|
||||||
self.includeBody.setChecked(
|
self.includeBody.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "incBodyText", True)
|
pOptions.getBool("GuiBuildNovel", "incBodyText", True)
|
||||||
)
|
)
|
||||||
|
|
||||||
synopsisLabel = QLabel(self.tr("Include synopsis"))
|
synopsisLabel = QLabel(self.tr("Include synopsis"))
|
||||||
@@ -360,17 +360,17 @@ class GuiBuildNovel(QDialog):
|
|||||||
|
|
||||||
self.novelFiles = QSwitch(width=wS, height=hS)
|
self.novelFiles = QSwitch(width=wS, height=hS)
|
||||||
self.novelFiles.setChecked(
|
self.novelFiles.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "addNovel", True)
|
pOptions.getBool("GuiBuildNovel", "addNovel", True)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.noteFiles = QSwitch(width=wS, height=hS)
|
self.noteFiles = QSwitch(width=wS, height=hS)
|
||||||
self.noteFiles.setChecked(
|
self.noteFiles.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "addNotes", False)
|
pOptions.getBool("GuiBuildNovel", "addNotes", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.ignoreFlag = QSwitch(width=wS, height=hS)
|
self.ignoreFlag = QSwitch(width=wS, height=hS)
|
||||||
self.ignoreFlag.setChecked(
|
self.ignoreFlag.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "ignoreFlag", False)
|
pOptions.getBool("GuiBuildNovel", "ignoreFlag", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
novelLabel = QLabel(self.tr("Include novel files"))
|
novelLabel = QLabel(self.tr("Include novel files"))
|
||||||
@@ -396,12 +396,12 @@ class GuiBuildNovel(QDialog):
|
|||||||
|
|
||||||
self.replaceTabs = QSwitch(width=wS, height=hS)
|
self.replaceTabs = QSwitch(width=wS, height=hS)
|
||||||
self.replaceTabs.setChecked(
|
self.replaceTabs.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "replaceTabs", False)
|
pOptions.getBool("GuiBuildNovel", "replaceTabs", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.replaceUCode = QSwitch(width=wS, height=hS)
|
self.replaceUCode = QSwitch(width=wS, height=hS)
|
||||||
self.replaceUCode.setChecked(
|
self.replaceUCode.setChecked(
|
||||||
self.optState.getBool("GuiBuildNovel", "replaceUCode", False)
|
pOptions.getBool("GuiBuildNovel", "replaceUCode", False)
|
||||||
)
|
)
|
||||||
|
|
||||||
tabsLabel = QLabel(self.tr("Replace tabs with spaces"))
|
tabsLabel = QLabel(self.tr("Replace tabs with spaces"))
|
||||||
@@ -493,9 +493,9 @@ class GuiBuildNovel(QDialog):
|
|||||||
|
|
||||||
# Splitter Position
|
# Splitter Position
|
||||||
boxWidth = self.mainConf.pxInt(350)
|
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 = max(self.width() - boxWidth, 100)
|
||||||
docWidth = self.optState.getInt("GuiBuildNovel", "docWidth", docWidth)
|
docWidth = pOptions.getInt("GuiBuildNovel", "docWidth", docWidth)
|
||||||
|
|
||||||
# The Tool Box
|
# The Tool Box
|
||||||
self.toolsBox = QVBoxLayout()
|
self.toolsBox = QVBoxLayout()
|
||||||
@@ -659,6 +659,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
fmtUnnumbered = self.fmtUnnumbered.text()
|
fmtUnnumbered = self.fmtUnnumbered.text()
|
||||||
fmtScene = self.fmtScene.text()
|
fmtScene = self.fmtScene.text()
|
||||||
fmtSection = self.fmtSection.text()
|
fmtSection = self.fmtSection.text()
|
||||||
|
buildLang = self.buildLang.currentData()
|
||||||
hideScene = self.hideScene.isChecked()
|
hideScene = self.hideScene.isChecked()
|
||||||
hideSection = self.hideSection.isChecked()
|
hideSection = self.hideSection.isChecked()
|
||||||
textFont = self.textFont.text()
|
textFont = self.textFont.text()
|
||||||
@@ -676,7 +677,7 @@ class GuiBuildNovel(QDialog):
|
|||||||
replaceUCode = self.replaceUCode.isChecked()
|
replaceUCode = self.replaceUCode.isChecked()
|
||||||
|
|
||||||
# The language lookup dict is reloaded if needed
|
# The language lookup dict is reloaded if needed
|
||||||
self.theProject.setProjectLang(self.buildLang.currentData())
|
self.theProject.setProjectLang(buildLang)
|
||||||
|
|
||||||
# Get font information
|
# Get font information
|
||||||
fontInfo = QFontInfo(QFont(textFont, textSize))
|
fontInfo = QFontInfo(QFont(textFont, textSize))
|
||||||
@@ -706,16 +707,16 @@ class GuiBuildNovel(QDialog):
|
|||||||
|
|
||||||
if isOdt:
|
if isOdt:
|
||||||
bldObj.setColourHeaders(not noStyling)
|
bldObj.setColourHeaders(not noStyling)
|
||||||
|
bldObj.setLanguage(buildLang)
|
||||||
bldObj.initDocument()
|
bldObj.initDocument()
|
||||||
|
|
||||||
# Make sure the project and document is up to date
|
# Make sure the project and document is up to date
|
||||||
self.theParent.treeView.flushTreeOrder()
|
|
||||||
self.theParent.saveDocument()
|
self.theParent.saveDocument()
|
||||||
|
|
||||||
self.buildProgress.setMaximum(len(self.theProject.projTree))
|
self.buildProgress.setMaximum(len(self.theProject.tree))
|
||||||
self.buildProgress.setValue(0)
|
self.buildProgress.setValue(0)
|
||||||
|
|
||||||
for nItt, tItem in enumerate(self.theProject.projTree):
|
for nItt, tItem in enumerate(self.theProject.tree):
|
||||||
|
|
||||||
noteRoot = noteFiles
|
noteRoot = noteFiles
|
||||||
noteRoot &= tItem.itemType == nwItemType.ROOT
|
noteRoot &= tItem.itemType == nwItemType.ROOT
|
||||||
@@ -780,14 +781,12 @@ class GuiBuildNovel(QDialog):
|
|||||||
if theItem is None:
|
if theItem is None:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
if not theItem.isExported and not ignoreFlag:
|
if not (theItem.isExported or ignoreFlag):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
isNone = theItem.itemType != nwItemType.FILE
|
isNone = theItem.itemType != nwItemType.FILE
|
||||||
isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
||||||
isNone |= theItem.itemClass == nwItemClass.NO_CLASS
|
isNone |= theItem.isInactive()
|
||||||
isNone |= theItem.itemClass == nwItemClass.TRASH
|
|
||||||
isNone |= theItem.itemParent == self.theProject.projTree.trashRoot()
|
|
||||||
isNone |= theItem.itemParent is None
|
isNone |= theItem.itemParent is None
|
||||||
isNote = theItem.itemLayout == nwItemLayout.NOTE
|
isNote = theItem.itemLayout == nwItemLayout.NOTE
|
||||||
isNovel = not isNone and not isNote
|
isNovel = not isNone and not isNote
|
||||||
@@ -799,10 +798,6 @@ class GuiBuildNovel(QDialog):
|
|||||||
if isNovel and not novelFiles:
|
if isNovel and not novelFiles:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
rootItem = self.theProject.projTree.getRootItem(theItem.itemHandle)
|
|
||||||
if rootItem.itemClass == nwItemClass.ARCHIVE:
|
|
||||||
return False
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def _saveDocument(self, theFmt):
|
def _saveDocument(self, theFmt):
|
||||||
@@ -1159,28 +1154,28 @@ class GuiBuildNovel(QDialog):
|
|||||||
self.theProject.setProjectLang(buildLang)
|
self.theProject.setProjectLang(buildLang)
|
||||||
|
|
||||||
# GUI Settings
|
# GUI Settings
|
||||||
self.optState.setValue("GuiBuildNovel", "hideScene", hideScene)
|
pOptions = self.theProject.options
|
||||||
self.optState.setValue("GuiBuildNovel", "hideSection", hideSection)
|
pOptions.setValue("GuiBuildNovel", "hideScene", hideScene)
|
||||||
self.optState.setValue("GuiBuildNovel", "winWidth", winWidth)
|
pOptions.setValue("GuiBuildNovel", "hideSection", hideSection)
|
||||||
self.optState.setValue("GuiBuildNovel", "winHeight", winHeight)
|
pOptions.setValue("GuiBuildNovel", "winWidth", winWidth)
|
||||||
self.optState.setValue("GuiBuildNovel", "boxWidth", boxWidth)
|
pOptions.setValue("GuiBuildNovel", "winHeight", winHeight)
|
||||||
self.optState.setValue("GuiBuildNovel", "docWidth", docWidth)
|
pOptions.setValue("GuiBuildNovel", "boxWidth", boxWidth)
|
||||||
self.optState.setValue("GuiBuildNovel", "justifyText", justifyText)
|
pOptions.setValue("GuiBuildNovel", "docWidth", docWidth)
|
||||||
self.optState.setValue("GuiBuildNovel", "noStyling", noStyling)
|
pOptions.setValue("GuiBuildNovel", "justifyText", justifyText)
|
||||||
self.optState.setValue("GuiBuildNovel", "textFont", textFont)
|
pOptions.setValue("GuiBuildNovel", "noStyling", noStyling)
|
||||||
self.optState.setValue("GuiBuildNovel", "textSize", textSize)
|
pOptions.setValue("GuiBuildNovel", "textFont", textFont)
|
||||||
self.optState.setValue("GuiBuildNovel", "lineHeight", lineHeight)
|
pOptions.setValue("GuiBuildNovel", "textSize", textSize)
|
||||||
self.optState.setValue("GuiBuildNovel", "addNovel", novelFiles)
|
pOptions.setValue("GuiBuildNovel", "lineHeight", lineHeight)
|
||||||
self.optState.setValue("GuiBuildNovel", "addNotes", noteFiles)
|
pOptions.setValue("GuiBuildNovel", "addNovel", novelFiles)
|
||||||
self.optState.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag)
|
pOptions.setValue("GuiBuildNovel", "addNotes", noteFiles)
|
||||||
self.optState.setValue("GuiBuildNovel", "incSynopsis", incSynopsis)
|
pOptions.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag)
|
||||||
self.optState.setValue("GuiBuildNovel", "incComments", incComments)
|
pOptions.setValue("GuiBuildNovel", "incSynopsis", incSynopsis)
|
||||||
self.optState.setValue("GuiBuildNovel", "incKeywords", incKeywords)
|
pOptions.setValue("GuiBuildNovel", "incComments", incComments)
|
||||||
self.optState.setValue("GuiBuildNovel", "incBodyText", incBodyText)
|
pOptions.setValue("GuiBuildNovel", "incKeywords", incKeywords)
|
||||||
self.optState.setValue("GuiBuildNovel", "replaceTabs", replaceTabs)
|
pOptions.setValue("GuiBuildNovel", "incBodyText", incBodyText)
|
||||||
self.optState.setValue("GuiBuildNovel", "replaceUCode", replaceUCode)
|
pOptions.setValue("GuiBuildNovel", "replaceTabs", replaceTabs)
|
||||||
|
pOptions.setValue("GuiBuildNovel", "replaceUCode", replaceUCode)
|
||||||
self.optState.saveSettings()
|
pOptions.saveSettings()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ class GuiLipsum(QDialog):
|
|||||||
# Form
|
# Form
|
||||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Insert Lorem Ipsum Text")))
|
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 = QSpinBox()
|
||||||
self.paraCount.setMinimum(1)
|
self.paraCount.setMinimum(1)
|
||||||
self.paraCount.setMaximum(100)
|
self.paraCount.setMaximum(100)
|
||||||
|
|||||||
@@ -31,12 +31,10 @@ from PyQt5.QtCore import Qt
|
|||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit,
|
QWizard, QWizardPage, QLabel, QVBoxLayout, QLineEdit, QPlainTextEdit,
|
||||||
QPushButton, QFileDialog, QHBoxLayout, QRadioButton, QFormLayout,
|
QPushButton, QFileDialog, QHBoxLayout, QRadioButton, QFormLayout,
|
||||||
QGroupBox, QGridLayout, QSpinBox
|
QGridLayout, QSpinBox
|
||||||
)
|
)
|
||||||
|
|
||||||
from novelwriter.enum import nwItemClass
|
|
||||||
from novelwriter.common import makeFileNameSafe
|
from novelwriter.common import makeFileNameSafe
|
||||||
from novelwriter.constants import trConst, nwLabels
|
|
||||||
from novelwriter.gui.custom import QSwitch
|
from novelwriter.gui.custom import QSwitch
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -98,10 +96,10 @@ class ProjWizardIntroPage(QWizardPage):
|
|||||||
|
|
||||||
self.setTitle(self.tr("Create New Project"))
|
self.setTitle(self.tr("Create New Project"))
|
||||||
self.theText = QLabel(self.tr(
|
self.theText = QLabel(self.tr(
|
||||||
"Provide at least a working title. The working title should not "
|
"Provide at least a project name. The project name should not "
|
||||||
"be change beyond this point as it is used by the application for "
|
"be changed beyond this point as it is used for generating file "
|
||||||
"generating file names for for instance backups. The other fields "
|
"names for for instance backups. The other fields are optional "
|
||||||
"are optional and can be changed at any time in Project Settings."
|
"and can be changed at any time in Project Settings."
|
||||||
))
|
))
|
||||||
self.theText.setWordWrap(True)
|
self.theText.setWordWrap(True)
|
||||||
|
|
||||||
@@ -134,7 +132,7 @@ class ProjWizardIntroPage(QWizardPage):
|
|||||||
self.projAuthors.setPlaceholderText(self.tr("Optional. One name per line."))
|
self.projAuthors.setPlaceholderText(self.tr("Optional. One name per line."))
|
||||||
|
|
||||||
self.mainForm = QFormLayout()
|
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("Novel Title"), self.projTitle)
|
||||||
self.mainForm.addRow(self.tr("Author(s)"), self.projAuthors)
|
self.mainForm.addRow(self.tr("Author(s)"), self.projAuthors)
|
||||||
self.mainForm.setVerticalSpacing(fS)
|
self.mainForm.setVerticalSpacing(fS)
|
||||||
@@ -185,6 +183,9 @@ class ProjWizardFolderPage(QWizardPage):
|
|||||||
self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||||
self.browseButton.clicked.connect(self._doBrowse)
|
self.browseButton.clicked.connect(self._doBrowse)
|
||||||
|
|
||||||
|
self.errLabel = QLabel("")
|
||||||
|
self.errLabel.setWordWrap(True)
|
||||||
|
|
||||||
self.mainForm = QHBoxLayout()
|
self.mainForm = QHBoxLayout()
|
||||||
self.mainForm.addWidget(QLabel(self.tr("Project Path")), 0)
|
self.mainForm.addWidget(QLabel(self.tr("Project Path")), 0)
|
||||||
self.mainForm.addWidget(self.projPath, 1)
|
self.mainForm.addWidget(self.projPath, 1)
|
||||||
@@ -198,11 +199,36 @@ class ProjWizardFolderPage(QWizardPage):
|
|||||||
self.outerBox.setSpacing(vS)
|
self.outerBox.setSpacing(vS)
|
||||||
self.outerBox.addWidget(self.theText)
|
self.outerBox.addWidget(self.theText)
|
||||||
self.outerBox.addLayout(self.mainForm)
|
self.outerBox.addLayout(self.mainForm)
|
||||||
|
self.outerBox.addWidget(self.errLabel)
|
||||||
self.outerBox.addStretch(1)
|
self.outerBox.addStretch(1)
|
||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
return
|
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
|
# Slots
|
||||||
##
|
##
|
||||||
@@ -296,68 +322,28 @@ class ProjWizardCustomPage(QWizardPage):
|
|||||||
|
|
||||||
self.setTitle(self.tr("Custom Project Options"))
|
self.setTitle(self.tr("Custom Project Options"))
|
||||||
self.theText = QLabel(self.tr(
|
self.theText = QLabel(self.tr(
|
||||||
"Select which additional root folders to make, and how to populate "
|
"Select which additional elements to populate the project with. "
|
||||||
"the Novel folder. If you don't want to add chapters or scenes, set "
|
"You can skip making chapters and add only scenes by setting the "
|
||||||
"the values to 0. You can add scenes without chapters."
|
"number of chapters to 0."
|
||||||
))
|
))
|
||||||
self.theText.setWordWrap(True)
|
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
|
# Root Folders
|
||||||
self.rootGroup = QGroupBox(self.tr("Additional Root Folders"))
|
self.addPlot = QSwitch()
|
||||||
self.rootForm = QGridLayout()
|
self.addChar = QSwitch()
|
||||||
self.rootGroup.setLayout(self.rootForm)
|
self.addWorld = QSwitch()
|
||||||
|
self.addNotes = QSwitch()
|
||||||
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.setChecked(True)
|
self.addPlot.setChecked(True)
|
||||||
self.addChar.setChecked(True)
|
self.addChar.setChecked(True)
|
||||||
self.addWorld.setChecked(True)
|
self.addWorld.setChecked(False)
|
||||||
|
self.addNotes.setChecked(False)
|
||||||
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)
|
|
||||||
|
|
||||||
|
# Generate Content
|
||||||
self.numChapters = QSpinBox()
|
self.numChapters = QSpinBox()
|
||||||
self.numChapters.setRange(0, 100)
|
self.numChapters.setRange(0, 100)
|
||||||
self.numChapters.setValue(5)
|
self.numChapters.setValue(5)
|
||||||
@@ -366,37 +352,40 @@ class ProjWizardCustomPage(QWizardPage):
|
|||||||
self.numScenes.setRange(0, 200)
|
self.numScenes.setRange(0, 200)
|
||||||
self.numScenes.setValue(5)
|
self.numScenes.setValue(5)
|
||||||
|
|
||||||
self.chFolders = QSwitch()
|
# Grid Form
|
||||||
self.chFolders.setChecked(True)
|
self.addBox = QGridLayout()
|
||||||
|
self.addBox.addWidget(QLabel(self.tr("Add a folder for plot notes")), 0, 0)
|
||||||
self.novelForm.addWidget(QLabel(self.tr("Add chapters")), 0, 0)
|
self.addBox.addWidget(QLabel(self.tr("Add a folder for character notes")), 1, 0)
|
||||||
self.novelForm.addWidget(QLabel(self.tr("Scenes (per chapter)")), 1, 0)
|
self.addBox.addWidget(QLabel(self.tr("Add a folder for location notes")), 2, 0)
|
||||||
self.novelForm.addWidget(QLabel(self.tr("Add chapter folders")), 2, 0)
|
self.addBox.addWidget(QLabel(self.tr("Add example notes to the above")), 3, 0)
|
||||||
self.novelForm.addWidget(self.numChapters, 0, 1, 1, 1, Qt.AlignRight)
|
self.addBox.addWidget(QLabel(self.tr("Add chapters to the novel folder")), 4, 0)
|
||||||
self.novelForm.addWidget(self.numScenes, 1, 1, 1, 1, Qt.AlignRight)
|
self.addBox.addWidget(QLabel(self.tr("Add scenes to each chapter")), 5, 0)
|
||||||
self.novelForm.addWidget(self.chFolders, 2, 1, 1, 1, Qt.AlignRight)
|
self.addBox.addWidget(self.addPlot, 0, 1, 1, 1, Qt.AlignRight)
|
||||||
self.novelForm.setRowStretch(3, 1)
|
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
|
# Wizard Fields
|
||||||
self.registerField("addPlot", self.addPlot)
|
self.registerField("addPlot", self.addPlot)
|
||||||
self.registerField("addChar", self.addChar)
|
self.registerField("addChar", self.addChar)
|
||||||
self.registerField("addWorld", self.addWorld)
|
self.registerField("addWorld", self.addWorld)
|
||||||
self.registerField("addTime", self.addTime)
|
self.registerField("addNotes", self.addNotes)
|
||||||
self.registerField("addObject", self.addObject)
|
|
||||||
self.registerField("addEntity", self.addEntity)
|
|
||||||
self.registerField("numChapters", self.numChapters)
|
self.registerField("numChapters", self.numChapters)
|
||||||
self.registerField("numScenes", self.numScenes)
|
self.registerField("numScenes", self.numScenes)
|
||||||
self.registerField("chFolders", self.chFolders)
|
|
||||||
|
|
||||||
# Assemble
|
# Assemble
|
||||||
self.innerBox = QHBoxLayout()
|
|
||||||
self.innerBox.addWidget(self.rootGroup)
|
|
||||||
self.innerBox.addWidget(self.novelGroup)
|
|
||||||
|
|
||||||
self.outerBox = QVBoxLayout()
|
self.outerBox = QVBoxLayout()
|
||||||
self.outerBox.setSpacing(vS)
|
self.outerBox.setSpacing(cM)
|
||||||
self.outerBox.addWidget(self.theText)
|
self.outerBox.addWidget(self.theText)
|
||||||
self.outerBox.addLayout(self.innerBox)
|
self.outerBox.addLayout(self.addBox)
|
||||||
self.outerBox.addStretch(1)
|
self.outerBox.addStretch(1)
|
||||||
self.setLayout(self.outerBox)
|
self.setLayout(self.outerBox)
|
||||||
|
|
||||||
@@ -413,15 +402,8 @@ class ProjWizardFinalPage(QWizardPage):
|
|||||||
self.mainConf = novelwriter.CONFIG
|
self.mainConf = novelwriter.CONFIG
|
||||||
self.theWizard = theWizard
|
self.theWizard = theWizard
|
||||||
|
|
||||||
self.setTitle(self.tr("Finished"))
|
self.setTitle(self.tr("Summary"))
|
||||||
self.theText = QLabel(
|
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.theText.setWordWrap(True)
|
self.theText.setWordWrap(True)
|
||||||
|
|
||||||
# Assemble
|
# Assemble
|
||||||
@@ -433,4 +415,51 @@ class ProjWizardFinalPage(QWizardPage):
|
|||||||
|
|
||||||
return
|
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
|
# END Class ProjWizardFinalPage
|
||||||
|
|||||||
@@ -67,33 +67,34 @@ class GuiWritingStats(QDialog):
|
|||||||
self.theParent = theParent
|
self.theParent = theParent
|
||||||
self.theTheme = theParent.theTheme
|
self.theTheme = theParent.theTheme
|
||||||
self.theProject = theParent.theProject
|
self.theProject = theParent.theProject
|
||||||
self.optState = theParent.theProject.optState
|
|
||||||
|
|
||||||
self.logData = []
|
self.logData = []
|
||||||
self.filterData = []
|
self.filterData = []
|
||||||
self.timeFilter = 0.0
|
self.timeFilter = 0.0
|
||||||
self.wordOffset = 0
|
self.wordOffset = 0
|
||||||
|
|
||||||
|
pOptions = self.theProject.options
|
||||||
|
|
||||||
self.setWindowTitle(self.tr("Writing Statistics"))
|
self.setWindowTitle(self.tr("Writing Statistics"))
|
||||||
self.setMinimumWidth(self.mainConf.pxInt(420))
|
self.setMinimumWidth(self.mainConf.pxInt(420))
|
||||||
self.setMinimumHeight(self.mainConf.pxInt(400))
|
self.setMinimumHeight(self.mainConf.pxInt(400))
|
||||||
self.resize(
|
self.resize(
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiWritingStats", "winWidth", 550)),
|
self.mainConf.pxInt(pOptions.getInt("GuiWritingStats", "winWidth", 550)),
|
||||||
self.mainConf.pxInt(self.optState.getInt("GuiWritingStats", "winHeight", 500))
|
self.mainConf.pxInt(pOptions.getInt("GuiWritingStats", "winHeight", 500))
|
||||||
)
|
)
|
||||||
|
|
||||||
# List Box
|
# List Box
|
||||||
wCol0 = self.mainConf.pxInt(
|
wCol0 = self.mainConf.pxInt(
|
||||||
self.optState.getInt("GuiWritingStats", "widthCol0", 180)
|
pOptions.getInt("GuiWritingStats", "widthCol0", 180)
|
||||||
)
|
)
|
||||||
wCol1 = self.mainConf.pxInt(
|
wCol1 = self.mainConf.pxInt(
|
||||||
self.optState.getInt("GuiWritingStats", "widthCol1", 80)
|
pOptions.getInt("GuiWritingStats", "widthCol1", 80)
|
||||||
)
|
)
|
||||||
wCol2 = self.mainConf.pxInt(
|
wCol2 = self.mainConf.pxInt(
|
||||||
self.optState.getInt("GuiWritingStats", "widthCol2", 80)
|
pOptions.getInt("GuiWritingStats", "widthCol2", 80)
|
||||||
)
|
)
|
||||||
wCol3 = self.mainConf.pxInt(
|
wCol3 = self.mainConf.pxInt(
|
||||||
self.optState.getInt("GuiWritingStats", "widthCol3", 80)
|
pOptions.getInt("GuiWritingStats", "widthCol3", 80)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.listBox = QTreeWidget()
|
self.listBox = QTreeWidget()
|
||||||
@@ -115,9 +116,9 @@ class GuiWritingStats(QDialog):
|
|||||||
hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
|
hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
|
||||||
hHeader.setTextAlignment(self.C_COUNT, 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(
|
sortOrder = checkIntTuple(
|
||||||
self.optState.getInt("GuiWritingStats", "sortOrder", Qt.DescendingOrder),
|
pOptions.getInt("GuiWritingStats", "sortOrder", Qt.DescendingOrder),
|
||||||
(Qt.AscendingOrder, Qt.DescendingOrder), Qt.DescendingOrder
|
(Qt.AscendingOrder, Qt.DescendingOrder), Qt.DescendingOrder
|
||||||
)
|
)
|
||||||
self.listBox.sortByColumn(sortCol, sortOrder)
|
self.listBox.sortByColumn(sortCol, sortOrder)
|
||||||
@@ -190,37 +191,37 @@ class GuiWritingStats(QDialog):
|
|||||||
|
|
||||||
self.incNovel = QSwitch(width=2*sPx, height=sPx)
|
self.incNovel = QSwitch(width=2*sPx, height=sPx)
|
||||||
self.incNovel.setChecked(
|
self.incNovel.setChecked(
|
||||||
self.optState.getBool("GuiWritingStats", "incNovel", True)
|
pOptions.getBool("GuiWritingStats", "incNovel", True)
|
||||||
)
|
)
|
||||||
self.incNovel.clicked.connect(self._updateListBox)
|
self.incNovel.clicked.connect(self._updateListBox)
|
||||||
|
|
||||||
self.incNotes = QSwitch(width=2*sPx, height=sPx)
|
self.incNotes = QSwitch(width=2*sPx, height=sPx)
|
||||||
self.incNotes.setChecked(
|
self.incNotes.setChecked(
|
||||||
self.optState.getBool("GuiWritingStats", "incNotes", True)
|
pOptions.getBool("GuiWritingStats", "incNotes", True)
|
||||||
)
|
)
|
||||||
self.incNotes.clicked.connect(self._updateListBox)
|
self.incNotes.clicked.connect(self._updateListBox)
|
||||||
|
|
||||||
self.hideZeros = QSwitch(width=2*sPx, height=sPx)
|
self.hideZeros = QSwitch(width=2*sPx, height=sPx)
|
||||||
self.hideZeros.setChecked(
|
self.hideZeros.setChecked(
|
||||||
self.optState.getBool("GuiWritingStats", "hideZeros", True)
|
pOptions.getBool("GuiWritingStats", "hideZeros", True)
|
||||||
)
|
)
|
||||||
self.hideZeros.clicked.connect(self._updateListBox)
|
self.hideZeros.clicked.connect(self._updateListBox)
|
||||||
|
|
||||||
self.hideNegative = QSwitch(width=2*sPx, height=sPx)
|
self.hideNegative = QSwitch(width=2*sPx, height=sPx)
|
||||||
self.hideNegative.setChecked(
|
self.hideNegative.setChecked(
|
||||||
self.optState.getBool("GuiWritingStats", "hideNegative", False)
|
pOptions.getBool("GuiWritingStats", "hideNegative", False)
|
||||||
)
|
)
|
||||||
self.hideNegative.clicked.connect(self._updateListBox)
|
self.hideNegative.clicked.connect(self._updateListBox)
|
||||||
|
|
||||||
self.groupByDay = QSwitch(width=2*sPx, height=sPx)
|
self.groupByDay = QSwitch(width=2*sPx, height=sPx)
|
||||||
self.groupByDay.setChecked(
|
self.groupByDay.setChecked(
|
||||||
self.optState.getBool("GuiWritingStats", "groupByDay", False)
|
pOptions.getBool("GuiWritingStats", "groupByDay", False)
|
||||||
)
|
)
|
||||||
self.groupByDay.clicked.connect(self._updateListBox)
|
self.groupByDay.clicked.connect(self._updateListBox)
|
||||||
|
|
||||||
self.showIdleTime = QSwitch(width=2*sPx, height=sPx)
|
self.showIdleTime = QSwitch(width=2*sPx, height=sPx)
|
||||||
self.showIdleTime.setChecked(
|
self.showIdleTime.setChecked(
|
||||||
self.optState.getBool("GuiWritingStats", "showIdleTime", False)
|
pOptions.getBool("GuiWritingStats", "showIdleTime", False)
|
||||||
)
|
)
|
||||||
self.showIdleTime.clicked.connect(self._updateListBox)
|
self.showIdleTime.clicked.connect(self._updateListBox)
|
||||||
|
|
||||||
@@ -244,7 +245,7 @@ class GuiWritingStats(QDialog):
|
|||||||
self.histMax.setMaximum(100000)
|
self.histMax.setMaximum(100000)
|
||||||
self.histMax.setSingleStep(100)
|
self.histMax.setSingleStep(100)
|
||||||
self.histMax.setValue(
|
self.histMax.setValue(
|
||||||
self.optState.getInt("GuiWritingStats", "histMax", 2000)
|
pOptions.getInt("GuiWritingStats", "histMax", 2000)
|
||||||
)
|
)
|
||||||
self.histMax.valueChanged.connect(self._updateListBox)
|
self.histMax.valueChanged.connect(self._updateListBox)
|
||||||
|
|
||||||
@@ -323,23 +324,23 @@ class GuiWritingStats(QDialog):
|
|||||||
showIdleTime = self.showIdleTime.isChecked()
|
showIdleTime = self.showIdleTime.isChecked()
|
||||||
histMax = self.histMax.value()
|
histMax = self.histMax.value()
|
||||||
|
|
||||||
self.optState.setValue("GuiWritingStats", "winWidth", winWidth)
|
pOptions = self.theProject.options
|
||||||
self.optState.setValue("GuiWritingStats", "winHeight", winHeight)
|
pOptions.setValue("GuiWritingStats", "winWidth", winWidth)
|
||||||
self.optState.setValue("GuiWritingStats", "widthCol0", widthCol0)
|
pOptions.setValue("GuiWritingStats", "winHeight", winHeight)
|
||||||
self.optState.setValue("GuiWritingStats", "widthCol1", widthCol1)
|
pOptions.setValue("GuiWritingStats", "widthCol0", widthCol0)
|
||||||
self.optState.setValue("GuiWritingStats", "widthCol2", widthCol2)
|
pOptions.setValue("GuiWritingStats", "widthCol1", widthCol1)
|
||||||
self.optState.setValue("GuiWritingStats", "widthCol3", widthCol3)
|
pOptions.setValue("GuiWritingStats", "widthCol2", widthCol2)
|
||||||
self.optState.setValue("GuiWritingStats", "sortCol", sortCol)
|
pOptions.setValue("GuiWritingStats", "widthCol3", widthCol3)
|
||||||
self.optState.setValue("GuiWritingStats", "sortOrder", sortOrder)
|
pOptions.setValue("GuiWritingStats", "sortCol", sortCol)
|
||||||
self.optState.setValue("GuiWritingStats", "incNovel", incNovel)
|
pOptions.setValue("GuiWritingStats", "sortOrder", sortOrder)
|
||||||
self.optState.setValue("GuiWritingStats", "incNotes", incNotes)
|
pOptions.setValue("GuiWritingStats", "incNovel", incNovel)
|
||||||
self.optState.setValue("GuiWritingStats", "hideZeros", hideZeros)
|
pOptions.setValue("GuiWritingStats", "incNotes", incNotes)
|
||||||
self.optState.setValue("GuiWritingStats", "hideNegative", hideNegative)
|
pOptions.setValue("GuiWritingStats", "hideZeros", hideZeros)
|
||||||
self.optState.setValue("GuiWritingStats", "groupByDay", groupByDay)
|
pOptions.setValue("GuiWritingStats", "hideNegative", hideNegative)
|
||||||
self.optState.setValue("GuiWritingStats", "showIdleTime", showIdleTime)
|
pOptions.setValue("GuiWritingStats", "groupByDay", groupByDay)
|
||||||
self.optState.setValue("GuiWritingStats", "histMax", histMax)
|
pOptions.setValue("GuiWritingStats", "showIdleTime", showIdleTime)
|
||||||
|
pOptions.setValue("GuiWritingStats", "histMax", histMax)
|
||||||
self.optState.saveSettings()
|
pOptions.saveSettings()
|
||||||
self.close()
|
self.close()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
# Mars
|
# Mars
|
||||||
|
|
||||||
@tag: Mars
|
@tag: Mars
|
||||||
|
@location: Space
|
||||||
|
|
||||||
It’s red. Dusty and red.
|
It’s red. Dusty and red.
|
||||||
|
|||||||
@@ -1,19 +1,19 @@
|
|||||||
%%~name: Making a Scene
|
%%~name: Making a Scene
|
||||||
%%~path: e7ded148d6e4a/636b6aa9b697b
|
%%~path: 6a2d6d5f4f401/636b6aa9b697b
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: NOVEL/DOCUMENT
|
||||||
### Making a Scene
|
### Making a Scene
|
||||||
|
|
||||||
@pov: Jane
|
@pov: Jane
|
||||||
@char: John
|
@char: John, Jane
|
||||||
@location: Earth
|
@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.
|
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.
|
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 “* * *”.
|
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:
|
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. <<
|
>> This text is centred. <<
|
||||||
|
|
||||||
#### Text Indent
|
|
||||||
|
|
||||||
You can indent a paragraph from both the left and right margin with ‘>’ and ‘<’ symbols.
|
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. <
|
> 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
|
%%~name: Chapter One
|
||||||
%%~path: e7ded148d6e4a/6a2d6d5f4f401
|
%%~path: 7031beac91f75/6a2d6d5f4f401
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: NOVEL/DOCUMENT
|
||||||
## So it Begins
|
## So it Begins
|
||||||
|
|
||||||
@pov: Jane
|
@pov: Jane
|
||||||
@location: Earth
|
@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
|
%%~name: Chapter Two
|
||||||
%%~path: e7ded148d6e4a/88706ddc78b1b
|
%%~path: 7031beac91f75/88706ddc78b1b
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: NOVEL/DOCUMENT
|
||||||
## Where has John Gone?
|
## Where has John Gone?
|
||||||
|
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
### Jane Cannot Find John
|
### Jane Cannot Find John
|
||||||
|
|
||||||
@pov: Jane
|
@pov: Jane
|
||||||
|
@focus: John
|
||||||
@location: Space
|
@location: Space
|
||||||
|
|
||||||
Jane has been looking all over for John. He’s nowhere to be found on Earth, so Jane goes to 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
|
%%~name: Old File
|
||||||
%%~path: ae9bf3c3ea159/8a5deb88c0e97
|
%%~path: ae9bf3c3ea159/8a5deb88c0e97
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: ARCHIVE/DOCUMENT
|
||||||
### Discarded Scene
|
### 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
|
%%~name: A Note on Structure
|
||||||
%%~path: e7ded148d6e4a/96b68994dfa3d
|
%%~path: 7031beac91f75/96b68994dfa3d
|
||||||
%%~kind: NOVEL/NOTE
|
%%~kind: NOVEL/NOTE
|
||||||
# A Note on Structure
|
# A Note on Structure
|
||||||
|
|
||||||
This file is just a note. You can save notes anywhere you like in the project tree. Notes can be filtered out when you export the project.
|
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
|
## 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.
|
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
|
## 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.
|
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.
|
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!
|
%%~name: We Found John!
|
||||||
%%~path: e7ded148d6e4a/ae7339df26ded
|
%%~path: 88706ddc78b1b/ae7339df26ded
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: NOVEL/DOCUMENT
|
||||||
### We Found John!
|
### We Found John!
|
||||||
|
|
||||||
@pov: John
|
@pov: John
|
||||||
|
@focus: John
|
||||||
@location: Mars
|
@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.
|
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
|
# Earth
|
||||||
|
|
||||||
@tag: 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!
|
%%~name: Delete Me!
|
||||||
%%~path: 98acd8c76c93a/b8136a5a774a0
|
%%~path: 98acd8c76c93a/b8136a5a774a0
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: TRASH/DOCUMENT
|
||||||
### Delete Me!
|
### Delete Me!
|
||||||
|
|
||||||
This scene is trash.
|
This scene is trash.
|
||||||
@@ -1,9 +1,9 @@
|
|||||||
%%~name: Interlude
|
%%~name: Interlude
|
||||||
%%~path: e7ded148d6e4a/ba8a28a246524
|
%%~path: 7031beac91f75/ba8a28a246524
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: NOVEL/DOCUMENT
|
||||||
##! Interlude
|
##! 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 am the very model of a modern Major-General
|
||||||
I've information vegetable, animal, and mineral
|
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
|
%%~name: Another Scene
|
||||||
%%~path: e7ded148d6e4a/bc0cbd2a407f3
|
%%~path: 6a2d6d5f4f401/bc0cbd2a407f3
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: NOVEL/DOCUMENT
|
||||||
### Another Scene
|
### Another Scene
|
||||||
|
|
||||||
@@ -7,9 +7,9 @@
|
|||||||
@focus: Jane
|
@focus: Jane
|
||||||
@location: Earth
|
@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
|
### 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
|
@focus: John
|
||||||
@location: Earth
|
@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'?>
|
<?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>
|
<project>
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<title>Sample Project</title>
|
<title>Sample Project</title>
|
||||||
<author>Jane Smith</author>
|
<author>Jane Smith</author>
|
||||||
<author>Jay Doh</author>
|
<author>Jay Doh</author>
|
||||||
<saveCount>1303</saveCount>
|
<saveCount>1334</saveCount>
|
||||||
<autoCount>199</autoCount>
|
<autoCount>225</autoCount>
|
||||||
<editTime>65005</editTime>
|
<editTime>67746</editTime>
|
||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<doBackup>False</doBackup>
|
<doBackup>False</doBackup>
|
||||||
@@ -15,11 +15,11 @@
|
|||||||
<spellCheck>True</spellCheck>
|
<spellCheck>True</spellCheck>
|
||||||
<spellLang>None</spellLang>
|
<spellLang>None</spellLang>
|
||||||
<autoOutline>True</autoOutline>
|
<autoOutline>True</autoOutline>
|
||||||
<lastEdited>636b6aa9b697b</lastEdited>
|
<lastEdited>a520879ca0b45</lastEdited>
|
||||||
<lastViewed>636b6aa9b697b</lastViewed>
|
<lastViewed>636b6aa9b697b</lastViewed>
|
||||||
<lastWordCount>1206</lastWordCount>
|
<lastWordCount>1363</lastWordCount>
|
||||||
<novelWordCount>830</novelWordCount>
|
<novelWordCount>954</novelWordCount>
|
||||||
<notesWordCount>376</notesWordCount>
|
<notesWordCount>409</notesWordCount>
|
||||||
<autoReplace>
|
<autoReplace>
|
||||||
<entry key="A">B</entry>
|
<entry key="A">B</entry>
|
||||||
<entry key="B">E</entry>
|
<entry key="B">E</entry>
|
||||||
@@ -33,121 +33,129 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="sf12341" count="7" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Notes</entry>
|
<entry key="sf24ce6" count="1" red="200" green="50" blue="0">Notes</entry>
|
||||||
<entry red="182" green="60" blue="0">Started</entry>
|
<entry key="sc24b8f" count="2" red="182" green="60" blue="0">Started</entry>
|
||||||
<entry red="193" green="129" blue="0">1st Draft</entry>
|
<entry key="s90e6c9" count="6" red="193" green="129" blue="0">1st Draft</entry>
|
||||||
<entry red="193" green="129" blue="0">2nd Draft</entry>
|
<entry key="sd51c5b" count="1" red="193" green="129" blue="0">2nd Draft</entry>
|
||||||
<entry red="193" green="129" blue="0">3rd Draft</entry>
|
<entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry>
|
||||||
<entry red="58" green="180" blue="58">Finished</entry>
|
<entry key="s78ea90" count="0" red="58" green="180" blue="58">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry red="100" green="100" blue="100">None</entry>
|
<entry key="ia857f0" count="5" red="100" green="100" blue="100">None</entry>
|
||||||
<entry red="0" green="122" blue="188">Minor</entry>
|
<entry key="icfb3a5" count="2" red="0" green="122" blue="188">Minor</entry>
|
||||||
<entry red="21" green="0" blue="180">Major</entry>
|
<entry key="i2d7a54" count="2" red="21" green="0" blue="180">Major</entry>
|
||||||
<entry red="117" green="0" blue="175">Main</entry>
|
<entry key="i56be10" count="1" red="117" green="0" blue="175">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="25">
|
<content count="27">
|
||||||
<item handle="7031beac91f75" parent="None" order="0" type="ROOT" class="NOVEL">
|
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="Started" import="None">Novel</name>
|
<name status="sc24b8f" import="ia857f0">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="53b69b83cdafc" parent="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="93" wordCount="19" paraCount="2" cursorPos="2"/>
|
<meta expanded="False" charCount="93" wordCount="19" paraCount="2" cursorPos="119"/>
|
||||||
<name status="Started" import="None" exported="True">Title Page</name>
|
<name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="974e400180a99" parent="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="186" wordCount="39" paraCount="2" cursorPos="212"/>
|
<meta expanded="False" charCount="251" wordCount="50" paraCount="2" cursorPos="277"/>
|
||||||
<name status="New" import="None" exported="True">Page</name>
|
<name status="sf12341" import="ia857f0" exported="True">Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="edca4be2fcaf8" parent="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="26" wordCount="6" paraCount="1" cursorPos="33"/>
|
<meta expanded="False" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
|
||||||
<name status="New" import="None" exported="True">Part One</name>
|
<name status="sf12341" import="ia857f0" exported="True">Part One</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="1st Draft" import="None">A Folder</name>
|
<name status="sf12341" import="ia857f0">Sequel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="6a2d6d5f4f401" parent="e7ded148d6e4a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="bacb7059e3083" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="75" wordCount="14" paraCount="1" cursorPos="279"/>
|
<meta expanded="False" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
|
||||||
<name status="Notes" import="None" exported="True">Chapter One</name>
|
<name status="sf12341" import="ia857f0" exported="True">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="636b6aa9b697b" parent="e7ded148d6e4a" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="2429" wordCount="432" paraCount="14" cursorPos="219"/>
|
<meta expanded="False" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
|
||||||
<name status="1st Draft" import="None" exported="True">Making a Scene</name>
|
<name status="s90e6c9" import="ia857f0" exported="True">Chapter One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bc0cbd2a407f3" parent="e7ded148d6e4a" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
|
||||||
<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">
|
|
||||||
<meta expanded="True"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="None">Characters</name>
|
<name status="sf12341" import="ia857f0">Characters</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="None">Main Characters</name>
|
<name status="sf12341" import="ia857f0">Main Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="14298de4d9524" parent="f7e2d9f330615" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
|
<meta expanded="False" charCount="49" wordCount="9" paraCount="1" cursorPos="24"/>
|
||||||
<name status="New" import="Minor" exported="True">John Smith</name>
|
<name status="sf12341" import="icfb3a5" exported="True">John Smith</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
|
<meta expanded="False" charCount="55" wordCount="9" paraCount="1" cursorPos="25"/>
|
||||||
<name status="New" import="Major" exported="True">Jane Smith</name>
|
<name status="sf12341" import="i2d7a54" exported="True">Jane Smith</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="None">Locations</name>
|
<name status="sf12341" import="ia857f0">Locations</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="b3e74dbc1f584" parent="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
|
<meta expanded="False" charCount="76" wordCount="15" paraCount="1" cursorPos="20"/>
|
||||||
<name status="New" import="Main" exported="True">Earth</name>
|
<name status="sf12341" import="i56be10" exported="True">Earth</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f1471bef9f2ae" parent="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
|
<meta expanded="False" charCount="115" wordCount="24" paraCount="1" cursorPos="133"/>
|
||||||
<name status="New" import="Minor" exported="True">Space</name>
|
<name status="sf12341" import="icfb3a5" exported="True">Space</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
|
<meta expanded="False" charCount="28" wordCount="6" paraCount="1" cursorPos="45"/>
|
||||||
<name status="New" import="Major" exported="True">Mars</name>
|
<name status="sf12341" import="i2d7a54" exported="True">Mars</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="None">Archive</name>
|
<name status="sf12341" import="ia857f0">Archive</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="None">Scenes</name>
|
<name status="sf12341" import="ia857f0">Scenes</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="8a5deb88c0e97" parent="ae9bf3c3ea159" root="6827118336ac1" order="0" type="FILE" class="ARCHIVE" layout="DOCUMENT">
|
||||||
<meta charCount="315" wordCount="55" paraCount="1" cursorPos="322"/>
|
<meta expanded="False" charCount="232" wordCount="42" paraCount="1" cursorPos="239"/>
|
||||||
<name status="1st Draft" import="None" exported="True">Old File</name>
|
<name status="s90e6c9" import="ia857f0" exported="True">Old File</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="None">Trash</name>
|
<name status="sf12341" import="ia857f0">Trash</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="b8136a5a774a0" parent="98acd8c76c93a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="b8136a5a774a0" parent="98acd8c76c93a" root="98acd8c76c93a" order="0" type="FILE" class="TRASH" layout="DOCUMENT">
|
||||||
<meta charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
|
<meta expanded="False" charCount="30" wordCount="6" paraCount="1" cursorPos="36"/>
|
||||||
<name status="New" import="None" exported="True">Delete Me!</name>
|
<name status="sf12341" import="ia857f0" exported="True">Delete Me!</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -4,5 +4,6 @@
|
|||||||
<comment>novelWriter Project</comment>
|
<comment>novelWriter Project</comment>
|
||||||
<sub-class-of type="application/xml"/>
|
<sub-class-of type="application/xml"/>
|
||||||
<glob pattern="*.nwx"/>
|
<glob pattern="*.nwx"/>
|
||||||
|
<icon name="application-x-novelwriter-project"/>
|
||||||
</mime-type>
|
</mime-type>
|
||||||
</mime-info>
|
</mime-info>
|
||||||
|
|||||||
@@ -180,6 +180,26 @@ def nwGUI(qtbot, monkeypatch, fncDir, fncConf):
|
|||||||
return
|
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
|
# Temp Project Folders
|
||||||
##
|
##
|
||||||
@@ -248,10 +268,6 @@ def nwOldProj(tmpDir):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
##
|
|
||||||
# Useful Fixtures
|
|
||||||
##
|
|
||||||
|
|
||||||
@pytest.fixture(scope="session")
|
@pytest.fixture(scope="session")
|
||||||
def ipsumText():
|
def ipsumText():
|
||||||
"""Return five paragraphs of Lorem Ipsum text.
|
"""Return five paragraphs of Lorem Ipsum text.
|
||||||
|
|||||||
@@ -1,12 +1,12 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?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>
|
<project>
|
||||||
<name>Lorem Ipsum</name>
|
<name>Lorem Ipsum</name>
|
||||||
<title>Lorem Ipsum</title>
|
<title>Lorem Ipsum</title>
|
||||||
<author>lipsum.com</author>
|
<author>lipsum.com</author>
|
||||||
<saveCount>23</saveCount>
|
<saveCount>26</saveCount>
|
||||||
<autoCount>24</autoCount>
|
<autoCount>24</autoCount>
|
||||||
<editTime>1854</editTime>
|
<editTime>1863</editTime>
|
||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<doBackup>False</doBackup>
|
<doBackup>False</doBackup>
|
||||||
@@ -31,102 +31,102 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="sbaa94f" count="3" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Note</entry>
|
<entry key="s27bf7c" count="0" red="200" green="50" blue="0">Note</entry>
|
||||||
<entry red="200" green="150" blue="0">Draft</entry>
|
<entry key="s92a87b" count="5" red="200" green="150" blue="0">Draft</entry>
|
||||||
<entry red="50" green="200" blue="0">Finished</entry>
|
<entry key="sedd043" count="7" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="i613591" count="6" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Minor</entry>
|
<entry key="i560cbf" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||||
<entry red="200" green="150" blue="0">Major</entry>
|
<entry key="i37861c" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry red="50" green="200" blue="0">Main</entry>
|
<entry key="id6b1d0" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="21">
|
<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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="New">Novel</name>
|
<name status="sbaa94f" import="i613591">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="7a992350f3eb6" parent="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="7a992350f3eb6" parent="b3643d0f92e32" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
|
<meta expanded="False" charCount="230" wordCount="40" paraCount="3" cursorPos="148"/>
|
||||||
<name status="Finished" import="New" exported="True">Lorem Ipsum</name>
|
<name status="sedd043" import="i613591" exported="True">Lorem Ipsum</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="8c58a65414c23" parent="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="8c58a65414c23" parent="b3643d0f92e32" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
|
<meta expanded="False" charCount="1058" wordCount="176" paraCount="2" cursorPos="43"/>
|
||||||
<name status="Finished" import="New" exported="True">Front Matter</name>
|
<name status="sedd043" import="i613591" exported="True">Front Matter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="88d59a277361b" parent="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
|
<meta expanded="False" charCount="584" wordCount="92" paraCount="1" cursorPos="4"/>
|
||||||
<name status="Draft" import="New" exported="True">Prologue</name>
|
<name status="s92a87b" import="i613591" exported="True">Prologue</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="db7e733775d4d" parent="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
|
<meta expanded="False" charCount="35" wordCount="6" paraCount="1" cursorPos="42"/>
|
||||||
<name status="New" import="New" exported="True">Act One</name>
|
<name status="sbaa94f" import="i613591" exported="True">Act One</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="Draft" import="New">Chapter One</name>
|
<name status="s92a87b" import="i613591">Chapter One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
|
<meta expanded="False" charCount="419" wordCount="67" paraCount="1" cursorPos="56"/>
|
||||||
<name status="Draft" import="New" exported="True">Chapter One</name>
|
<name status="s92a87b" import="i613591" exported="True">Chapter One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
|
<meta expanded="False" charCount="2758" wordCount="404" paraCount="4" cursorPos="1528"/>
|
||||||
<name status="Finished" import="New" exported="True">Scene One</name>
|
<name status="sedd043" import="i613591" exported="True">Scene One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
|
<meta expanded="False" charCount="4043" wordCount="600" paraCount="6" cursorPos="2335"/>
|
||||||
<name status="Finished" import="New" exported="True">Scene Two</name>
|
<name status="sedd043" import="i613591" exported="True">Scene Two</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="846352075de7d" parent="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="846352075de7d" parent="b3643d0f92e32" root="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
|
<meta expanded="False" charCount="631" wordCount="109" paraCount="3" cursorPos="376"/>
|
||||||
<name status="New" import="New" exported="False">Interlude</name>
|
<name status="sbaa94f" import="i613591" exported="False">Interlude</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="Draft" import="New">Chapter Two</name>
|
<name status="s92a87b" import="i613591">Chapter Two</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="441420a886d82" parent="6bd935d2490cd" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="441420a886d82" parent="6bd935d2490cd" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
|
<meta expanded="False" charCount="477" wordCount="70" paraCount="1" cursorPos="56"/>
|
||||||
<name status="Draft" import="New" exported="True">Chapter Two</name>
|
<name status="s92a87b" import="i613591" exported="True">Chapter Two</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="eb103bc70c90c" parent="6bd935d2490cd" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="eb103bc70c90c" parent="6bd935d2490cd" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
|
<meta expanded="False" charCount="3006" wordCount="439" paraCount="4" cursorPos="57"/>
|
||||||
<name status="Finished" import="New" exported="True">Scene Three</name>
|
<name status="sedd043" import="i613591" exported="True">Scene Three</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
|
<meta expanded="False" charCount="3839" wordCount="563" paraCount="6" cursorPos="56"/>
|
||||||
<name status="Finished" import="New" exported="True">Scene Four</name>
|
<name status="sedd043" import="i613591" exported="True">Scene Four</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="47666c91c7ccf" parent="6bd935d2490cd" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="47666c91c7ccf" parent="6bd935d2490cd" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
|
<meta expanded="False" charCount="3644" wordCount="543" paraCount="5" cursorPos="351"/>
|
||||||
<name status="Finished" import="New" exported="True">Scene Five</name>
|
<name status="sedd043" import="i613591" exported="True">Scene Five</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="New">Characters</name>
|
<name status="sbaa94f" import="i613591">Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="4c4f28287af27" parent="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
|
<meta expanded="False" charCount="1864" wordCount="284" paraCount="3" cursorPos="1883"/>
|
||||||
<name status="New" import="New" exported="True">Mr. Nobody</name>
|
<name status="sbaa94f" import="i613591" exported="True">Mr. Nobody</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="New">Plot</name>
|
<name status="sbaa94f" import="i613591">Plot</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="2426c6f0ca922" parent="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE">
|
<item handle="2426c6f0ca922" parent="6c6afb1247750" root="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE">
|
||||||
<meta charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
|
<meta expanded="False" charCount="1369" wordCount="195" paraCount="2" cursorPos="1387"/>
|
||||||
<name status="New" import="New" exported="True">Main</name>
|
<name status="sbaa94f" import="i613591" exported="True">Main</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="New">World</name>
|
<name status="sbaa94f" import="i613591">World</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="04468803b92e1" parent="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
|
<meta expanded="False" charCount="1770" wordCount="259" paraCount="3" cursorPos="1792"/>
|
||||||
<name status="New" import="New" exported="True">Ancient Europe</name>
|
<name status="sbaa94f" import="i613591" exported="True">Ancient Europe</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?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>
|
<project>
|
||||||
<name>Test Minimal</name>
|
<name>Test Minimal</name>
|
||||||
<title>Minimal</title>
|
<title>Minimal</title>
|
||||||
<author>Jane Doe</author>
|
<author>Jane Doe</author>
|
||||||
<author>John Doh</author>
|
<author>John Doh</author>
|
||||||
<saveCount>14</saveCount>
|
<saveCount>17</saveCount>
|
||||||
<autoCount>2</autoCount>
|
<autoCount>2</autoCount>
|
||||||
<editTime>135</editTime>
|
<editTime>150</editTime>
|
||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<doBackup>True</doBackup>
|
<doBackup>True</doBackup>
|
||||||
@@ -29,50 +29,50 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="s72322d" count="5" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Note</entry>
|
<entry key="sb6adc7" count="0" red="200" green="50" blue="0">Note</entry>
|
||||||
<entry red="200" green="150" blue="0">Draft</entry>
|
<entry key="s2ae76d" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||||
<entry red="50" green="200" blue="0">Finished</entry>
|
<entry key="s541953" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="iffcacb" count="3" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Minor</entry>
|
<entry key="i6b130b" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||||
<entry red="200" green="150" blue="0">Major</entry>
|
<entry key="iece803" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry red="50" green="200" blue="0">Main</entry>
|
<entry key="i5ba06e" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="8">
|
<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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="New">Novel</name>
|
<name status="s72322d" import="iffcacb">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="a35baf2e93843" parent="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="a35baf2e93843" parent="a508bb932959c" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="28" wordCount="6" paraCount="1" cursorPos="33"/>
|
<meta expanded="False" charCount="28" wordCount="6" paraCount="1" cursorPos="33"/>
|
||||||
<name status="New" import="New" exported="True">Title Page</name>
|
<name status="s72322d" import="iffcacb" exported="True">Title Page</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="True"/>
|
||||||
<name status="New" import="New">New Chapter</name>
|
<name status="s72322d" import="iffcacb">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f5ab3e30151e1" parent="a6d311a93600a" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="f5ab3e30151e1" parent="a6d311a93600a" root="a508bb932959c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="11" wordCount="2" paraCount="0" cursorPos="16"/>
|
<meta expanded="False" charCount="11" wordCount="2" paraCount="0" cursorPos="16"/>
|
||||||
<name status="New" import="New" exported="True">New Chapter</name>
|
<name status="s72322d" import="iffcacb" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="8c659a11cd429" parent="a6d311a93600a" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="8c659a11cd429" parent="a6d311a93600a" root="a508bb932959c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="9" wordCount="2" paraCount="0" cursorPos="15"/>
|
<meta expanded="False" charCount="9" wordCount="2" paraCount="0" cursorPos="15"/>
|
||||||
<name status="New" import="New" exported="True">New Scene</name>
|
<name status="s72322d" import="iffcacb" exported="True">New Scene</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Plot</name>
|
<name status="s72322d" import="iffcacb">Plot</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Characters</name>
|
<name status="s72322d" import="iffcacb">Characters</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">World</name>
|
<name status="s72322d" import="iffcacb">World</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -29,7 +29,6 @@ class MockGuiMain():
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.mainConf = None
|
self.mainConf = None
|
||||||
self.hasProject = True
|
self.hasProject = True
|
||||||
self.theIndex = None
|
|
||||||
self.theProject = None
|
self.theProject = None
|
||||||
self.statusBar = MockStatusBar()
|
self.statusBar = MockStatusBar()
|
||||||
|
|
||||||
|
|||||||
@@ -1,99 +1,125 @@
|
|||||||
{
|
{
|
||||||
"tagIndex": {
|
"tagsIndex": {
|
||||||
"Bod": [3, "4c4f28287af27", "CHARACTER", "T000001"],
|
"Bod": {"handle": "4c4f28287af27", "heading": "T000001", "class": "CHARACTER"},
|
||||||
"Main": [3, "2426c6f0ca922", "PLOT", "T000001"],
|
"Main": {"handle": "2426c6f0ca922", "heading": "T000001", "class": "PLOT"},
|
||||||
"Europe": [3, "04468803b92e1", "WORLD", "T000001"]
|
"Europe": {"handle": "04468803b92e1", "heading": "T000001", "class": "WORLD"}
|
||||||
},
|
|
||||||
"refIndex": {
|
|
||||||
"fb609cd8319dc": {
|
|
||||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
|
||||||
},
|
},
|
||||||
"88243afbe5ed8": {
|
"itemIndex": {
|
||||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
"7a992350f3eb6": {
|
||||||
},
|
"level": "H1",
|
||||||
"f96ec11c6a3da": {
|
"headings": {
|
||||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
"T000001": {"level": "H1", "title": "Lorem Ipsum", "tag": "", "cCount": 230, "wCount": 40, "pCount": 3, "synopsis": ""}
|
||||||
},
|
}
|
||||||
"441420a886d82": {
|
},
|
||||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
"8c58a65414c23": {
|
||||||
},
|
"level": "H0",
|
||||||
"eb103bc70c90c": {
|
"headings": {
|
||||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
"T000000": {"level": "H0", "title": "", "tag": "", "cCount": 1058, "wCount": 176, "pCount": 2, "synopsis": ""}
|
||||||
},
|
}
|
||||||
"f8c0562e50f1b": {
|
},
|
||||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
"88d59a277361b": {
|
||||||
},
|
"level": "H2",
|
||||||
"47666c91c7ccf": {
|
"headings": {
|
||||||
"T000001": [[3, "@pov", "Bod"], [4, "@plot", "Main"], [5, "@location", "Europe"]]
|
"T000001": {"level": "H2", "title": "Prologue", "tag": "", "cCount": 584, "wCount": 92, "pCount": 1, "synopsis": "Explanation from the lipsum.com website."}
|
||||||
},
|
}
|
||||||
"4c4f28287af27": {
|
},
|
||||||
"T000001": [[4, "@plot", "Main"]]
|
"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'?>
|
<?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>
|
<project>
|
||||||
<name>Test Custom</name>
|
<name>Test Custom</name>
|
||||||
<title>Test Novel</title>
|
<title>Test Novel</title>
|
||||||
@@ -29,110 +29,106 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="s000008" count="15" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Note</entry>
|
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||||
<entry red="200" green="150" blue="0">Draft</entry>
|
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||||
<entry red="50" green="200" blue="0">Finished</entry>
|
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="i00000c" count="7" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Minor</entry>
|
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||||
<entry red="200" green="150" blue="0">Major</entry>
|
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry red="50" green="200" blue="0">Main</entry>
|
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="23">
|
<content count="22">
|
||||||
<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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Novel</name>
|
<name status="s000008" import="i00000c">Novel</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Plot</name>
|
<name status="s000008" import="i00000c">Plot</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Characters</name>
|
<name status="s000008" import="i00000c">Characters</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Locations</name>
|
<name status="s000008" import="i00000c">Locations</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Timeline</name>
|
<name status="s000008" import="i00000c">Archive</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Objects</name>
|
<name status="s000008" import="i00000c">Trash</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>
|
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?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>
|
<project>
|
||||||
<name>Test Custom</name>
|
<name>Test Custom</name>
|
||||||
<title>Test Novel</title>
|
<title>Test Novel</title>
|
||||||
@@ -29,74 +29,82 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="s000008" count="9" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Note</entry>
|
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||||
<entry red="200" green="150" blue="0">Draft</entry>
|
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||||
<entry red="50" green="200" blue="0">Finished</entry>
|
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="i00000c" count="7" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Minor</entry>
|
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||||
<entry red="200" green="150" blue="0">Major</entry>
|
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry red="50" green="200" blue="0">Main</entry>
|
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="14">
|
<content count="16">
|
||||||
<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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Novel</name>
|
<name status="s000008" import="i00000c">Novel</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Plot</name>
|
<name status="s000008" import="i00000c">Plot</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Characters</name>
|
<name status="s000008" import="i00000c">Characters</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Locations</name>
|
<name status="s000008" import="i00000c">Locations</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Timeline</name>
|
<name status="s000008" import="i00000c">Archive</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Objects</name>
|
<name status="s000008" import="i00000c">Trash</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>
|
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?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>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title>New Novel</title>
|
||||||
|
<author>Jane Doe</author>
|
||||||
<saveCount>2</saveCount>
|
<saveCount>2</saveCount>
|
||||||
<autoCount>1</autoCount>
|
<autoCount>1</autoCount>
|
||||||
<editTime>0</editTime>
|
<editTime>0</editTime>
|
||||||
@@ -27,58 +28,58 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="s000008" count="5" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Note</entry>
|
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||||
<entry red="200" green="150" blue="0">Draft</entry>
|
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||||
<entry red="50" green="200" blue="0">Finished</entry>
|
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="i00000c" count="3" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Minor</entry>
|
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||||
<entry red="200" green="150" blue="0">Major</entry>
|
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry red="50" green="200" blue="0">Main</entry>
|
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="10">
|
<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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Novel</name>
|
<name status="s000008" import="i00000c">Novel</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Plot</name>
|
<name status="s000008" import="i00000c">Plot</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Characters</name>
|
<name status="s000008" import="i00000c">Characters</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">World</name>
|
<name status="s000008" import="i00000c">World</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0000000000014" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="New" import="New" exported="True">Title Page</name>
|
<name status="s000008" import="i00000c" exported="True">Title Page</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">New Chapter</name>
|
<name status="s000008" import="i00000c">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0000000000016" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="New" import="New" exported="True">New Chapter</name>
|
<name status="s000008" import="i00000c" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0000000000017" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="New" import="New" exported="True">New Scene</name>
|
<name status="s000008" import="i00000c" exported="True">New Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="1a6562590ef19" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0000000000028" parent="31489056e0916" root="None" order="0" type="FILE" class="NO_CLASS" layout="NO_LAYOUT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="New" import="None" exported="True">Hello</name>
|
<name status="None" import="None" exported="True">Hello</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="031b4af5197ec" parent="71ee45a3c0db9" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="0000000000029" parent="71ee45a3c0db9" root="None" order="0" type="FILE" class="NO_CLASS" layout="NO_LAYOUT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="New" import="None" exported="True">Jane</name>
|
<name status="None" import="None" exported="True">Jane</name>
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?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>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title></title>
|
||||||
<saveCount>1</saveCount>
|
<saveCount>2</saveCount>
|
||||||
<autoCount>1</autoCount>
|
<autoCount>1</autoCount>
|
||||||
<editTime>0</editTime>
|
<editTime>0</editTime>
|
||||||
</project>
|
</project>
|
||||||
@@ -27,50 +27,50 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="s000008" count="5" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Note</entry>
|
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||||
<entry red="200" green="150" blue="0">Draft</entry>
|
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||||
<entry red="50" green="200" blue="0">Finished</entry>
|
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="i00000c" count="3" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Minor</entry>
|
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||||
<entry red="200" green="150" blue="0">Major</entry>
|
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry red="50" green="200" blue="0">Main</entry>
|
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="8">
|
<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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Novel</name>
|
<name status="s000008" import="i00000c">Novel</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Plot</name>
|
<name status="s000008" import="i00000c">Plot</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Characters</name>
|
<name status="s000008" import="i00000c">Characters</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">World</name>
|
<name status="s000008" import="i00000c">Locations</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0000000000017" parent="None" root="0000000000017" order="0" type="ROOT" class="ARCHIVE">
|
||||||
<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">
|
|
||||||
<meta expanded="False"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">New Chapter</name>
|
<name status="s000008" import="i00000c">Archive</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>
|
|
||||||
</item>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,8 +1,9 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?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>
|
<project>
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<title></title>
|
<title>New Novel</title>
|
||||||
|
<author>Jane Doe</author>
|
||||||
<saveCount>2</saveCount>
|
<saveCount>2</saveCount>
|
||||||
<autoCount>1</autoCount>
|
<autoCount>1</autoCount>
|
||||||
<editTime>0</editTime>
|
<editTime>0</editTime>
|
||||||
@@ -27,66 +28,82 @@
|
|||||||
<section></section>
|
<section></section>
|
||||||
</titleFormat>
|
</titleFormat>
|
||||||
<status>
|
<status>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="s000008" count="6" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Note</entry>
|
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
|
||||||
<entry red="200" green="150" blue="0">Draft</entry>
|
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
|
||||||
<entry red="50" green="200" blue="0">Finished</entry>
|
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry red="100" green="100" blue="100">New</entry>
|
<entry key="i00000c" count="10" red="100" green="100" blue="100">New</entry>
|
||||||
<entry red="200" green="50" blue="0">Minor</entry>
|
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
|
||||||
<entry red="200" green="150" blue="0">Major</entry>
|
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
|
||||||
<entry red="50" green="200" blue="0">Main</entry>
|
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content count="12">
|
<content count="16">
|
||||||
<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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Novel</name>
|
<name status="s000008" import="i00000c">Novel</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Plot</name>
|
<name status="s000008" import="i00000c">Plot</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">Characters</name>
|
<name status="s000008" import="i00000c">Characters</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">World</name>
|
<name status="s000008" import="i00000c">World</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="25fc0e7096fc6" parent="73475cb40a568" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0000000000014" parent="0000000000010" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="New" import="New" exported="True">Title Page</name>
|
<name status="s000008" import="i00000c" exported="True">Title Page</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="New">New Chapter</name>
|
<name status="s000008" import="i00000c">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98010bd9270f9" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0000000000016" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="New" import="New" exported="True">New Chapter</name>
|
<name status="s000008" import="i00000c" exported="True">New Chapter</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="0e17daca5f3e1" parent="31489056e0916" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="0000000000017" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
|
||||||
<name status="New" import="New" exported="True">New Scene</name>
|
<name status="s000008" import="i00000c" exported="True">New Scene</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Timeline</name>
|
<name status="s000008" import="i00000c">Novel</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Object</name>
|
<name status="s000008" import="i00000c">Plot</name>
|
||||||
</item>
|
</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"/>
|
<meta expanded="False"/>
|
||||||
<name status="New" import="None">Custom1</name>
|
<name status="s000008" import="i00000c">Characters</name>
|
||||||
</item>
|
</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"/>
|
<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>
|
</item>
|
||||||
</content>
|
</content>
|
||||||
</novelWriterXML>
|
</novelWriterXML>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
%%~name: New Scene
|
%%~name: New Scene
|
||||||
%%~path: 31489056e0916/0e17daca5f3e1
|
%%~path: 000000000000d/000000000000f
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: NOVEL/DOCUMENT
|
||||||
# Novel
|
# Novel
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
%%~name: New File
|
%%~name: New Note
|
||||||
%%~path: 71ee45a3c0db9/1a6562590ef19
|
%%~path: 000000000000a/0000000000020
|
||||||
%%~kind: CHARACTER/NOTE
|
%%~kind: CHARACTER/NOTE
|
||||||
# Jane Doe
|
# Jane Doe
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
%%~name: New File
|
%%~name: New Note
|
||||||
%%~path: 44cb730c42048/031b4af5197ec
|
%%~path: 0000000000009/0000000000021
|
||||||
%%~kind: PLOT/NOTE
|
%%~kind: PLOT/NOTE
|
||||||
# Main Plot
|
# Main Plot
|
||||||
|
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
%%~name: New File
|
%%~name: New Note
|
||||||
%%~path: 811786ad1ae74/41cfc0d1f2d12
|
%%~path: 000000000000b/0000000000022
|
||||||
%%~kind: WORLD/NOTE
|
%%~kind: WORLD/NOTE
|
||||||
# Main Location
|
# Main Location
|
||||||
|
|
||||||