Merge branch 'main' into merge_1.6.5
@@ -29,5 +29,5 @@ jobs:
|
||||
flake8 tests --count --select=E9,F63,F7,F82 --show-source --statistics
|
||||
- name: Coding Style Violations
|
||||
run: |
|
||||
flake8 novelwriter --count --max-line-length=99 --ignore E221,E226,E228,E241 --show-source --statistics
|
||||
flake8 tests --count --max-line-length=99 --ignore E221,E226,E228,E241 --show-source --statistics
|
||||
flake8 novelwriter --count --max-line-length=99 --ignore E133,E221,E226,E228,E241,W503 --show-source --statistics
|
||||
flake8 tests --count --max-line-length=99 --ignore E133,E221,E226,E228,E241,W503 --show-source --statistics
|
||||
|
||||
@@ -14,7 +14,7 @@ jobs:
|
||||
testLinux:
|
||||
strategy:
|
||||
matrix:
|
||||
python-version: ["3.6", "3.7", "3.8", "3.9", "3.10"]
|
||||
python-version: ["3.7", "3.8", "3.9", "3.10"]
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Python Setup
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: "3.10"
|
||||
architecture: x64
|
||||
- name: Install Packages
|
||||
run: |
|
||||
|
||||
@@ -17,7 +17,7 @@ jobs:
|
||||
- name: Python Setup
|
||||
uses: actions/setup-python@v2
|
||||
with:
|
||||
python-version: 3.9
|
||||
python-version: "3.10"
|
||||
architecture: x64
|
||||
- name: Checkout Source
|
||||
uses: actions/checkout@v2
|
||||
|
||||
@@ -1,5 +1,96 @@
|
||||
# novelWriter Changelog
|
||||
|
||||
## Version 1.7 Beta 1 [2022-05-17]
|
||||
|
||||
**Note:** The 1.7 release was renamed to 2.0 on 2022-10-06. See #1144.
|
||||
|
||||
### 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.5 [2022-10-13]
|
||||
|
||||
### Release Notes
|
||||
|
||||
@@ -2,75 +2,131 @@
|
||||
|
||||
## Our Pledge
|
||||
|
||||
In the interest of fostering an open and welcoming environment, we as
|
||||
contributors and maintainers pledge to making participation in our project and
|
||||
our community a harassment-free experience for everyone, regardless of age, body
|
||||
size, disability, ethnicity, sex characteristics, gender identity and expression,
|
||||
level of experience, education, socio-economic status, nationality, personal
|
||||
appearance, race, religion, or sexual identity and orientation.
|
||||
We as members, contributors, and leaders pledge to make participation in our
|
||||
community a harassment-free experience for everyone, regardless of age, body
|
||||
size, visible or invisible disability, ethnicity, sex characteristics, gender
|
||||
identity and expression, level of experience, education, socio-economic status,
|
||||
nationality, personal appearance, race, caste, color, religion, or sexual
|
||||
identity and orientation.
|
||||
|
||||
We pledge to act and interact in ways that contribute to an open, welcoming,
|
||||
diverse, inclusive, and healthy community.
|
||||
|
||||
## Our Standards
|
||||
|
||||
Examples of behavior that contributes to creating a positive environment
|
||||
include:
|
||||
Examples of behavior that contributes to a positive environment for our
|
||||
community include:
|
||||
|
||||
* Using welcoming and inclusive language
|
||||
* Being respectful of differing viewpoints and experiences
|
||||
* Gracefully accepting constructive criticism
|
||||
* Focusing on what is best for the community
|
||||
* Showing empathy towards other community members
|
||||
* Demonstrating empathy and kindness toward other people
|
||||
* Being respectful of differing opinions, viewpoints, and experiences
|
||||
* Giving and gracefully accepting constructive feedback
|
||||
* Accepting responsibility and apologizing to those affected by our mistakes,
|
||||
and learning from the experience
|
||||
* Focusing on what is best not just for us as individuals, but for the overall
|
||||
community
|
||||
|
||||
Examples of unacceptable behavior by participants include:
|
||||
Examples of unacceptable behavior include:
|
||||
|
||||
* The use of sexualized language or imagery and unwelcome sexual attention or
|
||||
advances
|
||||
* Trolling, insulting/derogatory comments, and personal or political attacks
|
||||
* The use of sexualized language or imagery, and sexual attention or advances of
|
||||
any kind
|
||||
* Trolling, insulting or derogatory comments, and personal or political attacks
|
||||
* Public or private harassment
|
||||
* Publishing others' private information, such as a physical or electronic
|
||||
address, without explicit permission
|
||||
* Publishing others' private information, such as a physical or email address,
|
||||
without their explicit permission
|
||||
* Other conduct which could reasonably be considered inappropriate in a
|
||||
professional setting
|
||||
|
||||
## Our Responsibilities
|
||||
## Enforcement Responsibilities
|
||||
|
||||
Project maintainers are responsible for clarifying the standards of acceptable
|
||||
behavior and are expected to take appropriate and fair corrective action in
|
||||
response to any instances of unacceptable behavior.
|
||||
Community leaders are responsible for clarifying and enforcing our standards of
|
||||
acceptable behavior and will take appropriate and fair corrective action in
|
||||
response to any behavior that they deem inappropriate, threatening, offensive,
|
||||
or harmful.
|
||||
|
||||
Project maintainers have the right and responsibility to remove, edit, or
|
||||
reject comments, commits, code, wiki edits, issues, and other contributions
|
||||
that are not aligned to this Code of Conduct, or to ban temporarily or
|
||||
permanently any contributor for other behaviors that they deem inappropriate,
|
||||
threatening, offensive, or harmful.
|
||||
Community leaders have the right and responsibility to remove, edit, or reject
|
||||
comments, commits, code, wiki edits, issues, and other contributions that are
|
||||
not aligned to this Code of Conduct, and will communicate reasons for moderation
|
||||
decisions when appropriate.
|
||||
|
||||
## Scope
|
||||
|
||||
This Code of Conduct applies both within project spaces and in public spaces
|
||||
when an individual is representing the project or its community. Examples of
|
||||
representing a project or community include using an official project e-mail
|
||||
address, posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event. Representation of a project may be
|
||||
further defined and clarified by project maintainers.
|
||||
This Code of Conduct applies within all community spaces, and also applies when
|
||||
an individual is officially representing the community in public spaces.
|
||||
Examples of representing our community include using an official e-mail address,
|
||||
posting via an official social media account, or acting as an appointed
|
||||
representative at an online or offline event.
|
||||
|
||||
## Enforcement
|
||||
|
||||
Instances of abusive, harassing, or otherwise unacceptable behavior may be
|
||||
reported by contacting the project leader at issues (at) novelwriter.io. All
|
||||
complaints will be reviewed and investigated and will result in a response that
|
||||
is deemed necessary and appropriate to the circumstances. The project team is
|
||||
obligated to maintain confidentiality with regard to the reporter of an incident.
|
||||
Further details of specific enforcement policies may be posted separately.
|
||||
reported to the community leaders responsible for enforcement at issues (at)
|
||||
novelwriter.io. All complaints will be reviewed and investigated promptly and
|
||||
fairly.
|
||||
|
||||
Project maintainers who do not follow or enforce the Code of Conduct in good
|
||||
faith may face temporary or permanent repercussions as determined by other
|
||||
members of the project's leadership.
|
||||
All community leaders are obligated to respect the privacy and security of the
|
||||
reporter of any incident.
|
||||
|
||||
## Enforcement Guidelines
|
||||
|
||||
Community leaders will follow these Community Impact Guidelines in determining
|
||||
the consequences for any action they deem in violation of this Code of Conduct:
|
||||
|
||||
### 1. Correction
|
||||
|
||||
**Community Impact**: Use of inappropriate language or other behavior deemed
|
||||
unprofessional or unwelcome in the community.
|
||||
|
||||
**Consequence**: A private, written warning from community leaders, providing
|
||||
clarity around the nature of the violation and an explanation of why the
|
||||
behavior was inappropriate. A public apology may be requested.
|
||||
|
||||
### 2. Warning
|
||||
|
||||
**Community Impact**: A violation through a single incident or series of
|
||||
actions.
|
||||
|
||||
**Consequence**: A warning with consequences for continued behavior. No
|
||||
interaction with the people involved, including unsolicited interaction with
|
||||
those enforcing the Code of Conduct, for a specified period of time. This
|
||||
includes avoiding interactions in community spaces as well as external channels
|
||||
like social media. Violating these terms may lead to a temporary or permanent
|
||||
ban.
|
||||
|
||||
### 3. Temporary Ban
|
||||
|
||||
**Community Impact**: A serious violation of community standards, including
|
||||
sustained inappropriate behavior.
|
||||
|
||||
**Consequence**: A temporary ban from any sort of interaction or public
|
||||
communication with the community for a specified period of time. No public or
|
||||
private interaction with the people involved, including unsolicited interaction
|
||||
with those enforcing the Code of Conduct, is allowed during this period.
|
||||
Violating these terms may lead to a permanent ban.
|
||||
|
||||
### 4. Permanent Ban
|
||||
|
||||
**Community Impact**: Demonstrating a pattern of violation of community
|
||||
standards, including sustained inappropriate behavior, harassment of an
|
||||
individual, or aggression toward or disparagement of classes of individuals.
|
||||
|
||||
**Consequence**: A permanent ban from any sort of public interaction within the
|
||||
community.
|
||||
|
||||
## Attribution
|
||||
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4,
|
||||
available at https://www.contributor-covenant.org/version/1/4/code-of-conduct.html
|
||||
This Code of Conduct is adapted from the [Contributor Covenant][homepage],
|
||||
version 2.1, available at
|
||||
[https://www.contributor-covenant.org/version/2/1/code_of_conduct.html][v2.1].
|
||||
|
||||
Community Impact Guidelines were inspired by
|
||||
[Mozilla's code of conduct enforcement ladder][Mozilla CoC].
|
||||
|
||||
For answers to common questions about this code of conduct, see the FAQ at
|
||||
[https://www.contributor-covenant.org/faq][FAQ]. Translations are available at
|
||||
[https://www.contributor-covenant.org/translations][translations].
|
||||
|
||||
[homepage]: https://www.contributor-covenant.org
|
||||
|
||||
For answers to common questions about this code of conduct, see
|
||||
https://www.contributor-covenant.org/faq
|
||||
[v2.1]: https://www.contributor-covenant.org/version/2/1/code_of_conduct.html
|
||||
[Mozilla CoC]: https://github.com/mozilla/diversity
|
||||
[FAQ]: https://www.contributor-covenant.org/faq
|
||||
[translations]: https://www.contributor-covenant.org/translations
|
||||
|
||||
@@ -11,11 +11,14 @@
|
||||
|
||||
## Translations
|
||||
|
||||
The default language is English (UK) with English (US) as an option.
|
||||
|
||||
* Dutch: Martijn van der Kleijn (@mvdkleijn)
|
||||
* French: Jan Lüdke (@jyhelle)
|
||||
* Latin American Spanish: Tommy Marplatt (@tmarplatt)
|
||||
* Norwegian: Veronica Berglyd Olsen (@vkbo)
|
||||
* Portuguese: Bruno Meneguello (@bkmeneguello)
|
||||
* Simplified Chinese: Qianzhi Long (@longqzh)
|
||||
* Latin American Spanish: Tommy Marplatt (@tmarplatt)
|
||||
|
||||
## Libraries
|
||||
|
||||
@@ -31,7 +34,7 @@ The following libraries are dependencies of novelWriter:
|
||||
|
||||
Some of the assets bundled with novelWriter were adapted from the following sources:
|
||||
|
||||
* Typicons icons by Stephen Hutchings (CC BY-SA 4.0)
|
||||
* Typicons icons by [Stephen Hutchings](https://github.com/stephenhutchings/typicons.font) (CC BY-SA 4.0)
|
||||
* Tomorrow syntax themes by Chris Kempson (MIT License)
|
||||
* Owl syntax themes by Sarah Drasner (MIT License)
|
||||
* Solarized themes by Ethan Schoonover, added by @nullbasis (MIT License)
|
||||
|
||||
@@ -29,11 +29,13 @@ The full documentation is available at
|
||||
The full credits are listed in
|
||||
[CREDITS.md](https://github.com/vkbo/novelWriter/blob/main/CREDITS.md).
|
||||
|
||||
You can also follow novelWriter on Mastodon at [fosstodon.org/@novelwriter](https://fosstodon.org/@novelwriter).
|
||||
|
||||
## Implementation
|
||||
|
||||
The application is written in Python 3 (3.6+) using Qt5 and PyQt5 (5.3+). It is developed on Linux,
|
||||
but should in principle work fine on other operating systems as well as long as dependencies are
|
||||
met. It is regularly tested on Debian and Ubuntu Linux, Windows, and macOS.
|
||||
The application is written with Python 3 (3.7+) using Qt5 and PyQt5 (5.3+). It is developed on
|
||||
Linux, but should in principle work fine on other operating systems as well as long as dependencies
|
||||
are met. It is regularly tested on Debian and Ubuntu Linux, Windows, and macOS.
|
||||
|
||||
## Installation
|
||||
|
||||
|
||||
@@ -39,11 +39,13 @@ too. novelWriter can be run directly from the Python source, installed from the
|
||||
|
||||
* Website: https://novelwriter.io
|
||||
* Documentation: https://novelwriter.readthedocs.io
|
||||
* Internationalisation: https://crowdin.com/project/novelwriter
|
||||
* Source Code: https://github.com/vkbo/novelWriter
|
||||
* Source Releases: https://github.com/vkbo/novelWriter/releases
|
||||
* Issue Tracker: https://github.com/vkbo/novelWriter/issues
|
||||
* Feature Discussions: https://github.com/vkbo/novelWriter/discussions
|
||||
* PyPi Project: https://pypi.org/project/novelWriter
|
||||
* Social Media: https://fosstodon.org/@novelwriter
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
@@ -115,7 +115,7 @@ Windows
|
||||
-------
|
||||
|
||||
First, make sure you have Python installed on your system. If you don't, you can download it from
|
||||
`python.org`_. Python 3.6 or higher is required, but it is recommended that you install the latest
|
||||
`python.org`_. Python 3.7 or higher is required, but it is recommended that you install the latest
|
||||
version.
|
||||
|
||||
Make sure you select the "Add Python to PATH" option during installation, otherwise the ``python``
|
||||
|
||||
@@ -11,13 +11,32 @@ changes require minor actions from the user.
|
||||
The key changes in the formats are listed below, as well as the user actions required where
|
||||
applicable.
|
||||
|
||||
.. caution::
|
||||
|
||||
When you update a project from one format version to the next, the project can no longer be
|
||||
opened by a version of novelWriter prior to the version where the new file format was
|
||||
introduced. You will get a notification about any updates to your project file format and will
|
||||
have the option to decline the upgrade.
|
||||
|
||||
|
||||
.. _a_prjfmt_1_4:
|
||||
|
||||
Format 1.4 Changes
|
||||
==================
|
||||
|
||||
This project format was introduced in novelWriter version 1.7.
|
||||
|
||||
This format changes the way project items (folders, documents and notes) are stored. It is a more
|
||||
compact format that is simpler and faster to parse, and easier to extend. The conversion is done
|
||||
automatically the first time a project is loaded. No user action is required.
|
||||
|
||||
|
||||
.. _a_prjfmt_1_3:
|
||||
|
||||
Format 1.3 Changes
|
||||
==================
|
||||
|
||||
This project format vas introduces in novelWriter version 1.5.
|
||||
This project format was introduced in novelWriter version 1.5.
|
||||
|
||||
With this format, the number of document layouts was reduced from 8 to 2. The conversion of
|
||||
document layouts is performed automatically when the project is opened.
|
||||
@@ -55,7 +74,7 @@ should be used only a few places in any given project. These are as follows:
|
||||
Format 1.2 Changes
|
||||
==================
|
||||
|
||||
This project format was introduces in novelWriter version 0.10.
|
||||
This project format was introduced in novelWriter version 0.10.
|
||||
|
||||
With this format, the way auto-replace entries were stored in the main project XML file changed.
|
||||
Opening an old project automatically converts the storage format up to and including version 1.1.1.
|
||||
@@ -69,7 +88,7 @@ auto-replace is not being used, can still be opened in novelWriter as of version
|
||||
Format 1.1 Changes
|
||||
==================
|
||||
|
||||
This project format was introduces in novelWriter version 0.7.
|
||||
This project format was introduced in novelWriter version 0.7.
|
||||
|
||||
With this format, the ``content`` folder was introduced in the project storage. Previously, all
|
||||
novelWriter documents were saved in a series of folders numbered from ``data_0`` to ``data_f``.
|
||||
|
||||
@@ -25,7 +25,7 @@ The main shorcuts are as follows:
|
||||
":kbd:`Alt`:kbd:`4`", "Switch focus to outline view. On Windows, use :kbd:`Ctrl`:kbd:`Alt`:kbd:`4`."
|
||||
":kbd:`Alt`:kbd:`Left`", "Move backward in the view history of the document viewer."
|
||||
":kbd:`Alt`:kbd:`Right`", "Move forward in the view history of the document viewer."
|
||||
":kbd:`Ctrl`:kbd:`.`", "Open menu to correct word under cursor."
|
||||
":kbd:`Ctrl`:kbd:`.`", "Open the context menu in the project tree or the document editor."
|
||||
":kbd:`Ctrl`:kbd:`,`", "Open the :guilabel:`Preferences` dialog."
|
||||
":kbd:`Ctrl`:kbd:`/`", "Toggle block format as comment."
|
||||
":kbd:`Ctrl`:kbd:`0`", "Remove block formatting for block under cursor."
|
||||
@@ -42,13 +42,12 @@ The main shorcuts are as follows:
|
||||
":kbd:`Ctrl`:kbd:`B`", "Format selected text, or word under cursor, with strong emphasis (bold)."
|
||||
":kbd:`Ctrl`:kbd:`C`", "Copy selected text to clipboard."
|
||||
":kbd:`Ctrl`:kbd:`D`", "Strikethrough selected text, or word under cursor."
|
||||
":kbd:`Ctrl`:kbd:`E`", "If in the project tree, edit a document or folder settings."
|
||||
":kbd:`Ctrl`:kbd:`F`", "Open the search bar and search for the selected word, if any is selected."
|
||||
":kbd:`Ctrl`:kbd:`G`", "Find next occurrence of search word in current document."
|
||||
":kbd:`Ctrl`:kbd:`H`", "Open the search and replace bar and search for the selected word, if any is selected. (On Mac, this is :kbd:`Cmd`:kbd:`=`.)"
|
||||
":kbd:`Ctrl`:kbd:`I`", "Format selected text, or word under cursor, with emphasis (italic)."
|
||||
":kbd:`Ctrl`:kbd:`K`", "Activate the insert commands. The commands are listed in :ref:`a_kb_ins`."
|
||||
":kbd:`Ctrl`:kbd:`N`", "Create new document."
|
||||
":kbd:`Ctrl`:kbd:`N`", "Create new project item."
|
||||
":kbd:`Ctrl`:kbd:`O`", "Open selected document."
|
||||
":kbd:`Ctrl`:kbd:`Q`", "Exit novelWriter."
|
||||
":kbd:`Ctrl`:kbd:`R`", "If in the project tree, open a document for viewing. If the editor has focus, open current document for viewing."
|
||||
@@ -59,7 +58,6 @@ The main shorcuts are as follows:
|
||||
":kbd:`Ctrl`:kbd:`Y`", "Redo latest undo."
|
||||
":kbd:`Ctrl`:kbd:`Z`", "Undo latest changes."
|
||||
":kbd:`Ctrl`:kbd:`F7`", "Toggle spell checking."
|
||||
":kbd:`Ctrl`:kbd:`F10`", "Toggle automatic updating of project outline."
|
||||
":kbd:`Ctrl`:kbd:`Up`", "Move item one step up in the project tree."
|
||||
":kbd:`Ctrl`:kbd:`Down`", "Move item one step down in the project tree."
|
||||
":kbd:`Ctrl`:kbd:`Del`", "Delete next word in editor."
|
||||
@@ -73,7 +71,6 @@ The main shorcuts are as follows:
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`A`", "Select all text in current paragraph."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`G`", "Find previous occurrence of search word in current document."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`I`", "Import text to the current document from a text file."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`N`", "Create new folder."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`O`", "Open a project."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`R`", "Close the document viewer."
|
||||
":kbd:`Ctrl`:kbd:`Shift`:kbd:`S`", "Save the current project."
|
||||
@@ -88,7 +85,6 @@ The main shorcuts are as follows:
|
||||
":kbd:`F7`", "Re-run spell checker."
|
||||
":kbd:`F8`", "Activate :guilabel:`Focus Mode`, hiding the project tree and document viewer."
|
||||
":kbd:`F9`", "Re-build the project index."
|
||||
":kbd:`F10`", "Re-build the project outline."
|
||||
":kbd:`F11`", "Activate full screen mode."
|
||||
":kbd:`Shift`:kbd:`F1`", "Open the local user manual (PDF) if it is available."
|
||||
":kbd:`Shift`:kbd:`F3`", "Find previous occurrence of search word in current document."
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
|
||||
The maintenance of translations has been moved to the Crowdin service. The translation strings can
|
||||
be edited there at the [novelWriter project page](https://crowdin.com/project/novelwriter).
|
||||
However, please read the Translation Guidelines section below.
|
||||
|
||||
You can still use the manual approach listed below, and then upload the file through the website's
|
||||
interface. The translation strings for that language will then be updated and queued for approval.
|
||||
@@ -11,6 +12,22 @@ To verify a language file translated through the Crowdin tool, download and extr
|
||||
[Generate an Updated Translation File](#generate-an-updated-translation-file) below.
|
||||
|
||||
|
||||
# Translation Guidelines
|
||||
|
||||
When contributing translations, keep the following things in mind.
|
||||
|
||||
* For descriptive labels and dialog boxes, make sure you do _not_ change the meaning of the text
|
||||
when you translate it from English. The user must receive the same instructions or information
|
||||
regardless of language. This is improtant, otherwise the documentation will be inconsistent with
|
||||
the user interface and it will become a lot more difficult to handle user issues and questions.
|
||||
* If you think a label or description is misleading or incomplete, please file an issue report. The
|
||||
correct way to handle such changes is to change the text in the code first, which will then be
|
||||
forwarded to _all_ translators such that the GUI is consistent across all languages.
|
||||
* For very short labels, like button labels. it may be fine to replace the word with a similar
|
||||
word, but only as long as the user understands what it is supposed to do. Some buttons and tabs
|
||||
have limited space. If necessary, it is OK to use abbreviations.
|
||||
|
||||
|
||||
# Direct Approach Using Qt Linguist
|
||||
|
||||
Here you will find instructions for translating novelWriter to a new language directly using Qt
|
||||
|
||||
@@ -60,9 +60,9 @@ __license__ = "GPLv3"
|
||||
__author__ = "Veronica Berglyd Olsen"
|
||||
__maintainer__ = "Veronica Berglyd Olsen"
|
||||
__email__ = "code@vkbo.net"
|
||||
__version__ = "1.6.5"
|
||||
__hexversion__ = "0x010605f0"
|
||||
__date__ = "2022-10-13"
|
||||
__version__ = "2.0-beta1"
|
||||
__hexversion__ = "0x020000b1"
|
||||
__date__ = "2022-10-06"
|
||||
__status__ = "Stable"
|
||||
__domain__ = "novelwriter.io"
|
||||
__url__ = "https://novelwriter.io"
|
||||
@@ -209,9 +209,9 @@ def main(sysArgs=None):
|
||||
# Check Packages and Versions
|
||||
errorData = []
|
||||
errorCode = 0
|
||||
if sys.hexversion < 0x030600f0:
|
||||
if sys.hexversion < 0x030700f0:
|
||||
errorData.append(
|
||||
"At least Python 3.6 is required, found %s" % CONFIG.verPyString
|
||||
"At least Python 3.7 is required, found %s" % CONFIG.verPyString
|
||||
)
|
||||
errorCode |= 0x04
|
||||
if CONFIG.verQtValue < 50300:
|
||||
|
||||
@@ -2,104 +2,104 @@
|
||||
"Synopsis": "Synopsis",
|
||||
"Comment": "Opmerking",
|
||||
"Notes": "Notities",
|
||||
"0": "Nul",
|
||||
"1": "Één",
|
||||
"2": "Twee",
|
||||
"3": "Drie",
|
||||
"4": "Vier",
|
||||
"5": "Vijf",
|
||||
"6": "Zes",
|
||||
"7": "Zeven",
|
||||
"8": "Acht",
|
||||
"9": "Negen",
|
||||
"10": "Tien",
|
||||
"11": "Elf",
|
||||
"12": "Twaalf",
|
||||
"13": "Dertien",
|
||||
"14": "Veertien",
|
||||
"15": "Vijftien",
|
||||
"16": "Zestien",
|
||||
"17": "Zeventien",
|
||||
"18": "Achttien",
|
||||
"19": "Negentien",
|
||||
"20": "Twintig",
|
||||
"21": "Eenentwintig",
|
||||
"22": "Tweeentwintig",
|
||||
"23": "Drieentwintig",
|
||||
"24": "Vierentwintig",
|
||||
"25": "Vijfentwintig",
|
||||
"26": "Zesentwintig",
|
||||
"27": "Zevenentwintig",
|
||||
"28": "Achtentwintig",
|
||||
"29": "Negenentwintig",
|
||||
"30": "Dertig",
|
||||
"31": "Eenendertig",
|
||||
"32": "Tweeendertig",
|
||||
"33": "Drieendertig",
|
||||
"34": "Vierendertig",
|
||||
"35": "Vijfendertig",
|
||||
"36": "Zesendertig",
|
||||
"37": "Zevenendertig",
|
||||
"38": "Achtendertig",
|
||||
"39": "Negenendertig",
|
||||
"40": "Veertig",
|
||||
"41": "Eenenveertig",
|
||||
"42": "Tweeënveertig",
|
||||
"43": "Drieenveertig",
|
||||
"44": "Vierenveertig",
|
||||
"45": "Vijfenveertig",
|
||||
"46": "Zesenveertig",
|
||||
"47": "Zevenenveertig",
|
||||
"48": "Achtenveertig",
|
||||
"49": "Negenenveertig",
|
||||
"50": "Vijftig",
|
||||
"51": "Eenenvijftig",
|
||||
"52": "Tweeenvijftig",
|
||||
"53": "Drieenvijftig",
|
||||
"54": "Vierenvijftig",
|
||||
"55": "Vijfenvijftig",
|
||||
"56": "Zesenvijftig",
|
||||
"57": "Zevenenvijftig",
|
||||
"58": "Achtenvijftig",
|
||||
"59": "Negenenvijftig",
|
||||
"60": "Zestig",
|
||||
"61": "Eenenzestig",
|
||||
"62": "Tweeenzestig",
|
||||
"63": "Drieenzestig",
|
||||
"64": "Vierenzestig",
|
||||
"65": "Vijfenzestig",
|
||||
"66": "Zesenzestig",
|
||||
"67": "Zevenenzestig",
|
||||
"68": "Achtenzestig",
|
||||
"69": "Negenenzestig",
|
||||
"70": "Zeventig",
|
||||
"71": "Eenenzeventig",
|
||||
"72": "Tweeenzeventig",
|
||||
"73": "Drieenzeventig",
|
||||
"74": "Vierenzeventig",
|
||||
"75": "Vijfenzeventig",
|
||||
"76": "Zesenzeventig",
|
||||
"77": "Zevenenzeventig",
|
||||
"78": "Achtenzeventig",
|
||||
"79": "Negenenzeventig",
|
||||
"80": "Tachtig",
|
||||
"81": "Eenentachtig",
|
||||
"82": "Tweeentachtig",
|
||||
"83": "Drieentachtig",
|
||||
"84": "Vierentachtig",
|
||||
"85": "Vijfentachtig",
|
||||
"86": "Zesentachtig",
|
||||
"87": "Zevenentachtig",
|
||||
"88": "Achtentachtig",
|
||||
"89": "Negenentachtig",
|
||||
"90": "Negentig",
|
||||
"91": "Eenennegentig",
|
||||
"92": "Tweeennegentig",
|
||||
"93": "Drieennegentig",
|
||||
"94": "Vierennegentig",
|
||||
"95": "Vijfennegentig",
|
||||
"96": "Zesennegentig",
|
||||
"97": "Zevenennegentig",
|
||||
"98": "Achtennegentig",
|
||||
"99": "Negenennegentig"
|
||||
"0": "nul",
|
||||
"1": "één",
|
||||
"2": "twee",
|
||||
"3": "drie",
|
||||
"4": "vier",
|
||||
"5": "vijf",
|
||||
"6": "zes",
|
||||
"7": "zeven",
|
||||
"8": "acht",
|
||||
"9": "negen",
|
||||
"10": "tien",
|
||||
"11": "elf",
|
||||
"12": "twaalf",
|
||||
"13": "dertien",
|
||||
"14": "veertien",
|
||||
"15": "vijftien",
|
||||
"16": "zestien",
|
||||
"17": "zeventien",
|
||||
"18": "achttien",
|
||||
"19": "negentien",
|
||||
"20": "twintig",
|
||||
"21": "eenentwintig",
|
||||
"22": "tweeëntwintig",
|
||||
"23": "drieëntwintig",
|
||||
"24": "vierentwintig",
|
||||
"25": "vijfentwintig",
|
||||
"26": "zesentwintig",
|
||||
"27": "zevenentwintig",
|
||||
"28": "achtentwintig",
|
||||
"29": "negenentwintig",
|
||||
"30": "dertig",
|
||||
"31": "eenendertig",
|
||||
"32": "tweeëndertig",
|
||||
"33": "drieëndertig",
|
||||
"34": "vierendertig",
|
||||
"35": "vijfendertig",
|
||||
"36": "zesendertig",
|
||||
"37": "zevenendertig",
|
||||
"38": "achtendertig",
|
||||
"39": "negenendertig",
|
||||
"40": "veertig",
|
||||
"41": "eenenveertig",
|
||||
"42": "tweeënveertig",
|
||||
"43": "drieënveertig",
|
||||
"44": "vierenveertig",
|
||||
"45": "vijfenveertig",
|
||||
"46": "zesenveertig",
|
||||
"47": "zevenenveertig",
|
||||
"48": "achtenveertig",
|
||||
"49": "negenenveertig",
|
||||
"50": "vijftig",
|
||||
"51": "eenenvijftig",
|
||||
"52": "tweeënvijftig",
|
||||
"53": "drieënvijftig",
|
||||
"54": "vierenvijftig",
|
||||
"55": "vijfenvijftig",
|
||||
"56": "zesenvijftig",
|
||||
"57": "zevenenvijftig",
|
||||
"58": "achtenvijftig",
|
||||
"59": "negenenvijftig",
|
||||
"60": "zestig",
|
||||
"61": "eenenzestig",
|
||||
"62": "tweeënzestig",
|
||||
"63": "drieënzestig",
|
||||
"64": "vierenzestig",
|
||||
"65": "vijfenzestig",
|
||||
"66": "zesenzestig",
|
||||
"67": "zevenenzestig",
|
||||
"68": "achtenzestig",
|
||||
"69": "negenenzestig",
|
||||
"70": "zeventig",
|
||||
"71": "eenenzeventig",
|
||||
"72": "tweeënzeventig",
|
||||
"73": "drieënzeventig",
|
||||
"74": "vierenzeventig",
|
||||
"75": "vijfenzeventig",
|
||||
"76": "zesenzeventig",
|
||||
"77": "zevenenzeventig",
|
||||
"78": "achtenzeventig",
|
||||
"79": "negenenzeventig",
|
||||
"80": "tachtig",
|
||||
"81": "eenentachtig",
|
||||
"82": "tweeëntachtig",
|
||||
"83": "drieëntachtig",
|
||||
"84": "vierentachtig",
|
||||
"85": "vijfentachtig",
|
||||
"86": "zesentachtig",
|
||||
"87": "zevenentachtig",
|
||||
"88": "achtentachtig",
|
||||
"89": "negenentachtig",
|
||||
"90": "negentig",
|
||||
"91": "eenennegentig",
|
||||
"92": "tweeënnegentig",
|
||||
"93": "drieënnegentig",
|
||||
"94": "vierennegentig",
|
||||
"95": "vijfennegentig",
|
||||
"96": "zesennegentig",
|
||||
"97": "zevenennegentig",
|
||||
"98": "achtennegentig",
|
||||
"99": "negenennegentig"
|
||||
}
|
||||
|
||||
@@ -42,16 +42,20 @@ doc_h2 = mixed_heading2.svg
|
||||
doc_h3 = mixed_heading3.svg
|
||||
doc_h4 = mixed_heading4.svg
|
||||
done = typ_input-checked.svg
|
||||
down = typ_chevron-down.svg
|
||||
edit = typ_pencil.svg
|
||||
forward = typ_chevron-right.svg
|
||||
hash = typ_hash.svg
|
||||
maximise = typ_arrow-maximise.svg
|
||||
menu = typ_th-menu.svg
|
||||
minimise = typ_arrow-minimise.svg
|
||||
proj_chapter = mixed_document-chapter.svg
|
||||
proj_details = typ_th-list-grey.svg
|
||||
proj_document = typ_document-text.svg
|
||||
proj_folder = typ_folder.svg
|
||||
proj_note = mixed_document-note.svg
|
||||
proj_scene = mixed_document-scene.svg
|
||||
proj_stats = typ_chart-bar-grey.svg
|
||||
proj_title = mixed_document-title.svg
|
||||
reference = typ_at.svg
|
||||
refresh = typ_refresh.svg
|
||||
@@ -74,3 +78,15 @@ status_stats = typ_chart-bar-grey.svg
|
||||
status_time = typ_stopwatch-grey.svg
|
||||
sticky-off = typ_pin-outline.svg
|
||||
sticky-on = typ_pin.svg
|
||||
up = typ_chevron-up.svg
|
||||
view_build = typ_export.svg
|
||||
view_editor = mixed_edit.svg
|
||||
view_novel = typ_book-grey.svg
|
||||
view_outline = typ_puzzle-outline.svg
|
||||
|
||||
deco_doc_h0 = nw_deco-h0.svg
|
||||
deco_doc_h1 = nw_deco-h1.svg
|
||||
deco_doc_h2 = nw_deco-h2.svg
|
||||
deco_doc_h3 = nw_deco-h3.svg
|
||||
deco_doc_h4 = nw_deco-h4.svg
|
||||
deco_doc_more = nw_deco-noveltree-more.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,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="4"
|
||||
height="24"
|
||||
viewBox="0 0 4 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#848484;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="0"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 902 B |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="4"
|
||||
height="24"
|
||||
viewBox="0 0 4 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#99cc99;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="0"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 902 B |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="20"
|
||||
height="24"
|
||||
viewBox="0 0 20 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#d64848;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="16"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 905 B |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="36"
|
||||
height="24"
|
||||
viewBox="0 0 36 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#6699cc;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="32"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 905 B |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="52"
|
||||
height="24"
|
||||
viewBox="0 0 52 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#ffffff;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="48"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 905 B |
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<path
|
||||
d="M 9.7,18.2 16,12 9.7,5.8 C 9.5,5.6 9.2,5.5 9,5.5 8.8,5.5 8.5,5.6 8.3,5.8 8.1,6 8,6.2 8,6.5 v 11 c 0,0.3 0.1,0.5 0.3,0.7 0.2,0.2 0.5,0.3 0.7,0.3 0.2,0 0.5,-0.1 0.7,-0.3 z"
|
||||
id="path3096"
|
||||
style="fill:#848484;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 928 B |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg8707">
|
||||
<metadata
|
||||
id="metadata8713">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8711" />
|
||||
<path
|
||||
d="M 20,0 H 5.3333333 C 4.98,0 4.64,0.14 4.3906667,0.3906667 l -4,4 -0.076,0.082667 c -0.1853334,0.22 -0.3,0.4973334 -0.3133334,0.8 L 0,5.344 V 20 c 0,2.205333 1.7946667,4 4,4 h 12 c 1.738667,0 3.221333,-1.114667 3.772,-2.666667 h 0.894667 c 1.869333,0 3.333333,-1.756 3.333333,-4 V 4 C 24,1.7946667 22.205333,0 20,0 Z M 4,21.333333 C 3.2653333,21.333333 2.6666667,20.736 2.6666667,20 V 6.6666667 H 5.3333333 V 21.333333 Z M 17.333333,20 c 0,0.736 -0.598666,1.333333 -1.333333,1.333333 H 6.6666667 V 6.6666667 H 16 C 16.734667,6.6666667 17.333333,7.264 17.333333,8 Z m 4,-2.666667 c 0,0.826667 -0.432,1.333334 -0.666666,1.333334 H 20 V 8 C 20,5.7946667 18.205333,4 16,4 H 4.552 L 5.8853333,2.6666667 H 20 C 20.734667,2.6666667 21.333333,3.264 21.333333,4 Z"
|
||||
id="path8705"
|
||||
style="stroke-width:1.33333;fill:#aeaeae;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.5 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#6699cc;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 3.7531209,6.4345258 c -1.004161,1.002875 -1.004161,2.63319 0,3.6360652 l 8.2467181,8.248004 8.246719,-8.248004 C 20.749281,9.5691528 21,8.9108558 21,8.2525578 c 0,-0.658297 -0.250719,-1.316595 -0.753442,-1.818032 -1.004161,-1.004161 -2.631904,-1.004161 -3.636065,0 L 11.999839,11.043894 7.3891859,6.4345258 c -1.004161,-1.004161 -2.631905,-1.004161 -3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
id="svg8980"
|
||||
viewBox="0 0 24 24"
|
||||
height="24"
|
||||
width="24"
|
||||
version="1.2">
|
||||
<metadata
|
||||
id="metadata8986">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs8984" />
|
||||
<path
|
||||
style="fill:#6699cc;fill-opacity:1;stroke-width:1.28574"
|
||||
id="path8978"
|
||||
d="m 20.246879,17.565474 c 1.004161,-1.002875 1.004161,-2.63319 0,-3.636065 L 12.000161,5.6814052 3.7534421,13.929409 c -0.502723,0.501438 -0.753442,1.159735 -0.753442,1.818033 0,0.658297 0.250719,1.316595 0.753442,1.818032 1.004161,1.004161 2.631904,1.004161 3.636065,0 l 4.6106539,-4.609368 4.610653,4.609368 c 1.004161,1.004161 2.631905,1.004161 3.636065,0 z" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg1169"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs1173" />
|
||||
<path
|
||||
d="M 6.8888889,16.888889 V 17.5 c 2.0667778,-3.150889 4.4000001,-4.831444 7.3333331,-4.888889 v 3.666666 c 0,0.673444 0.624556,1.222223 1.397,1.222223 0.444889,0 0.825,-0.193112 1.079223,-0.477889 C 19.061,14.542222 24,9.5555555 24,9.5555555 c 0,0 -4.939,-4.9891115 -7.302778,-7.5007781 -0.254222,-0.2505555 -0.633111,-0.4436667 -1.078,-0.4436667 -0.772444,0 -1.397,0.5463333 -1.397,1.2222222 v 3.6666667 c -5.6955553,0 -7.3333331,5.9534434 -7.3333331,10.3888894 z m -3.6666667,5.5 H 20.333333 c 0.675889,0 1.222222,-0.547557 1.222222,-1.222223 V 13.77711 c -0.811555,0.826222 -1.66711,1.702555 -2.444444,2.501889 v 3.665444 H 4.4444445 V 5.2777775 H 13 V 2.833333 H 3.2222222 C 2.5463333,2.833333 2,3.3808886 2,4.0555552 V 21.166666 c 0,0.674666 0.5463333,1.222223 1.2222222,1.222223 z"
|
||||
id="path1167"
|
||||
style="stroke-width:1.22222;fill:#aeaeae;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.1 KiB |
@@ -0,0 +1,31 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
xmlns:dc="http://purl.org/dc/elements/1.1/"
|
||||
xmlns:cc="http://creativecommons.org/ns#"
|
||||
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
|
||||
xmlns:svg="http://www.w3.org/2000/svg"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg11920">
|
||||
<metadata
|
||||
id="metadata11926">
|
||||
<rdf:RDF>
|
||||
<cc:Work
|
||||
rdf:about="">
|
||||
<dc:format>image/svg+xml</dc:format>
|
||||
<dc:type
|
||||
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
|
||||
<dc:title></dc:title>
|
||||
</cc:Work>
|
||||
</rdf:RDF>
|
||||
</metadata>
|
||||
<defs
|
||||
id="defs11924" />
|
||||
<path
|
||||
d="M 22.461538,10.769231 V 8.615385 C 22.461538,6.24 20.52923,4.3076923 18.153846,4.3076923 H 17.219692 C 17.046153,1.8683077 14.816,0 12,0 9.1839995,0 6.9538461,1.8683077 6.7803077,4.3076923 H 5.8461538 C 3.4707692,4.3076923 1.5384615,6.24 1.5384615,8.615385 v 2.153846 c 0,1.245538 0.6326154,2.273231 1.5938462,2.764307 C 2.6916923,13.764923 2.3101538,14.110769 2.032,14.548923 1.7095384,15.058462 1.5384615,15.666462 1.5384615,16.307692 v 3.384616 C 1.5384615,22.067692 3.4707692,24 5.8461538,24 H 9.2307695 C 10.432,24 11.484308,23.388308 11.990154,22.401231 l 0.04431,0.08123 C 12.569847,23.432615 13.592616,24 14.76923,24 h 3.384616 c 2.375384,0 4.307692,-1.932308 4.307692,-4.307692 v -3.384616 c 0,-1.246769 -0.633845,-2.275692 -1.596308,-2.765538 0.955077,-0.505846 1.596308,-1.545846 1.596308,-2.772923 z m -2.461539,8.923077 c 0,1.015384 -0.830769,1.846154 -1.846153,1.846154 H 14.76923 c -0.438154,0 -0.891077,-0.265847 -0.481231,-0.94277 0.30277,-0.344615 0.481231,-0.761846 0.481231,-1.211077 0,-1.190153 -1.239384,-2.153846 -2.769231,-2.153846 -1.529846,0 -2.7692295,0.963693 -2.7692295,2.153846 0,0.369231 0.116923,0.708923 0.313846,1.012923 0.6239995,0.828308 0.167384,1.140924 -0.313846,1.140924 H 5.8461538 C 4.8307692,21.538462 4,20.707692 4,19.692308 v -3.384616 c 0,-0.317538 0.1390769,-0.64123 0.4726153,-0.64123 0.128,0 0.2818462,0.048 0.4701539,0.16 0.3446154,0.302769 0.7630769,0.48123 1.2110769,0.48123 1.1889231,0 2.1538462,-1.240615 2.1538462,-2.76923 0,-1.528616 -0.9649231,-2.769231 -2.1538462,-2.769231 -0.3692308,0 -0.7089231,0.116923 -1.0116923,0.313846 C 4.8504615,11.293538 4.6227692,11.382154 4.4504615,11.382154 4.1304615,11.382154 4,11.080615 4,10.769231 V 8.615385 C 4,7.6 4.8307692,6.7692308 5.8461538,6.7692308 h 3.3846157 c 0.48123,0 0.9378465,-0.3126154 0.299077,-1.1409231 -0.182154,-0.304 -0.299077,-0.6436923 -0.299077,-1.0129231 0,-1.1901538 1.2393835,-2.1538461 2.7692305,-2.1538461 1.529847,0 2.76923,0.9636923 2.76923,2.1538461 0,0.4492308 -0.17846,0.8664616 -0.48123,1.2110769 -0.409846,0.6769231 0.04308,0.9427693 0.48123,0.9427693 h 3.384616 C 19.16923,6.7692308 20,7.6 20,8.615385 v 2.153846 c 0,0.317538 -0.139076,0.641231 -0.472616,0.641231 -0.128,0 -0.281846,-0.048 -0.470154,-0.16 -0.344615,-0.30277 -0.763077,-0.481231 -1.211077,-0.481231 -1.188923,0 -2.153846,1.240615 -2.153846,2.769231 0,1.528615 0.964923,2.76923 2.153846,2.76923 0.369231,0 0.708923,-0.116923 1.011693,-0.313846 0.291693,-0.210461 0.519384,-0.300308 0.691692,-0.299077 0.318769,0 0.449232,0.301539 0.449232,0.612923 v 3.384616 z M 4,12.546462 c 0.1427692,0.03938 0.2904615,0.06646 0.4492307,0.06646 0.4209231,0 0.8406154,-0.146461 1.2775385,-0.448 l 0.084923,-0.05046 C 5.9310769,12.036923 6.0430769,12 6.1538461,12 c 0.4356923,0 0.9230769,0.658462 0.9230769,1.538462 0,0.88 -0.4873846,1.538461 -0.9230769,1.538461 -0.1329231,0 -0.2670769,-0.05908 -0.3987692,-0.174769 l -0.176,-0.128 C 5.2086153,14.548923 4.8356923,14.435692 4.4726153,14.435692 4.3076923,14.435692 4.1513846,14.464002 4,14.508312 Z M 19.550769,14.464 c -0.420922,0 -0.840616,0.146462 -1.277539,0.448 l -0.08492,0.05046 c -0.119384,0.07754 -0.231383,0.114461 -0.340923,0.114461 -0.435692,0 -0.923077,-0.658461 -0.923077,-1.538461 0,-0.88 0.487385,-1.538462 0.923077,-1.538462 0.132924,0 0.267077,0.05908 0.39877,0.174769 l 0.175999,0.128 c 0.371693,0.225231 0.743385,0.338462 1.106462,0.338462 0.167384,0 0.322461,-0.03077 0.472616,-0.07631 v 1.965539 c -0.144,-0.03938 -0.291693,-0.06646 -0.450462,-0.06646 z m -8.54523,7.074462 C 11.129847,21.112615 11.12,20.534154 10.630154,19.820308 l -0.05415,-0.09108 c -0.07631,-0.120616 -0.115692,-0.232616 -0.115692,-0.343385 0,-0.435692 0.657231,-0.923077 1.538462,-0.923077 0.88123,0 1.538461,0.487385 1.538461,0.923077 0,0.132923 -0.05908,0.267077 -0.176,0.4 l -0.128,0.174769 c -0.4,0.660923 -0.382769,1.204923 -0.270769,1.580308 h -1.956923 z"
|
||||
id="path11918"
|
||||
style="stroke-width:1.23077;fill:#aeaeae;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.6 KiB |
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg4455"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
xmlns:svg="http://www.w3.org/2000/svg">
|
||||
<defs
|
||||
id="defs4459" />
|
||||
<path
|
||||
d="M19 17h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 10h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2zM19 3h-14c-1.103 0-2 .897-2 2s.897 2 2 2h14c1.103 0 2-.897 2-2s-.897-2-2-2z"
|
||||
id="path4453"
|
||||
style="fill:#6699cc;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 571 B |
@@ -42,16 +42,20 @@ doc_h2 = mixed_heading2.svg
|
||||
doc_h3 = mixed_heading3.svg
|
||||
doc_h4 = mixed_heading4.svg
|
||||
done = typ_input-checked.svg
|
||||
down = typ_chevron-down.svg
|
||||
edit = typ_pencil.svg
|
||||
forward = typ_chevron-right.svg
|
||||
hash = typ_hash.svg
|
||||
maximise = typ_arrow-maximise.svg
|
||||
menu = typ_th-menu.svg
|
||||
minimise = typ_arrow-minimise.svg
|
||||
proj_chapter = mixed_document-chapter.svg
|
||||
proj_details = typ_th-list-grey.svg
|
||||
proj_document = typ_document-text.svg
|
||||
proj_folder = typ_folder.svg
|
||||
proj_note = mixed_document-note.svg
|
||||
proj_scene = mixed_document-scene.svg
|
||||
proj_stats = typ_chart-bar-grey.svg
|
||||
proj_title = mixed_document-title.svg
|
||||
reference = typ_at.svg
|
||||
refresh = typ_refresh.svg
|
||||
@@ -74,3 +78,15 @@ status_stats = typ_chart-bar-grey.svg
|
||||
status_time = typ_stopwatch-grey.svg
|
||||
sticky-off = typ_pin-outline.svg
|
||||
sticky-on = typ_pin.svg
|
||||
up = typ_chevron-up.svg
|
||||
view_build = typ_export.svg
|
||||
view_editor = mixed_edit.svg
|
||||
view_novel = typ_book-grey.svg
|
||||
view_outline = typ_puzzle-outline.svg
|
||||
|
||||
deco_doc_h0 = nw_deco-h0.svg
|
||||
deco_doc_h1 = nw_deco-h1.svg
|
||||
deco_doc_h2 = nw_deco-h2.svg
|
||||
deco_doc_h3 = nw_deco-h3.svg
|
||||
deco_doc_h4 = nw_deco-h4.svg
|
||||
deco_doc_more = nw_deco-noveltree-more.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,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="4"
|
||||
height="24"
|
||||
viewBox="0 0 4 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#848484;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="0"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 902 B |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="4"
|
||||
height="24"
|
||||
viewBox="0 0 4 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#718c00;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="0"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 902 B |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="20"
|
||||
height="24"
|
||||
viewBox="0 0 20 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#c82829;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="16"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 905 B |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="36"
|
||||
height="24"
|
||||
viewBox="0 0 36 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#4271ae;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="32"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 905 B |
@@ -0,0 +1,35 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="52"
|
||||
height="24"
|
||||
viewBox="0 0 52 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<rect
|
||||
style="fill:#333333;fill-opacity:1;stroke-width:0;stroke-linecap:round;stroke-linejoin:round;stroke-opacity:0.2"
|
||||
id="rect1955"
|
||||
width="4"
|
||||
height="22"
|
||||
x="48"
|
||||
y="1"
|
||||
rx="2"
|
||||
ry="2" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 905 B |
@@ -0,0 +1,30 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<svg
|
||||
version="1.2"
|
||||
width="24"
|
||||
height="24"
|
||||
viewBox="0 0 24 24"
|
||||
id="svg2161"
|
||||
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/">
|
||||
<metadata
|
||||
id="metadata2167">
|
||||
<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="defs2165" />
|
||||
<path
|
||||
d="M 9.7,18.2 16,12 9.7,5.8 C 9.5,5.6 9.2,5.5 9,5.5 8.8,5.5 8.5,5.6 8.3,5.8 8.1,6 8,6.2 8,6.5 v 11 c 0,0.3 0.1,0.5 0.3,0.7 0.2,0.2 0.5,0.3 0.7,0.3 0.2,0 0.5,-0.1 0.7,-0.3 z"
|
||||
id="path3096"
|
||||
style="fill:#848484;fill-opacity:1" />
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 928 B |
@@ -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 |
@@ -0,0 +1,100 @@
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vivamus diam nibh, tincidunt et quam at, fringilla malesuada risus. Nullam eleifend, sem nec varius tincidunt, urna mi varius dolor, sit amet gravida risus eros at purus. Etiam vehicula hendrerit elit, sit amet pulvinar dolor viverra sed. Curabitur metus ex, gravida at sodales sed, tristique eget diam. Suspendisse ultricies lorem sed ullamcorper rutrum. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis tempus magna mi, sit amet sagittis arcu viverra ut.
|
||||
Fusce in pretium tellus. Donec finibus vitae arcu at consectetur. Suspendisse et ultricies nisl. Integer et nisi vel sem aliquam scelerisque. Curabitur placerat massa et mi feugiat vulputate. In ac orci vitae risus dictum porttitor quis ut dui. Vivamus egestas condimentum risus quis fermentum. Nam lorem ante, rhoncus vel porttitor in, sodales in purus. Mauris ultricies ex metus, molestie ullamcorper arcu suscipit id.
|
||||
Cras dapibus porta eros, a tincidunt sapien mattis sit amet. Pellentesque ac metus in dui accumsan tristique eget eu tellus. Nam laoreet sapien vitae hendrerit ullamcorper. Mauris accumsan semper dui vitae pellentesque. Donec non velit cursus, eleifend nulla a, porttitor tellus. Donec elementum lobortis imperdiet. Etiam fermentum pretium arcu, sed consequat dui mattis nec. Mauris cursus fringilla magna, at pharetra justo. Sed tincidunt consequat urna, quis varius lacus dapibus id. Fusce sagittis lorem vitae sodales tempor. Ut vel feugiat lacus, in iaculis risus. Aliquam viverra tortor nec nibh tristique varius. Mauris interdum leo vitae massa sagittis venenatis.
|
||||
Quisque cursus eu orci at viverra. Donec libero libero, sagittis a sagittis vitae, efficitur sed nibh. Donec imperdiet malesuada est. Etiam consequat quam arcu, quis egestas libero sagittis a. Donec purus urna, volutpat id ante nec, iaculis maximus velit. Vivamus malesuada lacus sed velit consequat fringilla. Pellentesque ornare accumsan aliquam. Suspendisse tempor vel nisi quis iaculis. Curabitur porta ligula ac libero molestie, nec viverra ex feugiat. Donec lacinia eget lorem eu lacinia. Maecenas vestibulum ornare dui a aliquet.
|
||||
Proin id lobortis nunc, ut feugiat urna. Nam dictum odio tortor, bibendum sollicitudin erat suscipit eu. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Suspendisse eu imperdiet lectus. Nullam faucibus elit leo, sit amet pulvinar diam iaculis ac. Pellentesque accumsan vulputate orci sed ornare. Fusce at tortor ac libero volutpat tempus nec a diam. Nam eu libero tristique, rutrum magna suscipit, malesuada ligula.
|
||||
Praesent suscipit imperdiet arcu vitae faucibus. Phasellus massa mauris, pharetra at posuere vel, fringilla sed quam. Morbi nec congue ante, vel vulputate massa. Suspendisse imperdiet mollis dignissim. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis pulvinar odio tellus, non cursus turpis malesuada sit amet. Interdum et malesuada fames ac ante ipsum primis in faucibus. Pellentesque ex lorem, hendrerit ac enim et, tempor sodales ante. Curabitur quis tristique mauris, quis imperdiet metus. Mauris tristique interdum congue. Phasellus finibus condimentum pretium. Sed et tellus vel quam molestie aliquet.
|
||||
Phasellus sagittis ligula nibh, nec hendrerit libero maximus et. Morbi vestibulum, tortor non efficitur semper, velit lacus molestie augue, ac volutpat elit ante ac augue. Pellentesque ut tristique odio, vel ornare eros. Nunc at ullamcorper arcu, in ornare risus. Vivamus sed libero auctor, vulputate lectus ut, luctus lectus. Maecenas finibus at nulla vel congue. Nullam finibus mauris leo, ac volutpat sem pulvinar eget. Maecenas in posuere tellus, in placerat est. Ut at est semper, ullamcorper ligula ac, volutpat nisi. Donec tempus tellus sed lorem posuere, a rutrum odio faucibus. Donec porta sem ac tortor vulputate, sed pharetra sapien convallis. Praesent ac feugiat odio. In risus eros, pharetra sit amet porta eu, lacinia vel nunc.
|
||||
Praesent consectetur tincidunt mauris ut dignissim. Aenean sagittis ornare mi at sagittis. Proin id suscipit ipsum. Mauris lacinia commodo molestie. Fusce nec pharetra purus. Nulla eget velit varius, tincidunt erat tincidunt, cursus odio. Aliquam accumsan risus non nibh hendrerit fermentum. Aliquam erat volutpat. Phasellus non tincidunt lacus. Quisque sagittis augue eu egestas maximus. Integer sed eleifend neque, nec finibus sapien. Aliquam malesuada urna nec justo sodales dignissim. Cras volutpat maximus euismod. Pellentesque consequat lorem non augue placerat dignissim. Ut dolor nisl, malesuada sed nunc quis, malesuada dignissim nibh. Nam hendrerit hendrerit sapien quis auctor.
|
||||
Aenean feugiat enim eros, interdum tincidunt eros aliquam sit amet. Fusce hendrerit mi vulputate nisi vestibulum tempor. Vestibulum at dui et nisl viverra condimentum id sit amet leo. Phasellus vitae leo commodo, blandit magna ut, commodo enim. Donec ex elit, mattis non tincidunt quis, posuere ut sapien. Aliquam lacus mauris, commodo sed libero vitae, rhoncus sodales massa. Phasellus commodo, leo sit amet sollicitudin luctus, massa purus aliquet lectus, at rutrum nulla ante eget felis. Nulla facilisi. Aenean cursus ut diam non volutpat. Aliquam nec erat non ipsum ultricies hendrerit. Phasellus blandit ac lacus non laoreet. Etiam ultricies risus mauris, a volutpat nibh cursus in. Maecenas viverra odio sit amet libero feugiat tempor. Nullam eu tristique magna. Praesent sed lacus ligula.
|
||||
Sed iaculis viverra mollis. Phasellus sed eros sit amet lectus vulputate ornare sed non dui. Donec pretium dui quis felis feugiat elementum. Quisque eleifend eget nunc eu cursus. Etiam finibus lectus non ipsum ornare malesuada. Integer nec eros ullamcorper, pharetra neque at, suscipit ex. Nam faucibus sapien est, vel viverra ante viverra a.
|
||||
Ut quis euismod lorem. Fusce auctor quam eu velit semper, sed pretium odio semper. Fusce vehicula porta dignissim. Integer suscipit ultrices ultricies. Aenean pharetra cursus sem. Suspendisse ut porttitor neque. In luctus purus sagittis risus gravida pulvinar. Integer eu ante convallis, pharetra velit vel, sodales urna. Curabitur pretium sapien arcu, eget dignissim neque ornare in. Vivamus eget metus et diam pulvinar aliquam vel eu turpis. Donec augue metus, dapibus eu porta a, faucibus in felis. Etiam sodales arcu a ex fermentum, sit amet rutrum est auctor. Integer gravida lorem in mauris tincidunt lobortis.
|
||||
Etiam lobortis dictum dapibus. Nulla tincidunt placerat aliquam. Mauris non ornare nisi. Integer hendrerit ornare nibh, non vehicula sapien maximus eu. Proin purus erat, dapibus id dui commodo, blandit sodales mauris. Sed a vulputate lectus. Curabitur vel tincidunt orci. Vivamus cursus augue non nisl dictum, eget venenatis mi eleifend. Integer in ultricies ligula. Phasellus et sodales est, eget mollis magna. Sed lacinia, ligula eget feugiat ullamcorper, eros ipsum dignissim sapien, in suscipit mauris odio at metus. Suspendisse eget felis eu nulla porttitor dignissim at eu felis. Duis pulvinar est mauris, sit amet volutpat lacus hendrerit non. Aliquam finibus rhoncus mauris. Nunc dui nunc, lacinia aliquam finibus ut, maximus quis odio. Sed tempus dui vel orci vehicula, a tristique risus consectetur.
|
||||
Ut egestas velit eu urna imperdiet cursus. Aenean blandit pretium turpis, quis sollicitudin lacus dignissim eu. In eu orci posuere, volutpat velit et, varius velit. Mauris rutrum sem nunc, nec sagittis lectus accumsan at. Morbi vestibulum est ut dolor maximus, non euismod magna consequat. Morbi pharetra gravida velit ac suscipit. Fusce vitae turpis eget ante tempus bibendum. Integer sollicitudin vulputate neque eu pulvinar. Etiam consequat pulvinar lorem eu dignissim. Fusce luctus id lacus ut fermentum. Vestibulum ligula neque, finibus et aliquam vel, consequat id lacus. Quisque pretium elit a tincidunt aliquam.
|
||||
Maecenas varius magna in dictum dapibus. Vestibulum eget mattis velit, ac accumsan tellus. Donec luctus lorem in nisl convallis, eget egestas erat varius. Phasellus et lacinia dui. Praesent id nunc elementum, ultricies dolor a, pulvinar tellus. In a auctor mi, in fermentum justo. Proin sit amet vulputate leo, eu ultrices sem. Integer at sagittis ex, eu scelerisque velit.
|
||||
Curabitur auctor mollis nunc quis venenatis. Cras lectus ligula, auctor in tellus eget, aliquet molestie odio. Vivamus vel venenatis sem. Sed ac purus suscipit, pulvinar sapien sed, commodo arcu. Donec eget metus ipsum. Quisque posuere congue hendrerit. Phasellus eu scelerisque ipsum, ut vulputate diam. Curabitur sollicitudin tortor sapien, a finibus arcu consequat non. Suspendisse tempus magna porttitor mauris laoreet, quis tincidunt lectus iaculis. Fusce nibh magna, finibus ac enim at, volutpat dapibus metus. Duis iaculis sapien non erat vestibulum auctor.
|
||||
Maecenas nec porta leo. Fusce aliquam massa sit amet blandit interdum. Donec malesuada enim in eros laoreet iaculis. Integer fermentum faucibus nunc, et congue massa mattis eu. Pellentesque sed sodales orci. Praesent pulvinar lacus eget turpis condimentum imperdiet. Nullam molestie, ipsum a molestie accumsan, lectus quam sodales eros, malesuada faucibus ex leo malesuada velit. Fusce molestie ligula at sapien dictum semper non rutrum sapien. Ut facilisis gravida ante nec faucibus. Sed aliquet luctus auctor. In tempor libero at eleifend convallis. Phasellus in tristique ligula. Vestibulum eget quam dapibus, luctus enim vel, sagittis eros. Cras laoreet sapien diam, eu posuere nisl blandit quis.
|
||||
Vestibulum bibendum a massa ac faucibus. Maecenas vestibulum arcu id diam mattis, non varius orci aliquet. Vivamus sit amet ultrices velit. Donec vitae lorem vel lectus dignissim porta. Sed ac dui dui. Suspendisse lectus ante, pellentesque non euismod vel, finibus ut erat. Vestibulum accumsan facilisis velit, at posuere velit malesuada at. Mauris aliquam tortor sed pretium viverra. Ut et porttitor ex. In quis tellus vitae neque dignissim finibus nec eu dolor. Phasellus viverra nulla a vestibulum auctor. Praesent in lorem gravida, mattis enim commodo, volutpat tellus. Quisque non urna ac eros sollicitudin blandit sit amet ut risus. Integer sed diam a metus tristique consectetur vitae non velit. Aliquam justo est, pharetra vel libero in, molestie varius enim.
|
||||
Fusce vestibulum auctor varius. Maecenas malesuada, purus quis congue vehicula, arcu purus congue nunc, non convallis felis magna quis nisi. Curabitur nisi diam, imperdiet et sollicitudin quis, ultricies nec enim. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Phasellus rutrum, lectus at condimentum condimentum, felis justo lacinia orci, non venenatis nisl ex sed enim. Aenean semper ligula a diam cursus, ac sollicitudin ligula pretium. Etiam eleifend lorem nec massa tempor sollicitudin. Phasellus dignissim velit dignissim mauris vestibulum, sit amet sagittis sem blandit. Donec lobortis varius nunc sit amet posuere. Cras eu est lobortis, aliquet nisi in, varius mauris. Curabitur vitae leo laoreet sem condimentum vestibulum et eget enim. Nullam sed turpis ac nunc ornare tristique sit amet vel arcu. Cras efficitur ullamcorper lorem, et scelerisque lectus volutpat a. Maecenas ut urna in lacus rutrum volutpat.
|
||||
Pellentesque pretium, neque quis cursus sagittis, justo mauris euismod turpis, non feugiat lacus augue a magna. Nam ornare dictum erat et consequat. Vivamus fringilla odio velit, vitae convallis arcu vulputate et. Maecenas tristique purus ac velit cursus fringilla. Suspendisse id blandit dui, eu facilisis metus. Proin a erat rutrum, tempus lectus ut, consequat nisi. In dui sem, bibendum nec nibh nec, sollicitudin varius ipsum. Nulla id lectus eu eros placerat faucibus eget nec nunc. Nullam urna lectus, tempus nec libero a, egestas malesuada enim.
|
||||
Curabitur vestibulum a nibh eget varius. Sed lacus ipsum, porta sit amet egestas sit amet, pellentesque a dui. Etiam auctor mollis orci, eget pulvinar magna tristique quis. Proin condimentum ornare nibh, sed interdum orci congue at. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Maecenas quis lacinia tellus. Nullam ac nibh auctor, fermentum velit ac, rutrum ipsum. Nam egestas sodales hendrerit.
|
||||
Suspendisse a feugiat mauris. Nunc eget nisi augue. Suspendisse pharetra justo sit amet sollicitudin tincidunt. Duis pretium, leo quis euismod sagittis, ex ante sodales purus, vel tempor odio nisi ut diam. Praesent odio erat, ultricies et eros ac, pulvinar aliquet justo. Pellentesque non vestibulum sem, a vulputate purus. Nunc rutrum nulla vitae mattis pharetra. Integer dui diam, pulvinar sit amet velit bibendum, aliquet dictum nulla. Nam ut magna et lorem ornare dignissim eget quis mauris. Vestibulum aliquet mi ac rutrum varius. Mauris a imperdiet eros. Curabitur lobortis lectus vitae leo aliquam auctor. Ut et tincidunt dui.
|
||||
Mauris luctus risus eu tempor pulvinar. Mauris et mi nec ligula imperdiet vulputate a congue ex. Proin et venenatis mauris. Vivamus viverra accumsan lorem at gravida. Aliquam non pretium ante. Morbi aliquam risus sapien, quis consequat tellus maximus vitae. Sed consequat libero in molestie faucibus. Donec vestibulum arcu a sodales volutpat. Nullam ultricies ante quis quam accumsan rutrum. Quisque eu egestas velit. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed eu risus vestibulum enim iaculis pellentesque iaculis eu ligula. Nam rutrum, mauris sit amet efficitur eleifend, massa quam consequat metus, eget sodales leo sem eget risus. Aliquam dui libero, maximus at dui nec, consectetur maximus ligula. Sed gravida erat eget tristique lobortis.
|
||||
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean turpis velit, vestibulum a consequat vitae, efficitur id urna. Cras dapibus dui et scelerisque luctus. Sed vel malesuada justo. Suspendisse molestie iaculis lacus eu tempor. Praesent laoreet lectus quis malesuada volutpat. Fusce consectetur mollis ligula, a lobortis justo suscipit id. Integer eget tellus in arcu viverra accumsan eu et odio. Praesent ut felis risus. Pellentesque convallis felis et quam condimentum, at pellentesque nulla eleifend. Sed eget efficitur dui. Vivamus congue, leo eu lacinia condimentum, erat mi dignissim purus, sit amet convallis lorem sem sed ligula. Duis viverra leo enim, in pharetra felis consectetur viverra. Integer euismod feugiat ipsum eget vestibulum. Morbi fermentum dui vitae tincidunt dictum.
|
||||
Phasellus non eros ut ipsum pretium condimentum. Vestibulum tristique convallis aliquam. Phasellus tempor leo sit amet diam tristique, a auctor urna hendrerit. Mauris at velit euismod, sollicitudin mi at, scelerisque est. Mauris pulvinar consequat quam, eget varius augue tincidunt id. Curabitur turpis lectus, eleifend a egestas id, malesuada non diam. Proin aliquet tellus urna, venenatis consequat tortor finibus et. Quisque at tempor magna.
|
||||
Donec nec congue erat. Donec mauris lorem, dignissim euismod massa non, egestas pulvinar est. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Aenean aliquam venenatis eleifend. Vivamus consectetur ante et fringilla sodales. Nam vestibulum felis non sapien suscipit consectetur et interdum velit. Sed at placerat massa. Proin quis leo lectus. Integer diam velit, gravida quis rhoncus eget, tempor at ante. Aliquam pharetra dictum mi ac venenatis. Nunc volutpat id magna a pellentesque. In rutrum leo et ligula finibus, ut pretium lorem tristique. Curabitur vel orci iaculis, pellentesque justo et, ullamcorper orci. Quisque erat ante, venenatis ac lacinia ut, maximus ac odio. Aliquam varius bibendum varius.
|
||||
Duis ipsum orci, semper sit amet volutpat quis, scelerisque a nunc. Donec iaculis, erat vitae malesuada rutrum, nisl leo gravida nisi, et dignissim eros eros at mi. Cras volutpat est erat. Maecenas ex felis, porttitor vitae egestas sit amet, tincidunt efficitur tortor. Curabitur ac purus massa. Etiam porta maximus tristique. Morbi placerat pulvinar lectus sed tempus. Morbi fringilla odio posuere, volutpat mi sed, congue lorem. Cras convallis volutpat eros mattis vestibulum. Ut sit amet sodales eros, ac condimentum purus. Vivamus et elit augue. Pellentesque efficitur gravida ullamcorper. Sed non faucibus tellus, et consectetur urna. Pellentesque at turpis fringilla ipsum interdum hendrerit eu in dui. Nullam dictum eget metus non sollicitudin. Proin facilisis tincidunt euismod.
|
||||
Nunc et dui porta, suscipit mi vitae, pretium ante. Mauris a accumsan magna. Fusce tincidunt, nunc ut ullamcorper laoreet, nibh risus dignissim eros, a maximus arcu nunc congue arcu. Vivamus imperdiet quam lorem, vulputate consequat ante aliquet id. Sed porttitor mollis ullamcorper. Praesent dui justo, hendrerit quis vulputate sit amet, vulputate id libero. Etiam quam odio, dictum sit amet nisi in, semper placerat erat. Vivamus lacinia augue a dolor tincidunt dignissim. Curabitur ut massa sit amet elit tincidunt maximus.
|
||||
Integer sed lorem ac lacus tincidunt condimentum quis vel diam. Etiam vitae justo interdum, accumsan ex vel, pretium magna. Aenean id turpis malesuada, semper nisi vel, hendrerit leo. Vestibulum feugiat neque nec lacus auctor efficitur. Ut quis lobortis lacus, sit amet tempor orci. Donec a fringilla sem, nec sollicitudin ex. Sed vel faucibus libero. Sed pharetra leo sed porta ullamcorper. In auctor semper metus, id semper nunc rutrum a. Morbi rhoncus nulla quis ex condimentum, vel congue lectus tempor. Duis a venenatis est. Etiam dolor justo, rhoncus at lobortis quis, malesuada at quam. Donec facilisis convallis mi vitae rhoncus. Integer est ipsum, sollicitudin eu nulla in, accumsan egestas nisi. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nunc finibus sagittis nisl, et luctus ex.
|
||||
In molestie est luctus magna hendrerit, non tristique felis ultricies. Curabitur non lectus placerat, tincidunt ipsum quis, mollis nunc. Nam lacinia ullamcorper hendrerit. Fusce porttitor nisi laoreet, elementum velit vitae, venenatis mi. Aliquam venenatis dui ac justo varius fermentum. Etiam nec ligula eu neque aliquam placerat mollis ac dolor. Pellentesque gravida, enim commodo tempus faucibus, lorem nibh vulputate arcu, sodales pellentesque metus ligula volutpat sem. In scelerisque nisi ac ante lacinia auctor.
|
||||
Nunc at sodales libero, ut rutrum elit. Etiam vitae venenatis metus. Nullam faucibus turpis nisl. Maecenas varius sit amet lectus a ornare. Cras eget tortor mauris. Phasellus mattis nec tortor non sagittis. Integer eget placerat massa. Praesent eu porttitor leo. Vivamus sed bibendum sapien, sit amet maximus libero. Donec at lobortis lacus.
|
||||
Maecenas convallis eros nibh, nec pellentesque magna lacinia vulputate. Donec posuere neque ut lacinia iaculis. Quisque varius dictum condimentum. Aliquam lacinia elit sed sem porttitor tempus a ac ipsum. Aliquam suscipit ex vel tellus mollis, nec suscipit nunc ullamcorper. Cras urna magna, feugiat a odio a, pharetra congue velit. Proin quis hendrerit odio. Proin non orci ut sem ultricies sodales. Morbi dictum orci non enim tempor, ut condimentum erat maximus. Nam sagittis ornare lacus, at sodales dolor porttitor nec. Mauris dignissim ipsum in massa rutrum blandit. Nulla sit amet ante vestibulum, pharetra tellus eu, elementum tortor. Donec nec fringilla mi.
|
||||
Donec rhoncus odio a iaculis ullamcorper. Nam condimentum volutpat ante quis viverra. Vestibulum malesuada nibh tincidunt rutrum interdum. Sed dapibus et nisl vel ornare. Nunc ullamcorper nibh nec commodo eleifend. Nulla facilisi. Nullam ut est auctor ligula viverra dictum pellentesque id odio. Aenean eleifend ullamcorper mollis. Morbi quis rutrum dolor, vel dignissim purus. In vel metus scelerisque, auctor est quis, efficitur orci. Donec volutpat dui eu vulputate maximus. In hac habitasse platea dictumst. Quisque vel vulputate lorem. Phasellus lacinia tempus lectus, sed tristique magna hendrerit eu. Nullam non metus euismod dolor fringilla imperdiet et vitae purus. Sed varius ex tortor, vel rutrum turpis sagittis ac.
|
||||
Sed nec tincidunt turpis, sit amet suscipit nisi. Curabitur ultrices orci ligula, nec cursus eros placerat non. Pellentesque faucibus venenatis nisi in condimentum. Morbi facilisis mauris odio, id facilisis ex dictum ut. Duis bibendum ultricies elit, sit amet dapibus risus ultricies quis. Nulla eget ullamcorper risus, ac imperdiet nulla. Proin ultrices nisi eu turpis elementum hendrerit. Maecenas euismod pretium quam accumsan finibus. Morbi augue enim, laoreet nec nunc id, lobortis hendrerit lorem. Nunc mattis cursus dui vel lacinia. Aenean massa tellus, laoreet sit amet turpis vel, pharetra facilisis dui.
|
||||
Nunc sit amet tellus vel ligula vulputate dapibus. Donec non congue nisi. Mauris risus elit, vehicula a imperdiet a, tempor gravida ipsum. Sed erat magna, volutpat ac nunc sit amet, aliquam lobortis est. Mauris quis urna sem. Sed risus justo, vestibulum ac pharetra in, dignissim id nibh. Etiam nibh justo, auctor in tellus euismod, rhoncus lobortis dolor. Proin felis lectus, hendrerit nec dignissim nec, molestie a lorem. Phasellus commodo pellentesque ligula nec imperdiet. In quis sem a erat euismod elementum at sit amet sem.
|
||||
Praesent tortor enim, iaculis sit amet placerat in, sodales non erat. Phasellus porttitor, massa vel posuere tincidunt, mauris magna ultrices mi, in aliquet sapien sapien ut libero. Sed accumsan odio ut mollis dignissim. Aliquam varius egestas condimentum. Etiam id massa condimentum, scelerisque velit sed, vulputate augue. Duis mollis augue eu felis venenatis dapibus. Vivamus non gravida nulla. Integer a mi mollis, pharetra ipsum eget, tincidunt sapien.
|
||||
Phasellus libero arcu, aliquet rutrum commodo vel, efficitur quis libero. Morbi sed eros ante. Quisque efficitur leo eget nulla lobortis, eget imperdiet sem vehicula. Nunc commodo lorem sit amet felis aliquam, non facilisis velit tristique. Aliquam dapibus rutrum dignissim. Phasellus elementum nulla a enim venenatis, at ornare sapien feugiat. Proin ut sem eu nulla rutrum volutpat non non quam. Fusce vel pellentesque lectus, vitae pellentesque est. Praesent sit amet dolor non purus feugiat pharetra. Donec ullamcorper diam vitae sem varius, eget imperdiet nisi viverra. Fusce fermentum dui risus. Etiam sed diam ut libero sollicitudin faucibus at in sapien. Suspendisse bibendum purus at urna vehicula, sed maximus nisi pharetra. Proin posuere nisl ac consectetur sollicitudin. Nam auctor in ex scelerisque imperdiet.
|
||||
Donec consequat arcu non lacus ullamcorper, a fermentum felis lobortis. Nunc tempor est quam, ut finibus odio rutrum sit amet. Proin hendrerit tincidunt nunc, sed tincidunt nisi tempor eget. Phasellus maximus a risus ut luctus. Duis libero enim, varius a tellus vitae, dignissim facilisis felis. Vestibulum iaculis tempor condimentum. Sed mollis velit justo, quis mollis lectus imperdiet sed. Quisque laoreet eget eros porta imperdiet. Curabitur pretium velit quis sapien placerat, in blandit metus suscipit. Ut pretium urna lectus, sed interdum arcu fermentum et.
|
||||
Sed euismod nunc quam, nec tincidunt dui tempus at. Mauris molestie, quam vel lobortis dignissim, justo sem porta orci, nec commodo enim orci eget dolor. Pellentesque eros risus, fermentum non urna et, ultrices pharetra augue. Curabitur at tincidunt arcu. Etiam vel sollicitudin libero. Nulla dapibus orci odio, sit amet mattis quam sodales ac. Sed iaculis at eros volutpat egestas. In at fringilla massa. Proin at mattis turpis. Phasellus non metus et leo fermentum pretium non nec odio. Ut ante orci, interdum vel gravida eget, mollis in tortor. Sed rutrum ornare arcu in efficitur. Nulla facilisi.
|
||||
Aenean vestibulum nisl sed eleifend ullamcorper. Suspendisse viverra imperdiet nisi, sed imperdiet tortor vulputate ut. Nunc consequat nisi eu laoreet mollis. Quisque metus ante, fringilla at rutrum id, consectetur vel enim. Pellentesque posuere tempor urna, sit amet vulputate urna. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; In hac habitasse platea dictumst. Nullam quis elementum purus. Cras eleifend sed justo vitae faucibus. Morbi interdum neque vitae elementum sollicitudin. Etiam eleifend, sapien at interdum consectetur, ex mauris dictum sem, in convallis dolor felis sollicitudin augue. Curabitur id tortor ex. Mauris finibus, lectus convallis euismod congue, est tortor suscipit velit, ac dictum augue risus ut lectus. Vestibulum ultricies luctus hendrerit. Integer elit nibh, condimentum id tempus nec, euismod nec nunc.
|
||||
Sed porttitor hendrerit eros vitae suscipit. Vestibulum quis est elit. Sed neque leo, condimentum quis condimentum sed, posuere ut ex. Suspendisse mattis est nunc, in ullamcorper felis pellentesque sed. In hac habitasse platea dictumst. Maecenas tempus et turpis vestibulum porttitor. Quisque nulla odio, rhoncus et porta ut, hendrerit non sapien. Proin consequat, ex sed consequat iaculis, magna dolor consequat dui, eget rhoncus magna lorem in turpis. Cras urna sem, tincidunt ac aliquet id, imperdiet vitae neque. Fusce ac risus nisi. In erat urna, feugiat ac purus non, volutpat pulvinar ipsum. Morbi ultricies eros at nulla ullamcorper, nec lobortis arcu convallis. In maximus efficitur quam id maximus.
|
||||
Etiam posuere urna non diam accumsan tempor. Donec facilisis blandit leo sed convallis. Nullam ut nisl vel dolor varius accumsan vitae a dui. Nunc ullamcorper nunc ac rhoncus pretium. Mauris ac lectus urna. Phasellus quis interdum nisi. Suspendisse mi ex, mollis a euismod vel, pretium et est. Mauris condimentum in ipsum quis lacinia. Mauris scelerisque molestie nibh, auctor lobortis felis convallis ac. Praesent vestibulum luctus urna non tincidunt. Quisque id nisl pretium, bibendum nisl nec, gravida quam. Curabitur eget cursus purus, non commodo felis. Vestibulum non lectus nec quam auctor dictum. Curabitur molestie elit mi, non tempor nisi aliquam in. Etiam fringilla lacinia est.
|
||||
Pellentesque eleifend pulvinar eros, quis pulvinar arcu fermentum sed. Nam augue lectus, malesuada id mattis nec, ullamcorper sed nisi. Phasellus volutpat nisl eu commodo feugiat. Morbi eget sapien iaculis, consectetur est in, tempus metus. Donec hendrerit lectus aliquam ex faucibus pellentesque. Praesent lobortis libero sit amet metus commodo faucibus. Sed gravida eget mi at finibus.
|
||||
Vivamus nec ligula vitae augue auctor viverra. Sed vulputate eget libero a porta. Aenean nisl turpis, tincidunt et placerat sit amet, bibendum quis libero. Nullam ultrices a ex sit amet dapibus. Suspendisse id congue lorem. In viverra convallis neque. In facilisis dui quis rhoncus semper. Ut id imperdiet libero. Fusce condimentum, sem id elementum aliquam, quam augue tincidunt felis, at suscipit lorem eros eget augue. Suspendisse eros dui, consectetur a arcu in, hendrerit dapibus risus. Proin purus erat, tincidunt eget ante sit amet, consequat convallis erat. Praesent et ornare nisl, id vehicula nulla.
|
||||
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Aenean dignissim pellentesque mi, consectetur ornare leo egestas vitae. Suspendisse dignissim sapien orci, sed vestibulum neque maximus at. Sed lobortis urna consequat, sollicitudin lectus eget, finibus ante. Phasellus accumsan dolor non porttitor gravida. Duis eget eros ac lectus blandit posuere. Quisque sagittis nibh nunc, eu egestas nisi cursus at. Praesent accumsan fringilla neque eget iaculis. Nunc vulputate tempus tellus non porta. Sed faucibus vel dui in posuere. Sed quam arcu, accumsan id placerat sollicitudin, congue in diam.
|
||||
Cras sed nunc in turpis dignissim vehicula non a libero. Sed iaculis nulla fermentum lacinia porta. Pellentesque sit amet efficitur est. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Duis et ultrices justo. Etiam ut blandit nibh. In tempus lorem nec ultrices sagittis. Vivamus vel nulla faucibus, bibendum sapien ut, tristique diam. Donec pulvinar nisi nec semper blandit. Duis porttitor nibh enim, iaculis efficitur est sagittis a. Maecenas at eros ac quam pellentesque tempus nec ac magna. Donec pulvinar enim non congue facilisis. Donec eros orci, finibus vel lectus molestie, interdum scelerisque libero. Proin congue arcu lorem, in lobortis libero placerat vel. Nullam cursus est id lectus laoreet tempus et sed ex. Sed erat odio, molestie mattis egestas iaculis, sollicitudin eu ex.
|
||||
Donec sodales metus ut mi suscipit, quis pretium lorem ultrices. Donec placerat ante at pellentesque vulputate. Etiam id augue vel eros facilisis interdum. Mauris imperdiet, arcu sit amet tincidunt posuere, risus quam luctus diam, eget tincidunt ipsum magna a mauris. In hac habitasse platea dictumst. Fusce lacinia sem ut ullamcorper tristique. Nunc mattis massa tellus, vitae lacinia sem tincidunt ut. Phasellus sodales justo ut ligula iaculis, sit amet ultricies arcu lacinia. Nam ac dignissim orci. Nullam nec orci nec est bibendum ultrices. Nam pulvinar consequat eros, ut laoreet massa blandit nec. Proin tristique venenatis pretium. Curabitur vitae dui est. Integer fermentum, ligula in faucibus porttitor, sem nunc rhoncus turpis, a faucibus lectus risus quis eros. Pellentesque et lorem eu dolor placerat interdum. Aliquam luctus scelerisque leo eu faucibus.
|
||||
Vivamus id hendrerit odio, nec pellentesque magna. Proin augue eros, egestas nec eleifend ac, varius a sapien. Pellentesque viverra orci at condimentum euismod. Nam placerat lacus et augue porttitor blandit. Fusce scelerisque metus mollis nisl sollicitudin congue. Phasellus dictum velit arcu. Duis lacinia quam mauris, rhoncus porta enim dignissim posuere. Curabitur tempor urna eget ex cursus iaculis. Sed semper nisi nec nibh tempor pellentesque. Nulla vel turpis ac ipsum maximus porttitor eu sed nisl. Aliquam accumsan elit risus, eget sollicitudin ipsum lobortis at. Nulla sagittis faucibus sodales. Curabitur viverra pharetra quam quis efficitur. Integer eget justo maximus, dignissim magna vitae, consectetur metus. Suspendisse tincidunt, nunc eu vehicula faucibus, arcu felis accumsan velit, id rhoncus dui sapien vel purus.
|
||||
Nullam lacinia urna commodo vehicula fringilla. Donec tellus est, bibendum a quam in, pellentesque auctor odio. Donec vehicula velit et leo consequat, sed eleifend mauris aliquam. Vivamus nibh ligula, blandit eu leo quis, pretium malesuada augue. Pellentesque at lacinia magna. Sed nec dolor porttitor, finibus erat ut, fringilla leo. Phasellus sed ex pellentesque, semper elit auctor, fermentum turpis. Vestibulum dictum sodales augue volutpat pretium. Integer pharetra volutpat nisl at dignissim. Mauris mattis, turpis nec volutpat maximus, sapien dolor elementum urna, ac iaculis neque dui non urna. Duis scelerisque magna nec nunc mattis maximus. In lobortis massa ante, non sagittis turpis viverra nec. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Integer viverra velit a fringilla sodales.
|
||||
Suspendisse id arcu ut nisl ultrices convallis ornare in lectus. Nunc ultrices nulla vitae libero rutrum lobortis. Nullam hendrerit consectetur augue sit amet ornare. Cras dui est, pharetra at augue rhoncus, pharetra congue enim. Suspendisse maximus magna ut neque porta, in porttitor lorem pretium. Ut a dignissim magna, non tincidunt tortor. Vestibulum posuere tempus leo vitae blandit. Cras convallis ex a mi maximus fringilla a ac metus. Nunc sodales tincidunt semper. Praesent at risus eu neque tempus consectetur vel a odio.
|
||||
Fusce bibendum ex a risus feugiat tincidunt. In ac volutpat quam, vestibulum molestie nibh. Maecenas pretium ultricies augue vitae luctus. Ut congue leo ante, consequat ullamcorper mi commodo sit amet. Duis et libero leo. Nam eu dictum sem, quis commodo tellus. Sed et dolor eu nisi varius sodales sed eu quam. Aenean eget auctor mauris, vitae faucibus nunc. Nam ultrices elit eu libero porta, vitae cursus odio aliquet. Vivamus sit amet interdum tellus. Suspendisse suscipit fringilla nisi, id scelerisque nisi aliquet vitae. Aliquam est nibh, commodo nec neque et, consequat ultrices nibh. Suspendisse efficitur id massa sed bibendum. Phasellus vitae posuere est.
|
||||
Ut nec aliquet ex. Integer sapien nunc, tincidunt eu dolor ut, dictum pulvinar lorem. Donec nec augue nibh. Curabitur euismod, lorem ut mattis volutpat, nunc dui pharetra mauris, sed rutrum magna lacus non lacus. Nam ac rhoncus turpis, et dignissim erat. Fusce suscipit blandit mauris vel aliquam. Aenean id orci sed augue dapibus pellentesque ac ac sem. Nulla vel purus sapien. Integer vestibulum porttitor posuere. Mauris tincidunt elit nec risus tincidunt ornare. Duis bibendum, magna eu interdum bibendum, lectus nulla dictum dui, non condimentum sapien tortor sit amet orci. Integer ligula ligula, sodales vel nulla non, commodo semper eros. Quisque cursus tempus fringilla. Sed at nisl odio. Ut malesuada turpis ac libero consequat aliquet. Lorem ipsum dolor sit amet, consectetur adipiscing elit.
|
||||
Suspendisse tempus magna et massa euismod faucibus. Pellentesque sollicitudin nisl in pharetra aliquam. Vestibulum blandit massa ut turpis ornare, nec imperdiet metus interdum. Fusce molestie aliquet justo, ac condimentum justo. Praesent quis dolor vestibulum, posuere arcu eget, molestie nisi. Donec ac urna tempor, tempus lectus non, scelerisque est. Aliquam imperdiet dictum urna, non malesuada urna. Proin et tellus quis sapien dignissim vehicula. Vestibulum tortor metus, iaculis ac eros ut, laoreet vulputate nibh. Cras tempor vitae erat eget consequat. Sed ut nisl convallis, placerat quam quis, tincidunt turpis. Suspendisse sit amet rutrum augue. Duis sem justo, dictum eu dui at, eleifend commodo orci. Curabitur at erat odio. Phasellus porttitor interdum nibh, sit amet aliquet libero. Etiam convallis mattis massa, a rhoncus ligula condimentum et.
|
||||
Duis ut diam ac lectus viverra volutpat et eget dolor. Nam tincidunt mauris vitae aliquet laoreet. Nam id ipsum eget ante euismod vulputate. Mauris elementum, tellus vitae rutrum vestibulum, libero orci finibus risus, in lacinia libero massa vitae elit. Nam et vestibulum justo, sed gravida orci. In ultrices mollis ultricies. Pellentesque odio leo, sagittis lacinia odio eu, accumsan auctor nibh. In non sem elementum, mollis justo sed, ornare nisi. Fusce interdum lobortis turpis ut dictum. Integer lacinia mollis nunc, nec condimentum quam hendrerit in.
|
||||
Donec condimentum, dolor in aliquet facilisis, leo ex feugiat nisi, a commodo libero urna id enim. Donec faucibus urna eget pulvinar dapibus. Curabitur ultricies justo at ligula aliquam blandit. Mauris nisi urna, porttitor eu bibendum vitae, ultrices eu risus. In ac pulvinar nisl. Vestibulum viverra tellus purus, eget rutrum arcu venenatis et. Pellentesque nibh risus, sagittis eu hendrerit eget, sollicitudin sit amet enim. Quisque molestie ornare tellus sit amet placerat.
|
||||
Ut finibus metus sit amet velit posuere, in egestas massa congue. Nulla sagittis, nisi eget pretium vestibulum, mauris libero elementum mauris, vel sollicitudin ligula metus vitae elit. Sed feugiat lectus sed ante maximus pharetra. Praesent felis eros, gravida sed varius in, faucibus ut metus. Nulla eget pretium nulla. Aliquam ultricies viverra magna, vel semper ligula accumsan ut. Pellentesque sollicitudin bibendum pretium. Vivamus volutpat commodo eleifend. Maecenas ornare ac nisl at tristique. Vivamus tellus lectus, euismod nec pellentesque non, lobortis ut nunc. Quisque hendrerit mi eget hendrerit ultrices.
|
||||
Nunc rhoncus ligula ac libero consequat, semper commodo eros feugiat. Phasellus iaculis neque vitae luctus porttitor. Pellentesque suscipit mi ac ipsum bibendum mollis id at velit. Curabitur consectetur sollicitudin tincidunt. In a enim convallis, consequat justo nec, dignissim urna. Sed nisl ex, semper vitae lectus non, laoreet volutpat purus. Suspendisse ut mattis urna, quis hendrerit odio. Sed tincidunt libero pulvinar sem rhoncus, eu elementum enim ornare. Proin eu placerat ex. Cras euismod sem at ullamcorper scelerisque.
|
||||
Nam fringilla, velit in ultrices pellentesque, nisl magna tempor felis, vel congue purus lectus sed metus. Cras consectetur quis massa id bibendum. Praesent sed purus odio. Fusce volutpat dolor ut magna congue auctor. In at magna lacinia, bibendum nisi in, pellentesque nisl. Pellentesque metus ligula, malesuada non turpis et, vulputate ultrices lectus. Sed erat leo, maximus id elementum sagittis, lacinia a metus. Proin mattis id lorem non malesuada. Aliquam sodales quam id gravida condimentum.
|
||||
Phasellus quis bibendum risus, at porttitor sem. Nulla aliquet molestie eros quis sagittis. Ut lorem massa, ullamcorper vitae odio in, hendrerit vehicula urna. Quisque et viverra ex. Aliquam erat volutpat. Curabitur at interdum elit, ut tincidunt erat. Pellentesque quis augue pellentesque, finibus ante eget, feugiat quam. Cras nec efficitur ligula. Cras porta ac ipsum in ullamcorper. Nulla nunc erat, egestas egestas rhoncus viverra, mattis quis purus. Morbi convallis fringilla iaculis. Ut a sem mi. Praesent at velit ac dui euismod pulvinar. Donec mollis vehicula lorem vel tempus. Duis et scelerisque magna.
|
||||
Suspendisse sollicitudin a libero ut convallis. Donec interdum faucibus dolor, id imperdiet nibh mollis eget. Sed sem augue, bibendum nec risus quis, mollis scelerisque magna. Cras molestie velit eget est pulvinar, a pharetra sapien tempor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nulla accumsan semper arcu id porttitor. Sed lacinia massa ut nibh gravida consectetur. Donec semper ante sed scelerisque varius. Vestibulum ullamcorper varius dui, sed aliquam elit aliquam nec. Sed auctor, dui sit amet mollis egestas, libero neque interdum est, efficitur elementum nibh lacus sed libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Nam blandit lacus in lacinia tristique.
|
||||
Suspendisse scelerisque posuere nunc, non consectetur orci facilisis in. Donec aliquet fermentum mattis. Morbi fringilla id urna eget consectetur. Vivamus turpis est, efficitur a tempus ac, ornare vel lorem. Aenean bibendum metus non ante rutrum sollicitudin. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aliquam erat volutpat. Suspendisse dui eros, sagittis nec ante in, gravida varius ante. Mauris sapien neque, lobortis et euismod vel, venenatis at risus. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Interdum et malesuada fames ac ante ipsum primis in faucibus. Maecenas vestibulum metus arcu, sed faucibus lacus ultrices eget. Integer porta aliquam bibendum.
|
||||
Etiam tempus pulvinar dictum. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Sed sed nulla vehicula, iaculis nibh sit amet, blandit urna. Maecenas metus ante, posuere vitae bibendum quis, consequat sit amet magna. Cras venenatis nunc urna, vel posuere diam congue eu. Cras sit amet ipsum laoreet, tincidunt nunc ac, convallis elit. Praesent lacinia enim nibh, et efficitur leo convallis vel. Etiam varius diam arcu, sit amet convallis dui interdum vitae. In hac habitasse platea dictumst. Nullam commodo maximus turpis, quis congue mauris fermentum quis. Donec laoreet elit non lorem gravida pharetra. Ut aliquam purus a semper sodales.
|
||||
Donec nec sollicitudin nisi. Proin ipsum est, rhoncus vel commodo ut, tristique eget justo. Praesent tristique massa sed odio sodales viverra. Aliquam at nisi vel turpis pulvinar volutpat. Phasellus rhoncus scelerisque mollis. Nunc et cursus nulla, in consectetur magna. Praesent et mollis nibh, non tristique dolor. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Maecenas malesuada odio velit, eget finibus mauris congue eget. Praesent vestibulum mollis ex id ultricies. Integer sodales tempus vulputate. Phasellus blandit quam quis tortor blandit convallis. Donec tempor mi id urna venenatis fermentum.
|
||||
Vestibulum ut sapien non tellus eleifend vestibulum eu vitae magna. Donec in lectus eget nisi ornare efficitur sed ut ex. Cras volutpat metus rhoncus hendrerit fringilla. Vivamus rutrum rutrum neque, eu imperdiet augue malesuada a. Aenean tristique massa vel diam vehicula pulvinar sit amet vel velit. Aenean faucibus luctus magna, non auctor nunc tempor ut. Etiam eleifend auctor metus, ac rhoncus massa. Aenean dignissim ornare tincidunt. Donec pulvinar sapien ante, vel dignissim sapien dictum sed. In eu ante in tortor fringilla viverra sit amet quis metus. Aenean in ex elementum, gravida nunc non, pretium nibh. Donec laoreet arcu sit amet dolor tempus pulvinar. Nullam at pharetra nisl. Donec sed justo sed arcu luctus ultrices. Fusce at venenatis eros.
|
||||
Maecenas placerat quam ut massa suscipit lacinia. Maecenas et posuere risus. Proin interdum, libero sit amet consectetur pulvinar, dolor magna dignissim velit, sed hendrerit nulla ex sed nisi. Morbi dictum lobortis velit. Proin molestie mi at tortor finibus, quis ullamcorper enim maximus. Nullam varius, urna id volutpat volutpat, ligula lectus placerat eros, id sollicitudin dolor ligula eget sem. Curabitur quis augue vitae neque egestas tristique. Nam lacus libero, viverra faucibus risus et, placerat vestibulum neque.
|
||||
In et convallis ante. Nulla hendrerit turpis eget consequat molestie. Fusce pharetra nunc ornare leo commodo, eu consequat odio dictum. Cras est diam, consequat et eleifend sed, faucibus quis neque. Morbi fermentum sem non ipsum mollis, id tempus risus blandit. Phasellus vulputate, ante finibus molestie finibus, velit enim mattis neque, et posuere felis risus a nisl. Cras a risus eu eros porttitor malesuada. Maecenas in mattis diam, sed tempor ex. In hac habitasse platea dictumst. Aliquam in magna quis arcu ultrices fringilla. Cras rhoncus tortor sed lacus blandit commodo. Nullam placerat augue vitae diam rutrum, ut eleifend ligula pellentesque. Aenean blandit lectus orci, egestas dapibus nisi finibus at.
|
||||
Quisque id dapibus nisi, in placerat ante. Sed ut feugiat arcu. Etiam facilisis augue in nisi placerat facilisis. Donec vitae porttitor nibh. Nulla et nisl id purus egestas lobortis non id lorem. Nam viverra vulputate sapien, et posuere ex tincidunt a. Pellentesque venenatis turpis non purus rutrum faucibus. Nam non dapibus sem, sit amet rutrum neque. Vestibulum tempor, libero et faucibus feugiat, metus tellus condimentum nunc, sit amet pretium diam est a ipsum.
|
||||
Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Sed nulla neque, vulputate in aliquet quis, mollis non tellus. Ut aliquet lacus tellus, sed vehicula lectus aliquet ut. Nullam mattis in felis in condimentum. Vivamus facilisis, justo ac volutpat posuere, turpis ligula hendrerit tortor, eu ullamcorper quam dui non ex. Praesent feugiat, ligula eu aliquam interdum, mauris magna sagittis enim, eget facilisis felis lectus eget velit. In accumsan pharetra tincidunt. Sed mi massa, sodales nec nisi sed, lobortis fermentum ipsum. Nulla facilisi. Sed at accumsan felis. Donec sed condimentum metus, mollis gravida velit.
|
||||
Nulla at lobortis nisl. Sed eu laoreet felis. Maecenas velit erat, mattis a condimentum eu, fermentum vel elit. Aenean mattis rutrum risus, in interdum metus volutpat sed. Nulla at dignissim lorem, in convallis tellus. Suspendisse ac risus sit amet diam ullamcorper feugiat. Maecenas sed lectus id erat cursus aliquam vel eget nisi. Ut tempus, risus id mollis lobortis, sem metus suscipit nibh, ut dapibus lectus tellus quis ipsum. In nec hendrerit nibh. In vel consectetur ex, a ullamcorper sem. Donec mauris massa, dictum non luctus a, feugiat eu metus. Donec maximus ex sit amet sem vehicula finibus egestas eget ex. Etiam volutpat ut nulla quis euismod.
|
||||
Nunc consequat ut augue quis cursus. Aliquam placerat, enim eget scelerisque rhoncus, massa erat placerat tortor, ut varius dui felis in neque. Suspendisse potenti. Aliquam et magna tristique, volutpat sem eget, elementum lectus. Vivamus nunc velit, sagittis sed massa vel, vestibulum molestie urna. Cras at erat pharetra, porttitor turpis mattis, tempus magna. Nulla commodo enim mi, eu porttitor sapien pellentesque id. Aenean quis ligula eu mauris scelerisque porttitor quis ut orci. Praesent tincidunt risus mi. Nunc dignissim, arcu quis dictum consectetur, est massa imperdiet felis, ut scelerisque magna mi vel nisi. Mauris imperdiet, diam sit amet ultricies porttitor, odio sapien placerat ligula, in condimentum sem augue non justo. Duis a lorem augue.
|
||||
Aenean vitae venenatis dolor, at ultricies purus. Vivamus velit urna, tempus vitae ornare id, semper in metus. In sed mi et odio pulvinar ultricies ut quis enim. Duis erat dolor, aliquam sed sodales id, porttitor vitae turpis. Fusce feugiat venenatis ex quis aliquet. Nulla pretium elit vel nisi suscipit condimentum. Nunc dapibus, mi id venenatis ultrices, libero purus rhoncus erat, a eleifend metus nisl at elit.
|
||||
Aenean eget porttitor risus. Donec ac lacus feugiat, faucibus ante sed, convallis neque. Donec pharetra in sem eget congue. Quisque ac neque in urna varius interdum. Donec justo nisi, volutpat nec lobortis vitae, pulvinar sit amet lorem. Maecenas porttitor magna orci. In eleifend risus ut lectus facilisis aliquam. Nullam nibh nulla, sodales quis tempus sed, condimentum quis nulla. Proin vitae hendrerit ante. Morbi tincidunt pharetra metus, quis lobortis elit viverra sit amet. Vivamus mattis eros erat, ut semper velit pretium sed. Nulla ac vulputate leo.
|
||||
Praesent at ex at lacus dictum viverra vel nec nisi. Duis maximus nisi et eleifend fringilla. Cras lacinia arcu id turpis dignissim posuere. Sed faucibus nisi dignissim, sagittis orci quis, dapibus metus. Nullam ultricies libero eu auctor finibus. Vestibulum auctor odio in tortor molestie interdum. Proin nec libero mi. Donec tempor dignissim velit a molestie. Duis feugiat cursus turpis, in dignissim justo imperdiet non. Suspendisse faucibus dui non viverra interdum. Nullam dignissim interdum egestas. Phasellus vitae eros quis lectus bibendum scelerisque. Donec dui mauris, iaculis non molestie a, elementum sed felis. Donec iaculis congue tempor. Vestibulum dolor nisi, maximus quis cursus ut, vulputate non orci. Maecenas nisi ex, maximus ut aliquam commodo, interdum sed lorem.
|
||||
Cras sed vehicula risus, sed suscipit eros. Suspendisse scelerisque sapien sit amet volutpat porta. Sed sagittis orci eget feugiat eleifend. Nulla viverra ex vitae mauris lacinia imperdiet. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Vestibulum arcu augue, euismod non elit non, feugiat aliquet ligula. Nulla ut neque vehicula, malesuada ex sit amet, finibus sem. Maecenas eleifend molestie justo a lobortis.
|
||||
Aliquam at erat vitae neque porttitor ullamcorper. Integer et rutrum leo. Vivamus dui erat, luctus at purus et, auctor tincidunt felis. Proin fermentum maximus dui quis tempor. Nullam pellentesque at ex eget mollis. In hac habitasse platea dictumst. Maecenas efficitur turpis malesuada vulputate dapibus. Integer id faucibus diam. Aliquam lectus turpis, mollis vitae dolor eget, mattis faucibus est. Vestibulum id libero at est congue facilisis. Pellentesque tellus mauris, bibendum eget dui id, sagittis maximus enim. Sed condimentum ac lectus vel vulputate. Praesent lectus orci, pharetra id metus nec, fermentum congue nulla.
|
||||
Nunc vel sem sem. Integer varius tincidunt lorem, id porta massa venenatis porta. Maecenas dictum mollis pharetra. In ex justo, auctor ac tempus id, lacinia et erat. Fusce dignissim purus in metus facilisis, vitae congue nunc tempor. Nam a euismod elit. Vestibulum tincidunt sed orci et porta. Vestibulum eget dui id mi efficitur fermentum.
|
||||
Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Ut quis lorem vitae dui ullamcorper iaculis eget sed purus. Maecenas at ex lectus. Donec ut ligula eros. Nulla consectetur sed ligula quis volutpat. Integer rhoncus, justo egestas molestie scelerisque, nibh nibh finibus neque, dapibus vulputate quam lectus interdum tortor. Donec ultrices, tellus et suscipit tempor, urna lectus congue lorem, porta laoreet nibh purus id nisl. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse malesuada hendrerit turpis vel feugiat. Suspendisse at mi cursus, dictum ipsum in, tristique mi. Cras diam nisi, aliquam vel lacinia sed, vehicula id diam. Sed consectetur tempus massa, a pellentesque dolor posuere vel. Curabitur et finibus quam. Fusce orci magna, faucibus ac tellus id, fermentum tempor neque. Ut libero augue, egestas eget fringilla eget, suscipit vitae orci.
|
||||
Nunc posuere nisi purus, a bibendum elit interdum id. Donec accumsan metus non rutrum porttitor. Donec in sollicitudin lorem. Sed porta magna et lobortis consequat. Sed eget tempus nibh. Ut sed ante vel ex interdum commodo. Fusce eget nisi nec mauris tristique scelerisque. Ut eleifend nunc ultrices mattis ultricies. Vivamus id sodales lacus. Vivamus dignissim elementum ante, sit amet ornare ipsum lacinia sit amet. Proin maximus pulvinar nibh vitae luctus. Sed at gravida metus, at ultricies eros. Integer porta velit auctor, dictum elit non, dictum dolor.
|
||||
Suspendisse potenti. Morbi lobortis orci nisi, ac sollicitudin libero cursus sed. Integer vestibulum sem ac ante rutrum, at pellentesque tortor bibendum. Etiam at arcu et sapien molestie sodales. Nam fringilla turpis eget mi convallis, quis iaculis metus varius. Vivamus finibus purus ut nunc sodales pulvinar. Praesent varius, odio sed luctus tempus, orci augue sodales orci, a posuere nisi ante et justo. Ut ultricies rhoncus feugiat. Ut arcu massa, scelerisque vitae metus rhoncus, aliquet ultricies urna.
|
||||
Nulla facilisi. Aliquam at porttitor quam. Pellentesque eu faucibus nunc. Sed nec leo leo. Integer cursus ex a magna lacinia interdum. Vivamus luctus lacinia odio in sollicitudin. Curabitur sem felis, condimentum sed nisi at, posuere ullamcorper arcu. Vivamus convallis vel nisl id suscipit. Maecenas semper dictum nibh, nec malesuada dolor semper id. Maecenas turpis massa, vulputate ac euismod quis, viverra id lorem. Integer imperdiet tincidunt bibendum. Donec laoreet viverra volutpat. Aliquam arcu elit, euismod vitae felis quis, venenatis commodo lectus.
|
||||
Fusce mattis mi est. Proin augue dui, ultricies quis facilisis tristique, vehicula quis ex. Nunc blandit aliquet aliquet. Aliquam nibh sem, tempus a justo sit amet, viverra ullamcorper sem. Suspendisse potenti. Pellentesque quis risus eget ipsum tincidunt ornare ultrices at sapien. Phasellus viverra nec urna auctor dapibus. Curabitur id porttitor justo. Vestibulum risus sapien, dignissim vel libero ac, scelerisque fringilla risus. Donec porta at ex quis volutpat. Suspendisse potenti. Phasellus leo metus, pellentesque et laoreet sed, tempus ut ligula. Sed convallis elit libero, sed ornare odio mattis et.
|
||||
Ut lobortis libero vel nibh dignissim ullamcorper. Curabitur sed tortor id leo bibendum dignissim at sit amet dolor. Interdum et malesuada fames ac ante ipsum primis in faucibus. Sed vitae urna justo. Quisque in fringilla velit. Nullam venenatis laoreet tellus, sagittis sodales dolor facilisis ut. Integer tincidunt at est aliquet commodo. Cras sed urna ut sapien varius rhoncus. Duis pharetra a magna ut tincidunt.
|
||||
Sed mattis nunc ut pulvinar sodales. Nullam bibendum, ex vulputate volutpat porttitor, sapien mi gravida augue, at scelerisque mi ipsum sit amet risus. Phasellus non lacus molestie, faucibus nibh vitae, bibendum ex. Mauris et elit ut augue tincidunt pellentesque. Vestibulum vehicula pretium magna in faucibus. In fringilla tincidunt nisi. Integer porta vehicula risus eu commodo. Quisque iaculis laoreet vestibulum. Donec vitae aliquam metus, a gravida purus. Integer sit amet ligula vitae tortor lacinia mollis. Phasellus leo tellus, mattis ut maximus quis, ultricies in mauris. Praesent a scelerisque quam.
|
||||
Nunc tempor feugiat accumsan. Aliquam erat volutpat. Cras ut mi odio. Aenean sed purus sed nunc luctus tempor sit amet id ligula. Morbi nec faucibus neque, a luctus nisl. Maecenas aliquam lorem sed blandit maximus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
In hac habitasse platea dictumst. Cras blandit nibh lacus, nec venenatis nulla elementum nec. Nam tincidunt posuere augue rhoncus mattis. Cras interdum tincidunt massa, ac tristique libero porta in. Integer hendrerit gravida nisi, ut fringilla ligula ullamcorper nec. Fusce erat nulla, ullamcorper vel pulvinar eget, feugiat molestie velit. Integer viverra sollicitudin velit, vitae accumsan orci aliquet ac. Suspendisse aliquet augue ac mollis hendrerit. Proin euismod sagittis metus ac pretium. Nam augue nulla, posuere ac tortor nec, ornare interdum risus. Proin non massa eros. Morbi aliquet ante et purus placerat faucibus.
|
||||
Quisque pharetra dolor vitae magna vehicula fermentum quis nec odio. In commodo tincidunt turpis non scelerisque. Nam tempor consequat justo suscipit convallis. Sed sit amet augue at lorem porta eleifend sit amet sit amet velit. Fusce eu eros quis risus mattis vehicula eu at quam. Nulla faucibus elementum tincidunt. Sed in diam accumsan, blandit felis a, cursus mauris.
|
||||
Nunc in hendrerit augue. Phasellus vel purus ullamcorper, auctor ante sed, auctor nisi. Nunc rutrum est erat, ut consequat elit mattis ac. Suspendisse potenti. Maecenas purus libero, pharetra quis vestibulum at, egestas eget enim. Vestibulum ut condimentum tortor. Aliquam vestibulum mattis placerat. Morbi non leo eu nisl maximus lobortis eget eget sapien. Donec sollicitudin ipsum nulla, non dignissim massa pretium sed. Nulla et molestie nibh, nec aliquet arcu.
|
||||
Cras euismod ligula justo, at dignissim nibh mattis vitae. Integer bibendum sit amet urna vitae dapibus. Fusce gravida ut enim eget molestie. Quisque finibus nisl ut odio mattis, ut viverra justo pretium. Nunc convallis cursus tincidunt. Integer vel hendrerit orci. Donec leo orci, elementum at cursus a, eleifend a nisl. Nunc massa nunc, blandit non ex pretium, volutpat feugiat tortor. Phasellus gravida nibh ipsum, ac consequat ante eleifend eu. Nulla venenatis auctor efficitur. Vestibulum eleifend eros id nibh interdum, id tempus tortor molestie. Nam id finibus quam. Aenean eu vulputate ante. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Curabitur hendrerit mi ac venenatis suscipit.
|
||||
Vestibulum lobortis maximus metus, a commodo massa ultricies et. Etiam ut lectus quis lacus euismod consequat. Maecenas tincidunt, felis dapibus aliquam gravida, est sapien tempus nibh, et rutrum dolor orci in dui. Proin tellus nisl, imperdiet mollis elit ac, gravida lacinia sapien. Aenean quis lectus id ante gravida lobortis. In sollicitudin et ex et facilisis. Curabitur in velit at augue commodo placerat. Nulla facilisi. Fusce facilisis est eu dolor rhoncus, a bibendum felis tincidunt. Sed sit amet feugiat ante. Sed accumsan, ipsum nec bibendum semper, justo elit dignissim magna, eu lacinia sem lorem sed justo. Etiam posuere sollicitudin nisi ac placerat. Duis maximus nisl nec nisi accumsan posuere.
|
||||
Aliquam erat volutpat. Suspendisse tincidunt ut neque eu tincidunt. Nulla porttitor eu mi sed gravida. Maecenas in cursus nisl, et fringilla quam. Nulla posuere, turpis vel vestibulum condimentum, magna dui eleifend orci, nec semper metus risus id augue. Proin pretium risus id elit consequat tristique. Phasellus eget tortor eleifend, euismod arcu at, ullamcorper nibh. Etiam id ultricies dui, non mattis purus. Morbi sit amet mi diam. Nullam dictum erat vitae tortor lobortis imperdiet. Etiam venenatis ante non laoreet gravida.
|
||||
Vestibulum eget egestas mauris. Nulla facilisi. Vivamus ut dignissim turpis. Nulla quis tincidunt libero. Ut eget metus eleifend, volutpat enim ac, semper metus. Nunc vel ex nec neque maximus rhoncus vel et orci. Ut vitae congue sem, et porttitor diam.
|
||||
Duis sit amet est nec nibh scelerisque pellentesque ac in quam. Donec eget erat nec diam mattis feugiat eget a ex. Morbi interdum est non tortor accumsan porta. In hendrerit libero sit amet ex blandit, ut vestibulum quam dapibus. Integer quis velit id nibh tempor volutpat. Suspendisse euismod, sapien nec tempor malesuada, augue nunc tincidunt diam, et viverra dui massa sit amet velit. Proin efficitur, nisl non vulputate maximus, sapien nulla pharetra odio, vel eleifend neque velit at est. Phasellus leo sem, pharetra placerat lorem faucibus, pellentesque euismod mi. Nam vel diam neque. Praesent mollis feugiat magna vitae pellentesque. Proin elementum rhoncus ante, eget eleifend arcu volutpat quis. Fusce id enim sed odio mollis pulvinar sed a nulla. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae;
|
||||
Aliquam vulputate justo ut arcu lacinia accumsan. Phasellus quis convallis arcu, vel viverra urna. Curabitur tincidunt nibh id iaculis facilisis. In tellus arcu, elementum vel luctus at, tempor quis magna. Morbi maximus, elit in pharetra lacinia, ex lorem imperdiet risus, eget vehicula sapien enim sed nunc. Aliquam faucibus sodales tortor, vel fermentum quam finibus eu. Duis non dolor accumsan, lobortis neque a, pharetra purus. Donec a tincidunt urna. Cras eleifend elit ac tortor dictum rutrum. Donec ut est et diam tristique pellentesque. In mattis diam justo, sit amet rutrum ante varius nec. Sed in orci sit amet neque finibus fermentum. Aenean risus lectus, laoreet vitae finibus non, semper non sem. Donec vel porttitor mi. Duis efficitur posuere odio, in facilisis magna fringilla et. Donec quis tristique erat.
|
||||
Suspendisse mattis mattis mi at tristique. Ut ultricies sagittis iaculis. Aliquam sagittis, diam in commodo sagittis, odio nisl venenatis enim, suscipit venenatis felis diam vitae enim. Morbi a molestie urna, ac lacinia nunc. Nam quis metus augue. Fusce tristique orci ut euismod cursus. Pellentesque sed elit ac nisl placerat porttitor. Quisque molestie pharetra eros sed hendrerit. In tincidunt tellus in consequat ultricies. Nam condimentum sollicitudin aliquam. Nulla non odio urna. Aliquam gravida tincidunt erat at semper. Sed quis tempor turpis. Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas.
|
||||
Maecenas cursus massa tincidunt tortor placerat tempor. In justo nisl, posuere eu erat eget, vestibulum ultrices nulla. Vivamus vel est a erat gravida dapibus ut ac mauris. Integer eleifend gravida odio, aliquet tincidunt justo sollicitudin et. Ut ut ex in diam feugiat varius quis sit amet lectus. Aliquam sit amet nibh condimentum, sollicitudin neque id, vulputate orci. In vestibulum hendrerit libero, ut dapibus dui efficitur a. Aenean tristique at arcu id viverra. Maecenas cursus massa vel felis laoreet pretium. Praesent viverra tincidunt risus non sodales. Nulla id dignissim est, sed pharetra purus. Aliquam commodo, eros varius sagittis faucibus, diam lorem viverra erat, vel egestas ipsum dui sed turpis. Aenean placerat rhoncus nibh vel finibus.
|
||||
Interdum et malesuada fames ac ante ipsum primis in faucibus. Ut porttitor, nulla sed pharetra laoreet, lacus orci molestie mi, sed ullamcorper felis arcu id sapien. Curabitur et commodo velit. In pharetra arcu at augue pulvinar, commodo elementum augue condimentum. Sed mattis ipsum sed tempor faucibus. Donec non dolor sed purus ultrices condimentum sit amet vel nisi. Cras dignissim tellus et sapien porta elementum. Maecenas in eros vel orci congue dignissim. Donec et luctus libero. Phasellus vitae porttitor nisi. Donec a nibh nisi. Nulla in nibh nec tellus accumsan pretium. Aenean ultrices id est blandit malesuada. Integer blandit metus sed suscipit scelerisque. In ac urna cursus, vehicula justo nec, facilisis metus. Vivamus id scelerisque diam.
|
||||
In egestas nulla non tortor viverra laoreet. Fusce porttitor sem urna, sed varius justo maximus a. Duis ligula libero, elementum quis viverra mattis, vehicula nec sem. Aenean ut libero eu est auctor placerat. Nam quam dui, accumsan eget ipsum in, ornare volutpat purus. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. Nunc blandit, justo ut fermentum tincidunt, ex dolor suscipit eros, sed pretium lacus dolor vel tortor. Aliquam erat volutpat.
|
||||
Phasellus sagittis risus nec accumsan congue. Maecenas enim elit, mattis sit amet enim tristique, vestibulum sodales leo. Curabitur vel vehicula velit. Proin elementum dui at purus pulvinar, et dictum nisl sagittis. Nulla aliquet quam ultrices dapibus rutrum. Integer ultrices dapibus mauris, a vehicula elit posuere ut. Nullam lacinia mattis nulla, dignissim aliquam massa consequat at. Mauris ut mi dictum, iaculis urna rhoncus, euismod massa. Phasellus elementum libero id turpis imperdiet dictum. Nunc ac ipsum vitae orci ullamcorper tempus. Quisque lacinia quam sed molestie sagittis. Aenean sed rutrum sapien, et scelerisque sem.
|
||||
Suspendisse tincidunt, leo in vulputate rhoncus, neque augue feugiat purus, ac tempor libero nisi vitae ante. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean at augue quis ante porttitor blandit vitae non magna. Donec vel ligula hendrerit, sodales massa et, placerat libero. Etiam lectus felis, luctus et varius hendrerit, dignissim at massa. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Phasellus velit tortor, rutrum ut tristique non, feugiat at turpis. In iaculis leo metus, eu laoreet nunc viverra vel. Nam porttitor vitae diam vel pellentesque. In ullamcorper nulla id purus cursus accumsan. Etiam et mi turpis. Praesent volutpat sem odio, vitae dapibus libero posuere ac. Mauris dignissim ultricies felis, eu vestibulum turpis fringilla vitae. Sed porttitor ornare consectetur. Nulla id justo mollis, iaculis leo sit amet, bibendum eros. Donec ac interdum sapien.
|
||||
Praesent dolor urna, tincidunt a neque eu, hendrerit luctus odio. Pellentesque ac odio in est maximus maximus. Donec et lectus aliquet massa egestas euismod cursus et magna. Nam id mi hendrerit, rutrum eros iaculis, sodales enim. Nullam porttitor urna ac malesuada tincidunt. Suspendisse a turpis sapien. Curabitur rhoncus vel libero ut volutpat. Nam a sapien sem. Duis ac convallis eros, ut auctor nulla.
|
||||
Interdum et malesuada fames ac ante ipsum primis in faucibus. In sagittis lorem vitae est laoreet dictum. Integer posuere est ac tortor sagittis, quis mattis ligula laoreet. Curabitur urna sem, finibus id euismod at, varius ac sem. Donec tristique tristique est, a dictum lorem ornare eu. Praesent tempor diam ligula, at placerat leo placerat nec. Nam eget dui vulputate, vestibulum nisi ut, vehicula dui. Aenean elementum tincidunt lectus at vestibulum. Nam tristique mauris sit amet sapien iaculis facilisis. Nam accumsan, nibh ac aliquam venenatis, dolor lacus pretium dolor, accumsan feugiat lectus tellus facilisis sem. Nullam a dui sit amet tortor elementum rhoncus. Aliquam mattis eget quam et lobortis. Interdum et malesuada fames ac ante ipsum primis in faucibus. Suspendisse ultricies, magna eu imperdiet efficitur, tellus sapien suscipit massa, sit amet finibus dolor libero vitae ante. Maecenas vel aliquam massa.
|
||||
@@ -2,83 +2,16 @@
|
||||
<html>
|
||||
<body>
|
||||
|
||||
<h2>Release Notes for 1.6</h2>
|
||||
<p><i>Released on 20 February 2022</i></p>
|
||||
<h2>Release Notes for 1.7 Beta 1</h2>
|
||||
<p><i>Released on 17 May 2022</i></p>
|
||||
|
||||
<p>This release does not introduce any major new features, but is instead a collection of minor
|
||||
improvements and tweaks based on user requests. There are also a number of changes under the hood
|
||||
to improve the structure and performance of novelWriter.</p>
|
||||
<p><u>Some key improvements to the user interface are:</u></p>
|
||||
<p>✓ The max text width setting in Preferences now also applies to the document viewer, and
|
||||
the setting itself on the Preference dialog has been simplified a bit.</p>
|
||||
<p>✓ When text is selected in the document editor, the number of words selected is displayed
|
||||
in the editor's footer area.</p>
|
||||
<p>✓ The search tool in the document editor now shows the number of results in the
|
||||
document.</p>
|
||||
<p>✓ The Enter and Ctrl+O keyboard shortcuts should now work the same way in all tree
|
||||
views.</p>
|
||||
<p>✓ It is now possible to set a blank section title format on the Build Novel Project tool
|
||||
and get empty paragraphs in the output. Previously, a blank format would just remove the section
|
||||
break entirely. This change allows the user to define hard and soft scene breaks using level three
|
||||
and four headings. The scene and section titles can be hidden completely with two new switches
|
||||
added to the user interface.</p>
|
||||
<p><u>Other feature changes include:</u></p>
|
||||
<p>✓ The project index is now automatically rebuilt in the event it is empty or incomplete
|
||||
when the project is opened.</p>
|
||||
<p>✓ The user can now add their own syntax and GUI theme files in the app folder in their
|
||||
user area on the host operating system. Where the custom files must be added is described in the
|
||||
documentation.</p>
|
||||
<p>✓ A Windows installer is yet again provided for novelWriter. If you have novelWriter
|
||||
installed using another method, make sure you uninstall it properly first as the two methods are
|
||||
not compatible.</p>
|
||||
<p>✓ Release versions for Ubuntu 21.04 have been dropped, and added for the upcoming Ubuntu
|
||||
22.04.</p>
|
||||
<p>✓ Most translations have been updated. A Dutch translation is in the works.</p>
|
||||
<p>This is a beta release of the next release version, and is intended for testing purposes. Please
|
||||
be careful when using this version on live writing projects, and make sure you take frequent
|
||||
backups.</p>
|
||||
<p>Please check the changelog for an overview of changes. The full release notes will be added to
|
||||
the final release.</p>
|
||||
|
||||
<p><i>See also the <a href="https://github.com/vkbo/novelWriter/releases">Releases</a> page.</i></p>
|
||||
|
||||
<h2>Patch Notes</h2>
|
||||
|
||||
<h3>Patch 1.6.1 – 16 March 2022</h3>
|
||||
|
||||
<p>This is a bugfix and patch release that fixes two recursion/loop issues. One would potentially
|
||||
cause a crash if the window was resized rapidly, and one would cause a hang with certain search
|
||||
parameters in the editor's search box. The Latin American Spanish translation has also been
|
||||
updated.</p>
|
||||
|
||||
<h3>Patch 1.6.2 – 20 March 2022</h3>
|
||||
|
||||
<p>This is a bugfix release that fixes a couple of minor issues. Projects containing one or more
|
||||
empty documents would trigger a rebuild of the index each time the project was opened. This has now
|
||||
been fixed. Another fix resolves an error message being written to the console logging output when
|
||||
a new document was created. Both errors were harmless.</p>
|
||||
|
||||
<h3>Patch 1.6.3 – 18 August 2022</h3>
|
||||
|
||||
<p>This is a bugfix release that fixes a rare problem causing novelWriter to crash if the spell
|
||||
checker language setting was configured to an empty value.</p>
|
||||
<p>A few other minor issues have also been fixed: The project language setting is now properly
|
||||
exported to ODT documents. Spaces are no longer inserted automatically in front of colons in
|
||||
certain meta data settings when the feature is enabled (it is primarily used for French). Lastly,
|
||||
the slider splitting the editor and viewer panels can no longer be dragged until the viewer
|
||||
disappears. It was not necessarily obvious how the viewer panel could be restored in such cases.
|
||||
</p>
|
||||
|
||||
<h3>Patch 1.6.4 – 29 September 2022</h3>
|
||||
|
||||
<p>This is a bugfix release that fixes a critical bug in the insert non-breaking spaces feature. It
|
||||
basically no longer worked in the 1.6.3 release. This release also fixes a minor issue where the
|
||||
text cursor sometimes disappears when reaching the right-hand edge of the text editor window.</p>
|
||||
|
||||
<h3>Patch 1.6.5 – 13 October 2022</h3>
|
||||
|
||||
<p>This is a bugfix release that fixes a a few minor issues. The idle time for new projects would
|
||||
be artificially inflated as the clock was not reset when the project was first created. This only
|
||||
affects the first entry in the writing statistics. A scaling issue for the Preferences dialog has
|
||||
also been fixed. It only affected screens with UI scaling enabled. Lastly, typing Shift+Enter in
|
||||
the text editor now creates a regular line break instead of a special line separator. The line
|
||||
separator serves no purpose in plain text, and was producing inconsistencies in how text is
|
||||
processed and displayed.</p>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -10,7 +10,7 @@ licenseurl = https://creativecommons.org/licenses/by-sa/4.0/
|
||||
window = 54, 54, 54
|
||||
windowtext = 174, 174, 174
|
||||
base = 62, 62, 62
|
||||
alternatebase = 67, 67, 67
|
||||
alternatebase = 78, 78, 78
|
||||
text = 174, 174, 174
|
||||
tooltipbase = 255, 255, 192
|
||||
tooltiptext = 21, 21, 13
|
||||
|
||||
@@ -10,7 +10,7 @@ licenseurl = https://github.com/altercation/solarized/blob/master/LICENSE
|
||||
window = 0, 43, 54
|
||||
windowtext = 253, 246, 227
|
||||
base = 7, 54, 66
|
||||
alternatebase = 67, 67, 67
|
||||
alternatebase = 0, 43, 54
|
||||
text = 253, 246, 227
|
||||
tooltipbase = 133, 153, 0
|
||||
tooltiptext = 0, 43, 54
|
||||
|
||||
@@ -184,6 +184,12 @@ def checkIntRange(value, first, last, default):
|
||||
return default
|
||||
|
||||
|
||||
def minmax(value, minVal, maxVal):
|
||||
"""Make sure an integer is between min and max value (inclusive).
|
||||
"""
|
||||
return min(maxVal, max(minVal, value))
|
||||
|
||||
|
||||
def checkIntTuple(value, valid, default):
|
||||
"""Check that an int is an element of a tuple. If it isn't, return
|
||||
the default value.
|
||||
@@ -241,23 +247,17 @@ def formatTime(tS):
|
||||
return "ERROR"
|
||||
|
||||
|
||||
def parseTimeStamp(theStamp, default, allowNone=False):
|
||||
"""Parses a text representation of a timestamp and converts it into
|
||||
a float. Note that negative timestamps cause an OSError on Windows.
|
||||
See https://bugs.python.org/issue29097
|
||||
"""
|
||||
if str(theStamp).lower() == "none" and allowNone:
|
||||
return None
|
||||
try:
|
||||
return datetime.strptime(theStamp, nwConst.FMT_TSTAMP).timestamp()
|
||||
except Exception:
|
||||
return default
|
||||
|
||||
|
||||
# =============================================================================================== #
|
||||
# String Functions
|
||||
# =============================================================================================== #
|
||||
|
||||
def simplified(string):
|
||||
"""Take a string an strip leading and trailing whitespaces, and
|
||||
replace all occurences of (multiple) whitespaces with a 0x20 space.
|
||||
"""
|
||||
return " ".join(str(string).strip().split())
|
||||
|
||||
|
||||
def splitVersionNumber(value):
|
||||
"""Split a version string on the form aa.bb.cc into major, minor
|
||||
and patch, and computes an integer value aabbcc.
|
||||
|
||||
@@ -96,8 +96,6 @@ class Config:
|
||||
# Sizes
|
||||
self.winGeometry = [1200, 650]
|
||||
self.prefGeometry = [700, 615]
|
||||
self.treeColWidth = [200, 50, 30]
|
||||
self.novelColWidth = [200, 50]
|
||||
self.projColWidth = [200, 60, 140]
|
||||
self.mainPanePos = [300, 800]
|
||||
self.docPanePos = [400, 400]
|
||||
@@ -117,7 +115,7 @@ class Config:
|
||||
# Text Editor
|
||||
self.textFont = None # Editor font
|
||||
self.textSize = 12 # Editor font size
|
||||
self.textWidth = 600 # Editor text width
|
||||
self.textWidth = 700 # Editor text width
|
||||
self.textMargin = 40 # Editor/viewer text margin
|
||||
self.tabWidth = 40 # Editor tabulator width
|
||||
|
||||
@@ -460,8 +458,6 @@ class Config:
|
||||
cnfSec = "Sizes"
|
||||
self.winGeometry = theConf.rdIntList(cnfSec, "geometry", self.winGeometry)
|
||||
self.prefGeometry = theConf.rdIntList(cnfSec, "preferences", self.prefGeometry)
|
||||
self.treeColWidth = theConf.rdIntList(cnfSec, "treecols", self.treeColWidth)
|
||||
self.novelColWidth = theConf.rdIntList(cnfSec, "novelcols", self.novelColWidth)
|
||||
self.projColWidth = theConf.rdIntList(cnfSec, "projcols", self.projColWidth)
|
||||
self.mainPanePos = theConf.rdIntList(cnfSec, "mainpane", self.mainPanePos)
|
||||
self.docPanePos = theConf.rdIntList(cnfSec, "docpane", self.docPanePos)
|
||||
@@ -581,8 +577,6 @@ class Config:
|
||||
theConf["Sizes"] = {
|
||||
"geometry": self._packList(self.winGeometry),
|
||||
"preferences": self._packList(self.prefGeometry),
|
||||
"treecols": self._packList(self.treeColWidth),
|
||||
"novelcols": self._packList(self.novelColWidth),
|
||||
"projcols": self._packList(self.projColWidth),
|
||||
"mainpane": self._packList(self.mainPanePos),
|
||||
"docpane": self._packList(self.docPanePos),
|
||||
@@ -811,20 +805,6 @@ class Config:
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setTreeColWidths(self, colWidths):
|
||||
"""Set the column widths of the main project tree.
|
||||
"""
|
||||
self.treeColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setNovelColWidths(self, colWidths):
|
||||
"""Set the column widths of the novel tree.
|
||||
"""
|
||||
self.novelColWidth = [int(x/self.guiScale) for x in colWidths]
|
||||
self.confChanged = True
|
||||
return True
|
||||
|
||||
def setProjColWidths(self, colWidths):
|
||||
"""Set the column widths of the Load Project dialog.
|
||||
"""
|
||||
@@ -910,12 +890,6 @@ class Config:
|
||||
def getPreferencesSize(self):
|
||||
return [int(x*self.guiScale) for x in self.prefGeometry]
|
||||
|
||||
def getTreeColWidths(self):
|
||||
return [int(x*self.guiScale) for x in self.treeColWidth]
|
||||
|
||||
def getNovelColWidths(self):
|
||||
return [int(x*self.guiScale) for x in self.novelColWidth]
|
||||
|
||||
def getProjColWidths(self):
|
||||
return [int(x*self.guiScale) for x in self.projColWidth]
|
||||
|
||||
@@ -978,12 +952,12 @@ class Config:
|
||||
"""
|
||||
try:
|
||||
import enchant # noqa: F401
|
||||
self.hasEnchant = True
|
||||
logger.debug("Checking package 'pyenchant': OK")
|
||||
except Exception:
|
||||
except ImportError:
|
||||
self.hasEnchant = False
|
||||
logger.debug("Checking package 'pyenchant': Missing")
|
||||
|
||||
else:
|
||||
self.hasEnchant = True
|
||||
logger.debug("Checking package 'pyenchant': OK")
|
||||
return
|
||||
|
||||
# END Class Config
|
||||
|
||||
@@ -25,7 +25,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
|
||||
from PyQt5.QtCore import QCoreApplication, QT_TRANSLATE_NOOP
|
||||
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwOutline
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwOutline
|
||||
|
||||
|
||||
def trConst(tString):
|
||||
@@ -34,7 +34,7 @@ def trConst(tString):
|
||||
return QCoreApplication.translate("Constant", tString)
|
||||
|
||||
|
||||
class nwConst():
|
||||
class nwConst:
|
||||
|
||||
# Date and Time Formats
|
||||
FMT_TSTAMP = "%Y-%m-%d %H:%M:%S" # Default format
|
||||
@@ -42,32 +42,13 @@ class nwConst():
|
||||
FMT_DSTAMP = "%Y-%m-%d" # Date only format
|
||||
|
||||
# Various Hard Limits
|
||||
MAX_DEPTH = 30 # Maximum folder depth of a project
|
||||
MAX_DOCSIZE = 5000000 # Maxium size of a single document
|
||||
MAX_BUILDSIZE = 10000000 # Maxium size of a project build
|
||||
|
||||
# END Class nwConst
|
||||
|
||||
|
||||
class nwLists():
|
||||
"""Lists used for grouping various other constants.
|
||||
"""
|
||||
# Regular user-accessible item types
|
||||
REG_TYPES = {nwItemType.ROOT, nwItemType.FOLDER, nwItemType.FILE}
|
||||
|
||||
# Item classes where the full list of novel layouts are allowed
|
||||
CLS_NOVEL = {nwItemClass.NOVEL, nwItemClass.ARCHIVE}
|
||||
|
||||
# Item classes which do not require items to have same class
|
||||
FREE_CLASS = {nwItemClass.ARCHIVE, nwItemClass.TRASH}
|
||||
|
||||
# Deprecated nwItemLayout entries
|
||||
DEP_LAYOUT = ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE")
|
||||
|
||||
# END Class nwLists
|
||||
|
||||
|
||||
class nwRegEx():
|
||||
class nwRegEx:
|
||||
|
||||
FMT_EI = r"(?<![\w\\])(_)(?![\s_])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
FMT_EB = r"(?<![\w\\])([\*]{2})(?![\s\*])(.+?)(?<![\s\\])(\1)(?!\w)"
|
||||
@@ -76,7 +57,16 @@ class nwRegEx():
|
||||
# END Class nwRegEx
|
||||
|
||||
|
||||
class nwFiles():
|
||||
class nwHeaders:
|
||||
|
||||
H_VALID = ("H0", "H1", "H2", "H3", "H4")
|
||||
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
|
||||
TT_NONE = "T000000"
|
||||
|
||||
# END Class nwHeaders
|
||||
|
||||
|
||||
class nwFiles:
|
||||
|
||||
PROJ_FILE = "nwProject.nwx"
|
||||
PROJ_DICT = "wordlist.txt"
|
||||
@@ -126,7 +116,7 @@ class nwKeyWords:
|
||||
# END Class nwKeyWords
|
||||
|
||||
|
||||
class nwLabels():
|
||||
class nwLabels:
|
||||
|
||||
CLASS_NAME = {
|
||||
nwItemClass.NO_CLASS: QT_TRANSLATE_NOOP("Constant", "None"),
|
||||
@@ -204,7 +194,7 @@ class nwLabels():
|
||||
# END Class nwLabels
|
||||
|
||||
|
||||
class nwQuotes():
|
||||
class nwQuotes:
|
||||
"""Allowed quotation marks.
|
||||
Source: https://en.wikipedia.org/wiki/Quotation_mark
|
||||
"""
|
||||
|
||||
@@ -19,8 +19,9 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from novelwriter.core.doctools import DocMerger
|
||||
from novelwriter.core.document import NWDoc
|
||||
from novelwriter.core.index import NWIndex, countWords
|
||||
from novelwriter.core.index import countWords
|
||||
from novelwriter.core.project import NWProject
|
||||
from novelwriter.core.spellcheck import NWSpellEnchant
|
||||
from novelwriter.core.tohtml import ToHtml
|
||||
@@ -28,9 +29,9 @@ from novelwriter.core.toodt import ToOdt
|
||||
from novelwriter.core.tomd import ToMarkdown
|
||||
|
||||
__all__ = [
|
||||
"DocMerger",
|
||||
"countWords",
|
||||
"NWDoc",
|
||||
"NWIndex",
|
||||
"NWProject",
|
||||
"NWSpellEnchant",
|
||||
"ToHtml",
|
||||
|
||||
@@ -0,0 +1,119 @@
|
||||
"""
|
||||
novelWriter – Project Document Tools
|
||||
====================================
|
||||
A collection of tools to create and manipulate documents
|
||||
|
||||
File History:
|
||||
Created: 2022-10-02 [2.0b1]
|
||||
|
||||
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
|
||||
|
||||
from novelwriter.core.document import NWDoc
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DocMerger:
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.theProject = theProject
|
||||
|
||||
self._error = ""
|
||||
self._targetDoc = None
|
||||
self._targetText = []
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def getError(self):
|
||||
"""Return any collected errors.
|
||||
"""
|
||||
return self._error
|
||||
|
||||
def setTargetDoc(self, tHandle):
|
||||
"""Set the target document for the merging. Calling this
|
||||
function resets the class.
|
||||
"""
|
||||
self._targetDoc = tHandle
|
||||
self._targetText = []
|
||||
return
|
||||
|
||||
def newTargetDoc(self, srcHandle, docLabel):
|
||||
"""Create a barnd new target document based on a source handle
|
||||
and a new doc label. Calling this function resets the class.
|
||||
"""
|
||||
srcItem = self.theProject.tree[srcHandle]
|
||||
if srcItem is None:
|
||||
return None
|
||||
|
||||
newHandle = self.theProject.newFile(docLabel, srcItem.itemParent)
|
||||
newItem = self.theProject.tree[newHandle]
|
||||
newItem.setLayout(srcItem.itemLayout)
|
||||
newItem.setStatus(srcItem.itemStatus)
|
||||
newItem.setImport(srcItem.itemImport)
|
||||
|
||||
self._targetDoc = newHandle
|
||||
self._targetText = []
|
||||
|
||||
return newHandle
|
||||
|
||||
def appendText(self, srcHandle, addComment, cmtPrefix):
|
||||
"""Append text from an existing document to the text buffer.
|
||||
"""
|
||||
srcItem = self.theProject.tree[srcHandle]
|
||||
if srcItem is None:
|
||||
return False
|
||||
|
||||
inDoc = NWDoc(self.theProject, srcHandle)
|
||||
docText = (inDoc.readDocument() or "").rstrip("\n")
|
||||
|
||||
if addComment:
|
||||
docInfo = srcItem.describeMe("H0")
|
||||
docSt, _ = srcItem.getImportStatus(incIcon=False)
|
||||
cmtLine = f"% {cmtPrefix} {docInfo}: {srcItem.itemName} [{docSt}]\n\n"
|
||||
docText = cmtLine + docText
|
||||
|
||||
self._targetText.append(docText)
|
||||
|
||||
return True
|
||||
|
||||
def writeTargetDoc(self):
|
||||
"""Write the accumulated text into the designated target
|
||||
document, appending any existing text.
|
||||
"""
|
||||
if self._targetDoc is None:
|
||||
return False
|
||||
|
||||
outDoc = NWDoc(self.theProject, self._targetDoc)
|
||||
docText = (outDoc.readDocument() or "").rstrip("\n")
|
||||
if docText:
|
||||
self._targetText.insert(0, docText)
|
||||
|
||||
status = outDoc.writeDocument("\n\n".join(self._targetText) + "\n\n")
|
||||
if not status:
|
||||
self._error = outDoc.getError()
|
||||
|
||||
return status
|
||||
|
||||
# END Class DocMerger
|
||||
@@ -33,7 +33,7 @@ from novelwriter.common import isHandle, sha256sum
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWDoc():
|
||||
class NWDoc:
|
||||
|
||||
def __init__(self, theProject, theHandle):
|
||||
|
||||
@@ -52,7 +52,7 @@ class NWDoc():
|
||||
self._docHandle = theHandle
|
||||
|
||||
if self._docHandle is not None:
|
||||
self._theItem = self.theProject.projTree[theHandle]
|
||||
self._theItem = self.theProject.tree[theHandle]
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -29,14 +29,14 @@ from lxml import etree
|
||||
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.common import (
|
||||
checkInt, isHandle, isItemClass, isItemLayout, isItemType
|
||||
checkInt, isHandle, isItemClass, isItemLayout, isItemType, simplified
|
||||
)
|
||||
from novelwriter.constants import nwLabels, nwLists, trConst
|
||||
from novelwriter.constants import nwLabels, trConst
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWItem():
|
||||
class NWItem:
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
@@ -45,11 +45,13 @@ class NWItem():
|
||||
self._name = ""
|
||||
self._handle = None
|
||||
self._parent = None
|
||||
self._root = None
|
||||
self._order = 0
|
||||
self._type = nwItemType.NO_TYPE
|
||||
self._class = nwItemClass.NO_CLASS
|
||||
self._layout = nwItemLayout.NO_LAYOUT
|
||||
self._status = None
|
||||
self._import = None
|
||||
self._expanded = False
|
||||
self._exported = True
|
||||
|
||||
@@ -84,6 +86,10 @@ class NWItem():
|
||||
def itemParent(self):
|
||||
return self._parent
|
||||
|
||||
@property
|
||||
def itemRoot(self):
|
||||
return self._root
|
||||
|
||||
@property
|
||||
def itemOrder(self):
|
||||
return self._order
|
||||
@@ -104,6 +110,10 @@ class NWItem():
|
||||
def itemStatus(self):
|
||||
return self._status
|
||||
|
||||
@property
|
||||
def itemImport(self):
|
||||
return self._import
|
||||
|
||||
@property
|
||||
def isExpanded(self):
|
||||
return self._expanded
|
||||
@@ -139,24 +149,33 @@ class NWItem():
|
||||
def packXML(self, xParent):
|
||||
"""Pack all the data in the class instance into an XML object.
|
||||
"""
|
||||
xPack = etree.SubElement(xParent, "item", attrib={
|
||||
"handle": str(self._handle),
|
||||
"order": str(self._order),
|
||||
"parent": str(self._parent),
|
||||
})
|
||||
self._subPack(xPack, "name", text=str(self._name))
|
||||
self._subPack(xPack, "type", text=str(self._type.name))
|
||||
self._subPack(xPack, "class", text=str(self._class.name))
|
||||
self._subPack(xPack, "status", text=str(self._status))
|
||||
itemAttrib = {}
|
||||
itemAttrib["handle"] = str(self._handle)
|
||||
itemAttrib["parent"] = str(self._parent)
|
||||
itemAttrib["root"] = str(self._root)
|
||||
itemAttrib["order"] = str(self._order)
|
||||
itemAttrib["type"] = str(self._type.name)
|
||||
itemAttrib["class"] = str(self._class.name)
|
||||
if self._type == nwItemType.FILE:
|
||||
self._subPack(xPack, "exported", text=str(self._exported))
|
||||
self._subPack(xPack, "layout", text=str(self._layout.name))
|
||||
self._subPack(xPack, "charCount", text=str(self._charCount), none=False)
|
||||
self._subPack(xPack, "wordCount", text=str(self._wordCount), none=False)
|
||||
self._subPack(xPack, "paraCount", text=str(self._paraCount), none=False)
|
||||
self._subPack(xPack, "cursorPos", text=str(self._cursorPos), none=False)
|
||||
else:
|
||||
self._subPack(xPack, "expanded", text=str(self._expanded))
|
||||
itemAttrib["layout"] = str(self._layout.name)
|
||||
|
||||
metaAttrib = {}
|
||||
metaAttrib["expanded"] = str(self._expanded)
|
||||
if self._type == nwItemType.FILE:
|
||||
metaAttrib["charCount"] = str(self._charCount)
|
||||
metaAttrib["wordCount"] = str(self._wordCount)
|
||||
metaAttrib["paraCount"] = str(self._paraCount)
|
||||
metaAttrib["cursorPos"] = str(self._cursorPos)
|
||||
|
||||
nameAttrib = {}
|
||||
nameAttrib["status"] = str(self._status)
|
||||
nameAttrib["import"] = str(self._import)
|
||||
if self._type == nwItemType.FILE:
|
||||
nameAttrib["exported"] = str(self._exported)
|
||||
|
||||
xPack = etree.SubElement(xParent, "item", attrib=itemAttrib)
|
||||
self._subPack(xPack, "meta", attrib=metaAttrib)
|
||||
self._subPack(xPack, "name", text=str(self._name), attrib=nameAttrib)
|
||||
|
||||
return
|
||||
|
||||
@@ -174,20 +193,34 @@ class NWItem():
|
||||
return False
|
||||
|
||||
self.setParent(xItem.attrib.get("parent", None))
|
||||
self.setRoot(xItem.attrib.get("root", None))
|
||||
self.setOrder(xItem.attrib.get("order", 0))
|
||||
self.setType(xItem.attrib.get("type", nwItemType.NO_TYPE))
|
||||
self.setClass(xItem.attrib.get("class", nwItemClass.NO_CLASS))
|
||||
self.setLayout(xItem.attrib.get("layout", nwItemLayout.NO_LAYOUT))
|
||||
|
||||
tmpStatus = ""
|
||||
for xValue in xItem:
|
||||
if xValue.tag == "name":
|
||||
if xValue.tag == "meta":
|
||||
self.setExpanded(xValue.attrib.get("expanded", False))
|
||||
self.setCharCount(xValue.attrib.get("charCount", 0))
|
||||
self.setWordCount(xValue.attrib.get("wordCount", 0))
|
||||
self.setParaCount(xValue.attrib.get("paraCount", 0))
|
||||
self.setCursorPos(xValue.attrib.get("cursorPos", 0))
|
||||
elif xValue.tag == "name":
|
||||
self.setName(xValue.text)
|
||||
self.setStatus(xValue.attrib.get("status", None))
|
||||
self.setImport(xValue.attrib.get("import", None))
|
||||
self.setExported(xValue.attrib.get("exported", True))
|
||||
|
||||
# Legacy Format (1.3 and earlier)
|
||||
elif xValue.tag == "status":
|
||||
self.setImportStatus(xValue.text)
|
||||
elif xValue.tag == "type":
|
||||
self.setType(xValue.text)
|
||||
elif xValue.tag == "class":
|
||||
self.setClass(xValue.text)
|
||||
elif xValue.tag == "layout":
|
||||
self.setLayout(xValue.text)
|
||||
elif xValue.tag == "status":
|
||||
tmpStatus = xValue.text
|
||||
elif xValue.tag == "expanded":
|
||||
self.setExpanded(xValue.text)
|
||||
elif xValue.tag == "exported":
|
||||
@@ -206,8 +239,16 @@ class NWItem():
|
||||
# version of novelWriter that doesn't know the tag
|
||||
logger.error("Unknown tag '%s'", xValue.tag)
|
||||
|
||||
# Guarantees that <status> is parsed after <class>
|
||||
self.setStatus(tmpStatus)
|
||||
# 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
|
||||
|
||||
@@ -225,7 +266,7 @@ class NWItem():
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
# Lookup Methods
|
||||
##
|
||||
|
||||
def describeMe(self, hLevel=None):
|
||||
@@ -251,142 +292,245 @@ class NWItem():
|
||||
|
||||
return trConst(nwLabels.ITEM_DESCRIPTION.get(descKey, ""))
|
||||
|
||||
def getImportStatus(self, incIcon=True):
|
||||
"""Return the relevant importance or status label and icon for
|
||||
the current item based on its class.
|
||||
"""
|
||||
if self.isNovelLike():
|
||||
stName = self.theProject.statusItems.name(self._status)
|
||||
stIcon = self.theProject.statusItems.icon(self._status) if incIcon else None
|
||||
else:
|
||||
stName = self.theProject.importItems.name(self._import)
|
||||
stIcon = self.theProject.importItems.icon(self._import) if incIcon else None
|
||||
return stName, stIcon
|
||||
|
||||
##
|
||||
# Checker Methods
|
||||
##
|
||||
|
||||
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 isRootType(self):
|
||||
return self._type == nwItemType.ROOT
|
||||
|
||||
def isFolderType(self):
|
||||
return self._type == nwItemType.FOLDER
|
||||
|
||||
def isFileType(self):
|
||||
return self._type == nwItemType.FILE
|
||||
|
||||
def isNoteLayout(self):
|
||||
return self._layout == nwItemLayout.NOTE
|
||||
|
||||
def isDocumentLayout(self):
|
||||
return self._layout == nwItemLayout.DOCUMENT
|
||||
|
||||
##
|
||||
# Special Setters
|
||||
##
|
||||
|
||||
def setImportStatus(self, value):
|
||||
"""Update the importance or status value based on class. This is
|
||||
a wrapper setter for setStatus and setImport.
|
||||
"""
|
||||
if self.isNovelLike():
|
||||
self.setStatus(value)
|
||||
else:
|
||||
self.setImport(value)
|
||||
return
|
||||
|
||||
def setClassDefaults(self, itemClass):
|
||||
"""Set the default values based on the item's class and the
|
||||
project settings.
|
||||
"""
|
||||
if self._parent is not None:
|
||||
# Only update for child items
|
||||
self.setClass(itemClass)
|
||||
|
||||
if self._layout == nwItemLayout.NO_LAYOUT:
|
||||
# If no layout is set, pick one
|
||||
if self.isNovelLike():
|
||||
self._layout = nwItemLayout.DOCUMENT
|
||||
else:
|
||||
self._layout = nwItemLayout.NOTE
|
||||
elif not self.documentAllowed():
|
||||
# Change layout to note if it is not in an allowed folder
|
||||
self._layout = nwItemLayout.NOTE
|
||||
|
||||
if self._status is None:
|
||||
self.setStatus("New") # This forces a default value lookup
|
||||
|
||||
if self._import is None:
|
||||
self.setImport("New") # This forces a default value lookup
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Set Item Values
|
||||
##
|
||||
|
||||
def setName(self, theName):
|
||||
def setName(self, name):
|
||||
"""Set the item name.
|
||||
"""
|
||||
if isinstance(theName, str):
|
||||
self._name = theName.strip()
|
||||
if isinstance(name, str):
|
||||
self._name = simplified(name)
|
||||
else:
|
||||
self._name = ""
|
||||
return
|
||||
|
||||
def setHandle(self, theHandle):
|
||||
def setHandle(self, handle):
|
||||
"""Set the item handle, and ensure it is valid.
|
||||
"""
|
||||
if isHandle(theHandle):
|
||||
self._handle = theHandle
|
||||
if isHandle(handle):
|
||||
self._handle = handle
|
||||
else:
|
||||
self._handle = None
|
||||
return
|
||||
|
||||
def setParent(self, theParent):
|
||||
def setParent(self, handle):
|
||||
"""Set the parent handle, and ensure it is valid.
|
||||
"""
|
||||
if theParent is None:
|
||||
if handle is None:
|
||||
self._parent = None
|
||||
elif isHandle(theParent):
|
||||
self._parent = theParent
|
||||
elif isHandle(handle):
|
||||
self._parent = handle
|
||||
else:
|
||||
self._parent = None
|
||||
return
|
||||
|
||||
def setOrder(self, theOrder):
|
||||
def setRoot(self, handle):
|
||||
"""Set the root handle, and ensure it is valid.
|
||||
"""
|
||||
if handle is None:
|
||||
self._root = None
|
||||
elif isHandle(handle):
|
||||
self._root = handle
|
||||
else:
|
||||
self._root = None
|
||||
return
|
||||
|
||||
def setOrder(self, order):
|
||||
"""Set the item order, and ensure that it is valid. This value
|
||||
is purely a meta value, and not actually used by novelWriter at
|
||||
the moment.
|
||||
"""
|
||||
self._order = checkInt(theOrder, 0)
|
||||
self._order = checkInt(order, 0)
|
||||
return
|
||||
|
||||
def setType(self, theType):
|
||||
def setType(self, value):
|
||||
"""Set the item type from either a proper nwItemType, or set it
|
||||
from a string representing an nwItemType.
|
||||
"""
|
||||
if isinstance(theType, nwItemType):
|
||||
self._type = theType
|
||||
elif isItemType(theType):
|
||||
self._type = nwItemType[theType]
|
||||
if isinstance(value, nwItemType):
|
||||
self._type = value
|
||||
elif isItemType(value):
|
||||
self._type = nwItemType[value]
|
||||
elif value == "TRASH":
|
||||
self._type = nwItemType.ROOT
|
||||
else:
|
||||
logger.error("Unrecognised item type '%s'", theType)
|
||||
logger.error("Unrecognised item type '%s'", value)
|
||||
self._type = nwItemType.NO_TYPE
|
||||
return
|
||||
|
||||
def setClass(self, theClass):
|
||||
def setClass(self, value):
|
||||
"""Set the item class from either a proper nwItemClass, or set
|
||||
it from a string representing an nwItemClass.
|
||||
"""
|
||||
if isinstance(theClass, nwItemClass):
|
||||
self._class = theClass
|
||||
elif isItemClass(theClass):
|
||||
self._class = nwItemClass[theClass]
|
||||
if isinstance(value, nwItemClass):
|
||||
self._class = value
|
||||
elif isItemClass(value):
|
||||
self._class = nwItemClass[value]
|
||||
else:
|
||||
logger.error("Unrecognised item class '%s'", theClass)
|
||||
logger.error("Unrecognised item class '%s'", value)
|
||||
self._class = nwItemClass.NO_CLASS
|
||||
return
|
||||
|
||||
def setLayout(self, theLayout):
|
||||
def setLayout(self, value):
|
||||
"""Set the item layout from either a proper nwItemLayout, or set
|
||||
it from a string representing an nwItemLayout.
|
||||
"""
|
||||
if isinstance(theLayout, nwItemLayout):
|
||||
self._layout = theLayout
|
||||
elif isItemLayout(theLayout):
|
||||
self._layout = nwItemLayout[theLayout]
|
||||
elif theLayout in nwLists.DEP_LAYOUT:
|
||||
if isinstance(value, nwItemLayout):
|
||||
self._layout = value
|
||||
elif isItemLayout(value):
|
||||
self._layout = nwItemLayout[value]
|
||||
elif value in ("TITLE", "PAGE", "BOOK", "PARTITION", "UNNUMBERED", "CHAPTER", "SCENE"):
|
||||
self._layout = nwItemLayout.DOCUMENT
|
||||
else:
|
||||
logger.error("Unrecognised item layout '%s'", theLayout)
|
||||
logger.error("Unrecognised item layout '%s'", value)
|
||||
self._layout = nwItemLayout.NO_LAYOUT
|
||||
return
|
||||
|
||||
def setStatus(self, theStatus):
|
||||
def setStatus(self, value):
|
||||
"""Set the item status by looking it up in the valid status
|
||||
items of the current project.
|
||||
"""
|
||||
if self._class in nwLists.CLS_NOVEL:
|
||||
self._status = self.theProject.statusItems.checkEntry(theStatus)
|
||||
else:
|
||||
self._status = self.theProject.importItems.checkEntry(theStatus)
|
||||
self._status = self.theProject.statusItems.check(value)
|
||||
return
|
||||
|
||||
def setExpanded(self, expState):
|
||||
def setImport(self, value):
|
||||
"""Set the item importance by looking it up in the valid import
|
||||
items of the current project.
|
||||
"""
|
||||
self._import = self.theProject.importItems.check(value)
|
||||
return
|
||||
|
||||
def setExpanded(self, state):
|
||||
"""Set the expanded status of an item in the project tree.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self._expanded = (expState == str(True))
|
||||
if isinstance(state, str):
|
||||
self._expanded = (state == str(True))
|
||||
else:
|
||||
self._expanded = (expState is True)
|
||||
self._expanded = (state is True)
|
||||
return
|
||||
|
||||
def setExported(self, expState):
|
||||
def setExported(self, state):
|
||||
"""Set the export flag.
|
||||
"""
|
||||
if isinstance(expState, str):
|
||||
self._exported = (expState == str(True))
|
||||
if isinstance(state, str):
|
||||
self._exported = (state == str(True))
|
||||
else:
|
||||
self._exported = (expState is True)
|
||||
self._exported = (state is True)
|
||||
return
|
||||
|
||||
##
|
||||
# Set Document Meta Data
|
||||
##
|
||||
|
||||
def setCharCount(self, theCount):
|
||||
def setCharCount(self, count):
|
||||
"""Set the character count, and ensure that it is an integer.
|
||||
"""
|
||||
self._charCount = max(0, checkInt(theCount, 0))
|
||||
self._charCount = max(0, checkInt(count, 0))
|
||||
return
|
||||
|
||||
def setWordCount(self, theCount):
|
||||
def setWordCount(self, count):
|
||||
"""Set the word count, and ensure that it is an integer.
|
||||
"""
|
||||
self._wordCount = max(0, checkInt(theCount, 0))
|
||||
self._wordCount = max(0, checkInt(count, 0))
|
||||
return
|
||||
|
||||
def setParaCount(self, theCount):
|
||||
def setParaCount(self, count):
|
||||
"""Set the paragraph count, and ensure that it is an integer.
|
||||
"""
|
||||
self._paraCount = max(0, checkInt(theCount, 0))
|
||||
self._paraCount = max(0, checkInt(count, 0))
|
||||
return
|
||||
|
||||
def setCursorPos(self, thePosition):
|
||||
def setCursorPos(self, position):
|
||||
"""Set the cursor position, and ensure that it is an integer.
|
||||
"""
|
||||
self._cursorPos = max(0, checkInt(thePosition, 0))
|
||||
self._cursorPos = max(0, checkInt(position, 0))
|
||||
return
|
||||
|
||||
def saveInitialCount(self):
|
||||
|
||||
@@ -28,6 +28,8 @@ import os
|
||||
import json
|
||||
import logging
|
||||
|
||||
from enum import Enum
|
||||
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import checkBool, checkFloat, checkInt, checkString
|
||||
from novelwriter.constants import nwFiles
|
||||
@@ -56,11 +58,12 @@ VALID_MAP = {
|
||||
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
|
||||
"widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble"
|
||||
},
|
||||
"GuiWordList": {"winWidth", "winHeight"}
|
||||
"GuiWordList": {"winWidth", "winHeight"},
|
||||
"GuiNovelView": {"lastCol"},
|
||||
}
|
||||
|
||||
|
||||
class OptionState():
|
||||
class OptionState:
|
||||
|
||||
def __init__(self, theProject):
|
||||
self.theProject = theProject
|
||||
@@ -137,7 +140,10 @@ class OptionState():
|
||||
if group not in self._theState:
|
||||
self._theState[group] = {}
|
||||
|
||||
self._theState[group][name] = value
|
||||
if isinstance(value, Enum):
|
||||
self._theState[group][name] = value.name
|
||||
else:
|
||||
self._theState[group][name] = value
|
||||
|
||||
return True
|
||||
|
||||
@@ -186,4 +192,16 @@ class OptionState():
|
||||
return checkBool(self._theState[group].get(name, default), default)
|
||||
return default
|
||||
|
||||
def getEnum(self, group, name, lookup, default):
|
||||
"""Return the value mapped to an enum. Otherwise return the
|
||||
default value
|
||||
"""
|
||||
if issubclass(lookup, Enum):
|
||||
if group in self._theState:
|
||||
if name in self._theState[group]:
|
||||
value = self._theState[group][name]
|
||||
if value in lookup.__members__:
|
||||
return lookup[value]
|
||||
return default
|
||||
|
||||
# END Class OptionState
|
||||
|
||||
@@ -33,7 +33,7 @@ from novelwriter.error import logException
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWSpellEnchant():
|
||||
class NWSpellEnchant:
|
||||
|
||||
def __init__(self):
|
||||
|
||||
|
||||
@@ -4,7 +4,8 @@ novelWriter – Project Item Status Class
|
||||
Data class for the status/importance settings of a project item
|
||||
|
||||
File History:
|
||||
Created: 2019-05-19 [0.1.3]
|
||||
Created: 2019-05-19 [0.1.3]
|
||||
Rewritten: 2022-04-05 [1.7a0]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
||||
@@ -23,165 +24,269 @@ You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import random
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from lxml import etree
|
||||
|
||||
from novelwriter.common import checkInt
|
||||
from PyQt5.QtGui import QIcon, QPixmap, QColor
|
||||
|
||||
from novelwriter.common import checkInt, minmax, simplified
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWStatus():
|
||||
class NWStatus:
|
||||
|
||||
def __init__(self):
|
||||
STATUS = 1
|
||||
IMPORT = 2
|
||||
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
def __init__(self, type):
|
||||
|
||||
self._type = type
|
||||
self._store = {}
|
||||
self._reverse = {}
|
||||
self._default = None
|
||||
|
||||
self._iconSize = novelwriter.CONFIG.pxInt(32)
|
||||
pixmap = QPixmap(self._iconSize, self._iconSize)
|
||||
pixmap.fill(QColor(100, 100, 100))
|
||||
self._defaultIcon = QIcon(pixmap)
|
||||
|
||||
if self._type == self.STATUS:
|
||||
self._prefix = "s"
|
||||
elif self._type == self.IMPORT:
|
||||
self._prefix = "i"
|
||||
else:
|
||||
raise Exception("This is a bug!")
|
||||
|
||||
return
|
||||
|
||||
def addEntry(self, theLabel, theColours):
|
||||
"""Add a status entry to the status object, but ensure it isn't
|
||||
a duplicate.
|
||||
def write(self, key, name, cols, count=None):
|
||||
"""Add or update a status entry. If the key is invalid, a new
|
||||
key is generated.
|
||||
"""
|
||||
theLabel = theLabel.strip()
|
||||
if self.lookupEntry(theLabel) is None:
|
||||
self._theLabels.append(theLabel)
|
||||
self._theColours.append(theColours)
|
||||
self._theCounts.append(0)
|
||||
self._theMap[theLabel] = self._theLength
|
||||
self._theLength += 1
|
||||
if not self._isKey(key):
|
||||
key = self._newKey()
|
||||
if not isinstance(cols, tuple):
|
||||
cols = (100, 100, 100)
|
||||
if len(cols) != 3:
|
||||
cols = (100, 100, 100)
|
||||
|
||||
pixmap = QPixmap(self._iconSize, self._iconSize)
|
||||
pixmap.fill(QColor(*cols))
|
||||
|
||||
name = simplified(name)
|
||||
if count is None:
|
||||
count = self._store[key]["count"] if key in self._store else 0
|
||||
|
||||
self._store[key] = {
|
||||
"name": name,
|
||||
"icon": QIcon(pixmap),
|
||||
"cols": cols,
|
||||
"count": count,
|
||||
}
|
||||
self._reverse[name] = key
|
||||
|
||||
if self._default is None:
|
||||
self._default = key
|
||||
|
||||
return key
|
||||
|
||||
def remove(self, key):
|
||||
"""Remove an entry in the list, but not if the count is larger
|
||||
than 0.
|
||||
"""
|
||||
if key not in self._store:
|
||||
return False
|
||||
if self._store[key]["count"] > 0:
|
||||
return False
|
||||
|
||||
del self._reverse[self._store[key]["name"]]
|
||||
del self._store[key]
|
||||
|
||||
keys = list(self._store.keys())
|
||||
if key == self._default:
|
||||
if len(keys) > 0:
|
||||
self._default = keys[0]
|
||||
else:
|
||||
self._default = None
|
||||
|
||||
return True
|
||||
|
||||
def lookupEntry(self, theLabel):
|
||||
"""Look up a status entry in the object lists, and return it if
|
||||
it exists.
|
||||
def check(self, value):
|
||||
"""Check the key against the stored status names.
|
||||
"""
|
||||
if theLabel is None:
|
||||
return None
|
||||
theLabel = theLabel.strip()
|
||||
if theLabel in self._theMap.keys():
|
||||
return self._theMap[theLabel]
|
||||
return None
|
||||
if self._isKey(value) and value in self._store:
|
||||
return value
|
||||
elif value in self._reverse:
|
||||
return self._reverse[value]
|
||||
elif self._default is not None:
|
||||
return self._default
|
||||
else:
|
||||
return ""
|
||||
|
||||
def checkEntry(self, theStatus):
|
||||
"""Check if a status value is valid, and returns the safe
|
||||
reference to be used internally.
|
||||
def name(self, key):
|
||||
"""Return the name associated with a given key.
|
||||
"""
|
||||
if isinstance(theStatus, str):
|
||||
theStatus = theStatus.strip()
|
||||
if self.lookupEntry(theStatus) is not None:
|
||||
return theStatus
|
||||
theStatus = checkInt(theStatus, 0, False)
|
||||
if theStatus >= 0 and theStatus < self._theLength:
|
||||
return self._theLabels[theStatus]
|
||||
return self._theLabels[0]
|
||||
if key in self._store:
|
||||
return self._store[key]["name"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["name"]
|
||||
else:
|
||||
return ""
|
||||
|
||||
def setNewEntries(self, newList):
|
||||
"""Update the list of entries after they have been modified by
|
||||
the GUI tool.
|
||||
def cols(self, key):
|
||||
"""Return the colours associated with a given key.
|
||||
"""
|
||||
replaceMap = {}
|
||||
if key in self._store:
|
||||
return self._store[key]["cols"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["cols"]
|
||||
else:
|
||||
return (100, 100, 100)
|
||||
|
||||
if newList is not None:
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
def count(self, key):
|
||||
"""Return the count associated with a given key.
|
||||
"""
|
||||
if key in self._store:
|
||||
return self._store[key]["count"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["count"]
|
||||
else:
|
||||
return 0
|
||||
|
||||
for nName, nR, nG, nB, oName in newList:
|
||||
self.addEntry(nName, (nR, nG, nB))
|
||||
if nName != oName and oName is not None:
|
||||
replaceMap[oName] = nName
|
||||
def icon(self, key):
|
||||
"""Return the icon associated with a given key.
|
||||
"""
|
||||
if key in self._store:
|
||||
return self._store[key]["icon"]
|
||||
elif self._default is not None:
|
||||
return self._store[self._default]["icon"]
|
||||
else:
|
||||
return self._defaultIcon
|
||||
|
||||
return replaceMap
|
||||
def reorder(self, order):
|
||||
"""Reorder the items according to list.
|
||||
"""
|
||||
if len(order) != len(self._store):
|
||||
logger.error("Length mismatch between new and old order")
|
||||
return False
|
||||
|
||||
if order == list(self._store.keys()):
|
||||
return False
|
||||
|
||||
store = {}
|
||||
for key in order:
|
||||
if key in self._store:
|
||||
store[key] = self._store[key]
|
||||
else:
|
||||
logger.error("Unknown key '%s' in order", key)
|
||||
return False
|
||||
|
||||
self._store = store
|
||||
|
||||
return True
|
||||
|
||||
def resetCounts(self):
|
||||
"""Clear the counts of references to the status entries.
|
||||
"""
|
||||
self._theCounts = [0]*self._theLength
|
||||
for key in self._store:
|
||||
self._store[key]["count"] = 0
|
||||
return
|
||||
|
||||
def countEntry(self, theLabel):
|
||||
"""Increment the counter for a given label. This should be used
|
||||
together with resetCounts in a loop over project items.
|
||||
def increment(self, key):
|
||||
"""Increment the counter for a given entry.
|
||||
"""
|
||||
theIndex = self.lookupEntry(theLabel)
|
||||
if theIndex is not None:
|
||||
self._theCounts[theIndex] += 1
|
||||
if key in self._store:
|
||||
self._store[key]["count"] += 1
|
||||
return
|
||||
|
||||
def packXML(self, xParent):
|
||||
"""Pack the status entries into an XML object for saving to the
|
||||
main project file.
|
||||
"""
|
||||
for n in range(self._theLength):
|
||||
for key, data in self._store.items():
|
||||
xSub = etree.SubElement(xParent, "entry", attrib={
|
||||
"blue": str(self._theColours[n][2]),
|
||||
"green": str(self._theColours[n][1]),
|
||||
"red": str(self._theColours[n][0]),
|
||||
"key": key,
|
||||
"count": str(data["count"]),
|
||||
"red": str(data["cols"][0]),
|
||||
"green": str(data["cols"][1]),
|
||||
"blue": str(data["cols"][2]),
|
||||
})
|
||||
xSub.text = self._theLabels[n]
|
||||
xSub.text = data["name"]
|
||||
|
||||
return True
|
||||
|
||||
def unpackXML(self, xParent):
|
||||
"""Unpack an XML tree and set the class values.
|
||||
"""
|
||||
theLabels = []
|
||||
theColours = []
|
||||
self._store = {}
|
||||
self._reverse = {}
|
||||
self._default = None
|
||||
|
||||
for xChild in xParent:
|
||||
theLabels.append(xChild.text)
|
||||
cR = checkInt(xChild.attrib.get("red", 0), 0, False)
|
||||
cG = checkInt(xChild.attrib.get("green", 0), 0, False)
|
||||
cB = checkInt(xChild.attrib.get("blue", 0), 0, False)
|
||||
theColours.append((cR, cG, cB))
|
||||
key = xChild.attrib.get("key", None)
|
||||
name = xChild.text.strip()
|
||||
count = max(checkInt(xChild.attrib.get("count", 0), 0), 0)
|
||||
red = minmax(checkInt(xChild.attrib.get("red", 100), 100), 0, 255)
|
||||
green = minmax(checkInt(xChild.attrib.get("green", 100), 100), 0, 255)
|
||||
blue = minmax(checkInt(xChild.attrib.get("blue", 100), 100), 0, 255)
|
||||
self.write(key, name, (red, green, blue), count)
|
||||
|
||||
if len(theLabels) > 0:
|
||||
self._theLabels = []
|
||||
self._theColours = []
|
||||
self._theCounts = []
|
||||
self._theMap = {}
|
||||
self._theLength = 0
|
||||
self._theIndex = 0
|
||||
return True
|
||||
|
||||
for n in range(len(theLabels)):
|
||||
self.addEntry(theLabels[n], theColours[n])
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _newKey(self):
|
||||
"""Generate a new key for a status flag. This method is
|
||||
recursive, but should only fail if there is an issue with the
|
||||
random number generator or the user has added a lot of status
|
||||
flags. The Python recursion limit is given the job to handle
|
||||
the extreme case and will cause an app crash.
|
||||
"""
|
||||
key = f"{self._prefix}{random.getrandbits(24):06x}"
|
||||
if key in self._store:
|
||||
key = self._newKey()
|
||||
return key
|
||||
|
||||
def _isKey(self, value):
|
||||
"""Check if a value is a key or not.
|
||||
"""
|
||||
if not isinstance(value, str):
|
||||
return False
|
||||
if len(value) != 7:
|
||||
return False
|
||||
if value[0] != self._prefix:
|
||||
return False
|
||||
for c in value[1:]:
|
||||
if c not in "0123456789abcdef":
|
||||
return False
|
||||
return True
|
||||
|
||||
##
|
||||
# Iterator Bits
|
||||
##
|
||||
|
||||
def __getitem__(self, n):
|
||||
"""Return an entry by its index.
|
||||
"""
|
||||
if n >= 0 and n < self._theLength:
|
||||
return self._theLabels[n], self._theColours[n], self._theCounts[n]
|
||||
return None, None, None
|
||||
def __len__(self):
|
||||
return len(self._store)
|
||||
|
||||
def __getitem__(self, key):
|
||||
return self._store[key]
|
||||
|
||||
def __iter__(self):
|
||||
"""Initialise the iterator.
|
||||
"""
|
||||
self._theIndex = 0
|
||||
return self
|
||||
return iter(self._store)
|
||||
|
||||
def __next__(self):
|
||||
"""Return the next entry for the iterator.
|
||||
"""
|
||||
if self._theIndex < self._theLength:
|
||||
theLabel, theColour, theCount = self.__getitem__(self._theIndex)
|
||||
self._theIndex += 1
|
||||
return theLabel, theColour, theCount
|
||||
else:
|
||||
raise StopIteration
|
||||
def keys(self):
|
||||
return self._store.keys()
|
||||
|
||||
def items(self):
|
||||
return self._store.items()
|
||||
|
||||
def values(self):
|
||||
return self._store.values()
|
||||
|
||||
# END Class NWStatus
|
||||
|
||||
@@ -38,7 +38,7 @@ class ToHtml(Tokenizer):
|
||||
M_EBOOK = 2 # Tweak output for converting to epub
|
||||
|
||||
def __init__(self, theProject):
|
||||
Tokenizer.__init__(self, theProject)
|
||||
super().__init__(theProject)
|
||||
|
||||
self._genMode = self.M_EXPORT
|
||||
self._cssStyles = True
|
||||
@@ -107,7 +107,7 @@ class ToHtml(Tokenizer):
|
||||
"""Extend the auto-replace to also properly encode some unicode
|
||||
characters into their respective HTML entities.
|
||||
"""
|
||||
Tokenizer.doPreProcessing(self)
|
||||
super().doPreProcessing()
|
||||
self._theText = self._theText.translate(self._trMap)
|
||||
return
|
||||
|
||||
@@ -451,7 +451,7 @@ class ToHtml(Tokenizer):
|
||||
def _formatKeywords(self, tText):
|
||||
"""Apply HTML formatting to keywords.
|
||||
"""
|
||||
isValid, theBits, _ = self.theParent.theIndex.scanThis("@"+tText)
|
||||
isValid, theBits, _ = self.theProject.index.scanThis("@"+tText)
|
||||
if not isValid or not theBits:
|
||||
return ""
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"""
|
||||
novelWriter – Text Tokenizer
|
||||
============================
|
||||
Splits a piece of novelWriter markdown text into its elements
|
||||
Split novelWriter plain text into its elements
|
||||
|
||||
File History:
|
||||
Created: 2019-05-05 [0.0.1]
|
||||
@@ -27,6 +27,7 @@ import re
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from operator import itemgetter
|
||||
from functools import partial
|
||||
|
||||
@@ -40,7 +41,7 @@ from novelwriter.core.document import NWDoc
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class Tokenizer():
|
||||
class Tokenizer(ABC):
|
||||
|
||||
# In-Text Format
|
||||
FMT_B_B = 1 # Begin bold
|
||||
@@ -81,7 +82,6 @@ class Tokenizer():
|
||||
def __init__(self, theProject):
|
||||
|
||||
self.theProject = theProject
|
||||
self.theParent = theProject.theParent
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
|
||||
# Data Variables
|
||||
@@ -267,10 +267,14 @@ class Tokenizer():
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
@abstractmethod
|
||||
def doConvert(self):
|
||||
raise NotImplementedError
|
||||
|
||||
def addRootHeading(self, theHandle):
|
||||
"""Add a heading at the start of a new root folder.
|
||||
"""
|
||||
if not self.theProject.projTree.checkType(theHandle, nwItemType.ROOT):
|
||||
if not self.theProject.tree.checkType(theHandle, nwItemType.ROOT):
|
||||
return False
|
||||
|
||||
if self._isFirst:
|
||||
@@ -279,7 +283,7 @@ class Tokenizer():
|
||||
else:
|
||||
textAlign = self.A_PBB | self.A_CENTRE
|
||||
|
||||
theItem = self.theProject.projTree[theHandle]
|
||||
theItem = self.theProject.tree[theHandle]
|
||||
locNotes = self._localLookup("Notes")
|
||||
theTitle = f"{locNotes}: {theItem.itemName}"
|
||||
self._theTokens = []
|
||||
@@ -296,7 +300,7 @@ class Tokenizer():
|
||||
not set, load it from the file.
|
||||
"""
|
||||
self._theHandle = theHandle
|
||||
self._theItem = self.theProject.projTree[theHandle]
|
||||
self._theItem = self.theProject.tree[theHandle]
|
||||
if self._theItem is None:
|
||||
return False
|
||||
|
||||
|
||||
@@ -37,7 +37,7 @@ class ToMarkdown(Tokenizer):
|
||||
M_GH = 1 # GitHub Markdown
|
||||
|
||||
def __init__(self, theProject):
|
||||
Tokenizer.__init__(self, theProject)
|
||||
super().__init__(theProject)
|
||||
|
||||
self._genMode = self.M_STD
|
||||
self._fullMD = []
|
||||
@@ -193,7 +193,7 @@ class ToMarkdown(Tokenizer):
|
||||
def _formatKeywords(self, tText, tStyle):
|
||||
"""Apply Markdown formatting to keywords.
|
||||
"""
|
||||
isValid, theBits, _ = self.theParent.theIndex.scanThis("@"+tText)
|
||||
isValid, theBits, _ = self.theProject.index.scanThis("@"+tText)
|
||||
if not isValid or not theBits:
|
||||
return ""
|
||||
|
||||
|
||||
@@ -89,7 +89,7 @@ M_DEL = ~X_DEL
|
||||
class ToOdt(Tokenizer):
|
||||
|
||||
def __init__(self, theProject, isFlat):
|
||||
Tokenizer.__init__(self, theProject)
|
||||
super().__init__(theProject)
|
||||
|
||||
self._isFlat = isFlat # Flat: .fodt, otherwise .odt
|
||||
|
||||
@@ -330,7 +330,7 @@ class ToOdt(Tokenizer):
|
||||
|
||||
# Meta Data
|
||||
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "creation-date"))
|
||||
xMeta.text = datetime.now().strftime(r"%Y-%m-%dT%H:%M:%S")
|
||||
xMeta.text = datetime.now().isoformat(sep="T", timespec="seconds")
|
||||
|
||||
xMeta = etree.SubElement(self._xMeta, _mkTag("meta", "generator"))
|
||||
xMeta.text = f"novelWriter/{novelwriter.__version__}"
|
||||
@@ -550,7 +550,7 @@ class ToOdt(Tokenizer):
|
||||
def _formatKeywords(self, tText):
|
||||
"""Apply formatting to keywords.
|
||||
"""
|
||||
isValid, theBits, _ = self.theParent.theIndex.scanThis("@"+tText)
|
||||
isValid, theBits, _ = self.theProject.index.scanThis("@"+tText)
|
||||
if not isValid or not theBits:
|
||||
return ""
|
||||
|
||||
@@ -994,7 +994,7 @@ class ToOdt(Tokenizer):
|
||||
# Auto-Style Classes
|
||||
# =============================================================================================== #
|
||||
|
||||
class ODTParagraphStyle():
|
||||
class ODTParagraphStyle:
|
||||
"""Wrapper class for the paragraph style setting used by the
|
||||
exporter. Only the used settings are exposed here to keep the class
|
||||
minimal and fast.
|
||||
@@ -1208,7 +1208,7 @@ class ODTParagraphStyle():
|
||||
# END Class ODTParagraphStyle
|
||||
|
||||
|
||||
class ODTTextStyle():
|
||||
class ODTTextStyle:
|
||||
"""Wrapper class for the text style setting used by the exporter.
|
||||
Only the used settings are exposed here to keep the class minimal
|
||||
and fast.
|
||||
@@ -1297,7 +1297,7 @@ X_SPAN_TEXT = 2
|
||||
X_SPAN_SING = 3
|
||||
|
||||
|
||||
class XMLParagraph():
|
||||
class XMLParagraph:
|
||||
"""This is a helper class to manage the text content of a single
|
||||
XML element using mixed content tags.
|
||||
|
||||
|
||||
@@ -24,22 +24,23 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import os
|
||||
import random
|
||||
import logging
|
||||
|
||||
from time import time
|
||||
from lxml import etree
|
||||
from hashlib import sha256
|
||||
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.common import checkHandle
|
||||
from novelwriter.constants import nwConst, nwFiles
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.core.item import NWItem
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NWTree():
|
||||
class NWTree:
|
||||
|
||||
MAX_DEPTH = 1000 # Cap of tree traversing for loops
|
||||
|
||||
def __init__(self, theProject):
|
||||
|
||||
@@ -47,15 +48,12 @@ class NWTree():
|
||||
|
||||
self._projTree = {} # Holds all the items of the project
|
||||
self._treeOrder = [] # The order of the tree items on the tree view
|
||||
self._treeRoots = [] # The root items of the tree
|
||||
self._treeRoots = {} # The root items of the tree
|
||||
self._trashRoot = None # The handle of the trash root folder
|
||||
self._archRoot = None # The handle of the archive root folder
|
||||
self._theIndex = 0 # The current iterator index
|
||||
self._treeChanged = False # True if tree structure has changed
|
||||
|
||||
self._handleSeed = None # Used for generating handles for testing
|
||||
self._handleCount = 0 # A counter that is added to the handle generator
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
@@ -67,7 +65,7 @@ class NWTree():
|
||||
"""
|
||||
self._projTree = {}
|
||||
self._treeOrder = []
|
||||
self._treeRoots = []
|
||||
self._treeRoots = {}
|
||||
self._trashRoot = None
|
||||
self._archRoot = None
|
||||
self._theIndex = 0
|
||||
@@ -96,20 +94,19 @@ class NWTree():
|
||||
nwItem.setHandle(tHandle)
|
||||
nwItem.setParent(pHandle)
|
||||
|
||||
if nwItem.itemType == nwItemType.ROOT:
|
||||
if nwItem.isRootType():
|
||||
logger.verbose("Item '%s' is a root item", str(tHandle))
|
||||
self._treeRoots.append(tHandle)
|
||||
self._treeRoots[tHandle] = nwItem
|
||||
if nwItem.itemClass == nwItemClass.ARCHIVE:
|
||||
logger.verbose("Item '%s' is the archive folder", str(tHandle))
|
||||
self._archRoot = tHandle
|
||||
|
||||
if nwItem.itemType == nwItemType.TRASH:
|
||||
if self._trashRoot is None:
|
||||
logger.verbose("Item '%s' is the trash folder", str(tHandle))
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
return False
|
||||
elif nwItem.itemClass == nwItemClass.TRASH:
|
||||
if self._trashRoot is None:
|
||||
logger.verbose("Item '%s' is the trash folder", str(tHandle))
|
||||
self._trashRoot = tHandle
|
||||
else:
|
||||
logger.error("Only one trash folder allowed")
|
||||
return False
|
||||
|
||||
self._projTree[tHandle] = nwItem
|
||||
self._treeOrder.append(tHandle)
|
||||
@@ -207,9 +204,30 @@ class NWTree():
|
||||
return novelWords, noteWords
|
||||
|
||||
##
|
||||
# Tree Structure Methods
|
||||
# Tree Item Methods
|
||||
##
|
||||
|
||||
def updateItemData(self, tHandle):
|
||||
"""Update the root item handle of a given item. Returns True if
|
||||
a root was found and data updated, otherwise False.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return False
|
||||
|
||||
iItem = tItem
|
||||
for _ in range(self.MAX_DEPTH):
|
||||
if iItem.itemParent is None:
|
||||
tItem.setRoot(iItem.itemHandle)
|
||||
tItem.setClassDefaults(iItem.itemClass)
|
||||
return True
|
||||
else:
|
||||
iItem = self.__getitem__(iItem.itemParent)
|
||||
if iItem is None:
|
||||
return False
|
||||
else:
|
||||
raise RecursionError("Critical internal error")
|
||||
|
||||
def checkType(self, tHandle, itemType):
|
||||
"""Return true of item exists and is of the specified item type.
|
||||
"""
|
||||
@@ -218,71 +236,6 @@ class NWTree():
|
||||
return False
|
||||
return tItem.itemType == itemType
|
||||
|
||||
def trashRoot(self):
|
||||
"""Returns the handle of the trash folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._trashRoot:
|
||||
return self._trashRoot
|
||||
return None
|
||||
|
||||
def isTrashRoot(self, tHandle):
|
||||
"""Check if a handle is the trash folder.
|
||||
"""
|
||||
if self._trashRoot is None:
|
||||
return False
|
||||
return tHandle == self._trashRoot
|
||||
|
||||
def archiveRoot(self):
|
||||
"""Returns the handle of the archive folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._archRoot:
|
||||
return self._archRoot
|
||||
return None
|
||||
|
||||
def findRoot(self, theClass):
|
||||
"""Find the root item for a given class.
|
||||
Note: This returns the first item for class CUSTOM.
|
||||
"""
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return tItem.itemHandle
|
||||
return None
|
||||
|
||||
def checkRootUnique(self, theClass):
|
||||
"""Checks if there already is a root entry of class 'theClass'
|
||||
in the root of the project tree. CUSTOM class is skipped as it
|
||||
is not required to be unique.
|
||||
"""
|
||||
if theClass == nwItemClass.CUSTOM:
|
||||
return True
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return False
|
||||
return True
|
||||
|
||||
def getRootItem(self, tHandle):
|
||||
"""Iterate upwards in the tree until we find the item with
|
||||
parent None, the root item. We do this with a for loop with a
|
||||
maximum depth to make infinite loops impossible.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
for i in range(nwConst.MAX_DEPTH + 1):
|
||||
if tItem.itemParent is None:
|
||||
return tItem
|
||||
else:
|
||||
tHandle = tItem.itemParent
|
||||
tItem = self.__getitem__(tHandle)
|
||||
return None
|
||||
|
||||
def getItemPath(self, tHandle):
|
||||
"""Iterate upwards in the tree until we find the item with
|
||||
parent None, the root item, and return the list of handles.
|
||||
@@ -293,7 +246,7 @@ class NWTree():
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is not None:
|
||||
tTree.append(tHandle)
|
||||
for _ in range(nwConst.MAX_DEPTH + 1):
|
||||
for _ in range(self.MAX_DEPTH):
|
||||
if tItem.itemParent is None:
|
||||
return tTree
|
||||
else:
|
||||
@@ -303,8 +256,72 @@ class NWTree():
|
||||
return tTree
|
||||
else:
|
||||
tTree.append(tHandle)
|
||||
else:
|
||||
raise RecursionError("Critical internal error")
|
||||
|
||||
return tTree
|
||||
|
||||
##
|
||||
# Tree Root Methods
|
||||
##
|
||||
|
||||
def rootClasses(self):
|
||||
"""Return a set of all root classes in use by the project.
|
||||
"""
|
||||
rootClasses = set()
|
||||
for nwItem in self._treeRoots.values():
|
||||
rootClasses.add(nwItem.itemClass)
|
||||
return rootClasses
|
||||
|
||||
def iterRoots(self, itemClass):
|
||||
"""Iterate over all items of a given class.
|
||||
"""
|
||||
for tHandle, nwItem in self._treeRoots.items():
|
||||
if nwItem.itemClass == itemClass:
|
||||
yield tHandle, nwItem
|
||||
return
|
||||
|
||||
def isRoot(self, tHandle):
|
||||
"""Check if a handle is a root item.
|
||||
"""
|
||||
return tHandle in self._treeRoots
|
||||
|
||||
def isTrash(self, tHandle):
|
||||
"""Check if an item is in or is the trash folder.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return True
|
||||
if tItem.itemClass == nwItemClass.TRASH:
|
||||
return True
|
||||
if self._trashRoot is not None:
|
||||
if tHandle == self._trashRoot:
|
||||
return True
|
||||
elif tItem.itemParent == self._trashRoot:
|
||||
return True
|
||||
elif tItem.itemRoot == self._trashRoot:
|
||||
return True
|
||||
return False
|
||||
|
||||
def trashRoot(self):
|
||||
"""Returns the handle of the trash folder, or None if there
|
||||
isn't one.
|
||||
"""
|
||||
if self._trashRoot:
|
||||
return self._trashRoot
|
||||
return None
|
||||
|
||||
def findRoot(self, theClass):
|
||||
"""Find the first root item for a given class.
|
||||
"""
|
||||
for aRoot in self._treeRoots:
|
||||
tItem = self.__getitem__(aRoot)
|
||||
if tItem is None:
|
||||
continue
|
||||
if theClass == tItem.itemClass:
|
||||
return tItem.itemHandle
|
||||
return None
|
||||
|
||||
##
|
||||
# Setters
|
||||
##
|
||||
@@ -334,21 +351,13 @@ class NWTree():
|
||||
|
||||
return
|
||||
|
||||
def setSeed(self, theSeed):
|
||||
"""Used for debugging!
|
||||
Sets a seed for generating handles so that they always come out
|
||||
in a predictable order.
|
||||
"""
|
||||
self._handleSeed = theSeed
|
||||
return
|
||||
|
||||
def setFileItemLayout(self, tHandle, itemLayout):
|
||||
"""Set the nwItemLayout for a specific file.
|
||||
"""
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
return False
|
||||
if tItem.itemType != nwItemType.FILE:
|
||||
if not tItem.isFileType():
|
||||
logger.error("Item '%s' is not a file", tHandle)
|
||||
return False
|
||||
if not isinstance(itemLayout, nwItemLayout):
|
||||
@@ -358,30 +367,6 @@ class NWTree():
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Getters
|
||||
##
|
||||
|
||||
def countTypes(self):
|
||||
"""Count the number of files, folders and roots in the project.
|
||||
"""
|
||||
nRoot = 0
|
||||
nFolder = 0
|
||||
nFile = 0
|
||||
|
||||
for tHandle in self._treeOrder:
|
||||
tItem = self.__getitem__(tHandle)
|
||||
if tItem is None:
|
||||
continue
|
||||
elif tItem.itemType == nwItemType.ROOT:
|
||||
nRoot += 1
|
||||
elif tItem.itemType == nwItemType.FOLDER:
|
||||
nFolder += 1
|
||||
elif tItem.itemType == nwItemType.FILE:
|
||||
nFile += 1
|
||||
|
||||
return nRoot, nFolder, nFile
|
||||
|
||||
##
|
||||
# Meta Methods
|
||||
##
|
||||
@@ -420,7 +405,7 @@ class NWTree():
|
||||
return
|
||||
|
||||
if tHandle in self._treeRoots:
|
||||
self._treeRoots.remove(tHandle)
|
||||
del self._treeRoots[tHandle]
|
||||
if tHandle == self._trashRoot:
|
||||
self._trashRoot = None
|
||||
if tHandle == self._archRoot:
|
||||
@@ -468,29 +453,16 @@ class NWTree():
|
||||
self.theProject.setProjectChanged(True)
|
||||
return
|
||||
|
||||
def _makeHandle(self, addSeed=""):
|
||||
def _makeHandle(self):
|
||||
"""Generate a unique item handle. In the event that the key
|
||||
already exists, salt the seed and generate a new handle.
|
||||
A key collision is very unlikely to be caused by the truncation
|
||||
of the sha256 hash to 13 characters. Assuming it is near-random,
|
||||
it will on average happen every 4.5^15 times. However, the clock
|
||||
seed is likely to occasionally generate a collision if the
|
||||
handle requests come faster than the clock resolution.
|
||||
already exists, generate a new one.
|
||||
"""
|
||||
if self._handleSeed is None:
|
||||
newSeed = "%s_%d_%s" % (str(time()), self._handleCount, addSeed)
|
||||
self._handleCount += 1
|
||||
else:
|
||||
# This is used for debugging
|
||||
newSeed = str(self._handleSeed)
|
||||
self._handleSeed += 1
|
||||
|
||||
logger.verbose("Generating handle with seed '%s'", newSeed)
|
||||
itemHandle = sha256(newSeed.encode()).hexdigest()[0:13]
|
||||
if itemHandle in self._projTree:
|
||||
logger.verbose("Generating new handle")
|
||||
handle = f"{random.getrandbits(52):013x}"
|
||||
if handle in self._projTree:
|
||||
logger.warning("Duplicate handle encountered! Retrying ...")
|
||||
itemHandle = self._makeHandle(addSeed+"!")
|
||||
handle = self._makeHandle()
|
||||
|
||||
return itemHandle
|
||||
return handle
|
||||
|
||||
# END Class NWTree
|
||||
|
||||
@@ -22,7 +22,7 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
from novelwriter.dialogs.about import GuiAbout
|
||||
from novelwriter.dialogs.docmerge import GuiDocMerge
|
||||
from novelwriter.dialogs.docsplit import GuiDocSplit
|
||||
from novelwriter.dialogs.itemeditor import GuiItemEditor
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
from novelwriter.dialogs.preferences import GuiPreferences
|
||||
from novelwriter.dialogs.projdetails import GuiProjectDetails
|
||||
from novelwriter.dialogs.projload import GuiProjectLoad
|
||||
@@ -35,7 +35,7 @@ __all__ = [
|
||||
"GuiAbout",
|
||||
"GuiDocMerge",
|
||||
"GuiDocSplit",
|
||||
"GuiItemEditor",
|
||||
"GuiEditLabel",
|
||||
"GuiPreferences",
|
||||
"GuiProjectDetails",
|
||||
"GuiProjectLoad",
|
||||
|
||||
@@ -43,15 +43,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiAbout(QDialog):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiAbout ...")
|
||||
self.setObjectName("GuiAbout")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.innerBox = QHBoxLayout()
|
||||
@@ -63,7 +63,7 @@ class GuiAbout(QDialog):
|
||||
|
||||
nPx = self.mainConf.pxInt(96)
|
||||
self.nwIcon = QLabel()
|
||||
self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.nwIcon.setPixmap(self.mainGui.mainTheme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.lblName = QLabel("<b>novelWriter</b>")
|
||||
self.lblVers = QLabel(f"v{novelwriter.__version__}")
|
||||
self.lblDate = QLabel(datetime.strptime(novelwriter.__date__, "%Y-%m-%d").strftime("%x"))
|
||||
@@ -182,45 +182,46 @@ class GuiAbout(QDialog):
|
||||
self.tr("Translations"),
|
||||
self._wrapTable([
|
||||
("English", "Veronica Berglyd Olsen"),
|
||||
("Español Latinoamericano", "Tommy Marplatt"),
|
||||
("Français", "Jan Lüdke (jyhelle)"),
|
||||
("Nederlands", "Martijn van der Kleijn"),
|
||||
("Norsk Bokmål", "Veronica Berglyd Olsen"),
|
||||
("Português", "Bruno Meneguello"),
|
||||
("简体中文", "Qianzhi Long"),
|
||||
("Español Latinoamericano", "Tommy Marplatt"),
|
||||
])
|
||||
)
|
||||
|
||||
theTheme = self.theParent.theTheme
|
||||
theIcons = self.theParent.theTheme.theIcons
|
||||
if theTheme.themeName and theTheme.themeAuthor != "N/A":
|
||||
licURL = f"<a href='{theTheme.themeLicenseUrl}'>{theTheme.themeLicense}</a>"
|
||||
mainTheme = self.mainGui.mainTheme
|
||||
iconCache = self.mainGui.mainTheme.iconCache
|
||||
if mainTheme.themeName and mainTheme.themeAuthor != "N/A":
|
||||
licURL = f"<a href='{mainTheme.themeLicenseUrl}'>{mainTheme.themeLicense}</a>"
|
||||
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
||||
self.tr("Theme: {0}").format(theTheme.themeName),
|
||||
self.tr("Theme: {0}").format(mainTheme.themeName),
|
||||
self._wrapTable([
|
||||
(self.tr("Author"), theTheme.themeAuthor),
|
||||
(self.tr("Credit"), theTheme.themeCredit),
|
||||
(self.tr("Author"), mainTheme.themeAuthor),
|
||||
(self.tr("Credit"), mainTheme.themeCredit),
|
||||
(self.tr("Licence"), licURL),
|
||||
])
|
||||
)
|
||||
|
||||
if theIcons.themeName:
|
||||
licURL = f"<a href='{theIcons.themeLicenseUrl}'>{theIcons.themeLicense}</a>"
|
||||
if iconCache.themeName:
|
||||
licURL = f"<a href='{iconCache.themeLicenseUrl}'>{iconCache.themeLicense}</a>"
|
||||
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
||||
self.tr("Icons: {0}").format(theIcons.themeName),
|
||||
self.tr("Icons: {0}").format(iconCache.themeName),
|
||||
self._wrapTable([
|
||||
(self.tr("Author"), theIcons.themeAuthor),
|
||||
(self.tr("Credit"), theIcons.themeCredit),
|
||||
(self.tr("Author"), iconCache.themeAuthor),
|
||||
(self.tr("Credit"), iconCache.themeCredit),
|
||||
(self.tr("Licence"), licURL),
|
||||
])
|
||||
)
|
||||
|
||||
if theTheme.syntaxName:
|
||||
licURL = f"<a href='{theTheme.syntaxLicenseUrl}'>{theTheme.syntaxLicense}</a>"
|
||||
if mainTheme.syntaxName:
|
||||
licURL = f"<a href='{mainTheme.syntaxLicenseUrl}'>{mainTheme.syntaxLicense}</a>"
|
||||
aboutMsg += "<h4>{0}</h4><p>{1}</p>".format(
|
||||
self.tr("Syntax: {0}").format(theTheme.syntaxName),
|
||||
self.tr("Syntax: {0}").format(mainTheme.syntaxName),
|
||||
self._wrapTable([
|
||||
(self.tr("Author"), theTheme.syntaxAuthor),
|
||||
(self.tr("Credit"), theTheme.syntaxCredit),
|
||||
(self.tr("Author"), mainTheme.syntaxAuthor),
|
||||
(self.tr("Credit"), mainTheme.syntaxCredit),
|
||||
(self.tr("Licence"), licURL),
|
||||
])
|
||||
)
|
||||
@@ -278,12 +279,12 @@ class GuiAbout(QDialog):
|
||||
" padding-right: 0.8em;"
|
||||
"}}\n"
|
||||
).format(
|
||||
hColR=self.theParent.theTheme.colHead[0],
|
||||
hColG=self.theParent.theTheme.colHead[1],
|
||||
hColB=self.theParent.theTheme.colHead[2],
|
||||
kColR=self.theTheme.colKey[0],
|
||||
kColG=self.theTheme.colKey[1],
|
||||
kColB=self.theTheme.colKey[2],
|
||||
hColR=self.mainGui.mainTheme.colHead[0],
|
||||
hColG=self.mainGui.mainTheme.colHead[1],
|
||||
hColB=self.mainGui.mainTheme.colHead[2],
|
||||
kColR=self.mainTheme.colKey[0],
|
||||
kColG=self.mainTheme.colKey[1],
|
||||
kColB=self.mainTheme.colKey[2],
|
||||
)
|
||||
self.pageAbout.document().setDefaultStyleSheet(styleSheet)
|
||||
self.pageNotes.document().setDefaultStyleSheet(styleSheet)
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"""
|
||||
novelWriter – GUI Doc Merge Tool
|
||||
================================
|
||||
GUI class for merging multiple documents to one document
|
||||
novelWriter – GUI Doc Merge Dialog
|
||||
==================================
|
||||
Custom dialog class for merging documents.
|
||||
|
||||
File History:
|
||||
Created: 2020-01-23 [0.4.3]
|
||||
Created: 2020-01-23 [0.4.3]
|
||||
Rewritten: 2022-10-06 [2.0b1]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
||||
@@ -26,168 +27,147 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from PyQt5.QtCore import Qt
|
||||
from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QLabel, QListWidget, QAbstractItemView,
|
||||
QListWidgetItem, QDialogButtonBox
|
||||
QAbstractItemView, QDialog, QDialogButtonBox, QGridLayout, QLabel,
|
||||
QListWidget, QListWidgetItem, QVBoxLayout,
|
||||
)
|
||||
|
||||
from novelwriter.core import NWDoc
|
||||
from novelwriter.enum import nwAlert, nwItemType
|
||||
from novelwriter.gui.custom import QHelpLabel
|
||||
from novelwriter.gui.custom import QHelpLabel, QSwitch
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiDocMerge(QDialog):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui, sHandle, itemList):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiDocMerge ...")
|
||||
self.setObjectName("GuiDocMerge")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.sourceItem = None
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self._data = {}
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.setWindowTitle(self.tr("Merge Documents"))
|
||||
|
||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Documents to Merge")))
|
||||
self.helpLabel = QHelpLabel(
|
||||
self.tr("Drag and drop items to change the order."), self.theParent.theTheme.helpText
|
||||
)
|
||||
self.helpLabel = QHelpLabel(self.tr(
|
||||
"Drag and drop items to change the order, or uncheck to exclude."
|
||||
), self.mainTheme.helpText)
|
||||
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
hSp = self.mainConf.pxInt(12)
|
||||
vSp = self.mainConf.pxInt(8)
|
||||
bSp = self.mainConf.pxInt(12)
|
||||
|
||||
self.listBox = QListWidget()
|
||||
self.listBox.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
self.listBox.setIconSize(QSize(iPx, iPx))
|
||||
self.listBox.setMinimumWidth(self.mainConf.pxInt(400))
|
||||
self.listBox.setMinimumHeight(self.mainConf.pxInt(180))
|
||||
self.listBox.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.listBox.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.listBox.setDragDropMode(QAbstractItemView.InternalMove)
|
||||
|
||||
# Merge Options
|
||||
self.trashLabel = QLabel(self.tr("Move merged items to Trash"))
|
||||
self.trashSwitch = QSwitch()
|
||||
|
||||
self.optBox = QGridLayout()
|
||||
self.optBox.addWidget(self.trashLabel, 0, 0)
|
||||
self.optBox.addWidget(self.trashSwitch, 0, 1)
|
||||
self.optBox.setHorizontalSpacing(hSp)
|
||||
self.optBox.setColumnStretch(2, 1)
|
||||
|
||||
# Buttons
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.buttonBox.accepted.connect(self._doMerge)
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
self.resetButton = self.buttonBox.addButton(QDialogButtonBox.Reset)
|
||||
self.resetButton.clicked.connect(self._resetList)
|
||||
|
||||
# Assemble
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.setSpacing(0)
|
||||
self.outerBox.addWidget(self.headLabel)
|
||||
self.outerBox.addWidget(self.helpLabel)
|
||||
self.outerBox.addSpacing(self.mainConf.pxInt(8))
|
||||
self.outerBox.addSpacing(vSp)
|
||||
self.outerBox.addWidget(self.listBox)
|
||||
self.outerBox.addSpacing(self.mainConf.pxInt(12))
|
||||
self.outerBox.addSpacing(vSp)
|
||||
self.outerBox.addLayout(self.optBox)
|
||||
self.outerBox.addSpacing(bSp)
|
||||
self.outerBox.addWidget(self.buttonBox)
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
self.rejected.connect(self._doClose)
|
||||
|
||||
self._populateList()
|
||||
# Load Content
|
||||
self._loadContent(sHandle, itemList)
|
||||
|
||||
logger.debug("GuiDocMerge initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Buttons
|
||||
##
|
||||
|
||||
def _doMerge(self):
|
||||
"""Perform the merge of the files in the selected folder, and
|
||||
create a new file in the same parent folder. The old files are
|
||||
not removed in the merge process, and must be deleted manually.
|
||||
def getData(self):
|
||||
"""Return the user's choices.
|
||||
"""
|
||||
logger.verbose("GuiDocMerge merge button clicked")
|
||||
|
||||
finalOrder = []
|
||||
finalItems = []
|
||||
for i in range(self.listBox.count()):
|
||||
finalOrder.append(self.listBox.item(i).data(Qt.UserRole))
|
||||
item = self.listBox.item(i)
|
||||
if item.checkState() == Qt.Checked:
|
||||
finalItems.append(item.data(Qt.UserRole))
|
||||
|
||||
if len(finalOrder) == 0:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"No source documents found. Nothing to do."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
self._data["moveToTrash"] = self.trashSwitch.isChecked()
|
||||
self._data["finalItems"] = finalItems
|
||||
|
||||
theText = ""
|
||||
for tHandle in finalOrder:
|
||||
inDoc = NWDoc(self.theProject, tHandle)
|
||||
docText = inDoc.readDocument()
|
||||
docErr = inDoc.getError()
|
||||
if docText is None and docErr:
|
||||
self.theParent.makeAlert([
|
||||
self.tr("Failed to open document file."), docErr
|
||||
], nwAlert.ERROR)
|
||||
if docText:
|
||||
theText += docText.rstrip("\n")+"\n\n"
|
||||
return self._data
|
||||
|
||||
if self.sourceItem is None:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"No source folder selected. Nothing to do."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
srcItem = self.theProject.projTree[self.sourceItem]
|
||||
if srcItem is None:
|
||||
self.theParent.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemClass, srcItem.itemParent)
|
||||
newItem = self.theProject.projTree[nHandle]
|
||||
newItem.setStatus(srcItem.itemStatus)
|
||||
|
||||
outDoc = NWDoc(self.theProject, nHandle)
|
||||
if not outDoc.writeDocument(theText):
|
||||
self.theParent.makeAlert([
|
||||
self.tr("Could not save document."), outDoc.getError()
|
||||
], nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
self.theParent.treeView.revealNewTreeItem(nHandle)
|
||||
self.theParent.openDocument(nHandle, doScroll=True)
|
||||
|
||||
self._doClose()
|
||||
|
||||
return True
|
||||
|
||||
def _doClose(self):
|
||||
"""Close the dialog window without doing anything.
|
||||
def _resetList(self):
|
||||
"""Reset the content of the list box to its original state.
|
||||
"""
|
||||
self.close()
|
||||
logger.debug("Resetting list box content")
|
||||
sHandle = self._data.get("sHandle", None)
|
||||
itemList = self._data.get("origItems", [])
|
||||
self._loadContent(sHandle, itemList)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _populateList(self):
|
||||
"""Get the item selected in the tree, check that it is a folder,
|
||||
and try to find all files associated with it. The valid files
|
||||
are then added to the list view in order. The list itself can be
|
||||
reordered by the user.
|
||||
def _loadContent(self, sHandle, itemList):
|
||||
"""Load content from a given list of items.
|
||||
"""
|
||||
tHandle = self.theParent.treeView.getSelectedHandle()
|
||||
self.sourceItem = tHandle
|
||||
if tHandle is None:
|
||||
return False
|
||||
self._data = {}
|
||||
self._data["sHandle"] = sHandle
|
||||
self._data["origItems"] = itemList
|
||||
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
if nwItem is None:
|
||||
return False
|
||||
|
||||
if nwItem.itemType is not nwItemType.FOLDER:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Element selected in the project tree must be a folder."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
for sHandle in self.theParent.treeView.getTreeFromHandle(tHandle):
|
||||
newItem = QListWidgetItem()
|
||||
nwItem = self.theProject.projTree[sHandle]
|
||||
if nwItem.itemType is not nwItemType.FILE:
|
||||
self.listBox.clear()
|
||||
for tHandle in itemList:
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem is None or not nwItem.isFileType():
|
||||
continue
|
||||
|
||||
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
itemIcon = self.mainTheme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||||
)
|
||||
|
||||
newItem = QListWidgetItem()
|
||||
newItem.setIcon(itemIcon)
|
||||
newItem.setText(nwItem.itemName)
|
||||
newItem.setData(Qt.UserRole, sHandle)
|
||||
newItem.setData(Qt.UserRole, tHandle)
|
||||
newItem.setCheckState(Qt.Checked)
|
||||
|
||||
self.listBox.addItem(newItem)
|
||||
|
||||
return True
|
||||
return
|
||||
|
||||
# END Class GuiDocMerge
|
||||
|
||||
@@ -33,8 +33,7 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from novelwriter.core import NWDoc
|
||||
from novelwriter.enum import nwAlert, nwItemType, nwItemClass, nwItemLayout
|
||||
from novelwriter.constants import nwConst
|
||||
from novelwriter.enum import nwAlert
|
||||
from novelwriter.gui.custom import QHelpLabel
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -42,16 +41,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiDocSplit(QDialog):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiDocSplit ...")
|
||||
self.setObjectName("GuiDocSplit")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
self.mainGui = mainGui
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self.sourceItem = None
|
||||
self.sourceText = []
|
||||
@@ -62,7 +60,7 @@ class GuiDocSplit(QDialog):
|
||||
self.headLabel = QLabel("<b>{0}</b>".format(self.tr("Document Headers")))
|
||||
self.helpLabel = QHelpLabel(
|
||||
self.tr("Select the maximum level to split into files."),
|
||||
self.theParent.theTheme.helpText
|
||||
self.mainGui.mainTheme.helpText
|
||||
)
|
||||
|
||||
self.listBox = QListWidget()
|
||||
@@ -76,7 +74,7 @@ class GuiDocSplit(QDialog):
|
||||
self.splitLevel.addItem(self.tr("Split up to Header Level 3 (Scene)"), 3)
|
||||
self.splitLevel.addItem(self.tr("Split up to Header Level 4 (Section)"), 4)
|
||||
spIndex = self.splitLevel.findData(
|
||||
self.optState.getInt("GuiDocSplit", "spLevel", 3)
|
||||
self.theProject.options.getInt("GuiDocSplit", "spLevel", 3)
|
||||
)
|
||||
if spIndex != -1:
|
||||
self.splitLevel.setCurrentIndex(spIndex)
|
||||
@@ -117,14 +115,14 @@ class GuiDocSplit(QDialog):
|
||||
logger.verbose("GuiDocSplit split button clicked")
|
||||
|
||||
if self.sourceItem is None:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"No source document selected. Nothing to do."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
srcItem = self.theProject.projTree[self.sourceItem]
|
||||
srcItem = self.theProject.tree[self.sourceItem]
|
||||
if srcItem is None:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Could not parse source document."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
@@ -134,7 +132,7 @@ class GuiDocSplit(QDialog):
|
||||
|
||||
docErr = inDoc.getError()
|
||||
if theText is None and docErr:
|
||||
self.theParent.makeAlert([
|
||||
self.mainGui.makeAlert([
|
||||
self.tr("Failed to open document file."), docErr
|
||||
], nwAlert.ERROR)
|
||||
|
||||
@@ -155,22 +153,12 @@ class GuiDocSplit(QDialog):
|
||||
|
||||
nFiles = len(finalOrder)
|
||||
if nFiles == 0:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"No headers found. Nothing to do."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
# Check that another folder can be created
|
||||
parTree = self.theProject.projTree.getItemPath(srcItem.itemParent)
|
||||
if len(parTree) >= nwConst.MAX_DEPTH - 1:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Cannot add new folder for the document split. "
|
||||
"Maximum folder depth has been reached. "
|
||||
"Please move the file to another level in the project tree."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
msgYes = self.theParent.askQuestion(
|
||||
msgYes = self.mainGui.askQuestion(
|
||||
self.tr("Split Document"),
|
||||
"{0}<br><br>{1}".format(
|
||||
self.tr(
|
||||
@@ -186,23 +174,18 @@ class GuiDocSplit(QDialog):
|
||||
return False
|
||||
|
||||
# Create the folder
|
||||
fHandle = self.theProject.newFolder(
|
||||
srcItem.itemName, srcItem.itemClass, srcItem.itemParent
|
||||
)
|
||||
self.theParent.treeView.revealNewTreeItem(fHandle)
|
||||
fHandle = self.theProject.newFolder(srcItem.itemName, srcItem.itemParent)
|
||||
self.mainGui.projView.revealNewTreeItem(fHandle)
|
||||
logger.verbose("Creating folder '%s'", fHandle)
|
||||
|
||||
# Loop through, and create the files
|
||||
for wTitle, iStart, iEnd in finalOrder:
|
||||
|
||||
isNovel = srcItem.itemClass == nwItemClass.NOVEL
|
||||
itemLayout = nwItemLayout.DOCUMENT if isNovel else nwItemLayout.NOTE
|
||||
|
||||
wTitle = wTitle.lstrip("#").strip()
|
||||
nHandle = self.theProject.newFile(wTitle, srcItem.itemClass, fHandle)
|
||||
newItem = self.theProject.projTree[nHandle]
|
||||
newItem.setLayout(itemLayout)
|
||||
nHandle = self.theProject.newFile(wTitle, fHandle)
|
||||
newItem = self.theProject.tree[nHandle]
|
||||
newItem.setStatus(srcItem.itemStatus)
|
||||
newItem.setImport(srcItem.itemImport)
|
||||
logger.verbose(
|
||||
"Creating new document '%s' with text from line %d to %d",
|
||||
nHandle, iStart+1, iEnd
|
||||
@@ -213,12 +196,12 @@ class GuiDocSplit(QDialog):
|
||||
|
||||
outDoc = NWDoc(self.theProject, nHandle)
|
||||
if not outDoc.writeDocument(theText):
|
||||
self.theParent.makeAlert([
|
||||
self.mainGui.makeAlert([
|
||||
self.tr("Could not save document."), outDoc.getError()
|
||||
], nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
self.theParent.treeView.revealNewTreeItem(nHandle)
|
||||
self.mainGui.projView.revealNewTreeItem(nHandle)
|
||||
|
||||
self._doClose()
|
||||
|
||||
@@ -227,7 +210,7 @@ class GuiDocSplit(QDialog):
|
||||
def _doClose(self):
|
||||
"""Close the dialog window without doing anything.
|
||||
"""
|
||||
self.optState.saveSettings()
|
||||
self.theProject.options.saveSettings()
|
||||
self.close()
|
||||
return
|
||||
|
||||
@@ -243,17 +226,17 @@ class GuiDocSplit(QDialog):
|
||||
"""
|
||||
self.listBox.clear()
|
||||
if self.sourceItem is None:
|
||||
self.sourceItem = self.theParent.treeView.getSelectedHandle()
|
||||
self.sourceItem = self.mainGui.projView.getSelectedHandle()
|
||||
|
||||
if self.sourceItem is None:
|
||||
return False
|
||||
|
||||
nwItem = self.theProject.projTree[self.sourceItem]
|
||||
nwItem = self.theProject.tree[self.sourceItem]
|
||||
if nwItem is None:
|
||||
return False
|
||||
|
||||
if nwItem.itemType is not nwItemType.FILE:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
if not nwItem.isFileType():
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Element selected in the project tree must be a file."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
@@ -265,7 +248,7 @@ class GuiDocSplit(QDialog):
|
||||
return False
|
||||
|
||||
spLevel = self.splitLevel.currentData()
|
||||
self.optState.setValue("GuiDocSplit", "spLevel", spLevel)
|
||||
self.theProject.options.setValue("GuiDocSplit", "spLevel", spLevel)
|
||||
logger.debug(
|
||||
"Scanning document '%s' for headings level <= %d",
|
||||
self.sourceItem, spLevel
|
||||
|
||||
@@ -0,0 +1,84 @@
|
||||
"""
|
||||
novelWriter – Edit Label Dialog
|
||||
===============================
|
||||
A simple dialog for editing a label
|
||||
|
||||
File History:
|
||||
Created: 2022-06-11 [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.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QLineEdit, QLabel, QDialogButtonBox, QHBoxLayout
|
||||
)
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiEditLabel(QDialog):
|
||||
|
||||
def __init__(self, parent, text=""):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.setObjectName("GuiEditLabel")
|
||||
self.setWindowTitle(self.tr("Item Label"))
|
||||
|
||||
mVd = novelwriter.CONFIG.pxInt(220)
|
||||
mSp = novelwriter.CONFIG.pxInt(12)
|
||||
|
||||
# Item Label
|
||||
self.labelValue = QLineEdit()
|
||||
self.labelValue.setMinimumWidth(mVd)
|
||||
self.labelValue.setMaxLength(200)
|
||||
self.labelValue.setText(text)
|
||||
self.labelValue.selectAll()
|
||||
|
||||
# Buttons
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.buttonBox.accepted.connect(self.accept)
|
||||
self.buttonBox.rejected.connect(self.reject)
|
||||
|
||||
# Assemble
|
||||
self.innerBox = QHBoxLayout()
|
||||
self.innerBox.addWidget(QLabel(self.tr("Label")), 0)
|
||||
self.innerBox.addWidget(self.labelValue, 1)
|
||||
self.innerBox.setSpacing(mSp)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.setSpacing(mSp)
|
||||
self.outerBox.addLayout(self.innerBox, 1)
|
||||
self.outerBox.addWidget(self.buttonBox, 0)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
return
|
||||
|
||||
@property
|
||||
def itemLabel(self):
|
||||
return self.labelValue.text()
|
||||
|
||||
@classmethod
|
||||
def getLabel(cls, parent, text):
|
||||
cls = GuiEditLabel(parent, text=text)
|
||||
cls.exec_()
|
||||
return cls.itemLabel, cls.result() == QDialog.Accepted
|
||||
|
||||
# END Class GuiEditLabel
|
||||
@@ -1,202 +0,0 @@
|
||||
"""
|
||||
novelWriter – GUI Item Editor
|
||||
=============================
|
||||
GUI class for the item editor dialog
|
||||
|
||||
File History:
|
||||
Created: 2019-04-27 [0.0.1]
|
||||
|
||||
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 pyqtSlot
|
||||
from PyQt5.QtWidgets import (
|
||||
QDialog, QVBoxLayout, QGridLayout, QLineEdit, QComboBox, QLabel,
|
||||
QDialogButtonBox
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwItemLayout, nwItemType
|
||||
from novelwriter.constants import trConst, nwLists, nwLabels
|
||||
from novelwriter.gui.custom import QSwitch
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiItemEditor(QDialog):
|
||||
|
||||
def __init__(self, theParent, tHandle):
|
||||
QDialog.__init__(self, theParent)
|
||||
|
||||
logger.debug("Initialising GuiItemEditor ...")
|
||||
self.setObjectName("GuiItemEditor")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
|
||||
##
|
||||
# Build GUI
|
||||
##
|
||||
|
||||
self.theItem = self.theProject.projTree[tHandle]
|
||||
if self.theItem is None:
|
||||
self.close()
|
||||
return
|
||||
|
||||
self.setWindowTitle(self.tr("Item Settings"))
|
||||
|
||||
mVd = self.mainConf.pxInt(220)
|
||||
mSp = self.mainConf.pxInt(16)
|
||||
vSp = self.mainConf.pxInt(4)
|
||||
|
||||
# Item Label
|
||||
self.editName = QLineEdit()
|
||||
self.editName.setMinimumWidth(mVd)
|
||||
self.editName.setMaxLength(200)
|
||||
|
||||
# Item Status
|
||||
self.editStatus = QComboBox()
|
||||
self.editStatus.setMinimumWidth(mVd)
|
||||
if self.theItem.itemClass in nwLists.CLS_NOVEL:
|
||||
for sLabel, _, _ in self.theProject.statusItems:
|
||||
self.editStatus.addItem(
|
||||
self.theParent.statusIcons[sLabel], sLabel, sLabel
|
||||
)
|
||||
else:
|
||||
for sLabel, _, _ in self.theProject.importItems:
|
||||
self.editStatus.addItem(
|
||||
self.theParent.importIcons[sLabel], sLabel, sLabel
|
||||
)
|
||||
|
||||
# Item Layout
|
||||
self.editLayout = QComboBox()
|
||||
self.editLayout.setMinimumWidth(mVd)
|
||||
validLayouts = []
|
||||
if self.theItem.itemType == nwItemType.FILE:
|
||||
if self.theItem.itemClass in nwLists.CLS_NOVEL:
|
||||
validLayouts.append(nwItemLayout.DOCUMENT)
|
||||
validLayouts.append(nwItemLayout.NOTE)
|
||||
else:
|
||||
validLayouts.append(nwItemLayout.NO_LAYOUT)
|
||||
self.editLayout.setEnabled(False)
|
||||
|
||||
for itemLayout in nwItemLayout:
|
||||
if itemLayout in validLayouts:
|
||||
self.editLayout.addItem(trConst(nwLabels.LAYOUT_NAME[itemLayout]), itemLayout)
|
||||
|
||||
# Export Switch
|
||||
self.textExport = QLabel(self.tr("Include when building project"))
|
||||
self.editExport = QSwitch()
|
||||
if self.theItem.itemType == nwItemType.FILE:
|
||||
self.editExport.setEnabled(True)
|
||||
self.editExport.setChecked(self.theItem.isExported)
|
||||
else:
|
||||
self.editExport.setEnabled(False)
|
||||
self.editExport.setChecked(False)
|
||||
|
||||
# Buttons
|
||||
self.buttonBox = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
|
||||
self.buttonBox.accepted.connect(self._doSave)
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
|
||||
# Set Current Values
|
||||
self.editName.setText(self.theItem.itemName)
|
||||
self.editName.selectAll()
|
||||
|
||||
statusIdx = self.editStatus.findData(self.theItem.itemStatus)
|
||||
if statusIdx != -1:
|
||||
self.editStatus.setCurrentIndex(statusIdx)
|
||||
|
||||
layoutIdx = self.editLayout.findData(self.theItem.itemLayout)
|
||||
if layoutIdx != -1:
|
||||
self.editLayout.setCurrentIndex(layoutIdx)
|
||||
|
||||
##
|
||||
# Assemble
|
||||
##
|
||||
|
||||
nameLabel = QLabel(self.tr("Label"))
|
||||
statusLabel = QLabel(self.tr("Status"))
|
||||
layoutLabel = QLabel(self.tr("Layout"))
|
||||
|
||||
self.mainForm = QGridLayout()
|
||||
self.mainForm.setVerticalSpacing(vSp)
|
||||
self.mainForm.setHorizontalSpacing(mSp)
|
||||
self.mainForm.addWidget(nameLabel, 0, 0, 1, 1)
|
||||
self.mainForm.addWidget(self.editName, 0, 1, 1, 2)
|
||||
self.mainForm.addWidget(statusLabel, 1, 0, 1, 1)
|
||||
self.mainForm.addWidget(self.editStatus, 1, 1, 1, 2)
|
||||
self.mainForm.addWidget(layoutLabel, 2, 0, 1, 1)
|
||||
self.mainForm.addWidget(self.editLayout, 2, 1, 1, 2)
|
||||
self.mainForm.addWidget(self.textExport, 3, 0, 1, 2)
|
||||
self.mainForm.addWidget(self.editExport, 3, 2, 1, 1)
|
||||
self.mainForm.setColumnStretch(0, 0)
|
||||
self.mainForm.setColumnStretch(1, 1)
|
||||
self.mainForm.setColumnStretch(2, 0)
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.setSpacing(mSp)
|
||||
self.outerBox.addLayout(self.mainForm)
|
||||
self.outerBox.addStretch(1)
|
||||
self.outerBox.addWidget(self.buttonBox)
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
self.rejected.connect(self._doClose)
|
||||
|
||||
logger.debug("GuiItemEditor initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _doSave(self):
|
||||
"""Save the setting to the item.
|
||||
"""
|
||||
logger.verbose("ItemEditor save button clicked")
|
||||
|
||||
itemName = self.editName.text()
|
||||
itemStatus = self.editStatus.currentData()
|
||||
itemLayout = self.editLayout.currentData()
|
||||
isExported = self.editExport.isChecked()
|
||||
|
||||
self.theItem.setName(itemName)
|
||||
self.theItem.setStatus(itemStatus)
|
||||
self.theItem.setLayout(itemLayout)
|
||||
self.theItem.setExported(isExported)
|
||||
|
||||
self.theProject.setProjectChanged(True)
|
||||
|
||||
self.accept()
|
||||
self.close()
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _doClose(self):
|
||||
"""Close the dialog without saving the settings.
|
||||
"""
|
||||
logger.verbose("ItemEditor cancel button clicked")
|
||||
self.close()
|
||||
return
|
||||
|
||||
# END Class GuiItemEditor
|
||||
@@ -43,25 +43,25 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiPreferences(PagedDialog):
|
||||
|
||||
def __init__(self, theParent):
|
||||
PagedDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiPreferences ...")
|
||||
self.setObjectName("GuiPreferences")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.mainGui = mainGui
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self.setWindowTitle(self.tr("Preferences"))
|
||||
|
||||
self.tabGeneral = GuiPreferencesGeneral(self.theParent)
|
||||
self.tabProjects = GuiPreferencesProjects(self.theParent)
|
||||
self.tabDocs = GuiPreferencesDocuments(self.theParent)
|
||||
self.tabEditor = GuiPreferencesEditor(self.theParent)
|
||||
self.tabSyntax = GuiPreferencesSyntax(self.theParent)
|
||||
self.tabAuto = GuiPreferencesAutomation(self.theParent)
|
||||
self.tabQuote = GuiPreferencesQuotes(self.theParent)
|
||||
self.tabGeneral = GuiPreferencesGeneral(self)
|
||||
self.tabProjects = GuiPreferencesProjects(self)
|
||||
self.tabDocs = GuiPreferencesDocuments(self)
|
||||
self.tabEditor = GuiPreferencesEditor(self)
|
||||
self.tabSyntax = GuiPreferencesSyntax(self)
|
||||
self.tabAuto = GuiPreferencesAutomation(self)
|
||||
self.tabQuote = GuiPreferencesQuotes(self)
|
||||
|
||||
self.addTab(self.tabGeneral, self.tr("General"))
|
||||
self.addTab(self.tabProjects, self.tr("Projects"))
|
||||
@@ -102,12 +102,12 @@ class GuiPreferences(PagedDialog):
|
||||
self.tabQuote.saveValues()
|
||||
|
||||
if needsRestart:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Some changes will not be applied until novelWriter has been restarted."
|
||||
), nwAlert.INFO)
|
||||
|
||||
if refreshTree:
|
||||
self.theParent.treeView.buildTree()
|
||||
self.mainGui.projView.populateTree()
|
||||
|
||||
self._saveWindowSize()
|
||||
self.accept()
|
||||
@@ -136,16 +136,16 @@ class GuiPreferences(PagedDialog):
|
||||
|
||||
class GuiPreferencesGeneral(QWidget):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, prefsGui):
|
||||
super().__init__(parent=prefsGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = prefsGui.mainGui
|
||||
self.mainTheme = prefsGui.mainGui.mainTheme
|
||||
|
||||
# The Form
|
||||
self.mainForm = QConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
||||
self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Look and Feel
|
||||
@@ -172,7 +172,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
# Select Theme
|
||||
self.guiTheme = QComboBox()
|
||||
self.guiTheme.setMinimumWidth(minWidth)
|
||||
self.theThemes = self.theTheme.listThemes()
|
||||
self.theThemes = self.mainTheme.listThemes()
|
||||
for themeDir, themeName in self.theThemes:
|
||||
self.guiTheme.addItem(themeName, themeDir)
|
||||
themeIdx = self.guiTheme.findData(self.mainConf.guiTheme)
|
||||
@@ -188,8 +188,8 @@ class GuiPreferencesGeneral(QWidget):
|
||||
# Select Icon Theme
|
||||
self.guiIcons = QComboBox()
|
||||
self.guiIcons.setMinimumWidth(minWidth)
|
||||
self.theIcons = self.theTheme.theIcons.listThemes()
|
||||
for iconDir, iconName in self.theIcons:
|
||||
self.iconCache = self.mainTheme.iconCache.listThemes()
|
||||
for iconDir, iconName in self.iconCache:
|
||||
self.guiIcons.addItem(iconName, iconDir)
|
||||
iconIdx = self.guiIcons.findData(self.mainConf.guiIcons)
|
||||
if iconIdx != -1:
|
||||
@@ -201,13 +201,29 @@ class GuiPreferencesGeneral(QWidget):
|
||||
self.tr("Requires restart.")
|
||||
)
|
||||
|
||||
# Editor Theme
|
||||
self.guiSyntax = QComboBox()
|
||||
self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200))
|
||||
self.theSyntaxes = self.mainTheme.listSyntax()
|
||||
for syntaxFile, syntaxName in self.theSyntaxes:
|
||||
self.guiSyntax.addItem(syntaxName, syntaxFile)
|
||||
syntaxIdx = self.guiSyntax.findData(self.mainConf.guiSyntax)
|
||||
if syntaxIdx != -1:
|
||||
self.guiSyntax.setCurrentIndex(syntaxIdx)
|
||||
|
||||
self.mainForm.addRow(
|
||||
self.tr("Editor theme"),
|
||||
self.guiSyntax,
|
||||
self.tr("Colour theme for the editor and viewer.")
|
||||
)
|
||||
|
||||
# Font Family
|
||||
self.guiFont = QLineEdit()
|
||||
self.guiFont.setReadOnly(True)
|
||||
self.guiFont.setFixedWidth(self.mainConf.pxInt(162))
|
||||
self.guiFont.setText(self.mainConf.guiFont)
|
||||
self.fontButton = QPushButton("...")
|
||||
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||
self.fontButton.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("...")))
|
||||
self.fontButton.clicked.connect(self._selectFont)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Font family"),
|
||||
@@ -273,6 +289,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
guiLang = self.guiLang.currentData()
|
||||
guiTheme = self.guiTheme.currentData()
|
||||
guiIcons = self.guiIcons.currentData()
|
||||
guiSyntax = self.guiSyntax.currentData()
|
||||
guiFont = self.guiFont.text()
|
||||
guiFontSize = self.guiFontSize.value()
|
||||
emphLabels = self.emphLabels.isChecked()
|
||||
@@ -292,6 +309,7 @@ class GuiPreferencesGeneral(QWidget):
|
||||
self.mainConf.guiLang = guiLang
|
||||
self.mainConf.guiTheme = guiTheme
|
||||
self.mainConf.guiIcons = guiIcons
|
||||
self.mainConf.guiSyntax = guiSyntax
|
||||
self.mainConf.guiFont = guiFont
|
||||
self.mainConf.guiFontSize = guiFontSize
|
||||
self.mainConf.emphLabels = emphLabels
|
||||
@@ -324,16 +342,16 @@ class GuiPreferencesGeneral(QWidget):
|
||||
|
||||
class GuiPreferencesProjects(QWidget):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, prefsGui):
|
||||
super().__init__(parent=prefsGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = prefsGui.mainGui
|
||||
self.mainTheme = prefsGui.mainGui.mainTheme
|
||||
|
||||
# The Form
|
||||
self.mainForm = QConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
||||
self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Automatic Save
|
||||
@@ -485,16 +503,16 @@ class GuiPreferencesProjects(QWidget):
|
||||
|
||||
class GuiPreferencesDocuments(QWidget):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, prefsGui):
|
||||
super().__init__(parent=prefsGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = prefsGui.mainGui
|
||||
self.mainTheme = prefsGui.mainGui.mainTheme
|
||||
|
||||
# The Form
|
||||
self.mainForm = QConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
||||
self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Text Style
|
||||
@@ -507,7 +525,7 @@ class GuiPreferencesDocuments(QWidget):
|
||||
self.textFont.setFixedWidth(self.mainConf.pxInt(162))
|
||||
self.textFont.setText(self.mainConf.textFont)
|
||||
self.fontButton = QPushButton("...")
|
||||
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||
self.fontButton.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("...")))
|
||||
self.fontButton.clicked.connect(self._selectFont)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Font family"),
|
||||
@@ -646,16 +664,16 @@ class GuiPreferencesDocuments(QWidget):
|
||||
|
||||
class GuiPreferencesEditor(QWidget):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, prefsGui):
|
||||
super().__init__(parent=prefsGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = prefsGui.mainGui
|
||||
self.mainTheme = prefsGui.mainGui.mainTheme
|
||||
|
||||
# The Form
|
||||
self.mainForm = QConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
||||
self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
mW = self.mainConf.pxInt(250)
|
||||
@@ -668,7 +686,7 @@ class GuiPreferencesEditor(QWidget):
|
||||
self.spellLanguage = QComboBox(self)
|
||||
self.spellLanguage.setMaximumWidth(mW)
|
||||
|
||||
langAvail = self.theParent.docEditor.spEnchant.listDictionaries()
|
||||
langAvail = self.mainGui.docEditor.spEnchant.listDictionaries()
|
||||
if self.mainConf.hasEnchant:
|
||||
if langAvail:
|
||||
for spTag, spProv in langAvail:
|
||||
@@ -820,37 +838,18 @@ class GuiPreferencesEditor(QWidget):
|
||||
|
||||
class GuiPreferencesSyntax(QWidget):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, prefsGui):
|
||||
super().__init__(parent=prefsGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = prefsGui.mainGui
|
||||
self.mainTheme = prefsGui.mainGui.mainTheme
|
||||
|
||||
# The Form
|
||||
self.mainForm = QConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
||||
self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Highlighting Theme
|
||||
# ==================
|
||||
self.mainForm.addGroupLabel(self.tr("Highlighting Theme"))
|
||||
|
||||
self.guiSyntax = QComboBox()
|
||||
self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200))
|
||||
self.theSyntaxes = self.theTheme.listSyntax()
|
||||
for syntaxFile, syntaxName in self.theSyntaxes:
|
||||
self.guiSyntax.addItem(syntaxName, syntaxFile)
|
||||
syntaxIdx = self.guiSyntax.findData(self.mainConf.guiSyntax)
|
||||
if syntaxIdx != -1:
|
||||
self.guiSyntax.setCurrentIndex(syntaxIdx)
|
||||
|
||||
self.mainForm.addRow(
|
||||
self.tr("Highlighting theme"),
|
||||
self.guiSyntax,
|
||||
self.tr("Colour theme for the editor and viewer.")
|
||||
)
|
||||
|
||||
# Quotes & Dialogue
|
||||
# =================
|
||||
self.mainForm.addGroupLabel(self.tr("Quotes & Dialogue"))
|
||||
@@ -900,7 +899,7 @@ class GuiPreferencesSyntax(QWidget):
|
||||
self.showMultiSpaces = QSwitch()
|
||||
self.showMultiSpaces.setChecked(self.mainConf.showMultiSpaces)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Highlight multiple spaces"),
|
||||
self.tr("Highlight multiple or trailing spaces"),
|
||||
self.showMultiSpaces,
|
||||
self.tr("Applies to the document editor only.")
|
||||
)
|
||||
@@ -910,9 +909,6 @@ class GuiPreferencesSyntax(QWidget):
|
||||
def saveValues(self):
|
||||
"""Save the values set for this tab.
|
||||
"""
|
||||
# Highlighting Theme
|
||||
self.mainConf.guiSyntax = self.guiSyntax.currentData()
|
||||
|
||||
# Quotes & Dialogue
|
||||
self.mainConf.highlightQuotes = self.highlightQuotes.isChecked()
|
||||
self.mainConf.allowOpenSQuote = self.allowOpenSQuote.isChecked()
|
||||
@@ -945,16 +941,16 @@ class GuiPreferencesSyntax(QWidget):
|
||||
|
||||
class GuiPreferencesAutomation(QWidget):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, prefsGui):
|
||||
super().__init__(parent=prefsGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = prefsGui.mainGui
|
||||
self.mainTheme = prefsGui.mainGui.mainTheme
|
||||
|
||||
# The Form
|
||||
self.mainForm = QConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
||||
self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Automatic Features
|
||||
@@ -1102,16 +1098,16 @@ class GuiPreferencesAutomation(QWidget):
|
||||
|
||||
class GuiPreferencesQuotes(QWidget):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, prefsGui):
|
||||
super().__init__(parent=prefsGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = prefsGui.mainGui
|
||||
self.mainTheme = prefsGui.mainGui.mainTheme
|
||||
|
||||
# The Form
|
||||
self.mainForm = QConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
|
||||
self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
# Quotation Style
|
||||
@@ -1119,7 +1115,7 @@ class GuiPreferencesQuotes(QWidget):
|
||||
self.mainForm.addGroupLabel(self.tr("Quotation Style"))
|
||||
|
||||
qWidth = self.mainConf.pxInt(40)
|
||||
bWidth = int(2.5*self.theTheme.getTextWidth("..."))
|
||||
bWidth = int(2.5*self.mainTheme.getTextWidth("..."))
|
||||
self.quoteSym = {}
|
||||
|
||||
# Single Quote Style
|
||||
|
||||
@@ -27,15 +27,17 @@ import math
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtCore import Qt, QSize, pyqtSlot
|
||||
from PyQt5.QtGui import QFont
|
||||
from PyQt5.QtWidgets import (
|
||||
QWidget, QDialogButtonBox, QVBoxLayout, QTreeWidget, QTreeWidgetItem,
|
||||
QLabel, QSpinBox, QGridLayout, QHBoxLayout, QLineEdit, QAbstractItemView
|
||||
QAbstractItemView, QComboBox, QDialogButtonBox, QGridLayout, QHBoxLayout,
|
||||
QLabel, QLineEdit, QSpinBox, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
|
||||
QWidget
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwItemClass
|
||||
from novelwriter.common import numberToRoman
|
||||
from novelwriter.constants import nwUnicode
|
||||
from novelwriter.constants import nwLabels, nwUnicode
|
||||
from novelwriter.gui.custom import PagedDialog, QSwitch
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -43,31 +45,31 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiProjectDetails(PagedDialog):
|
||||
|
||||
def __init__(self, theParent):
|
||||
PagedDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiProjectDetails ...")
|
||||
self.setObjectName("GuiProjectDetails")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
self.mainGui = mainGui
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self.setWindowTitle(self.tr("Project Details"))
|
||||
|
||||
wW = self.mainConf.pxInt(600)
|
||||
wH = self.mainConf.pxInt(400)
|
||||
pOptions = self.theProject.options
|
||||
|
||||
self.setMinimumWidth(wW)
|
||||
self.setMinimumHeight(wH)
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "winWidth", wW)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "winHeight", wH))
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "winWidth", wW)),
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "winHeight", wH))
|
||||
)
|
||||
|
||||
self.tabMain = GuiProjectDetailsMain(self.theParent, self.theProject)
|
||||
self.tabContents = GuiProjectDetailsContents(self.theParent, self.theProject)
|
||||
self.tabMain = GuiProjectDetailsMain(self.mainGui, self.theProject)
|
||||
self.tabContents = GuiProjectDetailsContents(self.mainGui, self.theProject)
|
||||
|
||||
self.addTab(self.tabMain, self.tr("Overview"))
|
||||
self.addTab(self.tabContents, self.tr("Contents"))
|
||||
@@ -120,16 +122,17 @@ class GuiProjectDetails(PagedDialog):
|
||||
countFrom = self.tabContents.poValue.value()
|
||||
clearDouble = self.tabContents.dblValue.isChecked()
|
||||
|
||||
self.optState.setValue("GuiProjectDetails", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiProjectDetails", "winHeight", winHeight)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol0", widthCol0)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol1", widthCol1)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol2", widthCol2)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol3", widthCol3)
|
||||
self.optState.setValue("GuiProjectDetails", "widthCol4", widthCol4)
|
||||
self.optState.setValue("GuiProjectDetails", "wordsPerPage", wordsPerPage)
|
||||
self.optState.setValue("GuiProjectDetails", "countFrom", countFrom)
|
||||
self.optState.setValue("GuiProjectDetails", "clearDouble", clearDouble)
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiProjectDetails", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiProjectDetails", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol0", widthCol0)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol1", widthCol1)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol2", widthCol2)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol3", widthCol3)
|
||||
pOptions.setValue("GuiProjectDetails", "widthCol4", widthCol4)
|
||||
pOptions.setValue("GuiProjectDetails", "wordsPerPage", wordsPerPage)
|
||||
pOptions.setValue("GuiProjectDetails", "countFrom", countFrom)
|
||||
pOptions.setValue("GuiProjectDetails", "clearDouble", clearDouble)
|
||||
|
||||
return
|
||||
|
||||
@@ -138,17 +141,16 @@ class GuiProjectDetails(PagedDialog):
|
||||
|
||||
class GuiProjectDetailsMain(QWidget):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, mainGui, theProject):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
|
||||
fPx = self.theTheme.fontPixelSize
|
||||
fPt = self.theTheme.fontPointSize
|
||||
fPx = self.mainTheme.fontPixelSize
|
||||
fPt = self.mainTheme.fontPointSize
|
||||
vPx = self.mainConf.pxInt(4)
|
||||
hPx = self.mainConf.pxInt(12)
|
||||
|
||||
@@ -245,8 +247,9 @@ class GuiProjectDetailsMain(QWidget):
|
||||
def updateValues(self):
|
||||
"""Set all the values.
|
||||
"""
|
||||
hCounts = self.theIndex.getNovelTitleCounts()
|
||||
nwCount = self.theIndex.getNovelWordCount()
|
||||
pIndex = self.theProject.index
|
||||
hCounts = pIndex.getNovelTitleCounts()
|
||||
nwCount = pIndex.getNovelWordCount()
|
||||
edTime = self.theProject.getCurrentEditTime()
|
||||
|
||||
self.wordCountVal.setText(f"{nwCount:n}")
|
||||
@@ -270,22 +273,35 @@ class GuiProjectDetailsContents(QWidget):
|
||||
C_PAGE = 3
|
||||
C_PROG = 4
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, mainGui, theProject):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.optState = theProject.optState
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
|
||||
# Internal
|
||||
self._theToC = []
|
||||
self._currentRoot = None
|
||||
|
||||
iPx = self.theTheme.baseIconSize
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
hPx = self.mainConf.pxInt(12)
|
||||
vPx = self.mainConf.pxInt(4)
|
||||
pOptions = self.theProject.options
|
||||
|
||||
# Header
|
||||
# ======
|
||||
|
||||
self.tocLabel = QLabel("<b>%s</b>" % self.tr("Table of Contents"))
|
||||
|
||||
self.novelValue = QComboBox(self)
|
||||
self.novelValue.setMinimumWidth(self.mainConf.pxInt(200))
|
||||
self.novelValue.currentIndexChanged.connect(self._novelValueChanged)
|
||||
|
||||
self.headBox = QHBoxLayout()
|
||||
self.headBox.addWidget(self.tocLabel)
|
||||
self.headBox.addWidget(self.novelValue)
|
||||
|
||||
# Contents Tree
|
||||
# =============
|
||||
@@ -314,11 +330,11 @@ class GuiProjectDetailsContents(QWidget):
|
||||
treeHeader.setStretchLastSection(True)
|
||||
treeHeader.setMinimumSectionSize(hPx)
|
||||
|
||||
wCol0 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol0", 200))
|
||||
wCol1 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol1", 60))
|
||||
wCol2 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol2", 60))
|
||||
wCol3 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol3", 60))
|
||||
wCol4 = self.mainConf.pxInt(self.optState.getInt("GuiProjectDetails", "widthCol4", 90))
|
||||
wCol0 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol0", 200))
|
||||
wCol1 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol1", 60))
|
||||
wCol2 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol2", 60))
|
||||
wCol3 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol3", 60))
|
||||
wCol4 = self.mainConf.pxInt(pOptions.getInt("GuiProjectDetails", "widthCol4", 90))
|
||||
|
||||
self.tocTree.setColumnWidth(0, wCol0)
|
||||
self.tocTree.setColumnWidth(1, wCol1)
|
||||
@@ -330,9 +346,9 @@ class GuiProjectDetailsContents(QWidget):
|
||||
# Options
|
||||
# =======
|
||||
|
||||
wordsPerPage = self.optState.getInt("GuiProjectDetails", "wordsPerPage", 350)
|
||||
countFrom = self.optState.getInt("GuiProjectDetails", "countFrom", 1)
|
||||
clearDouble = self.optState.getInt("GuiProjectDetails", "clearDouble", True)
|
||||
wordsPerPage = pOptions.getInt("GuiProjectDetails", "wordsPerPage", 350)
|
||||
countFrom = pOptions.getInt("GuiProjectDetails", "countFrom", 1)
|
||||
clearDouble = pOptions.getInt("GuiProjectDetails", "clearDouble", True)
|
||||
|
||||
wordsHelp = (
|
||||
self.tr("Typical word count for a 5 by 8 inch book page with 11 pt font is 350.")
|
||||
@@ -389,7 +405,7 @@ class GuiProjectDetailsContents(QWidget):
|
||||
# ========
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addWidget(QLabel("<b>%s</b>" % self.tr("Table of Contents")))
|
||||
self.outerBox.addLayout(self.headBox)
|
||||
self.outerBox.addWidget(self.tocTree)
|
||||
self.outerBox.addLayout(self.optionsBox)
|
||||
|
||||
@@ -412,19 +428,37 @@ class GuiProjectDetailsContents(QWidget):
|
||||
def updateValues(self):
|
||||
"""Populate the tree.
|
||||
"""
|
||||
self._prepareData()
|
||||
self._currentRoot = None
|
||||
self._populateNovelList()
|
||||
|
||||
rootHandle = self.novelValue.currentData()
|
||||
self._prepareData(rootHandle)
|
||||
self._populateTree()
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _prepareData(self):
|
||||
"""Extract the data for the tree.
|
||||
def _populateNovelList(self):
|
||||
"""Fill the novel combo box with a list of all novel folders.
|
||||
"""
|
||||
self._theToC = []
|
||||
self._theToC = self.theIndex.getTableOfContents(2)
|
||||
self.novelValue.clear()
|
||||
|
||||
tIcon = self.mainTheme.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.setVisible(self.novelValue.count() > 1)
|
||||
|
||||
return
|
||||
|
||||
def _prepareData(self, rootHandle):
|
||||
"""Extract the information from the project index.
|
||||
"""
|
||||
logger.debug("Populating ToC from handle '%s'", rootHandle)
|
||||
self._theToC = self.theProject.index.getTableOfContents(rootHandle, 2)
|
||||
self._theToC.append(("", 0, self.tr("END"), 0))
|
||||
return
|
||||
|
||||
@@ -432,6 +466,18 @@ class GuiProjectDetailsContents(QWidget):
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _novelValueChanged(self):
|
||||
"""Refresh the tree with another root item.
|
||||
"""
|
||||
rootHandle = self.novelValue.currentData()
|
||||
if rootHandle != self._currentRoot:
|
||||
self._prepareData(rootHandle)
|
||||
self._populateTree()
|
||||
self._currentRoot = rootHandle
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _populateTree(self):
|
||||
"""Set the content of the chapter/page tree.
|
||||
"""
|
||||
@@ -469,7 +515,7 @@ class GuiProjectDetailsContents(QWidget):
|
||||
if tTitle.strip() == "":
|
||||
tTitle = self.tr("Untitled")
|
||||
|
||||
newItem.setIcon(self.C_TITLE, self.theTheme.getIcon("doc_h%d" % tLevel))
|
||||
newItem.setIcon(self.C_TITLE, self.mainTheme.getIcon("doc_h%d" % tLevel))
|
||||
newItem.setText(self.C_TITLE, tTitle)
|
||||
newItem.setText(self.C_WORDS, f"{wCount:n}")
|
||||
newItem.setText(self.C_PAGES, f"{pCount:n}")
|
||||
|
||||
@@ -53,21 +53,21 @@ class GuiProjectLoad(QDialog):
|
||||
C_COUNT = 1
|
||||
C_TIME = 2
|
||||
|
||||
def __init__(self, theParent):
|
||||
QDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiProjectLoad ...")
|
||||
self.setObjectName("GuiProjectLoad")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.openState = self.NONE_STATE
|
||||
self.openPath = None
|
||||
|
||||
sPx = self.mainConf.pxInt(16)
|
||||
nPx = self.mainConf.pxInt(96)
|
||||
iPx = self.theTheme.baseIconSize
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.innerBox = QHBoxLayout()
|
||||
@@ -80,7 +80,7 @@ class GuiProjectLoad(QDialog):
|
||||
self.setModal(True)
|
||||
|
||||
self.nwIcon = QLabel()
|
||||
self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.nwIcon.setPixmap(self.mainGui.mainTheme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.innerBox.addWidget(self.nwIcon, 0, Qt.AlignTop)
|
||||
|
||||
self.projectForm = QGridLayout()
|
||||
@@ -110,7 +110,7 @@ class GuiProjectLoad(QDialog):
|
||||
self.selPath.setReadOnly(True)
|
||||
|
||||
self.browseButton = QPushButton("...")
|
||||
self.browseButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||
self.browseButton.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("...")))
|
||||
self.browseButton.clicked.connect(self._doBrowse)
|
||||
|
||||
self.projectForm.addWidget(self.lblRecent, 0, 0, 1, 3)
|
||||
@@ -225,7 +225,7 @@ class GuiProjectLoad(QDialog):
|
||||
selList = self.listBox.selectedItems()
|
||||
if selList:
|
||||
projName = selList[0].text(self.C_NAME)
|
||||
msgYes = self.theParent.askQuestion(
|
||||
msgYes = self.mainGui.askQuestion(
|
||||
self.tr("Remove Entry"),
|
||||
self.tr(
|
||||
"Remove '{0}' from the recent projects list? "
|
||||
@@ -280,7 +280,7 @@ class GuiProjectLoad(QDialog):
|
||||
sortList = sorted(dataList, key=lambda x: x[1], reverse=True)
|
||||
for theTitle, theTime, theWords, projPath in sortList:
|
||||
newItem = QTreeWidgetItem([""]*4)
|
||||
newItem.setIcon(self.C_NAME, self.theParent.theTheme.getIcon("proj_nwx"))
|
||||
newItem.setIcon(self.C_NAME, self.mainGui.mainTheme.getIcon("proj_nwx"))
|
||||
newItem.setText(self.C_NAME, theTitle)
|
||||
newItem.setData(self.C_NAME, Qt.UserRole, projPath)
|
||||
newItem.setText(self.C_COUNT, formatInt(theWords))
|
||||
@@ -288,7 +288,7 @@ class GuiProjectLoad(QDialog):
|
||||
newItem.setTextAlignment(self.C_NAME, Qt.AlignLeft | Qt.AlignVCenter)
|
||||
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight | Qt.AlignVCenter)
|
||||
newItem.setTextAlignment(self.C_TIME, Qt.AlignRight | Qt.AlignVCenter)
|
||||
newItem.setFont(self.C_TIME, self.theTheme.guiFontFixed)
|
||||
newItem.setFont(self.C_TIME, self.mainTheme.guiFontFixed)
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
|
||||
if self.listBox.topLevelItemCount() > 0:
|
||||
|
||||
@@ -35,6 +35,7 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwAlert
|
||||
from novelwriter.common import simplified
|
||||
from novelwriter.gui.custom import QSwitch, PagedDialog, QConfigLayout
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -42,34 +43,34 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiProjectSettings(PagedDialog):
|
||||
|
||||
def __init__(self, theParent):
|
||||
PagedDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiProjectSettings ...")
|
||||
self.setObjectName("GuiProjectSettings")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
self.mainGui = mainGui
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self.theProject.countStatus()
|
||||
self.setWindowTitle(self.tr("Project Settings"))
|
||||
|
||||
wW = self.mainConf.pxInt(570)
|
||||
wH = self.mainConf.pxInt(375)
|
||||
pOptions = self.theProject.options
|
||||
|
||||
self.setMinimumWidth(wW)
|
||||
self.setMinimumHeight(wH)
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winWidth", wW)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiProjectSettings", "winHeight", wH))
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiProjectSettings", "winWidth", wW)),
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiProjectSettings", "winHeight", wH))
|
||||
)
|
||||
|
||||
self.tabMain = GuiProjectEditMain(self.theParent, self.theProject)
|
||||
self.tabStatus = GuiProjectEditStatus(self.theParent, self.theProject, True)
|
||||
self.tabImport = GuiProjectEditStatus(self.theParent, self.theProject, False)
|
||||
self.tabReplace = GuiProjectEditReplace(self.theParent, self.theProject)
|
||||
self.tabMain = GuiProjectEditMain(self)
|
||||
self.tabStatus = GuiProjectEditStatus(self, True)
|
||||
self.tabImport = GuiProjectEditStatus(self, False)
|
||||
self.tabReplace = GuiProjectEditReplace(self)
|
||||
|
||||
self.addTab(self.tabMain, self.tr("Settings"))
|
||||
self.addTab(self.tabStatus, self.tr("Status"))
|
||||
@@ -81,6 +82,9 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.buttonBox.rejected.connect(self._doClose)
|
||||
self.addControls(self.buttonBox)
|
||||
|
||||
# Flags
|
||||
self.spellChanged = False
|
||||
|
||||
logger.debug("GuiProjectSettings initialisation complete")
|
||||
|
||||
return
|
||||
@@ -103,19 +107,21 @@ class GuiProjectSettings(PagedDialog):
|
||||
self.theProject.setProjectName(projName)
|
||||
self.theProject.setBookTitle(bookTitle)
|
||||
self.theProject.setBookAuthors(bookAuthors)
|
||||
self.theProject.setSpellLang(spellLang)
|
||||
self.theProject.setProjBackup(doBackup)
|
||||
|
||||
# Remember this as updating spell dictionary can be expensive
|
||||
self.spellChanged = self.theProject.setSpellLang(spellLang)
|
||||
|
||||
if self.tabStatus.colChanged:
|
||||
statusCol = self.tabStatus.getNewList()
|
||||
self.theProject.setStatusColours(statusCol)
|
||||
newList, delList = self.tabStatus.getNewList()
|
||||
self.theProject.setStatusColours(newList, delList)
|
||||
|
||||
if self.tabImport.colChanged:
|
||||
importCol = self.tabImport.getNewList()
|
||||
self.theProject.setImportColours(importCol)
|
||||
newList, delList = self.tabImport.getNewList()
|
||||
self.theProject.setImportColours(newList, delList)
|
||||
|
||||
if self.tabStatus.colChanged or self.tabImport.colChanged:
|
||||
self.theParent.rebuildTrees()
|
||||
self.mainGui.rebuildTrees()
|
||||
|
||||
if self.tabReplace.arChanged:
|
||||
newList = self.tabReplace.getNewList()
|
||||
@@ -146,11 +152,12 @@ class GuiProjectSettings(PagedDialog):
|
||||
statusColW = self.mainConf.rpxInt(self.tabStatus.listBox.columnWidth(0))
|
||||
importColW = self.mainConf.rpxInt(self.tabImport.listBox.columnWidth(0))
|
||||
|
||||
self.optState.setValue("GuiProjectSettings", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiProjectSettings", "winHeight", winHeight)
|
||||
self.optState.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||
self.optState.setValue("GuiProjectSettings", "statusColW", statusColW)
|
||||
self.optState.setValue("GuiProjectSettings", "importColW", importColW)
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiProjectSettings", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiProjectSettings", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiProjectSettings", "replaceColW", replaceColW)
|
||||
pOptions.setValue("GuiProjectSettings", "statusColW", statusColW)
|
||||
pOptions.setValue("GuiProjectSettings", "importColW", importColW)
|
||||
|
||||
return
|
||||
|
||||
@@ -159,29 +166,29 @@ class GuiProjectSettings(PagedDialog):
|
||||
|
||||
class GuiProjectEditMain(QWidget):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, projGui):
|
||||
super().__init__(parent=projGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.mainGui = projGui.mainGui
|
||||
self.theProject = projGui.theProject
|
||||
|
||||
# The Form
|
||||
self.mainForm = QConfigLayout()
|
||||
self.mainForm.setHelpTextStyle(self.theParent.theTheme.helpText)
|
||||
self.mainForm.setHelpTextStyle(self.mainGui.mainTheme.helpText)
|
||||
self.setLayout(self.mainForm)
|
||||
|
||||
self.mainForm.addGroupLabel(self.tr("Project Settings"))
|
||||
|
||||
xW = self.mainConf.pxInt(250)
|
||||
xH = round(4.8*self.theParent.theTheme.fontPixelSize)
|
||||
xH = round(4.8*self.mainGui.mainTheme.fontPixelSize)
|
||||
|
||||
self.editName = QLineEdit()
|
||||
self.editName.setMaxLength(200)
|
||||
self.editName.setMaximumWidth(xW)
|
||||
self.editName.setText(self.theProject.projName)
|
||||
self.mainForm.addRow(
|
||||
self.tr("Working title"),
|
||||
self.tr("Project name"),
|
||||
self.editName,
|
||||
self.tr("Should be set only once.")
|
||||
)
|
||||
@@ -209,7 +216,7 @@ class GuiProjectEditMain(QWidget):
|
||||
self.spellLang = QComboBox(self)
|
||||
self.spellLang.setMaximumWidth(xW)
|
||||
self.spellLang.addItem(self.tr("Default"), "None")
|
||||
langAvail = self.theParent.docEditor.spEnchant.listDictionaries()
|
||||
langAvail = self.mainGui.docEditor.spEnchant.listDictionaries()
|
||||
for spTag, spProv in langAvail:
|
||||
qLocal = QLocale(spTag)
|
||||
spLang = qLocal.nativeLanguageName().title()
|
||||
@@ -245,14 +252,17 @@ class GuiProjectEditStatus(QWidget):
|
||||
COL_LABEL = 0
|
||||
COL_USAGE = 1
|
||||
|
||||
def __init__(self, theParent, theProject, isStatus):
|
||||
QWidget.__init__(self, theParent)
|
||||
KEY_ROLE = Qt.UserRole
|
||||
COL_ROLE = Qt.UserRole + 1
|
||||
NUM_ROLE = Qt.UserRole + 2
|
||||
|
||||
def __init__(self, projGui, isStatus):
|
||||
super().__init__(parent=projGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theProject
|
||||
self.optState = theProject.optState
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = projGui.mainGui
|
||||
self.theProject = projGui.theProject
|
||||
self.mainTheme = projGui.mainGui.mainTheme
|
||||
|
||||
if isStatus:
|
||||
self.theStatus = self.theProject.statusItems
|
||||
@@ -264,15 +274,14 @@ class GuiProjectEditStatus(QWidget):
|
||||
colSetting = "importColW"
|
||||
|
||||
wCol0 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiProjectSettings", colSetting, 130)
|
||||
self.theProject.options.getInt("GuiProjectSettings", colSetting, 130)
|
||||
)
|
||||
|
||||
self.colData = []
|
||||
self.colCounts = []
|
||||
self.colDeleted = []
|
||||
self.colChanged = False
|
||||
self.selColour = None
|
||||
self.selColour = QColor(100, 100, 100)
|
||||
|
||||
self.iPx = self.theTheme.baseIconSize
|
||||
self.iPx = self.mainTheme.baseIconSize
|
||||
|
||||
# The List
|
||||
# ========
|
||||
@@ -285,18 +294,24 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.listBox.setColumnWidth(self.COL_LABEL, wCol0)
|
||||
self.listBox.setIndentation(0)
|
||||
|
||||
for iName, iCol, nUse in self.theStatus:
|
||||
self._addItem(iName, iCol, iName, nUse)
|
||||
for key, entry in self.theStatus.items():
|
||||
self._addItem(key, entry["name"], entry["cols"], entry["count"])
|
||||
|
||||
# List Controls
|
||||
# =============
|
||||
|
||||
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
|
||||
self.addButton = QPushButton(self.mainTheme.getIcon("add"), "")
|
||||
self.addButton.clicked.connect(self._newItem)
|
||||
|
||||
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
|
||||
self.delButton = QPushButton(self.mainTheme.getIcon("remove"), "")
|
||||
self.delButton.clicked.connect(self._delItem)
|
||||
|
||||
self.upButton = QPushButton(self.mainTheme.getIcon("up"), "")
|
||||
self.upButton.clicked.connect(lambda: self._moveItem(-1))
|
||||
|
||||
self.dnButton = QPushButton(self.mainTheme.getIcon("down"), "")
|
||||
self.dnButton.clicked.connect(lambda: self._moveItem(1))
|
||||
|
||||
# Edit Form
|
||||
# =========
|
||||
|
||||
@@ -306,7 +321,7 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.editName.setPlaceholderText(self.tr("Select item to edit"))
|
||||
|
||||
self.colPixmap = QPixmap(self.iPx, self.iPx)
|
||||
self.colPixmap.fill(QColor(120, 120, 120))
|
||||
self.colPixmap.fill(QColor(100, 100, 100))
|
||||
self.colButton = QPushButton(QIcon(self.colPixmap), self.tr("Colour"))
|
||||
self.colButton.setIconSize(self.colPixmap.rect().size())
|
||||
self.colButton.clicked.connect(self._selectColour)
|
||||
@@ -320,6 +335,8 @@ class GuiProjectEditStatus(QWidget):
|
||||
self.listControls = QVBoxLayout()
|
||||
self.listControls.addWidget(self.addButton)
|
||||
self.listControls.addWidget(self.delButton)
|
||||
self.listControls.addWidget(self.upButton)
|
||||
self.listControls.addWidget(self.dnButton)
|
||||
self.listControls.addStretch(1)
|
||||
|
||||
self.editBox = QHBoxLayout()
|
||||
@@ -349,12 +366,15 @@ class GuiProjectEditStatus(QWidget):
|
||||
if self.colChanged:
|
||||
newList = []
|
||||
for n in range(self.listBox.topLevelItemCount()):
|
||||
nItem = self.listBox.topLevelItem(n)
|
||||
nIdx = nItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
newList.append(self.colData[nIdx])
|
||||
return newList
|
||||
item = self.listBox.topLevelItem(n)
|
||||
newList.append({
|
||||
"key": item.data(self.COL_LABEL, self.KEY_ROLE),
|
||||
"name": item.text(self.COL_LABEL),
|
||||
"cols": item.data(self.COL_LABEL, self.COL_ROLE),
|
||||
})
|
||||
return newList, self.colDeleted
|
||||
|
||||
return None
|
||||
return [], []
|
||||
|
||||
##
|
||||
# User Actions
|
||||
@@ -369,16 +389,16 @@ class GuiProjectEditStatus(QWidget):
|
||||
)
|
||||
if newCol.isValid():
|
||||
self.selColour = newCol
|
||||
colPixmap = QPixmap(self.iPx, self.iPx)
|
||||
colPixmap.fill(newCol)
|
||||
self.colButton.setIcon(QIcon(colPixmap))
|
||||
self.colButton.setIconSize(colPixmap.rect().size())
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(newCol)
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
self.colButton.setIconSize(pixmap.rect().size())
|
||||
return
|
||||
|
||||
def _newItem(self):
|
||||
"""Create a new status item.
|
||||
"""
|
||||
newItem = self._addItem(self.tr("New Item"), (0, 0, 0), None, 0)
|
||||
newItem = self._addItem(None, self.tr("New Item"), (100, 100, 100), 0)
|
||||
newItem.setBackground(self.COL_LABEL, QBrush(QColor(0, 255, 0, 70)))
|
||||
newItem.setBackground(self.COL_USAGE, QBrush(QColor(0, 255, 0, 70)))
|
||||
self.colChanged = True
|
||||
@@ -390,14 +410,14 @@ class GuiProjectEditStatus(QWidget):
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is not None:
|
||||
iRow = self.listBox.indexOfTopLevelItem(selItem)
|
||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
if self.colCounts[selIdx] == 0:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self.colChanged = True
|
||||
else:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Cannot delete a status item that is in use."
|
||||
), nwAlert.ERROR)
|
||||
else:
|
||||
self.listBox.takeTopLevelItem(iRow)
|
||||
self.colDeleted.append(selItem.data(self.COL_LABEL, self.KEY_ROLE))
|
||||
self.colChanged = True
|
||||
return
|
||||
|
||||
def _saveItem(self):
|
||||
@@ -405,53 +425,75 @@ class GuiProjectEditStatus(QWidget):
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is not None:
|
||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
self.colData[selIdx] = (
|
||||
self.editName.text().strip(),
|
||||
self.selColour.red(),
|
||||
self.selColour.green(),
|
||||
self.selColour.blue(),
|
||||
self.colData[selIdx][4]
|
||||
)
|
||||
selItem.setText(self.COL_LABEL, self.colData[selIdx][0])
|
||||
selItem.setText(self.COL_USAGE, self._usageString(self.colCounts[selIdx]))
|
||||
selItem.setText(self.COL_LABEL, simplified(self.editName.text()))
|
||||
selItem.setIcon(self.COL_LABEL, self.colButton.icon())
|
||||
selItem.setData(self.COL_LABEL, self.COL_ROLE, (
|
||||
self.selColour.red(), self.selColour.green(), self.selColour.blue()
|
||||
))
|
||||
self.editName.setEnabled(False)
|
||||
self.colChanged = True
|
||||
|
||||
return
|
||||
|
||||
def _addItem(self, iName, iCol, oName, nUse):
|
||||
def _addItem(self, key, name, cols, count):
|
||||
"""Add a status item to the list.
|
||||
"""
|
||||
newIcon = QPixmap(self.iPx, self.iPx)
|
||||
newIcon.fill(QColor(*iCol))
|
||||
newItem = QTreeWidgetItem()
|
||||
newItem.setText(self.COL_LABEL, iName)
|
||||
newItem.setText(self.COL_USAGE, self._usageString(nUse))
|
||||
newItem.setIcon(self.COL_LABEL, QIcon(newIcon))
|
||||
newItem.setData(self.COL_LABEL, Qt.UserRole, len(self.colData))
|
||||
self.listBox.addTopLevelItem(newItem)
|
||||
self.colData.append((iName, iCol[0], iCol[1], iCol[2], oName))
|
||||
self.colCounts.append(nUse)
|
||||
return newItem
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(*cols))
|
||||
|
||||
item = QTreeWidgetItem()
|
||||
item.setText(self.COL_LABEL, name)
|
||||
item.setIcon(self.COL_LABEL, QIcon(pixmap))
|
||||
item.setData(self.COL_LABEL, self.KEY_ROLE, key)
|
||||
item.setData(self.COL_LABEL, self.COL_ROLE, cols)
|
||||
item.setData(self.COL_LABEL, self.NUM_ROLE, count)
|
||||
item.setText(self.COL_USAGE, self._usageString(count))
|
||||
|
||||
self.listBox.addTopLevelItem(item)
|
||||
|
||||
return item
|
||||
|
||||
def _moveItem(self, step):
|
||||
"""Move and item up or down step.
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is None:
|
||||
return
|
||||
|
||||
tIndex = self.listBox.indexOfTopLevelItem(selItem)
|
||||
nChild = self.listBox.topLevelItemCount()
|
||||
nIndex = tIndex + step
|
||||
if nIndex < 0 or nIndex >= nChild:
|
||||
return
|
||||
|
||||
cItem = self.listBox.takeTopLevelItem(tIndex)
|
||||
self.listBox.insertTopLevelItem(nIndex, cItem)
|
||||
self.listBox.clearSelection()
|
||||
|
||||
cItem.setSelected(True)
|
||||
self.colChanged = True
|
||||
|
||||
return
|
||||
|
||||
def _selectedItem(self):
|
||||
"""Extract the info of a selected item and populate the settings
|
||||
boxes and button.
|
||||
"""
|
||||
selItem = self._getSelectedItem()
|
||||
if selItem is not None:
|
||||
selIdx = selItem.data(self.COL_LABEL, Qt.UserRole)
|
||||
selVal = self.colData[selIdx]
|
||||
self.selColour = QColor(selVal[1], selVal[2], selVal[3])
|
||||
newIcon = QPixmap(self.iPx, self.iPx)
|
||||
newIcon.fill(self.selColour)
|
||||
self.editName.setText(selVal[0])
|
||||
self.colButton.setIcon(QIcon(newIcon))
|
||||
self.editName.setEnabled(True)
|
||||
self.editName.selectAll()
|
||||
self.editName.setFocus()
|
||||
if selItem is None:
|
||||
return
|
||||
|
||||
cols = selItem.data(self.COL_LABEL, self.COL_ROLE)
|
||||
name = selItem.text(self.COL_LABEL)
|
||||
|
||||
pixmap = QPixmap(self.iPx, self.iPx)
|
||||
pixmap.fill(QColor(*cols))
|
||||
self.selColour = QColor(*cols)
|
||||
self.editName.setText(name)
|
||||
self.colButton.setIcon(QIcon(pixmap))
|
||||
self.editName.setEnabled(True)
|
||||
self.editName.selectAll()
|
||||
self.editName.setFocus()
|
||||
|
||||
return
|
||||
|
||||
@@ -467,12 +509,6 @@ class GuiProjectEditStatus(QWidget):
|
||||
return selItem[0]
|
||||
return None
|
||||
|
||||
def _rowsMoved(self):
|
||||
"""A row has been moved, so set the changed flag.
|
||||
"""
|
||||
self.colChanged = True
|
||||
return
|
||||
|
||||
def _usageString(self, nUse):
|
||||
"""Generate usage string.
|
||||
"""
|
||||
@@ -491,18 +527,17 @@ class GuiProjectEditReplace(QWidget):
|
||||
COL_KEY = 0
|
||||
COL_REPL = 1
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, projGui):
|
||||
super().__init__(parent=projGui)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theProject
|
||||
self.optState = theProject.optState
|
||||
self.mainGui = projGui.mainGui
|
||||
self.mainTheme = projGui.mainGui.mainTheme
|
||||
self.theProject = projGui.theProject
|
||||
self.arChanged = False
|
||||
|
||||
wCol0 = self.mainConf.pxInt(
|
||||
self.optState.getInt("GuiProjectSettings", "replaceColW", 130)
|
||||
self.theProject.options.getInt("GuiProjectSettings", "replaceColW", 130)
|
||||
)
|
||||
pageLabel = self.tr("Text Replace List for Preview and Export")
|
||||
|
||||
@@ -528,10 +563,10 @@ class GuiProjectEditReplace(QWidget):
|
||||
# List Controls
|
||||
# =============
|
||||
|
||||
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
|
||||
self.addButton = QPushButton(self.mainTheme.getIcon("add"), "")
|
||||
self.addButton.clicked.connect(self._addEntry)
|
||||
|
||||
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
|
||||
self.delButton = QPushButton(self.mainTheme.getIcon("remove"), "")
|
||||
self.delButton.clicked.connect(self._delEntry)
|
||||
|
||||
# Edit Form
|
||||
|
||||
@@ -42,8 +42,8 @@ class GuiQuoteSelect(QDialog):
|
||||
|
||||
selectedQuote = ""
|
||||
|
||||
def __init__(self, theParent=None, currentQuote='"'):
|
||||
QDialog.__init__(self, parent=theParent)
|
||||
def __init__(self, parent=None, currentQuote='"'):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
|
||||
|
||||
@@ -43,14 +43,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiUpdates(QDialog):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiUpdates ...")
|
||||
self.setObjectName("GuiUpdates")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.mainGui = mainGui
|
||||
|
||||
self.setWindowTitle(self.tr("Check for Updates"))
|
||||
|
||||
@@ -61,7 +61,7 @@ class GuiUpdates(QDialog):
|
||||
|
||||
# Left Box
|
||||
self.nwIcon = QLabel()
|
||||
self.nwIcon.setPixmap(self.theParent.theTheme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
self.nwIcon.setPixmap(self.mainGui.mainTheme.getPixmap("novelwriter", (nPx, nPx)))
|
||||
|
||||
self.leftBox = QVBoxLayout()
|
||||
self.leftBox.addWidget(self.nwIcon)
|
||||
|
||||
@@ -42,29 +42,29 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiWordList(QDialog):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiWordList ...")
|
||||
self.setObjectName("GuiWordList")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self.setWindowTitle(self.tr("Project Word List"))
|
||||
|
||||
mS = self.mainConf.pxInt(250)
|
||||
wW = self.mainConf.pxInt(320)
|
||||
wH = self.mainConf.pxInt(340)
|
||||
pOptions = self.theProject.options
|
||||
|
||||
self.setMinimumWidth(mS)
|
||||
self.setMinimumHeight(mS)
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winWidth", wW)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiWordList", "winHeight", wH))
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiWordList", "winWidth", wW)),
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiWordList", "winHeight", wH))
|
||||
)
|
||||
|
||||
# Main Widgets
|
||||
@@ -78,10 +78,10 @@ class GuiWordList(QDialog):
|
||||
|
||||
self.newEntry = QLineEdit()
|
||||
|
||||
self.addButton = QPushButton(self.theTheme.getIcon("add"), "")
|
||||
self.addButton = QPushButton(self.mainTheme.getIcon("add"), "")
|
||||
self.addButton.clicked.connect(self._doAdd)
|
||||
|
||||
self.delButton = QPushButton(self.theTheme.getIcon("remove"), "")
|
||||
self.delButton = QPushButton(self.mainTheme.getIcon("remove"), "")
|
||||
self.delButton.clicked.connect(self._doDelete)
|
||||
|
||||
self.editBox = QHBoxLayout()
|
||||
@@ -121,13 +121,13 @@ class GuiWordList(QDialog):
|
||||
"""
|
||||
newWord = self.newEntry.text().strip()
|
||||
if newWord == "":
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Cannot add a blank word."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
|
||||
if self.listBox.findItems(newWord, Qt.MatchExactly):
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"The word '{0}' is already in the word list."
|
||||
).format(newWord), nwAlert.ERROR)
|
||||
return False
|
||||
@@ -207,8 +207,9 @@ class GuiWordList(QDialog):
|
||||
winWidth = self.mainConf.rpxInt(self.width())
|
||||
winHeight = self.mainConf.rpxInt(self.height())
|
||||
|
||||
self.optState.setValue("GuiWordList", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiWordList", "winHeight", winHeight)
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiWordList", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiWordList", "winHeight", winHeight)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -32,7 +32,6 @@ class nwItemType(Enum):
|
||||
ROOT = 1
|
||||
FOLDER = 2
|
||||
FILE = 3
|
||||
TRASH = 4
|
||||
|
||||
# END Enum nwItemType
|
||||
|
||||
@@ -63,6 +62,14 @@ class nwItemLayout(Enum):
|
||||
# END Enum nwItemLayout
|
||||
|
||||
|
||||
class nwDocMode(Enum):
|
||||
|
||||
VIEW = 0
|
||||
EDIT = 1
|
||||
|
||||
# END Enum nwDocMode
|
||||
|
||||
|
||||
class nwDocAction(Enum):
|
||||
|
||||
NO_ACTION = 0
|
||||
@@ -131,6 +138,16 @@ class nwState(Enum):
|
||||
# END Enum nwState
|
||||
|
||||
|
||||
class nwView(Enum):
|
||||
|
||||
EDITOR = 0
|
||||
PROJECT = 1
|
||||
NOVEL = 2
|
||||
OUTLINE = 3
|
||||
|
||||
# END Enum nwView
|
||||
|
||||
|
||||
class nwWidget(Enum):
|
||||
|
||||
TREE = 1
|
||||
|
||||
@@ -61,7 +61,7 @@ def formatException(exc):
|
||||
class NWErrorMessage(QDialog):
|
||||
|
||||
def __init__(self, parent):
|
||||
QDialog.__init__(self, parent=parent)
|
||||
super().__init__(parent=parent)
|
||||
self.setObjectName("NWErrorMessage")
|
||||
|
||||
# Widgets
|
||||
@@ -87,6 +87,8 @@ class NWErrorMessage(QDialog):
|
||||
self.mainBox.addWidget(self.btnBox, 2, 0, 1, 2)
|
||||
self.mainBox.setSpacing(16)
|
||||
|
||||
# Pick a random window title from a set of error messages by
|
||||
# Hex, the computer, from Discworld
|
||||
self.setWindowTitle([
|
||||
"+++ Out of Cheese Error +++",
|
||||
"+++ Divide by Cucumber Error +++",
|
||||
|
||||
@@ -23,12 +23,12 @@ from novelwriter.gui.doceditor import GuiDocEditor
|
||||
from novelwriter.gui.docviewer import GuiDocViewer, GuiDocViewDetails
|
||||
from novelwriter.gui.itemdetails import GuiItemDetails
|
||||
from novelwriter.gui.mainmenu import GuiMainMenu
|
||||
from novelwriter.gui.noveltree import GuiNovelTree
|
||||
from novelwriter.gui.outline import GuiOutline
|
||||
from novelwriter.gui.outlinedetails import GuiOutlineDetails
|
||||
from novelwriter.gui.projtree import GuiProjectTree
|
||||
from novelwriter.gui.noveltree import GuiNovelView
|
||||
from novelwriter.gui.outline import GuiOutlineView
|
||||
from novelwriter.gui.projtree import GuiProjectView
|
||||
from novelwriter.gui.statusbar import GuiMainStatus
|
||||
from novelwriter.gui.theme import GuiTheme
|
||||
from novelwriter.gui.viewsbar import GuiViewsBar
|
||||
|
||||
__all__ = [
|
||||
"GuiDocEditor",
|
||||
@@ -37,9 +37,9 @@ __all__ = [
|
||||
"GuiItemDetails",
|
||||
"GuiMainMenu",
|
||||
"GuiMainStatus",
|
||||
"GuiNovelTree",
|
||||
"GuiOutline",
|
||||
"GuiOutlineDetails",
|
||||
"GuiProjectTree",
|
||||
"GuiNovelView",
|
||||
"GuiOutlineView",
|
||||
"GuiProjectView",
|
||||
"GuiTheme",
|
||||
"GuiViewsBar",
|
||||
]
|
||||
|
||||
@@ -202,7 +202,7 @@ class QConfigLayout(QGridLayout):
|
||||
class QHelpLabel(QLabel):
|
||||
|
||||
def __init__(self, theText, textCol, fontSize=0.9):
|
||||
QLabel.__init__(self, theText)
|
||||
super().__init__(theText)
|
||||
|
||||
if isinstance(textCol, QColor):
|
||||
qCol = textCol
|
||||
@@ -376,8 +376,8 @@ class QSwitch(QAbstractButton):
|
||||
|
||||
class PagedDialog(QDialog):
|
||||
|
||||
def __init__(self, theParent=None):
|
||||
QDialog.__init__(self, parent=theParent)
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
|
||||
self._tabBar = VerticalTabBar(self)
|
||||
self._tabBar.setExpanding(False)
|
||||
@@ -409,14 +409,14 @@ class PagedDialog(QDialog):
|
||||
|
||||
return
|
||||
|
||||
def addTab(self, tabWidget, tabLabel):
|
||||
"""Forwards the adding of tabs to the QTabWidget.
|
||||
def addTab(self, widget, label):
|
||||
"""Forward the adding of tabs to the QTabWidget.
|
||||
"""
|
||||
self._tabBox.addTab(tabWidget, tabLabel)
|
||||
self._tabBox.addTab(widget, label)
|
||||
return
|
||||
|
||||
def addControls(self, buttonBar):
|
||||
"""Adds a button bar to the dialog.
|
||||
"""Add a button bar to the dialog.
|
||||
"""
|
||||
self._buttonBox.addWidget(buttonBar)
|
||||
return
|
||||
@@ -426,20 +426,20 @@ class PagedDialog(QDialog):
|
||||
|
||||
class VerticalTabBar(QTabBar):
|
||||
|
||||
def __init__(self, theParent=None):
|
||||
QTabBar.__init__(self, parent=theParent)
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent=parent)
|
||||
self._mW = novelwriter.CONFIG.pxInt(150)
|
||||
return
|
||||
|
||||
def tabSizeHint(self, theIndex):
|
||||
"""Returns a transposed size hint for the rotated bar.
|
||||
def tabSizeHint(self, index):
|
||||
"""Return a transposed size hint for the rotated bar.
|
||||
"""
|
||||
tSize = QTabBar.tabSizeHint(self, theIndex)
|
||||
tSize = super().tabSizeHint(index)
|
||||
tSize.transpose()
|
||||
tSize.setWidth(min(tSize.width(), self._mW))
|
||||
return tSize
|
||||
|
||||
def paintEvent(self, theEvent):
|
||||
def paintEvent(self, event):
|
||||
"""Custom implementation of the label painter that rotates the
|
||||
label 90 degrees.
|
||||
"""
|
||||
|
||||
@@ -33,6 +33,7 @@ import bisect
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from enum import Enum
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtCore import (
|
||||
@@ -50,7 +51,7 @@ from PyQt5.QtWidgets import (
|
||||
)
|
||||
|
||||
from novelwriter.core import NWDoc, NWSpellEnchant, countWords
|
||||
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwItemClass
|
||||
from novelwriter.enum import nwAlert, nwDocAction, nwDocInsert, nwDocMode
|
||||
from novelwriter.common import transferCase
|
||||
from novelwriter.constants import nwConst, nwKeyWords, nwUnicode
|
||||
from novelwriter.gui.dochighlight import GuiDocHighlighter
|
||||
@@ -69,18 +70,18 @@ class GuiDocEditor(QTextEdit):
|
||||
spellDictionaryChanged = pyqtSignal(str, str)
|
||||
docEditedStatusChanged = pyqtSignal(bool)
|
||||
docCountsChanged = pyqtSignal(str, int, int, int)
|
||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||
|
||||
def __init__(self, theParent):
|
||||
QTextEdit.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiDocEditor ...")
|
||||
|
||||
# Class Variables
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.theProject = theParent.theProject
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self._nwDocument = None
|
||||
self._nwItem = None
|
||||
@@ -124,7 +125,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
# Syntax
|
||||
self.spEnchant = NWSpellEnchant()
|
||||
self.highLight = GuiDocHighlighter(qDoc, self.theParent, self.spEnchant)
|
||||
self.highLight = GuiDocHighlighter(qDoc, self.mainGui, self.spEnchant)
|
||||
|
||||
# Context Menu
|
||||
self.setContextMenuPolicy(Qt.CustomContextMenu)
|
||||
@@ -132,8 +133,9 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
# Editor Settings
|
||||
self.setMinimumWidth(self.mainConf.pxInt(300))
|
||||
self.setAutoFillBackground(True)
|
||||
self.setAcceptRichText(False)
|
||||
self.setAutoFillBackground(True)
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
|
||||
# Custom Shortcuts
|
||||
QShortcut(
|
||||
@@ -238,10 +240,10 @@ class GuiDocEditor(QTextEdit):
|
||||
if self.mainConf.textFont is None:
|
||||
# If none is defined, set a default font
|
||||
theFont = QFont()
|
||||
if self.mainConf.osWindows and "Arial" in self.theTheme.guiFontDB.families():
|
||||
if self.mainConf.osWindows and "Arial" in self.mainTheme.guiFontDB.families():
|
||||
theFont.setFamily("Arial")
|
||||
theFont.setPointSize(12)
|
||||
elif self.mainConf.osDarwin and "Courier" in self.theTheme.guiFontDB.families():
|
||||
elif self.mainConf.osDarwin and "Courier" in self.mainTheme.guiFontDB.families():
|
||||
theFont.setFamily("Courier")
|
||||
theFont.setPointSize(12)
|
||||
else:
|
||||
@@ -256,19 +258,20 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
# Set the widget colours to match syntax theme
|
||||
mainPalette = self.palette()
|
||||
mainPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*self.theTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
|
||||
mainPalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
self.setPalette(mainPalette)
|
||||
|
||||
docPalette = self.viewport().palette()
|
||||
docPalette.setColor(QPalette.Base, QColor(*self.theTheme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
|
||||
docPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
self.viewport().setPalette(docPalette)
|
||||
|
||||
self.docHeader.matchColours()
|
||||
self.docFooter.matchColours()
|
||||
|
||||
# Set default text margins
|
||||
# Due to cursor visibility, a part of the margin must be
|
||||
# allocated to the document itself. See issue #1112.
|
||||
cW = self.cursorWidth()
|
||||
@@ -342,7 +345,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
docSize = len(theDoc)
|
||||
if docSize > nwConst.MAX_DOCSIZE:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"The document you are trying to open is too big. "
|
||||
"The document size is {0} MB. "
|
||||
"The maximum size allowed is {1} MB."
|
||||
@@ -403,7 +406,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self.document().rootFrame().setFrameFormat(docFrame)
|
||||
|
||||
self.docFooter.updateLineCount()
|
||||
self._docHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
||||
self._docHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
|
||||
|
||||
qApp.processEvents()
|
||||
self.document().clearUndoRedoStacks()
|
||||
@@ -418,7 +421,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
# Update the status bar
|
||||
if self._nwItem is not None:
|
||||
self.theParent.setStatus(
|
||||
self.mainGui.setStatus(
|
||||
self.tr("Opened Document: {0}").format(self._nwItem.itemName)
|
||||
)
|
||||
|
||||
@@ -443,7 +446,7 @@ class GuiDocEditor(QTextEdit):
|
||||
"""
|
||||
docSize = len(theText)
|
||||
if docSize > nwConst.MAX_DOCSIZE:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"The text you are trying to add is too big. "
|
||||
"The text size is {0} MB. "
|
||||
"The maximum size allowed is {1} MB."
|
||||
@@ -489,7 +492,7 @@ class GuiDocEditor(QTextEdit):
|
||||
if not self._nwDocument.writeDocument(docText):
|
||||
saveOk = False
|
||||
if self._nwDocument._currHash != self._nwDocument._prevHash:
|
||||
msgYes = self.theParent.askQuestion(
|
||||
msgYes = self.mainGui.askQuestion(
|
||||
self.tr("File Changed on Disk"),
|
||||
self.tr(
|
||||
"This document has been changed outside of novelWriter "
|
||||
@@ -500,7 +503,7 @@ class GuiDocEditor(QTextEdit):
|
||||
saveOk = self._nwDocument.writeDocument(docText, forceWrite=True)
|
||||
|
||||
if not saveOk:
|
||||
self.theParent.makeAlert([
|
||||
self.mainGui.makeAlert([
|
||||
self.tr("Could not save document."), self._nwDocument.getError()
|
||||
], nwAlert.ERROR)
|
||||
|
||||
@@ -508,22 +511,24 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
self.setDocumentChanged(False)
|
||||
|
||||
oldHeader = self.theIndex.getHandleHeaderLevel(tHandle)
|
||||
self.theIndex.scanText(tHandle, docText)
|
||||
newHeader = self.theIndex.getHandleHeaderLevel(tHandle)
|
||||
oldHeader = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
self.theProject.index.scanText(tHandle, docText)
|
||||
newHeader = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
|
||||
# ToDo: This should be a signal
|
||||
if self._updateHeaders(checkLevel=True):
|
||||
self.theParent.requestNovelTreeRefresh()
|
||||
self.mainGui.requestNovelTreeRefresh()
|
||||
else:
|
||||
self.theParent.novelView.updateWordCounts(tHandle)
|
||||
self.mainGui.novelView.updateWordCounts(tHandle)
|
||||
|
||||
# ToDo: This should be a signal
|
||||
if oldHeader != newHeader:
|
||||
self.theParent.treeView.setTreeItemValues(tHandle)
|
||||
self.theParent.treeMeta.updateViewBox(tHandle)
|
||||
self.mainGui.projView.setTreeItemValues(tHandle)
|
||||
self.mainGui.itemDetails.updateViewBox(tHandle)
|
||||
self.docFooter.updateInfo()
|
||||
|
||||
# Update the status bar
|
||||
self.theParent.setStatus(
|
||||
self.mainGui.setStatus(
|
||||
self.tr("Saved Document: {0}").format(self._nwItem.itemName)
|
||||
)
|
||||
|
||||
@@ -544,8 +549,8 @@ class GuiDocEditor(QTextEdit):
|
||||
sH = hBar.height() if hBar.isVisible() else 0
|
||||
|
||||
tM = self._vpMargin
|
||||
if self.mainConf.textWidth > 0 or self.theParent.isFocusMode:
|
||||
tW = self.mainConf.getTextWidth(self.theParent.isFocusMode)
|
||||
if self.mainConf.textWidth > 0 or self.mainGui.isFocusMode:
|
||||
tW = self.mainConf.getTextWidth(self.mainGui.isFocusMode)
|
||||
tM = max((wW - sW - tW)//2, self._vpMargin)
|
||||
|
||||
tB = self.frameWidth()
|
||||
@@ -569,16 +574,6 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
return
|
||||
|
||||
def updateDocInfo(self, tHandle):
|
||||
"""Called when an item label is changed to check if the document
|
||||
title bar needs updating,
|
||||
"""
|
||||
if tHandle == self._docHandle:
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
self.docFooter.updateInfo()
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -714,7 +709,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
if not self.mainConf.hasEnchant:
|
||||
if theMode:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Spell checking requires the package PyEnchant. "
|
||||
"It does not appear to be installed."
|
||||
), nwAlert.INFO)
|
||||
@@ -724,7 +719,7 @@ class GuiDocEditor(QTextEdit):
|
||||
theMode = False
|
||||
|
||||
self._spellCheck = theMode
|
||||
self.theParent.mainMenu.setSpellCheck(theMode)
|
||||
self.mainGui.mainMenu.setSpellCheck(theMode)
|
||||
self.theProject.setSpellCheck(theMode)
|
||||
self.highLight.setSpellCheck(theMode)
|
||||
if not self._bigDoc:
|
||||
@@ -752,7 +747,7 @@ class GuiDocEditor(QTextEdit):
|
||||
qApp.restoreOverrideCursor()
|
||||
afTime = time()
|
||||
logger.debug("Document highlighted in %.3f ms", 1000*(afTime-bfTime))
|
||||
self.theParent.statusBar.setStatus(self.tr("Spell check complete"))
|
||||
self.mainGui.statusBar.setStatus(self.tr("Spell check complete"))
|
||||
|
||||
return True
|
||||
|
||||
@@ -1008,7 +1003,7 @@ class GuiDocEditor(QTextEdit):
|
||||
if self.mainConf.autoScroll:
|
||||
|
||||
cOld = self.cursorRect().center().y()
|
||||
QTextEdit.keyPressEvent(self, keyEvent)
|
||||
super().keyPressEvent(keyEvent)
|
||||
|
||||
kMod = keyEvent.modifiers()
|
||||
okMod = kMod == Qt.NoModifier or kMod == Qt.ShiftModifier
|
||||
@@ -1027,7 +1022,7 @@ class GuiDocEditor(QTextEdit):
|
||||
doAnim.start()
|
||||
|
||||
else:
|
||||
QTextEdit.keyPressEvent(self, keyEvent)
|
||||
super().keyPressEvent(keyEvent)
|
||||
|
||||
self.docFooter.updateLineCount()
|
||||
|
||||
@@ -1054,7 +1049,7 @@ class GuiDocEditor(QTextEdit):
|
||||
theCursor = self.cursorForPosition(theEvent.pos())
|
||||
self._followTag(theCursor)
|
||||
|
||||
QTextEdit.mouseReleaseEvent(self, theEvent)
|
||||
super().mouseReleaseEvent(theEvent)
|
||||
self.docFooter.updateLineCount()
|
||||
|
||||
return
|
||||
@@ -1064,11 +1059,26 @@ class GuiDocEditor(QTextEdit):
|
||||
has its margins adjusted according to user preferences.
|
||||
"""
|
||||
self.updateDocMargins()
|
||||
QTextEdit.resizeEvent(self, theEvent)
|
||||
super().resizeEvent(theEvent)
|
||||
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)
|
||||
@@ -1080,7 +1090,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self._lastFind = None
|
||||
|
||||
if self.document().characterCount() > nwConst.MAX_DOCSIZE:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"The document has grown too big and you cannot add more text to it. "
|
||||
"The maximum size of a single novelWriter document is {0} MB."
|
||||
).format(
|
||||
@@ -1240,7 +1250,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
if time() - self._lastEdit < 5 * self.wcInterval:
|
||||
logger.verbose("Running word counter")
|
||||
self.theParent.threadPool.start(self.wCounterDoc)
|
||||
self.mainGui.threadPool.start(self.wCounterDoc)
|
||||
|
||||
return
|
||||
|
||||
@@ -1297,7 +1307,7 @@ class GuiDocEditor(QTextEdit):
|
||||
logger.verbose("Selection word counter is busy")
|
||||
return
|
||||
|
||||
self.theParent.threadPool.start(self.wCounterSel)
|
||||
self.mainGui.threadPool.start(self.wCounterSel)
|
||||
|
||||
return
|
||||
|
||||
@@ -1376,7 +1386,7 @@ class GuiDocEditor(QTextEdit):
|
||||
self.docSearch.setResultCount(0, 0)
|
||||
self._lastFind = None
|
||||
if self.docSearch.doNextFile and not goBack:
|
||||
self.theParent.openNextDocument(
|
||||
self.mainGui.openNextDocument(
|
||||
self._docHandle, wrapAround=self.docSearch.doLoop
|
||||
)
|
||||
self.beginSearch()
|
||||
@@ -1396,7 +1406,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
if resIdx > maxIdx:
|
||||
if self.docSearch.doNextFile and not goBack:
|
||||
self.theParent.openNextDocument(
|
||||
self.mainGui.openNextDocument(
|
||||
self._docHandle, wrapAround=self.docSearch.doLoop
|
||||
)
|
||||
self.beginSearch()
|
||||
@@ -1640,7 +1650,7 @@ class GuiDocEditor(QTextEdit):
|
||||
"""
|
||||
theCursor = self.textCursor()
|
||||
if not theCursor.hasSelection():
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Please select some text before calling replace quotes."
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
@@ -1896,7 +1906,7 @@ class GuiDocEditor(QTextEdit):
|
||||
|
||||
if loadTag:
|
||||
logger.verbose("Attempting to follow tag '%s'", theWord)
|
||||
self.theParent.docViewer.loadFromTag(theWord)
|
||||
self.loadDocumentTagRequest.emit(theWord, nwDocMode.VIEW)
|
||||
else:
|
||||
logger.verbose("Potential tag '%s'", theWord)
|
||||
|
||||
@@ -1988,12 +1998,16 @@ class GuiDocEditor(QTextEdit):
|
||||
tInsert = nwUnicode.U_PSEP
|
||||
|
||||
tCheck = tInsert
|
||||
if tCheck in self.mainConf.fmtPadBefore:
|
||||
if self.mainConf.fmtPadBefore and tCheck in self.mainConf.fmtPadBefore:
|
||||
if self._allowSpaceBeforeColon(theText, tCheck):
|
||||
nDelete = max(nDelete, 1)
|
||||
chkPos = thePos - nDelete - 1
|
||||
if chkPos >= 0 and theText[chkPos].isspace():
|
||||
# Strip existing space before inserting a new (#1061)
|
||||
nDelete += 1
|
||||
tInsert = self._typPadChar + tInsert
|
||||
|
||||
if tCheck in self.mainConf.fmtPadAfter:
|
||||
if self.mainConf.fmtPadAfter and tCheck in self.mainConf.fmtPadAfter:
|
||||
if self._allowSpaceBeforeColon(theText, tCheck):
|
||||
nDelete = max(nDelete, 1)
|
||||
tInsert = tInsert + self._typPadChar
|
||||
@@ -2008,7 +2022,7 @@ class GuiDocEditor(QTextEdit):
|
||||
def _allowSpaceBeforeColon(text, char):
|
||||
"""Special checker function only used by the insert space
|
||||
feature for French, Spanish, etc, so it doesn't insert a
|
||||
sapce before colons in meta data lines.
|
||||
space before colons in meta data lines. See issue #1090.
|
||||
"""
|
||||
if char == ":" and len(text) > 1:
|
||||
if text[0] == "@":
|
||||
@@ -2025,7 +2039,7 @@ class GuiDocEditor(QTextEdit):
|
||||
if self._docHandle is None:
|
||||
return False
|
||||
|
||||
newHeaders = self.theIndex.getHandleHeaders(self._docHandle)
|
||||
newHeaders = self.theProject.index.getHandleHeaders(self._docHandle)
|
||||
if checkPos:
|
||||
newPos = [x[0] for x in newHeaders]
|
||||
oldPos = [x[0] for x in self._docHeaders]
|
||||
@@ -2147,7 +2161,7 @@ class GuiDocEditor(QTextEdit):
|
||||
class BackgroundWordCounter(QRunnable):
|
||||
|
||||
def __init__(self, docEditor, forSelection=False):
|
||||
QRunnable.__init__(self)
|
||||
super().__init__()
|
||||
|
||||
self._docEditor = docEditor
|
||||
self._forSelection = forSelection
|
||||
@@ -2197,15 +2211,15 @@ class BackgroundWordCounterSignals(QObject):
|
||||
class GuiDocEditSearch(QFrame):
|
||||
|
||||
def __init__(self, docEditor):
|
||||
QFrame.__init__(self, docEditor)
|
||||
super().__init__(parent=docEditor)
|
||||
|
||||
logger.debug("Initialising GuiDocEditSearch ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.docEditor = docEditor
|
||||
self.theParent = docEditor.theParent
|
||||
self.mainGui = docEditor.mainGui
|
||||
self.theProject = docEditor.theProject
|
||||
self.theTheme = docEditor.theTheme
|
||||
self.mainTheme = docEditor.mainTheme
|
||||
|
||||
self.repVisible = False
|
||||
self.isCaseSense = self.mainConf.searchCase
|
||||
@@ -2216,9 +2230,9 @@ class GuiDocEditSearch(QFrame):
|
||||
self.doMatchCap = self.mainConf.searchMatchCap
|
||||
|
||||
mPx = self.mainConf.pxInt(6)
|
||||
tPx = int(0.8*self.theTheme.fontPixelSize)
|
||||
self.boxFont = self.theTheme.guiFont
|
||||
self.boxFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
|
||||
tPx = int(0.8*self.mainTheme.fontPixelSize)
|
||||
self.boxFont = self.mainTheme.guiFont
|
||||
self.boxFont.setPointSizeF(0.9*self.mainTheme.fontPointSize)
|
||||
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setAutoFillBackground(True)
|
||||
@@ -2252,38 +2266,38 @@ class GuiDocEditSearch(QFrame):
|
||||
|
||||
self.resultLabel = QLabel("?/?")
|
||||
self.resultLabel.setFont(self.boxFont)
|
||||
self.resultLabel.setMinimumWidth(self.theTheme.getTextWidth("?/?", self.boxFont))
|
||||
self.resultLabel.setMinimumWidth(self.mainTheme.getTextWidth("?/?", self.boxFont))
|
||||
|
||||
self.toggleCase = QAction(self.tr("Case Sensitive"), self)
|
||||
self.toggleCase.setIcon(self.theTheme.getIcon("search_case"))
|
||||
self.toggleCase.setIcon(self.mainTheme.getIcon("search_case"))
|
||||
self.toggleCase.setCheckable(True)
|
||||
self.toggleCase.setChecked(self.isCaseSense)
|
||||
self.toggleCase.toggled.connect(self._doToggleCase)
|
||||
self.searchOpt.addAction(self.toggleCase)
|
||||
|
||||
self.toggleWord = QAction(self.tr("Whole Words Only"), self)
|
||||
self.toggleWord.setIcon(self.theTheme.getIcon("search_word"))
|
||||
self.toggleWord.setIcon(self.mainTheme.getIcon("search_word"))
|
||||
self.toggleWord.setCheckable(True)
|
||||
self.toggleWord.setChecked(self.isWholeWord)
|
||||
self.toggleWord.toggled.connect(self._doToggleWord)
|
||||
self.searchOpt.addAction(self.toggleWord)
|
||||
|
||||
self.toggleRegEx = QAction(self.tr("RegEx Mode"), self)
|
||||
self.toggleRegEx.setIcon(self.theTheme.getIcon("search_regex"))
|
||||
self.toggleRegEx.setIcon(self.mainTheme.getIcon("search_regex"))
|
||||
self.toggleRegEx.setCheckable(True)
|
||||
self.toggleRegEx.setChecked(self.isRegEx)
|
||||
self.toggleRegEx.toggled.connect(self._doToggleRegEx)
|
||||
self.searchOpt.addAction(self.toggleRegEx)
|
||||
|
||||
self.toggleLoop = QAction(self.tr("Loop Search"), self)
|
||||
self.toggleLoop.setIcon(self.theTheme.getIcon("search_loop"))
|
||||
self.toggleLoop.setIcon(self.mainTheme.getIcon("search_loop"))
|
||||
self.toggleLoop.setCheckable(True)
|
||||
self.toggleLoop.setChecked(self.doLoop)
|
||||
self.toggleLoop.toggled.connect(self._doToggleLoop)
|
||||
self.searchOpt.addAction(self.toggleLoop)
|
||||
|
||||
self.toggleProject = QAction(self.tr("Search Next File"), self)
|
||||
self.toggleProject.setIcon(self.theTheme.getIcon("search_project"))
|
||||
self.toggleProject.setIcon(self.mainTheme.getIcon("search_project"))
|
||||
self.toggleProject.setCheckable(True)
|
||||
self.toggleProject.setChecked(self.doNextFile)
|
||||
self.toggleProject.toggled.connect(self._doToggleProject)
|
||||
@@ -2292,7 +2306,7 @@ class GuiDocEditSearch(QFrame):
|
||||
self.searchOpt.addSeparator()
|
||||
|
||||
self.toggleMatchCap = QAction(self.tr("Preserve Case"), self)
|
||||
self.toggleMatchCap.setIcon(self.theTheme.getIcon("search_preserve"))
|
||||
self.toggleMatchCap.setIcon(self.mainTheme.getIcon("search_preserve"))
|
||||
self.toggleMatchCap.setCheckable(True)
|
||||
self.toggleMatchCap.setChecked(self.doMatchCap)
|
||||
self.toggleMatchCap.toggled.connect(self._doToggleMatchCap)
|
||||
@@ -2301,7 +2315,7 @@ class GuiDocEditSearch(QFrame):
|
||||
self.searchOpt.addSeparator()
|
||||
|
||||
self.cancelSearch = QAction(self.tr("Close Search"), self)
|
||||
self.cancelSearch.setIcon(self.theTheme.getIcon("search_cancel"))
|
||||
self.cancelSearch.setIcon(self.mainTheme.getIcon("search_cancel"))
|
||||
self.cancelSearch.triggered.connect(self._doClose)
|
||||
self.searchOpt.addAction(self.cancelSearch)
|
||||
|
||||
@@ -2316,12 +2330,12 @@ class GuiDocEditSearch(QFrame):
|
||||
self.showReplace.setStyleSheet("QToolButton {border: none; background: transparent;}")
|
||||
self.showReplace.toggled.connect(self._doToggleReplace)
|
||||
|
||||
self.searchButton = QPushButton(self.theTheme.getIcon("search"), "")
|
||||
self.searchButton = QPushButton(self.mainTheme.getIcon("search"), "")
|
||||
self.searchButton.setFixedSize(QSize(bPx, bPx))
|
||||
self.searchButton.setToolTip(self.tr("Find in current document"))
|
||||
self.searchButton.clicked.connect(self._doSearch)
|
||||
|
||||
self.replaceButton = QPushButton(self.theTheme.getIcon("search_replace"), "")
|
||||
self.replaceButton = QPushButton(self.mainTheme.getIcon("search_replace"), "")
|
||||
self.replaceButton.setFixedSize(QSize(bPx, bPx))
|
||||
self.replaceButton.setToolTip(self.tr("Find and replace in current document"))
|
||||
self.replaceButton.clicked.connect(self._doReplace)
|
||||
@@ -2440,7 +2454,7 @@ class GuiDocEditSearch(QFrame):
|
||||
"""
|
||||
currRes = "?" if currRes is None else currRes
|
||||
resCount = "?" if resCount is None else "1000+" if resCount > 1000 else resCount
|
||||
minWidth = self.theTheme.getTextWidth(f"{resCount}//{resCount}", self.boxFont)
|
||||
minWidth = self.mainTheme.getTextWidth(f"{resCount}//{resCount}", self.boxFont)
|
||||
self.resultLabel.setText(f"{currRes}/{resCount}")
|
||||
self.resultLabel.setMinimumWidth(minWidth)
|
||||
self.adjustSize()
|
||||
@@ -2585,19 +2599,19 @@ class GuiDocEditSearch(QFrame):
|
||||
class GuiDocEditHeader(QWidget):
|
||||
|
||||
def __init__(self, docEditor):
|
||||
QWidget.__init__(self, docEditor)
|
||||
super().__init__(parent=docEditor)
|
||||
|
||||
logger.debug("Initialising GuiDocEditHeader ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.docEditor = docEditor
|
||||
self.theParent = docEditor.theParent
|
||||
self.mainGui = docEditor.mainGui
|
||||
self.theProject = docEditor.theProject
|
||||
self.theTheme = docEditor.theTheme
|
||||
self.mainTheme = docEditor.mainTheme
|
||||
|
||||
self._docHandle = None
|
||||
|
||||
fPx = int(0.9*self.theTheme.fontPixelSize)
|
||||
fPx = int(0.9*self.mainTheme.fontPixelSize)
|
||||
hSp = self.mainConf.pxInt(6)
|
||||
|
||||
# Main Widget Settings
|
||||
@@ -2614,17 +2628,17 @@ class GuiDocEditHeader(QWidget):
|
||||
self.theTitle.setFixedHeight(fPx)
|
||||
|
||||
lblFont = self.theTitle.font()
|
||||
lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize)
|
||||
self.theTitle.setFont(lblFont)
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*self.theTheme.colText)
|
||||
).format(*self.mainTheme.colText)
|
||||
|
||||
# Buttons
|
||||
self.editButton = QToolButton(self)
|
||||
self.editButton.setIcon(self.theTheme.getIcon("edit"))
|
||||
self.editButton.setIcon(self.mainTheme.getIcon("edit"))
|
||||
self.editButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.editButton.setIconSize(QSize(fPx, fPx))
|
||||
self.editButton.setFixedSize(fPx, fPx)
|
||||
@@ -2635,7 +2649,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.editButton.clicked.connect(self._editDocument)
|
||||
|
||||
self.searchButton = QToolButton(self)
|
||||
self.searchButton.setIcon(self.theTheme.getIcon("search"))
|
||||
self.searchButton.setIcon(self.mainTheme.getIcon("search"))
|
||||
self.searchButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.searchButton.setIconSize(QSize(fPx, fPx))
|
||||
self.searchButton.setFixedSize(fPx, fPx)
|
||||
@@ -2646,7 +2660,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.searchButton.clicked.connect(self._searchDocument)
|
||||
|
||||
self.minmaxButton = QToolButton(self)
|
||||
self.minmaxButton.setIcon(self.theTheme.getIcon("maximise"))
|
||||
self.minmaxButton.setIcon(self.mainTheme.getIcon("maximise"))
|
||||
self.minmaxButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.minmaxButton.setIconSize(QSize(fPx, fPx))
|
||||
self.minmaxButton.setFixedSize(fPx, fPx)
|
||||
@@ -2657,7 +2671,7 @@ class GuiDocEditHeader(QWidget):
|
||||
self.minmaxButton.clicked.connect(self._minmaxDocument)
|
||||
|
||||
self.closeButton = QToolButton(self)
|
||||
self.closeButton.setIcon(self.theTheme.getIcon("close"))
|
||||
self.closeButton.setIcon(self.mainTheme.getIcon("close"))
|
||||
self.closeButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.closeButton.setIconSize(QSize(fPx, fPx))
|
||||
self.closeButton.setFixedSize(fPx, fPx)
|
||||
@@ -2700,9 +2714,9 @@ class GuiDocEditHeader(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
thePalette = QPalette()
|
||||
thePalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*self.theTheme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
|
||||
thePalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*self.mainTheme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
|
||||
self.setPalette(thePalette)
|
||||
self.theTitle.setPalette(thePalette)
|
||||
@@ -2724,15 +2738,15 @@ class GuiDocEditHeader(QWidget):
|
||||
|
||||
if self.mainConf.showFullPath:
|
||||
tTitle = []
|
||||
tTree = self.theProject.projTree.getItemPath(tHandle)
|
||||
tTree = self.theProject.tree.getItemPath(tHandle)
|
||||
for aHandle in reversed(tTree):
|
||||
nwItem = self.theProject.projTree[aHandle]
|
||||
nwItem = self.theProject.tree[aHandle]
|
||||
if nwItem is not None:
|
||||
tTitle.append(nwItem.itemName)
|
||||
sSep = " %s " % nwUnicode.U_RSAQUO
|
||||
self.theTitle.setText(sSep.join(tTitle))
|
||||
else:
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem is None:
|
||||
return False
|
||||
self.theTitle.setText(nwItem.itemName)
|
||||
@@ -2749,10 +2763,10 @@ class GuiDocEditHeader(QWidget):
|
||||
This function is called by the GuiMain class via the
|
||||
toggleFocusMode function and should not be activated directly.
|
||||
"""
|
||||
if self.theParent.isFocusMode:
|
||||
self.minmaxButton.setIcon(self.theTheme.getIcon("minimise"))
|
||||
if self.mainGui.isFocusMode:
|
||||
self.minmaxButton.setIcon(self.mainTheme.getIcon("minimise"))
|
||||
else:
|
||||
self.minmaxButton.setIcon(self.theTheme.getIcon("maximise"))
|
||||
self.minmaxButton.setIcon(self.mainTheme.getIcon("maximise"))
|
||||
return
|
||||
|
||||
##
|
||||
@@ -2762,7 +2776,7 @@ class GuiDocEditHeader(QWidget):
|
||||
def _editDocument(self):
|
||||
"""Open the edit item dialog from the main GUI.
|
||||
"""
|
||||
self.theParent.editItem(self._docHandle)
|
||||
self.mainGui.editItemLabel(self._docHandle)
|
||||
return
|
||||
|
||||
def _searchDocument(self):
|
||||
@@ -2774,7 +2788,7 @@ class GuiDocEditHeader(QWidget):
|
||||
def _closeDocument(self):
|
||||
"""Trigger the close editor on the main window.
|
||||
"""
|
||||
self.theParent.closeDocEditor()
|
||||
self.mainGui.closeDocEditor()
|
||||
self.editButton.setVisible(False)
|
||||
self.searchButton.setVisible(False)
|
||||
self.closeButton.setVisible(False)
|
||||
@@ -2784,7 +2798,7 @@ class GuiDocEditHeader(QWidget):
|
||||
def _minmaxDocument(self):
|
||||
"""Switch on or off Focus Mode.
|
||||
"""
|
||||
self.theParent.toggleFocusMode()
|
||||
self.mainGui.toggleFocusMode()
|
||||
return
|
||||
|
||||
##
|
||||
@@ -2795,7 +2809,7 @@ class GuiDocEditHeader(QWidget):
|
||||
"""Capture a click on the title and ensure that the item is
|
||||
selected in the project tree.
|
||||
"""
|
||||
self.theParent.treeView.setSelectedHandle(self._docHandle, doScroll=True)
|
||||
self.mainGui.projView.setSelectedHandle(self._docHandle, doScroll=True)
|
||||
return
|
||||
|
||||
# END Class GuiDocEditHeader
|
||||
@@ -2809,29 +2823,28 @@ class GuiDocEditHeader(QWidget):
|
||||
class GuiDocEditFooter(QWidget):
|
||||
|
||||
def __init__(self, docEditor):
|
||||
QWidget.__init__(self, docEditor)
|
||||
super().__init__(parent=docEditor)
|
||||
|
||||
logger.debug("Initialising GuiDocEditFooter ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.docEditor = docEditor
|
||||
self.theParent = docEditor.theParent
|
||||
self.mainGui = docEditor.mainGui
|
||||
self.theProject = docEditor.theProject
|
||||
self.theTheme = docEditor.theTheme
|
||||
self.optState = docEditor.theProject.optState
|
||||
self.mainTheme = docEditor.mainTheme
|
||||
|
||||
self._theItem = None
|
||||
self._docHandle = None
|
||||
|
||||
self._docSelection = False
|
||||
|
||||
self.sPx = int(round(0.9*self.theTheme.baseIconSize))
|
||||
fPx = int(0.9*self.theTheme.fontPixelSize)
|
||||
self.sPx = int(round(0.9*self.mainTheme.baseIconSize))
|
||||
fPx = int(0.9*self.mainTheme.fontPixelSize)
|
||||
bSp = self.mainConf.pxInt(4)
|
||||
hSp = self.mainConf.pxInt(6)
|
||||
|
||||
lblFont = self.font()
|
||||
lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize)
|
||||
|
||||
# Main Widget Settings
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
@@ -2854,7 +2867,7 @@ class GuiDocEditFooter(QWidget):
|
||||
|
||||
# Lines
|
||||
self.linesIcon = QLabel("")
|
||||
self.linesIcon.setPixmap(self.theTheme.getPixmap("status_lines", (self.sPx, self.sPx)))
|
||||
self.linesIcon.setPixmap(self.mainTheme.getPixmap("status_lines", (self.sPx, self.sPx)))
|
||||
self.linesIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.linesIcon.setFixedHeight(self.sPx)
|
||||
self.linesIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
@@ -2870,7 +2883,7 @@ class GuiDocEditFooter(QWidget):
|
||||
|
||||
# Words
|
||||
self.wordsIcon = QLabel("")
|
||||
self.wordsIcon.setPixmap(self.theTheme.getPixmap("status_stats", (self.sPx, self.sPx)))
|
||||
self.wordsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (self.sPx, self.sPx)))
|
||||
self.wordsIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.wordsIcon.setFixedHeight(self.sPx)
|
||||
self.wordsIcon.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
@@ -2922,9 +2935,9 @@ class GuiDocEditFooter(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
thePalette = QPalette()
|
||||
thePalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*self.theTheme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
|
||||
thePalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*self.mainTheme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
|
||||
self.setPalette(thePalette)
|
||||
self.statusText.setPalette(thePalette)
|
||||
@@ -2941,7 +2954,7 @@ class GuiDocEditFooter(QWidget):
|
||||
logger.verbose("No handle set, so clearing the editor footer")
|
||||
self._theItem = None
|
||||
else:
|
||||
self._theItem = self.theProject.projTree[self._docHandle]
|
||||
self._theItem = self.theProject.tree[self._docHandle]
|
||||
|
||||
self.setHasSelection(False)
|
||||
self.updateInfo()
|
||||
@@ -2963,17 +2976,10 @@ class GuiDocEditFooter(QWidget):
|
||||
sIcon = QPixmap()
|
||||
sText = ""
|
||||
else:
|
||||
iStatus = self._theItem.itemStatus
|
||||
if self._theItem.itemClass == nwItemClass.NOVEL:
|
||||
iStatus = self.theProject.statusItems.checkEntry(iStatus)
|
||||
theIcon = self.theParent.statusIcons[iStatus]
|
||||
else:
|
||||
iStatus = self.theProject.importItems.checkEntry(iStatus)
|
||||
theIcon = self.theParent.importIcons[iStatus]
|
||||
|
||||
theStatus, theIcon = self._theItem.getImportStatus()
|
||||
sIcon = theIcon.pixmap(self.sPx, self.sPx)
|
||||
hLevel = self.theParent.theIndex.getHandleHeaderLevel(self._docHandle)
|
||||
sText = f"{self._theItem.itemStatus} / {self._theItem.describeMe(hLevel)}"
|
||||
hLevel = self.theProject.index.getHandleHeaderLevel(self._docHandle)
|
||||
sText = f"{theStatus} / {self._theItem.describeMe(hLevel)}"
|
||||
|
||||
self.statusIcon.setPixmap(sIcon)
|
||||
self.statusText.setText(sText)
|
||||
|
||||
@@ -46,16 +46,16 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
BLOCK_META = 2
|
||||
BLOCK_TITLE = 4
|
||||
|
||||
def __init__(self, theDoc, theParent, spEnchant):
|
||||
QSyntaxHighlighter.__init__(self, theDoc)
|
||||
def __init__(self, theDoc, mainGui, spEnchant):
|
||||
super().__init__(theDoc)
|
||||
|
||||
logger.debug("Initialising GuiDocHighlighter ...")
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theDoc = theDoc
|
||||
self.spEnchant = spEnchant
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theIndex = theParent.theIndex
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.theProject = mainGui.theProject
|
||||
self.theHandle = None
|
||||
self.spellCheck = False
|
||||
self.spellRx = None
|
||||
@@ -87,24 +87,24 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
"""
|
||||
logger.debug("Setting up highlighting rules")
|
||||
|
||||
self.colHead = QColor(*self.theTheme.colHead)
|
||||
self.colHeadH = QColor(*self.theTheme.colHeadH)
|
||||
self.colDialN = QColor(*self.theTheme.colDialN)
|
||||
self.colDialD = QColor(*self.theTheme.colDialD)
|
||||
self.colDialS = QColor(*self.theTheme.colDialS)
|
||||
self.colHidden = QColor(*self.theTheme.colHidden)
|
||||
self.colKey = QColor(*self.theTheme.colKey)
|
||||
self.colVal = QColor(*self.theTheme.colVal)
|
||||
self.colSpell = QColor(*self.theTheme.colSpell)
|
||||
self.colError = QColor(*self.theTheme.colError)
|
||||
self.colRepTag = QColor(*self.theTheme.colRepTag)
|
||||
self.colMod = QColor(*self.theTheme.colMod)
|
||||
self.colBreak = QColor(*self.theTheme.colEmph)
|
||||
self.colHead = QColor(*self.mainTheme.colHead)
|
||||
self.colHeadH = QColor(*self.mainTheme.colHeadH)
|
||||
self.colDialN = QColor(*self.mainTheme.colDialN)
|
||||
self.colDialD = QColor(*self.mainTheme.colDialD)
|
||||
self.colDialS = QColor(*self.mainTheme.colDialS)
|
||||
self.colHidden = QColor(*self.mainTheme.colHidden)
|
||||
self.colKey = QColor(*self.mainTheme.colKey)
|
||||
self.colVal = QColor(*self.mainTheme.colVal)
|
||||
self.colSpell = QColor(*self.mainTheme.colSpell)
|
||||
self.colError = QColor(*self.mainTheme.colError)
|
||||
self.colRepTag = QColor(*self.mainTheme.colRepTag)
|
||||
self.colMod = QColor(*self.mainTheme.colMod)
|
||||
self.colBreak = QColor(*self.mainTheme.colEmph)
|
||||
self.colBreak.setAlpha(64)
|
||||
|
||||
self.colEmph = None
|
||||
if self.mainConf.highlightEmph:
|
||||
self.colEmph = QColor(*self.theTheme.colEmph)
|
||||
self.colEmph = QColor(*self.mainTheme.colEmph)
|
||||
|
||||
self.hStyles = {
|
||||
"header1": self._makeFormat(self.colHead, "bold", 1.8),
|
||||
@@ -287,9 +287,10 @@ class GuiDocHighlighter(QSyntaxHighlighter):
|
||||
|
||||
if theText.startswith("@"): # Keywords and commands
|
||||
self.setCurrentBlockState(self.BLOCK_META)
|
||||
tItem = self.theParent.theProject.projTree[self.theHandle]
|
||||
isValid, theBits, thePos = self.theIndex.scanThis(theText)
|
||||
isGood = self.theIndex.checkThese(theBits, tItem)
|
||||
pIndex = self.theProject.index
|
||||
tItem = self.mainGui.theProject.tree[self.theHandle]
|
||||
isValid, theBits, thePos = pIndex.scanThis(theText)
|
||||
isGood = pIndex.checkThese(theBits, tItem)
|
||||
if isValid:
|
||||
for n, theBit in enumerate(theBits):
|
||||
xPos = thePos[n]
|
||||
|
||||
@@ -30,17 +30,19 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot
|
||||
from enum import Enum
|
||||
|
||||
from PyQt5.QtCore import Qt, QUrl, QSize, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtGui import (
|
||||
QTextOption, QFont, QPalette, QColor, QTextCursor, QIcon, QCursor
|
||||
)
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QTextBrowser, QWidget, QScrollArea, QLabel, QHBoxLayout, QToolButton,
|
||||
QAction, QMenu
|
||||
QAction, QMenu, QFrame
|
||||
)
|
||||
|
||||
from novelwriter.core import ToHtml
|
||||
from novelwriter.enum import nwAlert, nwItemType, nwDocAction
|
||||
from novelwriter.enum import nwItemType, nwDocAction, nwDocMode
|
||||
from novelwriter.error import logException
|
||||
from novelwriter.constants import nwUnicode
|
||||
|
||||
@@ -49,16 +51,18 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiDocViewer(QTextBrowser):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QTextBrowser.__init__(self, theParent)
|
||||
loadDocumentTagRequest = pyqtSignal(str, Enum)
|
||||
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiDocViewer ...")
|
||||
|
||||
# Class Variables
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
# Internal Variables
|
||||
self._docHandle = None
|
||||
@@ -68,6 +72,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.setAutoFillBackground(True)
|
||||
self.setOpenExternalLinks(False)
|
||||
self.setFocusPolicy(Qt.StrongFocus)
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
|
||||
# Document Header and Footer
|
||||
self.docHeader = GuiDocViewHeader(self)
|
||||
@@ -113,14 +118,14 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
# Set the widget colours to match syntax theme
|
||||
mainPalette = self.palette()
|
||||
mainPalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*self.theTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
|
||||
mainPalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack))
|
||||
mainPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
self.setPalette(mainPalette)
|
||||
|
||||
docPalette = self.viewport().palette()
|
||||
docPalette.setColor(QPalette.Base, QColor(*self.theTheme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
|
||||
docPalette.setColor(QPalette.Base, QColor(*self.mainTheme.colBack))
|
||||
docPalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
self.viewport().setPalette(docPalette)
|
||||
|
||||
self.docHeader.matchColours()
|
||||
@@ -159,7 +164,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
def loadText(self, tHandle, updateHistory=True):
|
||||
"""Load text into the viewer from an item handle.
|
||||
"""
|
||||
if not self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
|
||||
if not self.theProject.tree.checkType(tHandle, nwItemType.FILE):
|
||||
logger.warning("Item not found")
|
||||
return False
|
||||
|
||||
@@ -216,7 +221,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.updateDocMargins()
|
||||
|
||||
# Make sure the main GUI knows we changed the content
|
||||
self.theParent.viewMeta.refreshReferences(tHandle)
|
||||
self.mainGui.viewMeta.refreshReferences(tHandle)
|
||||
|
||||
# Since we change the content while it may still be rendering, we mark
|
||||
# the document dirty again to make sure it's re-rendered properly.
|
||||
@@ -238,30 +243,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
def loadFromTag(self, theTag):
|
||||
"""Load text in the document from a reference given by a meta
|
||||
tag rather than a known handle. This function depends on the
|
||||
index being up to date.
|
||||
"""
|
||||
logger.debug("Loading document from tag '%s'", theTag)
|
||||
tHandle, _, sTitle = self.theParent.theIndex.getTagSource(theTag)
|
||||
if tHandle is None:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
"Could not find the reference for tag '{0}'. It either doesn't "
|
||||
"exist, or the index is out of date. The index can be updated "
|
||||
"from the Tools menu, or by pressing {1}."
|
||||
).format(
|
||||
theTag, "F9"
|
||||
), nwAlert.ERROR)
|
||||
return False
|
||||
else:
|
||||
# Let the parent handle the opening as it also ensures that
|
||||
# the doc view panel is visible in case this request comes
|
||||
# from outside this class.
|
||||
logger.verbose("Tag points to '%s#%s'", tHandle, sTitle)
|
||||
self.theParent.viewDocument(tHandle, "#%s" % sTitle)
|
||||
return True
|
||||
|
||||
def docAction(self, theAction):
|
||||
"""Wrapper function for various document actions on the current
|
||||
document.
|
||||
@@ -341,15 +322,6 @@ class GuiDocViewer(QTextBrowser):
|
||||
|
||||
return
|
||||
|
||||
def updateDocInfo(self, tHandle):
|
||||
"""Called when an item label is changed to check if the document
|
||||
title bar needs updating,
|
||||
"""
|
||||
if tHandle == self._docHandle:
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
@@ -408,19 +380,33 @@ class GuiDocViewer(QTextBrowser):
|
||||
return 0
|
||||
|
||||
##
|
||||
# Slots
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateDocInfo(self, tHandle):
|
||||
"""Called when an item label is changed to check if the document
|
||||
title bar needs updating,
|
||||
"""
|
||||
if tHandle == self._docHandle:
|
||||
self.docHeader.setTitleFromHandle(self._docHandle)
|
||||
self.updateDocMargins()
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot("QUrl")
|
||||
def _linkClicked(self, theURL):
|
||||
"""Slot for a link in the document being clicked.
|
||||
"""Process a clicked link internally in the document.
|
||||
"""
|
||||
theLink = theURL.url()
|
||||
logger.verbose("Clicked link: '%s'", theLink)
|
||||
if len(theLink) > 0:
|
||||
theBits = theLink.split("=")
|
||||
if len(theBits) == 2:
|
||||
self.loadFromTag(theBits[1])
|
||||
self.loadDocumentTagRequest.emit(theBits[1], nwDocMode.VIEW)
|
||||
return
|
||||
|
||||
@pyqtSlot("QPoint")
|
||||
@@ -475,7 +461,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
has its margins adjusted according to user preferences.
|
||||
"""
|
||||
self.updateDocMargins()
|
||||
QTextBrowser.resizeEvent(self, theEvent)
|
||||
super().resizeEvent(theEvent)
|
||||
return
|
||||
|
||||
def mouseReleaseEvent(self, theEvent):
|
||||
@@ -486,7 +472,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
elif theEvent.button() == Qt.ForwardButton:
|
||||
self.navForward()
|
||||
else:
|
||||
QTextBrowser.mouseReleaseEvent(self, theEvent)
|
||||
super().mouseReleaseEvent(theEvent)
|
||||
return
|
||||
|
||||
##
|
||||
@@ -553,27 +539,27 @@ class GuiDocViewer(QTextBrowser):
|
||||
" text-align: center;"
|
||||
"}}\n"
|
||||
).format(
|
||||
tColR=self.theTheme.colText[0],
|
||||
tColG=self.theTheme.colText[1],
|
||||
tColB=self.theTheme.colText[2],
|
||||
hColR=self.theTheme.colHead[0],
|
||||
hColG=self.theTheme.colHead[1],
|
||||
hColB=self.theTheme.colHead[2],
|
||||
aColR=self.theTheme.colVal[0],
|
||||
aColG=self.theTheme.colVal[1],
|
||||
aColB=self.theTheme.colVal[2],
|
||||
eColR=self.theTheme.colEmph[0],
|
||||
eColG=self.theTheme.colEmph[1],
|
||||
eColB=self.theTheme.colEmph[2],
|
||||
kColR=self.theTheme.colKey[0],
|
||||
kColG=self.theTheme.colKey[1],
|
||||
kColB=self.theTheme.colKey[2],
|
||||
cColR=self.theTheme.colHidden[0],
|
||||
cColG=self.theTheme.colHidden[1],
|
||||
cColB=self.theTheme.colHidden[2],
|
||||
mColR=self.theTheme.colMod[0],
|
||||
mColG=self.theTheme.colMod[1],
|
||||
mColB=self.theTheme.colMod[2],
|
||||
tColR=self.mainTheme.colText[0],
|
||||
tColG=self.mainTheme.colText[1],
|
||||
tColB=self.mainTheme.colText[2],
|
||||
hColR=self.mainTheme.colHead[0],
|
||||
hColG=self.mainTheme.colHead[1],
|
||||
hColB=self.mainTheme.colHead[2],
|
||||
aColR=self.mainTheme.colVal[0],
|
||||
aColG=self.mainTheme.colVal[1],
|
||||
aColB=self.mainTheme.colVal[2],
|
||||
eColR=self.mainTheme.colEmph[0],
|
||||
eColG=self.mainTheme.colEmph[1],
|
||||
eColB=self.mainTheme.colEmph[2],
|
||||
kColR=self.mainTheme.colKey[0],
|
||||
kColG=self.mainTheme.colKey[1],
|
||||
kColB=self.mainTheme.colKey[2],
|
||||
cColR=self.mainTheme.colHidden[0],
|
||||
cColG=self.mainTheme.colHidden[1],
|
||||
cColB=self.mainTheme.colHidden[2],
|
||||
mColR=self.mainTheme.colMod[0],
|
||||
mColG=self.mainTheme.colMod[1],
|
||||
mColB=self.mainTheme.colMod[2],
|
||||
)
|
||||
self.document().setDefaultStyleSheet(styleSheet)
|
||||
|
||||
@@ -582,7 +568,7 @@ class GuiDocViewer(QTextBrowser):
|
||||
# END Class GuiDocViewer
|
||||
|
||||
|
||||
class GuiDocViewHistory():
|
||||
class GuiDocViewHistory:
|
||||
|
||||
def __init__(self, docViewer):
|
||||
|
||||
@@ -722,20 +708,20 @@ class GuiDocViewHistory():
|
||||
class GuiDocViewHeader(QWidget):
|
||||
|
||||
def __init__(self, docViewer):
|
||||
QWidget.__init__(self, docViewer)
|
||||
super().__init__(parent=docViewer)
|
||||
|
||||
logger.debug("Initialising GuiDocViewHeader ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.docViewer = docViewer
|
||||
self.theParent = docViewer.theParent
|
||||
self.mainGui = docViewer.mainGui
|
||||
self.theProject = docViewer.theProject
|
||||
self.theTheme = docViewer.theTheme
|
||||
self.mainTheme = docViewer.mainTheme
|
||||
|
||||
# Internal Variables
|
||||
self._docHandle = None
|
||||
|
||||
fPx = int(0.9*self.theTheme.fontPixelSize)
|
||||
fPx = int(0.9*self.mainTheme.fontPixelSize)
|
||||
hSp = self.mainConf.pxInt(6)
|
||||
|
||||
# Main Widget Settings
|
||||
@@ -752,17 +738,17 @@ class GuiDocViewHeader(QWidget):
|
||||
self.theTitle.setFixedHeight(fPx)
|
||||
|
||||
lblFont = self.theTitle.font()
|
||||
lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize)
|
||||
self.theTitle.setFont(lblFont)
|
||||
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*self.theTheme.colText)
|
||||
).format(*self.mainTheme.colText)
|
||||
|
||||
# Buttons
|
||||
self.backButton = QToolButton(self)
|
||||
self.backButton.setIcon(self.theTheme.getIcon("backward"))
|
||||
self.backButton.setIcon(self.mainTheme.getIcon("backward"))
|
||||
self.backButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.backButton.setIconSize(QSize(fPx, fPx))
|
||||
self.backButton.setFixedSize(fPx, fPx)
|
||||
@@ -773,7 +759,7 @@ class GuiDocViewHeader(QWidget):
|
||||
self.backButton.clicked.connect(self.docViewer.navBackward)
|
||||
|
||||
self.forwardButton = QToolButton(self)
|
||||
self.forwardButton.setIcon(self.theTheme.getIcon("forward"))
|
||||
self.forwardButton.setIcon(self.mainTheme.getIcon("forward"))
|
||||
self.forwardButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.forwardButton.setIconSize(QSize(fPx, fPx))
|
||||
self.forwardButton.setFixedSize(fPx, fPx)
|
||||
@@ -784,7 +770,7 @@ class GuiDocViewHeader(QWidget):
|
||||
self.forwardButton.clicked.connect(self.docViewer.navForward)
|
||||
|
||||
self.refreshButton = QToolButton(self)
|
||||
self.refreshButton.setIcon(self.theTheme.getIcon("refresh"))
|
||||
self.refreshButton.setIcon(self.mainTheme.getIcon("refresh"))
|
||||
self.refreshButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.refreshButton.setIconSize(QSize(fPx, fPx))
|
||||
self.refreshButton.setFixedSize(fPx, fPx)
|
||||
@@ -795,7 +781,7 @@ class GuiDocViewHeader(QWidget):
|
||||
self.refreshButton.clicked.connect(self._refreshDocument)
|
||||
|
||||
self.closeButton = QToolButton(self)
|
||||
self.closeButton.setIcon(self.theTheme.getIcon("close"))
|
||||
self.closeButton.setIcon(self.mainTheme.getIcon("close"))
|
||||
self.closeButton.setContentsMargins(0, 0, 0, 0)
|
||||
self.closeButton.setIconSize(QSize(fPx, fPx))
|
||||
self.closeButton.setFixedSize(fPx, fPx)
|
||||
@@ -838,9 +824,9 @@ class GuiDocViewHeader(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
thePalette = QPalette()
|
||||
thePalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*self.theTheme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
|
||||
thePalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*self.mainTheme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
|
||||
self.setPalette(thePalette)
|
||||
self.theTitle.setPalette(thePalette)
|
||||
@@ -862,15 +848,15 @@ class GuiDocViewHeader(QWidget):
|
||||
|
||||
if self.mainConf.showFullPath:
|
||||
tTitle = []
|
||||
tTree = self.theProject.projTree.getItemPath(tHandle)
|
||||
tTree = self.theProject.tree.getItemPath(tHandle)
|
||||
for aHandle in reversed(tTree):
|
||||
nwItem = self.theProject.projTree[aHandle]
|
||||
nwItem = self.theProject.tree[aHandle]
|
||||
if nwItem is not None:
|
||||
tTitle.append(nwItem.itemName)
|
||||
sSep = " %s " % nwUnicode.U_RSAQUO
|
||||
self.theTitle.setText(sSep.join(tTitle))
|
||||
else:
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem is None:
|
||||
return False
|
||||
self.theTitle.setText(nwItem.itemName)
|
||||
@@ -896,14 +882,14 @@ class GuiDocViewHeader(QWidget):
|
||||
def _closeDocument(self):
|
||||
"""Trigger the close editor/viewer on the main window.
|
||||
"""
|
||||
self.theParent.closeDocViewer()
|
||||
self.mainGui.closeDocViewer()
|
||||
return
|
||||
|
||||
def _refreshDocument(self):
|
||||
"""Reload the content of the document.
|
||||
"""
|
||||
if self.docViewer.docHandle() == self.theParent.docEditor.docHandle():
|
||||
self.theParent.saveDocument()
|
||||
if self.docViewer.docHandle() == self.mainGui.docEditor.docHandle():
|
||||
self.mainGui.saveDocument()
|
||||
self.docViewer.reloadText()
|
||||
return
|
||||
|
||||
@@ -915,7 +901,7 @@ class GuiDocViewHeader(QWidget):
|
||||
"""Capture a click on the title and ensure that the item is
|
||||
selected in the project tree.
|
||||
"""
|
||||
self.theParent.treeView.setSelectedHandle(self._docHandle, doScroll=True)
|
||||
self.mainGui.projView.setSelectedHandle(self._docHandle, doScroll=True)
|
||||
return
|
||||
|
||||
# END Class GuiDocViewHeader
|
||||
@@ -929,32 +915,32 @@ class GuiDocViewHeader(QWidget):
|
||||
class GuiDocViewFooter(QWidget):
|
||||
|
||||
def __init__(self, docViewer):
|
||||
QWidget.__init__(self, docViewer)
|
||||
super().__init__(parent=docViewer)
|
||||
|
||||
logger.debug("Initialising GuiDocViewFooter ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.docViewer = docViewer
|
||||
self.theParent = docViewer.theParent
|
||||
self.theTheme = docViewer.theTheme
|
||||
self.viewMeta = docViewer.theParent.viewMeta
|
||||
self.mainGui = docViewer.mainGui
|
||||
self.mainTheme = docViewer.mainTheme
|
||||
self.viewMeta = docViewer.mainGui.viewMeta
|
||||
|
||||
# Internal Variables
|
||||
self._docHandle = None
|
||||
|
||||
fPx = int(0.9*self.theTheme.fontPixelSize)
|
||||
fPx = int(0.9*self.mainTheme.fontPixelSize)
|
||||
bSp = self.mainConf.pxInt(2)
|
||||
hSp = self.mainConf.pxInt(8)
|
||||
|
||||
# Icons
|
||||
stickyOn = self.theTheme.getPixmap("sticky-on", (fPx, fPx))
|
||||
stickyOff = self.theTheme.getPixmap("sticky-off", (fPx, fPx))
|
||||
stickyOn = self.mainTheme.getPixmap("sticky-on", (fPx, fPx))
|
||||
stickyOff = self.mainTheme.getPixmap("sticky-off", (fPx, fPx))
|
||||
stickyIcon = QIcon()
|
||||
stickyIcon.addPixmap(stickyOn, QIcon.Normal, QIcon.On)
|
||||
stickyIcon.addPixmap(stickyOff, QIcon.Normal, QIcon.Off)
|
||||
|
||||
bulletOn = self.theTheme.getPixmap("bullet-on", (fPx, fPx))
|
||||
bulletOff = self.theTheme.getPixmap("bullet-off", (fPx, fPx))
|
||||
bulletOn = self.mainTheme.getPixmap("bullet-on", (fPx, fPx))
|
||||
bulletOff = self.mainTheme.getPixmap("bullet-off", (fPx, fPx))
|
||||
bulletIcon = QIcon()
|
||||
bulletIcon.addPixmap(bulletOn, QIcon.Normal, QIcon.On)
|
||||
bulletIcon.addPixmap(bulletOff, QIcon.Normal, QIcon.Off)
|
||||
@@ -966,13 +952,13 @@ class GuiDocViewFooter(QWidget):
|
||||
buttonStyle = (
|
||||
"QToolButton {{border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({0},{1},{2},0.2);}}"
|
||||
).format(*self.theTheme.colText)
|
||||
).format(*self.mainTheme.colText)
|
||||
|
||||
# Show/Hide Details
|
||||
self.showHide = QToolButton(self)
|
||||
self.showHide.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.showHide.setStyleSheet(buttonStyle)
|
||||
self.showHide.setIcon(self.theTheme.getIcon("reference"))
|
||||
self.showHide.setIcon(self.mainTheme.getIcon("reference"))
|
||||
self.showHide.setIconSize(QSize(fPx, fPx))
|
||||
self.showHide.setFixedSize(QSize(fPx, fPx))
|
||||
self.showHide.clicked.connect(self._doShowHide)
|
||||
@@ -1053,7 +1039,7 @@ class GuiDocViewFooter(QWidget):
|
||||
self.lblSynopsis.setAlignment(Qt.AlignLeft | Qt.AlignTop)
|
||||
|
||||
lblFont = self.font()
|
||||
lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize)
|
||||
self.lblRefs.setFont(lblFont)
|
||||
self.lblSticky.setFont(lblFont)
|
||||
self.lblComments.setFont(lblFont)
|
||||
@@ -1098,9 +1084,9 @@ class GuiDocViewFooter(QWidget):
|
||||
theme rather than the main GUI.
|
||||
"""
|
||||
thePalette = QPalette()
|
||||
thePalette.setColor(QPalette.Window, QColor(*self.theTheme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*self.theTheme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*self.theTheme.colText))
|
||||
thePalette.setColor(QPalette.Window, QColor(*self.mainTheme.colBack))
|
||||
thePalette.setColor(QPalette.WindowText, QColor(*self.mainTheme.colText))
|
||||
thePalette.setColor(QPalette.Text, QColor(*self.mainTheme.colText))
|
||||
|
||||
self.setPalette(thePalette)
|
||||
self.lblRefs.setPalette(thePalette)
|
||||
@@ -1154,14 +1140,14 @@ class GuiDocViewFooter(QWidget):
|
||||
|
||||
class GuiDocViewDetails(QScrollArea):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QScrollArea.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiDocViewDetails ...")
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = mainGui
|
||||
self.theProject = mainGui.theProject
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
|
||||
self.refList = QLabel("")
|
||||
self.refList.setWordWrap(True)
|
||||
@@ -1170,7 +1156,7 @@ class GuiDocViewDetails(QScrollArea):
|
||||
self.refList.linkActivated.connect(self._linkClicked)
|
||||
|
||||
self.linkStyle = "style='color: rgb({0},{1},{2})'".format(
|
||||
*self.theTheme.colLink
|
||||
*self.mainTheme.colLink
|
||||
)
|
||||
|
||||
# Assemble
|
||||
@@ -1185,6 +1171,7 @@ class GuiDocViewDetails(QScrollArea):
|
||||
self.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
|
||||
self.setWidgetResizable(True)
|
||||
self.setMinimumHeight(self.mainConf.pxInt(50))
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
|
||||
logger.debug("GuiDocViewDetails initialisation complete")
|
||||
|
||||
@@ -1194,13 +1181,13 @@ class GuiDocViewDetails(QScrollArea):
|
||||
"""Update the current list of document references from the
|
||||
project index.
|
||||
"""
|
||||
if self.theParent.docViewer.stickyRef:
|
||||
if self.mainGui.docViewer.stickyRef:
|
||||
return
|
||||
|
||||
theRefs = self.theParent.theIndex.getBackReferenceList(tHandle)
|
||||
theRefs = self.theProject.index.getBackReferenceList(tHandle)
|
||||
theList = []
|
||||
for tHandle in theRefs:
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
tItem = self.theProject.tree[tHandle]
|
||||
if tItem is not None:
|
||||
theList.append("<a href='%s#%s' %s>%s</a>" % (
|
||||
tHandle, theRefs[tHandle], self.linkStyle, tItem.itemName
|
||||
@@ -1222,7 +1209,7 @@ class GuiDocViewDetails(QScrollArea):
|
||||
if len(theLink) == 21:
|
||||
tHandle = theLink[:13]
|
||||
tAnchor = theLink[13:]
|
||||
self.theParent.viewDocument(tHandle, tAnchor)
|
||||
self.mainGui.viewDocument(tHandle, tAnchor)
|
||||
return
|
||||
|
||||
# END Class GuiDocViewDetails
|
||||
|
||||
@@ -30,7 +30,6 @@ from PyQt5.QtCore import Qt, pyqtSlot
|
||||
from PyQt5.QtGui import QFont, QPixmap
|
||||
from PyQt5.QtWidgets import QWidget, QGridLayout, QLabel
|
||||
|
||||
from novelwriter.enum import nwItemClass, nwItemType
|
||||
from novelwriter.constants import trConst, nwLabels
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -38,14 +37,14 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiItemDetails(QWidget):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QWidget.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiItemDetails ...")
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = mainGui
|
||||
self.theProject = mainGui.theProject
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
|
||||
# Internal Variables
|
||||
self._itemHandle = None
|
||||
@@ -54,11 +53,11 @@ class GuiItemDetails(QWidget):
|
||||
hSp = self.mainConf.pxInt(6)
|
||||
vSp = self.mainConf.pxInt(1)
|
||||
mPx = self.mainConf.pxInt(6)
|
||||
iPx = self.theTheme.baseIconSize
|
||||
fPt = self.theTheme.fontPointSize
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
fPt = self.mainTheme.fontPointSize
|
||||
|
||||
self._expCheck = self.theTheme.getPixmap("check", (iPx, iPx))
|
||||
self._expCross = self.theTheme.getPixmap("cross", (iPx, iPx))
|
||||
self._expCheck = self.mainTheme.getPixmap("check", (iPx, iPx))
|
||||
self._expCross = self.mainTheme.getPixmap("cross", (iPx, iPx))
|
||||
|
||||
fntLabel = QFont()
|
||||
fntLabel.setBold(True)
|
||||
@@ -115,6 +114,7 @@ class GuiItemDetails(QWidget):
|
||||
self.usageData = QLabel("")
|
||||
self.usageData.setFont(fntValue)
|
||||
self.usageData.setAlignment(Qt.AlignLeft)
|
||||
self.usageData.setWordWrap(True)
|
||||
|
||||
# Character Count
|
||||
self.cCountName = QLabel(" "+self.tr("Characters"))
|
||||
@@ -180,8 +180,8 @@ class GuiItemDetails(QWidget):
|
||||
self.setLayout(self.mainBox)
|
||||
|
||||
# Make sure the columns for flags and counts don't resize too often
|
||||
flagWidth = self.theTheme.getTextWidth("Mm", fntValue)
|
||||
countWidth = self.theTheme.getTextWidth("99,999", fntValue)
|
||||
flagWidth = self.mainTheme.getTextWidth("Mm", fntValue)
|
||||
countWidth = self.mainTheme.getTextWidth("99,999", fntValue)
|
||||
self.mainBox.setColumnMinimumWidth(1, flagWidth)
|
||||
self.mainBox.setColumnMinimumWidth(4, countWidth)
|
||||
|
||||
@@ -214,6 +214,16 @@ class GuiItemDetails(QWidget):
|
||||
|
||||
return
|
||||
|
||||
def refreshDetails(self):
|
||||
"""Reload the content of the details panel.
|
||||
"""
|
||||
self.updateViewBox(self._itemHandle)
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateViewBox(self, tHandle):
|
||||
"""Populate the details box from a given handle.
|
||||
"""
|
||||
@@ -221,13 +231,13 @@ class GuiItemDetails(QWidget):
|
||||
self.clearDetails()
|
||||
return
|
||||
|
||||
nwItem = self.theProject.projTree[tHandle]
|
||||
nwItem = self.theProject.tree[tHandle]
|
||||
if nwItem is None:
|
||||
self.clearDetails()
|
||||
return
|
||||
|
||||
self._itemHandle = tHandle
|
||||
iPx = int(round(0.8*self.theTheme.baseIconSize))
|
||||
iPx = int(round(0.8*self.mainTheme.baseIconSize))
|
||||
|
||||
# Label
|
||||
# =====
|
||||
@@ -236,7 +246,7 @@ class GuiItemDetails(QWidget):
|
||||
if len(theLabel) > 100:
|
||||
theLabel = theLabel[:96].rstrip()+" ..."
|
||||
|
||||
if nwItem.itemType == nwItemType.FILE:
|
||||
if nwItem.isFileType():
|
||||
if nwItem.isExported:
|
||||
self.labelIcon.setPixmap(self._expCheck)
|
||||
else:
|
||||
@@ -249,29 +259,22 @@ class GuiItemDetails(QWidget):
|
||||
# Status
|
||||
# ======
|
||||
|
||||
itStatus = nwItem.itemStatus
|
||||
if nwItem.itemClass == nwItemClass.NOVEL:
|
||||
itStatus = self.theProject.statusItems.checkEntry(itStatus) # Make sure it's valid
|
||||
flagIcon = self.theParent.statusIcons[itStatus]
|
||||
else:
|
||||
itStatus = self.theProject.importItems.checkEntry(itStatus) # Make sure it's valid
|
||||
flagIcon = self.theParent.importIcons[itStatus]
|
||||
|
||||
self.statusIcon.setPixmap(flagIcon.pixmap(iPx, iPx))
|
||||
self.statusData.setText(nwItem.itemStatus)
|
||||
theStatus, theIcon = nwItem.getImportStatus()
|
||||
self.statusIcon.setPixmap(theIcon.pixmap(iPx, iPx))
|
||||
self.statusData.setText(theStatus)
|
||||
|
||||
# Class
|
||||
# =====
|
||||
|
||||
classIcon = self.theTheme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass])
|
||||
classIcon = self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItem.itemClass])
|
||||
self.classIcon.setPixmap(classIcon.pixmap(iPx, iPx))
|
||||
self.classData.setText(trConst(nwLabels.CLASS_NAME[nwItem.itemClass]))
|
||||
|
||||
# Layout
|
||||
# ======
|
||||
|
||||
hLevel = self.theParent.theIndex.getHandleHeaderLevel(tHandle)
|
||||
usageIcon = self.theTheme.getItemIcon(
|
||||
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
|
||||
usageIcon = self.mainTheme.getItemIcon(
|
||||
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
|
||||
)
|
||||
self.usageIcon.setPixmap(usageIcon.pixmap(iPx, iPx))
|
||||
@@ -280,7 +283,7 @@ class GuiItemDetails(QWidget):
|
||||
# Counts
|
||||
# ======
|
||||
|
||||
if nwItem.itemType == nwItemType.FILE:
|
||||
if nwItem.isFileType():
|
||||
self.cCountData.setText(f"{nwItem.charCount:n}")
|
||||
self.wCountData.setText(f"{nwItem.wordCount:n}")
|
||||
self.pCountData.setText(f"{nwItem.paraCount:n}")
|
||||
@@ -291,12 +294,8 @@ class GuiItemDetails(QWidget):
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str, int, int, int)
|
||||
def doUpdateCounts(self, tHandle, cC, wC, pC):
|
||||
def updateCounts(self, tHandle, cC, wC, pC):
|
||||
"""Update the counts if the handle is the same as the one we're
|
||||
already showing. Otherwise, do nothing.
|
||||
"""
|
||||
|
||||
@@ -33,7 +33,7 @@ from PyQt5.QtCore import QUrl
|
||||
from PyQt5.QtGui import QDesktopServices
|
||||
from PyQt5.QtWidgets import QMenuBar, QAction
|
||||
|
||||
from novelwriter.enum import nwItemType, nwItemClass, nwDocAction, nwDocInsert, nwWidget
|
||||
from novelwriter.enum import nwDocAction, nwDocInsert, nwWidget
|
||||
from novelwriter.constants import trConst, nwKeyWords, nwLabels, nwUnicode
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
@@ -41,13 +41,13 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiMainMenu(QMenuBar):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QMenuBar.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiMainMenu ...")
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theProject = theParent.theProject
|
||||
self.mainGui = mainGui
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
# Build Menu
|
||||
self._buildProjectMenu()
|
||||
@@ -61,34 +61,14 @@ class GuiMainMenu(QMenuBar):
|
||||
self._buildHelpMenu()
|
||||
|
||||
# Function Pointers
|
||||
self._docAction = self.theParent.passDocumentAction
|
||||
self._moveTreeItem = self.theParent.treeView.moveTreeItem
|
||||
self._newTreeItem = self.theParent.treeView.newTreeItem
|
||||
self._docInsert = self.theParent.docEditor.insertText
|
||||
self._insertKeyWord = self.theParent.docEditor.insertKeyWord
|
||||
self._docAction = self.mainGui.passDocumentAction
|
||||
self._docInsert = self.mainGui.docEditor.insertText
|
||||
self._insertKeyWord = self.mainGui.docEditor.insertKeyWord
|
||||
|
||||
logger.debug("GuiMainMenu initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def setAvailableRoot(self):
|
||||
"""Update the list of available root folders and set the ones
|
||||
that are active.
|
||||
"""
|
||||
for itemClass in nwItemClass:
|
||||
if itemClass == nwItemClass.NO_CLASS:
|
||||
continue
|
||||
if itemClass == nwItemClass.TRASH:
|
||||
continue
|
||||
self.rootItems[itemClass].setVisible(
|
||||
self.theProject.projTree.checkRootUnique(itemClass)
|
||||
)
|
||||
return
|
||||
|
||||
##
|
||||
# Update Menu on Settings Changed
|
||||
##
|
||||
@@ -99,12 +79,6 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aSpellCheck.setChecked(theMode)
|
||||
return
|
||||
|
||||
def setAutoOutline(self, theMode):
|
||||
"""Forward auto outline check state to its action.
|
||||
"""
|
||||
self.aAutoOutline.setChecked(theMode)
|
||||
return
|
||||
|
||||
def setFocusMode(self, theMode):
|
||||
"""Forward focus mode check state to its action.
|
||||
"""
|
||||
@@ -120,13 +94,7 @@ class GuiMainMenu(QMenuBar):
|
||||
flag is handled by the document editor class, so we make no
|
||||
decision, just pass a None to the function and let it decide.
|
||||
"""
|
||||
self.theParent.docEditor.toggleSpellCheck(None)
|
||||
return True
|
||||
|
||||
def _toggleAutoOutline(self, theMode):
|
||||
"""Toggle auto outline when the menu entry is checked.
|
||||
"""
|
||||
self.theProject.setAutoOutline(theMode)
|
||||
self.mainGui.docEditor.toggleSpellCheck(None)
|
||||
return True
|
||||
|
||||
def _openWebsite(self, theUrl):
|
||||
@@ -155,25 +123,25 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
# Project > New Project
|
||||
self.aNewProject = QAction(self.tr("New Project"), self)
|
||||
self.aNewProject.triggered.connect(lambda: self.theParent.newProject(None))
|
||||
self.aNewProject.triggered.connect(lambda: self.mainGui.newProject(None))
|
||||
self.projMenu.addAction(self.aNewProject)
|
||||
|
||||
# Project > Open Project
|
||||
self.aOpenProject = QAction(self.tr("Open Project"), self)
|
||||
self.aOpenProject.setShortcut("Ctrl+Shift+O")
|
||||
self.aOpenProject.triggered.connect(lambda: self.theParent.showProjectLoadDialog())
|
||||
self.aOpenProject.triggered.connect(lambda: self.mainGui.showProjectLoadDialog())
|
||||
self.projMenu.addAction(self.aOpenProject)
|
||||
|
||||
# Project > Save Project
|
||||
self.aSaveProject = QAction(self.tr("Save Project"), self)
|
||||
self.aSaveProject.setShortcut("Ctrl+Shift+S")
|
||||
self.aSaveProject.triggered.connect(lambda: self.theParent.saveProject())
|
||||
self.aSaveProject.triggered.connect(lambda: self.mainGui.saveProject())
|
||||
self.projMenu.addAction(self.aSaveProject)
|
||||
|
||||
# Project > Close Project
|
||||
self.aCloseProject = QAction(self.tr("Close Project"), self)
|
||||
self.aCloseProject.setShortcut("Ctrl+Shift+W")
|
||||
self.aCloseProject.triggered.connect(lambda: self.theParent.closeProject(False))
|
||||
self.aCloseProject.triggered.connect(lambda: self.mainGui.closeProject(False))
|
||||
self.projMenu.addAction(self.aCloseProject)
|
||||
|
||||
# Project > Separator
|
||||
@@ -182,78 +150,33 @@ class GuiMainMenu(QMenuBar):
|
||||
# Project > Project Settings
|
||||
self.aProjectSettings = QAction(self.tr("Project Settings"), self)
|
||||
self.aProjectSettings.setShortcut("Ctrl+Shift+,")
|
||||
self.aProjectSettings.triggered.connect(lambda: self.theParent.showProjectSettingsDialog())
|
||||
self.aProjectSettings.triggered.connect(lambda: self.mainGui.showProjectSettingsDialog())
|
||||
self.projMenu.addAction(self.aProjectSettings)
|
||||
|
||||
# Project > Project Details
|
||||
self.aProjectDetails = QAction(self.tr("Project Details"), self)
|
||||
self.aProjectDetails.setShortcut("Shift+F6")
|
||||
self.aProjectDetails.triggered.connect(lambda: self.theParent.showProjectDetailsDialog())
|
||||
self.aProjectDetails.triggered.connect(lambda: self.mainGui.showProjectDetailsDialog())
|
||||
self.projMenu.addAction(self.aProjectDetails)
|
||||
|
||||
# Project > Separator
|
||||
self.projMenu.addSeparator()
|
||||
|
||||
# Project > New Root
|
||||
self.rootMenu = self.projMenu.addMenu(self.tr("Create Root Folder"))
|
||||
self.rootItems = {}
|
||||
self.rootItems[nwItemClass.NOVEL] = QAction(self.tr("Novel Root"), self.rootMenu)
|
||||
self.rootItems[nwItemClass.PLOT] = QAction(self.tr("Plot Root"), self.rootMenu)
|
||||
self.rootItems[nwItemClass.CHARACTER] = QAction(self.tr("Character Root"), self.rootMenu)
|
||||
self.rootItems[nwItemClass.WORLD] = QAction(self.tr("Location Root"), self.rootMenu)
|
||||
self.rootItems[nwItemClass.TIMELINE] = QAction(self.tr("Timeline Root"), self.rootMenu)
|
||||
self.rootItems[nwItemClass.OBJECT] = QAction(self.tr("Object Root"), self.rootMenu)
|
||||
self.rootItems[nwItemClass.ENTITY] = QAction(self.tr("Entity Root"), self.rootMenu)
|
||||
self.rootItems[nwItemClass.CUSTOM] = QAction(self.tr("Custom Root"), self.rootMenu)
|
||||
self.rootItems[nwItemClass.ARCHIVE] = QAction(self.tr("Archive Root"), self.rootMenu)
|
||||
for n, itemClass in enumerate(self.rootItems.keys()):
|
||||
self.rootItems[itemClass].triggered.connect(
|
||||
lambda n, itemClass=itemClass: self._newTreeItem(nwItemType.ROOT, itemClass)
|
||||
)
|
||||
self.rootMenu.addAction(self.rootItems[itemClass])
|
||||
|
||||
# Project > New Folder
|
||||
self.aCreateFolder = QAction(self.tr("Create Folder"), self)
|
||||
self.aCreateFolder.setShortcut("Ctrl+Shift+N")
|
||||
self.aCreateFolder.triggered.connect(lambda: self._newTreeItem(nwItemType.FOLDER, None))
|
||||
self.projMenu.addAction(self.aCreateFolder)
|
||||
|
||||
# Project > Separator
|
||||
self.projMenu.addSeparator()
|
||||
|
||||
# Project > Edit
|
||||
self.aEditItem = QAction(self.tr("Edit Item"), self)
|
||||
self.aEditItem.setShortcuts(["Ctrl+E", "F2"])
|
||||
self.aEditItem.triggered.connect(lambda: self.theParent.editItem(None))
|
||||
self.aEditItem = QAction(self.tr("Rename Item"), self)
|
||||
self.aEditItem.setShortcuts(["F2"])
|
||||
self.aEditItem.triggered.connect(lambda: self.mainGui.editItemLabel(None))
|
||||
self.projMenu.addAction(self.aEditItem)
|
||||
|
||||
# Project > Delete
|
||||
self.aDeleteItem = QAction(self.tr("Delete Item"), self)
|
||||
self.aDeleteItem.setShortcut("Ctrl+Shift+Del")
|
||||
self.aDeleteItem.triggered.connect(lambda: self.theParent.treeView.deleteItem(None))
|
||||
self.aDeleteItem.triggered.connect(lambda: self.mainGui.projView.requestDeleteItem(None))
|
||||
self.projMenu.addAction(self.aDeleteItem)
|
||||
|
||||
# Project > Move Up
|
||||
self.aMoveUp = QAction(self.tr("Move Item Up"), self)
|
||||
self.aMoveUp.setShortcut("Ctrl+Up")
|
||||
self.aMoveUp.triggered.connect(lambda: self._moveTreeItem(-1))
|
||||
self.projMenu.addAction(self.aMoveUp)
|
||||
|
||||
# Project > Move Down
|
||||
self.aMoveDown = QAction(self.tr("Move Item Down"), self)
|
||||
self.aMoveDown.setShortcut("Ctrl+Down")
|
||||
self.aMoveDown.triggered.connect(lambda: self._moveTreeItem(1))
|
||||
self.projMenu.addAction(self.aMoveDown)
|
||||
|
||||
# Project > Undo Last Action
|
||||
self.aMoveUndo = QAction(self.tr("Undo Last Move"), self)
|
||||
self.aMoveUndo.setShortcut("Ctrl+Shift+Z")
|
||||
self.aMoveUndo.triggered.connect(lambda: self.theParent.treeView.undoLastMove())
|
||||
self.projMenu.addAction(self.aMoveUndo)
|
||||
|
||||
# Project > Empty Trash
|
||||
self.aEmptyTrash = QAction(self.tr("Empty Trash"), self)
|
||||
self.aEmptyTrash.triggered.connect(lambda: self.theParent.treeView.emptyTrash())
|
||||
self.aEmptyTrash.triggered.connect(lambda: self.mainGui.projView.emptyTrash())
|
||||
self.projMenu.addAction(self.aEmptyTrash)
|
||||
|
||||
# Project > Separator
|
||||
@@ -263,7 +186,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aExitNW = QAction(self.tr("Exit"), self)
|
||||
self.aExitNW.setShortcut("Ctrl+Q")
|
||||
self.aExitNW.setMenuRole(QAction.QuitRole)
|
||||
self.aExitNW.triggered.connect(lambda: self.theParent.closeMain())
|
||||
self.aExitNW.triggered.connect(lambda: self.mainGui.closeMain())
|
||||
self.projMenu.addAction(self.aExitNW)
|
||||
|
||||
return
|
||||
@@ -274,28 +197,22 @@ class GuiMainMenu(QMenuBar):
|
||||
# Document
|
||||
self.docuMenu = self.addMenu(self.tr("&Document"))
|
||||
|
||||
# Document > New
|
||||
self.aNewDoc = QAction(self.tr("New Document"), self)
|
||||
self.aNewDoc.setShortcut("Ctrl+N")
|
||||
self.aNewDoc.triggered.connect(lambda: self._newTreeItem(nwItemType.FILE, None))
|
||||
self.docuMenu.addAction(self.aNewDoc)
|
||||
|
||||
# Document > Open
|
||||
self.aOpenDoc = QAction(self.tr("Open Document"), self)
|
||||
self.aOpenDoc.setShortcut("Ctrl+O")
|
||||
self.aOpenDoc.triggered.connect(lambda: self.theParent.openSelectedItem())
|
||||
self.aOpenDoc.triggered.connect(lambda: self.mainGui.openSelectedItem())
|
||||
self.docuMenu.addAction(self.aOpenDoc)
|
||||
|
||||
# Document > Save
|
||||
self.aSaveDoc = QAction(self.tr("Save Document"), self)
|
||||
self.aSaveDoc.setShortcut("Ctrl+S")
|
||||
self.aSaveDoc.triggered.connect(lambda: self.theParent.saveDocument())
|
||||
self.aSaveDoc.triggered.connect(lambda: self.mainGui.saveDocument())
|
||||
self.docuMenu.addAction(self.aSaveDoc)
|
||||
|
||||
# Document > Close
|
||||
self.aCloseDoc = QAction(self.tr("Close Document"), self)
|
||||
self.aCloseDoc.setShortcut("Ctrl+W")
|
||||
self.aCloseDoc.triggered.connect(lambda: self.theParent.closeDocEditor())
|
||||
self.aCloseDoc.triggered.connect(lambda: self.mainGui.closeDocEditor())
|
||||
self.docuMenu.addAction(self.aCloseDoc)
|
||||
|
||||
# Document > Separator
|
||||
@@ -304,13 +221,13 @@ class GuiMainMenu(QMenuBar):
|
||||
# Document > Preview
|
||||
self.aViewDoc = QAction(self.tr("View Document"), self)
|
||||
self.aViewDoc.setShortcut("Ctrl+R")
|
||||
self.aViewDoc.triggered.connect(lambda: self.theParent.viewDocument(None))
|
||||
self.aViewDoc.triggered.connect(lambda: self.mainGui.viewDocument(None))
|
||||
self.docuMenu.addAction(self.aViewDoc)
|
||||
|
||||
# Document > Close Preview
|
||||
self.aCloseView = QAction(self.tr("Close Document View"), self)
|
||||
self.aCloseView.setShortcut("Ctrl+Shift+R")
|
||||
self.aCloseView.triggered.connect(lambda: self.theParent.closeDocViewer())
|
||||
self.aCloseView.triggered.connect(lambda: self.mainGui.closeDocViewer())
|
||||
self.docuMenu.addAction(self.aCloseView)
|
||||
|
||||
# Document > Separator
|
||||
@@ -318,23 +235,18 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
# Document > Show File Details
|
||||
self.aFileDetails = QAction(self.tr("Show File Details"), self)
|
||||
self.aFileDetails.triggered.connect(lambda: self.theParent.docEditor.revealLocation())
|
||||
self.aFileDetails.triggered.connect(lambda: self.mainGui.docEditor.revealLocation())
|
||||
self.docuMenu.addAction(self.aFileDetails)
|
||||
|
||||
# Document > Import From File
|
||||
self.aImportFile = QAction(self.tr("Import Text from File"), self)
|
||||
self.aImportFile.setShortcut("Ctrl+Shift+I")
|
||||
self.aImportFile.triggered.connect(lambda: self.theParent.importDocument())
|
||||
self.aImportFile.triggered.connect(lambda: self.mainGui.importDocument())
|
||||
self.docuMenu.addAction(self.aImportFile)
|
||||
|
||||
# Document > Merge Documents
|
||||
self.aMergeDocs = QAction(self.tr("Merge Folder to Document"), self)
|
||||
self.aMergeDocs.triggered.connect(lambda: self.theParent.mergeDocuments())
|
||||
self.docuMenu.addAction(self.aMergeDocs)
|
||||
|
||||
# Document > Split Document
|
||||
self.aSplitDoc = QAction(self.tr("Split Document to Folder"), self)
|
||||
self.aSplitDoc.triggered.connect(lambda: self.theParent.splitDocument())
|
||||
self.aSplitDoc.triggered.connect(lambda: self.mainGui.splitDocument())
|
||||
self.docuMenu.addAction(self.aSplitDoc)
|
||||
|
||||
return
|
||||
@@ -407,7 +319,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFocusTree.setShortcut("Ctrl+Alt+1")
|
||||
else:
|
||||
self.aFocusTree.setShortcut("Alt+1")
|
||||
self.aFocusTree.triggered.connect(lambda: self.theParent.switchFocus(nwWidget.TREE))
|
||||
self.aFocusTree.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.TREE))
|
||||
self.viewMenu.addAction(self.aFocusTree)
|
||||
|
||||
# View > Document Pane 1
|
||||
@@ -416,7 +328,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFocusEditor.setShortcut("Ctrl+Alt+2")
|
||||
else:
|
||||
self.aFocusEditor.setShortcut("Alt+2")
|
||||
self.aFocusEditor.triggered.connect(lambda: self.theParent.switchFocus(nwWidget.EDITOR))
|
||||
self.aFocusEditor.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.EDITOR))
|
||||
self.viewMenu.addAction(self.aFocusEditor)
|
||||
|
||||
# View > Document Pane 2
|
||||
@@ -425,7 +337,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFocusView.setShortcut("Ctrl+Alt+3")
|
||||
else:
|
||||
self.aFocusView.setShortcut("Alt+3")
|
||||
self.aFocusView.triggered.connect(lambda: self.theParent.switchFocus(nwWidget.VIEWER))
|
||||
self.aFocusView.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.VIEWER))
|
||||
self.viewMenu.addAction(self.aFocusView)
|
||||
|
||||
# View > Outline
|
||||
@@ -434,7 +346,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFocusOutline.setShortcut("Ctrl+Alt+4")
|
||||
else:
|
||||
self.aFocusOutline.setShortcut("Alt+4")
|
||||
self.aFocusOutline.triggered.connect(lambda: self.theParent.switchFocus(nwWidget.OUTLINE))
|
||||
self.aFocusOutline.triggered.connect(lambda: self.mainGui.switchFocus(nwWidget.OUTLINE))
|
||||
self.viewMenu.addAction(self.aFocusOutline)
|
||||
|
||||
# View > Separator
|
||||
@@ -443,13 +355,13 @@ class GuiMainMenu(QMenuBar):
|
||||
# View > Go Backward
|
||||
self.aViewPrev = QAction(self.tr("Navigate Backward"), self)
|
||||
self.aViewPrev.setShortcut("Alt+Left")
|
||||
self.aViewPrev.triggered.connect(lambda: self.theParent.docViewer.navBackward())
|
||||
self.aViewPrev.triggered.connect(lambda: self.mainGui.docViewer.navBackward())
|
||||
self.viewMenu.addAction(self.aViewPrev)
|
||||
|
||||
# View > Go Forward
|
||||
self.aViewNext = QAction(self.tr("Navigate Forward"), self)
|
||||
self.aViewNext.setShortcut("Alt+Right")
|
||||
self.aViewNext.triggered.connect(lambda: self.theParent.docViewer.navForward())
|
||||
self.aViewNext.triggered.connect(lambda: self.mainGui.docViewer.navForward())
|
||||
self.viewMenu.addAction(self.aViewNext)
|
||||
|
||||
# View > Separator
|
||||
@@ -459,14 +371,14 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFocusMode = QAction(self.tr("Focus Mode"), self)
|
||||
self.aFocusMode.setShortcut("F8")
|
||||
self.aFocusMode.setCheckable(True)
|
||||
self.aFocusMode.setChecked(self.theParent.isFocusMode)
|
||||
self.aFocusMode.triggered.connect(lambda: self.theParent.toggleFocusMode())
|
||||
self.aFocusMode.setChecked(self.mainGui.isFocusMode)
|
||||
self.aFocusMode.triggered.connect(lambda: self.mainGui.toggleFocusMode())
|
||||
self.viewMenu.addAction(self.aFocusMode)
|
||||
|
||||
# View > Toggle Full Screen
|
||||
self.aFullScreen = QAction(self.tr("Full Screen Mode"), self)
|
||||
self.aFullScreen.setShortcut("F11")
|
||||
self.aFullScreen.triggered.connect(lambda: self.theParent.toggleFullScreenMode())
|
||||
self.aFullScreen.triggered.connect(lambda: self.mainGui.toggleFullScreenMode())
|
||||
self.viewMenu.addAction(self.aFullScreen)
|
||||
|
||||
return
|
||||
@@ -669,6 +581,11 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aInsVSpaceM.triggered.connect(lambda: self._docInsert(nwDocInsert.VSPACE_M))
|
||||
self.mInsBreaks.addAction(self.aInsVSpaceM)
|
||||
|
||||
# Insert > Placeholder Text
|
||||
self.aLipsumText = QAction(self.tr("Placeholder Text"), self)
|
||||
self.aLipsumText.triggered.connect(lambda: self.mainGui.showLoremIpsumDialog())
|
||||
self.insertMenu.addAction(self.aLipsumText)
|
||||
|
||||
return
|
||||
|
||||
def _buildFormatMenu(self):
|
||||
@@ -830,7 +747,7 @@ class GuiMainMenu(QMenuBar):
|
||||
# Search > Find
|
||||
self.aFind = QAction(self.tr("Find"), self)
|
||||
self.aFind.setShortcut("Ctrl+F")
|
||||
self.aFind.triggered.connect(lambda: self.theParent.docEditor.beginSearch())
|
||||
self.aFind.triggered.connect(lambda: self.mainGui.docEditor.beginSearch())
|
||||
self.srcMenu.addAction(self.aFind)
|
||||
|
||||
# Search > Replace
|
||||
@@ -839,7 +756,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aReplace.setShortcut("Ctrl+=")
|
||||
else:
|
||||
self.aReplace.setShortcut("Ctrl+H")
|
||||
self.aReplace.triggered.connect(lambda: self.theParent.docEditor.beginReplace())
|
||||
self.aReplace.triggered.connect(lambda: self.mainGui.docEditor.beginReplace())
|
||||
self.srcMenu.addAction(self.aReplace)
|
||||
|
||||
# Search > Find Next
|
||||
@@ -848,7 +765,7 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFindNext.setShortcuts(["Ctrl+G", "F3"])
|
||||
else:
|
||||
self.aFindNext.setShortcuts(["F3", "Ctrl+G"])
|
||||
self.aFindNext.triggered.connect(lambda: self.theParent.docEditor.findNext())
|
||||
self.aFindNext.triggered.connect(lambda: self.mainGui.docEditor.findNext())
|
||||
self.srcMenu.addAction(self.aFindNext)
|
||||
|
||||
# Search > Find Prev
|
||||
@@ -857,13 +774,13 @@ class GuiMainMenu(QMenuBar):
|
||||
self.aFindPrev.setShortcuts(["Ctrl+Shift+G", "Shift+F3"])
|
||||
else:
|
||||
self.aFindPrev.setShortcuts(["Shift+F3", "Ctrl+Shift+G"])
|
||||
self.aFindPrev.triggered.connect(lambda: self.theParent.docEditor.findNext(goBack=True))
|
||||
self.aFindPrev.triggered.connect(lambda: self.mainGui.docEditor.findNext(goBack=True))
|
||||
self.srcMenu.addAction(self.aFindPrev)
|
||||
|
||||
# Search > Replace Next
|
||||
self.aReplaceNext = QAction(self.tr("Replace Next"), self)
|
||||
self.aReplaceNext.setShortcut("Ctrl+Shift+1")
|
||||
self.aReplaceNext.triggered.connect(lambda: self.theParent.docEditor.replaceNext())
|
||||
self.aReplaceNext.triggered.connect(lambda: self.mainGui.docEditor.replaceNext())
|
||||
self.srcMenu.addAction(self.aReplaceNext)
|
||||
|
||||
return
|
||||
@@ -885,12 +802,12 @@ class GuiMainMenu(QMenuBar):
|
||||
# Tools > Re-Run Spell Check
|
||||
self.aReRunSpell = QAction(self.tr("Re-Run Spell Check"), self)
|
||||
self.aReRunSpell.setShortcut("F7")
|
||||
self.aReRunSpell.triggered.connect(lambda: self.theParent.docEditor.spellCheckDocument())
|
||||
self.aReRunSpell.triggered.connect(lambda: self.mainGui.docEditor.spellCheckDocument())
|
||||
self.toolsMenu.addAction(self.aReRunSpell)
|
||||
|
||||
# Tools > Project Word List
|
||||
self.aEditWordList = QAction(self.tr("Project Word List"), self)
|
||||
self.aEditWordList.triggered.connect(lambda: self.theParent.showProjectWordListDialog())
|
||||
self.aEditWordList.triggered.connect(lambda: self.mainGui.showProjectWordListDialog())
|
||||
self.toolsMenu.addAction(self.aEditWordList)
|
||||
|
||||
# Tools > Separator
|
||||
@@ -899,22 +816,9 @@ class GuiMainMenu(QMenuBar):
|
||||
# Tools > Rebuild Indices
|
||||
self.aRebuildIndex = QAction(self.tr("Rebuild Index"), self)
|
||||
self.aRebuildIndex.setShortcut("F9")
|
||||
self.aRebuildIndex.triggered.connect(lambda: self.theParent.rebuildIndex())
|
||||
self.aRebuildIndex.triggered.connect(lambda: self.mainGui.rebuildIndex())
|
||||
self.toolsMenu.addAction(self.aRebuildIndex)
|
||||
|
||||
# Tools > Rebuild Outline
|
||||
self.aRebuildOutline = QAction(self.tr("Rebuild Outline"), self)
|
||||
self.aRebuildOutline.setShortcut("F10")
|
||||
self.aRebuildOutline.triggered.connect(lambda: self.theParent.rebuildOutline())
|
||||
self.toolsMenu.addAction(self.aRebuildOutline)
|
||||
|
||||
# Tools > Toggle Auto Build Outline
|
||||
self.aAutoOutline = QAction(self.tr("Auto-Update Outline"), self)
|
||||
self.aAutoOutline.setCheckable(True)
|
||||
self.aAutoOutline.toggled.connect(self._toggleAutoOutline)
|
||||
self.aAutoOutline.setShortcut("Ctrl+F10")
|
||||
self.toolsMenu.addAction(self.aAutoOutline)
|
||||
|
||||
# Tools > Separator
|
||||
self.toolsMenu.addSeparator()
|
||||
|
||||
@@ -926,20 +830,20 @@ class GuiMainMenu(QMenuBar):
|
||||
# Tools > Export Project
|
||||
self.aBuildProject = QAction(self.tr("Build Novel Project"), self)
|
||||
self.aBuildProject.setShortcut("F5")
|
||||
self.aBuildProject.triggered.connect(lambda: self.theParent.showBuildProjectDialog())
|
||||
self.aBuildProject.triggered.connect(lambda: self.mainGui.showBuildProjectDialog())
|
||||
self.toolsMenu.addAction(self.aBuildProject)
|
||||
|
||||
# Tools > Writing Stats
|
||||
self.aWritingStats = QAction(self.tr("Writing Statistics"), self)
|
||||
self.aWritingStats.setShortcut("F6")
|
||||
self.aWritingStats.triggered.connect(lambda: self.theParent.showWritingStatsDialog())
|
||||
self.aWritingStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog())
|
||||
self.toolsMenu.addAction(self.aWritingStats)
|
||||
|
||||
# Tools > Settings
|
||||
self.aPreferences = QAction(self.tr("Preferences"), self)
|
||||
self.aPreferences.setShortcut("Ctrl+,")
|
||||
self.aPreferences.setMenuRole(QAction.PreferencesRole)
|
||||
self.aPreferences.triggered.connect(lambda: self.theParent.showPreferencesDialog())
|
||||
self.aPreferences.triggered.connect(lambda: self.mainGui.showPreferencesDialog())
|
||||
self.toolsMenu.addAction(self.aPreferences)
|
||||
|
||||
return
|
||||
@@ -953,13 +857,13 @@ class GuiMainMenu(QMenuBar):
|
||||
# Help > About
|
||||
self.aAboutNW = QAction(self.tr("About novelWriter"), self)
|
||||
self.aAboutNW.setMenuRole(QAction.AboutRole)
|
||||
self.aAboutNW.triggered.connect(lambda: self.theParent.showAboutNWDialog())
|
||||
self.aAboutNW.triggered.connect(lambda: self.mainGui.showAboutNWDialog())
|
||||
self.helpMenu.addAction(self.aAboutNW)
|
||||
|
||||
# Help > About Qt5
|
||||
self.aAboutQt = QAction(self.tr("About Qt5"), self)
|
||||
self.aAboutQt.setMenuRole(QAction.AboutQtRole)
|
||||
self.aAboutQt.triggered.connect(lambda: self.theParent.showAboutQtDialog())
|
||||
self.aAboutQt.triggered.connect(lambda: self.mainGui.showAboutQtDialog())
|
||||
self.helpMenu.addAction(self.aAboutQt)
|
||||
|
||||
# Help > Separator
|
||||
@@ -1006,7 +910,7 @@ class GuiMainMenu(QMenuBar):
|
||||
|
||||
# Document > Check for Updates
|
||||
self.aUpdates = QAction(self.tr("Check for New Release"), self)
|
||||
self.aUpdates.triggered.connect(lambda: self.theParent.showUpdatesDialog())
|
||||
self.aUpdates.triggered.connect(lambda: self.mainGui.showUpdatesDialog())
|
||||
self.helpMenu.addAction(self.aUpdates)
|
||||
|
||||
return
|
||||
|
||||
@@ -4,7 +4,9 @@ novelWriter – GUI Novel Tree
|
||||
GUI classe for the main window novel tree
|
||||
|
||||
File History:
|
||||
Created: 2020-12-20 [1.1a0]
|
||||
Created: 2020-12-20 [1.1a0] GuiNovelTree
|
||||
Created: 2022-06-12 [1.7b1] GuiNovelView
|
||||
Created: 2022-06-12 [1.7b1] GuiNovelToolBar
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||
@@ -26,82 +28,390 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from enum import Enum
|
||||
from time import time
|
||||
|
||||
from PyQt5.QtCore import Qt, QSize
|
||||
from PyQt5.QtWidgets import QTreeWidget, QTreeWidgetItem, QAbstractItemView
|
||||
from PyQt5.QtGui import QPalette
|
||||
from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal
|
||||
from PyQt5.QtWidgets import (
|
||||
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel,
|
||||
QMenu, QSizePolicy, QToolButton, QToolTip, QTreeWidget, QTreeWidgetItem,
|
||||
QVBoxLayout, QWidget
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwDocMode, nwItemClass, nwOutline
|
||||
from novelwriter.common import checkInt
|
||||
from novelwriter.constants import nwKeyWords
|
||||
from novelwriter.constants import nwHeaders, nwKeyWords, nwLabels, trConst
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class NovelTreeColumn(Enum):
|
||||
|
||||
HIDDEN = 0
|
||||
POV = 1
|
||||
FOCUS = 2
|
||||
PLOT = 3
|
||||
|
||||
# END Enum NovelTreeColumn
|
||||
|
||||
|
||||
class GuiNovelView(QWidget):
|
||||
|
||||
# Signals for user interaction with the novel tree
|
||||
selectedItemChanged = pyqtSignal(str)
|
||||
openDocumentRequest = pyqtSignal(str, Enum, int, str)
|
||||
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
self.mainGui = mainGui
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
# Build GUI
|
||||
self.novelBar = GuiNovelToolBar(self)
|
||||
self.novelTree = GuiNovelTree(self)
|
||||
|
||||
# Assemble
|
||||
self.outerBox = QVBoxLayout()
|
||||
self.outerBox.addWidget(self.novelBar, 0)
|
||||
self.outerBox.addWidget(self.novelTree, 1)
|
||||
self.outerBox.setContentsMargins(0, 0, 0, 0)
|
||||
self.outerBox.setSpacing(0)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
# Function Mappings
|
||||
self.updateWordCounts = self.novelTree.updateWordCounts
|
||||
self.getSelectedHandle = self.novelTree.getSelectedHandle
|
||||
self.setActiveHandle = self.novelTree.setActiveHandle
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def initSettings(self):
|
||||
"""Initialise GUI elements that depend on specific settings.
|
||||
"""
|
||||
self.novelTree.initSettings()
|
||||
return
|
||||
|
||||
def refreshTree(self):
|
||||
"""Refresh the current tree.
|
||||
"""
|
||||
self.novelTree.refreshTree(rootHandle=self.theProject.lastNovel)
|
||||
return
|
||||
|
||||
def clearProject(self):
|
||||
"""Clear project-related GUI content.
|
||||
"""
|
||||
self.novelTree.clearContent()
|
||||
self.novelBar.clearContent()
|
||||
return
|
||||
|
||||
def openProjectTasks(self):
|
||||
"""Run open project tasks.
|
||||
"""
|
||||
lastNovel = self.theProject.lastNovel
|
||||
if lastNovel not in self.theProject.tree:
|
||||
lastNovel = self.theProject.tree.findRoot(nwItemClass.NOVEL)
|
||||
|
||||
logger.debug("Setting novel tree to root item '%s'", lastNovel)
|
||||
|
||||
lastCol = self.theProject.options.getEnum(
|
||||
"GuiNovelView", "lastCol", NovelTreeColumn, NovelTreeColumn.HIDDEN
|
||||
)
|
||||
|
||||
self.clearProject()
|
||||
self.novelBar.buildNovelRootMenu()
|
||||
self.novelBar.setLastColType(lastCol, doRefresh=False)
|
||||
self.novelBar.setCurrentRoot(lastNovel)
|
||||
|
||||
return
|
||||
|
||||
def closeProjectTasks(self):
|
||||
"""Run closing project tasks.
|
||||
"""
|
||||
lastColType = self.novelTree.lastColType
|
||||
self.theProject.options.setValue("GuiNovelView", "lastCol", lastColType)
|
||||
return
|
||||
|
||||
def setTreeFocus(self):
|
||||
"""Set the focus to the tree widget.
|
||||
"""
|
||||
self.novelTree.setFocus()
|
||||
return
|
||||
|
||||
def treeHasFocus(self):
|
||||
"""Check if the novel tree has focus.
|
||||
"""
|
||||
return self.novelTree.hasFocus()
|
||||
|
||||
##
|
||||
# Public Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str)
|
||||
def updateRootItem(self, tHandle):
|
||||
"""If any root item changes, rebuild the novel root menu.
|
||||
"""
|
||||
self.novelBar.buildNovelRootMenu()
|
||||
return
|
||||
|
||||
# END Class GuiNovelView
|
||||
|
||||
|
||||
class GuiNovelToolBar(QWidget):
|
||||
|
||||
def __init__(self, novelView):
|
||||
super().__init__(parent=novelView)
|
||||
|
||||
logger.debug("Initialising GuiNovelToolBar ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.novelView = novelView
|
||||
self.theProject = novelView.mainGui.theProject
|
||||
self.mainTheme = novelView.mainGui.mainTheme
|
||||
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
mPx = self.mainConf.pxInt(2)
|
||||
|
||||
self.setContentsMargins(0, 0, 0, 0)
|
||||
self.setAutoFillBackground(True)
|
||||
|
||||
qPalette = self.palette()
|
||||
qPalette.setBrush(QPalette.Window, qPalette.base())
|
||||
self.setPalette(qPalette)
|
||||
|
||||
fadeCol = qPalette.text().color()
|
||||
buttonStyle = (
|
||||
"QToolButton {{padding: {0}px; border: none; background: transparent;}} "
|
||||
"QToolButton:hover {{border: none; background: rgba({1},{2},{3},0.2);}}"
|
||||
).format(mPx, fadeCol.red(), fadeCol.green(), fadeCol.blue())
|
||||
|
||||
# Widget Label
|
||||
self.viewLabel = QLabel("<b>%s</b>" % self.tr("Novel Outline"))
|
||||
self.viewLabel.setContentsMargins(0, 0, 0, 0)
|
||||
self.viewLabel.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Expanding)
|
||||
|
||||
# Refresh Button
|
||||
self.tbRefresh = QToolButton(self)
|
||||
self.tbRefresh.setToolTip(self.tr("Refresh"))
|
||||
self.tbRefresh.setIcon(self.mainTheme.getIcon("refresh"))
|
||||
self.tbRefresh.setIconSize(QSize(iPx, iPx))
|
||||
self.tbRefresh.setStyleSheet(buttonStyle)
|
||||
self.tbRefresh.clicked.connect(self._refreshNovelTree)
|
||||
|
||||
# Novel Root Menu
|
||||
self.mRoot = QMenu()
|
||||
self.gRoot = QActionGroup(self.mRoot)
|
||||
self.aRoot = {}
|
||||
|
||||
self.tbRoot = QToolButton(self)
|
||||
self.tbRoot.setToolTip(self.tr("Novel Root"))
|
||||
self.tbRoot.setIcon(self.mainTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL]))
|
||||
self.tbRoot.setIconSize(QSize(iPx, iPx))
|
||||
self.tbRoot.setStyleSheet(buttonStyle)
|
||||
self.tbRoot.setMenu(self.mRoot)
|
||||
self.tbRoot.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
# More Options Menu
|
||||
self.mMore = QMenu()
|
||||
|
||||
self.mLastCol = self.mMore.addMenu(self.tr("Last Column"))
|
||||
self.gLastCol = QActionGroup(self.mMore)
|
||||
self.aLastCol = {}
|
||||
self._addLastColAction(NovelTreeColumn.HIDDEN, self.tr("Hidden"))
|
||||
self._addLastColAction(NovelTreeColumn.POV, self.tr("Point of View Character"))
|
||||
self._addLastColAction(NovelTreeColumn.FOCUS, self.tr("Focus Character"))
|
||||
self._addLastColAction(NovelTreeColumn.PLOT, self.tr("Novel Plot"))
|
||||
|
||||
self.tbMore = QToolButton(self)
|
||||
self.tbMore.setToolTip(self.tr("More Options"))
|
||||
self.tbMore.setIcon(self.mainTheme.getIcon("menu"))
|
||||
self.tbMore.setIconSize(QSize(iPx, iPx))
|
||||
self.tbMore.setStyleSheet(buttonStyle)
|
||||
self.tbMore.setMenu(self.mMore)
|
||||
self.tbMore.setPopupMode(QToolButton.InstantPopup)
|
||||
|
||||
# Assemble
|
||||
self.outerBox = QHBoxLayout()
|
||||
self.outerBox.addWidget(self.viewLabel)
|
||||
self.outerBox.addWidget(self.tbRefresh)
|
||||
self.outerBox.addWidget(self.tbRoot)
|
||||
self.outerBox.addWidget(self.tbMore)
|
||||
self.outerBox.setContentsMargins(mPx, mPx, 0, mPx)
|
||||
self.outerBox.setSpacing(0)
|
||||
|
||||
self.setLayout(self.outerBox)
|
||||
|
||||
logger.debug("GuiNovelToolBar initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Methods
|
||||
##
|
||||
|
||||
def clearContent(self):
|
||||
"""Run clearing project tasks.
|
||||
"""
|
||||
self.mRoot.clear()
|
||||
self.aRoot = {}
|
||||
return
|
||||
|
||||
def buildNovelRootMenu(self):
|
||||
"""Build the novel root menu.
|
||||
"""
|
||||
self.mRoot.clear()
|
||||
self.aRoot = {}
|
||||
for n, (tHandle, nwItem) in enumerate(self.theProject.tree.iterRoots(nwItemClass.NOVEL)):
|
||||
aRoot = self.mRoot.addAction(nwItem.itemName)
|
||||
aRoot.setData(tHandle)
|
||||
aRoot.setCheckable(True)
|
||||
aRoot.triggered.connect(lambda n, tHandle=tHandle: self.setCurrentRoot(tHandle))
|
||||
self.gRoot.addAction(aRoot)
|
||||
self.aRoot[tHandle] = aRoot
|
||||
|
||||
return
|
||||
|
||||
def setCurrentRoot(self, rootHandle):
|
||||
"""Set the current active root handle.
|
||||
"""
|
||||
if rootHandle in self.aRoot:
|
||||
self.aRoot[rootHandle].setChecked(True)
|
||||
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
|
||||
return
|
||||
|
||||
def setLastColType(self, colType, doRefresh=True):
|
||||
"""Set the last column type.
|
||||
"""
|
||||
self.aLastCol[colType].setChecked(True)
|
||||
self.novelView.novelTree.setLastColType(colType, doRefresh=doRefresh)
|
||||
return
|
||||
|
||||
##
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot()
|
||||
def _refreshNovelTree(self):
|
||||
"""Rebuild the current tree.
|
||||
"""
|
||||
rootHandle = self.theProject.lastNovel
|
||||
self.novelView.novelTree.refreshTree(rootHandle=rootHandle, overRide=True)
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _addLastColAction(self, colType, actionLabel):
|
||||
"""Add a column selection entry to the last column menu.
|
||||
"""
|
||||
aLast = self.mLastCol.addAction(actionLabel)
|
||||
aLast.setCheckable(True)
|
||||
aLast.setActionGroup(self.gLastCol)
|
||||
aLast.triggered.connect(lambda: self.setLastColType(colType))
|
||||
self.aLastCol[colType] = aLast
|
||||
return
|
||||
|
||||
# END Class GuiNovelToolBar
|
||||
|
||||
|
||||
class GuiNovelTree(QTreeWidget):
|
||||
|
||||
C_TITLE = 0
|
||||
C_WORDS = 1
|
||||
C_POV = 2
|
||||
C_EXTRA = 2
|
||||
C_MORE = 3
|
||||
|
||||
def __init__(self, theParent):
|
||||
QTreeWidget.__init__(self, theParent)
|
||||
D_HANDLE = Qt.UserRole
|
||||
D_TITLE = Qt.UserRole + 1
|
||||
D_KEY = Qt.UserRole + 2
|
||||
|
||||
def __init__(self, novelView):
|
||||
super().__init__(parent=novelView)
|
||||
|
||||
logger.debug("Initialising GuiNovelTree ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.theIndex = theParent.theIndex
|
||||
self.novelView = novelView
|
||||
self.mainGui = novelView.mainGui
|
||||
self.mainTheme = novelView.mainGui.mainTheme
|
||||
self.theProject = novelView.mainGui.theProject
|
||||
|
||||
# Internal Variables
|
||||
self._treeMap = {}
|
||||
self._lastBuild = 0
|
||||
self._lastCol = NovelTreeColumn.POV
|
||||
self._actHandle = None
|
||||
|
||||
# Cached Strings
|
||||
self._povLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.POV_KEY])
|
||||
self._focLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.FOCUS_KEY])
|
||||
self._pltLabel = trConst(nwLabels.KEY_NAME[nwKeyWords.PLOT_KEY])
|
||||
|
||||
# Build GUI
|
||||
iPx = self.theTheme.baseIconSize
|
||||
# =========
|
||||
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
cMg = self.mainConf.pxInt(6)
|
||||
|
||||
self.setIconSize(QSize(iPx, iPx))
|
||||
self.setIndentation(iPx)
|
||||
self.setColumnCount(3)
|
||||
self.setHeaderLabels([
|
||||
self.tr("Novel Outline"),
|
||||
self.tr("Words"),
|
||||
self.tr("POV")
|
||||
])
|
||||
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||
self.itemSelectionChanged.connect(self._itemSelected)
|
||||
self.setFrameStyle(QFrame.NoFrame)
|
||||
self.setUniformRowHeights(True)
|
||||
self.setAllColumnsShowFocus(True)
|
||||
self.setHeaderHidden(True)
|
||||
self.setIndentation(0)
|
||||
self.setColumnCount(4)
|
||||
self.setSelectionBehavior(QAbstractItemView.SelectRows)
|
||||
self.setSelectionMode(QAbstractItemView.SingleSelection)
|
||||
self.setExpandsOnDoubleClick(False)
|
||||
self.setDragEnabled(False)
|
||||
|
||||
treeHeadItem = self.headerItem()
|
||||
treeHeadItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
treeHeadItem.setToolTip(self.C_TITLE, self.tr("Section title"))
|
||||
treeHeadItem.setToolTip(self.C_WORDS, self.tr("Word count"))
|
||||
treeHeadItem.setToolTip(self.C_POV, self.tr("Point-of-view character"))
|
||||
|
||||
# Lock the column sizes
|
||||
treeHeader = self.header()
|
||||
treeHeader.setStretchLastSection(True)
|
||||
treeHeader.setMinimumSectionSize(iPx + 6)
|
||||
treeHeader.setStretchLastSection(False)
|
||||
treeHeader.setMinimumSectionSize(iPx + cMg)
|
||||
treeHeader.setSectionResizeMode(self.C_TITLE, QHeaderView.Stretch)
|
||||
treeHeader.setSectionResizeMode(self.C_WORDS, QHeaderView.ResizeToContents)
|
||||
treeHeader.setSectionResizeMode(self.C_EXTRA, QHeaderView.ResizeToContents)
|
||||
treeHeader.setSectionResizeMode(self.C_MORE, QHeaderView.ResizeToContents)
|
||||
|
||||
# Get user's column width preferences for NAME and COUNT
|
||||
treeColWidth = self.mainConf.getNovelColWidths()
|
||||
if len(treeColWidth) <= 3:
|
||||
for colN, colW in enumerate(treeColWidth):
|
||||
self.setColumnWidth(colN, colW)
|
||||
# Pre-Generate Tree Formatting
|
||||
fH1 = self.font()
|
||||
fH1.setBold(True)
|
||||
fH1.setUnderline(True)
|
||||
|
||||
# The last column should just auto-scale
|
||||
self.resizeColumnToContents(self.C_POV)
|
||||
fH2 = self.font()
|
||||
fH2.setBold(True)
|
||||
|
||||
self._hFonts = [self.font(), fH1, fH2, self.font(), self.font()]
|
||||
self._pIndent = [
|
||||
self.mainTheme.loadDecoration("deco_doc_h0", pxH=iPx),
|
||||
self.mainTheme.loadDecoration("deco_doc_h1", pxH=iPx),
|
||||
self.mainTheme.loadDecoration("deco_doc_h2", pxH=iPx),
|
||||
self.mainTheme.loadDecoration("deco_doc_h3", pxH=iPx),
|
||||
self.mainTheme.loadDecoration("deco_doc_h4", pxH=iPx),
|
||||
]
|
||||
self._pMore = self.mainTheme.loadDecoration("deco_doc_more", pxH=iPx)
|
||||
|
||||
# Connect signals
|
||||
self.clicked.connect(self._treeItemClicked)
|
||||
self.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||
self.itemSelectionChanged.connect(self._treeSelectionChange)
|
||||
|
||||
# Set custom settings
|
||||
self.initTree()
|
||||
self.initSettings()
|
||||
|
||||
logger.debug("GuiNovelTree initialisation complete")
|
||||
|
||||
return
|
||||
|
||||
def initTree(self):
|
||||
def initSettings(self):
|
||||
"""Set or update tree widget settings.
|
||||
"""
|
||||
# Scroll bars
|
||||
@@ -117,11 +427,19 @@ class GuiNovelTree(QTreeWidget):
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Properties
|
||||
##
|
||||
|
||||
@property
|
||||
def lastColType(self):
|
||||
return self._lastCol
|
||||
|
||||
##
|
||||
# Class Methods
|
||||
##
|
||||
|
||||
def clearTree(self):
|
||||
def clearContent(self):
|
||||
"""Clear the GUI content and the related maps.
|
||||
"""
|
||||
self.clear()
|
||||
@@ -129,12 +447,15 @@ class GuiNovelTree(QTreeWidget):
|
||||
self._lastBuild = 0
|
||||
return
|
||||
|
||||
def refreshTree(self, overRide=False):
|
||||
def refreshTree(self, rootHandle=None, overRide=False):
|
||||
"""Called whenever the Novel tab is activated.
|
||||
"""
|
||||
logger.verbose("Requesting refresh of the novel tree")
|
||||
treeChanged = self.theParent.treeView.changedSince(self._lastBuild)
|
||||
indexChanged = self.theIndex.novelChangedSince(self._lastBuild)
|
||||
if rootHandle is None:
|
||||
rootHandle = self.theProject.tree.findRoot(nwItemClass.NOVEL)
|
||||
|
||||
treeChanged = self.mainGui.projView.changedSince(self._lastBuild)
|
||||
indexChanged = self.theProject.index.rootChangedSince(rootHandle, self._lastBuild)
|
||||
if not (treeChanged or indexChanged or overRide):
|
||||
logger.verbose("No changes have been made to the novel index")
|
||||
return
|
||||
@@ -142,10 +463,10 @@ class GuiNovelTree(QTreeWidget):
|
||||
selItem = self.selectedItems()
|
||||
titleKey = None
|
||||
if selItem:
|
||||
titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2]
|
||||
titleKey = selItem[0].data(self.C_TITLE, self.D_KEY)
|
||||
|
||||
self.theParent.treeView.flushTreeOrder()
|
||||
self._populateTree()
|
||||
self._populateTree(rootHandle)
|
||||
self.theProject.setLastNovelViewed(rootHandle)
|
||||
|
||||
if titleKey is not None and titleKey in self._treeMap:
|
||||
self._treeMap[titleKey].setSelected(True)
|
||||
@@ -155,21 +476,12 @@ class GuiNovelTree(QTreeWidget):
|
||||
def updateWordCounts(self, tHandle):
|
||||
"""Update the word count for a given handle.
|
||||
"""
|
||||
tHeaders = self.theIndex.getHandleWordCounts(tHandle)
|
||||
tHeaders = self.theProject.index.getHandleWordCounts(tHandle)
|
||||
for titleKey, wCount in tHeaders:
|
||||
if titleKey in self._treeMap:
|
||||
self._treeMap[titleKey].setText(self.C_WORDS, f"{wCount:n}")
|
||||
return
|
||||
|
||||
def getColumnSizes(self):
|
||||
"""Return the column widths for the tree columns.
|
||||
"""
|
||||
retVals = [
|
||||
self.columnWidth(0),
|
||||
self.columnWidth(1),
|
||||
]
|
||||
return retVals
|
||||
|
||||
def getSelectedHandle(self):
|
||||
"""Get the currently selected handle. If multiple items are
|
||||
selected, return the first.
|
||||
@@ -178,11 +490,46 @@ class GuiNovelTree(QTreeWidget):
|
||||
tHandle = None
|
||||
tLine = 0
|
||||
if selItem:
|
||||
tHandle = selItem[0].data(self.C_TITLE, Qt.UserRole)[0]
|
||||
tLine = checkInt(selItem[0].data(self.C_TITLE, Qt.UserRole)[1], 1) - 1
|
||||
tHandle = selItem[0].data(self.C_TITLE, self.D_HANDLE)
|
||||
sTitle = selItem[0].data(self.C_TITLE, self.D_TITLE)
|
||||
tLine = checkInt(sTitle[1:], 1) - 1
|
||||
|
||||
return tHandle, tLine
|
||||
|
||||
def setLastColType(self, colType, doRefresh=True):
|
||||
"""Change the content type of the last column and rebuild.
|
||||
"""
|
||||
if self._lastCol != colType:
|
||||
logger.debug("Changing last column to %s", colType.name)
|
||||
self._lastCol = colType
|
||||
self.setColumnHidden(self.C_EXTRA, colType == NovelTreeColumn.HIDDEN)
|
||||
if doRefresh:
|
||||
self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True)
|
||||
return
|
||||
|
||||
def setActiveHandle(self, tHandle):
|
||||
"""Highlight the rows associated with a given handle.
|
||||
"""
|
||||
tStart = time()
|
||||
|
||||
self._actHandle = tHandle
|
||||
for i in range(self.topLevelItemCount()):
|
||||
tItem = self.topLevelItem(i)
|
||||
if tItem.data(self.C_TITLE, self.D_HANDLE) == tHandle:
|
||||
tItem.setBackground(self.C_TITLE, self.palette().alternateBase())
|
||||
tItem.setBackground(self.C_WORDS, self.palette().alternateBase())
|
||||
tItem.setBackground(self.C_EXTRA, self.palette().alternateBase())
|
||||
tItem.setBackground(self.C_MORE, self.palette().alternateBase())
|
||||
else:
|
||||
tItem.setBackground(self.C_TITLE, self.palette().base())
|
||||
tItem.setBackground(self.C_WORDS, self.palette().base())
|
||||
tItem.setBackground(self.C_EXTRA, self.palette().base())
|
||||
tItem.setBackground(self.C_MORE, self.palette().base())
|
||||
|
||||
logger.verbose("Highlighted Novel Tree in %.3f ms", (time() - tStart)*1000)
|
||||
|
||||
return
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -192,7 +539,7 @@ class GuiNovelTree(QTreeWidget):
|
||||
mouse in a blank area of the tree view, and to load a document
|
||||
for viewing if the user middle-clicked.
|
||||
"""
|
||||
QTreeWidget.mousePressEvent(self, theEvent)
|
||||
super().mousePressEvent(theEvent)
|
||||
|
||||
if theEvent.button() == Qt.LeftButton:
|
||||
selItem = self.indexAt(theEvent.pos())
|
||||
@@ -208,113 +555,161 @@ class GuiNovelTree(QTreeWidget):
|
||||
if tHandle is None:
|
||||
return
|
||||
|
||||
self.theParent.viewDocument(tHandle)
|
||||
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
|
||||
|
||||
return
|
||||
|
||||
def focusOutEvent(self, theEvent):
|
||||
"""Clear the selection when the tree no longer has focus.
|
||||
"""
|
||||
super().focusOutEvent(theEvent)
|
||||
self.clearSelection()
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
def _treeDoubleClick(self, tItem, tCol):
|
||||
@pyqtSlot("QModelIndex")
|
||||
def _treeItemClicked(self, mIndex):
|
||||
"""The user clicked on an item in the tree.
|
||||
"""
|
||||
if mIndex.column() == self.C_MORE:
|
||||
tHandle = mIndex.siblingAtColumn(self.C_TITLE).data(self.D_HANDLE)
|
||||
sTitle = mIndex.siblingAtColumn(self.C_TITLE).data(self.D_TITLE)
|
||||
tipPos = self.mapToGlobal(self.visualRect(mIndex).topRight())
|
||||
self._popMetaBox(tipPos, tHandle, sTitle)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _treeSelectionChange(self):
|
||||
"""Extract the handle and line number of the currently selected
|
||||
title, and send it to the tree meta panel.
|
||||
"""
|
||||
tHandle, _ = self.getSelectedHandle()
|
||||
if tHandle is not None:
|
||||
self.novelView.selectedItemChanged.emit(tHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot("QTreeWidgetItem*", int)
|
||||
def _treeDoubleClick(self, tItem, colNo):
|
||||
"""Extract the handle and line number of the title double-
|
||||
clicked, and send it to the main gui class for opening in the
|
||||
document editor.
|
||||
"""
|
||||
tHandle, tLine = self.getSelectedHandle()
|
||||
self.theParent.openDocument(tHandle, tLine=tLine-1, doScroll=True)
|
||||
return
|
||||
|
||||
def _itemSelected(self):
|
||||
"""Extract the handle and line number of the currently selected
|
||||
title, and send it to the tree meta panel.
|
||||
"""
|
||||
selItems = self.selectedItems()
|
||||
if selItems:
|
||||
tHandle = selItems[0].data(self.C_TITLE, Qt.UserRole)[0]
|
||||
self.theParent.treeMeta.updateViewBox(tHandle)
|
||||
|
||||
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.EDIT, tLine, "")
|
||||
return
|
||||
|
||||
##
|
||||
# Internal Functions
|
||||
##
|
||||
|
||||
def _populateTree(self):
|
||||
def _populateTree(self, rootHandle):
|
||||
"""Build the tree based on the project index.
|
||||
"""
|
||||
self.clearTree()
|
||||
self.clearContent()
|
||||
tStart = time()
|
||||
logger.verbose("Building novel tree for root item '%s'", rootHandle)
|
||||
|
||||
currTitle = None
|
||||
currChapter = None
|
||||
currScene = None
|
||||
novStruct = self.theProject.index.novelStructure(rootHandle=rootHandle, skipExcl=True)
|
||||
for tKey, tHandle, sTitle, novIdx in novStruct:
|
||||
|
||||
for tKey, tHandle, sTitle, novIdx in self.theIndex.novelStructure(skipExcluded=True):
|
||||
iLevel = nwHeaders.H_LEVEL.get(novIdx.level, 0)
|
||||
if iLevel == 0:
|
||||
continue
|
||||
|
||||
tItem = self._createTreeItem(tHandle, sTitle, tKey, novIdx)
|
||||
self._treeMap[tKey] = tItem
|
||||
newItem = QTreeWidgetItem()
|
||||
newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel])
|
||||
newItem.setText(self.C_TITLE, novIdx.title)
|
||||
newItem.setData(self.C_TITLE, self.D_HANDLE, tHandle)
|
||||
newItem.setData(self.C_TITLE, self.D_TITLE, sTitle)
|
||||
newItem.setData(self.C_TITLE, self.D_KEY, tKey)
|
||||
newItem.setFont(self.C_TITLE, self._hFonts[iLevel])
|
||||
newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}")
|
||||
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
newItem.setData(self.C_MORE, Qt.DecorationRole, self._pMore)
|
||||
|
||||
tLevel = novIdx["level"]
|
||||
if tLevel == "H1":
|
||||
self.addTopLevelItem(tItem)
|
||||
currTitle = tItem
|
||||
currChapter = None
|
||||
currScene = None
|
||||
# Custom column
|
||||
lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
|
||||
newItem.setText(self.C_EXTRA, lastText)
|
||||
if lastText:
|
||||
newItem.setToolTip(self.C_EXTRA, toolTip)
|
||||
|
||||
elif tLevel == "H2":
|
||||
if currTitle is None:
|
||||
self.addTopLevelItem(tItem)
|
||||
else:
|
||||
currTitle.addChild(tItem)
|
||||
currChapter = tItem
|
||||
currScene = None
|
||||
self._treeMap[tKey] = newItem
|
||||
self.addTopLevelItem(newItem)
|
||||
|
||||
elif tLevel == "H3":
|
||||
if currChapter is None:
|
||||
if currTitle is None:
|
||||
self.addTopLevelItem(tItem)
|
||||
else:
|
||||
currTitle.addChild(tItem)
|
||||
else:
|
||||
currChapter.addChild(tItem)
|
||||
currScene = tItem
|
||||
|
||||
elif tLevel == "H4":
|
||||
if currScene is None:
|
||||
if currChapter is None:
|
||||
if currTitle is None:
|
||||
self.addTopLevelItem(tItem)
|
||||
else:
|
||||
currTitle.addChild(tItem)
|
||||
else:
|
||||
currChapter.addChild(tItem)
|
||||
else:
|
||||
currScene.addChild(tItem)
|
||||
|
||||
tItem.setExpanded(True)
|
||||
self.setActiveHandle(self._actHandle)
|
||||
|
||||
logger.verbose("Novel Tree built in %.3f ms", (time() - tStart)*1000)
|
||||
self._lastBuild = time()
|
||||
|
||||
return
|
||||
|
||||
def _createTreeItem(self, tHandle, sTitle, titleKey, novIdx):
|
||||
"""Populate a tree item with all the column values.
|
||||
def _getLastColumnText(self, tHandle, sTitle):
|
||||
"""Generate the text for the last column based on user settings.
|
||||
"""
|
||||
newItem = QTreeWidgetItem()
|
||||
hIcon = "doc_%s" % novIdx["level"].lower()
|
||||
theData = (tHandle, sTitle[1:].lstrip("0"), titleKey)
|
||||
if self._lastCol == NovelTreeColumn.HIDDEN:
|
||||
return "", ""
|
||||
|
||||
wC = int(novIdx["wCount"])
|
||||
theRefs = self.theProject.index.getReferences(tHandle, sTitle)
|
||||
if self._lastCol == NovelTreeColumn.POV:
|
||||
newText = ", ".join(theRefs[nwKeyWords.POV_KEY])
|
||||
return newText, f"{self._povLabel}: {newText}"
|
||||
|
||||
newItem.setText(self.C_TITLE, novIdx["title"])
|
||||
newItem.setData(self.C_TITLE, Qt.UserRole, theData)
|
||||
newItem.setIcon(self.C_TITLE, self.theTheme.getIcon(hIcon))
|
||||
newItem.setText(self.C_WORDS, f"{wC:n}")
|
||||
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
|
||||
elif self._lastCol == NovelTreeColumn.FOCUS:
|
||||
newText = ", ".join(theRefs[nwKeyWords.FOCUS_KEY])
|
||||
return newText, f"{self._focLabel}: {newText}"
|
||||
|
||||
theRefs = self.theIndex.getReferences(tHandle, sTitle)
|
||||
newItem.setText(self.C_POV, ", ".join(theRefs[nwKeyWords.POV_KEY]))
|
||||
elif self._lastCol == NovelTreeColumn.PLOT:
|
||||
newText = ", ".join(theRefs[nwKeyWords.PLOT_KEY])
|
||||
return newText, f"{self._pltLabel}: {newText}"
|
||||
|
||||
return newItem
|
||||
return "", ""
|
||||
|
||||
def _popMetaBox(self, qPos, tHandle, sTitle):
|
||||
"""Show the novel meta data box.
|
||||
"""
|
||||
logger.debug("Generating meta data tooltip for '%s:%s'", tHandle, sTitle)
|
||||
|
||||
pIndex = self.theProject.index
|
||||
novIdx = pIndex.getNovelData(tHandle, sTitle)
|
||||
refTags = pIndex.getReferences(tHandle, sTitle)
|
||||
|
||||
synopText = novIdx.synopsis
|
||||
if synopText:
|
||||
synopLabel = trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP])
|
||||
synopText = f"<p><b>{synopLabel}</b>: {synopText}</p>"
|
||||
|
||||
refLines = []
|
||||
refLines = self._appendMetaTag(refTags, nwKeyWords.POV_KEY, refLines)
|
||||
refLines = self._appendMetaTag(refTags, nwKeyWords.FOCUS_KEY, refLines)
|
||||
refLines = self._appendMetaTag(refTags, nwKeyWords.CHAR_KEY, refLines)
|
||||
refLines = self._appendMetaTag(refTags, nwKeyWords.PLOT_KEY, refLines)
|
||||
refLines = self._appendMetaTag(refTags, nwKeyWords.TIME_KEY, refLines)
|
||||
refLines = self._appendMetaTag(refTags, nwKeyWords.WORLD_KEY, refLines)
|
||||
refLines = self._appendMetaTag(refTags, nwKeyWords.OBJECT_KEY, refLines)
|
||||
refLines = self._appendMetaTag(refTags, nwKeyWords.ENTITY_KEY, refLines)
|
||||
refLines = self._appendMetaTag(refTags, nwKeyWords.CUSTOM_KEY, refLines)
|
||||
|
||||
refText = ""
|
||||
if refLines:
|
||||
refList = "<br>".join(refLines)
|
||||
refText = f"<p>{refList}</p>"
|
||||
|
||||
ttText = refText + synopText or self.tr("No meta data")
|
||||
if ttText:
|
||||
QToolTip.showText(qPos, ttText)
|
||||
|
||||
return
|
||||
|
||||
@staticmethod
|
||||
def _appendMetaTag(refs, key, lines):
|
||||
"""Generate a reference list for a given reference key.
|
||||
"""
|
||||
tags = ", ".join(refs.get(key, []))
|
||||
if tags:
|
||||
lines.append(f"<b>{trConst(nwLabels.KEY_NAME[key])}</b>: {tags}")
|
||||
return lines
|
||||
|
||||
# END Class GuiNovelTree
|
||||
|
||||
@@ -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
|
||||
@@ -41,22 +41,22 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
class GuiMainStatus(QStatusBar):
|
||||
|
||||
def __init__(self, theParent):
|
||||
QStatusBar.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiMainStatus ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.refTime = None
|
||||
self.userIdle = False
|
||||
|
||||
colNone = QColor(*self.theTheme.statNone)
|
||||
colTrue = QColor(*self.theTheme.statUnsaved)
|
||||
colFalse = QColor(*self.theTheme.statSaved)
|
||||
colNone = QColor(*self.mainTheme.statNone)
|
||||
colTrue = QColor(*self.mainTheme.statUnsaved)
|
||||
colFalse = QColor(*self.mainTheme.statSaved)
|
||||
|
||||
iPx = self.theTheme.baseIconSize
|
||||
iPx = self.mainTheme.baseIconSize
|
||||
|
||||
# Permanent Widgets
|
||||
# =================
|
||||
@@ -66,7 +66,7 @@ class GuiMainStatus(QStatusBar):
|
||||
# The Spell Checker Language
|
||||
self.langIcon = QLabel("")
|
||||
self.langText = QLabel(self.tr("None"))
|
||||
self.langIcon.setPixmap(self.theTheme.getPixmap("status_lang", (iPx, iPx)))
|
||||
self.langIcon.setPixmap(self.mainTheme.getPixmap("status_lang", (iPx, iPx)))
|
||||
self.langIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.langText.setContentsMargins(0, 0, xM, 0)
|
||||
self.addPermanentWidget(self.langIcon)
|
||||
@@ -91,7 +91,7 @@ class GuiMainStatus(QStatusBar):
|
||||
# The Project and Session Stats
|
||||
self.statsIcon = QLabel()
|
||||
self.statsText = QLabel("")
|
||||
self.statsIcon.setPixmap(self.theTheme.getPixmap("status_stats", (iPx, iPx)))
|
||||
self.statsIcon.setPixmap(self.mainTheme.getPixmap("status_stats", (iPx, iPx)))
|
||||
self.statsIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.statsText.setContentsMargins(0, 0, xM, 0)
|
||||
self.addPermanentWidget(self.statsIcon)
|
||||
@@ -99,14 +99,14 @@ class GuiMainStatus(QStatusBar):
|
||||
|
||||
# The Session Clock
|
||||
# Set the mimimum width so the label doesn't rescale every second
|
||||
self.timePixmap = self.theTheme.getPixmap("status_time", (iPx, iPx))
|
||||
self.idlePixmap = self.theTheme.getPixmap("status_idle", (iPx, iPx))
|
||||
self.timePixmap = self.mainTheme.getPixmap("status_time", (iPx, iPx))
|
||||
self.idlePixmap = self.mainTheme.getPixmap("status_idle", (iPx, iPx))
|
||||
|
||||
self.timeIcon = QLabel()
|
||||
self.timeText = QLabel("")
|
||||
self.timeIcon.setPixmap(self.timePixmap)
|
||||
self.timeText.setToolTip(self.tr("Session Time"))
|
||||
self.timeText.setMinimumWidth(self.theTheme.getTextWidth("00:00:00:"))
|
||||
self.timeText.setMinimumWidth(self.mainTheme.getTextWidth("00:00:00:"))
|
||||
self.timeIcon.setContentsMargins(0, 0, 0, 0)
|
||||
self.timeText.setContentsMargins(0, 0, 0, 0)
|
||||
self.addPermanentWidget(self.timeIcon)
|
||||
|
||||
@@ -54,7 +54,7 @@ class GuiTheme:
|
||||
def __init__(self):
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theIcons = GuiIcons(self)
|
||||
self.iconCache = GuiIcons(self)
|
||||
|
||||
# Loaded Theme Settings
|
||||
# =====================
|
||||
@@ -127,13 +127,13 @@ class GuiTheme:
|
||||
|
||||
self.updateFont()
|
||||
self.updateTheme()
|
||||
self.theIcons.updateTheme()
|
||||
self.iconCache.updateTheme()
|
||||
|
||||
# Icon Functions
|
||||
self.getIcon = self.theIcons.getIcon
|
||||
self.getPixmap = self.theIcons.getPixmap
|
||||
self.getItemIcon = self.theIcons.getItemIcon
|
||||
self.loadDecoration = self.theIcons.loadDecoration
|
||||
self.getIcon = self.iconCache.getIcon
|
||||
self.getPixmap = self.iconCache.getPixmap
|
||||
self.getItemIcon = self.iconCache.getItemIcon
|
||||
self.loadDecoration = self.iconCache.loadDecoration
|
||||
|
||||
# Extract Other Info
|
||||
self.guiDPI = qApp.primaryScreen().logicalDotsPerInchX()
|
||||
@@ -456,34 +456,35 @@ class GuiIcons:
|
||||
|
||||
ICON_KEYS = {
|
||||
# Project and GUI icons
|
||||
"novelwriter", "proj_nwx",
|
||||
"cls_none", "cls_novel", "cls_plot", "cls_character", "cls_world",
|
||||
"cls_timeline", "cls_object", "cls_entity", "cls_custom", "cls_archive", "cls_trash",
|
||||
"proj_document", "proj_title", "proj_chapter", "proj_scene", "proj_note", "proj_folder",
|
||||
"status_lang", "status_time", "status_idle", "status_stats", "status_lines",
|
||||
"doc_h0", "doc_h1", "doc_h2", "doc_h3", "doc_h4",
|
||||
"search_case", "search_regex", "search_word", "search_loop", "search_project",
|
||||
"search_cancel", "search_preserve",
|
||||
"novelwriter", "cls_archive", "cls_character", "cls_custom", "cls_entity", "cls_none",
|
||||
"cls_novel", "cls_object", "cls_plot", "cls_timeline", "cls_trash", "cls_world", "doc_h0",
|
||||
"doc_h1", "doc_h2", "doc_h3", "doc_h4", "proj_chapter", "proj_details", "proj_document",
|
||||
"proj_folder", "proj_note", "proj_nwx", "proj_scene", "proj_stats", "proj_title",
|
||||
"search_cancel", "search_case", "search_loop", "search_preserve", "search_project",
|
||||
"search_regex", "search_word", "status_idle", "status_lang", "status_lines",
|
||||
"status_stats", "status_time", "view_build", "view_editor", "view_novel", "view_outline",
|
||||
|
||||
# General Button Icons
|
||||
"delete", "close", "done", "clear", "save", "add", "remove",
|
||||
"search", "search_replace", "edit", "check", "cross", "hash",
|
||||
"maximise", "minimise", "refresh", "reference", "backward",
|
||||
"forward", "settings",
|
||||
"add", "backward", "check", "clear", "close", "cross", "delete", "done", "down", "edit",
|
||||
"forward", "hash", "maximise", "menu", "minimise", "reference", "refresh", "remove",
|
||||
"save", "search_replace", "search", "settings", "up",
|
||||
|
||||
# Switches
|
||||
"sticky-on", "sticky-off",
|
||||
"bullet-on", "bullet-off",
|
||||
|
||||
# Decorations
|
||||
"deco_doc_h0", "deco_doc_h1", "deco_doc_h2", "deco_doc_h3", "deco_doc_h4", "deco_doc_more",
|
||||
}
|
||||
|
||||
DECO_MAP = {
|
||||
IMAGE_MAP = {
|
||||
"wiz-back": "wizard-back.jpg",
|
||||
}
|
||||
|
||||
def __init__(self, theTheme):
|
||||
def __init__(self, mainTheme):
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theTheme = theTheme
|
||||
self.mainTheme = mainTheme
|
||||
|
||||
# Storage
|
||||
self._qIcons = {}
|
||||
@@ -575,19 +576,22 @@ class GuiIcons:
|
||||
# Access Functions
|
||||
##
|
||||
|
||||
def loadDecoration(self, decoKey, pxW, pxH):
|
||||
def loadDecoration(self, decoKey, pxW=None, pxH=None):
|
||||
"""Load graphical decoration element based on the decoration
|
||||
map. This function always returns a QSwgWidget.
|
||||
map or the icon map. This function always returns a QPixmap.
|
||||
"""
|
||||
if decoKey not in self.DECO_MAP:
|
||||
if decoKey in self._themeMap:
|
||||
imgPath = self._themeMap[decoKey]
|
||||
elif decoKey in self.IMAGE_MAP:
|
||||
imgPath = os.path.join(
|
||||
self.mainConf.assetPath, "images", self.IMAGE_MAP[decoKey]
|
||||
)
|
||||
else:
|
||||
logger.error("Decoration with name '%s' does not exist", decoKey)
|
||||
return QPixmap()
|
||||
|
||||
imgPath = os.path.join(
|
||||
self.mainConf.assetPath, "images", self.DECO_MAP[decoKey]
|
||||
)
|
||||
if not os.path.isfile(imgPath):
|
||||
logger.error("Decoration file '%s' not in assets folder", self.DECO_MAP[decoKey])
|
||||
logger.error("Asset '%s' not found", self.IMAGE_MAP[decoKey])
|
||||
return QPixmap()
|
||||
|
||||
theDeco = QPixmap(imgPath)
|
||||
@@ -639,9 +643,6 @@ class GuiIcons:
|
||||
iconName = "proj_scene"
|
||||
elif tLayout == nwItemLayout.NOTE:
|
||||
iconName = "proj_note"
|
||||
elif tType == nwItemType.TRASH:
|
||||
iconName = nwLabels.CLASS_ICON[tClass]
|
||||
|
||||
if iconName is None:
|
||||
return QIcon()
|
||||
|
||||
|
||||
@@ -0,0 +1,139 @@
|
||||
"""
|
||||
novelWriter – GUI Main Window Views ToolBar
|
||||
===========================================
|
||||
GUI class for the main window "Views" toolbar
|
||||
|
||||
File History:
|
||||
Created: 2022-05-10 [1.7b1]
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright 2018–2022, Veronica Berglyd Olsen
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from PyQt5.QtCore import Qt, QSize, pyqtSignal
|
||||
from PyQt5.QtWidgets import (
|
||||
QToolBar, QWidget, QSizePolicy, QAction, QMenu, QToolButton
|
||||
)
|
||||
|
||||
from novelwriter.enum import nwView
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class GuiViewsBar(QToolBar):
|
||||
|
||||
viewChangeRequested = pyqtSignal(nwView)
|
||||
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiViewsBar ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
|
||||
# Style
|
||||
iPx = self.mainConf.pxInt(22)
|
||||
mPx = self.mainConf.pxInt(60)
|
||||
|
||||
lblFont = self.mainTheme.guiFont
|
||||
lblFont.setPointSizeF(0.65*self.mainTheme.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.mainTheme.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.mainTheme.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.mainTheme.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.mainTheme.getIcon("view_build"))
|
||||
self.aBuild.triggered.connect(lambda: self.mainGui.showBuildProjectDialog())
|
||||
|
||||
self.aDetails = QAction(self.tr("Details"))
|
||||
self.aDetails.setFont(lblFont)
|
||||
self.aDetails.setToolTip(self.tr("Show project details"))
|
||||
self.aDetails.setIcon(self.mainTheme.getIcon("proj_details"))
|
||||
self.aDetails.triggered.connect(lambda: self.mainGui.showProjectDetailsDialog())
|
||||
|
||||
self.aStats = QAction(self.tr("Stats"))
|
||||
self.aStats.setFont(lblFont)
|
||||
self.aStats.setToolTip(self.tr("Show project statistics"))
|
||||
self.aStats.setIcon(self.mainTheme.getIcon("proj_stats"))
|
||||
self.aStats.triggered.connect(lambda: self.mainGui.showWritingStatsDialog())
|
||||
|
||||
# Settings Menu
|
||||
self.mSettings = QMenu()
|
||||
|
||||
self.aPrjSettings = QAction(self.tr("Project Settings"))
|
||||
self.aPrjSettings.triggered.connect(lambda: self.mainGui.showProjectSettingsDialog())
|
||||
self.mSettings.addAction(self.aPrjSettings)
|
||||
|
||||
self.aPreferences = QAction(self.tr("Preferences"))
|
||||
self.aPreferences.triggered.connect(lambda: self.mainGui.showPreferencesDialog())
|
||||
self.mSettings.addAction(self.aPreferences)
|
||||
|
||||
self.tbSettings = QToolButton(self)
|
||||
self.tbSettings.setFont(lblFont)
|
||||
self.tbSettings.setText(self.tr("Settings"))
|
||||
self.tbSettings.setIcon(self.mainTheme.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,33 +27,34 @@ import os
|
||||
import logging
|
||||
import novelwriter
|
||||
|
||||
from enum import Enum
|
||||
from time import time
|
||||
from datetime import datetime
|
||||
|
||||
from PyQt5.QtCore import Qt, QTimer, QSize, QThreadPool, pyqtSlot
|
||||
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
|
||||
from PyQt5.QtCore import Qt, QTimer, QThreadPool, pyqtSlot
|
||||
from PyQt5.QtGui import QIcon, QKeySequence, QCursor
|
||||
from PyQt5.QtWidgets import (
|
||||
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
|
||||
QMessageBox, QDialog, QTabWidget, QToolBar, QAction
|
||||
QMessageBox, QDialog, QStackedWidget
|
||||
)
|
||||
|
||||
from novelwriter.gui import (
|
||||
GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu,
|
||||
GuiMainStatus, GuiNovelTree, GuiOutline, GuiOutlineDetails, GuiProjectTree,
|
||||
GuiTheme
|
||||
GuiMainStatus, GuiNovelView, GuiOutlineView, GuiProjectView, GuiTheme,
|
||||
GuiViewsBar
|
||||
)
|
||||
from novelwriter.dialogs import (
|
||||
GuiAbout, GuiDocMerge, GuiDocSplit, GuiItemEditor, GuiPreferences,
|
||||
GuiProjectDetails, GuiProjectLoad, GuiProjectSettings, GuiUpdates,
|
||||
GuiWordList
|
||||
GuiAbout, GuiDocSplit, GuiPreferences, GuiProjectDetails, GuiProjectLoad,
|
||||
GuiProjectSettings, GuiUpdates, GuiWordList
|
||||
)
|
||||
from novelwriter.tools import GuiBuildNovel, GuiProjectWizard, GuiWritingStats
|
||||
from novelwriter.core import NWProject, NWIndex
|
||||
from novelwriter.tools import (
|
||||
GuiBuildNovel, GuiLipsum, GuiProjectWizard, GuiWritingStats
|
||||
)
|
||||
from novelwriter.core import NWProject
|
||||
from novelwriter.enum import (
|
||||
nwItemType, nwItemClass, nwAlert, nwWidget, nwState
|
||||
nwDocMode, nwItemType, nwItemClass, nwAlert, nwWidget, nwState, nwView
|
||||
)
|
||||
from novelwriter.common import getGuiItem, hexToInt
|
||||
from novelwriter.constants import nwLists
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
@@ -61,7 +62,7 @@ logger = logging.getLogger(__name__)
|
||||
class GuiMain(QMainWindow):
|
||||
|
||||
def __init__(self):
|
||||
QMainWindow.__init__(self)
|
||||
super().__init__()
|
||||
|
||||
logger.debug("Initialising GUI ...")
|
||||
self.setObjectName("GuiMain")
|
||||
@@ -83,9 +84,8 @@ class GuiMain(QMainWindow):
|
||||
# ============
|
||||
|
||||
# Core Classes and Settings
|
||||
self.theTheme = GuiTheme()
|
||||
self.mainTheme = GuiTheme()
|
||||
self.theProject = NWProject(self)
|
||||
self.theIndex = NWIndex(self.theProject)
|
||||
self.hasProject = False
|
||||
self.isFocusMode = False
|
||||
self.idleRefTime = time()
|
||||
@@ -101,126 +101,75 @@ class GuiMain(QMainWindow):
|
||||
|
||||
# Sizes
|
||||
mPx = self.mainConf.pxInt(4)
|
||||
fPx = self.theTheme.fontPixelSize
|
||||
fPt = self.theTheme.fontPointSize
|
||||
hWd = self.mainConf.pxInt(4)
|
||||
|
||||
# Main GUI Elements
|
||||
self.statusBar = GuiMainStatus(self)
|
||||
self.treeView = GuiProjectTree(self)
|
||||
self.novelView = GuiNovelTree(self)
|
||||
self.docEditor = GuiDocEditor(self)
|
||||
self.viewMeta = GuiDocViewDetails(self)
|
||||
self.docViewer = GuiDocViewer(self)
|
||||
self.treeMeta = GuiItemDetails(self)
|
||||
self.projView = GuiOutline(self)
|
||||
self.projMeta = GuiOutlineDetails(self)
|
||||
self.mainMenu = GuiMainMenu(self)
|
||||
self.statusBar = GuiMainStatus(self)
|
||||
self.projView = GuiProjectView(self)
|
||||
self.novelView = GuiNovelView(self)
|
||||
self.docEditor = GuiDocEditor(self)
|
||||
self.viewMeta = GuiDocViewDetails(self)
|
||||
self.docViewer = GuiDocViewer(self)
|
||||
self.itemDetails = GuiItemDetails(self)
|
||||
self.outlineView = GuiOutlineView(self)
|
||||
self.mainMenu = GuiMainMenu(self)
|
||||
self.viewsBar = GuiViewsBar(self)
|
||||
|
||||
# Connect Signals Between Main Elements
|
||||
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
|
||||
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
|
||||
self.docEditor.docCountsChanged.connect(self.treeMeta.doUpdateCounts)
|
||||
self.docEditor.docCountsChanged.connect(self.treeView.doUpdateCounts)
|
||||
|
||||
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
|
||||
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
|
||||
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
|
||||
self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
|
||||
|
||||
# Minor GUI Elements
|
||||
self.statusIcons = []
|
||||
self.importIcons = []
|
||||
|
||||
# Project Tree Tabs
|
||||
self.projTabs = QTabWidget()
|
||||
self.projTabs.setTabPosition(QTabWidget.South)
|
||||
self.projTabs.setStyleSheet("QTabWidget::pane {border: 0;};")
|
||||
self.projTabs.addTab(self.treeView, self.tr("Project"))
|
||||
self.projTabs.addTab(self.novelView, self.tr("Novel"))
|
||||
self.projTabs.currentChanged.connect(self._projTabsChanged)
|
||||
|
||||
tabFont = self.projTabs.tabBar().font()
|
||||
tabFont.setPointSizeF(0.9*fPt)
|
||||
self.projTabs.tabBar().setFont(tabFont)
|
||||
|
||||
# Project Tree Action Buttons
|
||||
btnSize = int(round(0.7*fPx))
|
||||
self.treeButtons = QToolBar()
|
||||
self.treeButtons.setToolButtonStyle(Qt.ToolButtonIconOnly)
|
||||
self.treeButtons.setIconSize(QSize(btnSize, btnSize))
|
||||
self.treeButtons.setContentsMargins(0, 0, 0, 0)
|
||||
self.treeButtons.setStyleSheet("QToolBar {padding: 0;}")
|
||||
self.projTabs.setCornerWidget(self.treeButtons, Qt.BottomRightCorner)
|
||||
|
||||
self.projDetailsBtn = QAction(self.tr("Project Details"))
|
||||
self.projDetailsBtn.setIcon(self.theTheme.getIcon("status_lines"))
|
||||
self.projDetailsBtn.triggered.connect(lambda: self.showProjectDetailsDialog())
|
||||
self.treeButtons.addAction(self.projDetailsBtn)
|
||||
|
||||
self.projStatsBtn = QAction(self.tr("Writing Statistics"))
|
||||
self.projStatsBtn.setIcon(self.theTheme.getIcon("status_stats"))
|
||||
self.projStatsBtn.triggered.connect(lambda: self.showWritingStatsDialog())
|
||||
self.treeButtons.addAction(self.projStatsBtn)
|
||||
|
||||
self.projSettingsBtn = QAction(self.tr("Project Settings"))
|
||||
self.projSettingsBtn.setIcon(self.theTheme.getIcon("settings"))
|
||||
self.projSettingsBtn.triggered.connect(lambda: self.showProjectSettingsDialog())
|
||||
self.treeButtons.addAction(self.projSettingsBtn)
|
||||
# Project Tree Stack
|
||||
self.projStack = QStackedWidget()
|
||||
self.projStack.addWidget(self.projView)
|
||||
self.projStack.addWidget(self.novelView)
|
||||
self.projStack.currentChanged.connect(self._projStackChanged)
|
||||
|
||||
# Project Tree View
|
||||
self.treePane = QWidget()
|
||||
self.treeBox = QVBoxLayout()
|
||||
self.treeBox.setContentsMargins(0, 0, 0, 0)
|
||||
self.treeBox.setSpacing(mPx)
|
||||
self.treeBox.addWidget(self.projTabs)
|
||||
self.treeBox.addWidget(self.treeMeta)
|
||||
self.treeBox.addWidget(self.projStack)
|
||||
self.treeBox.addWidget(self.itemDetails)
|
||||
self.treePane.setLayout(self.treeBox)
|
||||
|
||||
# Splitter : Document Viewer / Document Meta
|
||||
self.splitView = QSplitter(Qt.Vertical)
|
||||
self.splitView.addWidget(self.docViewer)
|
||||
self.splitView.addWidget(self.viewMeta)
|
||||
self.splitView.setHandleWidth(hWd)
|
||||
self.splitView.setSizes(self.mainConf.getViewPanePos())
|
||||
|
||||
# Splitter : Document Editor / Document Viewer
|
||||
self.splitDocs = QSplitter(Qt.Horizontal)
|
||||
self.splitDocs.addWidget(self.docEditor)
|
||||
self.splitDocs.addWidget(self.splitView)
|
||||
|
||||
# Splitter : Project Outlie / Outline Details
|
||||
self.splitOutline = QSplitter(Qt.Vertical)
|
||||
self.splitOutline.addWidget(self.projView)
|
||||
self.splitOutline.addWidget(self.projMeta)
|
||||
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos())
|
||||
|
||||
# Main Tabs : Editor / Outline
|
||||
self.mainTabs = QTabWidget()
|
||||
self.mainTabs.setTabPosition(QTabWidget.East)
|
||||
self.mainTabs.setStyleSheet("QTabWidget::pane {border: 0;}")
|
||||
self.mainTabs.addTab(self.splitDocs, self.tr("Editor"))
|
||||
self.mainTabs.addTab(self.splitOutline, self.tr("Outline"))
|
||||
self.mainTabs.currentChanged.connect(self._mainTabChanged)
|
||||
self.splitDocs.setHandleWidth(hWd)
|
||||
|
||||
# Splitter : Project Tree / Main Tabs
|
||||
self.splitMain = QSplitter(Qt.Horizontal)
|
||||
self.splitMain.setContentsMargins(mPx, mPx, mPx, mPx)
|
||||
self.splitMain.setContentsMargins(0, 0, 0, 0)
|
||||
self.splitMain.addWidget(self.treePane)
|
||||
self.splitMain.addWidget(self.mainTabs)
|
||||
self.splitMain.addWidget(self.splitDocs)
|
||||
self.splitMain.setHandleWidth(hWd)
|
||||
self.splitMain.setSizes(self.mainConf.getMainPanePos())
|
||||
|
||||
# Main Stack : Editor / Outline
|
||||
self.mainStack = QStackedWidget()
|
||||
self.mainStack.addWidget(self.splitMain)
|
||||
self.mainStack.addWidget(self.outlineView)
|
||||
self.mainStack.currentChanged.connect(self._mainStackChanged)
|
||||
|
||||
# Indices of Splitter Widgets
|
||||
self.idxTree = self.splitMain.indexOf(self.treePane)
|
||||
self.idxMain = self.splitMain.indexOf(self.mainTabs)
|
||||
self.idxMain = self.splitMain.indexOf(self.splitDocs)
|
||||
self.idxEditor = self.splitDocs.indexOf(self.docEditor)
|
||||
self.idxViewer = self.splitDocs.indexOf(self.splitView)
|
||||
self.idxViewDoc = self.splitView.indexOf(self.docViewer)
|
||||
self.idxViewMeta = self.splitView.indexOf(self.viewMeta)
|
||||
|
||||
# Indices of Tab Widgets
|
||||
self.idxTabEdit = self.mainTabs.indexOf(self.splitDocs)
|
||||
self.idxTabProj = self.mainTabs.indexOf(self.splitOutline)
|
||||
self.idxTreeView = self.projTabs.indexOf(self.treeView)
|
||||
self.idxNovelView = self.projTabs.indexOf(self.novelView)
|
||||
self.idxEditorView = self.mainStack.indexOf(self.splitMain)
|
||||
self.idxOutlineView = self.mainStack.indexOf(self.outlineView)
|
||||
self.idxProjView = self.projStack.indexOf(self.projView)
|
||||
self.idxNovelView = self.projStack.indexOf(self.novelView)
|
||||
|
||||
# Splitter Behaviour
|
||||
self.splitMain.setCollapsible(self.idxTree, False)
|
||||
@@ -239,8 +188,37 @@ class GuiMain(QMainWindow):
|
||||
|
||||
# Set Main Window Elements
|
||||
self.setMenuBar(self.mainMenu)
|
||||
self.setCentralWidget(self.splitMain)
|
||||
self.setCentralWidget(self.mainStack)
|
||||
self.setStatusBar(self.statusBar)
|
||||
self.addToolBar(Qt.LeftToolBarArea, self.viewsBar)
|
||||
self.setContextMenuPolicy(Qt.NoContextMenu) # Issue #1147
|
||||
|
||||
# Connect Signals
|
||||
# ===============
|
||||
|
||||
self.viewsBar.viewChangeRequested.connect(self._changeView)
|
||||
|
||||
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
||||
self.projView.openDocumentRequest.connect(self._openDocument)
|
||||
self.projView.wordCountsChanged.connect(self._updateStatusWordCount)
|
||||
self.projView.treeItemChanged.connect(self.docEditor.updateDocInfo)
|
||||
self.projView.treeItemChanged.connect(self.docViewer.updateDocInfo)
|
||||
self.projView.treeItemChanged.connect(self.itemDetails.updateViewBox)
|
||||
self.projView.rootFolderChanged.connect(self.outlineView.updateRootItem)
|
||||
self.projView.rootFolderChanged.connect(self.novelView.updateRootItem)
|
||||
|
||||
self.novelView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
|
||||
self.novelView.openDocumentRequest.connect(self._openDocument)
|
||||
|
||||
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
|
||||
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
|
||||
self.docEditor.docCountsChanged.connect(self.itemDetails.updateCounts)
|
||||
self.docEditor.docCountsChanged.connect(self.projView.updateCounts)
|
||||
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
|
||||
|
||||
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
|
||||
|
||||
self.outlineView.loadDocumentTagRequest.connect(self._followTag)
|
||||
|
||||
# Finalise Initialisation
|
||||
# =======================
|
||||
@@ -317,15 +295,15 @@ class GuiMain(QMainWindow):
|
||||
"""Wrapper function to clear all sub-elements of the main GUI.
|
||||
"""
|
||||
# Project Area
|
||||
self.treeView.clearTree()
|
||||
self.novelView.clearTree()
|
||||
self.treeMeta.clearDetails()
|
||||
self.projView.clearProject()
|
||||
self.novelView.clearProject()
|
||||
self.itemDetails.clearDetails()
|
||||
|
||||
# Work Area
|
||||
self.docEditor.clearEditor()
|
||||
self.docEditor.setDictionaries()
|
||||
self.closeDocViewer()
|
||||
self.projMeta.clearDetails()
|
||||
self.outlineView.clearProject()
|
||||
|
||||
# General
|
||||
self.statusBar.clearStatus()
|
||||
@@ -392,6 +370,8 @@ class GuiMain(QMainWindow):
|
||||
self.saveProject()
|
||||
|
||||
self.docEditor.setDictionaries()
|
||||
self.novelView.openProjectTasks()
|
||||
self.outlineView.openProjectTasks()
|
||||
self.rebuildIndex(beQuiet=True)
|
||||
|
||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||
@@ -450,16 +430,17 @@ class GuiMain(QMainWindow):
|
||||
if saveOK:
|
||||
self.closeDocument()
|
||||
self.docViewer.clearNavHistory()
|
||||
self.projView.closeOutline()
|
||||
self.outlineView.closeProjectTasks()
|
||||
self.novelView.closeProjectTasks()
|
||||
|
||||
self.theProject.closeProject(self.idleTime)
|
||||
self.idleRefTime = time()
|
||||
self.idleTime = 0.0
|
||||
|
||||
self.theIndex.clearIndex()
|
||||
self.theProject.index.clearIndex()
|
||||
self.clearGUI()
|
||||
self.hasProject = False
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.PROJECT)
|
||||
|
||||
return saveOK
|
||||
|
||||
@@ -475,7 +456,7 @@ class GuiMain(QMainWindow):
|
||||
return False
|
||||
|
||||
# Switch main tab to editor view
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.PROJECT)
|
||||
|
||||
# Try to open the project
|
||||
if not self.theProject.openProject(projFile):
|
||||
@@ -533,15 +514,16 @@ class GuiMain(QMainWindow):
|
||||
self.idleTime = 0.0
|
||||
|
||||
# Load the tag index
|
||||
self.theIndex.loadIndex()
|
||||
self.theProject.index.loadIndex()
|
||||
|
||||
# Update GUI
|
||||
self._updateWindowTitle(self.theProject.projName)
|
||||
self.rebuildTrees()
|
||||
self.docEditor.setDictionaries()
|
||||
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
|
||||
self.mainMenu.setAutoOutline(self.theProject.autoOutline)
|
||||
self.statusBar.setRefTime(self.theProject.projOpened)
|
||||
self.novelView.openProjectTasks()
|
||||
self.outlineView.openProjectTasks()
|
||||
self._updateStatusWordCount()
|
||||
|
||||
# Restore previously open documents, if any
|
||||
@@ -552,10 +534,10 @@ class GuiMain(QMainWindow):
|
||||
self.viewDocument(self.theProject.lastViewed)
|
||||
|
||||
# Check if we need to rebuild the index
|
||||
if self.theIndex.indexBroken:
|
||||
if self.theProject.index.indexBroken:
|
||||
self.makeAlert(self.tr(
|
||||
"The project index is outdated or broken. Rebuilding index."
|
||||
), nwAlert.WARN)
|
||||
), nwAlert.INFO)
|
||||
self.rebuildIndex()
|
||||
|
||||
# Make sure the changed status is set to false on things opened
|
||||
@@ -574,9 +556,9 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
self.treeView.saveTreeOrder()
|
||||
self.projView.saveProjectTree()
|
||||
if self.theProject.saveProject(autoSave=autoSave):
|
||||
self.theIndex.saveIndex()
|
||||
self.theProject.index.saveIndex()
|
||||
|
||||
return True
|
||||
|
||||
@@ -584,7 +566,7 @@ class GuiMain(QMainWindow):
|
||||
# Document Actions
|
||||
##
|
||||
|
||||
def closeDocument(self):
|
||||
def closeDocument(self, beforeOpen=False):
|
||||
"""Close the document and clear the editor and title field.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
@@ -599,6 +581,8 @@ class GuiMain(QMainWindow):
|
||||
if self.docEditor.docChanged():
|
||||
self.saveDocument()
|
||||
self.docEditor.clearEditor()
|
||||
if not beforeOpen:
|
||||
self.novelView.setActiveHandle(None)
|
||||
|
||||
return True
|
||||
|
||||
@@ -609,17 +593,18 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
if not self.theProject.projTree.checkType(tHandle, nwItemType.FILE):
|
||||
if not self.theProject.tree.checkType(tHandle, nwItemType.FILE):
|
||||
logger.debug("Requested item '%s' is not a document", tHandle)
|
||||
return False
|
||||
|
||||
self.closeDocument()
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self.closeDocument(beforeOpen=True)
|
||||
self._changeView(nwView.EDITOR)
|
||||
if self.docEditor.loadText(tHandle, tLine):
|
||||
if changeFocus:
|
||||
self.docEditor.setFocus()
|
||||
self.theProject.setLastEdited(tHandle)
|
||||
self.treeView.setSelectedHandle(tHandle, doScroll=doScroll)
|
||||
self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
|
||||
self.novelView.setActiveHandle(tHandle)
|
||||
else:
|
||||
return False
|
||||
|
||||
@@ -633,12 +618,11 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
self.treeView.flushTreeOrder()
|
||||
nHandle = None # The next handle after tHandle
|
||||
fHandle = None # The first file handle we encounter
|
||||
foundIt = False # We've found tHandle, pick the next we see
|
||||
for tItem in self.theProject.projTree:
|
||||
if not self.theProject.projTree.checkType(tItem.itemHandle, nwItemType.FILE):
|
||||
for tItem in self.theProject.tree:
|
||||
if not self.theProject.tree.checkType(tItem.itemHandle, nwItemType.FILE):
|
||||
continue
|
||||
if fHandle is None:
|
||||
fHandle = tItem.itemHandle
|
||||
@@ -686,7 +670,7 @@ class GuiMain(QMainWindow):
|
||||
self.saveDocument()
|
||||
else:
|
||||
logger.verbose("Trying selected document")
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
tHandle = self.projView.getSelectedHandle()
|
||||
|
||||
if tHandle is None:
|
||||
logger.verbose("Trying last viewed document")
|
||||
@@ -697,7 +681,7 @@ class GuiMain(QMainWindow):
|
||||
return False
|
||||
|
||||
# Make sure main tab is in Editor view
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.EDITOR)
|
||||
|
||||
logger.debug("Viewing document with handle '%s'", tHandle)
|
||||
if self.docViewer.loadText(tHandle):
|
||||
@@ -770,18 +754,6 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return True
|
||||
|
||||
def mergeDocuments(self):
|
||||
"""Merge multiple documents to one single new document.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
dlgMerge = GuiDocMerge(self)
|
||||
dlgMerge.exec_()
|
||||
|
||||
return True
|
||||
|
||||
def splitDocument(self):
|
||||
"""Split a single document into multiple documents.
|
||||
"""
|
||||
@@ -823,12 +795,12 @@ class GuiMain(QMainWindow):
|
||||
|
||||
tHandle = None
|
||||
tLine = None
|
||||
if self.treeView.hasFocus():
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
elif self.novelView.hasFocus():
|
||||
if self.projView.treeHasFocus():
|
||||
tHandle = self.projView.getSelectedHandle()
|
||||
elif self.novelView.treeHasFocus():
|
||||
tHandle, tLine = self.novelView.getSelectedHandle()
|
||||
elif self.projView.hasFocus():
|
||||
tHandle, tLine = self.projView.getSelectedHandle()
|
||||
elif self.outlineView.treeHasFocus():
|
||||
tHandle, tLine = self.outlineView.getSelectedHandle()
|
||||
else:
|
||||
logger.warning("No item selected")
|
||||
return False
|
||||
@@ -838,7 +810,7 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return True
|
||||
|
||||
def editItem(self, tHandle=None):
|
||||
def editItemLabel(self, tHandle=None):
|
||||
"""Open the edit item dialog.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
@@ -849,42 +821,23 @@ class GuiMain(QMainWindow):
|
||||
if self.docEditor.anyFocus() or self.isFocusMode:
|
||||
tHandle = self.docEditor.docHandle()
|
||||
else:
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
tHandle = self.projView.getSelectedHandle()
|
||||
if tHandle:
|
||||
return self.projView.renameTreeItem(tHandle)
|
||||
|
||||
if tHandle is None:
|
||||
logger.warning("No item selected")
|
||||
return False
|
||||
|
||||
tItem = self.theProject.projTree[tHandle]
|
||||
if tItem is None:
|
||||
return False
|
||||
if tItem.itemType not in nwLists.REG_TYPES:
|
||||
return False
|
||||
|
||||
logger.verbose("Requesting change to item '%s'", tHandle)
|
||||
dlgProj = GuiItemEditor(self, tHandle)
|
||||
dlgProj.exec_()
|
||||
if dlgProj.result() == QDialog.Accepted:
|
||||
self.treeView.setTreeItemValues(tHandle)
|
||||
self.treeMeta.updateViewBox(tHandle)
|
||||
self.docEditor.updateDocInfo(tHandle)
|
||||
self.docViewer.updateDocInfo(tHandle)
|
||||
|
||||
return True
|
||||
return False
|
||||
|
||||
def rebuildTrees(self):
|
||||
"""Rebuild the project tree.
|
||||
"""
|
||||
self._makeStatusIcons()
|
||||
self._makeImportIcons()
|
||||
self.treeView.buildTree()
|
||||
self.novelView.refreshTree()
|
||||
self.projView.populateTree()
|
||||
# self.novelView.refreshTree()
|
||||
return
|
||||
|
||||
def requestNovelTreeRefresh(self):
|
||||
"""Update the novel tree, but only if it is visible.
|
||||
"""
|
||||
if self.projTabs.currentIndex() == self.idxNovelView and self.hasProject:
|
||||
if self.projStack.currentIndex() == self.idxNovelView and self.hasProject:
|
||||
self.novelView.refreshTree()
|
||||
return True
|
||||
return False
|
||||
@@ -900,27 +853,18 @@ class GuiMain(QMainWindow):
|
||||
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
|
||||
tStart = time()
|
||||
|
||||
self.treeView.saveTreeOrder()
|
||||
self.theIndex.clearIndex()
|
||||
self.projView.saveProjectTree()
|
||||
self.theProject.index.clearIndex()
|
||||
|
||||
for tItem in self.theProject.projTree:
|
||||
for tItem in self.theProject.tree:
|
||||
if tItem is None: # pragma: no cover
|
||||
continue # This is a bug trap
|
||||
|
||||
if tItem is not None:
|
||||
self.setStatus(self.tr("Indexing: '{0}'").format(tItem.itemName))
|
||||
else:
|
||||
self.setStatus(self.tr("Indexing: '{0}'").format(self.tr("Unknown item")))
|
||||
|
||||
if tItem is not None and tItem.itemType == nwItemType.FILE:
|
||||
logger.verbose("Scanning '%s'", tItem.itemName)
|
||||
self.theIndex.reIndexHandle(tItem.itemHandle)
|
||||
|
||||
# Get Word Counts
|
||||
cC, wC, pC = self.theIndex.getCounts(tItem.itemHandle)
|
||||
tItem.setCharCount(cC)
|
||||
tItem.setWordCount(wC)
|
||||
tItem.setParaCount(pC)
|
||||
self.treeView.propagateCount(tItem.itemHandle, wC)
|
||||
self.treeView.setTreeItemValues(tItem.itemHandle)
|
||||
logger.verbose("Indexing '%s'", tItem.itemName)
|
||||
if self.theProject.index.reIndexHandle(tItem.itemHandle):
|
||||
# Update Word Counts
|
||||
self.projView.propagateCount(tItem.itemHandle, tItem.wordCount, countChildren=True)
|
||||
self.projView.setTreeItemValues(tItem.itemHandle)
|
||||
|
||||
tEnd = time()
|
||||
self.setStatus(
|
||||
@@ -937,19 +881,6 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return True
|
||||
|
||||
def rebuildOutline(self):
|
||||
"""Force a rebuild of the Outline view.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
logger.verbose("Forcing a rebuild of the Project Outline")
|
||||
self.mainTabs.setCurrentWidget(self.splitOutline)
|
||||
self.projView.refreshTree(overRide=True)
|
||||
|
||||
return True
|
||||
|
||||
##
|
||||
# Main Dialogs
|
||||
##
|
||||
@@ -990,14 +921,13 @@ class GuiMain(QMainWindow):
|
||||
if dlgConf.result() == QDialog.Accepted:
|
||||
logger.debug("Applying new preferences")
|
||||
self.initMain()
|
||||
self.theTheme.updateTheme()
|
||||
self.mainTheme.updateTheme()
|
||||
self.saveDocument()
|
||||
self.docEditor.initEditor()
|
||||
self.docViewer.initViewer()
|
||||
self.treeView.initTree()
|
||||
self.novelView.initTree()
|
||||
self.projView.initOutline()
|
||||
self.projMeta.initDetails()
|
||||
self.projView.initSettings()
|
||||
self.novelView.initSettings()
|
||||
self.outlineView.initSettings()
|
||||
self._updateStatusWordCount()
|
||||
|
||||
return
|
||||
@@ -1014,7 +944,9 @@ class GuiMain(QMainWindow):
|
||||
|
||||
if dlgProj.result() == QDialog.Accepted:
|
||||
logger.debug("Applying new project settings")
|
||||
self.docEditor.setDictionaries()
|
||||
if dlgProj.spellChanged:
|
||||
self.docEditor.setDictionaries()
|
||||
self.itemDetails.refreshDetails()
|
||||
self._updateWindowTitle(self.theProject.projName)
|
||||
|
||||
return True
|
||||
@@ -1026,8 +958,6 @@ class GuiMain(QMainWindow):
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
self.treeView.flushTreeOrder()
|
||||
|
||||
dlgDetails = getGuiItem("GuiProjectDetails")
|
||||
if dlgDetails is None:
|
||||
dlgDetails = GuiProjectDetails(self)
|
||||
@@ -1058,6 +988,24 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return True
|
||||
|
||||
def showLoremIpsumDialog(self):
|
||||
"""Open the insert lorem ipsum text dialog.
|
||||
"""
|
||||
if not self.hasProject:
|
||||
logger.error("No project open")
|
||||
return False
|
||||
|
||||
dlgLipsum = getGuiItem("GuiLipsum")
|
||||
if dlgLipsum is None:
|
||||
dlgLipsum = GuiLipsum(self)
|
||||
|
||||
dlgLipsum.setModal(False)
|
||||
dlgLipsum.show()
|
||||
dlgLipsum.raise_()
|
||||
qApp.processEvents()
|
||||
|
||||
return True
|
||||
|
||||
def showProjectWordListDialog(self):
|
||||
"""Open the project word list dialog.
|
||||
"""
|
||||
@@ -1214,13 +1162,11 @@ class GuiMain(QMainWindow):
|
||||
if not self.isFocusMode:
|
||||
self.mainConf.setMainPanePos(self.splitMain.sizes())
|
||||
self.mainConf.setDocPanePos(self.splitDocs.sizes())
|
||||
self.mainConf.setOutlinePanePos(self.splitOutline.sizes())
|
||||
self.mainConf.setOutlinePanePos(self.outlineView.splitSizes())
|
||||
if self.viewMeta.isVisible():
|
||||
self.mainConf.setViewPanePos(self.splitView.sizes())
|
||||
|
||||
self.mainConf.setShowRefPanel(self.viewMeta.isVisible())
|
||||
self.mainConf.setTreeColWidths(self.treeView.getColumnSizes())
|
||||
self.mainConf.setNovelColWidths(self.novelView.getColumnSizes())
|
||||
if not self.mainConf.isFullScreen:
|
||||
self.mainConf.setWinSize(self.width(), self.height())
|
||||
|
||||
@@ -1238,20 +1184,20 @@ class GuiMain(QMainWindow):
|
||||
"""Switch focus between main GUI views.
|
||||
"""
|
||||
if paneNo == nwWidget.TREE:
|
||||
tabIdx = self.projTabs.currentIndex()
|
||||
if tabIdx == self.idxTreeView:
|
||||
self.treeView.setFocus()
|
||||
tabIdx = self.projStack.currentIndex()
|
||||
if tabIdx == self.idxProjView:
|
||||
self.projView.setFocus()
|
||||
elif tabIdx == self.idxNovelView:
|
||||
self.novelView.setFocus()
|
||||
self.novelView.setTreeFocus()
|
||||
elif paneNo == nwWidget.EDITOR:
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.EDITOR)
|
||||
self.docEditor.setFocus()
|
||||
elif paneNo == nwWidget.VIEWER:
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self._changeView(nwView.EDITOR)
|
||||
self.docViewer.setFocus()
|
||||
elif paneNo == nwWidget.OUTLINE:
|
||||
self.mainTabs.setCurrentWidget(self.splitOutline)
|
||||
self.projView.setFocus()
|
||||
self._changeView(nwView.OUTLINE)
|
||||
self.outlineView.setTreeFocus()
|
||||
return
|
||||
|
||||
def closeDocEditor(self):
|
||||
@@ -1283,7 +1229,6 @@ class GuiMain(QMainWindow):
|
||||
self.mainMenu.setFocusMode(self.isFocusMode)
|
||||
if self.isFocusMode:
|
||||
logger.debug("Activating Focus Mode")
|
||||
self.mainTabs.setCurrentWidget(self.splitDocs)
|
||||
self.switchFocus(nwWidget.EDITOR)
|
||||
else:
|
||||
logger.debug("Deactivating Focus Mode")
|
||||
@@ -1292,7 +1237,7 @@ class GuiMain(QMainWindow):
|
||||
self.treePane.setVisible(isVisible)
|
||||
self.statusBar.setVisible(isVisible)
|
||||
self.mainMenu.setVisible(isVisible)
|
||||
self.mainTabs.tabBar().setVisible(isVisible)
|
||||
self.viewsBar.setVisible(isVisible)
|
||||
|
||||
hideDocFooter = self.isFocusMode and self.mainConf.hideFocusFooter
|
||||
self.docEditor.docFooter.setVisible(not hideDocFooter)
|
||||
@@ -1420,7 +1365,7 @@ class GuiMain(QMainWindow):
|
||||
return True
|
||||
|
||||
def _updateWindowTitle(self, projName=None):
|
||||
"""Set the window title and add the project's working title.
|
||||
"""Set the window title and add the project's name.
|
||||
"""
|
||||
winTitle = self.mainConf.appName
|
||||
if projName is not None:
|
||||
@@ -1450,29 +1395,6 @@ class GuiMain(QMainWindow):
|
||||
self.saveDocument()
|
||||
return
|
||||
|
||||
def _makeStatusIcons(self):
|
||||
"""Generate all the item status icons based on project settings.
|
||||
"""
|
||||
self.statusIcons = {}
|
||||
iPx = self.mainConf.pxInt(32)
|
||||
for sLabel, sCol, _ in self.theProject.statusItems:
|
||||
theIcon = QPixmap(iPx, iPx)
|
||||
theIcon.fill(QColor(*sCol))
|
||||
self.statusIcons[sLabel] = QIcon(theIcon)
|
||||
return
|
||||
|
||||
def _makeImportIcons(self):
|
||||
"""Generate all the item importance icons based on project
|
||||
settings.
|
||||
"""
|
||||
self.importIcons = {}
|
||||
iPx = self.mainConf.pxInt(32)
|
||||
for sLabel, sCol, _ in self.theProject.importItems:
|
||||
theIcon = QPixmap(iPx, iPx)
|
||||
theIcon.fill(QColor(*sCol))
|
||||
self.importIcons[sLabel] = QIcon(theIcon)
|
||||
return
|
||||
|
||||
def _assembleProjectWizardData(self, newProj):
|
||||
"""Extract the user choices from the New Project Wizard and
|
||||
store them in a dictionary.
|
||||
@@ -1486,9 +1408,9 @@ class GuiMain(QMainWindow):
|
||||
"popMinimal": newProj.field("popMinimal"),
|
||||
"popCustom": newProj.field("popCustom"),
|
||||
"addRoots": [],
|
||||
"addNotes": False,
|
||||
"numChapters": 0,
|
||||
"numScenes": 0,
|
||||
"chFolders": False,
|
||||
}
|
||||
if newProj.field("popCustom"):
|
||||
addRoots = []
|
||||
@@ -1498,19 +1420,30 @@ class GuiMain(QMainWindow):
|
||||
addRoots.append(nwItemClass.CHARACTER)
|
||||
if newProj.field("addWorld"):
|
||||
addRoots.append(nwItemClass.WORLD)
|
||||
if newProj.field("addTime"):
|
||||
addRoots.append(nwItemClass.TIMELINE)
|
||||
if newProj.field("addObject"):
|
||||
addRoots.append(nwItemClass.OBJECT)
|
||||
if newProj.field("addEntity"):
|
||||
addRoots.append(nwItemClass.ENTITY)
|
||||
projData["addRoots"] = addRoots
|
||||
projData["addNotes"] = newProj.field("addNotes")
|
||||
projData["numChapters"] = newProj.field("numChapters")
|
||||
projData["numScenes"] = newProj.field("numScenes")
|
||||
projData["chFolders"] = newProj.field("chFolders")
|
||||
|
||||
return projData
|
||||
|
||||
def _getTagSource(self, tTag):
|
||||
"""A wrapper function for the index lookup of a tag that will
|
||||
display an alert if the tag cannot be found.
|
||||
"""
|
||||
tHandle, sTitle = self.theProject.index.getTagSource(tTag)
|
||||
if tHandle is None:
|
||||
self.makeAlert(self.tr(
|
||||
"Could not find the reference for tag '{0}'. It either doesn't "
|
||||
"exist, or the index is out of date. The index can be updated "
|
||||
"from the Tools menu, or by pressing {1}."
|
||||
).format(
|
||||
tTag, "F9"
|
||||
), nwAlert.ERROR)
|
||||
return None, None
|
||||
|
||||
return tHandle, sTitle
|
||||
|
||||
##
|
||||
# Events
|
||||
##
|
||||
@@ -1526,9 +1459,53 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
##
|
||||
# Slots
|
||||
# Private Slots
|
||||
##
|
||||
|
||||
@pyqtSlot(str, Enum)
|
||||
def _followTag(self, tTag, tMode):
|
||||
"""Follow a tag after user interaction with a link.
|
||||
"""
|
||||
tHandle, sTitle = self._getTagSource(tTag)
|
||||
if tHandle is not None:
|
||||
if tMode == nwDocMode.EDIT:
|
||||
self.openDocument(tHandle)
|
||||
elif tMode == nwDocMode.VIEW:
|
||||
self.viewDocument(tHandle=tHandle, tAnchor=f"#{sTitle}")
|
||||
return
|
||||
|
||||
@pyqtSlot(str, Enum, int, str)
|
||||
def _openDocument(self, tHandle, tMode, tLine, tAnchor):
|
||||
"""Handle an open document request from one of the tree views.
|
||||
"""
|
||||
if tHandle is not None:
|
||||
if tMode == nwDocMode.EDIT:
|
||||
self.openDocument(tHandle, tLine=tLine, changeFocus=False)
|
||||
elif tMode == nwDocMode.VIEW:
|
||||
self.viewDocument(tHandle=tHandle, tAnchor=(tAnchor or None))
|
||||
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.projView)
|
||||
|
||||
elif view == nwView.NOVEL:
|
||||
self.mainStack.setCurrentWidget(self.splitMain)
|
||||
self.projStack.setCurrentWidget(self.novelView)
|
||||
|
||||
elif view == nwView.OUTLINE:
|
||||
self.mainStack.setCurrentWidget(self.outlineView)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _timeTick(self):
|
||||
"""Triggered on every tick of the main timer.
|
||||
@@ -1571,39 +1548,6 @@ class GuiMain(QMainWindow):
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _treeSingleClick(self):
|
||||
"""Single click on a project tree item just updates the details
|
||||
panel below the tree.
|
||||
"""
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
if tHandle is not None:
|
||||
self.treeMeta.updateViewBox(tHandle)
|
||||
return
|
||||
|
||||
@pyqtSlot("QTreeWidgetItem*", int)
|
||||
def _treeDoubleClick(self, tItem, colNo):
|
||||
"""The user double-clicked an item in the tree. If it is a file,
|
||||
we open it. Otherwise, we do nothing.
|
||||
"""
|
||||
tHandle = self.treeView.getSelectedHandle()
|
||||
if tHandle is not None:
|
||||
self.openDocument(tHandle, changeFocus=False, doScroll=False)
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _treeNovelItemChanged(self):
|
||||
"""Triggered when there is a change to a novel item in the
|
||||
project tree.
|
||||
"""
|
||||
if self.mainTabs.currentIndex() == self.idxTabProj:
|
||||
logger.verbose("Novel tree changed while Outline tab active")
|
||||
if self.hasProject:
|
||||
self.treeView.flushTreeOrder()
|
||||
self.projView.refreshTree(novelChanged=True)
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot()
|
||||
def _keyPressReturn(self):
|
||||
"""Forward the return/enter keypress to the function that opens
|
||||
@@ -1625,35 +1569,35 @@ class GuiMain(QMainWindow):
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _mainTabChanged(self, tabIndex):
|
||||
def _mainStackChanged(self, stIndex):
|
||||
"""Activated when the main window tab is changed.
|
||||
"""
|
||||
if tabIndex == self.idxTabEdit:
|
||||
logger.verbose("Editor tab activated")
|
||||
elif tabIndex == self.idxTabProj:
|
||||
logger.verbose("Project outline tab activated")
|
||||
if stIndex == self.idxEditorView:
|
||||
logger.verbose("Editor View activated")
|
||||
elif stIndex == self.idxOutlineView:
|
||||
logger.verbose("Outline View activated")
|
||||
if self.hasProject:
|
||||
self.projView.refreshTree()
|
||||
self.outlineView.refreshTree()
|
||||
|
||||
return
|
||||
|
||||
@pyqtSlot(int)
|
||||
def _projTabsChanged(self, tabIndex):
|
||||
def _projStackChanged(self, stIndex):
|
||||
"""Activated when the project view tab is changed.
|
||||
"""
|
||||
sHandle = None
|
||||
|
||||
if tabIndex == self.idxTreeView:
|
||||
logger.verbose("Project tree tab activated")
|
||||
sHandle = self.treeView.getSelectedHandle()
|
||||
if stIndex == self.idxProjView:
|
||||
logger.verbose("Project Tree View activated")
|
||||
sHandle = self.projView.getSelectedHandle()
|
||||
|
||||
elif tabIndex == self.idxNovelView:
|
||||
logger.verbose("Novel tree tab activated")
|
||||
elif stIndex == self.idxNovelView:
|
||||
logger.verbose("Novel Tree View activated")
|
||||
if self.hasProject:
|
||||
self.novelView.refreshTree()
|
||||
sHandle, _ = self.novelView.getSelectedHandle()
|
||||
|
||||
self.treeMeta.updateViewBox(sHandle)
|
||||
self.itemDetails.updateViewBox(sHandle)
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -20,11 +20,13 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
|
||||
from novelwriter.tools.build import GuiBuildNovel
|
||||
from novelwriter.tools.lipsum import GuiLipsum
|
||||
from novelwriter.tools.projwizard import GuiProjectWizard
|
||||
from novelwriter.tools.writingstats import GuiWritingStats
|
||||
|
||||
__all__ = [
|
||||
"GuiBuildNovel",
|
||||
"GuiLipsum",
|
||||
"GuiProjectWizard",
|
||||
"GuiWritingStats",
|
||||
]
|
||||
|
||||
@@ -65,17 +65,16 @@ class GuiBuildNovel(QDialog):
|
||||
FMT_JSON_H = 8 # HTML5 wrapped in JSON
|
||||
FMT_JSON_M = 9 # nW Markdown wrapped in JSON
|
||||
|
||||
def __init__(self, theParent):
|
||||
QDialog.__init__(self, theParent)
|
||||
def __init__(self, mainGui):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiBuildNovel ...")
|
||||
self.setObjectName("GuiBuildNovel")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.theProject = theParent.theProject
|
||||
self.optState = theParent.theProject.optState
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.theProject = mainGui.theProject
|
||||
|
||||
self.htmlText = [] # List of html documents
|
||||
self.htmlStyle = [] # List of html styles
|
||||
@@ -86,14 +85,15 @@ class GuiBuildNovel(QDialog):
|
||||
self.setMinimumWidth(self.mainConf.pxInt(700))
|
||||
self.setMinimumHeight(self.mainConf.pxInt(600))
|
||||
|
||||
pOptions = self.theProject.options
|
||||
self.resize(
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winWidth", 900)),
|
||||
self.mainConf.pxInt(self.optState.getInt("GuiBuildNovel", "winHeight", 800))
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winWidth", 900)),
|
||||
self.mainConf.pxInt(pOptions.getInt("GuiBuildNovel", "winHeight", 800))
|
||||
)
|
||||
|
||||
self.docView = GuiBuildNovelDocView(self, self.theProject)
|
||||
|
||||
hS = self.theTheme.fontPixelSize
|
||||
hS = self.mainTheme.fontPixelSize
|
||||
wS = 2*hS
|
||||
|
||||
# Title Formats
|
||||
@@ -174,12 +174,12 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.hideScene = QSwitch(width=wS, height=hS)
|
||||
self.hideScene.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "hideScene", False)
|
||||
pOptions.getBool("GuiBuildNovel", "hideScene", False)
|
||||
)
|
||||
|
||||
self.hideSection = QSwitch(width=wS, height=hS)
|
||||
self.hideSection.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "hideSection", True)
|
||||
pOptions.getBool("GuiBuildNovel", "hideSection", True)
|
||||
)
|
||||
|
||||
# Wrapper boxes due to QGridView and QLineEdit expand bug
|
||||
@@ -235,29 +235,29 @@ class GuiBuildNovel(QDialog):
|
||||
self.textFont.setReadOnly(True)
|
||||
self.textFont.setMinimumWidth(xFmt)
|
||||
self.textFont.setText(
|
||||
self.optState.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
|
||||
pOptions.getString("GuiBuildNovel", "textFont", self.mainConf.textFont)
|
||||
)
|
||||
self.fontButton = QPushButton("...")
|
||||
self.fontButton.setMaximumWidth(int(2.5*self.theTheme.getTextWidth("...")))
|
||||
self.fontButton.setMaximumWidth(int(2.5*self.mainTheme.getTextWidth("...")))
|
||||
self.fontButton.clicked.connect(self._selectFont)
|
||||
|
||||
self.textSize = QSpinBox(self)
|
||||
self.textSize.setFixedWidth(6*self.theTheme.textNWidth)
|
||||
self.textSize.setFixedWidth(6*self.mainTheme.textNWidth)
|
||||
self.textSize.setMinimum(6)
|
||||
self.textSize.setMaximum(72)
|
||||
self.textSize.setSingleStep(1)
|
||||
self.textSize.setValue(
|
||||
self.optState.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
|
||||
pOptions.getInt("GuiBuildNovel", "textSize", self.mainConf.textSize)
|
||||
)
|
||||
|
||||
self.lineHeight = QDoubleSpinBox(self)
|
||||
self.lineHeight.setFixedWidth(6*self.theTheme.textNWidth)
|
||||
self.lineHeight.setFixedWidth(6*self.mainTheme.textNWidth)
|
||||
self.lineHeight.setMinimum(0.8)
|
||||
self.lineHeight.setMaximum(3.0)
|
||||
self.lineHeight.setSingleStep(0.05)
|
||||
self.lineHeight.setDecimals(2)
|
||||
self.lineHeight.setValue(
|
||||
self.optState.getFloat("GuiBuildNovel", "lineHeight", 1.15)
|
||||
pOptions.getFloat("GuiBuildNovel", "lineHeight", 1.15)
|
||||
)
|
||||
|
||||
# Wrapper box due to QGridView and QLineEdit expand bug
|
||||
@@ -291,12 +291,12 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.justifyText = QSwitch(width=wS, height=hS)
|
||||
self.justifyText.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "justifyText", False)
|
||||
pOptions.getBool("GuiBuildNovel", "justifyText", False)
|
||||
)
|
||||
|
||||
self.noStyling = QSwitch(width=wS, height=hS)
|
||||
self.noStyling.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "noStyling", False)
|
||||
pOptions.getBool("GuiBuildNovel", "noStyling", False)
|
||||
)
|
||||
|
||||
self.styleForm.addWidget(justifyLabel, 1, 0, 1, 1, Qt.AlignLeft)
|
||||
@@ -316,22 +316,22 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.includeSynopsis = QSwitch(width=wS, height=hS)
|
||||
self.includeSynopsis.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incSynopsis", False)
|
||||
pOptions.getBool("GuiBuildNovel", "incSynopsis", False)
|
||||
)
|
||||
|
||||
self.includeComments = QSwitch(width=wS, height=hS)
|
||||
self.includeComments.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incComments", False)
|
||||
pOptions.getBool("GuiBuildNovel", "incComments", False)
|
||||
)
|
||||
|
||||
self.includeKeywords = QSwitch(width=wS, height=hS)
|
||||
self.includeKeywords.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incKeywords", False)
|
||||
pOptions.getBool("GuiBuildNovel", "incKeywords", False)
|
||||
)
|
||||
|
||||
self.includeBody = QSwitch(width=wS, height=hS)
|
||||
self.includeBody.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "incBodyText", True)
|
||||
pOptions.getBool("GuiBuildNovel", "incBodyText", True)
|
||||
)
|
||||
|
||||
synopsisLabel = QLabel(self.tr("Include synopsis"))
|
||||
@@ -360,17 +360,17 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.novelFiles = QSwitch(width=wS, height=hS)
|
||||
self.novelFiles.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "addNovel", True)
|
||||
pOptions.getBool("GuiBuildNovel", "addNovel", True)
|
||||
)
|
||||
|
||||
self.noteFiles = QSwitch(width=wS, height=hS)
|
||||
self.noteFiles.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "addNotes", False)
|
||||
pOptions.getBool("GuiBuildNovel", "addNotes", False)
|
||||
)
|
||||
|
||||
self.ignoreFlag = QSwitch(width=wS, height=hS)
|
||||
self.ignoreFlag.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "ignoreFlag", False)
|
||||
pOptions.getBool("GuiBuildNovel", "ignoreFlag", False)
|
||||
)
|
||||
|
||||
novelLabel = QLabel(self.tr("Include novel files"))
|
||||
@@ -396,12 +396,12 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
self.replaceTabs = QSwitch(width=wS, height=hS)
|
||||
self.replaceTabs.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "replaceTabs", False)
|
||||
pOptions.getBool("GuiBuildNovel", "replaceTabs", False)
|
||||
)
|
||||
|
||||
self.replaceUCode = QSwitch(width=wS, height=hS)
|
||||
self.replaceUCode.setChecked(
|
||||
self.optState.getBool("GuiBuildNovel", "replaceUCode", False)
|
||||
pOptions.getBool("GuiBuildNovel", "replaceUCode", False)
|
||||
)
|
||||
|
||||
tabsLabel = QLabel(self.tr("Replace tabs with spaces"))
|
||||
@@ -493,9 +493,9 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
# Splitter Position
|
||||
boxWidth = self.mainConf.pxInt(350)
|
||||
boxWidth = self.optState.getInt("GuiBuildNovel", "boxWidth", boxWidth)
|
||||
boxWidth = pOptions.getInt("GuiBuildNovel", "boxWidth", boxWidth)
|
||||
docWidth = max(self.width() - boxWidth, 100)
|
||||
docWidth = self.optState.getInt("GuiBuildNovel", "docWidth", docWidth)
|
||||
docWidth = pOptions.getInt("GuiBuildNovel", "docWidth", docWidth)
|
||||
|
||||
# The Tool Box
|
||||
self.toolsBox = QVBoxLayout()
|
||||
@@ -677,7 +677,7 @@ class GuiBuildNovel(QDialog):
|
||||
replaceUCode = self.replaceUCode.isChecked()
|
||||
|
||||
# The language lookup dict is reloaded if needed
|
||||
self.theProject.setProjectLang(self.buildLang.currentData())
|
||||
self.theProject.setProjectLang(buildLang)
|
||||
|
||||
# Get font information
|
||||
fontInfo = QFontInfo(QFont(textFont, textSize))
|
||||
@@ -711,13 +711,12 @@ class GuiBuildNovel(QDialog):
|
||||
bldObj.initDocument()
|
||||
|
||||
# Make sure the project and document is up to date
|
||||
self.theParent.treeView.flushTreeOrder()
|
||||
self.theParent.saveDocument()
|
||||
self.mainGui.saveDocument()
|
||||
|
||||
self.buildProgress.setMaximum(len(self.theProject.projTree))
|
||||
self.buildProgress.setMaximum(len(self.theProject.tree))
|
||||
self.buildProgress.setValue(0)
|
||||
|
||||
for nItt, tItem in enumerate(self.theProject.projTree):
|
||||
for nItt, tItem in enumerate(self.theProject.tree):
|
||||
|
||||
noteRoot = noteFiles
|
||||
noteRoot &= tItem.itemType == nwItemType.ROOT
|
||||
@@ -761,7 +760,7 @@ class GuiBuildNovel(QDialog):
|
||||
logger.debug("Built project in %.3f ms", 1000*(tEnd - tStart))
|
||||
|
||||
if bldObj.errData:
|
||||
self.theParent.makeAlert([
|
||||
self.mainGui.makeAlert([
|
||||
self.tr("There were problems when building the project:")
|
||||
] + bldObj.errData, nwAlert.ERROR)
|
||||
|
||||
@@ -782,16 +781,14 @@ class GuiBuildNovel(QDialog):
|
||||
if theItem is None:
|
||||
return False
|
||||
|
||||
if not theItem.isExported and not ignoreFlag:
|
||||
if not (theItem.isExported or ignoreFlag):
|
||||
return False
|
||||
|
||||
isNone = theItem.itemType != nwItemType.FILE
|
||||
isNone = not theItem.isFileType()
|
||||
isNone |= theItem.itemLayout == nwItemLayout.NO_LAYOUT
|
||||
isNone |= theItem.itemClass == nwItemClass.NO_CLASS
|
||||
isNone |= theItem.itemClass == nwItemClass.TRASH
|
||||
isNone |= theItem.itemParent == self.theProject.projTree.trashRoot()
|
||||
isNone |= theItem.isInactive()
|
||||
isNone |= theItem.itemParent is None
|
||||
isNote = theItem.itemLayout == nwItemLayout.NOTE
|
||||
isNote = theItem.isNoteLayout()
|
||||
isNovel = not isNone and not isNote
|
||||
|
||||
if isNone:
|
||||
@@ -801,10 +798,6 @@ class GuiBuildNovel(QDialog):
|
||||
if isNovel and not novelFiles:
|
||||
return False
|
||||
|
||||
rootItem = self.theProject.projTree.getRootItem(theItem.itemHandle)
|
||||
if rootItem.itemClass == nwItemClass.ARCHIVE:
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
def _saveDocument(self, theFmt):
|
||||
@@ -1010,11 +1003,11 @@ class GuiBuildNovel(QDialog):
|
||||
# ==============
|
||||
|
||||
if wSuccess:
|
||||
self.theParent.makeAlert([
|
||||
self.mainGui.makeAlert([
|
||||
self.tr("{0} file successfully written to:").format(textFmt), savePath
|
||||
], nwAlert.INFO)
|
||||
else:
|
||||
self.theParent.makeAlert(self.tr(
|
||||
self.mainGui.makeAlert(self.tr(
|
||||
"Failed to write {0} file. {1}"
|
||||
).format(textFmt, errMsg), nwAlert.ERROR)
|
||||
|
||||
@@ -1161,28 +1154,28 @@ class GuiBuildNovel(QDialog):
|
||||
self.theProject.setProjectLang(buildLang)
|
||||
|
||||
# GUI Settings
|
||||
self.optState.setValue("GuiBuildNovel", "hideScene", hideScene)
|
||||
self.optState.setValue("GuiBuildNovel", "hideSection", hideSection)
|
||||
self.optState.setValue("GuiBuildNovel", "winWidth", winWidth)
|
||||
self.optState.setValue("GuiBuildNovel", "winHeight", winHeight)
|
||||
self.optState.setValue("GuiBuildNovel", "boxWidth", boxWidth)
|
||||
self.optState.setValue("GuiBuildNovel", "docWidth", docWidth)
|
||||
self.optState.setValue("GuiBuildNovel", "justifyText", justifyText)
|
||||
self.optState.setValue("GuiBuildNovel", "noStyling", noStyling)
|
||||
self.optState.setValue("GuiBuildNovel", "textFont", textFont)
|
||||
self.optState.setValue("GuiBuildNovel", "textSize", textSize)
|
||||
self.optState.setValue("GuiBuildNovel", "lineHeight", lineHeight)
|
||||
self.optState.setValue("GuiBuildNovel", "addNovel", novelFiles)
|
||||
self.optState.setValue("GuiBuildNovel", "addNotes", noteFiles)
|
||||
self.optState.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag)
|
||||
self.optState.setValue("GuiBuildNovel", "incSynopsis", incSynopsis)
|
||||
self.optState.setValue("GuiBuildNovel", "incComments", incComments)
|
||||
self.optState.setValue("GuiBuildNovel", "incKeywords", incKeywords)
|
||||
self.optState.setValue("GuiBuildNovel", "incBodyText", incBodyText)
|
||||
self.optState.setValue("GuiBuildNovel", "replaceTabs", replaceTabs)
|
||||
self.optState.setValue("GuiBuildNovel", "replaceUCode", replaceUCode)
|
||||
|
||||
self.optState.saveSettings()
|
||||
pOptions = self.theProject.options
|
||||
pOptions.setValue("GuiBuildNovel", "hideScene", hideScene)
|
||||
pOptions.setValue("GuiBuildNovel", "hideSection", hideSection)
|
||||
pOptions.setValue("GuiBuildNovel", "winWidth", winWidth)
|
||||
pOptions.setValue("GuiBuildNovel", "winHeight", winHeight)
|
||||
pOptions.setValue("GuiBuildNovel", "boxWidth", boxWidth)
|
||||
pOptions.setValue("GuiBuildNovel", "docWidth", docWidth)
|
||||
pOptions.setValue("GuiBuildNovel", "justifyText", justifyText)
|
||||
pOptions.setValue("GuiBuildNovel", "noStyling", noStyling)
|
||||
pOptions.setValue("GuiBuildNovel", "textFont", textFont)
|
||||
pOptions.setValue("GuiBuildNovel", "textSize", textSize)
|
||||
pOptions.setValue("GuiBuildNovel", "lineHeight", lineHeight)
|
||||
pOptions.setValue("GuiBuildNovel", "addNovel", novelFiles)
|
||||
pOptions.setValue("GuiBuildNovel", "addNotes", noteFiles)
|
||||
pOptions.setValue("GuiBuildNovel", "ignoreFlag", ignoreFlag)
|
||||
pOptions.setValue("GuiBuildNovel", "incSynopsis", incSynopsis)
|
||||
pOptions.setValue("GuiBuildNovel", "incComments", incComments)
|
||||
pOptions.setValue("GuiBuildNovel", "incKeywords", incKeywords)
|
||||
pOptions.setValue("GuiBuildNovel", "incBodyText", incBodyText)
|
||||
pOptions.setValue("GuiBuildNovel", "replaceTabs", replaceTabs)
|
||||
pOptions.setValue("GuiBuildNovel", "replaceUCode", replaceUCode)
|
||||
pOptions.saveSettings()
|
||||
|
||||
return
|
||||
|
||||
@@ -1200,18 +1193,18 @@ class GuiBuildNovel(QDialog):
|
||||
|
||||
class GuiBuildNovelDocView(QTextBrowser):
|
||||
|
||||
def __init__(self, theParent, theProject):
|
||||
QTextBrowser.__init__(self, theParent)
|
||||
def __init__(self, mainGui, theProject):
|
||||
super().__init__(parent=mainGui)
|
||||
|
||||
logger.debug("Initialising GuiBuildNovelDocView ...")
|
||||
|
||||
self.mainConf = novelwriter.CONFIG
|
||||
self.theProject = theProject
|
||||
self.theParent = theParent
|
||||
self.theTheme = theParent.theTheme
|
||||
self.mainGui = mainGui
|
||||
self.mainTheme = mainGui.mainTheme
|
||||
self.buildTime = 0
|
||||
|
||||
self.setMinimumWidth(40*self.theParent.theTheme.textNWidth)
|
||||
self.setMinimumWidth(40*self.mainGui.mainTheme.textNWidth)
|
||||
self.setOpenExternalLinks(False)
|
||||
|
||||
self.document().setDocumentMargin(self.mainConf.getTextMargin())
|
||||
@@ -1245,9 +1238,9 @@ class GuiBuildNovelDocView(QTextBrowser):
|
||||
lblPalette.setColor(QPalette.Foreground, lblPalette.toolTipText().color())
|
||||
|
||||
lblFont = self.font()
|
||||
lblFont.setPointSizeF(0.9*self.theTheme.fontPointSize)
|
||||
lblFont.setPointSizeF(0.9*self.mainTheme.fontPointSize)
|
||||
|
||||
fPx = int(1.1*self.theTheme.fontPixelSize)
|
||||
fPx = int(1.1*self.mainTheme.fontPixelSize)
|
||||
|
||||
self.theTitle = QLabel("", self)
|
||||
self.theTitle.setIndent(0)
|
||||
@@ -1350,7 +1343,7 @@ class GuiBuildNovelDocView(QTextBrowser):
|
||||
def resizeEvent(self, theEvent):
|
||||
"""Make sure the document title is the same width as the window.
|
||||
"""
|
||||
QTextBrowser.resizeEvent(self, theEvent)
|
||||
super().resizeEvent(theEvent)
|
||||
self._updateDocMargins()
|
||||
return
|
||||
|
||||
|
||||