Merge branch 'main' into i18n-de_DE-created

This commit is contained in:
Veronica Berglyd Olsen
2022-06-15 17:18:47 +02:00
committed by GitHub
79 changed files with 2985 additions and 2317 deletions
+103 -47
View File
@@ -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
+2 -4
View File
@@ -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."
@@ -72,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."
@@ -83,3 +83,8 @@ view_build = typ_export.svg
view_editor = mixed_edit.svg
view_novel = typ_book-grey.svg
view_outline = typ_puzzle-outline.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
@@ -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

@@ -83,3 +83,8 @@ view_build = typ_export.svg
view_editor = mixed_edit.svg
view_novel = typ_book-grey.svg
view_outline = typ_puzzle-outline.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
@@ -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

+9
View File
@@ -57,6 +57,15 @@ class nwRegEx:
# END Class nwRegEx
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"
+8 -17
View File
@@ -34,7 +34,7 @@ from time import time
from novelwriter.enum import nwItemType, nwItemLayout
from novelwriter.error import logException
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode
from novelwriter.constants import nwFiles, nwKeyWords, nwUnicode, nwHeaders
from novelwriter.core.document import NWDoc
from novelwriter.common import (
checkInt, isHandle, isItemClass, isTitleTag, jsonEncode
@@ -42,10 +42,6 @@ from novelwriter.common import (
logger = logging.getLogger(__name__)
H_VALID = ("H0", "H1", "H2", "H3", "H4")
H_LEVEL = {"H0": 0, "H1": 1, "H2": 2, "H3": 3, "H4": 4}
TT_NONE = "T000000"
class NWIndex:
"""This class holds the entire index for a given project. The index
@@ -477,7 +473,7 @@ class NWIndex:
"""
hCount = [0, 0, 0, 0, 0]
for _, _, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl):
iLevel = H_LEVEL.get(hItem.level, 0)
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
hCount[iLevel] += 1
return hCount
@@ -502,11 +498,6 @@ class NWIndex:
"""
return self._itemIndex.mainItemHeader(tHandle)
def getHandleHeaderIntLevel(self, tHandle):
"""Get the integer header level of the first header of a handle.
"""
return H_LEVEL.get(self._itemIndex.mainItemHeader(tHandle), 0)
def getTableOfContents(self, maxDepth, skipExcl=True):
"""Generate a table of contents up to a maximum depth.
"""
@@ -515,7 +506,7 @@ class NWIndex:
pKey = None
for tHandle, sTitle, hItem in self._itemIndex.iterNovelStructure(skipExcl=skipExcl):
tKey = f"{tHandle}:{sTitle}"
iLevel = H_LEVEL.get(hItem.level, 0)
iLevel = nwHeaders.H_LEVEL.get(hItem.level, 0)
if iLevel > maxDepth:
if pKey in tData:
tData[pKey]["words"] += hItem.wordCount
@@ -665,7 +656,7 @@ class TagsIndex:
"""
if tagKey in self._tags:
return self._tags.get(tagKey).get("heading")
return TT_NONE
return nwHeaders.TT_NONE
def tagClass(self, tagKey):
"""Get the class of a given tag.
@@ -911,7 +902,7 @@ class IndexItem:
self._index = 0
# Add a placeholder heading
self._headings[TT_NONE] = IndexHeading(TT_NONE)
self._headings[nwHeaders.TT_NONE] = IndexHeading(nwHeaders.TT_NONE)
return
@@ -945,8 +936,8 @@ class IndexItem:
"""Add a heading to the item. Also remove the placeholder entry
if it exists.
"""
if TT_NONE in self._headings:
self._headings.pop(TT_NONE)
if nwHeaders.TT_NONE in self._headings:
self._headings.pop(nwHeaders.TT_NONE)
self._headings[tHeading.key] = tHeading
return
@@ -1115,7 +1106,7 @@ class IndexHeading:
def setLevel(self, level):
"""Set the level of the header if it's a valid value.
"""
if level in H_VALID:
if level in nwHeaders.H_VALID:
self._level = level
return
+20 -2
View File
@@ -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,7 +58,8 @@ VALID_MAP = {
"winWidth", "winHeight", "widthCol0", "widthCol1", "widthCol2",
"widthCol3", "widthCol4", "wordsPerPage", "countFrom", "clearDouble"
},
"GuiWordList": {"winWidth", "winHeight"}
"GuiWordList": {"winWidth", "winHeight"},
"GuiNovelView": {"lastCol"},
}
@@ -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
+113 -109
View File
@@ -45,7 +45,7 @@ from novelwriter.enum import nwItemType, nwItemClass, nwItemLayout, nwAlert
from novelwriter.error import logException
from novelwriter.common import (
checkString, checkBool, checkInt, isHandle, formatTimeStamp,
makeFileNameSafe, hexToInt, simplified
makeFileNameSafe, hexToInt, minmax, simplified
)
from novelwriter.constants import trConst, nwFiles, nwLabels
@@ -56,11 +56,11 @@ class NWProject():
FILE_VERSION = "1.4" # The current project file format version
def __init__(self, theParent):
def __init__(self, mainGui):
# Internal
self.theParent = theParent
self.mainConf = novelwriter.CONFIG
self.mainConf = novelwriter.CONFIG
self.mainGui = mainGui
# Core Elements
self._optState = OptionState(self) # Project-specific GUI options
@@ -102,6 +102,8 @@ class NWProject():
self.importItems = None # Note file importance values
self.lastEdited = None # The handle of the last file to be edited
self.lastViewed = None # The handle of the last file to be viewed
self.lastNovel = None # The handle of the last novel root viewed
self.lastOutline = None # The handle of the last outline root viewed
self.lastWCount = 0 # The project word count from last session
self.lastNovelWC = 0 # The novel files word count from last session
self.lastNotesWC = 0 # The note files word count from last session
@@ -154,6 +156,8 @@ class NWProject():
def newFolder(self, label, pHandle):
"""Add a new folder with a given label and parent item.
"""
if pHandle not in self._projTree:
return None
newItem = NWItem(self)
newItem.setName(label)
newItem.setType(nwItemType.FOLDER)
@@ -164,6 +168,8 @@ class NWProject():
def newFile(self, label, pHandle):
"""Add a new file with a given label and parent item.
"""
if pHandle not in self._projTree:
return None
newItem = NWItem(self)
newItem.setName(label)
newItem.setType(nwItemType.FILE)
@@ -171,6 +177,32 @@ class NWProject():
self._projTree.updateItemData(newItem.itemHandle)
return newItem.itemHandle
def writeNewFile(self, tHandle, hLevel, isDocument):
"""Write content to a new document after it is created. This
will not run if the file exists and is not empty.
"""
tItem = self._projTree[tHandle]
if tItem is None:
return False
if tItem.itemType != nwItemType.FILE:
return False
newDoc = NWDoc(self, tHandle)
if newDoc.readDocument().strip():
return False
hshText = "#"*minmax(hLevel, 1, 4)
newText = f"{hshText} {tItem.itemName}\n\n"
if tItem.isNovelLike() and isDocument:
tItem.setLayout(nwItemLayout.DOCUMENT)
else:
tItem.setLayout(nwItemLayout.NOTE)
newDoc.writeDocument(newText)
self._projIndex.scanText(tHandle, newText)
return True
def trashFolder(self):
"""Add the special trash root folder to the project.
"""
@@ -384,7 +416,7 @@ class NWProject():
if not os.path.isfile(fileName):
fileName = os.path.join(fileName, nwFiles.PROJ_FILE)
if not os.path.isfile(fileName):
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"File not found: {0}"
).format(fileName), nwAlert.ERROR)
return False
@@ -408,7 +440,7 @@ class NWProject():
legacyList = [] # Cleanup is done later
for projItem in os.listdir(self.projPath):
logger.verbose("Project contains: %s", projItem)
if projItem.startswith("data_"):
if projItem.startswith("data_") and len(projItem) == 6:
legacyList.append(projItem)
# Project Lock
@@ -435,20 +467,20 @@ class NWProject():
try:
nwXML = etree.parse(fileName)
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Failed to parse project xml."
), nwAlert.ERROR, exception=exc)
# Trying to open backup file instead
backFile = fileName[:-3]+"bak"
if os.path.isfile(backFile):
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Attempting to open backup project file instead."
), nwAlert.INFO)
try:
nwXML = etree.parse(backFile)
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Failed to parse project xml."
), nwAlert.ERROR, exception=exc)
self.clearProject()
@@ -470,8 +502,8 @@ class NWProject():
# Check File Type
# ===============
if not nwxRoot == "novelWriterXML":
self.theParent.makeAlert(self.tr(
if nwxRoot != "novelWriterXML":
self.mainGui.makeAlert(self.tr(
"Project file does not appear to be a novelWriterXML file."
), nwAlert.ERROR)
self.clearProject()
@@ -497,7 +529,7 @@ class NWProject():
# stored and handled. Introduced in version 1.7.
if fileVersion not in ("1.0", "1.1", "1.2", "1.3", "1.4"):
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Unknown or unsupported novelWriter project file format. "
"The project cannot be opened by this version of novelWriter. "
"The file was saved with novelWriter version {0}."
@@ -506,7 +538,7 @@ class NWProject():
return False
if fileVersion != self.FILE_VERSION:
msgYes = self.theParent.askQuestion(
msgYes = self.mainGui.askQuestion(
self.tr("File Version"),
self.tr(
"The file format of your project is about to be updated. "
@@ -522,7 +554,7 @@ class NWProject():
# =========================
if hexToInt(hexVersion) > hexToInt(novelwriter.__hexversion__):
msgYes = self.theParent.askQuestion(
msgYes = self.mainGui.askQuestion(
self.tr("Version Conflict"),
self.tr(
"This project was saved by a newer version of "
@@ -582,6 +614,10 @@ class NWProject():
self.lastEdited = checkString(xItem.text, None, True)
elif xItem.tag == "lastViewed":
self.lastViewed = checkString(xItem.text, None, True)
elif xItem.tag == "lastNovel":
self.lastNovel = checkString(xItem.text, None, True)
elif xItem.tag == "lastOutline":
self.lastOutline = checkString(xItem.text, None, True)
elif xItem.tag == "lastWordCount":
self.lastWCount = checkInt(xItem.text, 0, False)
elif xItem.tag == "novelWordCount":
@@ -612,11 +648,14 @@ class NWProject():
# Sort out old file locations
if legacyList:
errList = []
for projItem in legacyList:
errList = self._legacyDataFolder(projItem, errList)
if errList:
self.theParent.makeAlert(errList, nwAlert.ERROR)
try:
for projItem in legacyList:
self._legacyDataFolder(projItem)
except Exception:
self.mainGui.makeAlert(self.tr(
"There was an error updating the project. "
"Some data may not have been preserved."
), nwAlert.ERROR)
# Clean up no longer used files
self._deprecatedFiles()
@@ -642,7 +681,7 @@ class NWProject():
self._writeLockFile()
self.setProjectChanged(False)
self.theParent.setStatus(self.tr("Opened Project: {0}").format(self.projName))
self.mainGui.setStatus(self.tr("Opened Project: {0}").format(self.projName))
return True
@@ -653,7 +692,7 @@ class NWProject():
file.
"""
if self.projPath is None:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Project path not set, cannot save project."
), nwAlert.ERROR)
return False
@@ -699,6 +738,8 @@ class NWProject():
self._packProjectValue(xSettings, "autoOutline", self.autoOutline)
self._packProjectValue(xSettings, "lastEdited", self.lastEdited)
self._packProjectValue(xSettings, "lastViewed", self.lastViewed)
self._packProjectValue(xSettings, "lastNovel", self.lastNovel)
self._packProjectValue(xSettings, "lastOutline", self.lastOutline)
self._packProjectValue(xSettings, "lastWordCount", self.currWCount)
self._packProjectValue(xSettings, "novelWordCount", self.currNovelWC)
self._packProjectValue(xSettings, "notesWordCount", self.currNotesWC)
@@ -733,7 +774,7 @@ class NWProject():
xml_declaration=True
))
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Failed to save project."
), nwAlert.ERROR, exception=exc)
return False
@@ -745,7 +786,7 @@ class NWProject():
os.replace(saveFile, backFile)
os.replace(tempFile, saveFile)
except OSError as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Failed to save project."
), nwAlert.ERROR, exception=exc)
return False
@@ -758,7 +799,7 @@ class NWProject():
self.mainConf.saveRecentCache()
self._writeLockFile()
self.theParent.setStatus(self.tr("Saved Project: {0}").format(self.projName))
self.mainGui.setStatus(self.tr("Saved Project: {0}").format(self.projName))
self.setProjectChanged(False)
return True
@@ -806,22 +847,22 @@ class NWProject():
def zipIt(self, doNotify):
"""Create a zip file of the entire project.
"""
if not self.theParent.hasProject:
if not self.mainGui.hasProject:
logger.error("No project open")
return False
logger.info("Backing up project")
self.theParent.setStatus(self.tr("Backing up project ..."))
self.mainGui.setStatus(self.tr("Backing up project ..."))
if not (self.mainConf.backupPath and os.path.isdir(self.mainConf.backupPath)):
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Cannot backup project because no valid backup path is set. "
"Please set a valid backup location in Preferences."
), nwAlert.ERROR)
return False
if not self.projName:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Cannot backup project because no project name is set. "
"Please set a Working Title in Project Settings."
), nwAlert.ERROR)
@@ -834,13 +875,13 @@ class NWProject():
os.mkdir(baseDir)
logger.debug("Created folder: %s", baseDir)
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Could not create backup folder."
), nwAlert.ERROR, exception=exc)
return False
if baseDir and baseDir.startswith(self.projPath):
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Cannot backup project because the backup path is within the "
"project folder to be backed up. Please choose a different "
"backup path in Preferences."
@@ -856,17 +897,17 @@ class NWProject():
self._writeLockFile()
logger.info("Backup written to: %s", archName)
if doNotify:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Backup archive file written to: {0}"
).format(f"{os.path.join(cleanName, archName)}.zip"), nwAlert.INFO)
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Could not write backup archive."
), nwAlert.ERROR, exception=exc)
return False
self.theParent.setStatus(self.tr(
self.mainGui.setStatus(self.tr(
"Project backed up to '{0}'"
).format(f"{baseName}.zip"))
@@ -894,7 +935,7 @@ class NWProject():
shutil.unpack_archive(pkgSample, projPath)
isSuccess = True
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Failed to create a new example project."
), nwAlert.ERROR, exception=exc)
@@ -916,12 +957,12 @@ class NWProject():
isSuccess = True
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Failed to create a new example project."
), nwAlert.ERROR, exception=exc)
else:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Failed to create a new example project. "
"Could not find the necessary files. "
"They seem to be missing from this installation."
@@ -929,8 +970,8 @@ class NWProject():
if isSuccess:
self.clearProject()
self.theParent.openProject(projPath)
self.theParent.rebuildIndex()
self.mainGui.openProject(projPath)
self.mainGui.rebuildIndex()
return isSuccess
@@ -955,14 +996,14 @@ class NWProject():
os.mkdir(projPath)
logger.debug("Created folder: %s", projPath)
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Could not create new project folder."
), nwAlert.ERROR, exception=exc)
return False
if os.path.isdir(projPath):
if os.listdir(self.projPath):
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"New project folder is not empty. "
"Each project requires a dedicated project folder."
), nwAlert.ERROR)
@@ -1012,14 +1053,14 @@ class NWProject():
self.doBackup = doBackup
if doBackup:
if not os.path.isdir(self.mainConf.backupPath):
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"You must set a valid backup path in Preferences to use "
"the automatic project backup feature."
), nwAlert.WARN)
return False
if self.projName == "":
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"You must set a valid project name in Project Settings to "
"use the automatic project backup feature."
), nwAlert.WARN)
@@ -1090,6 +1131,14 @@ class NWProject():
self.setProjectChanged(True)
return True
def setLastNovelViewed(self, tHandle):
"""Set last viewed novel root in the novel tree.
"""
if self.lastNovel != tHandle:
self.lastNovel = tHandle
self.setProjectChanged(True)
return True
def setStatusColours(self, newCols, delCols):
"""Update the list of novel file status flags.
"""
@@ -1124,7 +1173,7 @@ class NWProject():
information to the GUI statusbar.
"""
self.projChanged = bValue
self.theParent.statusBar.doUpdateProjectStatus(bValue)
self.mainGui.statusBar.doUpdateProjectStatus(bValue)
if bValue:
# If we've changed the project at all, this should be True
self.projAltered = True
@@ -1354,7 +1403,7 @@ class NWProject():
os.mkdir(thePath)
logger.debug("Created folder: %s", thePath)
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Could not create folder."
), nwAlert.ERROR, exception=exc)
return False
@@ -1417,7 +1466,7 @@ class NWProject():
# Report status
if len(orphanFiles) > 0:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Found {0} orphaned file(s) in project folder."
).format(len(orphanFiles)), nwAlert.WARN)
else:
@@ -1474,7 +1523,7 @@ class NWProject():
self._projTree.updateItemData(orphItem.itemHandle)
if noWhere:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"One or more orphaned files could not be added back into the project. "
"Make sure at least a Novel root folder exists."
), nwAlert.WARN)
@@ -1528,82 +1577,37 @@ class NWProject():
# Legacy Data Structure Handlers
##
def _legacyDataFolder(self, theFolder, errList):
def _legacyDataFolder(self, dataDir):
"""Clean up legacy data folders.
"""
theData = os.path.join(self.projPath, theFolder)
if not os.path.isdir(theData):
errList.append(self.tr("Not a folder: {0}").format(theData))
return errList
dataPath = os.path.join(self.projPath, dataDir)
if not os.path.isdir(dataPath):
return False
logger.info("Old data folder %s found", theFolder)
logger.info("Old data folder found: %s", dataDir)
# Move Documents to Content
for dataItem in os.listdir(theData):
theFile = os.path.join(theData, dataItem)
if not os.path.isfile(theFile):
theErr = self._moveUnknownItem(theData, dataItem)
if theErr:
errList.append(theErr)
for dataItem in os.listdir(dataPath):
dataFile = os.path.join(dataPath, dataItem)
if not os.path.isfile(dataFile):
continue
if len(dataItem) == 21 and dataItem.endswith("_main.nwd"):
tHandle = theFolder[-1]+dataItem[:12]
newPath = os.path.join(self.projContent, tHandle+".nwd")
try:
os.rename(theFile, newPath)
logger.info("Moved file: %s", theFile)
logger.info("New location: %s", newPath)
except Exception:
errList.append(self.tr("Could not move: {0}").format(theFile))
logger.error("Could not move: %s", theFile)
logException()
tHandle = dataDir[-1] + dataItem[:12]
newPath = os.path.join(self.projContent, f"{tHandle}.nwd")
os.rename(dataFile, newPath)
logger.info("Moved file: %s", dataFile)
elif len(dataItem) == 21 and dataItem.endswith("_main.bak"):
try:
os.unlink(theFile)
logger.info("Deleted file: %s", theFile)
except Exception:
errList.append(self.tr("Could not delete: {0}").format(theFile))
logger.error("Could not delete: %s", theFile)
logException()
else:
theErr = self._moveUnknownItem(theData, dataItem)
if theErr:
errList.append(theErr)
os.unlink(dataFile)
logger.info("Deleted file: %s", dataFile)
# Remove Data Folder
try:
os.rmdir(theData)
logger.info("Deleted folder: %s", theFolder)
except Exception:
errList.append(self.tr("Could not delete: {0}").format(theFolder))
logger.error("Could not delete: %s", theFolder)
logException()
if not os.listdir(dataPath):
os.rmdir(dataPath)
logger.info("Deleted folder: %s", dataDir)
return errList
def _moveUnknownItem(self, theDir, theItem):
"""Move an item that doesn't belong in the project folder to
a junk folder.
"""
theJunk = os.path.join(self.projPath, "junk")
if not self._checkFolder(theJunk):
return self.tr("Could not make folder: {0}").format(theJunk)
theSrc = os.path.join(theDir, theItem)
theDst = os.path.join(theJunk, theItem)
try:
os.rename(theSrc, theDst)
logger.info("Moved to junk: %s", theSrc)
except Exception:
logger.error("Could not move item %s to junk", theSrc)
logException()
return self.tr("Could not move item {0} to {1}.").format(theSrc, theJunk)
return ""
return True
def _deprecatedFiles(self):
"""Delete files that are no longer used by novelWriter.
-1
View File
@@ -82,7 +82,6 @@ class Tokenizer(ABC):
def __init__(self, theProject):
self.theProject = theProject
self.theParent = theProject.theParent
self.mainConf = novelwriter.CONFIG
# Data Variables
+2 -2
View File
@@ -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",
+28 -28
View File
@@ -43,15 +43,15 @@ logger = logging.getLogger(__name__)
class GuiAbout(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
def __init__(self, mainGui):
QDialog.__init__(self, 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"))
@@ -192,37 +192,37 @@ class GuiAbout(QDialog):
])
)
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),
])
)
@@ -280,12 +280,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)
+15 -15
View File
@@ -41,15 +41,15 @@ logger = logging.getLogger(__name__)
class GuiDocMerge(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
def __init__(self, mainGui):
QDialog.__init__(self, mainGui)
logger.debug("Initialising GuiDocMerge ...")
self.setObjectName("GuiDocMerge")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theParent.theProject
self.mainGui = mainGui
self.theProject = mainGui.theProject
self.sourceItem = None
self.outerBox = QVBoxLayout()
@@ -57,7 +57,7 @@ class GuiDocMerge(QDialog):
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.tr("Drag and drop items to change the order."), self.mainGui.mainTheme.helpText
)
self.listBox = QListWidget()
@@ -102,7 +102,7 @@ class GuiDocMerge(QDialog):
finalOrder.append(self.listBox.item(i).data(Qt.UserRole))
if len(finalOrder) == 0:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"No source documents found. Nothing to do."
), nwAlert.ERROR)
return False
@@ -113,21 +113,21 @@ class GuiDocMerge(QDialog):
docText = inDoc.readDocument()
docErr = inDoc.getError()
if docText is None and docErr:
self.theParent.makeAlert([
self.mainGui.makeAlert([
self.tr("Failed to open document file."), docErr
], nwAlert.ERROR)
if docText:
theText += docText.rstrip("\n")+"\n\n"
if self.sourceItem is None:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"No source folder selected. Nothing to do."
), nwAlert.ERROR)
return False
srcItem = self.theProject.tree[self.sourceItem]
if srcItem is None:
self.theParent.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
self.mainGui.makeAlert(self.tr("Internal error."), nwAlert.ERROR)
return False
nHandle = self.theProject.newFile(srcItem.itemName, srcItem.itemParent)
@@ -137,13 +137,13 @@ class GuiDocMerge(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.theParent.openDocument(nHandle, doScroll=True)
self.mainGui.projView.revealNewTreeItem(nHandle)
self.mainGui.openDocument(nHandle, doScroll=True)
self._doClose()
@@ -165,7 +165,7 @@ class GuiDocMerge(QDialog):
are then added to the list view in order. The list itself can be
reordered by the user.
"""
tHandle = self.theParent.treeView.getSelectedHandle()
tHandle = self.mainGui.projView.getSelectedHandle()
self.sourceItem = tHandle
if tHandle is None:
return False
@@ -175,12 +175,12 @@ class GuiDocMerge(QDialog):
return False
if nwItem.itemType is not nwItemType.FOLDER:
self.theParent.makeAlert(self.tr(
self.mainGui.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):
for sHandle in self.mainGui.projView.getTreeFromHandle(tHandle):
newItem = QListWidgetItem()
nwItem = self.theProject.tree[sHandle]
if nwItem.itemType is not nwItemType.FILE:
+15 -15
View File
@@ -41,15 +41,15 @@ logger = logging.getLogger(__name__)
class GuiDocSplit(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
def __init__(self, mainGui):
QDialog.__init__(self, mainGui)
logger.debug("Initialising GuiDocSplit ...")
self.setObjectName("GuiDocSplit")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theParent.theProject
self.mainGui = mainGui
self.theProject = mainGui.theProject
self.sourceItem = None
self.sourceText = []
@@ -60,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()
@@ -115,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.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
@@ -132,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)
@@ -153,12 +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
msgYes = self.theParent.askQuestion(
msgYes = self.mainGui.askQuestion(
self.tr("Split Document"),
"{0}<br><br>{1}".format(
self.tr(
@@ -175,7 +175,7 @@ class GuiDocSplit(QDialog):
# Create the folder
fHandle = self.theProject.newFolder(srcItem.itemName, srcItem.itemParent)
self.theParent.treeView.revealNewTreeItem(fHandle)
self.mainGui.projView.revealNewTreeItem(fHandle)
logger.verbose("Creating folder '%s'", fHandle)
# Loop through, and create the files
@@ -196,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()
@@ -226,7 +226,7 @@ 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
@@ -236,7 +236,7 @@ class GuiDocSplit(QDialog):
return False
if nwItem.itemType is not nwItemType.FILE:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Element selected in the project tree must be a file."
), nwAlert.ERROR)
return False
+84
View File
@@ -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 20182022, 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=""):
QDialog.__init__(self, 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
-203
View File
@@ -1,203 +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 20182022, 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, 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.tree[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.isNovelLike():
for key, entry in self.theProject.statusItems.items():
self.editStatus.addItem(entry["icon"], entry["name"], key)
index = self.editStatus.findData(self.theItem.itemStatus)
if index != -1:
self.editStatus.setCurrentIndex(index)
else:
for key, entry in self.theProject.importItems.items():
self.editStatus.addItem(entry["icon"], entry["name"], key)
index = self.editStatus.findData(self.theItem.itemImport)
if index != -1:
self.editStatus.setCurrentIndex(index)
# Item Layout
self.editLayout = QComboBox()
self.editLayout.setMinimumWidth(mVd)
validLayouts = []
if self.theItem.itemType == nwItemType.FILE:
if self.theItem.documentAllowed():
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)
index = self.editLayout.findData(self.theItem.itemLayout)
if index != -1:
self.editLayout.setCurrentIndex(index)
# Export Switch
self.textExport = QLabel(self.tr("Include when building project"))
self.editExport = QSwitch()
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()
##
# 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.setImportStatus(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
+56 -56
View File
@@ -43,25 +43,25 @@ logger = logging.getLogger(__name__)
class GuiPreferences(PagedDialog):
def __init__(self, theParent):
PagedDialog.__init__(self, theParent)
def __init__(self, mainGui):
PagedDialog.__init__(self, 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.mainGui)
self.tabProjects = GuiPreferencesProjects(self.mainGui)
self.tabDocs = GuiPreferencesDocuments(self.mainGui)
self.tabEditor = GuiPreferencesEditor(self.mainGui)
self.tabSyntax = GuiPreferencesSyntax(self.mainGui)
self.tabAuto = GuiPreferencesAutomation(self.mainGui)
self.tabQuote = GuiPreferencesQuotes(self.mainGui)
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()
@@ -138,16 +138,16 @@ class GuiPreferences(PagedDialog):
class GuiPreferencesGeneral(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, mainGui):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = 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
@@ -174,7 +174,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)
@@ -190,8 +190,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:
@@ -206,7 +206,7 @@ class GuiPreferencesGeneral(QWidget):
# Editor Theme
self.guiSyntax = QComboBox()
self.guiSyntax.setMinimumWidth(self.mainConf.pxInt(200))
self.theSyntaxes = self.theTheme.listSyntax()
self.theSyntaxes = self.mainTheme.listSyntax()
for syntaxFile, syntaxName in self.theSyntaxes:
self.guiSyntax.addItem(syntaxName, syntaxFile)
syntaxIdx = self.guiSyntax.findData(self.mainConf.guiSyntax)
@@ -225,7 +225,7 @@ class GuiPreferencesGeneral(QWidget):
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"),
@@ -344,16 +344,16 @@ class GuiPreferencesGeneral(QWidget):
class GuiPreferencesProjects(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, mainGui):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = 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
@@ -505,16 +505,16 @@ class GuiPreferencesProjects(QWidget):
class GuiPreferencesDocuments(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, mainGui):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = 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
@@ -527,7 +527,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"),
@@ -666,16 +666,16 @@ class GuiPreferencesDocuments(QWidget):
class GuiPreferencesEditor(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, mainGui):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = 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)
@@ -688,7 +688,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:
@@ -840,16 +840,16 @@ class GuiPreferencesEditor(QWidget):
class GuiPreferencesSyntax(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, mainGui):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = mainGui.mainTheme
# The Form
self.mainForm = QConfigLayout()
self.mainForm.setHelpTextStyle(self.theTheme.helpText)
self.mainForm.setHelpTextStyle(self.mainTheme.helpText)
self.setLayout(self.mainForm)
# Quotes & Dialogue
@@ -943,16 +943,16 @@ class GuiPreferencesSyntax(QWidget):
class GuiPreferencesAutomation(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, mainGui):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = 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
@@ -1100,16 +1100,16 @@ class GuiPreferencesAutomation(QWidget):
class GuiPreferencesQuotes(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, mainGui):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = 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
@@ -1117,7 +1117,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
+18 -18
View File
@@ -43,15 +43,15 @@ logger = logging.getLogger(__name__)
class GuiProjectDetails(PagedDialog):
def __init__(self, theParent):
PagedDialog.__init__(self, theParent)
def __init__(self, mainGui):
PagedDialog.__init__(self, mainGui)
logger.debug("Initialising GuiProjectDetails ...")
self.setObjectName("GuiProjectDetails")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theParent.theProject
self.mainGui = mainGui
self.theProject = mainGui.theProject
self.setWindowTitle(self.tr("Project Details"))
@@ -66,8 +66,8 @@ class GuiProjectDetails(PagedDialog):
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"))
@@ -139,16 +139,16 @@ class GuiProjectDetails(PagedDialog):
class GuiProjectDetailsMain(QWidget):
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
def __init__(self, mainGui, theProject):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theProject
self.theTheme = theParent.theTheme
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)
@@ -271,18 +271,18 @@ class GuiProjectDetailsContents(QWidget):
C_PAGE = 3
C_PROG = 4
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
def __init__(self, mainGui, theProject):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theProject
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = mainGui.mainTheme
# Internal
self._theToC = []
iPx = self.theTheme.baseIconSize
iPx = self.mainTheme.baseIconSize
hPx = self.mainConf.pxInt(12)
vPx = self.mainConf.pxInt(4)
pOptions = self.theProject.options
@@ -469,7 +469,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}")
+10 -10
View File
@@ -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):
QDialog.__init__(self, 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:
+31 -31
View File
@@ -43,15 +43,15 @@ logger = logging.getLogger(__name__)
class GuiProjectSettings(PagedDialog):
def __init__(self, theParent):
PagedDialog.__init__(self, theParent)
def __init__(self, mainGui):
PagedDialog.__init__(self, mainGui)
logger.debug("Initialising GuiProjectSettings ...")
self.setObjectName("GuiProjectSettings")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theProject = theParent.theProject
self.mainGui = mainGui
self.theProject = mainGui.theProject
self.theProject.countStatus()
self.setWindowTitle(self.tr("Project Settings"))
@@ -67,10 +67,10 @@ class GuiProjectSettings(PagedDialog):
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.mainGui, self.theProject)
self.tabStatus = GuiProjectEditStatus(self.mainGui, self.theProject, True)
self.tabImport = GuiProjectEditStatus(self.mainGui, self.theProject, False)
self.tabReplace = GuiProjectEditReplace(self.mainGui, self.theProject)
self.addTab(self.tabMain, self.tr("Settings"))
self.addTab(self.tabStatus, self.tr("Status"))
@@ -121,7 +121,7 @@ class GuiProjectSettings(PagedDialog):
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()
@@ -166,22 +166,22 @@ class GuiProjectSettings(PagedDialog):
class GuiProjectEditMain(QWidget):
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
def __init__(self, mainGui, theProject):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.mainGui = mainGui
self.theProject = 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)
@@ -216,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()
@@ -256,13 +256,13 @@ class GuiProjectEditStatus(QWidget):
COL_ROLE = Qt.UserRole + 1
NUM_ROLE = Qt.UserRole + 2
def __init__(self, theParent, theProject, isStatus):
QWidget.__init__(self, theParent)
def __init__(self, mainGui, theProject, isStatus):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.mainGui = mainGui
self.theProject = theProject
self.theTheme = theParent.theTheme
self.mainTheme = mainGui.mainTheme
if isStatus:
self.theStatus = self.theProject.statusItems
@@ -281,7 +281,7 @@ class GuiProjectEditStatus(QWidget):
self.colChanged = False
self.selColour = QColor(100, 100, 100)
self.iPx = self.theTheme.baseIconSize
self.iPx = self.mainTheme.baseIconSize
# The List
# ========
@@ -300,16 +300,16 @@ class GuiProjectEditStatus(QWidget):
# 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.theTheme.getIcon("up"), "")
self.upButton = QPushButton(self.mainTheme.getIcon("up"), "")
self.upButton.clicked.connect(lambda: self._moveItem(-1))
self.dnButton = QPushButton(self.theTheme.getIcon("down"), "")
self.dnButton = QPushButton(self.mainTheme.getIcon("down"), "")
self.dnButton.clicked.connect(lambda: self._moveItem(1))
# Edit Form
@@ -411,7 +411,7 @@ class GuiProjectEditStatus(QWidget):
if selItem is not None:
iRow = self.listBox.indexOfTopLevelItem(selItem)
if selItem.data(self.COL_LABEL, self.NUM_ROLE) > 0:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Cannot delete a status item that is in use."
), nwAlert.ERROR)
else:
@@ -527,12 +527,12 @@ class GuiProjectEditReplace(QWidget):
COL_KEY = 0
COL_REPL = 1
def __init__(self, theParent, theProject):
QWidget.__init__(self, theParent)
def __init__(self, mainGui, theProject):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = mainGui.mainTheme
self.theProject = theProject
self.arChanged = False
@@ -563,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
+2 -2
View File
@@ -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='"'):
QDialog.__init__(self, parent=parent)
self.mainConf = novelwriter.CONFIG
+5 -5
View File
@@ -43,14 +43,14 @@ logger = logging.getLogger(__name__)
class GuiUpdates(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
def __init__(self, mainGui):
QDialog.__init__(self, 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)
+9 -9
View File
@@ -42,16 +42,16 @@ logger = logging.getLogger(__name__)
class GuiWordList(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
def __init__(self, mainGui):
QDialog.__init__(self, mainGui)
logger.debug("Initialising GuiWordList ...")
self.setObjectName("GuiWordList")
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
self.setWindowTitle(self.tr("Project Word List"))
@@ -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
+6 -6
View File
@@ -23,9 +23,9 @@ 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.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
@@ -37,9 +37,9 @@ __all__ = [
"GuiItemDetails",
"GuiMainMenu",
"GuiMainStatus",
"GuiNovelTree",
"GuiOutline",
"GuiProjectTree",
"GuiNovelView",
"GuiOutlineView",
"GuiProjectView",
"GuiTheme",
"GuiViewsBar",
]
+4 -4
View File
@@ -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):
QDialog.__init__(self, parent=parent)
self._tabBar = VerticalTabBar(self)
self._tabBar.setExpanding(False)
@@ -426,8 +426,8 @@ class PagedDialog(QDialog):
class VerticalTabBar(QTabBar):
def __init__(self, theParent=None):
QTabBar.__init__(self, parent=theParent)
def __init__(self, parent=None):
QTabBar.__init__(self, parent=parent)
self._mW = novelwriter.CONFIG.pxInt(150)
return
+81 -79
View File
@@ -72,16 +72,16 @@ class GuiDocEditor(QTextEdit):
docCountsChanged = pyqtSignal(str, int, int, int)
loadDocumentTagRequest = pyqtSignal(str, Enum)
def __init__(self, theParent):
QTextEdit.__init__(self, theParent)
def __init__(self, mainGui):
QTextEdit.__init__(self, mainGui)
logger.debug("Initialising GuiDocEditor ...")
# 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
self._nwDocument = None
self._nwItem = None
@@ -124,7 +124,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)
@@ -239,10 +239,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:
@@ -257,14 +257,14 @@ 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()
@@ -341,7 +341,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."
@@ -417,7 +417,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)
)
@@ -442,7 +442,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."
@@ -488,7 +488,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 "
@@ -499,7 +499,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)
@@ -511,18 +511,20 @@ class GuiDocEditor(QTextEdit):
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 +546,8 @@ class GuiDocEditor(QTextEdit):
sH = hBar.height() if hBar.isVisible() else 0
tM = cM
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, cM)
tB = self.frameWidth()
@@ -704,7 +706,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)
@@ -714,7 +716,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:
@@ -742,7 +744,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
@@ -1085,7 +1087,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(
@@ -1245,7 +1247,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
@@ -1302,7 +1304,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
@@ -1381,7 +1383,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()
@@ -1401,7 +1403,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()
@@ -1645,7 +1647,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
@@ -2187,9 +2189,9 @@ class GuiDocEditSearch(QFrame):
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
@@ -2200,9 +2202,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)
@@ -2236,38 +2238,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)
@@ -2276,7 +2278,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)
@@ -2285,7 +2287,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)
@@ -2300,12 +2302,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)
@@ -2424,7 +2426,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()
@@ -2575,13 +2577,13 @@ class GuiDocEditHeader(QWidget):
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
@@ -2598,17 +2600,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)
@@ -2619,7 +2621,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)
@@ -2630,7 +2632,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)
@@ -2641,7 +2643,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)
@@ -2684,9 +2686,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)
@@ -2733,10 +2735,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
##
@@ -2746,7 +2748,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):
@@ -2758,7 +2760,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)
@@ -2768,7 +2770,7 @@ class GuiDocEditHeader(QWidget):
def _minmaxDocument(self):
"""Switch on or off Focus Mode.
"""
self.theParent.toggleFocusMode()
self.mainGui.toggleFocusMode()
return
##
@@ -2779,7 +2781,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
@@ -2799,22 +2801,22 @@ class GuiDocEditFooter(QWidget):
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._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)
@@ -2837,7 +2839,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)
@@ -2853,7 +2855,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)
@@ -2905,9 +2907,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)
+19 -19
View File
@@ -46,16 +46,16 @@ class GuiDocHighlighter(QSyntaxHighlighter):
BLOCK_META = 2
BLOCK_TITLE = 4
def __init__(self, theDoc, theParent, spEnchant):
def __init__(self, theDoc, mainGui, spEnchant):
QSyntaxHighlighter.__init__(self, theDoc)
logger.debug("Initialising GuiDocHighlighter ...")
self.mainConf = novelwriter.CONFIG
self.theDoc = theDoc
self.spEnchant = spEnchant
self.theParent = theParent
self.theTheme = theParent.theTheme
self.theProject = theParent.theProject
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),
@@ -288,7 +288,7 @@ class GuiDocHighlighter(QSyntaxHighlighter):
if theText.startswith("@"): # Keywords and commands
self.setCurrentBlockState(self.BLOCK_META)
pIndex = self.theProject.index
tItem = self.theParent.theProject.tree[self.theHandle]
tItem = self.mainGui.theProject.tree[self.theHandle]
isValid, theBits, thePos = pIndex.scanThis(theText)
isGood = pIndex.checkThese(theBits, tItem)
if isValid:
+70 -70
View File
@@ -53,16 +53,16 @@ class GuiDocViewer(QTextBrowser):
loadDocumentTagRequest = pyqtSignal(str, Enum)
def __init__(self, theParent):
QTextBrowser.__init__(self, theParent)
def __init__(self, mainGui):
QTextBrowser.__init__(self, 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
@@ -118,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()
@@ -221,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.
@@ -539,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)
@@ -714,14 +714,14 @@ class GuiDocViewHeader(QWidget):
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
@@ -738,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)
@@ -759,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)
@@ -770,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)
@@ -781,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)
@@ -824,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)
@@ -882,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
@@ -901,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
@@ -921,26 +921,26 @@ class GuiDocViewFooter(QWidget):
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)
@@ -952,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)
@@ -1039,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)
@@ -1084,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)
@@ -1140,14 +1140,14 @@ class GuiDocViewFooter(QWidget):
class GuiDocViewDetails(QScrollArea):
def __init__(self, theParent):
QScrollArea.__init__(self, theParent)
def __init__(self, mainGui):
QScrollArea.__init__(self, 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)
@@ -1156,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
@@ -1181,7 +1181,7 @@ 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.theProject.index.getBackReferenceList(tHandle)
@@ -1209,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
+15 -15
View File
@@ -38,14 +38,14 @@ logger = logging.getLogger(__name__)
class GuiItemDetails(QWidget):
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, mainGui):
QWidget.__init__(self, 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 +54,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)
@@ -181,8 +181,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)
@@ -238,7 +238,7 @@ class GuiItemDetails(QWidget):
return
self._itemHandle = tHandle
iPx = int(round(0.8*self.theTheme.baseIconSize))
iPx = int(round(0.8*self.mainTheme.baseIconSize))
# Label
# =====
@@ -267,7 +267,7 @@ class GuiItemDetails(QWidget):
# 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]))
@@ -275,7 +275,7 @@ class GuiItemDetails(QWidget):
# ======
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
usageIcon = self.theTheme.getItemIcon(
usageIcon = self.mainTheme.getItemIcon(
nwItem.itemType, nwItem.itemClass, nwItem.itemLayout, hLevel
)
self.usageIcon.setPixmap(usageIcon.pixmap(iPx, iPx))
@@ -296,7 +296,7 @@ class GuiItemDetails(QWidget):
return
@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.
"""
+54 -107
View File
@@ -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):
QMenuBar.__init__(self, 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,11 +61,9 @@ 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")
@@ -96,7 +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)
self.mainGui.docEditor.toggleSpellCheck(None)
return True
def _openWebsite(self, theUrl):
@@ -125,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
@@ -152,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))
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.deleteItem(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
@@ -233,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
@@ -244,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))
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
@@ -274,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
@@ -288,23 +235,23 @@ 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.aMergeDocs.triggered.connect(lambda: self.mainGui.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
@@ -377,7 +324,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
@@ -386,7 +333,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
@@ -395,7 +342,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
@@ -404,7 +351,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
@@ -413,13 +360,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
@@ -429,14 +376,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
@@ -641,7 +588,7 @@ class GuiMainMenu(QMenuBar):
# Insert > Placeholder Text
self.aLipsumText = QAction(self.tr("Placeholder Text"), self)
self.aLipsumText.triggered.connect(lambda: self.theParent.showLoremIpsumDialog())
self.aLipsumText.triggered.connect(lambda: self.mainGui.showLoremIpsumDialog())
self.insertMenu.addAction(self.aLipsumText)
return
@@ -805,7 +752,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
@@ -814,7 +761,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
@@ -823,7 +770,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
@@ -832,13 +779,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
@@ -860,12 +807,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
@@ -874,7 +821,7 @@ 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 > Separator
@@ -888,20 +835,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
@@ -915,13 +862,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
@@ -968,7 +915,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
+414 -125
View File
@@ -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 20182020, Veronica Berglyd Olsen
@@ -26,84 +28,376 @@ 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.QtGui import QPalette
from PyQt5.QtCore import Qt, QSize, pyqtSlot, pyqtSignal
from PyQt5.QtWidgets import (
QTreeWidget, QTreeWidgetItem, QAbstractItemView, QFrame
QAbstractItemView, QActionGroup, QFrame, QHBoxLayout, QHeaderView, QLabel,
QMenu, QSizePolicy, QToolButton, QTreeWidget, QTreeWidgetItem, QVBoxLayout,
QWidget
)
from novelwriter.enum import nwDocMode, nwItemClass
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):
QWidget.__init__(self, mainGui)
self.mainGui = mainGui
self.theProject = mainGui.theProject
# Build GUI
self.novelTree = GuiNovelTree(self)
self.novelBar = GuiNovelToolBar(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
return
##
# Methods
##
def initSettings(self):
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 opening 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 setFocus(self):
"""Forward the set focus call 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):
QTreeWidget.__init__(self, 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(3)
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_LAST = 2
def __init__(self, theParent):
QTreeWidget.__init__(self, theParent)
def __init__(self, novelView):
QTreeWidget.__init__(self, novelView)
logger.debug("Initialising GuiNovelTree ...")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.theProject = theParent.theProject
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
# 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
self.setFrameStyle(QFrame.NoFrame)
# =========
iPx = self.mainTheme.baseIconSize
cMg = self.mainConf.pxInt(6)
self.setIconSize(QSize(iPx, iPx))
self.setIndentation(iPx)
self.setFrameStyle(QFrame.NoFrame)
self.setHeaderHidden(True)
self.setIndentation(0)
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.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_LAST, 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),
]
# Connect signals
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
@@ -119,11 +413,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()
@@ -131,12 +433,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.theProject.index.indexChangedSince(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
@@ -146,7 +451,8 @@ class GuiNovelTree(QTreeWidget):
if selItem:
titleKey = selItem[0].data(self.C_TITLE, Qt.UserRole)[2]
self._populateTree()
self._populateTree(rootHandle)
self.theProject.setLastNovelViewed(rootHandle)
if titleKey is not None and titleKey in self._treeMap:
self._treeMap[titleKey].setSelected(True)
@@ -162,15 +468,6 @@ class GuiNovelTree(QTreeWidget):
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.
@@ -184,6 +481,17 @@ class GuiNovelTree(QTreeWidget):
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_LAST, colType == NovelTreeColumn.HIDDEN)
if doRefresh:
self.refreshTree(rootHandle=self.theProject.lastNovel, overRide=True)
return
##
# Events
##
@@ -209,113 +517,94 @@ class GuiNovelTree(QTreeWidget):
if tHandle is None:
return
self.theParent.viewDocument(tHandle)
self.novelView.openDocumentRequest.emit(tHandle, nwDocMode.VIEW, -1, "")
return
##
# Slots
# Private Slots
##
def _treeDoubleClick(self, tItem, tCol):
@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.theProject.index.novelStructure(skipExcl=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()
theData = (tHandle, sTitle[1:].lstrip("0"), tKey)
tLevel = novIdx.level
if tLevel == "H1":
self.addTopLevelItem(tItem)
currTitle = tItem
currChapter = None
currScene = None
newItem.setData(self.C_TITLE, Qt.DecorationRole, self._pIndent[iLevel])
newItem.setText(self.C_TITLE, novIdx.title)
newItem.setData(self.C_TITLE, Qt.UserRole, theData)
newItem.setFont(self.C_TITLE, self._hFonts[iLevel])
newItem.setText(self.C_WORDS, f"{novIdx.wordCount:n}")
newItem.setTextAlignment(self.C_WORDS, Qt.AlignRight)
elif tLevel == "H2":
if currTitle is None:
self.addTopLevelItem(tItem)
else:
currTitle.addChild(tItem)
currChapter = tItem
currScene = None
lastText, toolTip = self._getLastColumnText(tHandle, sTitle)
newItem.setText(self.C_LAST, lastText)
if lastText:
newItem.setToolTip(self.C_LAST, toolTip)
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._treeMap[tKey] = newItem
self.addTopLevelItem(newItem)
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)
wC = int(novIdx.wordCount)
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)
if self._lastCol == NovelTreeColumn.HIDDEN:
return "", ""
theRefs = self.theProject.index.getReferences(tHandle, sTitle)
newItem.setText(self.C_POV, ", ".join(theRefs[nwKeyWords.POV_KEY]))
if self._lastCol == NovelTreeColumn.POV:
newText = ", ".join(theRefs[nwKeyWords.POV_KEY])
return newText, f"{self._povLabel}: {newText}"
return newItem
elif self._lastCol == NovelTreeColumn.FOCUS:
newText = ", ".join(theRefs[nwKeyWords.FOCUS_KEY])
return newText, f"{self._focLabel}: {newText}"
elif self._lastCol == NovelTreeColumn.PLOT:
newText = ", ".join(theRefs[nwKeyWords.PLOT_KEY])
return newText, f"{self._pltLabel}: {newText}"
return "", ""
# END Class GuiNovelTree
+48 -45
View File
@@ -4,9 +4,9 @@ novelWriter GUI Project Outline
GUI class for the project outline view
File History:
Created: 2022-05-15 [1.7b1] GuiOutline
Created: 2022-05-15 [1.7b1] GuiOutlineView
Created: 2022-05-22 [1.7b1] GuiOutlineToolBar
Created: 2019-11-16 [0.4.1] GuiOutlineView
Created: 2019-11-16 [0.4.1] GuiOutlineTree
Created: 2019-11-16 [0.4.1] GuiOutlineHeaderMenu
Created: 2020-06-02 [0.7.0] GuiOutlineDetails
@@ -52,22 +52,23 @@ from novelwriter.constants import trConst, nwKeyWords, nwLabels
logger = logging.getLogger(__name__)
class GuiOutline(QWidget):
class GuiOutlineView(QWidget):
loadDocumentTagRequest = pyqtSignal(str, Enum)
def __init__(self, theParent):
QWidget.__init__(self, theParent)
def __init__(self, mainGui):
QWidget.__init__(self, mainGui)
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.mainConf = novelwriter.CONFIG
self.mainGui = mainGui
# Build GUI
self.outlineBar = GuiOutlineToolBar(self)
self.outlineView = GuiOutlineView(self)
self.outlineTree = GuiOutlineTree(self)
self.outlineData = GuiOutlineDetails(self)
self.splitOutline = QSplitter(Qt.Vertical)
self.splitOutline.addWidget(self.outlineView)
self.splitOutline.addWidget(self.outlineTree)
self.splitOutline.addWidget(self.outlineData)
self.splitOutline.setSizes(self.mainConf.getOutlinePanePos())
@@ -80,14 +81,14 @@ class GuiOutline(QWidget):
self.setLayout(self.outerBox)
# Connect Signals
self.outlineView.hiddenStateChanged.connect(self._updateMenuColumns)
self.outlineView.activeItemChanged.connect(self.outlineData.showItem)
self.outlineTree.hiddenStateChanged.connect(self._updateMenuColumns)
self.outlineTree.activeItemChanged.connect(self.outlineData.showItem)
self.outlineData.itemTagClicked.connect(self._tagClicked)
self.outlineBar.loadNovelRootRequest.connect(self._rootItemChanged)
self.outlineBar.viewColumnToggled.connect(self.outlineView.menuColumnToggled)
self.outlineBar.viewColumnToggled.connect(self.outlineTree.menuColumnToggled)
# Function Mappings
self.getSelectedHandle = self.outlineView.getSelectedHandle
self.getSelectedHandle = self.outlineTree.getSelectedHandle
return
@@ -103,24 +104,24 @@ class GuiOutline(QWidget):
return
def initOutline(self):
self.outlineView.initOutline()
self.outlineTree.initOutline()
self.outlineData.initDetails()
return
def closeOutline(self):
self.outlineView.closeOutline()
self.outlineTree.closeOutline()
self.outlineData.updateClasses()
return
def refreshView(self, overRide=False, novelChanged=False):
self.outlineView.refreshTree(overRide=overRide, novelChanged=novelChanged)
self.outlineTree.refreshTree(overRide=overRide, novelChanged=novelChanged)
return
def treeFocus(self):
return self.outlineView.hasFocus()
def treeHasFocus(self):
return self.outlineTree.hasFocus()
def setTreeFocus(self):
return self.outlineView.setFocus()
return self.outlineTree.setFocus()
##
# Public Slots
@@ -144,7 +145,7 @@ class GuiOutline(QWidget):
checkboxes whenever a signal is received that the hidden state
of columns has changed.
"""
self.outlineBar.setColumnHiddenState(self.outlineView.hiddenColumns)
self.outlineBar.setColumnHiddenState(self.outlineTree.hiddenColumns)
return
@pyqtSlot(str)
@@ -159,10 +160,10 @@ class GuiOutline(QWidget):
def _rootItemChanged(self, handle):
"""The root novel handle has changed or needs to be refreshed.
"""
self.outlineView.refreshTree(rootHandle=(handle or None), overRide=True)
self.outlineTree.refreshTree(rootHandle=(handle or None), overRide=True)
return
# END Class GuiOutline
# END Class GuiOutlineView
class GuiOutlineToolBar(QToolBar):
@@ -176,9 +177,9 @@ class GuiOutlineToolBar(QToolBar):
logger.debug("Initialising GuiOutlineToolBar ...")
self.mainConf = novelwriter.CONFIG
self.theParent = theOutline.theParent
self.theProject = theOutline.theParent.theProject
self.theTheme = theOutline.theParent.theTheme
self.mainGui = theOutline.mainGui
self.theProject = theOutline.mainGui.theProject
self.mainTheme = theOutline.mainGui.mainTheme
iPx = self.mainConf.pxInt(22)
mPx = self.mainConf.pxInt(12)
@@ -201,7 +202,7 @@ class GuiOutlineToolBar(QToolBar):
# Actions
self.aRefresh = QAction(self.tr("Refresh"), self)
self.aRefresh.setIcon(self.theTheme.getIcon("refresh"))
self.aRefresh.setIcon(self.mainTheme.getIcon("refresh"))
self.aRefresh.triggered.connect(self._refreshRequested)
# Column Menu
@@ -211,7 +212,7 @@ class GuiOutlineToolBar(QToolBar):
)
self.tbColumns = QToolButton(self)
self.tbColumns.setIcon(self.theTheme.getIcon("menu"))
self.tbColumns.setIcon(self.mainTheme.getIcon("menu"))
self.tbColumns.setMenu(self.mColumns)
self.tbColumns.setPopupMode(QToolButton.InstantPopup)
@@ -225,6 +226,8 @@ class GuiOutlineToolBar(QToolBar):
logger.debug("GuiOutlineToolBar initialisation complete")
return
##
# Methods
##
@@ -233,7 +236,7 @@ class GuiOutlineToolBar(QToolBar):
"""Fill the novel combo box with a list of all novel folders.
"""
self.novelValue.clear()
tIcon = self.theTheme.getIcon(nwLabels.CLASS_ICON[nwItemClass.NOVEL])
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.insertSeparator(self.novelValue.count())
@@ -268,7 +271,7 @@ class GuiOutlineToolBar(QToolBar):
# END Class GuiOutlineToolBar
class GuiOutlineView(QTreeWidget):
class GuiOutlineTree(QTreeWidget):
DEF_WIDTH = {
nwOutline.TITLE: 200,
@@ -316,12 +319,12 @@ class GuiOutlineView(QTreeWidget):
def __init__(self, theOutline):
QTreeWidget.__init__(self, theOutline)
logger.debug("Initialising GuiOutlineView ...")
logger.debug("Initialising GuiOutlineTree ...")
self.mainConf = novelwriter.CONFIG
self.theParent = theOutline.theParent
self.theProject = theOutline.theParent.theProject
self.theTheme = theOutline.theParent.theTheme
self.mainGui = theOutline.mainGui
self.theProject = theOutline.mainGui.theProject
self.mainTheme = theOutline.mainGui.mainTheme
self.setFrameStyle(QFrame.NoFrame)
self.setSelectionBehavior(QAbstractItemView.SelectRows)
@@ -331,7 +334,7 @@ class GuiOutlineView(QTreeWidget):
self.itemDoubleClicked.connect(self._treeDoubleClick)
self.itemSelectionChanged.connect(self._itemSelected)
iPx = self.theTheme.baseIconSize
iPx = self.mainTheme.baseIconSize
self.setIconSize(QSize(iPx, iPx))
self.setIndentation(iPx)
@@ -352,7 +355,7 @@ class GuiOutlineView(QTreeWidget):
self.hiddenStateChanged.emit()
logger.debug("GuiOutlineView initialisation complete")
logger.debug("GuiOutlineTree initialisation complete")
return
@@ -461,7 +464,7 @@ class GuiOutlineView(QTreeWidget):
document editor.
"""
tHandle, tLine = self.getSelectedHandle()
self.theParent.openDocument(tHandle, tLine=tLine - 1, doScroll=True)
self.mainGui.openDocument(tHandle, tLine=tLine - 1, doScroll=True)
return
@pyqtSlot()
@@ -679,7 +682,7 @@ class GuiOutlineView(QTreeWidget):
hIcon = "doc_%s" % novIdx.level.lower()
hLevel = self.theProject.index.getHandleHeaderLevel(tHandle)
dIcon = self.theTheme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, hLevel)
dIcon = self.mainTheme.getItemIcon(nwItemType.FILE, None, nwItemLayout.DOCUMENT, hLevel)
cC = int(novIdx.charCount)
wC = int(novIdx.wordCount)
@@ -687,7 +690,7 @@ class GuiOutlineView(QTreeWidget):
newItem.setText(self._colIdx[nwOutline.TITLE], novIdx.title)
newItem.setData(self._colIdx[nwOutline.TITLE], Qt.UserRole, tHandle)
newItem.setIcon(self._colIdx[nwOutline.TITLE], self.theTheme.getIcon(hIcon))
newItem.setIcon(self._colIdx[nwOutline.TITLE], self.mainTheme.getIcon(hIcon))
newItem.setText(self._colIdx[nwOutline.LEVEL], novIdx.level)
newItem.setText(self._colIdx[nwOutline.LABEL], nwItem.itemName)
newItem.setIcon(self._colIdx[nwOutline.LABEL], dIcon)
@@ -714,7 +717,7 @@ class GuiOutlineView(QTreeWidget):
return newItem
# END Class GuiOutlineView
# END Class GuiOutlineTree
class GuiOutlineHeaderMenu(QMenu):
@@ -779,14 +782,14 @@ class GuiOutlineDetails(QScrollArea):
self.mainConf = novelwriter.CONFIG
self.theOutline = theOutline
self.theParent = theOutline.theParent
self.theProject = theOutline.theParent.theProject
self.theTheme = theOutline.theParent.theTheme
self.mainGui = theOutline.mainGui
self.theProject = theOutline.mainGui.theProject
self.mainTheme = theOutline.mainGui.mainTheme
# Sizes
minTitle = 30*self.theTheme.textNWidth
maxTitle = 40*self.theTheme.textNWidth
wCount = self.theTheme.getTextWidth("999,999")
minTitle = 30*self.mainTheme.textNWidth
maxTitle = 40*self.mainTheme.textNWidth
wCount = self.mainTheme.getTextWidth("999,999")
hSpace = int(self.mainConf.pxInt(10))
vSpace = int(self.mainConf.pxInt(4))
File diff suppressed because it is too large Load Diff
+13 -13
View File
@@ -41,22 +41,22 @@ logger = logging.getLogger(__name__)
class GuiMainStatus(QStatusBar):
def __init__(self, theParent):
QStatusBar.__init__(self, theParent)
def __init__(self, mainGui):
QStatusBar.__init__(self, 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)
+22 -16
View File
@@ -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()
@@ -472,16 +472,19 @@ class GuiIcons:
# 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_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 = {}
@@ -573,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)
+18 -18
View File
@@ -40,21 +40,21 @@ class GuiViewsBar(QToolBar):
viewChangeRequested = pyqtSignal(nwView)
def __init__(self, theParent):
QToolBar.__init__(self, theParent)
def __init__(self, mainGui):
QToolBar.__init__(self, mainGui)
logger.debug("Initialising GuiViewsBar ...")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = mainGui.mainTheme
# Style
iPx = self.mainConf.pxInt(22)
mPx = self.mainConf.pxInt(60)
lblFont = self.theTheme.guiFont
lblFont.setPointSizeF(0.65*self.theTheme.fontPointSize)
lblFont = self.mainTheme.guiFont
lblFont.setPointSizeF(0.65*self.mainTheme.fontPointSize)
self.setMovable(False)
self.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
@@ -70,54 +70,54 @@ class GuiViewsBar(QToolBar):
self.aProject = QAction(self.tr("Project"))
self.aProject.setFont(lblFont)
self.aProject.setToolTip(self.tr("Show project tree and editor"))
self.aProject.setIcon(self.theTheme.getIcon("view_editor"))
self.aProject.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.theTheme.getIcon("view_novel"))
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.theTheme.getIcon("view_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.theTheme.getIcon("view_build"))
self.aBuild.triggered.connect(lambda: self.theParent.showBuildProjectDialog())
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.theTheme.getIcon("proj_details"))
self.aDetails.triggered.connect(lambda: self.theParent.showProjectDetailsDialog())
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.theTheme.getIcon("proj_stats"))
self.aStats.triggered.connect(lambda: self.theParent.showWritingStatsDialog())
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.theParent.showProjectSettingsDialog())
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.theParent.showPreferencesDialog())
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.theTheme.getIcon("settings"))
self.tbSettings.setIcon(self.mainTheme.getIcon("settings"))
self.tbSettings.setMenu(self.mSettings)
self.tbSettings.setToolButtonStyle(Qt.ToolButtonTextUnderIcon)
self.tbSettings.setPopupMode(QToolButton.InstantPopup)
+108 -116
View File
@@ -40,7 +40,7 @@ from PyQt5.QtWidgets import (
from novelwriter.gui import (
GuiDocEditor, GuiDocViewDetails, GuiDocViewer, GuiItemDetails, GuiMainMenu,
GuiMainStatus, GuiNovelTree, GuiOutline, GuiProjectTree, GuiTheme,
GuiMainStatus, GuiNovelView, GuiOutlineView, GuiProjectView, GuiTheme,
GuiViewsBar
)
from novelwriter.dialogs import (
@@ -84,7 +84,7 @@ class GuiMain(QMainWindow):
# ============
# Core Classes and Settings
self.theTheme = GuiTheme()
self.mainTheme = GuiTheme()
self.theProject = NWProject(self)
self.hasProject = False
self.isFocusMode = False
@@ -104,42 +104,20 @@ class GuiMain(QMainWindow):
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.mainMenu = GuiMainMenu(self)
self.viewsBar = GuiViewsBar(self)
# Connect Signals Between Main Elements
self.viewsBar.viewChangeRequested.connect(self._changeView)
self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
self.treeView.treeItemChanged.connect(self.docEditor.updateDocInfo)
self.treeView.treeItemChanged.connect(self.docViewer.updateDocInfo)
self.treeView.treeItemChanged.connect(self.treeMeta.updateViewBox)
self.treeView.rootFolderChanged.connect(self.projView.updateRootItem)
self.docEditor.spellDictionaryChanged.connect(self.statusBar.setLanguage)
self.docEditor.docEditedStatusChanged.connect(self.statusBar.doUpdateDocumentStatus)
self.docEditor.docCountsChanged.connect(self.treeMeta.doUpdateCounts)
self.docEditor.docCountsChanged.connect(self.treeView.doUpdateCounts)
self.docEditor.loadDocumentTagRequest.connect(self._followTag)
self.docViewer.loadDocumentTagRequest.connect(self._followTag)
self.projView.loadDocumentTagRequest.connect(self._followTag)
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)
# Project Tree Stack
self.projStack = QStackedWidget()
self.projStack.addWidget(self.treeView)
self.projStack.addWidget(self.projView)
self.projStack.addWidget(self.novelView)
self.projStack.currentChanged.connect(self._projStackChanged)
@@ -149,7 +127,7 @@ class GuiMain(QMainWindow):
self.treeBox.setContentsMargins(0, 0, 0, 0)
self.treeBox.setSpacing(mPx)
self.treeBox.addWidget(self.projStack)
self.treeBox.addWidget(self.treeMeta)
self.treeBox.addWidget(self.itemDetails)
self.treePane.setLayout(self.treeBox)
# Splitter : Document Viewer / Document Meta
@@ -176,7 +154,7 @@ class GuiMain(QMainWindow):
# Main Stack : Editor / Outline
self.mainStack = QStackedWidget()
self.mainStack.addWidget(self.splitMain)
self.mainStack.addWidget(self.projView)
self.mainStack.addWidget(self.outlineView)
self.mainStack.currentChanged.connect(self._mainStackChanged)
# Indices of Splitter Widgets
@@ -189,15 +167,15 @@ class GuiMain(QMainWindow):
# Indices of Tab Widgets
self.idxEditorView = self.mainStack.indexOf(self.splitMain)
self.idxOutlineView = self.mainStack.indexOf(self.projView)
self.idxTreeView = self.projStack.indexOf(self.treeView)
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)
self.splitMain.setCollapsible(self.idxMain, False)
self.splitDocs.setCollapsible(self.idxEditor, False)
self.splitDocs.setCollapsible(self.idxViewer, True)
self.splitDocs.setCollapsible(self.idxViewer, False)
self.splitView.setCollapsible(self.idxViewDoc, False)
self.splitView.setCollapsible(self.idxViewMeta, False)
@@ -214,6 +192,34 @@ class GuiMain(QMainWindow):
self.setStatusBar(self.statusBar)
self.addToolBar(Qt.LeftToolBarArea, self.viewsBar)
# Connect Signals
# ===============
self.viewsBar.viewChangeRequested.connect(self._changeView)
self.projView.selectedItemChanged.connect(self.itemDetails.updateViewBox)
self.projView.openDocumentRequest.connect(self._openDocument)
self.projView.novelItemChanged.connect(self._treeNovelItemChanged)
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
# =======================
@@ -289,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.projView.clearOutline()
self.outlineView.clearOutline()
# General
self.statusBar.clearStatus()
@@ -359,7 +365,8 @@ class GuiMain(QMainWindow):
self.rebuildTrees()
self.saveProject()
self.docEditor.setDictionaries()
self.projView.updateRootItem(None)
self.outlineView.updateRootItem(None)
self.novelView.openProjectTasks()
self.rebuildIndex(beQuiet=True)
self.statusBar.setRefTime(self.theProject.projOpened)
self.statusBar.setProjectStatus(nwState.GOOD)
@@ -415,7 +422,8 @@ class GuiMain(QMainWindow):
if saveOK:
self.closeDocument()
self.docViewer.clearNavHistory()
self.projView.closeOutline()
self.outlineView.closeOutline()
self.novelView.closeProjectTasks()
self.theProject.closeProject(self.idleTime)
self.idleRefTime = time()
@@ -506,7 +514,8 @@ class GuiMain(QMainWindow):
self.docEditor.setDictionaries()
self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
self.statusBar.setRefTime(self.theProject.projOpened)
self.projView.updateRootItem(None)
self.outlineView.updateRootItem(None)
self.novelView.openProjectTasks()
self._updateStatusWordCount()
# Restore previously open documents, if any
@@ -539,7 +548,7 @@ class GuiMain(QMainWindow):
logger.error("No project open")
return False
self.treeView.saveTreeOrder()
self.projView.saveProjectTree()
if self.theProject.saveProject(autoSave=autoSave):
self.theProject.index.saveIndex()
@@ -584,7 +593,7 @@ class GuiMain(QMainWindow):
if changeFocus:
self.docEditor.setFocus()
self.theProject.setLastEdited(tHandle)
self.treeView.setSelectedHandle(tHandle, doScroll=doScroll)
self.projView.setSelectedHandle(tHandle, doScroll=doScroll)
else:
return False
@@ -650,7 +659,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")
@@ -787,12 +796,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.treeFocus():
tHandle, tLine = self.projView.getSelectedHandle()
elif self.outlineView.treeHasFocus():
tHandle, tLine = self.outlineView.getSelectedHandle()
else:
logger.warning("No item selected")
return False
@@ -802,7 +811,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:
@@ -813,17 +822,17 @@ 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.treeView.editTreeItem(tHandle)
return self.projView.renameTreeItem(tHandle)
return False
def rebuildTrees(self):
"""Rebuild the project tree.
"""
self.treeView.buildTree()
self.novelView.refreshTree()
self.projView.populateTree()
# self.novelView.refreshTree()
return
def requestNovelTreeRefresh(self):
@@ -845,7 +854,7 @@ class GuiMain(QMainWindow):
qApp.setOverrideCursor(QCursor(Qt.WaitCursor))
tStart = time()
self.treeView.saveTreeOrder()
self.projView.saveProjectTree()
self.theProject.index.clearIndex()
for tItem in self.theProject.tree:
@@ -855,8 +864,8 @@ class GuiMain(QMainWindow):
logger.verbose("Indexing '%s'", tItem.itemName)
if self.theProject.index.reIndexHandle(tItem.itemHandle):
# Update Word Counts
self.treeView.propagateCount(tItem.itemHandle, tItem.wordCount, countChildren=True)
self.treeView.setTreeItemValues(tItem.itemHandle)
self.projView.propagateCount(tItem.itemHandle, tItem.wordCount, countChildren=True)
self.projView.setTreeItemValues(tItem.itemHandle)
tEnd = time()
self.setStatus(
@@ -913,13 +922,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.projView.initSettings()
self.novelView.initSettings()
self.outlineView.initOutline()
self._updateStatusWordCount()
return
@@ -938,7 +947,7 @@ class GuiMain(QMainWindow):
logger.debug("Applying new project settings")
if dlgProj.spellChanged:
self.docEditor.setDictionaries()
self.treeMeta.refreshDetails()
self.itemDetails.refreshDetails()
self._updateWindowTitle(self.theProject.projName)
return True
@@ -1154,13 +1163,11 @@ class GuiMain(QMainWindow):
if not self.isFocusMode:
self.mainConf.setMainPanePos(self.splitMain.sizes())
self.mainConf.setDocPanePos(self.splitDocs.sizes())
self.mainConf.setOutlinePanePos(self.projView.splitSizes())
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())
@@ -1179,8 +1186,8 @@ class GuiMain(QMainWindow):
"""
if paneNo == nwWidget.TREE:
tabIdx = self.projStack.currentIndex()
if tabIdx == self.idxTreeView:
self.treeView.setFocus()
if tabIdx == self.idxProjView:
self.projView.setFocus()
elif tabIdx == self.idxNovelView:
self.novelView.setFocus()
elif paneNo == nwWidget.EDITOR:
@@ -1191,7 +1198,7 @@ class GuiMain(QMainWindow):
self.docViewer.setFocus()
elif paneNo == nwWidget.OUTLINE:
self._changeView(nwView.OUTLINE)
self.projView.setTreeFocus()
self.outlineView.setTreeFocus()
return
def closeDocEditor(self):
@@ -1468,6 +1475,17 @@ class GuiMain(QMainWindow):
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.
@@ -1478,14 +1496,14 @@ class GuiMain(QMainWindow):
elif view == nwView.PROJECT:
self.mainStack.setCurrentWidget(self.splitMain)
self.projStack.setCurrentWidget(self.treeView)
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.projView)
self.mainStack.setCurrentWidget(self.outlineView)
return
@@ -1531,32 +1549,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 toggle the expanded status.
"""
tHandle = self.treeView.getSelectedHandle()
if tHandle is not None:
tItem = self.theProject.tree[tHandle]
if tItem is None:
return
if tItem.itemType == nwItemType.FILE:
self.openDocument(tHandle, changeFocus=False, doScroll=False)
else:
self.treeView.toggleExpanded(tHandle)
return
@pyqtSlot()
def _treeNovelItemChanged(self):
"""Triggered when there is a change to a novel item in the
@@ -1565,7 +1557,7 @@ class GuiMain(QMainWindow):
if self.mainStack.currentIndex() == self.idxOutlineView:
logger.verbose("Novel tree changed while Outline tab active")
if self.hasProject:
self.projView.refreshView(novelChanged=True)
self.outlineView.refreshView(novelChanged=True)
return
@@ -1590,35 +1582,35 @@ class GuiMain(QMainWindow):
return
@pyqtSlot(int)
def _mainStackChanged(self, tabIndex):
def _mainStackChanged(self, stIndex):
"""Activated when the main window tab is changed.
"""
if tabIndex == self.idxEditorView:
logger.verbose("Editor tab activated")
elif tabIndex == self.idxOutlineView:
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.refreshView()
self.outlineView.refreshView()
return
@pyqtSlot(int)
def _projStackChanged(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 -20
View File
@@ -65,16 +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):
QDialog.__init__(self, mainGui)
logger.debug("Initialising GuiBuildNovel ...")
self.setObjectName("GuiBuildNovel")
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
self.htmlText = [] # List of html documents
self.htmlStyle = [] # List of html styles
@@ -93,7 +93,7 @@ class GuiBuildNovel(QDialog):
self.docView = GuiBuildNovelDocView(self, self.theProject)
hS = self.theTheme.fontPixelSize
hS = self.mainTheme.fontPixelSize
wS = 2*hS
# Title Formats
@@ -238,11 +238,11 @@ class GuiBuildNovel(QDialog):
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)
@@ -251,7 +251,7 @@ class GuiBuildNovel(QDialog):
)
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)
@@ -711,7 +711,7 @@ class GuiBuildNovel(QDialog):
bldObj.initDocument()
# Make sure the project and document is up to date
self.theParent.saveDocument()
self.mainGui.saveDocument()
self.buildProgress.setMaximum(len(self.theProject.tree))
self.buildProgress.setValue(0)
@@ -760,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)
@@ -1003,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)
@@ -1193,18 +1193,18 @@ class GuiBuildNovel(QDialog):
class GuiBuildNovelDocView(QTextBrowser):
def __init__(self, theParent, theProject):
QTextBrowser.__init__(self, theParent)
def __init__(self, mainGui, theProject):
QTextBrowser.__init__(self, 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())
@@ -1238,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)
+6 -6
View File
@@ -42,15 +42,15 @@ logger = logging.getLogger(__name__)
class GuiLipsum(QDialog):
def __init__(self, theParent):
QDialog.__init__(self, theParent)
def __init__(self, mainGui):
QDialog.__init__(self, mainGui)
logger.debug("Initialising GuiLipsum ...")
self.setObjectName("GuiLipsum")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = mainGui.mainTheme
self.setWindowTitle(self.tr("Insert Placeholder Text"))
@@ -61,7 +61,7 @@ class GuiLipsum(QDialog):
nPx = self.mainConf.pxInt(64)
vSp = self.mainConf.pxInt(4)
self.docIcon = QLabel()
self.docIcon.setPixmap(self.theParent.theTheme.getPixmap("proj_document", (nPx, nPx)))
self.docIcon.setPixmap(self.mainTheme.getPixmap("proj_document", (nPx, nPx)))
self.leftBox = QVBoxLayout()
self.leftBox.setSpacing(vSp)
@@ -129,7 +129,7 @@ class GuiLipsum(QDialog):
pCount = self.paraCount.value()
inText = "\n\n".join(lipsumText[0:pCount]) + "\n\n"
self.theParent.docEditor.insertText(inText)
self.mainGui.docEditor.insertText(inText)
return
+9 -9
View File
@@ -48,17 +48,17 @@ PAGE_FINAL = 4
class GuiProjectWizard(QWizard):
def __init__(self, theParent):
QWizard.__init__(self, theParent)
def __init__(self, mainGui):
QWizard.__init__(self, mainGui)
logger.debug("Initialising GuiProjectWizard ...")
self.setObjectName("GuiProjectWizard")
self.mainConf = novelwriter.CONFIG
self.theParent = theParent
self.theTheme = theParent.theTheme
self.mainGui = mainGui
self.mainTheme = mainGui.mainTheme
self.sideImage = self.theTheme.loadDecoration(
self.sideImage = self.mainTheme.loadDecoration(
"wiz-back", None, self.mainConf.pxInt(370)
)
self.setWizardStyle(QWizard.ModernStyle)
@@ -92,7 +92,7 @@ class ProjWizardIntroPage(QWizardPage):
self.mainConf = novelwriter.CONFIG
self.theWizard = theWizard
self.theTheme = theWizard.theTheme
self.mainTheme = theWizard.mainTheme
self.setTitle(self.tr("Create New Project"))
self.theText = QLabel(self.tr(
@@ -107,7 +107,7 @@ class ProjWizardIntroPage(QWizardPage):
"Peter Mitterhofer", "CC BY-SA 4.0"
))
lblFont = self.imgCredit.font()
lblFont.setPointSizeF(0.6*self.theTheme.fontPointSize)
lblFont.setPointSizeF(0.6*self.mainTheme.fontPointSize)
self.imgCredit.setFont(lblFont)
xW = self.mainConf.pxInt(300)
@@ -162,7 +162,7 @@ class ProjWizardFolderPage(QWizardPage):
self.mainConf = novelwriter.CONFIG
self.theWizard = theWizard
self.theTheme = theWizard.theTheme
self.mainTheme = theWizard.mainTheme
self.setTitle(self.tr("Select Project Folder"))
self.theText = QLabel(self.tr(
@@ -180,7 +180,7 @@ class ProjWizardFolderPage(QWizardPage):
self.projPath.setPlaceholderText(self.tr("Required"))
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.errLabel = QLabel("")
+21 -21
View File
@@ -57,16 +57,16 @@ class GuiWritingStats(QDialog):
FMT_JSON = 0
FMT_CSV = 1
def __init__(self, theParent):
QDialog.__init__(self, theParent)
def __init__(self, mainGui):
QDialog.__init__(self, mainGui)
logger.debug("Initialising GuiWritingStats ...")
self.setObjectName("GuiWritingStats")
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
self.logData = []
self.filterData = []
@@ -125,7 +125,7 @@ class GuiWritingStats(QDialog):
self.listBox.setSortingEnabled(True)
# Word Bar
self.barHeight = int(round(0.5*self.theTheme.fontPixelSize))
self.barHeight = int(round(0.5*self.mainTheme.fontPixelSize))
self.barWidth = self.mainConf.pxInt(200)
self.barImage = QPixmap(self.barHeight, self.barHeight)
self.barImage.fill(self.palette().highlight().color())
@@ -136,27 +136,27 @@ class GuiWritingStats(QDialog):
self.infoBox.setLayout(self.infoForm)
self.labelTotal = QLabel(formatTime(0))
self.labelTotal.setFont(self.theTheme.guiFontFixed)
self.labelTotal.setFont(self.mainTheme.guiFontFixed)
self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.labelIdleT = QLabel(formatTime(0))
self.labelIdleT.setFont(self.theTheme.guiFontFixed)
self.labelIdleT.setFont(self.mainTheme.guiFontFixed)
self.labelIdleT.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.labelFilter = QLabel(formatTime(0))
self.labelFilter.setFont(self.theTheme.guiFontFixed)
self.labelFilter.setFont(self.mainTheme.guiFontFixed)
self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.novelWords = QLabel("0")
self.novelWords.setFont(self.theTheme.guiFontFixed)
self.novelWords.setFont(self.mainTheme.guiFontFixed)
self.novelWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.notesWords = QLabel("0")
self.notesWords.setFont(self.theTheme.guiFontFixed)
self.notesWords.setFont(self.mainTheme.guiFontFixed)
self.notesWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.totalWords = QLabel("0")
self.totalWords.setFont(self.theTheme.guiFontFixed)
self.totalWords.setFont(self.mainTheme.guiFontFixed)
self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
lblTTime = QLabel(self.tr("Total Time:"))
@@ -183,7 +183,7 @@ class GuiWritingStats(QDialog):
self.infoForm.setRowStretch(6, 1)
# Filter Options
sPx = self.theTheme.baseIconSize
sPx = self.mainTheme.baseIconSize
self.filterBox = QGroupBox(self.tr("Filters"), self)
self.filterForm = QGridLayout(self)
@@ -411,11 +411,11 @@ class GuiWritingStats(QDialog):
# Report to user
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.mainGui.makeAlert([
self.tr("Failed to write {0} file.").format(textFmt), errMsg
], nwAlert.ERROR)
@@ -478,7 +478,7 @@ class GuiWritingStats(QDialog):
self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle))
except Exception as exc:
self.theParent.makeAlert(self.tr(
self.mainGui.makeAlert(self.tr(
"Failed to read session log file."
), nwAlert.ERROR, exception=exc)
return False
@@ -605,13 +605,13 @@ class GuiWritingStats(QDialog):
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
newItem.setTextAlignment(self.C_BAR, Qt.AlignLeft | Qt.AlignVCenter)
newItem.setFont(self.C_TIME, self.theTheme.guiFontFixed)
newItem.setFont(self.C_LENGTH, self.theTheme.guiFontFixed)
newItem.setFont(self.C_COUNT, self.theTheme.guiFontFixed)
newItem.setFont(self.C_TIME, self.mainTheme.guiFontFixed)
newItem.setFont(self.C_LENGTH, self.mainTheme.guiFontFixed)
newItem.setFont(self.C_COUNT, self.mainTheme.guiFontFixed)
if showIdleTime:
newItem.setFont(self.C_IDLE, self.theTheme.guiFontFixed)
newItem.setFont(self.C_IDLE, self.mainTheme.guiFontFixed)
else:
newItem.setFont(self.C_IDLE, self.theTheme.guiFont)
newItem.setFont(self.C_IDLE, self.mainTheme.guiFont)
self.listBox.addTopLevelItem(newItem)
self.timeFilter += sDiff
+1 -1
View File
@@ -1,7 +1,7 @@
%%~name: Chapter One
%%~path: e5e47ebf63b1c/a520879ca0b45
%%~kind: NOVEL/DOCUMENT
### Chapter One
## Chapter One
@pov: Jane
+1 -1
View File
@@ -3,4 +3,4 @@
%%~kind: NOVEL/DOCUMENT
# Part One
>> In the beginning … <<
>> In the beginning … <<
+21 -19
View File
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-05 15:03:00">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-15 13:07:17">
<project>
<name>Sample Project</name>
<title>Sample Project</title>
<author>Jane Smith</author>
<author>Jay Doh</author>
<saveCount>1334</saveCount>
<autoCount>225</autoCount>
<editTime>67746</editTime>
<saveCount>1371</saveCount>
<autoCount>236</autoCount>
<editTime>69222</editTime>
</project>
<settings>
<doBackup>False</doBackup>
@@ -15,8 +15,10 @@
<spellCheck>True</spellCheck>
<spellLang>None</spellLang>
<autoOutline>True</autoOutline>
<lastEdited>a520879ca0b45</lastEdited>
<lastEdited>636b6aa9b697b</lastEdited>
<lastViewed>636b6aa9b697b</lastViewed>
<lastNovel>7031beac91f75</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>1363</lastWordCount>
<novelWordCount>954</novelWordCount>
<notesWordCount>409</notesWordCount>
@@ -33,13 +35,13 @@
<section></section>
</titleFormat>
<status>
<entry key="sf12341" count="7" red="100" green="100" blue="100">New</entry>
<entry key="sf24ce6" count="1" red="200" green="50" blue="0">Notes</entry>
<entry key="sc24b8f" count="2" red="182" green="60" blue="0">Started</entry>
<entry key="s90e6c9" count="6" red="193" green="129" blue="0">1st Draft</entry>
<entry key="sd51c5b" count="1" red="193" green="129" blue="0">2nd Draft</entry>
<entry key="sf12341" count="4" red="100" green="100" blue="100">New</entry>
<entry key="sf24ce6" count="2" red="200" green="50" blue="0">Notes</entry>
<entry key="sc24b8f" count="3" red="182" green="60" blue="0">Started</entry>
<entry key="s90e6c9" count="7" red="193" green="129" blue="0">1st Draft</entry>
<entry key="sd51c5b" count="0" red="193" green="129" blue="0">2nd Draft</entry>
<entry key="s8ae72a" count="0" red="193" green="129" blue="0">3rd Draft</entry>
<entry key="s78ea90" count="0" red="58" green="180" blue="58">Finished</entry>
<entry key="s78ea90" count="1" red="58" green="180" blue="58">Finished</entry>
</status>
<importance>
<entry key="ia857f0" count="5" red="100" green="100" blue="100">None</entry>
@@ -63,34 +65,34 @@
</item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="26" wordCount="6" paraCount="1" cursorPos="36"/>
<name status="sf12341" import="ia857f0" exported="True">Part One</name>
<name status="s90e6c9" import="ia857f0" exported="True">Part One</name>
</item>
<item handle="6a2d6d5f4f401" parent="7031beac91f75" root="7031beac91f75" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" charCount="95" wordCount="18" paraCount="1" cursorPos="291"/>
<name status="sf24ce6" import="ia857f0" exported="True">Chapter One</name>
</item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="2687" wordCount="479" paraCount="14" cursorPos="61"/>
<meta expanded="False" charCount="2687" wordCount="479" paraCount="14" cursorPos="2183"/>
<name status="s90e6c9" import="ia857f0" exported="True">Making a Scene</name>
</item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="548" wordCount="108" paraCount="3" cursorPos="649"/>
<meta expanded="False" charCount="548" wordCount="108" paraCount="3" cursorPos="465"/>
<name status="s90e6c9" import="ia857f0" exported="True">Another Scene</name>
</item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="617" wordCount="101" paraCount="3" cursorPos="310"/>
<name status="sf12341" import="ia857f0" exported="True">Interlude</name>
<name status="s78ea90" import="ia857f0" exported="True">Interlude</name>
</item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="False" charCount="1909" wordCount="346" paraCount="7" cursorPos="0"/>
<name status="sd51c5b" import="ia857f0" exported="False">A Note on Structure</name>
<name status="sf24ce6" import="ia857f0" exported="False">A Note on Structure</name>
</item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="True" charCount="139" wordCount="28" paraCount="1" cursorPos="343"/>
<meta expanded="True" charCount="139" wordCount="28" paraCount="1" cursorPos="188"/>
<name status="s90e6c9" import="ia857f0" exported="True">Chapter Two</name>
</item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="189" wordCount="37" paraCount="1" cursorPos="224"/>
<meta expanded="False" charCount="189" wordCount="37" paraCount="1" cursorPos="0"/>
<name status="s90e6c9" import="ia857f0" exported="True">We Found John!</name>
</item>
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
@@ -99,7 +101,7 @@
</item>
<item handle="bacb7059e3083" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="27" wordCount="5" paraCount="1" cursorPos="100"/>
<name status="sf12341" import="ia857f0" exported="True">Title Page</name>
<name status="sc24b8f" import="ia857f0" exported="True">Title Page</name>
</item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="299" wordCount="55" paraCount="2" cursorPos="104"/>
+2
View File
@@ -16,6 +16,8 @@
<autoOutline>True</autoOutline>
<lastEdited>7a992350f3eb6</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>3847</lastWordCount>
<novelWordCount>3109</novelWordCount>
<notesWordCount>738</notesWordCount>
+2
View File
@@ -17,6 +17,8 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>10</lastWordCount>
<novelWordCount>10</novelWordCount>
<notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:49:23">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project>
<name>Test Custom</name>
<title>Test Novel</title>
@@ -17,6 +17,8 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:48:40">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project>
<name>Test Custom</name>
<title>Test Novel</title>
@@ -17,6 +17,8 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:08:21">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project>
<name>New Project</name>
<title>New Novel</title>
@@ -16,9 +16,11 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>2</lastWordCount>
<novelWordCount>1</novelWordCount>
<notesWordCount>1</notesWordCount>
<autoReplace/>
<titleFormat>
<title>%title%</title>
@@ -28,19 +30,19 @@
<section></section>
</titleFormat>
<status>
<entry key="s000008" count="5" red="100" green="100" blue="100">New</entry>
<entry key="s000008" count="7" red="100" green="100" blue="100">New</entry>
<entry key="s000009" count="0" red="200" green="50" blue="0">Note</entry>
<entry key="s00000a" count="0" red="200" green="150" blue="0">Draft</entry>
<entry key="s00000b" count="0" red="50" green="200" blue="0">Finished</entry>
</status>
<importance>
<entry key="i00000c" count="3" red="100" green="100" blue="100">New</entry>
<entry key="i00000c" count="4" red="100" green="100" blue="100">New</entry>
<entry key="i00000d" count="0" red="200" green="50" blue="0">Minor</entry>
<entry key="i00000e" count="0" red="200" green="150" blue="0">Major</entry>
<entry key="i00000f" count="0" red="50" green="200" blue="0">Main</entry>
</importance>
</settings>
<content count="10">
<content count="11">
<item handle="0000000000010" parent="None" root="0000000000010" order="0" type="ROOT" class="NOVEL">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Novel</name>
@@ -73,13 +75,17 @@
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" exported="True">New Scene</name>
</item>
<item handle="0000000000028" parent="31489056e0916" root="None" order="0" type="FILE" class="NO_CLASS" layout="NO_LAYOUT">
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="None" import="None" exported="True">Hello</name>
<item handle="0000000000028" parent="0000000000015" root="0000000000010" order="0" type="FOLDER" class="NOVEL">
<meta expanded="False"/>
<name status="s000008" import="i00000c">Stuff</name>
</item>
<item handle="0000000000029" parent="71ee45a3c0db9" root="None" order="0" type="FILE" class="NO_CLASS" layout="NO_LAYOUT">
<meta expanded="False" charCount="0" wordCount="0" paraCount="0" cursorPos="0"/>
<name status="None" import="None" exported="True">Jane</name>
<item handle="0000000000029" parent="0000000000015" root="0000000000010" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="False" charCount="5" wordCount="1" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" exported="True">Hello</name>
</item>
<item handle="000000000002a" parent="0000000000012" root="0000000000012" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="False" charCount="4" wordCount="1" paraCount="0" cursorPos="0"/>
<name status="s000008" import="i00000c" exported="True">Jane</name>
</item>
</content>
</novelWriterXML>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:50:26">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project>
<name>New Project</name>
<title></title>
@@ -15,6 +15,8 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 22:07:28">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:51">
<project>
<name>New Project</name>
<title>New Novel</title>
@@ -16,6 +16,8 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>0</lastWordCount>
<novelWordCount>0</novelWordCount>
<notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:27:16">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:52:44">
<project>
<name>New Project</name>
<title>New Novel</title>
@@ -16,6 +16,8 @@
<autoOutline>True</autoOutline>
<lastEdited>000000000000f</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>0000000000008</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>129</lastWordCount>
<novelWordCount>102</novelWordCount>
<notesWordCount>27</notesWordCount>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:25:41">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:57">
<project>
<name>New Project</name>
<title>New Novel</title>
@@ -16,6 +16,8 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>9</lastWordCount>
<novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-05-21 17:31:17">
<novelWriterXML appVersion="1.7-beta1" hexVersion="0x010700b1" fileVersion="1.4" timeStamp="2022-06-12 17:45:53">
<project>
<name>Project Name</name>
<title>Project Title</title>
@@ -17,6 +17,8 @@
<autoOutline>True</autoOutline>
<lastEdited>None</lastEdited>
<lastViewed>None</lastViewed>
<lastNovel>None</lastNovel>
<lastOutline>None</lastOutline>
<lastWordCount>9</lastWordCount>
<novelWordCount>9</novelWordCount>
<notesWordCount>0</notesWordCount>
-3
View File
@@ -232,9 +232,6 @@ def testCoreIndex_CheckThese(mockGUI, fncDir, mockRnd):
assert theIndex.getHandleHeaderLevel(cHandle) == "H1"
assert theIndex.getHandleHeaderLevel(nHandle) == "H1"
assert theIndex.getHandleHeaderIntLevel(cHandle) == 1
assert theIndex.getHandleHeaderIntLevel(nHandle) == 1
assert theIndex.getHandleHeaderIntLevel("stuff") == 0
# Zero Items
assert theIndex.checkThese([], cItem) == []
+81 -81
View File
@@ -29,10 +29,14 @@ from lxml import etree
from tools import cmpFiles, writeFile, readFile, buildTestProject, XML_IGNORE
from mock import causeOSError
from novelwriter.core.project import NWProject
from novelwriter.enum import nwItemClass, nwItemType, nwItemLayout
from novelwriter.common import formatTimeStamp
from novelwriter.constants import nwFiles
from novelwriter.core.tree import NWTree
from novelwriter.core.index import NWIndex
from novelwriter.core.project import NWProject
from novelwriter.core.options import OptionState
from novelwriter.core.document import NWDoc
@pytest.mark.core
@@ -280,12 +284,12 @@ def testCoreProject_NewRoot(fncDir, outDir, refDir, mockGUI, mockRnd):
@pytest.mark.core
def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI, mockRnd):
def testCoreProject_NewFileFolder(fncDir, outDir, refDir, mockGUI, mockRnd):
"""Check that new files can be added to the project.
"""
projFile = os.path.join(fncDir, "nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_NewFile_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewFile_nwProject.nwx")
testFile = os.path.join(outDir, "coreProject_NewFileFolder_nwProject.nwx")
compFile = os.path.join(refDir, "coreProject_NewFileFolder_nwProject.nwx")
theProject = NWProject(mockGUI)
buildTestProject(theProject, fncDir)
@@ -295,9 +299,33 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI, mockRnd):
assert theProject.closeProject() is True
assert theProject.openProject(projFile) is True
assert isinstance(theProject.newFile("Hello", "31489056e0916"), str)
assert isinstance(theProject.newFile("Jane", "71ee45a3c0db9"), str)
assert theProject.projChanged
# Invalid call
assert theProject.newFolder("New Folder", "1234567890abc") is None
assert theProject.newFile("New File", "1234567890abc") is None
# Add files properly
assert theProject.newFolder("Stuff", "0000000000015") == "0000000000028"
assert theProject.newFile("Hello", "0000000000015") == "0000000000029"
assert theProject.newFile("Jane", "0000000000012") == "000000000002a"
assert "0000000000028" in theProject.tree
assert "0000000000029" in theProject.tree
assert "000000000002a" in theProject.tree
# Write to file, failed
assert theProject.writeNewFile("blabla", 1, True) is False # Not a handle
assert theProject.writeNewFile("0000000000028", 1, True) is False # Not a file
assert theProject.writeNewFile("0000000000014", 1, True) is False # Already has content
# Write to file, success
assert theProject.writeNewFile("0000000000029", 2, True) is True
assert NWDoc(theProject, "0000000000029").readDocument() == "## Hello\n\n"
assert theProject.writeNewFile("000000000002a", 1, False) is True
assert NWDoc(theProject, "000000000002a").readDocument() == "# Jane\n\n"
# Save, close and check
assert theProject.projChanged is True
assert theProject.saveProject() is True
assert theProject.closeProject() is True
@@ -305,7 +333,7 @@ def testCoreProject_NewFile(fncDir, outDir, refDir, mockGUI, mockRnd):
assert cmpFiles(testFile, compFile, ignoreStart=XML_IGNORE)
assert theProject.projChanged is False
# END Test testCoreProject_NewFile
# END Test testCoreProject_NewFileFolder
@pytest.mark.core
@@ -435,12 +463,15 @@ def testCoreProject_Open(monkeypatch, nwMinimal, mockGUI):
os.rename(oName, rName)
# Add some legacy stuff that cannot be removed
writeFile(os.path.join(nwMinimal, "junk"), "stuff")
os.mkdir(os.path.join(nwMinimal, "data_0"))
writeFile(os.path.join(nwMinimal, "data_0", "junk"), "stuff")
mockGUI.clear()
assert theProject.openProject(nwMinimal) is True
assert "data_0" in mockGUI.lastAlert
with monkeypatch.context() as mp:
mp.setattr(theProject, "_legacyDataFolder", causeOSError)
os.mkdir(os.path.join(nwMinimal, "data_0"))
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.nwd"), "stuff")
writeFile(os.path.join(nwMinimal, "data_0", "123456789abc_main.bak"), "stuff")
mockGUI.clear()
assert theProject.openProject(nwMinimal) is True
assert "There was an error updating the project." in mockGUI.lastAlert
assert theProject.closeProject()
# END Test testCoreProject_Open
@@ -613,6 +644,11 @@ def testCoreProject_AccessItems(nwMinimal, mockGUI):
theProject = NWProject(mockGUI)
theProject.openProject(nwMinimal)
# Storage Objects
assert isinstance(theProject.index, NWIndex)
assert isinstance(theProject.tree, NWTree)
assert isinstance(theProject.options, OptionState)
# Move Novel ROOT to after its files
oldOrder = [
"a508bb932959c", # ROOT: Novel
@@ -722,7 +758,7 @@ def testCoreProject_StatusImport(mockGUI, fncDir, mockRnd):
# Change Importance
# =================
fHandle = theProject.newFile("Jane Doe", "8b9d2e465e150")
fHandle = theProject.newFile("Jane Doe", "0000000000012")
theProject.tree[fHandle].setImport("Main")
assert theProject.tree[fHandle].itemImport == importKeys[3]
@@ -894,6 +930,10 @@ def testCoreProject_Methods(monkeypatch, mockGUI, tmpDir, fncDir, mockRnd):
assert theProject.setProjectLang("en_GB") is True
assert theProject.projLang == "en_GB"
# Language Lookup
assert theProject.localLookup(1) == "One"
assert theProject.localLookup(10) == "Ten"
# Automatic outline update
theProject.projChanged = False
assert theProject.setAutoOutline(True)
@@ -1007,7 +1047,8 @@ def testCoreProject_OrphanedFiles(mockGUI, nwLipsum):
# Add a file with non-existent parent
# This file will be renoved from the project on open
assert theProject.newFile("Oops", "0000000000000")
oHandle = theProject.newFile("Oops", "b3643d0f92e32")
theProject.tree[oHandle].setParent("1234567890abc")
# Save and close
assert theProject.saveProject() is True
@@ -1103,14 +1144,6 @@ def testCoreProject_OldFormat(mockGUI, nwOldProj):
os.path.join(nwOldProj, "meta", "sessionLogOptions.json"),
]
# Add some files that shouldn't be there
deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.nwd"))
deleteFiles.append(os.path.join(nwOldProj, "data_f", "whatnow.txt"))
# Add some folders that shouldn't be there
os.mkdir(os.path.join(nwOldProj, "stuff"))
os.mkdir(os.path.join(nwOldProj, "data_1", "stuff"))
# Create mock files
os.mkdir(os.path.join(nwOldProj, "cache"))
for aFile in deleteFiles:
@@ -1124,7 +1157,6 @@ def testCoreProject_OldFormat(mockGUI, nwOldProj):
for aFile in deleteFiles:
assert not os.path.isfile(aFile)
assert not os.path.isdir(os.path.join(nwOldProj, "data_1", "stuff"))
assert not os.path.isdir(os.path.join(nwOldProj, "data_1"))
assert not os.path.isdir(os.path.join(nwOldProj, "data_7"))
assert not os.path.isdir(os.path.join(nwOldProj, "data_8"))
@@ -1132,12 +1164,6 @@ def testCoreProject_OldFormat(mockGUI, nwOldProj):
assert not os.path.isdir(os.path.join(nwOldProj, "data_a"))
assert not os.path.isdir(os.path.join(nwOldProj, "data_f"))
# Check stuff that has been moved
assert os.path.isdir(os.path.join(nwOldProj, "junk"))
assert os.path.isdir(os.path.join(nwOldProj, "junk", "stuff"))
assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.nwd"))
assert os.path.isfile(os.path.join(nwOldProj, "junk", "whatnow.txt"))
# Check that files we want to keep are in the right place
assert os.path.isdir(os.path.join(nwOldProj, "cache"))
assert os.path.isdir(os.path.join(nwOldProj, "content"))
@@ -1179,7 +1205,7 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
with monkeypatch.context() as mp:
mp.setattr("os.unlink", causeOSError)
assert not theProject._deprecatedFiles()
assert theProject._deprecatedFiles() is False
assert theProject._deprecatedFiles()
assert not os.path.isfile(tstFile)
@@ -1188,63 +1214,36 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
tstFile = os.path.join(fncDir, "data_0")
writeFile(tstFile, "stuff")
assert os.path.isfile(tstFile)
errList = []
errList = theProject._legacyDataFolder(tstFile, errList)
assert len(errList) > 0
# Move folder in data folder, shouldn't be there
tstData = os.path.join(fncDir, "data_1")
errItem = os.path.join(fncDir, "data_1", "stuff")
os.mkdir(tstData)
os.mkdir(errItem)
assert os.path.isdir(tstData)
assert os.path.isdir(errItem)
# This causes a failure to create the 'junk' folder
with monkeypatch.context() as mp:
mp.setattr("os.mkdir", causeOSError)
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) > 0
# This causes a failure to move 'stuff' to 'junk'
with monkeypatch.context() as mp:
mp.setattr("os.rename", causeOSError)
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) > 0
# This should be successful
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) == 0
assert os.path.isdir(os.path.join(fncDir, "junk", "stuff"))
assert theProject._legacyDataFolder(tstFile) is False
# Check renaming/deleting of old document files
tstData = os.path.join(fncDir, "data_2")
tstDoc1m = os.path.join(tstData, "000000000001_main.nwd")
tstDoc1b = os.path.join(tstData, "000000000001_main.bak")
tstDoc2m = os.path.join(tstData, "000000000002_main.nwd")
tstDoc2b = os.path.join(tstData, "000000000002_main.bak")
tstDoc3m = os.path.join(tstData, "tooshort003_main.nwd")
tstDoc3b = os.path.join(tstData, "tooshort003_main.bak")
tstData2 = os.path.join(fncDir, "data_2")
tstData3 = os.path.join(fncDir, "data_3")
tstDoc1m = os.path.join(tstData2, "000000000001_main.nwd")
tstDoc1b = os.path.join(tstData2, "000000000001_main.bak")
tstDoc2m = os.path.join(tstData2, "000000000002_main.nwd")
tstDoc2b = os.path.join(tstData2, "000000000002_main.bak")
tstDoc3m = os.path.join(tstData3, "tooshort003_main.nwd")
tstDoc3b = os.path.join(tstData3, "tooshort003_main.bak")
tstDir4a = os.path.join(tstData3, "stuff")
os.mkdir(tstData)
os.mkdir(tstData2)
os.mkdir(tstData3)
writeFile(tstDoc1m, "stuff")
writeFile(tstDoc1b, "stuff")
writeFile(tstDoc2m, "stuff")
writeFile(tstDoc2b, "stuff")
writeFile(tstDoc3m, "stuff")
writeFile(tstDoc3b, "stuff")
os.mkdir(tstDir4a)
# Make the above fail
with monkeypatch.context() as mp:
mp.setattr("os.rename", causeOSError)
mp.setattr("os.unlink", causeOSError)
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) > 0
with pytest.raises(OSError):
theProject._legacyDataFolder(tstData2)
theProject._legacyDataFolder(tstData3)
assert os.path.isfile(tstDoc1m)
assert os.path.isfile(tstDoc1b)
assert os.path.isfile(tstDoc2m)
@@ -1253,15 +1252,16 @@ def testCoreProject_LegacyData(monkeypatch, mockGUI, fncDir):
assert os.path.isfile(tstDoc3b)
# And succeed ...
errList = []
errList = theProject._legacyDataFolder(tstData, errList)
assert len(errList) == 0
assert theProject._legacyDataFolder(tstData2) is True
assert theProject._legacyDataFolder(tstData3) is True
assert not os.path.isdir(tstData)
assert not os.path.isdir(tstData2)
assert os.path.isdir(tstData3)
assert os.path.isfile(os.path.join(fncDir, "content", "2000000000001.nwd"))
assert os.path.isfile(os.path.join(fncDir, "content", "2000000000002.nwd"))
assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.nwd"))
assert os.path.isfile(os.path.join(fncDir, "junk", "tooshort003_main.bak"))
assert os.path.isfile(os.path.join(fncDir, tstData3, "tooshort003_main.nwd"))
assert os.path.isfile(os.path.join(fncDir, tstData3, "tooshort003_main.bak"))
assert os.path.isdir(tstDir4a)
# END Test testCoreProject_LegacyData
+2 -2
View File
@@ -36,8 +36,8 @@ def testDlgAbout_NWDialog(qtbot, monkeypatch, nwGUI):
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
# NW About
nwGUI.theTheme.themeName = "A Theme"
nwGUI.theTheme.themeAuthor = "An Author"
nwGUI.mainTheme.themeName = "A Theme"
nwGUI.mainTheme.themeAuthor = "An Author"
assert nwGUI.showAboutNWDialog(showNotes=True) is True
qtbot.waitUntil(lambda: getGuiItem("GuiAbout") is not None, timeout=1000)
+22 -5
View File
@@ -24,11 +24,7 @@ import pytest
from PyQt5.QtCore import QItemSelectionModel
from PyQt5.QtWidgets import QAction, QListWidgetItem, QDialog, QMessageBox
from novelwriter.dialogs import GuiQuoteSelect, GuiUpdates
keyDelay = 2
typeDelay = 1
stepDelay = 20
from novelwriter.dialogs import GuiQuoteSelect, GuiUpdates, GuiEditLabel
@pytest.mark.gui
@@ -101,3 +97,24 @@ def testDlgOther_Updates(qtbot, monkeypatch, nwGUI):
nwUpdate._doClose()
# END Test testDlgOther_Updates
@pytest.mark.gui
def testDlgOther_EditLabel(qtbot, monkeypatch):
"""Test the label editor dialog.
"""
monkeypatch.setattr(GuiEditLabel, "exec_", lambda *a: None)
with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "result", lambda *a: QDialog.Accepted)
newLabel, dlgOk = GuiEditLabel.getLabel(None, text="Hello World")
assert dlgOk is True
assert newLabel == "Hello World"
with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "result", lambda *a: QDialog.Rejected)
newLabel, dlgOk = GuiEditLabel.getLabel(None, text="Hello World")
assert dlgOk is False
assert newLabel == "Hello World"
# END Test testDlgOther_EditLabel
+17 -17
View File
@@ -25,10 +25,10 @@ import pytest
from mock import causeOSError
from tools import getGuiItem, readFile, writeFile, buildTestProject
from PyQt5.QtWidgets import QAction, QMessageBox, QDialog
from PyQt5.QtWidgets import QAction, QMessageBox
from novelwriter.enum import nwItemType, nwWidget
from novelwriter.dialogs import GuiDocMerge, GuiItemEditor
from novelwriter.dialogs import GuiDocMerge, GuiEditLabel
from novelwriter.core.tree import NWTree
@@ -39,6 +39,7 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
# Create a new project
buildTestProject(nwGUI, fncProj)
@@ -54,13 +55,12 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
hMergedDoc = "0000000000023"
# Add Project Content
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: QDialog.Accepted)
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE)
nwGUI.treeView.newTreeItem(nwItemType.FILE)
nwGUI.treeView.newTreeItem(nwItemType.FILE)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
assert nwGUI.saveProject() is True
assert nwGUI.closeProject() is True
@@ -82,8 +82,8 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Open the Merge tool
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)
monkeypatch.setattr(GuiDocMerge, "exec_", lambda *a: None)
nwGUI.mainMenu.aMergeDocs.activate(QAction.Trigger)
@@ -101,27 +101,27 @@ def testDlgMerge_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
assert nwMerge.listBox.count() == 0
# No item selected
nwGUI.treeView.clearSelection()
nwGUI.projView.projTree.clearSelection()
assert nwMerge._populateList() is False
assert nwMerge.listBox.count() == 0
# Non-existing item
with monkeypatch.context() as mp:
mp.setattr(NWTree, "__getitem__", lambda *a: None)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)
assert nwMerge._populateList() is False
assert nwMerge.listBox.count() == 0
# Select a non-folder
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterOne).setSelected(True)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem(hChapterOne).setSelected(True)
assert nwMerge._populateList() is False
assert nwMerge.listBox.count() == 0
# Select the chapter folder
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)
assert nwMerge._populateList() is True
assert nwMerge.listBox.count() == 5
+13 -13
View File
@@ -25,10 +25,10 @@ import pytest
from mock import causeOSError
from tools import getGuiItem, readFile, writeFile, buildTestProject
from PyQt5.QtWidgets import QAction, QMessageBox, QDialog
from PyQt5.QtWidgets import QAction, QMessageBox
from novelwriter.enum import nwItemType, nwWidget
from novelwriter.dialogs import GuiDocSplit, GuiItemEditor
from novelwriter.dialogs import GuiDocSplit, GuiEditLabel
from novelwriter.core.tree import NWTree
from novelwriter.core.document import NWDoc
@@ -40,6 +40,7 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Ok)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
# Create a new project
buildTestProject(nwGUI, fncProj)
@@ -58,11 +59,10 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
hSceneFive = "0000000000028"
# Add Project Content
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: QDialog.Accepted)
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hNovelRoot).setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem(hNovelRoot).setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
assert nwGUI.saveProject() is True
assert nwGUI.closeProject() is True
@@ -89,8 +89,8 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# Open the Split tool
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hToSplit).setSelected(True)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem(hToSplit).setSelected(True)
monkeypatch.setattr(GuiDocSplit, "exec_", lambda *a: None)
nwGUI.mainMenu.aSplitDoc.activate(QAction.Trigger)
@@ -109,7 +109,7 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
# No item selected
nwSplit.sourceItem = None
nwGUI.treeView.clearSelection()
nwGUI.projView.projTree.clearSelection()
assert nwSplit._populateList() is False
assert nwSplit.listBox.count() == 0
@@ -117,15 +117,15 @@ def testDlgSplit_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
with monkeypatch.context() as mp:
mp.setattr(NWTree, "__getitem__", lambda *a: None)
nwSplit.sourceItem = None
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hToSplit).setSelected(True)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem(hToSplit).setSelected(True)
assert nwSplit._populateList() is False
assert nwSplit.listBox.count() == 0
# Select a non-file
nwSplit.sourceItem = None
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem(hChapterDir).setSelected(True)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem(hChapterDir).setSelected(True)
assert nwSplit._populateList() is False
assert nwSplit.listBox.count() == 0
-245
View File
@@ -1,245 +0,0 @@
"""
novelWriter Item Editor Dialog Class Tester
=============================================
This file is a part of novelWriter
Copyright 20182022, 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 pytest
from tools import getGuiItem, buildTestProject
from PyQt5.QtWidgets import QAction, QDialog, QMessageBox
from novelwriter.gui import GuiProjectTree
from novelwriter.enum import nwItemLayout, nwItemType
from novelwriter.dialogs import GuiItemEditor
from novelwriter.core.tree import NWTree
statusKeys = ["s000000", "s000001", "s000002", "s000003"]
importKeys = ["i000004", "i000005", "i000006", "i000007"]
@pytest.mark.gui
def testDlgItemEditor_Dialog(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test launching the item editor dialog from GuiMain.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
# Block Dialog exec_
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: None)
# Open Editor wo/Project
assert nwGUI.editItem() is False
# Create and Open Project
buildTestProject(nwGUI, fncProj)
tHandle = "000000000000f"
# No Selection
nwGUI.treeView.clearSelection()
assert nwGUI.editItem() is False
# Force opening from editor
assert nwGUI.openDocument(tHandle)
nwGUI.isFocusMode = True
# Block Tree Lookup
with monkeypatch.context() as mp:
mp.setattr(NWTree, "__getitem__", lambda *a: None)
assert nwGUI.editItem() is False
# Invalid Type
nwGUI.theProject.tree[tHandle]._type = nwItemType.NO_TYPE
assert nwGUI.editItem() is False
nwGUI.theProject.tree[tHandle]._type = nwItemType.FILE
# Open Properly
assert nwGUI.editItem() is True
qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
itemEdit = getGuiItem("GuiItemEditor")
assert itemEdit is not None
itemEdit.close()
# Open Via Menu
with monkeypatch.context() as mp:
mp.setattr(GuiItemEditor, "result", lambda *a: QDialog.Accepted)
nwGUI.mainMenu.aEditItem.activate(QAction.Trigger)
qtbot.waitUntil(lambda: getGuiItem("GuiItemEditor") is not None, timeout=1000)
itemEdit = getGuiItem("GuiItemEditor")
assert itemEdit is not None
itemEdit.close()
nwGUI.isFocusMode = False
# END Test testDlgItemEditor_Dialog
@pytest.mark.gui
def testDlgItemEditor_Novel(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test the item editor dialog for a novel document.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document
buildTestProject(nwGUI, fncProj)
tHandle = "000000000000f"
assert nwGUI.theProject.statusItems.name(statusKeys[0]) == "New"
assert nwGUI.theProject.statusItems.name(statusKeys[1]) == "Note"
assert nwGUI.openDocument(tHandle) is True
# Check that an invalid handle is managed
itemEdit = GuiItemEditor(nwGUI, "whatever")
itemEdit.show()
itemEdit._doClose()
# Edit a Document
itemEdit = GuiItemEditor(nwGUI, tHandle)
itemEdit.show()
# Check Existing Settings
assert itemEdit.editName.text() == "New Scene"
assert itemEdit.editStatus.currentData() == statusKeys[0]
assert itemEdit.editLayout.currentData() == nwItemLayout.DOCUMENT
assert itemEdit.editExport.isChecked() is True
# Change Settings
layoutIdx = itemEdit.editLayout.findData(nwItemLayout.NOTE)
itemEdit.editName.setText("Great Scene")
itemEdit.editStatus.setCurrentIndex(1)
itemEdit.editLayout.setCurrentIndex(layoutIdx)
itemEdit.editExport.setChecked(False)
# Check New Settings
itemEdit._doSave()
assert itemEdit.theItem.itemName == "Great Scene"
assert itemEdit.theItem.itemStatus == statusKeys[1]
assert itemEdit.theItem.itemLayout == nwItemLayout.NOTE
assert itemEdit.theItem.isExported is False
# Check that the editor header is updated
nwGUI.docEditor.updateDocInfo(tHandle)
assert nwGUI.docEditor.docHeader.theTitle.text() == "Novel New Chapter Great Scene"
itemEdit.close()
del itemEdit
# qtbot.stopForInteraction()
# END Test testDlgItemEditor_Dialog
@pytest.mark.gui
def testDlgItemEditor_Note(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test the item editor dialog for a project note.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document
buildTestProject(nwGUI, fncProj)
assert nwGUI.theProject.statusItems.name(statusKeys[0]) == "New"
assert nwGUI.theProject.statusItems.name(statusKeys[1]) == "Note"
assert nwGUI.theProject.importItems.name(importKeys[0]) == "New"
assert nwGUI.theProject.importItems.name(importKeys[1]) == "Minor"
# Create Note
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("000000000000a").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
# Open Note
assert nwGUI.openDocument("0000000000010")
# Edit a Document
itemEdit = GuiItemEditor(nwGUI, "0000000000010")
itemEdit.show()
# Check Existing Settings
assert itemEdit.editName.text() == "New Note"
assert itemEdit.editStatus.currentData() == importKeys[0]
assert itemEdit.editLayout.currentData() == nwItemLayout.NOTE
assert itemEdit.editExport.isChecked() is True
# Change Settings
itemEdit.editName.setText("New Character")
itemEdit.editStatus.setCurrentIndex(1)
itemEdit.editExport.setChecked(False)
itemEdit._doSave()
# Check New Settings
assert itemEdit.theItem.itemName == "New Character"
assert itemEdit.theItem.itemStatus == statusKeys[0]
assert itemEdit.theItem.itemImport == importKeys[1]
assert itemEdit.theItem.itemLayout == nwItemLayout.NOTE
assert itemEdit.theItem.isExported is False
itemEdit.close()
del itemEdit
# qtbot.stopForInteraction()
# END Test testDlgItemEditor_Note
@pytest.mark.gui
def testDlgItemEditor_Folder(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test the item editor dialog for a folder.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Create Project and Open Document
buildTestProject(nwGUI, fncProj)
# Edit a Folder
itemEdit = GuiItemEditor(nwGUI, "000000000000d")
itemEdit.show()
assert nwGUI.theProject.statusItems.name(statusKeys[0]) == "New"
assert nwGUI.theProject.statusItems.name(statusKeys[1]) == "Note"
# Check Existing Settings
assert itemEdit.editName.text() == "New Chapter"
assert itemEdit.editStatus.currentData() == statusKeys[0]
assert itemEdit.editLayout.currentData() == nwItemLayout.NO_LAYOUT
assert itemEdit.editExport.isChecked() is False
assert itemEdit.editLayout.isEnabled() is False
assert itemEdit.editExport.isEnabled() is False
# Change Settings
itemEdit.editName.setText("Chapter One")
itemEdit.editStatus.setCurrentIndex(1)
# Check New Settings
itemEdit._doSave()
assert itemEdit.theItem.itemName == "Chapter One"
assert itemEdit.theItem.itemStatus == statusKeys[1]
assert itemEdit.theItem.itemLayout == nwItemLayout.NO_LAYOUT
assert itemEdit.theItem.isExported is False
itemEdit.close()
del itemEdit
# qtbot.stopForInteraction()
# END Test testDlgItemEditor_Folder
+1 -1
View File
@@ -1147,7 +1147,7 @@ def testGuiEditor_Tags(qtbot, monkeypatch, nwGUI, nwMinimal, ipsumText):
assert nwGUI.openDocument(cHandle) is True
assert nwGUI.docEditor.replaceText(theText) is True
assert nwGUI.saveDocument() is True
assert nwGUI.treeView.revealNewTreeItem(cHandle)
assert nwGUI.projView.revealNewTreeItem(cHandle)
nwGUI.docEditor.updateTagHighLighting()
# Follow Tag
+5 -5
View File
@@ -51,12 +51,12 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
assert nwGUI.theProject.index._itemIndex._items != {}
# Select a document in the project tree
nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
nwGUI.projView.setSelectedHandle("88243afbe5ed8")
# Middle-click the selected item
theItem = nwGUI.treeView._getTreeItem("88243afbe5ed8")
theRect = nwGUI.treeView.visualItemRect(theItem)
qtbot.mouseClick(nwGUI.treeView.viewport(), Qt.MidButton, pos=theRect.center())
theItem = nwGUI.projView.projTree._getTreeItem("88243afbe5ed8")
theRect = nwGUI.projView.projTree.visualItemRect(theItem)
qtbot.mouseClick(nwGUI.projView.projTree.viewport(), Qt.MidButton, pos=theRect.center())
assert nwGUI.docViewer.docHandle() == "88243afbe5ed8"
# Reload the text
@@ -117,7 +117,7 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, nwLipsum):
assert nwGUI.docViewer.docAction(nwDocAction.COPY) is False
# Open again via menu
assert nwGUI.treeView.setSelectedHandle("88243afbe5ed8")
assert nwGUI.projView.setSelectedHandle("88243afbe5ed8")
nwGUI.mainMenu.aViewDoc.activate(QAction.Trigger)
# Select "Bod" link
+46 -47
View File
@@ -26,14 +26,13 @@ from shutil import copyfile
from tools import cmpFiles, buildTestProject, XML_IGNORE, writeFile
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox, QDialog
from PyQt5.QtWidgets import QMessageBox, QInputDialog
from novelwriter.gui import (
GuiDocEditor, GuiProjectTree, GuiNovelTree, GuiOutline
)
from novelwriter.enum import nwItemType, nwWidget
from novelwriter.gui import GuiDocEditor, GuiNovelView, GuiOutlineView
from novelwriter.enum import nwItemType, nwView, nwWidget
from novelwriter.tools import GuiProjectWizard
from novelwriter.dialogs.itemeditor import GuiItemEditor
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.dialogs import GuiEditLabel
keyDelay = 2
typeDelay = 1
@@ -58,7 +57,7 @@ def testGuiMain_ProjectBlocker(monkeypatch, nwGUI):
assert nwGUI.mergeDocuments() is False
assert nwGUI.splitDocument() is False
assert nwGUI.openSelectedItem() is False
assert nwGUI.editItem() is False
assert nwGUI.editItemLabel() is False
assert nwGUI.requestNovelTreeRefresh() is False
assert nwGUI.rebuildIndex() is False
assert nwGUI.showProjectSettingsDialog() is False
@@ -121,44 +120,44 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
assert nwGUI.openSelectedItem() is False
# Project Tree has focus
nwGUI._changeView(nwView.PROJECT)
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.projStack.setCurrentIndex(0)
with monkeypatch.context() as mp:
mp.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle() is None
nwGUI.treeView._getTreeItem(sHandle).setSelected(True)
nwGUI.projView.projTree._getTreeItem(sHandle).setSelected(True)
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.closeDocument() is True
# Novel Tree has focus
nwGUI.projStack.setCurrentIndex(1)
nwGUI.novelView.refreshTree(True)
nwGUI._changeView(nwView.NOVEL)
nwGUI.novelView.novelTree.refreshTree(rootHandle=None, overRide=True)
with monkeypatch.context() as mp:
mp.setattr(GuiNovelTree, "hasFocus", lambda *a: True)
mp.setattr(GuiNovelView, "treeHasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle() is None
actItem = nwGUI.novelView.topLevelItem(0)
chpItem = actItem.child(0)
selItem = chpItem.child(0)
nwGUI.novelView.setCurrentItem(selItem)
selItem = nwGUI.novelView.novelTree.topLevelItem(2)
nwGUI.novelView.novelTree.setCurrentItem(selItem)
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.closeDocument() is True
# Project Outline has focus
nwGUI._changeView(nwView.OUTLINE)
nwGUI.switchFocus(nwWidget.OUTLINE)
with monkeypatch.context() as mp:
mp.setattr(GuiOutline, "treeFocus", lambda *a: True)
mp.setattr(GuiOutlineView, "treeHasFocus", lambda *a: True)
assert nwGUI.docEditor.docHandle() is None
actItem = nwGUI.projView.outlineView.topLevelItem(0)
actItem = nwGUI.outlineView.outlineTree.topLevelItem(0)
chpItem = actItem.child(0)
selItem = chpItem.child(0)
nwGUI.projView.outlineView.setCurrentItem(selItem)
nwGUI.outlineView.outlineTree.setCurrentItem(selItem)
nwGUI._keyPressReturn()
assert nwGUI.docEditor.docHandle() == sHandle
assert nwGUI.closeDocument() is True
# qtbot.stopForInteraction()
# qtbot.stop()
# END Test testGuiMain_ProjectTreeItems
@@ -170,10 +169,10 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiItemEditor, "exec_", lambda *a: None)
monkeypatch.setattr(GuiItemEditor, "result", lambda *a: QDialog.Accepted)
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
monkeypatch.setattr(GuiDocEditor, "hasFocus", lambda *a: True)
monkeypatch.setattr(QInputDialog, "getText", lambda *a, text: (text, True))
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
# Create new, save, close project
buildTestProject(nwGUI, fncProj)
@@ -220,14 +219,14 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
assert nwGUI.theProject.spellCheck is False
# Check that tree items have been created
assert nwGUI.treeView._getTreeItem("0000000000008") is not None
assert nwGUI.treeView._getTreeItem("0000000000009") is not None
assert nwGUI.treeView._getTreeItem("000000000000a") is not None
assert nwGUI.treeView._getTreeItem("000000000000b") is not None
assert nwGUI.treeView._getTreeItem("000000000000c") is not None
assert nwGUI.treeView._getTreeItem("000000000000d") is not None
assert nwGUI.treeView._getTreeItem("000000000000e") is not None
assert nwGUI.treeView._getTreeItem("000000000000f") is not None
assert nwGUI.projView.projTree._getTreeItem("0000000000008") is not None
assert nwGUI.projView.projTree._getTreeItem("0000000000009") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000a") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000b") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000c") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000d") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000e") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000f") is not None
nwGUI.mainMenu.aSpellCheck.setChecked(True)
assert nwGUI.mainMenu._toggleSpellCheck()
@@ -240,9 +239,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Add a Character File
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("000000000000a").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("000000000000a").setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
assert nwGUI.openSelectedItem()
# Type something into the document
@@ -262,9 +261,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Add a Plot File
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("0000000000009").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("0000000000009").setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
assert nwGUI.openSelectedItem()
# Type something into the document
@@ -284,9 +283,9 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Add a World File
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("000000000000b").setSelected(True)
nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("000000000000b").setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None, isNote=True)
assert nwGUI.openSelectedItem()
# Add Some Text
@@ -315,10 +314,10 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
# Select the 'New Scene' file
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.treeView.clearSelection()
nwGUI.treeView._getTreeItem("0000000000008").setExpanded(True)
nwGUI.treeView._getTreeItem("000000000000d").setExpanded(True)
nwGUI.treeView._getTreeItem("000000000000f").setSelected(True)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("0000000000008").setExpanded(True)
nwGUI.projView.projTree._getTreeItem("000000000000d").setExpanded(True)
nwGUI.projView.projTree._getTreeItem("000000000000f").setSelected(True)
assert nwGUI.openSelectedItem()
# Type something into the document
@@ -461,12 +460,12 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, fncProj, refDir, outDir, mock
qtbot.wait(stepDelay)
# Check a Quick Create and Delete
assert nwGUI.treeView.newTreeItem(nwItemType.FILE, None)
newHandle = nwGUI.treeView.getSelectedHandle()
assert nwGUI.projView.projTree.newTreeItem(nwItemType.FILE, None)
newHandle = nwGUI.projView.getSelectedHandle()
assert nwGUI.theProject.tree["0000000000020"] is not None
assert nwGUI.treeView.deleteItem()
assert nwGUI.treeView.setSelectedHandle(newHandle)
assert nwGUI.treeView.deleteItem()
assert nwGUI.projView.deleteItem()
assert nwGUI.projView.setSelectedHandle(newHandle)
assert nwGUI.projView.deleteItem()
assert nwGUI.theProject.tree["0000000000024"] is not None # Trash
assert nwGUI.saveProject()
+3 -3
View File
@@ -467,7 +467,7 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
buildTestProject(nwGUI, fncProj)
assert nwGUI.treeView._getTreeItem("000000000000f") is not None
assert nwGUI.projView.projTree._getTreeItem("000000000000f") is not None
assert nwGUI.openDocument("000000000000f") is True
nwGUI.docEditor.clear()
@@ -476,10 +476,10 @@ def testGuiMenu_Insert(qtbot, monkeypatch, nwGUI, fncDir, fncProj, mockRnd):
assert nwGUI.docEditor.getText() == "hello world"
nwGUI.docEditor.clear()
assert not nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT)
assert nwGUI.docEditor.insertText(nwDocInsert.NO_INSERT) is False
assert nwGUI.docEditor.isEmpty()
assert not nwGUI.docEditor.insertText(None)
assert nwGUI.docEditor.insertText(None) is False
assert nwGUI.docEditor.isEmpty()
# qtbot.stopForInteraction()
+91 -77
View File
@@ -19,73 +19,93 @@ 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 pytest
import os
import pytest
from tools import writeFile
from tools import buildTestProject, writeFile
from PyQt5.QtCore import Qt
from PyQt5.QtWidgets import QMessageBox
from novelwriter.enum import nwWidget, nwItemType
from novelwriter.dialogs import GuiEditLabel
from novelwriter.gui.noveltree import NovelTreeColumn
@pytest.mark.gui
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
"""Test navigating the novel tree.
"""
# Block message box
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwGUI.openProject(nwMinimal)
nwTree = nwGUI.novelView
buildTestProject(nwGUI, fncProj)
##
# Show/Hide Scrollbars
##
nwGUI.switchFocus(nwWidget.TREE)
nwGUI.projView.projTree.clearSelection()
nwGUI.projView.projTree._getTreeItem("000000000000a").setSelected(True)
nwGUI.projView.projTree.newTreeItem(nwItemType.FILE)
nwTree.mainConf.hideVScroll = True
nwTree.mainConf.hideHScroll = True
nwTree.initTree()
assert not nwTree.verticalScrollBar().isVisible()
assert not nwTree.horizontalScrollBar().isVisible()
writeFile(
os.path.join(nwGUI.theProject.projContent, "0000000000010.nwd"),
"# Jane Doe\n\n@tag: Jane\n\n"
)
writeFile(
os.path.join(nwGUI.theProject.projContent, "000000000000f.nwd"),
"### Scene One\n\n@pov: Jane\n@focus: Jane\n\n"
)
nwTree.mainConf.hideVScroll = False
nwTree.mainConf.hideHScroll = False
nwTree.initTree()
assert nwTree.verticalScrollBar().isEnabled()
assert nwTree.horizontalScrollBar().isEnabled()
novelView = nwGUI.novelView
novelTree = novelView.novelTree
novelBar = novelView.novelBar
##
# Populate Tree
##
# Show/Hide Scrollbars
# ====================
nwGUI.mainConf.hideVScroll = True
nwGUI.mainConf.hideHScroll = True
novelView.initSettings()
assert not novelTree.verticalScrollBar().isVisible()
assert not novelTree.horizontalScrollBar().isVisible()
nwGUI.mainConf.hideVScroll = False
nwGUI.mainConf.hideHScroll = False
novelView.initSettings()
assert novelTree.verticalScrollBar().isEnabled()
assert novelTree.horizontalScrollBar().isEnabled()
# Populate Tree
# =============
nwGUI.projStack.setCurrentIndex(nwGUI.idxNovelView)
nwGUI.rebuildIndex()
nwTree._populateTree()
assert nwTree.topLevelItemCount() == 1
novelTree._populateTree(rootHandle=None)
assert novelTree.topLevelItemCount() == 3
# Rebuild should preserve selection
topItem = nwTree.topLevelItem(0)
topItem = novelTree.topLevelItem(0)
assert not topItem.isSelected()
topItem.setSelected(True)
assert nwTree.selectedItems()[0] == topItem
assert nwTree.getSelectedHandle() == ("a35baf2e93843", 0)
assert novelTree.selectedItems()[0] == topItem
assert novelView.getSelectedHandle() == ("000000000000c", 0)
nwTree.refreshTree()
assert nwTree.topLevelItem(0).isSelected()
# Refresh using the slot for the butoom
novelBar._refreshNovelTree()
assert novelTree.topLevelItem(0).isSelected()
##
# Open Items
##
# Open Items
# ==========
# Clear selection
nwTree.clearSelection()
scItem = nwTree.topLevelItem(0).child(0).child(0)
novelTree.clearSelection()
scItem = novelTree.topLevelItem(2)
scItem.setSelected(True)
assert scItem.isSelected()
# Clear selection with mouse
vPort = nwTree.viewport()
vPort = novelTree.viewport()
qtbot.mouseClick(vPort, Qt.LeftButton, pos=vPort.rect().center(), delay=10)
assert not scItem.isSelected()
@@ -93,8 +113,8 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
scItem.setSelected(True)
assert scItem.isSelected()
assert nwGUI.docEditor.docHandle() is None
nwTree._treeDoubleClick(scItem, 0)
assert nwGUI.docEditor.docHandle() == "8c659a11cd429"
novelTree._treeDoubleClick(scItem, 0)
assert nwGUI.docEditor.docHandle() == "000000000000f"
# Open item with middle mouse button
scItem.setSelected(True)
@@ -103,58 +123,52 @@ def testGuiNovelTree_TreeItems(qtbot, monkeypatch, nwGUI, nwMinimal):
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=vPort.rect().center(), delay=10)
assert nwGUI.docViewer.docHandle() is None
scRect = nwTree.visualItemRect(scItem)
oldData = scItem.data(nwTree.C_TITLE, Qt.UserRole)
scItem.setData(nwTree.C_TITLE, Qt.UserRole, (None, "", ""))
scRect = novelTree.visualItemRect(scItem)
oldData = scItem.data(novelTree.C_TITLE, Qt.UserRole)
scItem.setData(novelTree.C_TITLE, Qt.UserRole, (None, "", ""))
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
assert nwGUI.docViewer.docHandle() is None
scItem.setData(nwTree.C_TITLE, Qt.UserRole, oldData)
scItem.setData(novelTree.C_TITLE, Qt.UserRole, oldData)
qtbot.mouseClick(vPort, Qt.MiddleButton, pos=scRect.center(), delay=10)
assert nwGUI.docViewer.docHandle() == "8c659a11cd429"
assert nwGUI.docViewer.docHandle() == "000000000000f"
##
# Populate Tree
##
# Last Column
# ===========
# Add weird titles to first file to check hnadling of non-standard
# order of title levels.
writeFile(os.path.join(nwMinimal, "content", "a35baf2e93843.nwd"), (
"#### Section wo/Scene\n\n"
"### Scene wo/Chapter\n\n"
"## Chapter wo/Title\n\n"
"# Title\n\n"
"#### Section w/Title, wo/Scene\n\n"
"### Scene w/Title, wo/Chapter\n\n"
"## Chapter\n\n"
"#### Section w/Chapter, wo/Scene\n\n"
"### Scene\n\n"
"#### Section\n\n"
))
nwGUI.rebuildIndex()
nwTree._populateTree()
assert nwTree.topLevelItem(0).text(nwTree.C_TITLE) == "Section wo/Scene"
assert nwTree.topLevelItem(1).text(nwTree.C_TITLE) == "Scene wo/Chapter"
assert nwTree.topLevelItem(2).text(nwTree.C_TITLE) == "Chapter wo/Title"
assert nwTree.topLevelItem(3).text(nwTree.C_TITLE) == "Title"
novelBar.setLastColType(NovelTreeColumn.HIDDEN)
assert novelTree.isColumnHidden(novelTree.C_LAST) is True
assert novelTree.lastColType == NovelTreeColumn.HIDDEN
assert novelTree._getLastColumnText("000000000000f", "T000001") == ("", "")
tTitle = nwTree.topLevelItem(3)
assert tTitle.child(0).text(nwTree.C_TITLE) == "Section w/Title, wo/Scene"
assert tTitle.child(1).text(nwTree.C_TITLE) == "Scene w/Title, wo/Chapter"
assert tTitle.child(2).text(nwTree.C_TITLE) == "Chapter"
novelBar.setLastColType(NovelTreeColumn.POV)
assert novelTree.isColumnHidden(novelTree.C_LAST) is False
assert novelTree.lastColType == NovelTreeColumn.POV
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"Jane", "Point of View: Jane"
)
tChap = tTitle.child(2)
assert tChap.child(0).text(nwTree.C_TITLE) == "Section w/Chapter, wo/Scene"
assert tChap.child(1).text(nwTree.C_TITLE) == "Scene"
novelBar.setLastColType(NovelTreeColumn.FOCUS)
assert novelTree.isColumnHidden(novelTree.C_LAST) is False
assert novelTree.lastColType == NovelTreeColumn.FOCUS
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"Jane", "Focus: Jane"
)
tScene = tChap.child(1)
assert tScene.child(0).text(nwTree.C_TITLE) == "Section"
novelBar.setLastColType(NovelTreeColumn.PLOT)
assert novelTree.isColumnHidden(novelTree.C_LAST) is False
assert novelTree.lastColType == NovelTreeColumn.PLOT
assert novelTree._getLastColumnText("000000000000f", "T000001") == (
"", "Plot: "
)
##
# Close
##
novelTree._lastCol = None
assert novelTree._getLastColumnText("0000000000000", "T000000") == ("", "")
# qtbot.stopForInteraction()
# Close
# =====
# qtbot.stop()
nwGUI.closeProject()
# END Test testGuiNovelTree_TreeItems
+56 -56
View File
@@ -46,57 +46,57 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir):
nwGUI.rebuildIndex()
nwGUI._changeView(nwView.OUTLINE)
outlineMain = nwGUI.projView
outlineView = outlineMain.outlineView
outlineData = outlineMain.outlineData
outlineMenu = outlineMain.outlineBar.mColumns
outlineView = nwGUI.outlineView
outlineTree = outlineView.outlineTree
outlineData = outlineView.outlineData
outlineMenu = outlineView.outlineBar.mColumns
# Toggle scrollbars
nwGUI.mainConf.hideVScroll = True
nwGUI.mainConf.hideHScroll = True
nwGUI.projView.initOutline()
assert outlineView.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineView.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
outlineView.initOutline()
assert outlineTree.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineTree.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineData.verticalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
assert outlineData.horizontalScrollBarPolicy() == Qt.ScrollBarAlwaysOff
nwGUI.mainConf.hideVScroll = False
nwGUI.mainConf.hideHScroll = False
nwGUI.projView.initOutline()
assert outlineView.verticalScrollBarPolicy() == Qt.ScrollBarAsNeeded
assert outlineView.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded
outlineView.initOutline()
assert outlineTree.verticalScrollBarPolicy() == Qt.ScrollBarAsNeeded
assert outlineTree.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded
assert outlineData.verticalScrollBarPolicy() == Qt.ScrollBarAsNeeded
assert outlineData.horizontalScrollBarPolicy() == Qt.ScrollBarAsNeeded
# Check focus
with monkeypatch.context() as mp:
mp.setattr(QWidget, "hasFocus", lambda *a: True)
assert outlineMain.treeFocus() is True
assert outlineView.treeHasFocus() is True
outlineMain.setTreeFocus() # Can't check. just ensures that it doesn't error
outlineView.setTreeFocus() # Can't check. just ensures that it doesn't error
# Option State
# ============
pOptions = nwGUI.theProject.options
colNames = [h.name for h in nwOutline]
colItems = [h for h in nwOutline]
colWidth = {h: outlineView.DEF_WIDTH[h] for h in nwOutline}
colHidden = {h: outlineView.DEF_HIDDEN[h] for h in nwOutline}
colWidth = {h: outlineTree.DEF_WIDTH[h] for h in nwOutline}
colHidden = {h: outlineTree.DEF_HIDDEN[h] for h in nwOutline}
assert outlineView.topLevelItemCount() > 0
assert outlineTree.topLevelItemCount() > 0
# Save header state not allowed
outlineView._lastBuild = 0
outlineView._saveHeaderState()
outlineTree._lastBuild = 0
outlineTree._saveHeaderState()
assert pOptions.getValue("GuiOutline", "headerOrder", []) == []
# Allow saving header state
outlineView._lastBuild = time.time()
outlineView._saveHeaderState()
outlineTree._lastBuild = time.time()
outlineTree._saveHeaderState()
assert pOptions.getValue("GuiOutline", "headerOrder", []) == colNames
assert outlineView._treeOrder == colItems
assert outlineView._colWidth == colWidth
assert outlineView._colHidden == colHidden
assert outlineTree._treeOrder == colItems
assert outlineTree._colWidth == colWidth
assert outlineTree._colHidden == colHidden
# Get default values
optItems = pOptions.getValue("GuiOutline", "headerOrder", [])
@@ -105,49 +105,49 @@ def testGuiOutline_Main(qtbot, monkeypatch, nwGUI, fncDir):
# Add invalid column name
pOptions.setValue("GuiOutline", "headerOrder", optItems + ["blabla"])
outlineView._loadHeaderState()
assert outlineView._treeOrder == colItems
assert outlineView._colHidden == colHidden
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Add duplicate column name
pOptions.setValue("GuiOutline", "headerOrder", optItems + [optItems[-1]])
outlineView._loadHeaderState()
assert outlineView._treeOrder == colItems
assert outlineView._colHidden == colHidden
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Invalid column width data
pOptions.setValue("GuiOutline", "headerOrder", optItems)
pOptions.setValue("GuiOutline", "columnWidth", {"blabla": None})
outlineView._loadHeaderState()
assert outlineView._treeOrder == colItems
assert outlineView._colHidden == colHidden
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Invalid column width data
pOptions.setValue("GuiOutline", "headerOrder", optItems)
pOptions.setValue("GuiOutline", "columnWidth", optWidth)
pOptions.setValue("GuiOutline", "columnHidden", {"bloabla": None})
outlineView._loadHeaderState()
assert outlineView._treeOrder == colItems
assert outlineView._colHidden == colHidden
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Valid settings
pOptions.setValue("GuiOutline", "headerOrder", optItems)
pOptions.setValue("GuiOutline", "columnWidth", optWidth)
pOptions.setValue("GuiOutline", "columnHidden", optHidden)
outlineView._loadHeaderState()
assert outlineView._treeOrder == colItems
assert outlineView._colHidden == colHidden
outlineTree._loadHeaderState()
assert outlineTree._treeOrder == colItems
assert outlineTree._colHidden == colHidden
# Header Menu
# ===========
# Trigger the menu entry for all hidden columns
for hItem in nwOutline:
if outlineView.DEF_HIDDEN[hItem]:
if outlineTree.DEF_HIDDEN[hItem]:
outlineMenu.actionMap[hItem].activate(QAction.Trigger)
# Now no columns should be hidden
outlineView._saveHeaderState()
outlineTree._saveHeaderState()
assert not any(pOptions.getValue("GuiOutline", "columnHidden", None).values())
# qtbot.stop()
@@ -169,10 +169,10 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
nwGUI.rebuildIndex()
nwGUI._changeView(nwView.OUTLINE)
outlineMain = nwGUI.projView
outlineBar = outlineMain.outlineBar
outlineView = outlineMain.outlineView
outlineData = outlineMain.outlineData
outlineView = nwGUI.outlineView
outlineBar = outlineView.outlineBar
outlineTree = outlineView.outlineTree
outlineData = outlineView.outlineData
lipHandle = "b3643d0f92e32"
@@ -183,7 +183,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
# Add a second novel folder
newHandle = nwGUI.theProject.newRoot(nwItemClass.NOVEL)
nwGUI.treeView.revealNewTreeItem(newHandle)
nwGUI.projView.revealNewTreeItem(newHandle)
# Check new values in dropdown list
assert outlineBar.novelValue.itemData(0) == lipHandle
@@ -202,7 +202,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
aHandle = nwGUI.theProject.newFile(dTitle, newHandle)
hHash = "#"*hLevel
writeFile(os.path.join(nwLipsum, "content", f"{aHandle}.nwd"), f"{hHash} {dTitle}\n\n")
nwGUI.treeView.revealNewTreeItem(aHandle)
nwGUI.projView.revealNewTreeItem(aHandle)
nwGUI.rebuildIndex()
@@ -218,10 +218,10 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
# =============
# First Item
outlineView.refreshTree()
selItem = outlineView.topLevelItem(0)
outlineTree.refreshTree()
selItem = outlineTree.topLevelItem(0)
outlineView.setCurrentItem(selItem)
outlineTree.setCurrentItem(selItem)
assert outlineData.titleLabel.text() == "<b>Title</b>"
assert outlineData.titleValue.text() == "Lorem Ipsum"
assert outlineData.fileValue.text() == "Lorem Ipsum"
@@ -232,12 +232,12 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
assert outlineData.pCValue.text() == "3"
# Scene One
actItem = outlineView.topLevelItem(1)
actItem = outlineTree.topLevelItem(1)
chpItem = actItem.child(0)
selItem = chpItem.child(0)
outlineView.setCurrentItem(selItem)
tHandle, tLine = outlineView.getSelectedHandle()
outlineTree.setCurrentItem(selItem)
tHandle, tLine = outlineTree.getSelectedHandle()
assert tHandle == "88243afbe5ed8"
assert tLine == 0
@@ -248,17 +248,17 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
# Click POV Link
assert outlineData.povKeyValue.text() == "<a href='Bod'>Bod</a>"
nwGUI.projView._tagClicked("Bod")
outlineView._tagClicked("Bod")
assert nwGUI.docViewer.docHandle() == "4c4f28287af27"
# Scene One, Section Two
actItem = outlineView.topLevelItem(1)
actItem = outlineTree.topLevelItem(1)
chpItem = actItem.child(0)
scnItem = chpItem.child(0)
selItem = scnItem.child(0)
outlineView.setCurrentItem(selItem)
tHandle, tLine = outlineView.getSelectedHandle()
outlineTree.setCurrentItem(selItem)
tHandle, tLine = outlineTree.getSelectedHandle()
assert tHandle == "88243afbe5ed8"
assert tLine == 12
@@ -267,7 +267,7 @@ def testGuiOutline_Content(qtbot, monkeypatch, nwGUI, nwLipsum):
assert outlineData.fileValue.text() == "Scene One"
assert outlineData.itemValue.text() == "Finished"
outlineView._treeDoubleClick(selItem, 0)
outlineTree._treeDoubleClick(selItem, 0)
assert nwGUI.docEditor.docHandle() == "88243afbe5ed8"
# qtbot.stop()
+169 -71
View File
@@ -24,11 +24,11 @@ import os
from tools import buildTestProject
from PyQt5.QtWidgets import QAction, QMessageBox
from PyQt5.QtWidgets import QMessageBox, QMenu
from novelwriter.guimain import GuiMain
from novelwriter.enum import nwItemLayout, nwItemType, nwItemClass
from novelwriter.dialogs import GuiEditLabel
from novelwriter.gui.projtree import GuiProjectTree
from novelwriter.enum import nwItemType, nwItemClass
@pytest.mark.gui
@@ -40,86 +40,101 @@ def testGuiProjTree_NewItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwTree = nwGUI.treeView
nwTree = nwGUI.projView
# Try to add item with no project
assert nwTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
# Create a project
prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir)
# No itemType set
nwTree.clearSelection()
assert nwTree.newTreeItem(None) is False
nwTree.projTree.clearSelection()
assert nwTree.projTree.newTreeItem(None) is False
# Root Items
# ==========
# No class set
assert nwTree.newTreeItem(nwItemType.ROOT) is False
assert nwTree.projTree.newTreeItem(nwItemType.ROOT) is False
# Create root item
assert nwTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True
assert nwTree.projTree.newTreeItem(nwItemType.ROOT, nwItemClass.WORLD) is True
assert "0000000000010" in nwGUI.theProject.tree
# File/Folder Items
# =================
# No location selected for new item
nwTree.clearSelection()
nwTree.projTree.clearSelection()
caplog.clear()
assert nwTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.newTreeItem(nwItemType.FOLDER) is False
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is False
assert "Did not find anywhere" in caplog.text
# Create new folder as child of Novel folder
nwTree.setSelectedHandle("0000000000008")
assert nwTree.newTreeItem(nwItemType.FOLDER) is True
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is True
assert nwGUI.theProject.tree["0000000000011"].itemParent == "0000000000008"
assert nwGUI.theProject.tree["0000000000011"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000011"].itemClass == nwItemClass.NOVEL
# Add a new file in the new folder
nwTree.setSelectedHandle("0000000000011")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwGUI.theProject.tree["0000000000012"].itemParent == "0000000000011"
assert nwGUI.theProject.tree["0000000000012"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000012"].itemClass == nwItemClass.NOVEL
# Add a new file next to the other new file
# Add a new chapter next to the other new file
nwTree.setSelectedHandle("0000000000012")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE, hLevel=2) is True
assert nwGUI.theProject.tree["0000000000013"].itemParent == "0000000000011"
assert nwGUI.theProject.tree["0000000000013"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000013"].itemClass == nwItemClass.NOVEL
assert nwGUI.openDocument("0000000000013")
assert nwGUI.docEditor.getText() == "## New Document\n\n"
assert nwGUI.docEditor.getText() == "## New Chapter\n\n"
# Add a new scene next to the other new file
nwTree.setSelectedHandle("0000000000012")
assert nwTree.projTree.newTreeItem(nwItemType.FILE, hLevel=3) is True
assert nwGUI.theProject.tree["0000000000014"].itemParent == "0000000000011"
assert nwGUI.theProject.tree["0000000000014"].itemRoot == "0000000000008"
assert nwGUI.theProject.tree["0000000000014"].itemClass == nwItemClass.NOVEL
assert nwGUI.openDocument("0000000000014")
assert nwGUI.docEditor.getText() == "### New Scene\n\n"
# Add a new file to the characters folder
nwTree.setSelectedHandle("000000000000a")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwGUI.theProject.tree["0000000000014"].itemParent == "000000000000a"
assert nwGUI.theProject.tree["0000000000014"].itemRoot == "000000000000a"
assert nwGUI.theProject.tree["0000000000014"].itemClass == nwItemClass.CHARACTER
assert nwGUI.openDocument("0000000000014")
assert nwTree.projTree.newTreeItem(nwItemType.FILE, hLevel=1, isNote=True) is True
assert nwGUI.theProject.tree["0000000000015"].itemParent == "000000000000a"
assert nwGUI.theProject.tree["0000000000015"].itemRoot == "000000000000a"
assert nwGUI.theProject.tree["0000000000015"].itemClass == nwItemClass.CHARACTER
assert nwGUI.openDocument("0000000000015")
assert nwGUI.docEditor.getText() == "# New Note\n\n"
# Make sure the sibling folder bug trap works
nwTree.setSelectedHandle("0000000000013")
nwGUI.theProject.tree["0000000000013"].setParent(None) # This should not happen
caplog.clear()
assert nwTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
assert "Internal error" in caplog.text
nwGUI.theProject.tree["0000000000013"].setParent("0000000000011")
# Cancel during creation
with monkeypatch.context() as mp:
mp.setattr(GuiEditLabel, "getLabel", lambda *a, **k: ("", False))
nwTree.setSelectedHandle("0000000000013")
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
# Get the trash folder
nwTree._addTrashRoot()
nwTree.projTree._addTrashRoot()
trashHandle = nwGUI.theProject.trashFolder()
nwTree.setSelectedHandle(trashHandle)
assert nwTree.newTreeItem(nwItemType.FILE) is False
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is False
assert "Cannot add new files or folders to the Trash folder" in caplog.text
# Other Checks
@@ -148,12 +163,12 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwTree = nwGUI.treeView
nwTree = nwGUI.projView
# Try to move item with no project
assert nwTree.moveTreeItem(1) is False
assert nwTree.projTree.moveTreeItem(1) is False
# Create a project
prjDir = os.path.join(fncDir, "project")
@@ -164,42 +179,33 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
# Add some files
nwTree.setSelectedHandle("000000000000d")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
]
# Move item without focus
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: False)
assert nwTree.moveTreeItem(1) is False
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
]
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# Move with no selections
nwTree.clearSelection()
assert nwTree.moveTreeItem(1) is False
nwTree.projTree.clearSelection()
assert nwTree.projTree.moveTreeItem(1) is False
# Move second item up twice (should give same result)
nwTree.setSelectedHandle("000000000000f")
assert nwTree.moveTreeItem(-1) is True
assert nwTree.projTree.moveTreeItem(-1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000f", "000000000000e",
"0000000000010", "0000000000011", "0000000000012",
]
assert nwTree.moveTreeItem(-1) is False
assert nwTree.projTree.moveTreeItem(-1) is False
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000f", "000000000000e",
"0000000000010", "0000000000011", "0000000000012",
]
# Restore via menu entry
nwGUI.mainMenu.aMoveDown.activate(QAction.Trigger)
# Restore
assert nwTree.projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
@@ -207,19 +213,19 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
# Move fifth item down twice (should give same result)
nwTree.setSelectedHandle("0000000000011")
assert nwTree.moveTreeItem(1) is True
assert nwTree.projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000012", "0000000000011",
]
assert nwTree.moveTreeItem(1) is False
assert nwTree.projTree.moveTreeItem(1) is False
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000012", "0000000000011",
]
# Restore via menu entry
nwGUI.mainMenu.aMoveUp.activate(QAction.Trigger)
# Restore
assert nwTree.projTree.moveTreeItem(-1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
@@ -227,12 +233,12 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
# Move down again, and restore via undo
nwTree.setSelectedHandle("0000000000011")
assert nwTree.moveTreeItem(1) is True
assert nwTree.projTree.moveTreeItem(1) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000012", "0000000000011",
]
nwGUI.mainMenu.aMoveUndo.activate(QAction.Trigger)
assert nwTree.projTree.undoLastMove() is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
@@ -245,15 +251,15 @@ def testGuiProjTree_MoveItems(qtbot, monkeypatch, nwGUI, fncDir, mockRnd):
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Move novel folder up
assert nwTree.moveTreeItem(-1) is False
assert nwTree.projTree.moveTreeItem(-1) is False
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Move novel folder down
assert nwTree.moveTreeItem(1) is True
assert nwTree.projTree.moveTreeItem(1) is True
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 1
# Move novel folder up again
assert nwTree.moveTreeItem(-1) is True
assert nwTree.projTree.moveTreeItem(-1) is True
assert nwGUI.theProject.tree._treeOrder.index("0000000000008") == 0
# Clean up
@@ -272,9 +278,9 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiMain, "editItem", lambda *a: None)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
nwTree = nwGUI.treeView
nwTree = nwGUI.projView
# Try to run with no project
assert nwTree.emptyTrash() is False
@@ -289,9 +295,9 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
# Add some files
nwTree.setSelectedHandle("000000000000d")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.getTreeFromHandle("000000000000d") == [
"000000000000d", "000000000000e", "000000000000f",
"0000000000010", "0000000000011", "0000000000012",
@@ -304,13 +310,13 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
monkeypatch.setattr(GuiProjectTree, "hasFocus", lambda *a: True)
# No selection made
nwTree.clearSelection()
nwTree.projTree.clearSelection()
caplog.clear()
assert nwTree.deleteItem() is False
assert "no item to delete" in caplog.text
# Not a valid handle
nwTree.clearSelection()
nwTree.projTree.clearSelection()
caplog.clear()
assert nwTree.deleteItem("0000000000000") is False
assert "Could not find tree item" in caplog.text
@@ -326,10 +332,10 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
# ===========
# Block adding trash folder
funcPointer = nwTree._addTrashRoot
nwTree._addTrashRoot = lambda *a: None
funcPointer = nwTree.projTree._addTrashRoot
nwTree.projTree._addTrashRoot = lambda *a: None
assert nwTree.deleteItem("0000000000012") is False
nwTree._addTrashRoot = funcPointer
nwTree.projTree._addTrashRoot = funcPointer
# Delete last two documents, which also adds the trash folder
assert nwTree.deleteItem("0000000000012") is True
@@ -373,10 +379,10 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
# Add a folder with two files
nwTree.setSelectedHandle("0000000000009")
assert nwTree.newTreeItem(nwItemType.FOLDER) is True
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is True
nwTree.setSelectedHandle("0000000000014")
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert nwTree.projTree.newTreeItem(nwItemType.FILE) is True
assert os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000015.nwd"))
assert os.path.isfile(os.path.join(fncDir, "project", "content", "0000000000016.nwd"))
@@ -399,7 +405,7 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
# Add an empty folder, which can be deleted with no further restrictions
nwTree.setSelectedHandle("0000000000009")
assert nwTree.newTreeItem(nwItemType.FOLDER) is True
assert nwTree.projTree.newTreeItem(nwItemType.FOLDER) is True
assert nwTree.getTreeFromHandle("0000000000009") == ["0000000000009", "0000000000017"]
nwTree.setSelectedHandle("0000000000017")
@@ -441,7 +447,7 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
assert os.path.isfile(os.path.join(fncDir, "project", "content", "000000000000e.nwd"))
# Delete proper
assert nwTree._deleteTreeItem("000000000000e") is True
assert nwTree.projTree._deleteTreeItem("000000000000e") is True
assert not os.path.isfile(os.path.join(fncDir, "project", "content", "000000000000e.nwd"))
# Clean up
@@ -449,3 +455,95 @@ def testGuiProjTree_DeleteItems(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockR
nwGUI.closeProject()
# END Test testGuiProjTree_DeleteItems
@pytest.mark.gui
def testGuiProjTree_ContextMenu(qtbot, caplog, monkeypatch, nwGUI, fncDir, mockRnd):
"""Test the building of the project tree context menu. All this does
is test that the menu builds. It doesn't open the actual menu,
"""
# Block message box
monkeypatch.setattr(QMessageBox, "warning", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "critical", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(QMessageBox, "information", lambda *a: QMessageBox.Yes)
monkeypatch.setattr(GuiEditLabel, "getLabel", lambda *a, text: (text, True))
monkeypatch.setattr(QMenu, "exec_", lambda *a: None)
# Create a project
prjDir = os.path.join(fncDir, "project")
buildTestProject(nwGUI, prjDir)
# Handles for new objects
hNovelRoot = "0000000000008"
hTitlePage = "000000000000c"
hChapterDir = "000000000000d"
hChapterFile = "000000000000e"
hCharRoot = "000000000000a"
hCharNote = "0000000000011"
hNovelNote = "0000000000012"
projTree = nwGUI.projView.projTree
projTree._getTreeItem(hNovelRoot).setExpanded(True)
projTree._getTreeItem(hChapterDir).setExpanded(True)
projTree._addTrashRoot()
hTrashRoot = projTree.theProject.tree.trashRoot()
projTree.setSelectedHandle(hCharRoot)
projTree.newTreeItem(nwItemType.FILE)
projTree.setSelectedHandle(hNovelRoot)
projTree.newTreeItem(nwItemType.FILE, isNote=True)
def itemPos(tHandle):
return projTree.visualItemRect(projTree._getTreeItem(tHandle)).center()
# No item under menu
assert projTree._openContextMenu(projTree.viewport().rect().bottomRight()) is False
# Generate the possible menu combinarions
assert projTree._openContextMenu(itemPos(hTrashRoot)) is True
assert projTree._openContextMenu(itemPos(hNovelRoot)) is True
assert projTree._openContextMenu(itemPos(hNovelNote)) is True
assert projTree._openContextMenu(itemPos(hTitlePage)) is True
assert projTree._openContextMenu(itemPos(hChapterDir)) is True
assert projTree._openContextMenu(itemPos(hChapterFile)) is True
assert projTree._openContextMenu(itemPos(hCharRoot)) is True
assert projTree._openContextMenu(itemPos(hCharNote)) is True
# Check the keyboard shortcut handler as well
projTree.setSelectedHandle(hNovelRoot)
assert projTree.openContextOnSelected() is True
projTree.clearSelection()
assert projTree.openContextOnSelected() is False
# Direct Edit Functions
# =====================
# Trigger the dedicated functions the menu entries connect to
nwItem = projTree.theProject.tree[hNovelNote]
# Toggle exported flag
assert nwItem.isExported is True
projTree._toggleItemExported(hNovelNote)
assert nwItem.isExported is False
# Change item status
assert nwItem.itemStatus == "s000000"
projTree._changeItemStatus(hNovelNote, "s000001")
assert nwItem.itemStatus == "s000001"
# Change item importance
assert nwItem.itemImport == "i000004"
projTree._changeItemImport(hNovelNote, "i000005")
assert nwItem.itemImport == "i000005"
# Change item layout
assert nwItem.itemLayout == nwItemLayout.NOTE
projTree._changeItemLayout(hNovelNote, nwItemLayout.DOCUMENT)
assert nwItem.itemLayout == nwItemLayout.DOCUMENT
projTree._changeItemLayout(hNovelNote, nwItemLayout.NOTE)
assert nwItem.itemLayout == nwItemLayout.NOTE
# qtbot.stop()
# END Test testGuiProjTree_ContextMenu
+1 -1
View File
@@ -40,7 +40,7 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj, mockRnd):
cHandle = nwGUI.theProject.newFile("A Note", "000000000000a")
newDoc = NWDoc(nwGUI.theProject, cHandle)
newDoc.writeDocument("# A Note\n\n")
nwGUI.treeView.revealNewTreeItem(cHandle)
nwGUI.projView.revealNewTreeItem(cHandle)
nwGUI.rebuildIndex(beQuiet=True)
# Reference Time
+32 -32
View File
@@ -93,82 +93,82 @@ def testGuiTheme_Main(qtbot, monkeypatch, nwMinimal, tmpDir):
assert thePalette.link().color() == QColor(44, 152, 247)
assert thePalette.linkVisited().color() == QColor(44, 152, 247)
assert nwGUI.theTheme.statNone == [150, 152, 150]
assert nwGUI.theTheme.statSaved == [39, 135, 78]
assert nwGUI.theTheme.statUnsaved == [138, 32, 32]
assert nwGUI.mainTheme.statNone == [150, 152, 150]
assert nwGUI.mainTheme.statSaved == [39, 135, 78]
assert nwGUI.mainTheme.statUnsaved == [138, 32, 32]
# Check Syntax Colours
assert nwGUI.theTheme.colBack == [45, 45, 45]
assert nwGUI.theTheme.colText == [204, 204, 204]
assert nwGUI.theTheme.colLink == [102, 153, 204]
assert nwGUI.theTheme.colHead == [102, 153, 204]
assert nwGUI.theTheme.colHeadH == [102, 153, 204]
assert nwGUI.theTheme.colEmph == [249, 145, 57]
assert nwGUI.theTheme.colDialN == [242, 119, 122]
assert nwGUI.theTheme.colDialD == [153, 204, 153]
assert nwGUI.theTheme.colDialS == [255, 204, 102]
assert nwGUI.theTheme.colHidden == [153, 153, 153]
assert nwGUI.theTheme.colKey == [242, 119, 122]
assert nwGUI.theTheme.colVal == [204, 153, 204]
assert nwGUI.theTheme.colSpell == [242, 119, 122]
assert nwGUI.theTheme.colError == [153, 204, 153]
assert nwGUI.theTheme.colRepTag == [102, 204, 204]
assert nwGUI.theTheme.colMod == [249, 145, 57]
assert nwGUI.mainTheme.colBack == [45, 45, 45]
assert nwGUI.mainTheme.colText == [204, 204, 204]
assert nwGUI.mainTheme.colLink == [102, 153, 204]
assert nwGUI.mainTheme.colHead == [102, 153, 204]
assert nwGUI.mainTheme.colHeadH == [102, 153, 204]
assert nwGUI.mainTheme.colEmph == [249, 145, 57]
assert nwGUI.mainTheme.colDialN == [242, 119, 122]
assert nwGUI.mainTheme.colDialD == [153, 204, 153]
assert nwGUI.mainTheme.colDialS == [255, 204, 102]
assert nwGUI.mainTheme.colHidden == [153, 153, 153]
assert nwGUI.mainTheme.colKey == [242, 119, 122]
assert nwGUI.mainTheme.colVal == [204, 153, 204]
assert nwGUI.mainTheme.colSpell == [242, 119, 122]
assert nwGUI.mainTheme.colError == [153, 204, 153]
assert nwGUI.mainTheme.colRepTag == [102, 204, 204]
assert nwGUI.mainTheme.colMod == [249, 145, 57]
# Test Icon class
theIcons = nwGUI.theTheme.theIcons
iconCache = nwGUI.mainTheme.iconCache
novelwriter.CONFIG.guiIcons = "invalid"
assert theIcons.updateTheme() is True
assert iconCache.updateTheme() is True
assert novelwriter.CONFIG.guiIcons == "typicons_light"
# Ask for a non-existent key
anImg = theIcons.loadDecoration("nonsense", 20, 20)
anImg = iconCache.loadDecoration("nonsense", 20, 20)
assert isinstance(anImg, QPixmap)
assert anImg.isNull()
# Add a non-existent file and request it
theIcons.DECO_MAP["nonsense"] = "nofile.jpg"
anImg = theIcons.loadDecoration("nonsense", 20, 20)
iconCache.IMAGE_MAP["nonsense"] = "nofile.jpg"
anImg = iconCache.loadDecoration("nonsense", 20, 20)
assert isinstance(anImg, QPixmap)
assert anImg.isNull()
# Get a real image, with different size parameters
anImg = theIcons.loadDecoration("wiz-back", 20, None)
anImg = iconCache.loadDecoration("wiz-back", 20, None)
assert isinstance(anImg, QPixmap)
assert not anImg.isNull()
assert anImg.width() == 20
assert anImg.height() >= 56
anImg = theIcons.loadDecoration("wiz-back", None, 70)
anImg = iconCache.loadDecoration("wiz-back", None, 70)
assert isinstance(anImg, QPixmap)
assert not anImg.isNull()
assert anImg.height() == 70
assert anImg.width() >= 24
anImg = theIcons.loadDecoration("wiz-back", 30, 70)
anImg = iconCache.loadDecoration("wiz-back", 30, 70)
assert isinstance(anImg, QPixmap)
assert not anImg.isNull()
assert anImg.height() == 70
assert anImg.width() == 30
anImg = theIcons.loadDecoration("wiz-back", None, None)
anImg = iconCache.loadDecoration("wiz-back", None, None)
assert isinstance(anImg, QPixmap)
assert not anImg.isNull()
assert anImg.height() >= 1500
assert anImg.width() >= 500
# Load icons
anIcon = theIcons.getIcon("nonsense")
anIcon = iconCache.getIcon("nonsense")
assert isinstance(anIcon, QIcon)
assert anIcon.isNull()
anIcon = theIcons.getIcon("novelwriter")
anIcon = iconCache.getIcon("novelwriter")
assert isinstance(anIcon, QIcon)
assert not anIcon.isNull()
# Check return empty icon if file not found
theIcons.ICON_KEYS.add("testicon3")
anIcon = theIcons.getIcon("testicon3")
iconCache.ICON_KEYS.add("testicon3")
anIcon = iconCache.getIcon("testicon3")
assert isinstance(anIcon, QIcon)
assert anIcon.isNull()