Merge branch 'main' into restructure_docs
This commit is contained in:
@@ -0,0 +1,34 @@
|
|||||||
|
name: LockThreads
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "14 3 */3 * *"
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
issues: write
|
||||||
|
pull-requests: write
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: lock-threads
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
action:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: dessant/lock-threads@v5
|
||||||
|
with:
|
||||||
|
github-token: ${{ github.token }}
|
||||||
|
issue-inactive-days: 120
|
||||||
|
issue-comment: >
|
||||||
|
This issue has been automatically locked since there has not been
|
||||||
|
any recent activity after it was closed. Please open a new issue
|
||||||
|
for related bugs or feature requests.
|
||||||
|
issue-lock-reason: "resolved"
|
||||||
|
pr-inactive-days: 120
|
||||||
|
# pr-comment: >
|
||||||
|
# This pull request thread has been automatically locked since there
|
||||||
|
# has not been any recent activity after it was closed. Please open
|
||||||
|
# an issue for any related bugs or feature requests.
|
||||||
|
pr-lock-reason: "resolved"
|
||||||
|
process-only: "issue, pr"
|
||||||
|
log-output: true
|
||||||
@@ -1,5 +1,84 @@
|
|||||||
# novelWriter Changelog
|
# novelWriter Changelog
|
||||||
|
|
||||||
|
## Version 2.7 RC 1 [2025-05-19]
|
||||||
|
|
||||||
|
### Release Notes
|
||||||
|
|
||||||
|
This is a release candidate of the next release version, and is intended for testing purposes.
|
||||||
|
Please be careful when using this version on live writing projects, and make sure you take frequent
|
||||||
|
backups.
|
||||||
|
|
||||||
|
### Detailed Changelog
|
||||||
|
|
||||||
|
**New Features**
|
||||||
|
|
||||||
|
* It is now possible to set character count as the main statistics displayed on the GUI instead of
|
||||||
|
word count. The setting is available under the "Appearance" section in Preferences. Issue #1657.
|
||||||
|
PR #2323.
|
||||||
|
* Comments can now be marked as "Manuscript Notes" with the `%Note.term:` syntax. The term is a
|
||||||
|
free form value that is displayed alongside the note when exported to the manuscript. Each term
|
||||||
|
is also added as a column in exports from the Outline View. Issue #1133. PR #2346.
|
||||||
|
* A completer drop down menu has been added to comments as well in the editor. It is triggered when
|
||||||
|
a new line starts with `%`. It will show the options of Synopsis, Short, Story and Note, and when
|
||||||
|
a period is added after the latter two, show a list of previously used Story and Note keys.
|
||||||
|
Issue #1784. PR #2290.
|
||||||
|
|
||||||
|
**Improvements**
|
||||||
|
|
||||||
|
* The dialogue colour of the Snazzy Light syntax theme has changed from yellow to blue, which
|
||||||
|
should make it easier to read. PR #2336.
|
||||||
|
* The Snazzy Light theme has also been added as a GUI theme. PR #2334.
|
||||||
|
* The auto-generated title page for new projects should now make a little more sense in terms of
|
||||||
|
default values filled in for the author. PR #2333.
|
||||||
|
* The new project form on the Welcome dialog now remembers the previous author name filled in, so
|
||||||
|
there should be no need to type it again every time. PR #2333.
|
||||||
|
* Project name and author name values are now also used for new example projects. PR #2333.
|
||||||
|
* The narrator break symbol settings for dialogue have been changed from a free text field to a
|
||||||
|
dropdown list of dashes. This helps to avoid misunderstandings where the field was populated with
|
||||||
|
other symbols, like quote symbols (see #2320). Issue #2324. PR #2327.
|
||||||
|
* A splash screen is now shown at startup, providing some information about the initialisation
|
||||||
|
steps the startup script is running before the GUI is launched. Since loading system fonts can be
|
||||||
|
quote heavy, this at least gives some user feedback to users running on systems where there is a
|
||||||
|
significant delay. In particular, there is a long delay when loading fonts with poor Unicode
|
||||||
|
support, in which case the Qt library will look for replacement glyphs when the symbols are first
|
||||||
|
encountered. Previously, the delay happened when the Welcome dialog was being drawn, since this
|
||||||
|
is the first time a non-Ascii Unicode character is used. Now these Unicode characters are probed
|
||||||
|
when the config is loaded, with progress output on the splash screen. Issue #2315. PR #2316.
|
||||||
|
|
||||||
|
**Bugfixes**
|
||||||
|
|
||||||
|
* Moving a note between root folders of a different type will now update the class the tags defined
|
||||||
|
within the note are associated with. This association is used to populate the tag auto-completion
|
||||||
|
in the editor. Issue #2330. PR #2343.
|
||||||
|
* The story structure comments now appear under the correct column when exported to CSV from the
|
||||||
|
Outline View. Issue #2313. PR #2343.
|
||||||
|
* A new unique ID is now assigned to new example projects. Creating multiple example projects would
|
||||||
|
previously retain the original ID, causing the Welcome dialog to override them rather than list
|
||||||
|
them as unique projects. PR #2333.
|
||||||
|
* In Qt6, or probably in PyQt6, the datetime type from Python was truncated to a date variable when
|
||||||
|
localising it. This has been fixed by explicitly converting the Python datetime to a QDateTime
|
||||||
|
before passing it to the PyQt wrapper. This is probably a bug in PyQt6. Issue #2325. PR #2326.
|
||||||
|
* A missing Remix icon for project copy has been added. Issue #2314. PR #2317.
|
||||||
|
|
||||||
|
**Accessibility**
|
||||||
|
|
||||||
|
* Trees and lists on the GUI have been assigned names so that screen readers can more easily
|
||||||
|
identify them. Switches on the GUI have also been associated with their labels so the screen
|
||||||
|
reader can pick up the label text. Icons on the project tree also have an accessibility label.
|
||||||
|
Issues #2106 and #2107. PR #2337.
|
||||||
|
* Switches should now show a highlighted border when they have focus, and the toggle animation also
|
||||||
|
now works properly when a switch is toggled using the keyboard. This should improve keyboard
|
||||||
|
navigation. PR #2337.
|
||||||
|
* The default status and importance labels for new projects now use shapes for icons, to make the
|
||||||
|
defaults more accessible for people with reduced colour vision. Issue #2332. PR #2333.
|
||||||
|
|
||||||
|
**Code Improvements**
|
||||||
|
|
||||||
|
* New tests have been added for the custom data models, using the Qt model tester framework that
|
||||||
|
ensures the models have all the virtual methods implemented. PR #2331.
|
||||||
|
|
||||||
|
----
|
||||||
|
|
||||||
## Version 2.7 Beta 1 [2025-04-20]
|
## Version 2.7 Beta 1 [2025-04-20]
|
||||||
|
|
||||||
### Release Notes
|
### Release Notes
|
||||||
|
|||||||
+1291
-1144
File diff suppressed because it is too large
Load Diff
@@ -49,9 +49,9 @@ __license__ = "GPLv3"
|
|||||||
__author__ = "Veronica Berglyd Olsen"
|
__author__ = "Veronica Berglyd Olsen"
|
||||||
__maintainer__ = "Veronica Berglyd Olsen"
|
__maintainer__ = "Veronica Berglyd Olsen"
|
||||||
__email__ = "code@vkbo.net"
|
__email__ = "code@vkbo.net"
|
||||||
__version__ = "2.7b1"
|
__version__ = "2.7rc1"
|
||||||
__hexversion__ = "0x020700b1"
|
__hexversion__ = "0x020700c1"
|
||||||
__date__ = "2025-04-20"
|
__date__ = "2025-05-19"
|
||||||
__status__ = "Stable"
|
__status__ = "Stable"
|
||||||
__domain__ = "novelwriter.io"
|
__domain__ = "novelwriter.io"
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
"Footnotes": "Footnotes",
|
"Footnotes": "Footnotes",
|
||||||
"Comment": "Comment",
|
"Comment": "Comment",
|
||||||
"Story Structure": "Story Structure",
|
"Story Structure": "Story Structure",
|
||||||
|
"Note": "Note",
|
||||||
"Notes": "Notes",
|
"Notes": "Notes",
|
||||||
"Tag": "Tag",
|
"Tag": "Tag",
|
||||||
"Point of View": "Point of View",
|
"Point of View": "Point of View",
|
||||||
|
|||||||
+20
-4
@@ -919,9 +919,10 @@ class RecentProjects:
|
|||||||
puuid = str(entry.get("uuid", ""))
|
puuid = str(entry.get("uuid", ""))
|
||||||
title = str(entry.get("title", ""))
|
title = str(entry.get("title", ""))
|
||||||
words = checkInt(entry.get("words", 0), 0)
|
words = checkInt(entry.get("words", 0), 0)
|
||||||
|
chars = checkInt(entry.get("chars", 0), 0)
|
||||||
saved = checkInt(entry.get("time", 0), 0)
|
saved = checkInt(entry.get("time", 0), 0)
|
||||||
if path and title:
|
if path and title:
|
||||||
self._setEntry(puuid, path, title, words, saved)
|
self._setEntry(puuid, path, title, words, chars, saved)
|
||||||
except Exception:
|
except Exception:
|
||||||
logger.error("Could not load recent project cache")
|
logger.error("Could not load recent project cache")
|
||||||
logException()
|
logException()
|
||||||
@@ -954,7 +955,14 @@ class RecentProjects:
|
|||||||
try:
|
try:
|
||||||
if (remove := self._map.get(data.uuid)) and (remove != str(path)):
|
if (remove := self._map.get(data.uuid)) and (remove != str(path)):
|
||||||
self.remove(remove)
|
self.remove(remove)
|
||||||
self._setEntry(data.uuid, str(path), data.name, sum(data.currCounts), int(saved))
|
self._setEntry(
|
||||||
|
data.uuid,
|
||||||
|
str(path),
|
||||||
|
data.name,
|
||||||
|
sum(data.currCounts[:2]),
|
||||||
|
sum(data.currCounts[2:]),
|
||||||
|
int(saved),
|
||||||
|
)
|
||||||
self.saveCache()
|
self.saveCache()
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
@@ -967,9 +975,17 @@ class RecentProjects:
|
|||||||
self.saveCache()
|
self.saveCache()
|
||||||
return
|
return
|
||||||
|
|
||||||
def _setEntry(self, puuid: str, path: str, title: str, words: int, saved: int) -> None:
|
def _setEntry(
|
||||||
|
self, puuid: str, path: str, title: str, words: int, chars: int, saved: int
|
||||||
|
) -> None:
|
||||||
"""Set an entry in the recent projects record."""
|
"""Set an entry in the recent projects record."""
|
||||||
self._data[path] = {"uuid": puuid, "title": title, "words": words, "time": saved}
|
self._data[path] = {
|
||||||
|
"uuid": puuid,
|
||||||
|
"title": title,
|
||||||
|
"words": words,
|
||||||
|
"chars": chars,
|
||||||
|
"time": saved,
|
||||||
|
}
|
||||||
if puuid:
|
if puuid:
|
||||||
self._map[puuid] = path
|
self._map[puuid] = path
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -352,6 +352,10 @@ class nwLabels:
|
|||||||
nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Stats", "Words in Text"),
|
nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Stats", "Words in Text"),
|
||||||
nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Stats", "Words in Headings"),
|
nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Stats", "Words in Headings"),
|
||||||
}
|
}
|
||||||
|
STATS_DISPLAY: Final[dict[str, str]] = {
|
||||||
|
nwStats.CHARS: QT_TRANSLATE_NOOP("Stats", "Characters: {0} ({1})"),
|
||||||
|
nwStats.WORDS: QT_TRANSLATE_NOOP("Stats", "Words: {0} ({1})"),
|
||||||
|
}
|
||||||
BUILD_FMT: Final[dict[nwBuildFmt, str]] = {
|
BUILD_FMT: Final[dict[nwBuildFmt, str]] = {
|
||||||
nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
|
nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
|
||||||
nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
|
nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ SETTINGS_TEMPLATE: dict[str, tuple[type, T_BuildValue]] = {
|
|||||||
"text.includeSynopsis": (bool, False),
|
"text.includeSynopsis": (bool, False),
|
||||||
"text.includeComments": (bool, False),
|
"text.includeComments": (bool, False),
|
||||||
"text.includeStory": (bool, False),
|
"text.includeStory": (bool, False),
|
||||||
|
"text.includeNotes": (bool, False),
|
||||||
"text.includeKeywords": (bool, False),
|
"text.includeKeywords": (bool, False),
|
||||||
"text.includeBodyText": (bool, True),
|
"text.includeBodyText": (bool, True),
|
||||||
"text.ignoredKeywords": (str, ""),
|
"text.ignoredKeywords": (str, ""),
|
||||||
@@ -146,6 +147,7 @@ SETTINGS_LABELS = {
|
|||||||
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
|
"text.includeSynopsis": QT_TRANSLATE_NOOP("Builds", "Include Synopsis"),
|
||||||
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
|
"text.includeComments": QT_TRANSLATE_NOOP("Builds", "Include Comments"),
|
||||||
"text.includeStory": QT_TRANSLATE_NOOP("Builds", "Include Story Structure"),
|
"text.includeStory": QT_TRANSLATE_NOOP("Builds", "Include Story Structure"),
|
||||||
|
"text.includeNotes": QT_TRANSLATE_NOOP("Builds", "Include Manuscript Notes"),
|
||||||
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
|
"text.includeKeywords": QT_TRANSLATE_NOOP("Builds", "Include Keywords"),
|
||||||
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
|
"text.includeBodyText": QT_TRANSLATE_NOOP("Builds", "Include Body Text"),
|
||||||
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
|
"text.ignoredKeywords": QT_TRANSLATE_NOOP("Builds", "Ignore These Keywords"),
|
||||||
@@ -388,7 +390,7 @@ class BuildSettings:
|
|||||||
def setValue(self, key: str, value: T_BuildValue) -> None:
|
def setValue(self, key: str, value: T_BuildValue) -> None:
|
||||||
"""Set a specific value for a build setting."""
|
"""Set a specific value for a build setting."""
|
||||||
if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]):
|
if (d := SETTINGS_TEMPLATE.get(key)) and len(d) == 2 and isinstance(value, d[0]):
|
||||||
self._changed = value != self._settings[key]
|
self._changed |= (value != self._settings[key])
|
||||||
self._settings[key] = value
|
self._settings[key] = value
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -317,6 +317,7 @@ class NWBuildDocument:
|
|||||||
bldObj.setCommentType(nwComment.SYNOPSIS, self._build.getBool("text.includeSynopsis"))
|
bldObj.setCommentType(nwComment.SYNOPSIS, self._build.getBool("text.includeSynopsis"))
|
||||||
bldObj.setCommentType(nwComment.SHORT, self._build.getBool("text.includeSynopsis"))
|
bldObj.setCommentType(nwComment.SHORT, self._build.getBool("text.includeSynopsis"))
|
||||||
bldObj.setCommentType(nwComment.STORY, self._build.getBool("text.includeStory"))
|
bldObj.setCommentType(nwComment.STORY, self._build.getBool("text.includeStory"))
|
||||||
|
bldObj.setCommentType(nwComment.NOTE, self._build.getBool("text.includeNotes"))
|
||||||
|
|
||||||
if isinstance(bldObj, ToHtml):
|
if isinstance(bldObj, ToHtml):
|
||||||
bldObj.setStyles(self._build.getBool("html.addStyles"))
|
bldObj.setStyles(self._build.getBool("html.addStyles"))
|
||||||
|
|||||||
@@ -131,6 +131,12 @@ class Index:
|
|||||||
self._novelExtra = extra
|
self._novelExtra = extra
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def setItemClass(self, tHandle: str, itemClass: nwItemClass) -> None:
|
||||||
|
"""Update the class for all tags of a handle."""
|
||||||
|
logger.info("Updating class for '%s'", tHandle)
|
||||||
|
self._tagsIndex.updateClass(tHandle, itemClass.name)
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Public Methods
|
# Public Methods
|
||||||
##
|
##
|
||||||
@@ -612,6 +618,10 @@ class Index:
|
|||||||
"""Return all story structure keys."""
|
"""Return all story structure keys."""
|
||||||
return self._itemIndex.allStoryKeys()
|
return self._itemIndex.allStoryKeys()
|
||||||
|
|
||||||
|
def getNoteKeys(self) -> set[str]:
|
||||||
|
"""Return all note comment keys."""
|
||||||
|
return self._itemIndex.allNoteKeys()
|
||||||
|
|
||||||
def novelStructure(
|
def novelStructure(
|
||||||
self, rootHandle: str | None = None, activeOnly: bool = True
|
self, rootHandle: str | None = None, activeOnly: bool = True
|
||||||
) -> Iterable[tuple[str, str, str, IndexHeading]]:
|
) -> Iterable[tuple[str, str, str, IndexHeading]]:
|
||||||
@@ -854,6 +864,15 @@ class TagsIndex:
|
|||||||
x.get("name", "") for x in self._tags.values() if x.get("class", "") == className
|
x.get("name", "") for x in self._tags.values() if x.get("class", "") == className
|
||||||
]
|
]
|
||||||
|
|
||||||
|
def updateClass(self, tHandle: str, className: str) -> None:
|
||||||
|
"""Update the class name of an item. This must be called when a
|
||||||
|
document moves to another class.
|
||||||
|
"""
|
||||||
|
for entry in self._tags.values():
|
||||||
|
if entry.get("handle") == tHandle:
|
||||||
|
entry["class"] = className
|
||||||
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Pack/Unpack
|
# Pack/Unpack
|
||||||
##
|
##
|
||||||
@@ -905,11 +924,12 @@ class IndexCache:
|
|||||||
which provides lookup capabilities and caching for shared data.
|
which provides lookup capabilities and caching for shared data.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = ("story", "tags")
|
__slots__ = ("note", "story", "tags")
|
||||||
|
|
||||||
def __init__(self, tagsIndex: TagsIndex) -> None:
|
def __init__(self, tagsIndex: TagsIndex) -> None:
|
||||||
self.tags: TagsIndex = tagsIndex
|
self.tags: TagsIndex = tagsIndex
|
||||||
self.story: set[str] = set()
|
self.story: set[str] = set()
|
||||||
|
self.note: set[str] = set()
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -964,6 +984,10 @@ class ItemIndex:
|
|||||||
"""Return all story structure keys."""
|
"""Return all story structure keys."""
|
||||||
return self._cache.story.copy()
|
return self._cache.story.copy()
|
||||||
|
|
||||||
|
def allNoteKeys(self) -> set[str]:
|
||||||
|
"""Return all note comment keys."""
|
||||||
|
return self._cache.note.copy()
|
||||||
|
|
||||||
def allItemTags(self, tHandle: str) -> list[str]:
|
def allItemTags(self, tHandle: str) -> list[str]:
|
||||||
"""Get all tags set for headings of an item."""
|
"""Get all tags set for headings of an item."""
|
||||||
if tHandle in self._items:
|
if tHandle in self._items:
|
||||||
|
|||||||
@@ -316,6 +316,9 @@ class IndexHeading:
|
|||||||
case "story" if key:
|
case "story" if key:
|
||||||
self._cache.story.add(key)
|
self._cache.story.add(key)
|
||||||
self._comments[f"story.{key}"] = str(text)
|
self._comments[f"story.{key}"] = str(text)
|
||||||
|
case "note" if key:
|
||||||
|
self._cache.note.add(key)
|
||||||
|
self._comments[f"note.{key}"] = str(text)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setTag(self, tag: str) -> None:
|
def setTag(self, tag: str) -> None:
|
||||||
@@ -395,7 +398,7 @@ class IndexHeading:
|
|||||||
self.addReference(tag, keyword)
|
self.addReference(tag, keyword)
|
||||||
else:
|
else:
|
||||||
raise ValueError("Heading reference contains an invalid keyword")
|
raise ValueError("Heading reference contains an invalid keyword")
|
||||||
elif key == "summary" or key.startswith("story"):
|
elif key == "summary" or key.startswith(("story", "note")):
|
||||||
comment, _, kind = str(key).partition(".")
|
comment, _, kind = str(key).partition(".")
|
||||||
self.setComment(comment, compact(kind), str(entry))
|
self.setComment(comment, compact(kind), str(entry))
|
||||||
else:
|
else:
|
||||||
|
|||||||
+35
-24
@@ -53,10 +53,10 @@ class NWItem:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
__slots__ = (
|
__slots__ = (
|
||||||
"_active", "_charCount", "_class", "_cursorPos", "_expanded",
|
"_active", "_charCount", "_charInit", "_class", "_cursorPos",
|
||||||
"_handle", "_heading", "_import", "_initCount", "_layout", "_name",
|
"_expanded", "_handle", "_heading", "_import", "_layout", "_name",
|
||||||
"_order", "_paraCount", "_parent", "_project", "_root", "_status",
|
"_order", "_paraCount", "_parent", "_project", "_root", "_status",
|
||||||
"_type", "_wordCount",
|
"_type", "_wordCount", "_wordInit",
|
||||||
)
|
)
|
||||||
|
|
||||||
def __init__(self, project: NWProject, handle: str) -> None:
|
def __init__(self, project: NWProject, handle: str) -> None:
|
||||||
@@ -81,7 +81,8 @@ class NWItem:
|
|||||||
self._wordCount = 0 # Current word count
|
self._wordCount = 0 # Current word count
|
||||||
self._paraCount = 0 # Current paragraph count
|
self._paraCount = 0 # Current paragraph count
|
||||||
self._cursorPos = 0 # Last cursor position
|
self._cursorPos = 0 # Last cursor position
|
||||||
self._initCount = 0 # Initial word count
|
self._wordInit = 0 # Initial character count
|
||||||
|
self._charInit = 0 # Initial word count
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -164,9 +165,13 @@ class NWItem:
|
|||||||
def paraCount(self) -> int:
|
def paraCount(self) -> int:
|
||||||
return self._paraCount
|
return self._paraCount
|
||||||
|
|
||||||
|
@property
|
||||||
|
def mainCount(self) -> int:
|
||||||
|
return self._charCount if CONFIG.useCharCount else self._wordCount
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def initCount(self) -> int:
|
def initCount(self) -> int:
|
||||||
return self._initCount
|
return self._wordInit if CONFIG.useCharCount else self._charInit
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def cursorPos(self) -> int:
|
def cursorPos(self) -> int:
|
||||||
@@ -257,7 +262,8 @@ class NWItem:
|
|||||||
self._paraCount = 0
|
self._paraCount = 0
|
||||||
self._cursorPos = 0
|
self._cursorPos = 0
|
||||||
|
|
||||||
self._initCount = self._wordCount
|
self._wordInit = self._charCount
|
||||||
|
self._charInit = self._wordCount
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
@@ -265,23 +271,24 @@ class NWItem:
|
|||||||
def duplicate(cls, source: NWItem, handle: str) -> NWItem:
|
def duplicate(cls, source: NWItem, handle: str) -> NWItem:
|
||||||
"""Make a copy of an item."""
|
"""Make a copy of an item."""
|
||||||
new = cls(source._project, handle)
|
new = cls(source._project, handle)
|
||||||
new._name = source._name
|
new._name = source._name
|
||||||
new._parent = source._parent
|
new._parent = source._parent
|
||||||
new._root = source._root
|
new._root = source._root
|
||||||
new._order = source._order
|
new._order = source._order
|
||||||
new._type = source._type
|
new._type = source._type
|
||||||
new._class = source._class
|
new._class = source._class
|
||||||
new._layout = source._layout
|
new._layout = source._layout
|
||||||
new._status = source._status
|
new._status = source._status
|
||||||
new._import = source._import
|
new._import = source._import
|
||||||
new._active = source._active
|
new._active = source._active
|
||||||
new._expanded = source._expanded
|
new._expanded = source._expanded
|
||||||
new._heading = source._heading
|
new._heading = source._heading
|
||||||
new._charCount = source._charCount
|
new._charCount = source._charCount
|
||||||
new._wordCount = source._wordCount
|
new._wordCount = source._wordCount
|
||||||
new._paraCount = source._paraCount
|
new._paraCount = source._paraCount
|
||||||
new._cursorPos = source._cursorPos
|
new._cursorPos = source._cursorPos
|
||||||
new._initCount = source._initCount
|
new._wordInit = source._wordInit
|
||||||
|
new._charInit = source._charInit
|
||||||
return new
|
return new
|
||||||
|
|
||||||
##
|
##
|
||||||
@@ -428,7 +435,11 @@ class NWItem:
|
|||||||
"""
|
"""
|
||||||
if self._parent is not None:
|
if self._parent is not None:
|
||||||
# Only update for child items
|
# Only update for child items
|
||||||
self.setClass(itemClass)
|
if itemClass != self._class:
|
||||||
|
self.setClass(itemClass)
|
||||||
|
if self._type == nwItemType.FILE:
|
||||||
|
# Notify the index of the class change
|
||||||
|
self._project.index.setItemClass(self._handle, itemClass)
|
||||||
|
|
||||||
if self._layout == nwItemLayout.NO_LAYOUT:
|
if self._layout == nwItemLayout.NO_LAYOUT:
|
||||||
# If no layout is set, pick one
|
# If no layout is set, pick one
|
||||||
|
|||||||
@@ -166,7 +166,7 @@ class ProjectNode:
|
|||||||
|
|
||||||
def updateCount(self, propagate: bool = True) -> None:
|
def updateCount(self, propagate: bool = True) -> None:
|
||||||
"""Update counts, and propagate upwards in the tree."""
|
"""Update counts, and propagate upwards in the tree."""
|
||||||
self._count = self._item.wordCount + sum(c._count for c in self._children) # noqa: SLF001
|
self._count = self._item.mainCount + sum(c._count for c in self._children) # noqa: SLF001
|
||||||
self._cache[C_COUNT_TEXT] = f"{self._count:n}"
|
self._cache[C_COUNT_TEXT] = f"{self._count:n}"
|
||||||
if propagate and (parent := self._parent):
|
if propagate and (parent := self._parent):
|
||||||
parent.updateCount()
|
parent.updateCount()
|
||||||
|
|||||||
@@ -367,7 +367,7 @@ class NWProject:
|
|||||||
# Often, the index needs to be rebuilt when updating format
|
# Often, the index needs to be rebuilt when updating format
|
||||||
self._index.rebuild()
|
self._index.rebuild()
|
||||||
|
|
||||||
self.updateWordCounts()
|
self.updateCounts()
|
||||||
self._session.startSession()
|
self._session.startSession()
|
||||||
self.setProjectChanged(False)
|
self.setProjectChanged(False)
|
||||||
self._valid = True
|
self._valid = True
|
||||||
@@ -397,7 +397,7 @@ class NWProject:
|
|||||||
else:
|
else:
|
||||||
self._data.incSaveCount()
|
self._data.incSaveCount()
|
||||||
|
|
||||||
self.updateWordCounts()
|
self.updateCounts()
|
||||||
self.countStatus()
|
self.countStatus()
|
||||||
|
|
||||||
xmlWriter = self._storage.getXmlWriter()
|
xmlWriter = self._storage.getXmlWriter()
|
||||||
@@ -515,10 +515,10 @@ class NWProject:
|
|||||||
# Class Methods
|
# Class Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
def updateWordCounts(self) -> None:
|
def updateCounts(self) -> None:
|
||||||
"""Update the total word count values."""
|
"""Update the total word and character count values."""
|
||||||
novel, notes = self._tree.sumWords()
|
wNovel, wNotes, cNovel, cNotes = self._tree.sumCounts()
|
||||||
self._data.setCurrCounts(novel=novel, notes=notes)
|
self._data.setCurrCounts(wNovel=wNovel, wNotes=wNotes, cNovel=cNovel, cNotes=cNotes)
|
||||||
return
|
return
|
||||||
|
|
||||||
def countStatus(self) -> None:
|
def countStatus(self) -> None:
|
||||||
|
|||||||
@@ -66,8 +66,8 @@ class NWProjectData:
|
|||||||
self._spellLang = None
|
self._spellLang = None
|
||||||
|
|
||||||
# Project Dictionaries
|
# Project Dictionaries
|
||||||
self._initCounts = [0, 0]
|
self._initCounts = [0, 0, 0, 0]
|
||||||
self._currCounts = [0, 0]
|
self._currCounts = [0, 0, 0, 0]
|
||||||
self._lastHandle: dict[str, str | None] = {
|
self._lastHandle: dict[str, str | None] = {
|
||||||
"editor": None,
|
"editor": None,
|
||||||
"viewer": None,
|
"viewer": None,
|
||||||
@@ -148,18 +148,18 @@ class NWProjectData:
|
|||||||
return self._spellLang
|
return self._spellLang
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def initCounts(self) -> tuple[int, int]:
|
def initCounts(self) -> tuple[int, int, int, int]:
|
||||||
"""Return the initial count of words for novel and note
|
"""Return the initial count of words and characters for novel
|
||||||
documents.
|
and note documents.
|
||||||
"""
|
"""
|
||||||
return self._initCounts[0], self._initCounts[1]
|
return self._initCounts[0], self._initCounts[1], self._initCounts[2], self._initCounts[3]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def currCounts(self) -> tuple[int, int]:
|
def currCounts(self) -> tuple[int, int, int, int]:
|
||||||
"""Return the current count of words for novel and note
|
"""Return the current count of words and characters for novel
|
||||||
documents.
|
and note documents.
|
||||||
"""
|
"""
|
||||||
return self._currCounts[0], self._currCounts[1]
|
return self._currCounts[0], self._currCounts[1], self._currCounts[2], self._currCounts[3]
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def lastHandle(self) -> dict[str, str | None]:
|
def lastHandle(self) -> dict[str, str | None]:
|
||||||
@@ -301,22 +301,40 @@ class NWProjectData:
|
|||||||
self._project.setProjectChanged(True)
|
self._project.setProjectChanged(True)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setInitCounts(self, novel: Any = None, notes: Any = None) -> None:
|
def setInitCounts(
|
||||||
"""Set the word count totals for novel and note files."""
|
self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
|
||||||
if novel is not None:
|
) -> None:
|
||||||
self._initCounts[0] = checkInt(novel, 0)
|
"""Set the count totals for novel and note files."""
|
||||||
self._currCounts[0] = checkInt(novel, 0)
|
if wNovel is not None:
|
||||||
if notes is not None:
|
count = checkInt(wNovel, 0)
|
||||||
self._initCounts[1] = checkInt(notes, 0)
|
self._initCounts[0] = count
|
||||||
self._currCounts[1] = checkInt(notes, 0)
|
self._currCounts[0] = count
|
||||||
|
if wNotes is not None:
|
||||||
|
count = checkInt(wNotes, 0)
|
||||||
|
self._initCounts[1] = count
|
||||||
|
self._currCounts[1] = count
|
||||||
|
if cNovel is not None:
|
||||||
|
count = checkInt(cNovel, 0)
|
||||||
|
self._initCounts[2] = count
|
||||||
|
self._currCounts[2] = count
|
||||||
|
if cNotes is not None:
|
||||||
|
count = checkInt(cNotes, 0)
|
||||||
|
self._initCounts[3] = count
|
||||||
|
self._currCounts[3] = count
|
||||||
return
|
return
|
||||||
|
|
||||||
def setCurrCounts(self, novel: Any = None, notes: Any = None) -> None:
|
def setCurrCounts(
|
||||||
"""Set the word count totals for novel and note files."""
|
self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
|
||||||
if novel is not None:
|
) -> None:
|
||||||
self._currCounts[0] = checkInt(novel, 0)
|
"""Set the count totals for novel and note files."""
|
||||||
if notes is not None:
|
if wNovel is not None:
|
||||||
self._currCounts[1] = checkInt(notes, 0)
|
self._currCounts[0] = checkInt(wNovel, 0)
|
||||||
|
if wNotes is not None:
|
||||||
|
self._currCounts[1] = checkInt(wNotes, 0)
|
||||||
|
if cNovel is not None:
|
||||||
|
self._currCounts[2] = checkInt(cNovel, 0)
|
||||||
|
if cNotes is not None:
|
||||||
|
self._currCounts[3] = checkInt(cNotes, 0)
|
||||||
return
|
return
|
||||||
|
|
||||||
def setAutoReplace(self, value: dict) -> None:
|
def setAutoReplace(self, value: dict) -> None:
|
||||||
|
|||||||
@@ -46,7 +46,7 @@ if TYPE_CHECKING:
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
FILE_VERSION = "1.5" # The current project file format version
|
FILE_VERSION = "1.5" # The current project file format version
|
||||||
FILE_REVISION = "4" # The current project file format revision
|
FILE_REVISION = "5" # The current project file format revision
|
||||||
HEX_VERSION = 0x0105
|
HEX_VERSION = 0x0105
|
||||||
|
|
||||||
NUM_VERSION = {
|
NUM_VERSION = {
|
||||||
@@ -109,6 +109,8 @@ class ProjectXMLReader:
|
|||||||
Rev 3: Added TEMPLATE class. 2.3.
|
Rev 3: Added TEMPLATE class. 2.3.
|
||||||
Rev 4: Added shape attribute to status and importance entry
|
Rev 4: Added shape attribute to status and importance entry
|
||||||
nodes. 2.5.
|
nodes. 2.5.
|
||||||
|
Rev 5: Added novelChars and notesChars attributes to content
|
||||||
|
node. 2.7 RC 1.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, path: str | Path) -> None:
|
def __init__(self, path: str | Path) -> None:
|
||||||
@@ -286,9 +288,9 @@ class ProjectXMLReader:
|
|||||||
elif xItem.tag == "spellLang": # Changed to spellChecking in 1.5
|
elif xItem.tag == "spellLang": # Changed to spellChecking in 1.5
|
||||||
data.setSpellLang(xItem.text)
|
data.setSpellLang(xItem.text)
|
||||||
elif xItem.tag == "novelWordCount": # Moved to content attribute in 1.5
|
elif xItem.tag == "novelWordCount": # Moved to content attribute in 1.5
|
||||||
data.setInitCounts(novel=xItem.text)
|
data.setInitCounts(wNovel=xItem.text)
|
||||||
elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5
|
elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5
|
||||||
data.setInitCounts(notes=xItem.text)
|
data.setInitCounts(wNotes=xItem.text)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -298,8 +300,13 @@ class ProjectXMLReader:
|
|||||||
"""Parse the content section of the XML file."""
|
"""Parse the content section of the XML file."""
|
||||||
logger.debug("Parsing <content> section")
|
logger.debug("Parsing <content> section")
|
||||||
|
|
||||||
data.setInitCounts(novel=xSection.attrib.get("novelWords", None)) # Moved in 1.5
|
# Moved in 1.5
|
||||||
data.setInitCounts(notes=xSection.attrib.get("notesWords", None)) # Moved in 1.5
|
data.setInitCounts(
|
||||||
|
wNovel=xSection.attrib.get("novelWords", None),
|
||||||
|
wNotes=xSection.attrib.get("notesWords", None),
|
||||||
|
cNovel=xSection.attrib.get("novelChars", None),
|
||||||
|
cNotes=xSection.attrib.get("notesChars", None),
|
||||||
|
)
|
||||||
|
|
||||||
for xItem in xSection:
|
for xItem in xSection:
|
||||||
if xItem.tag != "item":
|
if xItem.tag != "item":
|
||||||
@@ -527,10 +534,13 @@ class ProjectXMLWriter:
|
|||||||
self._packSingleValue(xImport, "entry", label, attrib=attrib)
|
self._packSingleValue(xImport, "entry", label, attrib=attrib)
|
||||||
|
|
||||||
# Save Tree Content
|
# Save Tree Content
|
||||||
|
counts = data.currCounts
|
||||||
contAttr = {
|
contAttr = {
|
||||||
"items": str(len(content)),
|
"items": str(len(content)),
|
||||||
"novelWords": str(data.currCounts[0]),
|
"novelWords": str(counts[0]),
|
||||||
"notesWords": str(data.currCounts[1]),
|
"notesWords": str(counts[1]),
|
||||||
|
"novelChars": str(counts[2]),
|
||||||
|
"notesChars": str(counts[3]),
|
||||||
}
|
}
|
||||||
|
|
||||||
xContent = ET.SubElement(xRoot, "content", attrib=contAttr)
|
xContent = ET.SubElement(xRoot, "content", attrib=contAttr)
|
||||||
|
|||||||
@@ -79,29 +79,36 @@ class NWSessionLog:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
now = time()
|
now = time()
|
||||||
iNovel, iNotes = self._project.data.initCounts
|
iWNovel, iWNotes, iCNovel, iCNotes = self._project.data.initCounts
|
||||||
cNovel, cNotes = self._project.data.currCounts
|
cWNovel, cWNotes, cCNovel, cCNotes = self._project.data.currCounts
|
||||||
iTotal = iNovel + iNotes
|
iWTotal = iWNovel + iWNotes
|
||||||
wDiff = cNovel + cNotes - iTotal
|
iCTotal = iCNovel + iCNotes
|
||||||
|
wDiff = cWNovel + cWNotes - iWTotal
|
||||||
|
cDiff = cCNovel + cCNotes - iCTotal
|
||||||
sTime = now - self._start
|
sTime = now - self._start
|
||||||
|
|
||||||
logger.info("The session lasted %d sec and added %d words", int(sTime), wDiff)
|
logger.info(
|
||||||
if sTime < 300 and wDiff == 0:
|
"The session lasted %d sec and added %d words abd %d characters",
|
||||||
|
int(sTime), wDiff, cDiff
|
||||||
|
)
|
||||||
|
if sTime < 300 and (wDiff == 0 or cDiff == 0):
|
||||||
logger.info("Session too short, skipping log entry")
|
logger.info("Session too short, skipping log entry")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if not sessFile.exists():
|
if not sessFile.exists():
|
||||||
with open(sessFile, mode="w", encoding="utf-8") as fObj:
|
with open(sessFile, mode="w", encoding="utf-8") as fObj:
|
||||||
fObj.write(self.createInitial(iTotal))
|
fObj.write(self.createInitial(iWTotal))
|
||||||
|
|
||||||
with open(sessFile, mode="a+", encoding="utf-8") as fObj:
|
with open(sessFile, mode="a+", encoding="utf-8") as fObj:
|
||||||
fObj.write(self.createRecord(
|
fObj.write(self.createRecord(
|
||||||
start=formatTimeStamp(self._start),
|
start=formatTimeStamp(self._start),
|
||||||
end=formatTimeStamp(now),
|
end=formatTimeStamp(now),
|
||||||
novel=cNovel,
|
novel=cWNovel,
|
||||||
notes=cNotes,
|
notes=cWNotes,
|
||||||
idle=round(idleTime)
|
idle=round(idleTime),
|
||||||
|
cnovel=cCNovel,
|
||||||
|
cnotes=cCNotes,
|
||||||
))
|
))
|
||||||
|
|
||||||
except Exception:
|
except Exception:
|
||||||
@@ -129,10 +136,19 @@ class NWSessionLog:
|
|||||||
data = json.dumps({"type": "initial", "offset": total})
|
data = json.dumps({"type": "initial", "offset": total})
|
||||||
return f"{data}\n"
|
return f"{data}\n"
|
||||||
|
|
||||||
def createRecord(self, start: str, end: str, novel: int, notes: int, idle: int) -> str:
|
def createRecord(
|
||||||
|
self, start: str, end: str, novel: int, notes: int, idle: int,
|
||||||
|
cnovel: int = 0, cnotes: int = 0,
|
||||||
|
) -> str:
|
||||||
"""Low level function to create a log record."""
|
"""Low level function to create a log record."""
|
||||||
data = json.dumps({
|
data = json.dumps({
|
||||||
"type": "record", "start": start, "end": end,
|
"type": "record",
|
||||||
"novel": novel, "notes": notes, "idle": idle,
|
"start": start,
|
||||||
|
"end": end,
|
||||||
|
"novel": novel,
|
||||||
|
"notes": notes,
|
||||||
|
"cnovel": cnovel,
|
||||||
|
"cnotes": cnotes,
|
||||||
|
"idle": idle,
|
||||||
})
|
})
|
||||||
return f"{data}\n"
|
return f"{data}\n"
|
||||||
|
|||||||
@@ -410,16 +410,20 @@ class NWTree:
|
|||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
def sumWords(self) -> tuple[int, int]:
|
def sumCounts(self) -> tuple[int, int, int, int]:
|
||||||
"""Loop over all entries and add up the word counts."""
|
"""Loop over all entries and add up the word and char counts."""
|
||||||
noteWords = 0
|
|
||||||
novelWords = 0
|
novelWords = 0
|
||||||
|
notesWords = 0
|
||||||
|
novelChars = 0
|
||||||
|
notesChars = 0
|
||||||
for item in self._items.values():
|
for item in self._items.values():
|
||||||
if item.itemLayout == nwItemLayout.NOTE:
|
if item.itemLayout == nwItemLayout.NOTE:
|
||||||
noteWords += item.wordCount
|
notesWords += item.wordCount
|
||||||
|
notesChars += item.charCount
|
||||||
elif item.itemLayout == nwItemLayout.DOCUMENT:
|
elif item.itemLayout == nwItemLayout.DOCUMENT:
|
||||||
novelWords += item.wordCount
|
novelWords += item.wordCount
|
||||||
return novelWords, noteWords
|
novelChars += item.charCount
|
||||||
|
return novelWords, notesWords, novelChars, notesChars
|
||||||
|
|
||||||
##
|
##
|
||||||
# Tree Item Methods
|
# Tree Item Methods
|
||||||
|
|||||||
@@ -226,6 +226,14 @@ class GuiPreferences(NDialog):
|
|||||||
self.tr("Turn off to use the Qt font dialog, which may have more options.")
|
self.tr("Turn off to use the Qt font dialog, which may have more options.")
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# Use Character Count
|
||||||
|
self.useCharCount = NSwitch(self)
|
||||||
|
self.useCharCount.setChecked(CONFIG.useCharCount)
|
||||||
|
self.mainForm.addRow(
|
||||||
|
self.tr("Prefer character count over word count"), self.useCharCount,
|
||||||
|
self.tr("Display character count instead where available.")
|
||||||
|
)
|
||||||
|
|
||||||
# Document Style
|
# Document Style
|
||||||
# ==============
|
# ==============
|
||||||
|
|
||||||
@@ -957,21 +965,24 @@ class GuiPreferences(NDialog):
|
|||||||
refreshTree = False
|
refreshTree = False
|
||||||
|
|
||||||
# Appearance
|
# Appearance
|
||||||
guiLocale = self.guiLocale.currentData()
|
guiLocale = self.guiLocale.currentData()
|
||||||
guiTheme = self.guiTheme.currentData()
|
guiTheme = self.guiTheme.currentData()
|
||||||
iconTheme = self.iconTheme.currentData()
|
iconTheme = self.iconTheme.currentData()
|
||||||
|
useCharCount = self.useCharCount.isChecked()
|
||||||
|
|
||||||
updateTheme |= CONFIG.guiTheme != guiTheme
|
updateTheme |= CONFIG.guiTheme != guiTheme
|
||||||
updateTheme |= CONFIG.iconTheme != iconTheme
|
updateTheme |= CONFIG.iconTheme != iconTheme
|
||||||
needsRestart |= CONFIG.guiLocale != guiLocale
|
needsRestart |= CONFIG.guiLocale != guiLocale
|
||||||
needsRestart |= CONFIG.guiFont != self._guiFont
|
needsRestart |= CONFIG.guiFont != self._guiFont
|
||||||
|
refreshTree |= CONFIG.useCharCount != useCharCount
|
||||||
|
|
||||||
CONFIG.guiLocale = guiLocale
|
CONFIG.guiLocale = guiLocale
|
||||||
CONFIG.guiTheme = guiTheme
|
CONFIG.guiTheme = guiTheme
|
||||||
CONFIG.iconTheme = iconTheme
|
CONFIG.iconTheme = iconTheme
|
||||||
CONFIG.hideVScroll = self.hideVScroll.isChecked()
|
CONFIG.hideVScroll = self.hideVScroll.isChecked()
|
||||||
CONFIG.hideHScroll = self.hideHScroll.isChecked()
|
CONFIG.hideHScroll = self.hideHScroll.isChecked()
|
||||||
CONFIG.nativeFont = self.nativeFont.isChecked()
|
CONFIG.nativeFont = self.nativeFont.isChecked()
|
||||||
|
CONFIG.useCharCount = useCharCount
|
||||||
CONFIG.setGuiFont(self._guiFont)
|
CONFIG.setGuiFont(self._guiFont)
|
||||||
|
|
||||||
# Document Style
|
# Document Style
|
||||||
|
|||||||
@@ -614,7 +614,8 @@ class Tokenizer(ABC):
|
|||||||
tStyle |= BlockFmt.JUSTIFY
|
tStyle |= BlockFmt.JUSTIFY
|
||||||
|
|
||||||
if cStyle in (
|
if cStyle in (
|
||||||
nwComment.SYNOPSIS, nwComment.SHORT, nwComment.PLAIN, nwComment.STORY
|
nwComment.SYNOPSIS, nwComment.SHORT, nwComment.PLAIN,
|
||||||
|
nwComment.STORY, nwComment.NOTE,
|
||||||
):
|
):
|
||||||
bStyle = COMMENT_STYLE[cStyle]
|
bStyle = COMMENT_STYLE[cStyle]
|
||||||
tLine, tFmt = self._formatComment(bStyle, cKey, cText)
|
tLine, tFmt = self._formatComment(bStyle, cKey, cText)
|
||||||
|
|||||||
@@ -3,15 +3,16 @@ novelWriter – GUI Document Editor
|
|||||||
=================================
|
=================================
|
||||||
|
|
||||||
File History:
|
File History:
|
||||||
Created: 2018-09-29 [0.0.1] GuiDocEditor
|
Created: 2018-09-29 [0.0.1] GuiDocEditor
|
||||||
Created: 2019-04-22 [0.0.1] BackgroundWordCounter
|
Created: 2019-04-22 [0.0.1] BackgroundWordCounter
|
||||||
Created: 2019-09-29 [0.2.1] GuiDocEditSearch
|
Created: 2019-09-29 [0.2.1] GuiDocEditSearch
|
||||||
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
|
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
|
||||||
Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
|
Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
|
||||||
Created: 2020-06-27 [0.10] GuiDocEditFooter
|
Created: 2020-06-27 [0.10] GuiDocEditFooter
|
||||||
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
|
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
|
||||||
Created: 2023-11-06 [2.2b1] MetaCompleter
|
Created: 2023-11-06 [2.2b1] MetaCompleter
|
||||||
Created: 2023-11-07 [2.2b1] GuiDocToolBar
|
Created: 2023-11-07 [2.2b1] GuiDocToolBar
|
||||||
|
Extended: 2025-05-18 [2.7rc1] CommandCompleter
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
|
Copyright (C) 2018 Veronica Berglyd Olsen and novelWriter contributors
|
||||||
@@ -55,7 +56,9 @@ from novelwriter import CONFIG, SHARED
|
|||||||
from novelwriter.common import (
|
from novelwriter.common import (
|
||||||
decodeMimeHandles, fontMatcher, minmax, qtAddAction, qtLambda, transferCase
|
decodeMimeHandles, fontMatcher, minmax, qtAddAction, qtLambda, transferCase
|
||||||
)
|
)
|
||||||
from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode
|
from novelwriter.constants import (
|
||||||
|
nwConst, nwKeyWords, nwLabels, nwShortcode, nwStats, nwUnicode, trStats
|
||||||
|
)
|
||||||
from novelwriter.core.document import NWDocument
|
from novelwriter.core.document import NWDocument
|
||||||
from novelwriter.enum import (
|
from novelwriter.enum import (
|
||||||
nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass,
|
nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass,
|
||||||
@@ -147,7 +150,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
self._autoReplace = TextAutoReplace()
|
self._autoReplace = TextAutoReplace()
|
||||||
|
|
||||||
# Completer
|
# Completer
|
||||||
self._completer = MetaCompleter(self)
|
self._completer = CommandCompleter(self)
|
||||||
self._completer.complete.connect(self._insertCompletion)
|
self._completer.complete.connect(self._insertCompletion)
|
||||||
|
|
||||||
# Create Custom Document
|
# Create Custom Document
|
||||||
@@ -321,6 +324,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
"""
|
"""
|
||||||
# Auto-Replace
|
# Auto-Replace
|
||||||
self._autoReplace.initSettings()
|
self._autoReplace.initSettings()
|
||||||
|
self.docFooter.initSettings()
|
||||||
|
|
||||||
# Reload spell check and dictionaries
|
# Reload spell check and dictionaries
|
||||||
SHARED.updateSpellCheckLanguage()
|
SHARED.updateSpellCheckLanguage()
|
||||||
@@ -1076,13 +1080,16 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
|
|
||||||
if (block := self._qDocument.findBlock(pos)).isValid():
|
if (block := self._qDocument.findBlock(pos)).isValid():
|
||||||
text = block.text()
|
text = block.text()
|
||||||
if text.startswith("@") and added + removed == 1:
|
if text and text[0] in "@%" and added + removed == 1:
|
||||||
# Only run on single character changes, or it will trigger
|
# Only run on single character changes, or it will trigger
|
||||||
# at unwanted times when other changes are made to the document
|
# at unwanted times when other changes are made to the document
|
||||||
cursor = self.textCursor()
|
cursor = self.textCursor()
|
||||||
bPos = cursor.positionInBlock()
|
bPos = cursor.positionInBlock()
|
||||||
if bPos > 0 and (viewport := self.viewport()):
|
if bPos > 0 and (viewport := self.viewport()):
|
||||||
show = self._completer.updateText(text, bPos)
|
if text[0] == "@":
|
||||||
|
show = self._completer.updateMetaText(text, bPos)
|
||||||
|
else:
|
||||||
|
show = self._completer.updateCommentText(text, bPos)
|
||||||
point = self.cursorRect().bottomRight()
|
point = self.cursorRect().bottomRight()
|
||||||
self._completer.move(viewport.mapToGlobal(point))
|
self._completer.move(viewport.mapToGlobal(point))
|
||||||
self._completer.setVisible(show)
|
self._completer.setVisible(show)
|
||||||
@@ -1233,7 +1240,8 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
"""Process the word counter's finished signal."""
|
"""Process the word counter's finished signal."""
|
||||||
if self._docHandle and self._nwItem:
|
if self._docHandle and self._nwItem:
|
||||||
logger.debug("Updating word count")
|
logger.debug("Updating word count")
|
||||||
needsRefresh = wCount != self._nwItem.wordCount
|
mCount = cCount if CONFIG.useCharCount else wCount
|
||||||
|
needsRefresh = mCount != self._nwItem.mainCount
|
||||||
self._nwItem.setCharCount(cCount)
|
self._nwItem.setCharCount(cCount)
|
||||||
self._nwItem.setWordCount(wCount)
|
self._nwItem.setWordCount(wCount)
|
||||||
self._nwItem.setParaCount(pCount)
|
self._nwItem.setParaCount(pCount)
|
||||||
@@ -1241,7 +1249,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
self._nwItem.notifyToRefresh()
|
self._nwItem.notifyToRefresh()
|
||||||
if not self.textCursor().hasSelection():
|
if not self.textCursor().hasSelection():
|
||||||
# Selection counter should take precedence (#2155)
|
# Selection counter should take precedence (#2155)
|
||||||
self.docFooter.updateWordCount(wCount, False)
|
self.docFooter.updateMainCount(mCount, False)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -1254,7 +1262,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
self._timerSel.start()
|
self._timerSel.start()
|
||||||
else:
|
else:
|
||||||
self._timerSel.stop()
|
self._timerSel.stop()
|
||||||
self.docFooter.updateWordCount(0, False)
|
self.docFooter.updateMainCount(0, False)
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
@@ -1271,8 +1279,7 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
|
||||||
"""Update the counts on the counter's finished signal."""
|
"""Update the counts on the counter's finished signal."""
|
||||||
if self._docHandle and self._nwItem:
|
if self._docHandle and self._nwItem:
|
||||||
logger.debug("User selected %d words", wCount)
|
self.docFooter.updateMainCount(cCount if CONFIG.useCharCount else wCount, True)
|
||||||
self.docFooter.updateWordCount(wCount, True)
|
|
||||||
self._timerSel.stop()
|
self._timerSel.stop()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -2070,13 +2077,13 @@ class GuiDocEditor(QPlainTextEdit):
|
|||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
class MetaCompleter(QMenu):
|
class CommandCompleter(QMenu):
|
||||||
"""GuiWidget: Meta Completer Menu
|
"""GuiWidget: Command Completer Menu
|
||||||
|
|
||||||
This is a context menu with options populated from the user's
|
This is a context menu with options populated from the user's
|
||||||
defined tags. It also helps to type the meta data keyword on a new
|
defined tags and keys. It also helps to type the meta data keyword
|
||||||
line starting with an @. The updateText function should be called on
|
on a new line starting with @ or %. The update functions should be
|
||||||
every keystroke on a line starting with @.
|
called on every keystroke on a line starting with @ or %.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
complete = pyqtSignal(int, int, str)
|
complete = pyqtSignal(int, int, str)
|
||||||
@@ -2085,7 +2092,7 @@ class MetaCompleter(QMenu):
|
|||||||
super().__init__(parent=parent)
|
super().__init__(parent=parent)
|
||||||
return
|
return
|
||||||
|
|
||||||
def updateText(self, text: str, pos: int) -> bool:
|
def updateMetaText(self, text: str, pos: int) -> bool:
|
||||||
"""Update the menu options based on the line of text."""
|
"""Update the menu options based on the line of text."""
|
||||||
self.clear()
|
self.clear()
|
||||||
kw, sep, _ = text.partition(":")
|
kw, sep, _ = text.partition(":")
|
||||||
@@ -2093,7 +2100,7 @@ class MetaCompleter(QMenu):
|
|||||||
offset = 0
|
offset = 0
|
||||||
length = len(kw.rstrip())
|
length = len(kw.rstrip())
|
||||||
suffix = "" if sep else ":"
|
suffix = "" if sep else ":"
|
||||||
options = list(filter(
|
options = sorted(filter(
|
||||||
lambda x: x.startswith(kw.rstrip()), nwKeyWords.VALID_KEYS
|
lambda x: x.startswith(kw.rstrip()), nwKeyWords.VALID_KEYS
|
||||||
))
|
))
|
||||||
else:
|
else:
|
||||||
@@ -2105,7 +2112,7 @@ class MetaCompleter(QMenu):
|
|||||||
offset = tPos[index] if lookup else pos
|
offset = tPos[index] if lookup else pos
|
||||||
length = len(lookup)
|
length = len(lookup)
|
||||||
suffix = ""
|
suffix = ""
|
||||||
options = list(filter(
|
options = sorted(filter(
|
||||||
lambda x: lookup in x.lower(), SHARED.project.index.getClassTags(
|
lambda x: lookup in x.lower(), SHARED.project.index.getClassTags(
|
||||||
nwKeyWords.KEY_CLASS.get(kw.strip())
|
nwKeyWords.KEY_CLASS.get(kw.strip())
|
||||||
)
|
)
|
||||||
@@ -2114,13 +2121,57 @@ class MetaCompleter(QMenu):
|
|||||||
if not options:
|
if not options:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
for value in sorted(options):
|
for value in options:
|
||||||
rep = value + suffix
|
rep = value + suffix
|
||||||
action = qtAddAction(self, value)
|
action = qtAddAction(self, value)
|
||||||
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
def updateCommentText(self, text: str, pos: int) -> bool:
|
||||||
|
"""Update the menu options based on the line of text."""
|
||||||
|
self.clear()
|
||||||
|
cmd, sep, _ = text.partition(":")
|
||||||
|
if pos <= len(cmd):
|
||||||
|
clean = text[1:].lstrip()[:6].lower()
|
||||||
|
if clean[:6] == "story.":
|
||||||
|
pre, _, key = cmd.partition(".")
|
||||||
|
offset = len(pre) + 1
|
||||||
|
length = len(key)
|
||||||
|
suffix = "" if sep else ": "
|
||||||
|
options = sorted(filter(
|
||||||
|
lambda x: x.startswith(key.rstrip()),
|
||||||
|
SHARED.project.index.getStoryKeys(),
|
||||||
|
))
|
||||||
|
elif clean[:5] == "note.":
|
||||||
|
pre, _, key = cmd.partition(".")
|
||||||
|
offset = len(pre) + 1
|
||||||
|
length = len(key)
|
||||||
|
suffix = "" if sep else ": "
|
||||||
|
options = sorted(filter(
|
||||||
|
lambda x: x.startswith(key.rstrip()),
|
||||||
|
SHARED.project.index.getNoteKeys(),
|
||||||
|
))
|
||||||
|
elif pos < 12:
|
||||||
|
offset = 0
|
||||||
|
length = len(cmd.rstrip())
|
||||||
|
suffix = ""
|
||||||
|
options = list(filter(
|
||||||
|
lambda x: x.startswith(cmd.rstrip()),
|
||||||
|
["%Synopsis: ", "%Short: ", "%Story", "%Note"],
|
||||||
|
))
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
|
||||||
|
if options:
|
||||||
|
for value in options:
|
||||||
|
rep = value + suffix
|
||||||
|
action = qtAddAction(self, rep.rstrip(":. "))
|
||||||
|
action.triggered.connect(qtLambda(self._emitComplete, offset, length, rep))
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
##
|
##
|
||||||
# Events
|
# Events
|
||||||
##
|
##
|
||||||
@@ -3045,9 +3096,9 @@ class GuiDocEditFooter(QWidget):
|
|||||||
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
fPx = int(0.9*SHARED.theme.fontPixelSize)
|
||||||
|
|
||||||
# Cached Translations
|
# Cached Translations
|
||||||
|
self.initSettings()
|
||||||
self._trLineCount = self.tr("Line: {0} ({1})")
|
self._trLineCount = self.tr("Line: {0} ({1})")
|
||||||
self._trWordCount = self.tr("Words: {0} ({1})")
|
self._trSelectCount = self.tr("Selected: {0}")
|
||||||
self._trSelectCount = self.tr("Words: {0} selected")
|
|
||||||
|
|
||||||
# Main Widget Settings
|
# Main Widget Settings
|
||||||
self.setContentsMargins(0, 0, 0, 0)
|
self.setContentsMargins(0, 0, 0, 0)
|
||||||
@@ -3108,7 +3159,7 @@ class GuiDocEditFooter(QWidget):
|
|||||||
self.updateTheme()
|
self.updateTheme()
|
||||||
|
|
||||||
# Initialise Info
|
# Initialise Info
|
||||||
self.updateWordCount(0, False)
|
self.updateMainCount(0, False)
|
||||||
|
|
||||||
logger.debug("Ready: GuiDocEditFooter")
|
logger.debug("Ready: GuiDocEditFooter")
|
||||||
|
|
||||||
@@ -3118,6 +3169,13 @@ class GuiDocEditFooter(QWidget):
|
|||||||
# Methods
|
# Methods
|
||||||
##
|
##
|
||||||
|
|
||||||
|
def initSettings(self) -> None:
|
||||||
|
"""Apply user settings."""
|
||||||
|
self._trMainCount = trStats(nwLabels.STATS_DISPLAY[
|
||||||
|
nwStats.CHARS if CONFIG.useCharCount else nwStats.WORDS
|
||||||
|
])
|
||||||
|
return
|
||||||
|
|
||||||
def updateFont(self) -> None:
|
def updateFont(self) -> None:
|
||||||
"""Update the font settings."""
|
"""Update the font settings."""
|
||||||
self.setFont(SHARED.theme.guiFont)
|
self.setFont(SHARED.theme.guiFont)
|
||||||
@@ -3162,7 +3220,7 @@ class GuiDocEditFooter(QWidget):
|
|||||||
self._tItem = SHARED.project.tree[self._docHandle]
|
self._tItem = SHARED.project.tree[self._docHandle]
|
||||||
|
|
||||||
self.updateInfo()
|
self.updateInfo()
|
||||||
self.updateWordCount(0, False)
|
self.updateMainCount(0, False)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -3193,15 +3251,15 @@ class GuiDocEditFooter(QWidget):
|
|||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
def updateWordCount(self, wCount: int, selection: bool) -> None:
|
def updateMainCount(self, count: int, selection: bool) -> None:
|
||||||
"""Update word counter information."""
|
"""Update main counter information."""
|
||||||
if selection and wCount:
|
if selection and count:
|
||||||
wText = self._trSelectCount.format(f"{wCount:n}")
|
text = self._trSelectCount.format(f"{count:n}")
|
||||||
elif self._tItem:
|
elif self._tItem:
|
||||||
wCount = self._tItem.wordCount
|
count = self._tItem.mainCount
|
||||||
wDiff = wCount - self._tItem.initCount
|
diff = count - self._tItem.initCount
|
||||||
wText = self._trWordCount.format(f"{wCount:n}", f"{wDiff:+n}")
|
text = self._trMainCount.format(f"{count:n}", f"{diff:+n}")
|
||||||
else:
|
else:
|
||||||
wText = self._trWordCount.format("0", "+0")
|
text = self._trMainCount.format("0", "+0")
|
||||||
self.wordsText.setText(wText)
|
self.wordsText.setText(text)
|
||||||
return
|
return
|
||||||
|
|||||||
@@ -80,6 +80,7 @@ class GuiNovelView(QWidget):
|
|||||||
# Function Mappings
|
# Function Mappings
|
||||||
self.setActive = self.novelBar.setActive
|
self.setActive = self.novelBar.setActive
|
||||||
self.getSelectedHandle = self.novelTree.getSelectedHandle
|
self.getSelectedHandle = self.novelTree.getSelectedHandle
|
||||||
|
self.refreshCurrentTree = self.novelBar.forceRefreshNovelTree
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -209,7 +210,7 @@ class GuiNovelToolBar(QWidget):
|
|||||||
# Refresh Button
|
# Refresh Button
|
||||||
self.tbRefresh = NIconToolButton(self, iSz)
|
self.tbRefresh = NIconToolButton(self, iSz)
|
||||||
self.tbRefresh.setToolTip(self.tr("Refresh"))
|
self.tbRefresh.setToolTip(self.tr("Refresh"))
|
||||||
self.tbRefresh.clicked.connect(self._forceRefreshNovelTree)
|
self.tbRefresh.clicked.connect(self.forceRefreshNovelTree)
|
||||||
|
|
||||||
# More Options Menu
|
# More Options Menu
|
||||||
self.mMore = QMenu(self)
|
self.mMore = QMenu(self)
|
||||||
@@ -274,7 +275,7 @@ class GuiNovelToolBar(QWidget):
|
|||||||
self.novelValue.updateTheme()
|
self.novelValue.updateTheme()
|
||||||
self.tbNovel.setVisible(self.novelValue.count() > 1)
|
self.tbNovel.setVisible(self.novelValue.count() > 1)
|
||||||
|
|
||||||
self._forceRefreshNovelTree()
|
self.forceRefreshNovelTree()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -306,7 +307,7 @@ class GuiNovelToolBar(QWidget):
|
|||||||
self.aLastCol[colType].setChecked(True)
|
self.aLastCol[colType].setChecked(True)
|
||||||
self.novelView.novelTree.setLastColType(colType)
|
self.novelView.novelTree.setLastColType(colType)
|
||||||
if doRefresh:
|
if doRefresh:
|
||||||
self._forceRefreshNovelTree()
|
self.forceRefreshNovelTree()
|
||||||
self.novelView.novelTree.resizeColumns()
|
self.novelView.novelTree.resizeColumns()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -324,11 +325,11 @@ class GuiNovelToolBar(QWidget):
|
|||||||
return
|
return
|
||||||
|
|
||||||
##
|
##
|
||||||
# Private Slots
|
# Public Slots
|
||||||
##
|
##
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot()
|
||||||
def _forceRefreshNovelTree(self) -> None:
|
def forceRefreshNovelTree(self) -> None:
|
||||||
"""Rebuild the current tree."""
|
"""Rebuild the current tree."""
|
||||||
if tHandle := self.novelValue.handle:
|
if tHandle := self.novelValue.handle:
|
||||||
self.novelView.setCurrentNovel(tHandle)
|
self.novelView.setCurrentNovel(tHandle)
|
||||||
@@ -336,6 +337,10 @@ class GuiNovelToolBar(QWidget):
|
|||||||
self._refresh[tHandle] = False
|
self._refresh[tHandle] = False
|
||||||
return
|
return
|
||||||
|
|
||||||
|
##
|
||||||
|
# Private Slots
|
||||||
|
##
|
||||||
|
|
||||||
@pyqtSlot(str)
|
@pyqtSlot(str)
|
||||||
def _refreshNovelTree(self, tHandle: str) -> None:
|
def _refreshNovelTree(self, tHandle: str) -> None:
|
||||||
"""Refresh or schedule refresh of a novel tree."""
|
"""Refresh or schedule refresh of a novel tree."""
|
||||||
|
|||||||
@@ -751,9 +751,13 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
def _dumpNovelData(self, rootHandle: str | None) -> list[list[str | int]]:
|
def _dumpNovelData(self, rootHandle: str | None) -> list[list[str | int]]:
|
||||||
"""Dump all novel data into a table."""
|
"""Dump all novel data into a table."""
|
||||||
sLabel = SHARED.project.localLookup("Story Structure")
|
sLabel = SHARED.project.localLookup("Story Structure")
|
||||||
|
nLabel = SHARED.project.localLookup("Note")
|
||||||
sKeys = sorted(SHARED.project.index.getStoryKeys())
|
sKeys = sorted(SHARED.project.index.getStoryKeys())
|
||||||
|
nKeys = sorted(SHARED.project.index.getNoteKeys())
|
||||||
sMatch = [f"story.{k}" for k in sKeys]
|
sMatch = [f"story.{k}" for k in sKeys]
|
||||||
|
nMatch = [f"note.{k}" for k in nKeys]
|
||||||
sHeaders = [f"{sLabel} ({k})" for k in sKeys]
|
sHeaders = [f"{sLabel} ({k})" for k in sKeys]
|
||||||
|
nHeaders = [f"{nLabel} ({k})" for k in nKeys]
|
||||||
|
|
||||||
data: list[list[str | int]] = [[
|
data: list[list[str | int]] = [[
|
||||||
"H",
|
"H",
|
||||||
@@ -777,6 +781,7 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
trConst(nwLabels.OUTLINE_COLS[nwOutline.MENTION]),
|
trConst(nwLabels.OUTLINE_COLS[nwOutline.MENTION]),
|
||||||
trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP]),
|
trConst(nwLabels.OUTLINE_COLS[nwOutline.SYNOP]),
|
||||||
*sHeaders,
|
*sHeaders,
|
||||||
|
*nHeaders,
|
||||||
]]
|
]]
|
||||||
|
|
||||||
for _, tHandle, sTitle, novIdx in SHARED.project.index.novelStructure(
|
for _, tHandle, sTitle, novIdx in SHARED.project.index.novelStructure(
|
||||||
@@ -784,7 +789,9 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
):
|
):
|
||||||
if novIdx.level != "H0" and (nwItem := SHARED.project.tree[tHandle]):
|
if novIdx.level != "H0" and (nwItem := SHARED.project.tree[tHandle]):
|
||||||
refs = SHARED.project.index.getReferences(tHandle, sTitle)
|
refs = SHARED.project.index.getReferences(tHandle, sTitle)
|
||||||
story = {k: v for k, v in novIdx.comments.items() if k in sMatch}.values()
|
comments = dict(novIdx.comments.items())
|
||||||
|
story = [comments.get(k, "") for k in sMatch]
|
||||||
|
notes = [comments.get(k, "") for k in nMatch]
|
||||||
data.append([
|
data.append([
|
||||||
novIdx.level,
|
novIdx.level,
|
||||||
novIdx.title,
|
novIdx.title,
|
||||||
@@ -807,6 +814,7 @@ class GuiOutlineTree(QTreeWidget):
|
|||||||
", ".join(refs[nwKeyWords.MENTION_KEY]),
|
", ".join(refs[nwKeyWords.MENTION_KEY]),
|
||||||
novIdx.synopsis,
|
novIdx.synopsis,
|
||||||
*story,
|
*story,
|
||||||
|
*notes,
|
||||||
])
|
])
|
||||||
|
|
||||||
return data
|
return data
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ from PyQt6.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
|
|||||||
|
|
||||||
from novelwriter import CONFIG, SHARED
|
from novelwriter import CONFIG, SHARED
|
||||||
from novelwriter.common import formatTime
|
from novelwriter.common import formatTime
|
||||||
from novelwriter.constants import nwConst
|
from novelwriter.constants import nwConst, nwLabels, nwStats, trStats
|
||||||
from novelwriter.extensions.modified import NClickableLabel
|
from novelwriter.extensions.modified import NClickableLabel
|
||||||
from novelwriter.extensions.statusled import StatusLED
|
from novelwriter.extensions.statusled import StatusLED
|
||||||
|
|
||||||
@@ -108,11 +108,22 @@ class GuiMainStatus(QStatusBar):
|
|||||||
|
|
||||||
logger.debug("Ready: GuiMainStatus")
|
logger.debug("Ready: GuiMainStatus")
|
||||||
|
|
||||||
|
self.initSettings()
|
||||||
self.updateTheme()
|
self.updateTheme()
|
||||||
self.clearStatus()
|
self.clearStatus()
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def initSettings(self) -> None:
|
||||||
|
"""Apply user settings."""
|
||||||
|
if CONFIG.useCharCount:
|
||||||
|
self._trStatsCount = trStats(nwLabels.STATS_DISPLAY[nwStats.CHARS])
|
||||||
|
self._trStatsTip = self.tr("Total character count (session change)")
|
||||||
|
else:
|
||||||
|
self._trStatsCount = trStats(nwLabels.STATS_DISPLAY[nwStats.WORDS])
|
||||||
|
self._trStatsTip = self.tr("Total word count (session change)")
|
||||||
|
return
|
||||||
|
|
||||||
def clearStatus(self) -> None:
|
def clearStatus(self) -> None:
|
||||||
"""Reset all widgets on the status bar to default values."""
|
"""Reset all widgets on the status bar to default values."""
|
||||||
self.setRefTime(-1.0)
|
self.setRefTime(-1.0)
|
||||||
@@ -173,11 +184,8 @@ class GuiMainStatus(QStatusBar):
|
|||||||
|
|
||||||
def setProjectStats(self, pWC: int, sWC: int) -> None:
|
def setProjectStats(self, pWC: int, sWC: int) -> None:
|
||||||
"""Update the current project statistics."""
|
"""Update the current project statistics."""
|
||||||
self.statsText.setText(self.tr("Words: {0} ({1})").format(f"{pWC:n}", f"{sWC:+n}"))
|
self.statsText.setText(self._trStatsCount.format(f"{pWC:n}", f"{sWC:+n}"))
|
||||||
if CONFIG.incNotesWCount:
|
self.statsText.setToolTip(self._trStatsTip)
|
||||||
self.statsText.setToolTip(self.tr("Project word count (session change)"))
|
|
||||||
else:
|
|
||||||
self.statsText.setToolTip(self.tr("Novel word count (session change)"))
|
|
||||||
return
|
return
|
||||||
|
|
||||||
def updateTime(self, idleTime: float = 0.0) -> None:
|
def updateTime(self, idleTime: float = 0.0) -> None:
|
||||||
|
|||||||
+19
-8
@@ -1048,8 +1048,10 @@ class GuiMain(QMainWindow):
|
|||||||
self.initMain()
|
self.initMain()
|
||||||
self.saveDocument()
|
self.saveDocument()
|
||||||
|
|
||||||
if tree:
|
if tree and not theme:
|
||||||
|
# These are also updated by a theme refresh
|
||||||
SHARED.project.tree.refreshAllItems()
|
SHARED.project.tree.refreshAllItems()
|
||||||
|
self.novelView.refreshCurrentTree()
|
||||||
|
|
||||||
if theme:
|
if theme:
|
||||||
SHARED.theme.loadTheme()
|
SHARED.theme.loadTheme()
|
||||||
@@ -1075,6 +1077,7 @@ class GuiMain(QMainWindow):
|
|||||||
self.projView.initSettings()
|
self.projView.initSettings()
|
||||||
self.novelView.initSettings()
|
self.novelView.initSettings()
|
||||||
self.outlineView.initSettings()
|
self.outlineView.initSettings()
|
||||||
|
self.mainStatus.initSettings()
|
||||||
|
|
||||||
# Force update of word count
|
# Force update of word count
|
||||||
self._lastTotalCount = 0
|
self._lastTotalCount = 0
|
||||||
@@ -1261,15 +1264,23 @@ class GuiMain(QMainWindow):
|
|||||||
if self._lastTotalCount != currentTotalCount:
|
if self._lastTotalCount != currentTotalCount:
|
||||||
self._lastTotalCount = currentTotalCount
|
self._lastTotalCount = currentTotalCount
|
||||||
|
|
||||||
SHARED.project.updateWordCounts()
|
SHARED.project.updateCounts()
|
||||||
if CONFIG.incNotesWCount:
|
if CONFIG.incNotesWCount:
|
||||||
iTotal = sum(SHARED.project.data.initCounts)
|
if CONFIG.useCharCount:
|
||||||
cTotal = sum(SHARED.project.data.currCounts)
|
iTotal = sum(SHARED.project.data.initCounts[2:])
|
||||||
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
|
cTotal = sum(SHARED.project.data.currCounts[2:])
|
||||||
|
else:
|
||||||
|
iTotal = sum(SHARED.project.data.initCounts[:2])
|
||||||
|
cTotal = sum(SHARED.project.data.currCounts[:2])
|
||||||
else:
|
else:
|
||||||
iNovel, _ = SHARED.project.data.initCounts
|
if CONFIG.useCharCount:
|
||||||
cNovel, _ = SHARED.project.data.currCounts
|
iTotal = SHARED.project.data.initCounts[2]
|
||||||
self.mainStatus.setProjectStats(cNovel, cNovel - iNovel)
|
cTotal = SHARED.project.data.currCounts[2]
|
||||||
|
else:
|
||||||
|
iTotal = SHARED.project.data.initCounts[0]
|
||||||
|
cTotal = SHARED.project.data.currCounts[0]
|
||||||
|
|
||||||
|
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|||||||
@@ -630,7 +630,7 @@ class _DetailsWidget(QWidget):
|
|||||||
self.listView.addTopLevelItem(item)
|
self.listView.addTopLevelItem(item)
|
||||||
for key in [
|
for key in [
|
||||||
"text.includeSynopsis", "text.includeComments", "text.includeStory",
|
"text.includeSynopsis", "text.includeComments", "text.includeStory",
|
||||||
"text.includeKeywords", "text.includeBodyText",
|
"text.includeNotes", "text.includeKeywords", "text.includeBodyText",
|
||||||
]:
|
]:
|
||||||
sub = QTreeWidgetItem()
|
sub = QTreeWidgetItem()
|
||||||
sub.setText(0, build.getLabel(key))
|
sub.setText(0, build.getLabel(key))
|
||||||
|
|||||||
@@ -80,7 +80,8 @@ class GuiBuildSettings(NToolDialog):
|
|||||||
logger.debug("Create: GuiBuildSettings")
|
logger.debug("Create: GuiBuildSettings")
|
||||||
self.setObjectName("GuiBuildSettings")
|
self.setObjectName("GuiBuildSettings")
|
||||||
|
|
||||||
self._build = build
|
# Make a copy of the build object
|
||||||
|
self._build = BuildSettings.fromDict(build.pack())
|
||||||
|
|
||||||
self.setWindowTitle(self.tr("Manuscript Build Settings"))
|
self.setWindowTitle(self.tr("Manuscript Build Settings"))
|
||||||
self.setMinimumSize(700, 400)
|
self.setMinimumSize(700, 400)
|
||||||
@@ -184,6 +185,7 @@ class GuiBuildSettings(NToolDialog):
|
|||||||
settings.
|
settings.
|
||||||
"""
|
"""
|
||||||
logger.debug("Closing: GuiBuildSettings")
|
logger.debug("Closing: GuiBuildSettings")
|
||||||
|
self._applyChanges()
|
||||||
self._askToSaveBuild()
|
self._askToSaveBuild()
|
||||||
self._saveSettings()
|
self._saveSettings()
|
||||||
event.accept()
|
event.accept()
|
||||||
@@ -211,11 +213,14 @@ class GuiBuildSettings(NToolDialog):
|
|||||||
"""Handle button clicks from the dialog button box."""
|
"""Handle button clicks from the dialog button box."""
|
||||||
role = self.buttonBox.buttonRole(button)
|
role = self.buttonBox.buttonRole(button)
|
||||||
if role == QtRoleApply:
|
if role == QtRoleApply:
|
||||||
|
self._applyChanges()
|
||||||
self._emitBuildData()
|
self._emitBuildData()
|
||||||
elif role == QtRoleAccept:
|
elif role == QtRoleAccept:
|
||||||
|
self._applyChanges()
|
||||||
self._emitBuildData()
|
self._emitBuildData()
|
||||||
self.close()
|
self.close()
|
||||||
elif role == QtRoleReject:
|
elif role == QtRoleReject:
|
||||||
|
self._build.resetChangedState()
|
||||||
self.close()
|
self.close()
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -228,10 +233,9 @@ class GuiBuildSettings(NToolDialog):
|
|||||||
whether the user wants to save them.
|
whether the user wants to save them.
|
||||||
"""
|
"""
|
||||||
if self._build.changed:
|
if self._build.changed:
|
||||||
response = SHARED.question(self.tr(
|
if SHARED.question(self.tr(
|
||||||
"Do you want to save your changes to '{0}'?"
|
"Do you want to save your changes to '{0}'?"
|
||||||
).format(self._build.name))
|
).format(self._build.name)):
|
||||||
if response:
|
|
||||||
self._emitBuildData()
|
self._emitBuildData()
|
||||||
self._build.resetChangedState()
|
self._build.resetChangedState()
|
||||||
return
|
return
|
||||||
@@ -246,14 +250,17 @@ class GuiBuildSettings(NToolDialog):
|
|||||||
pOptions.setValue("GuiBuildSettings", "treeWidth", treeWidth)
|
pOptions.setValue("GuiBuildSettings", "treeWidth", treeWidth)
|
||||||
pOptions.setValue("GuiBuildSettings", "filterWidth", filterWidth)
|
pOptions.setValue("GuiBuildSettings", "filterWidth", filterWidth)
|
||||||
pOptions.saveSettings()
|
pOptions.saveSettings()
|
||||||
|
return
|
||||||
|
|
||||||
|
def _applyChanges(self) -> None:
|
||||||
|
"""Apply all settings changes to the build object."""
|
||||||
|
self._build.setName(self.editBuildName.text())
|
||||||
|
self.optTabHeadings.saveContent()
|
||||||
|
self.optTabFormatting.saveContent()
|
||||||
return
|
return
|
||||||
|
|
||||||
def _emitBuildData(self) -> None:
|
def _emitBuildData(self) -> None:
|
||||||
"""Assemble the build data and emit the signal."""
|
"""Assemble the build data and emit the signal."""
|
||||||
self._build.setName(self.editBuildName.text())
|
|
||||||
self.optTabHeadings.saveContent()
|
|
||||||
self.optTabFormatting.saveContent()
|
|
||||||
self.newSettingsReady.emit(self._build)
|
self.newSettingsReady.emit(self._build)
|
||||||
self._build.resetChangedState()
|
self._build.resetChangedState()
|
||||||
return
|
return
|
||||||
@@ -974,12 +981,14 @@ class _FormattingTab(NScrollableForm):
|
|||||||
self.incSynopsis = NSwitch(self, height=iPx)
|
self.incSynopsis = NSwitch(self, height=iPx)
|
||||||
self.incComments = NSwitch(self, height=iPx)
|
self.incComments = NSwitch(self, height=iPx)
|
||||||
self.incStory = NSwitch(self, height=iPx)
|
self.incStory = NSwitch(self, height=iPx)
|
||||||
|
self.incNotes = NSwitch(self, height=iPx)
|
||||||
self.incKeywords = NSwitch(self, height=iPx)
|
self.incKeywords = NSwitch(self, height=iPx)
|
||||||
|
|
||||||
self.addRow(self._build.getLabel("text.includeBodyText"), self.incBodyText)
|
self.addRow(self._build.getLabel("text.includeBodyText"), self.incBodyText)
|
||||||
self.addRow(self._build.getLabel("text.includeSynopsis"), self.incSynopsis)
|
self.addRow(self._build.getLabel("text.includeSynopsis"), self.incSynopsis)
|
||||||
self.addRow(self._build.getLabel("text.includeComments"), self.incComments)
|
self.addRow(self._build.getLabel("text.includeComments"), self.incComments)
|
||||||
self.addRow(self._build.getLabel("text.includeStory"), self.incStory)
|
self.addRow(self._build.getLabel("text.includeStory"), self.incStory)
|
||||||
|
self.addRow(self._build.getLabel("text.includeNotes"), self.incNotes)
|
||||||
self.addRow(self._build.getLabel("text.includeKeywords"), self.incKeywords)
|
self.addRow(self._build.getLabel("text.includeKeywords"), self.incKeywords)
|
||||||
|
|
||||||
# Ignored Keywords
|
# Ignored Keywords
|
||||||
@@ -1288,6 +1297,7 @@ class _FormattingTab(NScrollableForm):
|
|||||||
self.incSynopsis.setChecked(self._build.getBool("text.includeSynopsis"))
|
self.incSynopsis.setChecked(self._build.getBool("text.includeSynopsis"))
|
||||||
self.incComments.setChecked(self._build.getBool("text.includeComments"))
|
self.incComments.setChecked(self._build.getBool("text.includeComments"))
|
||||||
self.incStory.setChecked(self._build.getBool("text.includeStory"))
|
self.incStory.setChecked(self._build.getBool("text.includeStory"))
|
||||||
|
self.incNotes.setChecked(self._build.getBool("text.includeNotes"))
|
||||||
self.incKeywords.setChecked(self._build.getBool("text.includeKeywords"))
|
self.incKeywords.setChecked(self._build.getBool("text.includeKeywords"))
|
||||||
self.ignoredKeywords.setText(self._build.getStr("text.ignoredKeywords"))
|
self.ignoredKeywords.setText(self._build.getStr("text.ignoredKeywords"))
|
||||||
self.addNoteHead.setChecked(self._build.getBool("text.addNoteHeadings"))
|
self.addNoteHead.setChecked(self._build.getBool("text.addNoteHeadings"))
|
||||||
@@ -1387,6 +1397,7 @@ class _FormattingTab(NScrollableForm):
|
|||||||
self._build.setValue("text.includeSynopsis", self.incSynopsis.isChecked())
|
self._build.setValue("text.includeSynopsis", self.incSynopsis.isChecked())
|
||||||
self._build.setValue("text.includeComments", self.incComments.isChecked())
|
self._build.setValue("text.includeComments", self.incComments.isChecked())
|
||||||
self._build.setValue("text.includeStory", self.incStory.isChecked())
|
self._build.setValue("text.includeStory", self.incStory.isChecked())
|
||||||
|
self._build.setValue("text.includeNotes", self.incNotes.isChecked())
|
||||||
self._build.setValue("text.includeKeywords", self.incKeywords.isChecked())
|
self._build.setValue("text.includeKeywords", self.incKeywords.isChecked())
|
||||||
self._build.setValue("text.ignoredKeywords", self.ignoredKeywords.text())
|
self._build.setValue("text.ignoredKeywords", self.ignoredKeywords.text())
|
||||||
self._build.setValue("text.addNoteHeadings", self.addNoteHead.isChecked())
|
self._build.setValue("text.addNoteHeadings", self.addNoteHead.isChecked())
|
||||||
|
|||||||
@@ -257,8 +257,8 @@ class _OverviewPage(NScrollablePage):
|
|||||||
def updateProjectData(self) -> None:
|
def updateProjectData(self) -> None:
|
||||||
"""Load information about the project."""
|
"""Load information about the project."""
|
||||||
project = SHARED.project
|
project = SHARED.project
|
||||||
project.updateWordCounts()
|
project.updateCounts()
|
||||||
wcNovel, wcNotes = project.data.currCounts
|
wcNovel, wcNotes, _, _ = project.data.currCounts
|
||||||
|
|
||||||
self.projName.setText(project.data.name)
|
self.projName.setText(project.data.name)
|
||||||
self.projRevisions.setText(f"{project.data.saveCount:n}")
|
self.projRevisions.setText(f"{project.data.saveCount:n}")
|
||||||
|
|||||||
+1
-1
@@ -1,5 +1,5 @@
|
|||||||
[build-system]
|
[build-system]
|
||||||
requires = ["setuptools"]
|
requires = ["setuptools >= 77.0.3"]
|
||||||
build-backend = "setuptools.build_meta"
|
build-backend = "setuptools.build_meta"
|
||||||
|
|
||||||
[project]
|
[project]
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
%%~name: Making a Scene
|
%%~name: Making a Scene
|
||||||
%%~path: 6a2d6d5f4f401/636b6aa9b697b
|
%%~path: 6a2d6d5f4f401/636b6aa9b697b
|
||||||
%%~kind: NOVEL/DOCUMENT
|
%%~kind: NOVEL/DOCUMENT
|
||||||
%%~hash: c057a5e9309b0e764c367b0fe9ab0607e3308622
|
%%~hash: 1f3d98a6a27b4f9a7f2239fcd78f9e2263cd3b97
|
||||||
%%~date: Unknown/2025-04-08 20:10:33
|
%%~date: Unknown/2025-05-18 22:36:59
|
||||||
### Making a Scene
|
### Making a Scene
|
||||||
|
|
||||||
@pov: Jane
|
@pov: Jane
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
@mention: Space
|
@mention: Space
|
||||||
|
|
||||||
%Story.Resolution: You can describe the scene structure with story comments.
|
%Story.Resolution: You can describe the scene structure with story comments.
|
||||||
|
%Note.Consistency: You can also make notes about things like consistency of the story.
|
||||||
|
|
||||||
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree. The scene document can be sorted after the chapter document, or as a child of the chapter. Both result in the same output in the end, so it is a matter of preference.
|
A scene is defined by a level three heading, like the one at the top of this page. The scene will be assigned to the chapter preceding it in the project tree. The scene document can be sorted after the chapter document, or as a child of the chapter. Both result in the same output in the end, so it is a matter of preference.
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-04-20 19:01:45">
|
<novelWriterXML appVersion="2.7rc1" hexVersion="0x020700c1" fileVersion="1.5" fileRevision="5" timeStamp="2025-05-19 12:51:02">
|
||||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2178" autoCount="285" editTime="96586">
|
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2191" autoCount="286" editTime="96880">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jane Smith</author>
|
<author>Jane Smith</author>
|
||||||
</project>
|
</project>
|
||||||
@@ -36,7 +36,7 @@
|
|||||||
<entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
<entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="31" novelWords="1016" notesWords="416">
|
<content items="31" novelWords="1016" notesWords="416" novelChars="5602" notesChars="2285">
|
||||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="sc24b8f" import="ia857f0">Novel</name>
|
<name status="sc24b8f" import="ia857f0">Novel</name>
|
||||||
@@ -58,7 +58,7 @@
|
|||||||
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
|
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="no" heading="H3" charCount="2999" wordCount="530" paraCount="16" cursorPos="159" />
|
<meta expanded="no" heading="H3" charCount="2999" wordCount="530" paraCount="16" cursorPos="718" />
|
||||||
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
|
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
|
|||||||
+1
-1
@@ -49,5 +49,5 @@ echo " Done!"
|
|||||||
echo "================================================================================"
|
echo "================================================================================"
|
||||||
echo ""
|
echo ""
|
||||||
echo " To upload packages to PyPi, run:"
|
echo " To upload packages to PyPi, run:"
|
||||||
echo " twine upload dist/*"
|
echo " python3 -m twine upload dist/*"
|
||||||
echo ""
|
echo ""
|
||||||
|
|||||||
@@ -1,13 +1,13 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2022-11-07 13:00:48">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:07:59">
|
||||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="5" autoCount="10" editTime="1000">
|
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2179" autoCount="285" editTime="1000">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jane Smith</author>
|
<author>Jane Smith</author>
|
||||||
</project>
|
</project>
|
||||||
<settings>
|
<settings>
|
||||||
<doBackup>yes</doBackup>
|
<doBackup>no</doBackup>
|
||||||
<language>en_GB</language>
|
<language>en_GB</language>
|
||||||
<spellChecking auto="yes">en_GB</spellChecking>
|
<spellChecking auto="yes">None</spellChecking>
|
||||||
<lastHandle>
|
<lastHandle>
|
||||||
<entry key="editor">636b6aa9b697b</entry>
|
<entry key="editor">636b6aa9b697b</entry>
|
||||||
<entry key="viewer">636b6aa9b697b</entry>
|
<entry key="viewer">636b6aa9b697b</entry>
|
||||||
@@ -20,32 +20,33 @@
|
|||||||
<entry key="C">D</entry>
|
<entry key="C">D</entry>
|
||||||
</autoReplace>
|
</autoReplace>
|
||||||
<status>
|
<status>
|
||||||
<entry key="sf12341" count="4" red="100" green="100" blue="100" shape="SQUARE">New</entry>
|
<entry key="sf12341" count="8" red="100" green="100" blue="100" shape="SQUARE">New</entry>
|
||||||
<entry key="sf24ce6" count="2" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
|
<entry key="sf24ce6" count="2" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
|
||||||
<entry key="sc24b8f" count="3" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
|
<entry key="sc24b8f" count="3" red="182" green="60" blue="0" shape="BARS_1">Started</entry>
|
||||||
<entry key="s90e6c9" count="7" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
|
<entry key="s90e6c9" count="5" red="193" green="129" blue="0" shape="BARS_2">1st Draft</entry>
|
||||||
<entry key="sd51c5b" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
|
<entry key="sd51c5b" count="1" red="193" green="129" blue="0" shape="BARS_3">2nd Draft</entry>
|
||||||
<entry key="s8ae72a" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
|
<entry key="s8ae72a" count="1" red="193" green="129" blue="0" shape="BARS_4">3rd Draft</entry>
|
||||||
<entry key="s78ea90" count="1" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
|
<entry key="s78ea90" count="1" red="58" green="180" blue="58" shape="STAR">Finished</entry>
|
||||||
</status>
|
</status>
|
||||||
<importance>
|
<importance>
|
||||||
<entry key="ia857f0" count="5" red="100" green="100" blue="100" shape="SQUARE">None</entry>
|
<entry key="ia857f0" count="5" red="100" green="100" blue="100" shape="SQUARE">None</entry>
|
||||||
<entry key="icfb3a5" count="2" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
|
<entry key="i4a1d39" count="1" red="220" green="138" blue="221" shape="BLOCK_1">Background</entry>
|
||||||
<entry key="i2d7a54" count="2" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
|
<entry key="icfb3a5" count="1" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
|
||||||
<entry key="i56be10" count="1" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
<entry key="i2d7a54" count="2" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
|
||||||
|
<entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="27" novelWords="954" notesWords="409">
|
<content items="31" novelWords="1016" notesWords="416" novelChars="5602" notesChars="2285">
|
||||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="sc24b8f" import="ia857f0">Novel</name>
|
<name status="sc24b8f" import="ia857f0">Novel</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="no" heading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119" />
|
<meta expanded="no" heading="H1" charCount="148" wordCount="29" paraCount="4" cursorPos="178" />
|
||||||
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
|
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="no" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277" />
|
<meta expanded="no" heading="H0" charCount="233" wordCount="47" paraCount="2" cursorPos="194" />
|
||||||
<name status="sf12341" import="ia857f0" active="yes">Page</name>
|
<name status="sf12341" import="ia857f0" active="yes">Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
@@ -57,28 +58,28 @@
|
|||||||
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
|
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="no" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67" />
|
<meta expanded="no" heading="H3" charCount="2999" wordCount="530" paraCount="16" cursorPos="159" />
|
||||||
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
|
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="no" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465" />
|
<meta expanded="no" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="650" />
|
||||||
<name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name>
|
<name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310" />
|
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="1182" />
|
||||||
<name status="s78ea90" import="ia857f0" active="yes">Interlude</name>
|
<name status="s78ea90" import="ia857f0" active="yes">Interlude</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
|
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
|
||||||
<meta expanded="no" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0" />
|
<meta expanded="no" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="1940" />
|
||||||
<name status="sf24ce6" import="ia857f0" active="no">A Note on Structure</name>
|
<name status="sf24ce6" import="ia857f0" active="no">A Note on Structure</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="yes" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188" />
|
<meta expanded="yes" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="356" />
|
||||||
<name status="s90e6c9" import="ia857f0" active="yes">Chapter Two</name>
|
<name status="s90e6c9" import="ia857f0" active="yes">Chapter Two</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="no" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0" />
|
<meta expanded="no" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="237" />
|
||||||
<name status="s90e6c9" import="ia857f0" active="yes">We Found John!</name>
|
<name status="sd51c5b" import="ia857f0" active="yes">We Found John!</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
|
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
@@ -89,8 +90,8 @@
|
|||||||
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
|
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||||
<meta expanded="no" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104" />
|
<meta expanded="no" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="387" />
|
||||||
<name status="s90e6c9" import="ia857f0" active="yes">Chapter One</name>
|
<name status="s8ae72a" import="ia857f0" active="yes">Chapter One</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
|
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
@@ -101,28 +102,28 @@
|
|||||||
<name status="sf12341" import="ia857f0">Main Characters</name>
|
<name status="sf12341" import="ia857f0">Main Characters</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24" />
|
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="23" />
|
||||||
<name status="sf12341" import="icfb3a5" active="yes">John Smith</name>
|
<name status="sf12341" import="i2d7a54" active="yes">John Smith</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
|
||||||
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25" />
|
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="31" />
|
||||||
<name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name>
|
<name status="sf12341" import="i56be10" active="yes">Jane Smith</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
|
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="sf12341" import="ia857f0">Locations</name>
|
<name status="sf12341" import="ia857f0">Locations</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta expanded="no" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20" />
|
<meta expanded="no" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="111" />
|
||||||
<name status="sf12341" import="i56be10" active="yes">Earth</name>
|
<name status="sf12341" import="i2d7a54" active="yes">Earth</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133" />
|
<meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="0" />
|
||||||
<name status="sf12341" import="icfb3a5" active="yes">Space</name>
|
<name status="sf12341" import="i4a1d39" active="yes">Space</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
|
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
|
||||||
<meta expanded="no" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45" />
|
<meta expanded="no" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="62" />
|
||||||
<name status="sf12341" import="i2d7a54" active="yes">Mars</name>
|
<name status="sf12341" import="icfb3a5" active="yes">Mars</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
|
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
@@ -136,7 +137,23 @@
|
|||||||
<meta expanded="no" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239" />
|
<meta expanded="no" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239" />
|
||||||
<name status="s90e6c9" import="ia857f0" active="yes">Old File</name>
|
<name status="s90e6c9" import="ia857f0" active="yes">Old File</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH">
|
<item handle="f4ed1ae756a1f" parent="None" root="f4ed1ae756a1f" order="5" type="ROOT" class="TEMPLATE">
|
||||||
|
<meta expanded="yes" />
|
||||||
|
<name status="sf12341" import="ia857f0">Templates</name>
|
||||||
|
</item>
|
||||||
|
<item handle="5aec885635c85" parent="f4ed1ae756a1f" root="f4ed1ae756a1f" order="0" type="FILE" class="TEMPLATE" layout="DOCUMENT">
|
||||||
|
<meta expanded="no" heading="H3" charCount="9" wordCount="2" paraCount="1" cursorPos="78" />
|
||||||
|
<name status="sf12341" import="ia857f0" active="yes">Scene</name>
|
||||||
|
</item>
|
||||||
|
<item handle="2a60782759c6f" parent="f4ed1ae756a1f" root="f4ed1ae756a1f" order="1" type="FILE" class="TEMPLATE" layout="DOCUMENT">
|
||||||
|
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="1" cursorPos="81" />
|
||||||
|
<name status="sf12341" import="ia857f0" active="yes">Chapter</name>
|
||||||
|
</item>
|
||||||
|
<item handle="5ee8aebcdebc9" parent="f4ed1ae756a1f" root="f4ed1ae756a1f" order="2" type="FILE" class="TEMPLATE" layout="NOTE">
|
||||||
|
<meta expanded="no" heading="H1" charCount="53" wordCount="7" paraCount="1" cursorPos="75" />
|
||||||
|
<name status="sf12341" import="ia857f0" active="yes">Character Note</name>
|
||||||
|
</item>
|
||||||
|
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="6" type="ROOT" class="TRASH">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="sf12341" import="ia857f0">Trash</name>
|
<name status="sf12341" import="ia857f0">Trash</name>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.6b1" hexVersion="0x020600b1" fileVersion="1.5" fileRevision="4" timeStamp="2024-11-20 19:22:22">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:45:09">
|
||||||
<project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="51" autoCount="29" editTime="2456">
|
<project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="52" autoCount="29" editTime="2465">
|
||||||
<name>Lorem Ipsum</name>
|
<name>Lorem Ipsum</name>
|
||||||
<author>lipsum.com</author>
|
<author>lipsum.com</author>
|
||||||
</project>
|
</project>
|
||||||
@@ -31,7 +31,7 @@
|
|||||||
<entry key="id6b1d0" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
|
<entry key="id6b1d0" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="22" novelWords="3115" notesWords="738">
|
<content items="22" novelWords="3115" notesWords="738" novelChars="20774" notesChars="5003">
|
||||||
<item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL">
|
<item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="sbaa94f" import="i613591">Novel</name>
|
<name status="sbaa94f" import="i613591">Novel</name>
|
||||||
@@ -116,7 +116,7 @@
|
|||||||
<meta expanded="no" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="47" />
|
<meta expanded="no" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="47" />
|
||||||
<name status="sbaa94f" import="i613591" active="yes">Ancient Europe</name>
|
<name status="sbaa94f" import="i613591" active="yes">Ancient Europe</name>
|
||||||
</item>
|
</item>
|
||||||
<item handle="1ace7ab1a0fc6" parent="None" root="1ace7ab1a0fc6" order="0" type="ROOT" class="TRASH">
|
<item handle="1ace7ab1a0fc6" parent="None" root="1ace7ab1a0fc6" order="4" type="ROOT" class="TRASH">
|
||||||
<meta expanded="no" />
|
<meta expanded="no" />
|
||||||
<name status="sbaa94f" import="i613591">Trash</name>
|
<name status="sbaa94f" import="i613591">Trash</name>
|
||||||
</item>
|
</item>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:48">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:54">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<author>Jane Doe</author>
|
<author>Jane Doe</author>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="12" novelWords="10" notesWords="6">
|
<content items="12" novelWords="10" notesWords="6" novelChars="45" notesChars="22">
|
||||||
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="no" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Novel</name>
|
<name status="s000000" import="i000004">Novel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:48">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:54">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<author>Jane Doe</author>
|
<author>Jane Doe</author>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="16" novelWords="9" notesWords="0">
|
<content items="16" novelWords="9" notesWords="0" novelChars="40" notesChars="0">
|
||||||
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="no" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Novel</name>
|
<name status="s000000" import="i000004">Novel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:46">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:52">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<author>Jane Doe</author>
|
<author>Jane Doe</author>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="19" novelWords="28" notesWords="0">
|
<content items="19" novelWords="28" notesWords="0" novelChars="129" notesChars="0">
|
||||||
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="no" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Novel</name>
|
<name status="s000000" import="i000004">Novel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:46">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:52">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
|
||||||
<name>Test Project A</name>
|
<name>Test Project A</name>
|
||||||
<author>Jane Doe</author>
|
<author>Jane Doe</author>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="22" novelWords="0" notesWords="0">
|
<content items="22" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
|
||||||
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="no" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Novel</name>
|
<name status="s000000" import="i000004">Novel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:46">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:52">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
|
||||||
<name>Test Project B</name>
|
<name>Test Project B</name>
|
||||||
<author>Jane Doe</author>
|
<author>Jane Doe</author>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="16" novelWords="0" notesWords="0">
|
<content items="16" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
|
||||||
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="no" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Novel</name>
|
<name status="s000000" import="i000004">Novel</name>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<ns0:Properties xmlns:ns0="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
|
<ns0:Properties xmlns:ns0="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
|
||||||
<ns0:TotalTime>40</ns0:TotalTime>
|
<ns0:TotalTime>41</ns0:TotalTime>
|
||||||
<ns0:Application>novelWriter/2.6a3</ns0:Application>
|
<ns0:Application>novelWriter/2.7b1</ns0:Application>
|
||||||
<ns0:Words>4035</ns0:Words>
|
<ns0:Words>4035</ns0:Words>
|
||||||
<ns0:Characters>21296</ns0:Characters>
|
<ns0:Characters>21296</ns0:Characters>
|
||||||
<ns0:CharactersWithSpaces>24964</ns0:CharactersWithSpaces>
|
<ns0:CharactersWithSpaces>24964</ns0:CharactersWithSpaces>
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
|
||||||
<dcterms:created xsi:type="dcterms:W3CDTF">2024-11-20T19:45:15</dcterms:created>
|
<dcterms:created xsi:type="dcterms:W3CDTF">2025-04-29T22:46:36</dcterms:created>
|
||||||
<dcterms:modified xsi:type="dcterms:W3CDTF">2024-11-20T19:45:15</dcterms:modified>
|
<dcterms:modified xsi:type="dcterms:W3CDTF">2025-04-29T22:46:36</dcterms:modified>
|
||||||
<dc:creator>lipsum.com</dc:creator>
|
<dc:creator>lipsum.com</dc:creator>
|
||||||
<dc:title>Lorem Ipsum</dc:title>
|
<dc:title>Lorem Ipsum</dc:title>
|
||||||
<dc:language>en_GB</dc:language>
|
<dc:language>en_GB</dc:language>
|
||||||
<cp:revision>51</cp:revision>
|
<cp:revision>52</cp:revision>
|
||||||
<cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy>
|
<cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy>
|
||||||
</cp:coreProperties>
|
</cp:coreProperties>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:59">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:08:41">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="4">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="4">
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<author>Jane Doe</author>
|
<author>Jane Doe</author>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="12" novelWords="173" notesWords="27">
|
<content items="12" novelWords="173" notesWords="27" novelChars="1007" notesChars="133">
|
||||||
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="s000000" import="i000004">Novel</name>
|
<name status="s000000" import="i000004">Novel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:39:41">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:01:24">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0">
|
||||||
<name>New Project</name>
|
<name>New Project</name>
|
||||||
<author>Jane Doe</author>
|
<author>Jane Doe</author>
|
||||||
@@ -28,7 +28,7 @@
|
|||||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="8" novelWords="9" notesWords="0">
|
<content items="8" novelWords="9" notesWords="0" novelChars="40" notesChars="0">
|
||||||
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="no" />
|
<meta expanded="no" />
|
||||||
<name status="s000000" import="i000004">Novel</name>
|
<name status="s000000" import="i000004">Novel</name>
|
||||||
|
|||||||
@@ -38,10 +38,10 @@
|
|||||||
"metaAttr": {
|
"metaAttr": {
|
||||||
"expanded": false,
|
"expanded": false,
|
||||||
"heading": "H1",
|
"heading": "H1",
|
||||||
"charCount": 93,
|
"charCount": 148,
|
||||||
"wordCount": 19,
|
"wordCount": 29,
|
||||||
"paraCount": 2,
|
"paraCount": 4,
|
||||||
"cursorPos": 119
|
"cursorPos": 178
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "sc24b8f",
|
"status": "sc24b8f",
|
||||||
@@ -63,10 +63,10 @@
|
|||||||
"metaAttr": {
|
"metaAttr": {
|
||||||
"expanded": false,
|
"expanded": false,
|
||||||
"heading": "H0",
|
"heading": "H0",
|
||||||
"charCount": 251,
|
"charCount": 233,
|
||||||
"wordCount": 50,
|
"wordCount": 47,
|
||||||
"paraCount": 2,
|
"paraCount": 2,
|
||||||
"cursorPos": 277
|
"cursorPos": 194
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "sf12341",
|
"status": "sf12341",
|
||||||
@@ -138,10 +138,10 @@
|
|||||||
"metaAttr": {
|
"metaAttr": {
|
||||||
"expanded": false,
|
"expanded": false,
|
||||||
"heading": "H3",
|
"heading": "H3",
|
||||||
"charCount": 2687,
|
"charCount": 2999,
|
||||||
"wordCount": 479,
|
"wordCount": 530,
|
||||||
"paraCount": 14,
|
"paraCount": 16,
|
||||||
"cursorPos": 67
|
"cursorPos": 159
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "s90e6c9",
|
"status": "s90e6c9",
|
||||||
@@ -166,7 +166,7 @@
|
|||||||
"charCount": 548,
|
"charCount": 548,
|
||||||
"wordCount": 108,
|
"wordCount": 108,
|
||||||
"paraCount": 3,
|
"paraCount": 3,
|
||||||
"cursorPos": 465
|
"cursorPos": 650
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "s90e6c9",
|
"status": "s90e6c9",
|
||||||
@@ -191,7 +191,7 @@
|
|||||||
"charCount": 617,
|
"charCount": 617,
|
||||||
"wordCount": 101,
|
"wordCount": 101,
|
||||||
"paraCount": 3,
|
"paraCount": 3,
|
||||||
"cursorPos": 310
|
"cursorPos": 1182
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "s78ea90",
|
"status": "s78ea90",
|
||||||
@@ -216,7 +216,7 @@
|
|||||||
"charCount": 1909,
|
"charCount": 1909,
|
||||||
"wordCount": 346,
|
"wordCount": 346,
|
||||||
"paraCount": 7,
|
"paraCount": 7,
|
||||||
"cursorPos": 0
|
"cursorPos": 1940
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "sf24ce6",
|
"status": "sf24ce6",
|
||||||
@@ -241,7 +241,7 @@
|
|||||||
"charCount": 139,
|
"charCount": 139,
|
||||||
"wordCount": 28,
|
"wordCount": 28,
|
||||||
"paraCount": 1,
|
"paraCount": 1,
|
||||||
"cursorPos": 188
|
"cursorPos": 356
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "s90e6c9",
|
"status": "s90e6c9",
|
||||||
@@ -266,10 +266,10 @@
|
|||||||
"charCount": 189,
|
"charCount": 189,
|
||||||
"wordCount": 37,
|
"wordCount": 37,
|
||||||
"paraCount": 1,
|
"paraCount": 1,
|
||||||
"cursorPos": 0
|
"cursorPos": 237
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "s90e6c9",
|
"status": "sd51c5b",
|
||||||
"import": "ia857f0",
|
"import": "ia857f0",
|
||||||
"active": true
|
"active": true
|
||||||
}
|
}
|
||||||
@@ -341,10 +341,10 @@
|
|||||||
"charCount": 299,
|
"charCount": 299,
|
||||||
"wordCount": 55,
|
"wordCount": 55,
|
||||||
"paraCount": 2,
|
"paraCount": 2,
|
||||||
"cursorPos": 104
|
"cursorPos": 387
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "s90e6c9",
|
"status": "s8ae72a",
|
||||||
"import": "ia857f0",
|
"import": "ia857f0",
|
||||||
"active": true
|
"active": true
|
||||||
}
|
}
|
||||||
@@ -416,11 +416,11 @@
|
|||||||
"charCount": 49,
|
"charCount": 49,
|
||||||
"wordCount": 9,
|
"wordCount": 9,
|
||||||
"paraCount": 1,
|
"paraCount": 1,
|
||||||
"cursorPos": 24
|
"cursorPos": 23
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "sf12341",
|
"status": "sf12341",
|
||||||
"import": "icfb3a5",
|
"import": "i2d7a54",
|
||||||
"active": true
|
"active": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -441,11 +441,11 @@
|
|||||||
"charCount": 55,
|
"charCount": 55,
|
||||||
"wordCount": 9,
|
"wordCount": 9,
|
||||||
"paraCount": 1,
|
"paraCount": 1,
|
||||||
"cursorPos": 25
|
"cursorPos": 31
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "sf12341",
|
"status": "sf12341",
|
||||||
"import": "i2d7a54",
|
"import": "i56be10",
|
||||||
"active": true
|
"active": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -491,11 +491,11 @@
|
|||||||
"charCount": 76,
|
"charCount": 76,
|
||||||
"wordCount": 15,
|
"wordCount": 15,
|
||||||
"paraCount": 1,
|
"paraCount": 1,
|
||||||
"cursorPos": 20
|
"cursorPos": 111
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "sf12341",
|
"status": "sf12341",
|
||||||
"import": "i56be10",
|
"import": "i2d7a54",
|
||||||
"active": true
|
"active": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -516,11 +516,11 @@
|
|||||||
"charCount": 115,
|
"charCount": 115,
|
||||||
"wordCount": 24,
|
"wordCount": 24,
|
||||||
"paraCount": 1,
|
"paraCount": 1,
|
||||||
"cursorPos": 133
|
"cursorPos": 0
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "sf12341",
|
"status": "sf12341",
|
||||||
"import": "icfb3a5",
|
"import": "i4a1d39",
|
||||||
"active": true
|
"active": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -541,11 +541,11 @@
|
|||||||
"charCount": 28,
|
"charCount": 28,
|
||||||
"wordCount": 6,
|
"wordCount": 6,
|
||||||
"paraCount": 1,
|
"paraCount": 1,
|
||||||
"cursorPos": 45
|
"cursorPos": 62
|
||||||
},
|
},
|
||||||
"nameAttr": {
|
"nameAttr": {
|
||||||
"status": "sf12341",
|
"status": "sf12341",
|
||||||
"import": "i2d7a54",
|
"import": "icfb3a5",
|
||||||
"active": true
|
"active": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@@ -624,13 +624,113 @@
|
|||||||
"active": true
|
"active": true
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "Templates",
|
||||||
|
"itemAttr": {
|
||||||
|
"handle": "f4ed1ae756a1f",
|
||||||
|
"parent": null,
|
||||||
|
"root": "f4ed1ae756a1f",
|
||||||
|
"order": 5,
|
||||||
|
"type": "ROOT",
|
||||||
|
"class": "TEMPLATE",
|
||||||
|
"layout": "NO_LAYOUT"
|
||||||
|
},
|
||||||
|
"metaAttr": {
|
||||||
|
"expanded": true,
|
||||||
|
"heading": "H0",
|
||||||
|
"charCount": 0,
|
||||||
|
"wordCount": 0,
|
||||||
|
"paraCount": 0,
|
||||||
|
"cursorPos": 0
|
||||||
|
},
|
||||||
|
"nameAttr": {
|
||||||
|
"status": "sf12341",
|
||||||
|
"import": "ia857f0",
|
||||||
|
"active": false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Scene",
|
||||||
|
"itemAttr": {
|
||||||
|
"handle": "5aec885635c85",
|
||||||
|
"parent": "f4ed1ae756a1f",
|
||||||
|
"root": "f4ed1ae756a1f",
|
||||||
|
"order": 0,
|
||||||
|
"type": "FILE",
|
||||||
|
"class": "TEMPLATE",
|
||||||
|
"layout": "DOCUMENT"
|
||||||
|
},
|
||||||
|
"metaAttr": {
|
||||||
|
"expanded": false,
|
||||||
|
"heading": "H3",
|
||||||
|
"charCount": 9,
|
||||||
|
"wordCount": 2,
|
||||||
|
"paraCount": 1,
|
||||||
|
"cursorPos": 78
|
||||||
|
},
|
||||||
|
"nameAttr": {
|
||||||
|
"status": "sf12341",
|
||||||
|
"import": "ia857f0",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Chapter",
|
||||||
|
"itemAttr": {
|
||||||
|
"handle": "2a60782759c6f",
|
||||||
|
"parent": "f4ed1ae756a1f",
|
||||||
|
"root": "f4ed1ae756a1f",
|
||||||
|
"order": 1,
|
||||||
|
"type": "FILE",
|
||||||
|
"class": "TEMPLATE",
|
||||||
|
"layout": "DOCUMENT"
|
||||||
|
},
|
||||||
|
"metaAttr": {
|
||||||
|
"expanded": false,
|
||||||
|
"heading": "H2",
|
||||||
|
"charCount": 11,
|
||||||
|
"wordCount": 2,
|
||||||
|
"paraCount": 1,
|
||||||
|
"cursorPos": 81
|
||||||
|
},
|
||||||
|
"nameAttr": {
|
||||||
|
"status": "sf12341",
|
||||||
|
"import": "ia857f0",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Character Note",
|
||||||
|
"itemAttr": {
|
||||||
|
"handle": "5ee8aebcdebc9",
|
||||||
|
"parent": "f4ed1ae756a1f",
|
||||||
|
"root": "f4ed1ae756a1f",
|
||||||
|
"order": 2,
|
||||||
|
"type": "FILE",
|
||||||
|
"class": "TEMPLATE",
|
||||||
|
"layout": "NOTE"
|
||||||
|
},
|
||||||
|
"metaAttr": {
|
||||||
|
"expanded": false,
|
||||||
|
"heading": "H1",
|
||||||
|
"charCount": 53,
|
||||||
|
"wordCount": 7,
|
||||||
|
"paraCount": 1,
|
||||||
|
"cursorPos": 75
|
||||||
|
},
|
||||||
|
"nameAttr": {
|
||||||
|
"status": "sf12341",
|
||||||
|
"import": "ia857f0",
|
||||||
|
"active": true
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Trash",
|
"name": "Trash",
|
||||||
"itemAttr": {
|
"itemAttr": {
|
||||||
"handle": "98acd8c76c93a",
|
"handle": "98acd8c76c93a",
|
||||||
"parent": null,
|
"parent": null,
|
||||||
"root": "98acd8c76c93a",
|
"root": "98acd8c76c93a",
|
||||||
"order": 5,
|
"order": 6,
|
||||||
"type": "ROOT",
|
"type": "ROOT",
|
||||||
"class": "TRASH",
|
"class": "TRASH",
|
||||||
"layout": "NO_LAYOUT"
|
"layout": "NO_LAYOUT"
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2020-05-28 09:59:15">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2020-05-28 09:59:15">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jay Doh</author>
|
<author>Jay Doh</author>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="22" novelWords="0" notesWords="0">
|
<content items="22" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
|
||||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="s000002" import="i000007">Novel</name>
|
<name status="s000002" import="i000007">Novel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2020-06-26 21:20:24">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2020-06-26 21:20:24">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jay Doh</author>
|
<author>Jay Doh</author>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="22" novelWords="0" notesWords="0">
|
<content items="22" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
|
||||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="s000002" import="i000007">Novel</name>
|
<name status="s000002" import="i000007">Novel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2021-08-30 23:33:44">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2021-08-30 23:33:44">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jay Doh</author>
|
<author>Jay Doh</author>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="25" novelWords="840" notesWords="376">
|
<content items="25" novelWords="840" notesWords="376" novelChars="0" notesChars="0">
|
||||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="s000002" import="i000007">Novel</name>
|
<name status="s000002" import="i000007">Novel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2022-10-25 18:26:15">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2022-10-25 18:26:15">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jay Doh</author>
|
<author>Jay Doh</author>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="25" novelWords="830" notesWords="376">
|
<content items="25" novelWords="830" notesWords="376" novelChars="0" notesChars="0">
|
||||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="s000002" import="i000007">Novel</name>
|
<name status="s000002" import="i000007">Novel</name>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
<?xml version='1.0' encoding='utf-8'?>
|
<?xml version='1.0' encoding='utf-8'?>
|
||||||
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2022-10-15 12:12:59">
|
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2022-10-15 12:12:59">
|
||||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
||||||
<name>Sample Project</name>
|
<name>Sample Project</name>
|
||||||
<author>Jay Doh</author>
|
<author>Jay Doh</author>
|
||||||
@@ -35,7 +35,7 @@
|
|||||||
<entry key="i56be10" count="1" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
<entry key="i56be10" count="1" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||||
</importance>
|
</importance>
|
||||||
</settings>
|
</settings>
|
||||||
<content items="27" novelWords="954" notesWords="409">
|
<content items="27" novelWords="954" notesWords="409" novelChars="0" notesChars="0">
|
||||||
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
|
||||||
<meta expanded="yes" />
|
<meta expanded="yes" />
|
||||||
<name status="sc24b8f" import="ia857f0">Novel</name>
|
<name status="sc24b8f" import="ia857f0">Novel</name>
|
||||||
|
|||||||
@@ -1049,6 +1049,12 @@ def testCoreIndex_TagsIndex():
|
|||||||
assert tagsIndex.tagClass("Tag3") == nwItemClass.PLOT.name
|
assert tagsIndex.tagClass("Tag3") == nwItemClass.PLOT.name
|
||||||
assert tagsIndex.tagClass("Tag4") is None
|
assert tagsIndex.tagClass("Tag4") is None
|
||||||
|
|
||||||
|
# Change class of item
|
||||||
|
tagsIndex.updateClass("0000000000003", nwItemClass.WORLD.name)
|
||||||
|
assert tagsIndex.tagClass("Tag3") == nwItemClass.WORLD.name
|
||||||
|
tagsIndex.updateClass("0000000000003", nwItemClass.PLOT.name)
|
||||||
|
assert tagsIndex.tagClass("Tag3") == nwItemClass.PLOT.name
|
||||||
|
|
||||||
# Pack Data
|
# Pack Data
|
||||||
assert tagsIndex.packData() == content
|
assert tagsIndex.packData() == content
|
||||||
|
|
||||||
|
|||||||
@@ -224,6 +224,14 @@ def testCoreIndexData_IndexHeading():
|
|||||||
"story.crisis": "It exploded!",
|
"story.crisis": "It exploded!",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# Set Note Comment
|
||||||
|
head.setComment(nwComment.NOTE.name, "consitency", "Only explode once")
|
||||||
|
assert head.comments == {
|
||||||
|
"summary": "In the beginning ...",
|
||||||
|
"story.crisis": "It exploded!",
|
||||||
|
"note.consitency": "Only explode once",
|
||||||
|
}
|
||||||
|
|
||||||
# Set Tag
|
# Set Tag
|
||||||
head.setTag("Stuff")
|
head.setTag("Stuff")
|
||||||
assert head.tag == "stuff" # Case insensitive
|
assert head.tag == "stuff" # Case insensitive
|
||||||
@@ -240,6 +248,7 @@ def testCoreIndexData_IndexHeading():
|
|||||||
"refs": {"stuff": "@object"},
|
"refs": {"stuff": "@object"},
|
||||||
"summary": "In the beginning ...",
|
"summary": "In the beginning ...",
|
||||||
"story.crisis": "It exploded!",
|
"story.crisis": "It exploded!",
|
||||||
|
"note.consitency": "Only explode once",
|
||||||
}
|
}
|
||||||
|
|
||||||
# Unpack KeyError
|
# Unpack KeyError
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ import pytest
|
|||||||
|
|
||||||
from PyQt6.QtGui import QIcon
|
from PyQt6.QtGui import QIcon
|
||||||
|
|
||||||
|
from novelwriter import CONFIG
|
||||||
from novelwriter.core.item import NWItem
|
from novelwriter.core.item import NWItem
|
||||||
from novelwriter.core.project import NWProject
|
from novelwriter.core.project import NWProject
|
||||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||||
@@ -168,6 +169,15 @@ def testCoreItem_Setters(mockGUI, mockRnd, fncPath):
|
|||||||
item.setParaCount(1)
|
item.setParaCount(1)
|
||||||
assert item.paraCount == 1
|
assert item.paraCount == 1
|
||||||
|
|
||||||
|
# MainCount
|
||||||
|
item.setWordCount(123)
|
||||||
|
item.setCharCount(1234)
|
||||||
|
CONFIG.useCharCount = False
|
||||||
|
assert item.mainCount == 123
|
||||||
|
CONFIG.useCharCount = True
|
||||||
|
assert item.mainCount == 1234
|
||||||
|
CONFIG.useCharCount = False
|
||||||
|
|
||||||
# CursorPos
|
# CursorPos
|
||||||
item.setCursorPos(None)
|
item.setCursorPos(None)
|
||||||
assert item.cursorPos == 0
|
assert item.cursorPos == 0
|
||||||
|
|||||||
@@ -52,8 +52,8 @@ class MockProject:
|
|||||||
@pytest.fixture(scope="function", autouse=True)
|
@pytest.fixture(scope="function", autouse=True)
|
||||||
def mockVersion(monkeypatch):
|
def mockVersion(monkeypatch):
|
||||||
"""Mock the version info to prevent diff from failing."""
|
"""Mock the version info to prevent diff from failing."""
|
||||||
monkeypatch.setattr("novelwriter.core.projectxml.__version__", "2.0-rc1")
|
monkeypatch.setattr("novelwriter.core.projectxml.__version__", "2.7b1")
|
||||||
monkeypatch.setattr("novelwriter.core.projectxml.__hexversion__", "0x020000c1")
|
monkeypatch.setattr("novelwriter.core.projectxml.__hexversion__", "0x020700b1")
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
||||||
@@ -137,23 +137,23 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath):
|
|||||||
assert xmlReader.state == XMLReadState.PARSED_OK
|
assert xmlReader.state == XMLReadState.PARSED_OK
|
||||||
assert xmlReader.xmlRoot == "novelWriterXML"
|
assert xmlReader.xmlRoot == "novelWriterXML"
|
||||||
assert xmlReader.xmlVersion == 0x0105
|
assert xmlReader.xmlVersion == 0x0105
|
||||||
assert xmlReader.xmlRevision == 4
|
assert xmlReader.xmlRevision == 5
|
||||||
assert xmlReader.appVersion == "2.0-rc1"
|
assert xmlReader.appVersion == "2.7b1"
|
||||||
assert xmlReader.hexVersion == 0x020000c1
|
assert xmlReader.hexVersion == 0x020700b1
|
||||||
|
|
||||||
# Check loaded data
|
# Check loaded data
|
||||||
assert data.name == "Sample Project"
|
assert data.name == "Sample Project"
|
||||||
assert data.author == "Jane Smith"
|
assert data.author == "Jane Smith"
|
||||||
assert data.saveCount == 5
|
assert data.saveCount == 2179
|
||||||
assert data.autoCount == 10
|
assert data.autoCount == 285
|
||||||
assert data.editTime == 1000
|
assert data.editTime == 1000
|
||||||
|
|
||||||
assert data.doBackup is True
|
assert data.doBackup is False
|
||||||
assert data.language == "en_GB"
|
assert data.language == "en_GB"
|
||||||
assert data.spellCheck is True
|
assert data.spellCheck is True
|
||||||
assert data.spellLang == "en_GB"
|
assert data.spellLang is None
|
||||||
assert data.initCounts == (954, 409)
|
assert data.initCounts == (1016, 416, 5602, 2285)
|
||||||
assert data.currCounts == (954, 409)
|
assert data.currCounts == (1016, 416, 5602, 2285)
|
||||||
|
|
||||||
assert data.getLastHandle("editor") == "636b6aa9b697b"
|
assert data.getLastHandle("editor") == "636b6aa9b697b"
|
||||||
assert data.getLastHandle("viewer") == "636b6aa9b697b"
|
assert data.getLastHandle("viewer") == "636b6aa9b697b"
|
||||||
@@ -182,33 +182,36 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath):
|
|||||||
assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58)
|
assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58)
|
||||||
|
|
||||||
assert data.itemImport["ia857f0"].color == QColor(100, 100, 100)
|
assert data.itemImport["ia857f0"].color == QColor(100, 100, 100)
|
||||||
assert data.itemImport["icfb3a5"].color == QColor(0, 122, 188)
|
assert data.itemImport["i4a1d39"].color == QColor(220, 138, 221)
|
||||||
assert data.itemImport["i2d7a54"].color == QColor(21, 0, 180)
|
assert data.itemImport["icfb3a5"].color == QColor(220, 138, 221)
|
||||||
assert data.itemImport["i56be10"].color == QColor(117, 0, 175)
|
assert data.itemImport["i2d7a54"].color == QColor(220, 138, 221)
|
||||||
|
assert data.itemImport["i56be10"].color == QColor(220, 138, 221)
|
||||||
|
|
||||||
assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE
|
assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE
|
||||||
assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE
|
assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE
|
||||||
assert data.itemStatus["sc24b8f"].shape == nwStatusShape.SQUARE
|
assert data.itemStatus["sc24b8f"].shape == nwStatusShape.BARS_1
|
||||||
assert data.itemStatus["s90e6c9"].shape == nwStatusShape.SQUARE
|
assert data.itemStatus["s90e6c9"].shape == nwStatusShape.BARS_2
|
||||||
assert data.itemStatus["sd51c5b"].shape == nwStatusShape.SQUARE
|
assert data.itemStatus["sd51c5b"].shape == nwStatusShape.BARS_3
|
||||||
assert data.itemStatus["s8ae72a"].shape == nwStatusShape.SQUARE
|
assert data.itemStatus["s8ae72a"].shape == nwStatusShape.BARS_4
|
||||||
assert data.itemStatus["s78ea90"].shape == nwStatusShape.SQUARE
|
assert data.itemStatus["s78ea90"].shape == nwStatusShape.STAR
|
||||||
|
|
||||||
assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE
|
assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE
|
||||||
assert data.itemImport["icfb3a5"].shape == nwStatusShape.SQUARE
|
assert data.itemImport["i4a1d39"].shape == nwStatusShape.BLOCK_1
|
||||||
assert data.itemImport["i2d7a54"].shape == nwStatusShape.SQUARE
|
assert data.itemImport["icfb3a5"].shape == nwStatusShape.BLOCK_2
|
||||||
assert data.itemImport["i56be10"].shape == nwStatusShape.SQUARE
|
assert data.itemImport["i2d7a54"].shape == nwStatusShape.BLOCK_3
|
||||||
|
assert data.itemImport["i56be10"].shape == nwStatusShape.BLOCK_4
|
||||||
|
|
||||||
assert data.itemStatus["sf12341"].count == 4
|
assert data.itemStatus["sf12341"].count == 8
|
||||||
assert data.itemStatus["sf24ce6"].count == 2
|
assert data.itemStatus["sf24ce6"].count == 2
|
||||||
assert data.itemStatus["sc24b8f"].count == 3
|
assert data.itemStatus["sc24b8f"].count == 3
|
||||||
assert data.itemStatus["s90e6c9"].count == 7
|
assert data.itemStatus["s90e6c9"].count == 5
|
||||||
assert data.itemStatus["sd51c5b"].count == 0
|
assert data.itemStatus["sd51c5b"].count == 1
|
||||||
assert data.itemStatus["s8ae72a"].count == 0
|
assert data.itemStatus["s8ae72a"].count == 1
|
||||||
assert data.itemStatus["s78ea90"].count == 1
|
assert data.itemStatus["s78ea90"].count == 1
|
||||||
|
|
||||||
assert data.itemImport["ia857f0"].count == 5
|
assert data.itemImport["ia857f0"].count == 5
|
||||||
assert data.itemImport["icfb3a5"].count == 2
|
assert data.itemImport["i4a1d39"].count == 1
|
||||||
|
assert data.itemImport["icfb3a5"].count == 1
|
||||||
assert data.itemImport["i2d7a54"].count == 2
|
assert data.itemImport["i2d7a54"].count == 2
|
||||||
assert data.itemImport["i56be10"].count == 1
|
assert data.itemImport["i56be10"].count == 1
|
||||||
|
|
||||||
@@ -281,8 +284,8 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockGUI, mockRnd):
|
|||||||
assert data.language is None # Doesn't exist in 1.0
|
assert data.language is None # Doesn't exist in 1.0
|
||||||
assert data.spellCheck is True
|
assert data.spellCheck is True
|
||||||
assert data.spellLang is None # Doesn't exist in 1.0
|
assert data.spellLang is None # Doesn't exist in 1.0
|
||||||
assert data.initCounts == (0, 0)
|
assert data.initCounts == (0, 0, 0, 0)
|
||||||
assert data.currCounts == (0, 0)
|
assert data.currCounts == (0, 0, 0, 0)
|
||||||
|
|
||||||
assert data.getLastHandle("editor") is None # Dropped by conversion
|
assert data.getLastHandle("editor") is None # Dropped by conversion
|
||||||
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
||||||
@@ -426,8 +429,8 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockGUI, mockRnd):
|
|||||||
assert data.language is None # Doesn't exist in 1.1
|
assert data.language is None # Doesn't exist in 1.1
|
||||||
assert data.spellCheck is True
|
assert data.spellCheck is True
|
||||||
assert data.spellLang is None # Doesn't exist in 1.1
|
assert data.spellLang is None # Doesn't exist in 1.1
|
||||||
assert data.initCounts == (0, 0)
|
assert data.initCounts == (0, 0, 0, 0)
|
||||||
assert data.currCounts == (0, 0)
|
assert data.currCounts == (0, 0, 0, 0)
|
||||||
|
|
||||||
assert data.getLastHandle("editor") is None # Dropped by conversion
|
assert data.getLastHandle("editor") is None # Dropped by conversion
|
||||||
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
||||||
@@ -571,8 +574,8 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockGUI, mockRnd):
|
|||||||
assert data.language == "en_GB"
|
assert data.language == "en_GB"
|
||||||
assert data.spellCheck is True
|
assert data.spellCheck is True
|
||||||
assert data.spellLang == "en_GB"
|
assert data.spellLang == "en_GB"
|
||||||
assert data.initCounts == (840, 376)
|
assert data.initCounts == (840, 376, 0, 0)
|
||||||
assert data.currCounts == (840, 376)
|
assert data.currCounts == (840, 376, 0, 0)
|
||||||
|
|
||||||
assert data.getLastHandle("editor") is None # Dropped by conversion
|
assert data.getLastHandle("editor") is None # Dropped by conversion
|
||||||
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
||||||
@@ -719,8 +722,8 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockGUI, mockRnd):
|
|||||||
assert data.language == "en_GB"
|
assert data.language == "en_GB"
|
||||||
assert data.spellCheck is True
|
assert data.spellCheck is True
|
||||||
assert data.spellLang == "en_GB"
|
assert data.spellLang == "en_GB"
|
||||||
assert data.initCounts == (830, 376)
|
assert data.initCounts == (830, 376, 0, 0)
|
||||||
assert data.currCounts == (830, 376)
|
assert data.currCounts == (830, 376, 0, 0)
|
||||||
|
|
||||||
assert data.getLastHandle("editor") is None # Dropped by conversion
|
assert data.getLastHandle("editor") is None # Dropped by conversion
|
||||||
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
||||||
@@ -867,8 +870,8 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockGUI, mockRnd):
|
|||||||
assert data.language == "en_GB"
|
assert data.language == "en_GB"
|
||||||
assert data.spellCheck is True
|
assert data.spellCheck is True
|
||||||
assert data.spellLang == "en_GB"
|
assert data.spellLang == "en_GB"
|
||||||
assert data.initCounts == (954, 409)
|
assert data.initCounts == (954, 409, 0, 0)
|
||||||
assert data.currCounts == (954, 409)
|
assert data.currCounts == (954, 409, 0, 0)
|
||||||
|
|
||||||
assert data.getLastHandle("editor") is None # Dropped by conversion
|
assert data.getLastHandle("editor") is None # Dropped by conversion
|
||||||
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
assert data.getLastHandle("viewer") is None # Dropped by conversion
|
||||||
|
|||||||
@@ -43,8 +43,8 @@ def testCoreSessions_Main(monkeypatch, mockGUI, fncPath):
|
|||||||
assert isinstance(logFile, Path)
|
assert isinstance(logFile, Path)
|
||||||
|
|
||||||
# Set some mock word counts
|
# Set some mock word counts
|
||||||
project.data.setInitCounts(50, 60)
|
project.data.setInitCounts(50, 60, 500, 600)
|
||||||
project.data.setCurrCounts(160, 150)
|
project.data.setCurrCounts(160, 150, 1600, 1500)
|
||||||
|
|
||||||
# The project init should already have created the session
|
# The project init should already have created the session
|
||||||
sessLog = project.session
|
sessLog = project.session
|
||||||
@@ -71,17 +71,19 @@ def testCoreSessions_Main(monkeypatch, mockGUI, fncPath):
|
|||||||
assert records[1]["type"] == "record"
|
assert records[1]["type"] == "record"
|
||||||
assert records[1]["novel"] == 160
|
assert records[1]["novel"] == 160
|
||||||
assert records[1]["notes"] == 150
|
assert records[1]["notes"] == 150
|
||||||
|
assert records[1]["cnovel"] == 1600
|
||||||
|
assert records[1]["cnotes"] == 1500
|
||||||
assert records[1]["idle"] == 1 # Should be rounded to full seconds
|
assert records[1]["idle"] == 1 # Should be rounded to full seconds
|
||||||
|
|
||||||
# Adding another record without changing word count should do nothing
|
# Adding another record without changing word count should do nothing
|
||||||
project.data.setInitCounts(160, 150)
|
project.data.setInitCounts(160, 150, 1600, 1500)
|
||||||
project.data.setCurrCounts(160, 150)
|
project.data.setCurrCounts(160, 150, 1600, 1500)
|
||||||
assert sessLog.appendSession(1.6) is False
|
assert sessLog.appendSession(1.6) is False
|
||||||
assert len(list(sessLog.iterRecords())) == 2
|
assert len(list(sessLog.iterRecords())) == 2
|
||||||
|
|
||||||
# But adding when count has changed should
|
# But adding when count has changed should
|
||||||
project.data.setInitCounts(160, 150)
|
project.data.setInitCounts(160, 150, 1600, 1500)
|
||||||
project.data.setCurrCounts(270, 240)
|
project.data.setCurrCounts(270, 240, 2700, 2400)
|
||||||
sessLog._start -= 350.0 # Backdate the session start to allow logging
|
sessLog._start -= 350.0 # Backdate the session start to allow logging
|
||||||
assert sessLog.appendSession(1.6) is True
|
assert sessLog.appendSession(1.6) is True
|
||||||
records = list(sessLog.iterRecords())
|
records = list(sessLog.iterRecords())
|
||||||
|
|||||||
@@ -435,8 +435,8 @@ def testCoreStorage_OldFormatConvert(monkeypatch, mockGUI, fncPath):
|
|||||||
sessLogOld.write_text((
|
sessLogOld.write_text((
|
||||||
"# Offset 150\n"
|
"# Offset 150\n"
|
||||||
"# Start Time End Time Novel Notes Idle\n"
|
"# Start Time End Time Novel Notes Idle\n"
|
||||||
"2021-02-02 02:02:02 2021-02-02 03:03:03 200 200 10\n"
|
"2021-02-02 02:02:02 2021-02-02 03:03:03 200 200 10\n"
|
||||||
"2021-03-03 03:03:03 2021-03-03 04:04:04 300 300 20\n"
|
"2021-03-03 03:03:03 2021-03-03 04:04:04 300 300 20\n"
|
||||||
), encoding="utf-8")
|
), encoding="utf-8")
|
||||||
|
|
||||||
assert sessLogOld.exists() is True
|
assert sessLogOld.exists() is True
|
||||||
@@ -496,6 +496,8 @@ def testCoreStorage_OldFormatConvert(monkeypatch, mockGUI, fncPath):
|
|||||||
"end": "2021-02-02 03:03:03",
|
"end": "2021-02-02 03:03:03",
|
||||||
"novel": 200,
|
"novel": 200,
|
||||||
"notes": 200,
|
"notes": 200,
|
||||||
|
"cnovel": 0,
|
||||||
|
"cnotes": 0,
|
||||||
"idle": 10,
|
"idle": 10,
|
||||||
}
|
}
|
||||||
assert data[2] == {
|
assert data[2] == {
|
||||||
@@ -504,6 +506,8 @@ def testCoreStorage_OldFormatConvert(monkeypatch, mockGUI, fncPath):
|
|||||||
"end": "2021-03-03 04:04:04",
|
"end": "2021-03-03 04:04:04",
|
||||||
"novel": 300,
|
"novel": 300,
|
||||||
"notes": 300,
|
"notes": 300,
|
||||||
|
"cnovel": 0,
|
||||||
|
"cnotes": 0,
|
||||||
"idle": 20,
|
"idle": 20,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -436,7 +436,7 @@ def testCoreTree_OtherMethods(qtbot, monkeypatch, mockGUI, fncPath, mockRnd):
|
|||||||
]
|
]
|
||||||
|
|
||||||
# Refresh All
|
# Refresh All
|
||||||
assert tree.sumWords() == (9, 0)
|
assert tree.sumCounts() == (9, 0, 40, 0)
|
||||||
assert tree.model.root.count == 9
|
assert tree.model.root.count == 9
|
||||||
|
|
||||||
for node in tree.nodes.values():
|
for node in tree.nodes.values():
|
||||||
|
|||||||
@@ -163,14 +163,17 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
|||||||
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
|
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
|
||||||
prefs.nativeFont.setChecked(True) # Use OS font dialog
|
prefs.nativeFont.setChecked(True) # Use OS font dialog
|
||||||
prefs.guiFontButton.click()
|
prefs.guiFontButton.click()
|
||||||
|
|
||||||
prefs.hideVScroll.setChecked(True)
|
prefs.hideVScroll.setChecked(True)
|
||||||
prefs.hideHScroll.setChecked(True)
|
prefs.hideHScroll.setChecked(True)
|
||||||
|
prefs.useCharCount.setChecked(True)
|
||||||
|
|
||||||
assert CONFIG.guiLocale != "en_US"
|
assert CONFIG.guiLocale != "en_US"
|
||||||
assert CONFIG.guiTheme != "default_dark"
|
assert CONFIG.guiTheme != "default_dark"
|
||||||
assert CONFIG.guiFont.family() != ""
|
assert CONFIG.guiFont.family() != ""
|
||||||
assert CONFIG.hideVScroll is False
|
assert CONFIG.hideVScroll is False
|
||||||
assert CONFIG.hideHScroll is False
|
assert CONFIG.hideHScroll is False
|
||||||
|
assert CONFIG.useCharCount is False
|
||||||
|
|
||||||
# Document Style
|
# Document Style
|
||||||
prefs.guiSyntax.setCurrentIndex(prefs.guiSyntax.findData("default_dark"))
|
prefs.guiSyntax.setCurrentIndex(prefs.guiSyntax.findData("default_dark"))
|
||||||
@@ -178,6 +181,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
|||||||
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
|
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
|
||||||
prefs.nativeFont.setChecked(False) # Use Qt font dialog
|
prefs.nativeFont.setChecked(False) # Use Qt font dialog
|
||||||
prefs.textFontButton.click()
|
prefs.textFontButton.click()
|
||||||
|
|
||||||
prefs.showFullPath.setChecked(False)
|
prefs.showFullPath.setChecked(False)
|
||||||
prefs.incNotesWCount.setChecked(False)
|
prefs.incNotesWCount.setChecked(False)
|
||||||
|
|
||||||
@@ -344,6 +348,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
|||||||
assert CONFIG.guiFont == QFont()
|
assert CONFIG.guiFont == QFont()
|
||||||
assert CONFIG.hideVScroll is True
|
assert CONFIG.hideVScroll is True
|
||||||
assert CONFIG.hideHScroll is True
|
assert CONFIG.hideHScroll is True
|
||||||
|
assert CONFIG.useCharCount is True
|
||||||
|
|
||||||
# Document Style
|
# Document Style
|
||||||
assert CONFIG.guiSyntax == "default_dark"
|
assert CONFIG.guiSyntax == "default_dark"
|
||||||
|
|||||||
@@ -1877,10 +1877,68 @@ def testGuiEditor_Completer(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
qtbot.keyClick(completer, Qt.Key.Key_Escape, delay=KEY_DELAY)
|
qtbot.keyClick(completer, Qt.Key.Key_Escape, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
assert docEditor.getText() == (
|
assert docEditor.getText() == (
|
||||||
"### Scene One\n\n"
|
"### Scene One\n\n"
|
||||||
"@char: Jane\n"
|
"@char: Jane\n"
|
||||||
"@focus: John"
|
"@focus: John\n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Send keypresses to the completer object for a comment
|
||||||
|
qtbot.keyClick(docEditor, "%", delay=KEY_DELAY)
|
||||||
|
assert len(completer.actions()) == 4
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
|
assert docEditor.getText() == (
|
||||||
|
"### Scene One\n\n"
|
||||||
|
"@char: Jane\n"
|
||||||
|
"@focus: John\n"
|
||||||
|
"%Synopsis: \n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Auto-complete story comment
|
||||||
|
SHARED.project.index._itemIndex._cache.story.add("Resolution")
|
||||||
|
qtbot.keyClick(docEditor, "%", delay=KEY_DELAY)
|
||||||
|
assert len(completer.actions()) == 4
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, ".", delay=KEY_DELAY)
|
||||||
|
assert len(completer.actions()) == 1
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
|
assert docEditor.getText() == (
|
||||||
|
"### Scene One\n\n"
|
||||||
|
"@char: Jane\n"
|
||||||
|
"@focus: John\n"
|
||||||
|
"%Synopsis: \n"
|
||||||
|
"%Story.Resolution: \n"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Auto-complete note comment
|
||||||
|
SHARED.project.index._itemIndex._cache.note.add("Consistency")
|
||||||
|
qtbot.keyClick(docEditor, "%", delay=KEY_DELAY)
|
||||||
|
assert len(completer.actions()) == 4
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, ".", delay=KEY_DELAY)
|
||||||
|
assert len(completer.actions()) == 1
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Down, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(completer, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
|
qtbot.keyClick(docEditor, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||||
|
assert docEditor.getText() == (
|
||||||
|
"### Scene One\n\n"
|
||||||
|
"@char: Jane\n"
|
||||||
|
"@focus: John\n"
|
||||||
|
"%Synopsis: \n"
|
||||||
|
"%Story.Resolution: \n"
|
||||||
|
"%Note.Consistency: \n"
|
||||||
)
|
)
|
||||||
|
|
||||||
# qtbot.stop()
|
# qtbot.stop()
|
||||||
@@ -1957,7 +2015,7 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, m
|
|||||||
assert docEditor.docFooter.wordsText.text() == "Words: 0 (+0)"
|
assert docEditor.docFooter.wordsText.text() == "Words: 0 (+0)"
|
||||||
|
|
||||||
# Open a document and populate it
|
# Open a document and populate it
|
||||||
SHARED.project.tree[C.hSceneDoc]._initCount = 0 # type: ignore
|
SHARED.project.tree[C.hSceneDoc]._wordInit = 0 # type: ignore
|
||||||
SHARED.project.tree[C.hSceneDoc]._wordCount = 0 # type: ignore
|
SHARED.project.tree[C.hSceneDoc]._wordCount = 0 # type: ignore
|
||||||
assert nwGUI.openDocument(C.hSceneDoc) is True
|
assert nwGUI.openDocument(C.hSceneDoc) is True
|
||||||
|
|
||||||
@@ -1981,7 +2039,6 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, m
|
|||||||
assert threadPool.objectID() == id(docEditor._wCounterDoc)
|
assert threadPool.objectID() == id(docEditor._wCounterDoc)
|
||||||
|
|
||||||
docEditor._wCounterDoc.run()
|
docEditor._wCounterDoc.run()
|
||||||
# docEditor._updateDocCounts(cC, wC, pC)
|
|
||||||
assert SHARED.project.tree[C.hSceneDoc]._charCount == cC # type: ignore
|
assert SHARED.project.tree[C.hSceneDoc]._charCount == cC # type: ignore
|
||||||
assert SHARED.project.tree[C.hSceneDoc]._wordCount == wC # type: ignore
|
assert SHARED.project.tree[C.hSceneDoc]._wordCount == wC # type: ignore
|
||||||
assert SHARED.project.tree[C.hSceneDoc]._paraCount == pC # type: ignore
|
assert SHARED.project.tree[C.hSceneDoc]._paraCount == pC # type: ignore
|
||||||
@@ -1993,7 +2050,7 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, m
|
|||||||
assert threadPool.objectID() == id(docEditor._wCounterSel)
|
assert threadPool.objectID() == id(docEditor._wCounterSel)
|
||||||
|
|
||||||
docEditor._wCounterSel.run()
|
docEditor._wCounterSel.run()
|
||||||
assert docEditor.docFooter.wordsText.text() == f"Words: {wC} selected"
|
assert docEditor.docFooter.wordsText.text() == f"Selected: {wC}"
|
||||||
|
|
||||||
# qtbot.stop()
|
# qtbot.stop()
|
||||||
|
|
||||||
|
|||||||
@@ -184,6 +184,7 @@ def testGuiMain_UpdateTheme(qtbot, nwGUI):
|
|||||||
CONFIG.guiSyntax = "default_dark"
|
CONFIG.guiSyntax = "default_dark"
|
||||||
mainTheme.loadTheme()
|
mainTheme.loadTheme()
|
||||||
mainTheme.loadSyntax()
|
mainTheme.loadSyntax()
|
||||||
|
nwGUI._processConfigChanges(False, True, False, False)
|
||||||
nwGUI._processConfigChanges(True, True, True, True)
|
nwGUI._processConfigChanges(True, True, True, True)
|
||||||
|
|
||||||
syntax = SHARED.theme.syntaxTheme
|
syntax = SHARED.theme.syntaxTheme
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ def testGuiNovelView_Content(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
assert novelTree._getModel() is None
|
assert novelTree._getModel() is None
|
||||||
|
|
||||||
# Reload
|
# Reload
|
||||||
novelBar._forceRefreshNovelTree()
|
novelView.refreshCurrentTree()
|
||||||
model = novelTree._getModel()
|
model = novelTree._getModel()
|
||||||
assert isinstance(model, NovelModel)
|
assert isinstance(model, NovelModel)
|
||||||
|
|
||||||
|
|||||||
@@ -109,4 +109,18 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
|||||||
nwGUI._timeTick()
|
nwGUI._timeTick()
|
||||||
assert status.statsText.text() == "Words: 11 (+11)"
|
assert status.statsText.text() == "Words: 11 (+11)"
|
||||||
|
|
||||||
|
# Switch to character count
|
||||||
|
CONFIG.useCharCount = True
|
||||||
|
status.initSettings()
|
||||||
|
with monkeypatch.context() as mp:
|
||||||
|
mp.setattr("novelwriter.guimain.time", lambda *a: 50.0)
|
||||||
|
CONFIG.incNotesWCount = True
|
||||||
|
nwGUI._lastTotalCount = 0
|
||||||
|
nwGUI._timeTick()
|
||||||
|
assert status.statsText.text() == "Characters: 46 (+46)"
|
||||||
|
CONFIG.incNotesWCount = False
|
||||||
|
nwGUI._lastTotalCount = 0
|
||||||
|
nwGUI._timeTick()
|
||||||
|
assert status.statsText.text() == "Characters: 40 (+40)"
|
||||||
|
|
||||||
# qtbot.stop()
|
# qtbot.stop()
|
||||||
|
|||||||
@@ -72,7 +72,7 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
@pyqtSlot(BuildSettings)
|
@pyqtSlot(BuildSettings)
|
||||||
def _testNewSettingsReady(new: BuildSettings):
|
def _testNewSettingsReady(new: BuildSettings):
|
||||||
nonlocal triggered
|
nonlocal triggered
|
||||||
assert new is build
|
assert new.buildID == build.buildID
|
||||||
triggered = True
|
triggered = True
|
||||||
|
|
||||||
# Capture Apply button
|
# Capture Apply button
|
||||||
@@ -103,6 +103,7 @@ def testToolBuildSettings_Init(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
|
|
||||||
with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000):
|
with qtbot.waitSignal(bSettings.newSettingsReady, timeout=5000):
|
||||||
bSettings.newSettingsReady.connect(_testNewSettingsReady)
|
bSettings.newSettingsReady.connect(_testNewSettingsReady)
|
||||||
|
bSettings._build._changed = True
|
||||||
bSettings.close()
|
bSettings.close()
|
||||||
|
|
||||||
assert triggered
|
assert triggered
|
||||||
@@ -140,6 +141,9 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
bSettings.show()
|
bSettings.show()
|
||||||
bSettings.loadContent()
|
bSettings.loadContent()
|
||||||
|
|
||||||
|
sBuild = bSettings._build
|
||||||
|
assert sBuild.buildID == build.buildID
|
||||||
|
|
||||||
filterTab = bSettings.optTabSelect
|
filterTab = bSettings.optTabSelect
|
||||||
button = bSettings.sidebar._group.button(bSettings.OPT_FILTERS)
|
button = bSettings.sidebar._group.button(bSettings.OPT_FILTERS)
|
||||||
assert button is not None
|
assert button is not None
|
||||||
@@ -153,15 +157,15 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
# Un-toggle note folders
|
# Un-toggle note folders
|
||||||
filterTab.filterOpt._widgets[switchMap["worldRoot"]].setChecked(False) # World Root
|
filterTab.filterOpt._widgets[switchMap["worldRoot"]].setChecked(False) # World Root
|
||||||
assert filterTab.optTree.topLevelItemCount() == 3
|
assert filterTab.optTree.topLevelItemCount() == 3
|
||||||
assert C.hWorldRoot in build._skipRoot
|
assert C.hWorldRoot in sBuild._skipRoot
|
||||||
|
|
||||||
filterTab.filterOpt._widgets[switchMap["charRoot"]].setChecked(False) # Char Root
|
filterTab.filterOpt._widgets[switchMap["charRoot"]].setChecked(False) # Char Root
|
||||||
assert filterTab.optTree.topLevelItemCount() == 2
|
assert filterTab.optTree.topLevelItemCount() == 2
|
||||||
assert C.hCharRoot in build._skipRoot
|
assert C.hCharRoot in sBuild._skipRoot
|
||||||
|
|
||||||
filterTab.filterOpt._widgets[switchMap["plotRoot"]].setChecked(False) # Plot Root
|
filterTab.filterOpt._widgets[switchMap["plotRoot"]].setChecked(False) # Plot Root
|
||||||
assert filterTab.optTree.topLevelItemCount() == 1
|
assert filterTab.optTree.topLevelItemCount() == 1
|
||||||
assert C.hPlotRoot in build._skipRoot
|
assert C.hPlotRoot in sBuild._skipRoot
|
||||||
|
|
||||||
# Reset Plot and Char
|
# Reset Plot and Char
|
||||||
filterTab.filterOpt._widgets[switchMap["plotRoot"]].setChecked(True)
|
filterTab.filterOpt._widgets[switchMap["plotRoot"]].setChecked(True)
|
||||||
@@ -170,7 +174,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
|
|
||||||
# Switch off novel docs
|
# Switch off novel docs
|
||||||
filterTab.filterOpt._widgets[switchMap["incNovel"]].setChecked(False)
|
filterTab.filterOpt._widgets[switchMap["incNovel"]].setChecked(False)
|
||||||
assert build.buildItemFilter(SHARED.project) == {
|
assert sBuild.buildItemFilter(SHARED.project) == {
|
||||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||||
@@ -186,7 +190,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
|
|
||||||
# Switch on note docs
|
# Switch on note docs
|
||||||
filterTab.filterOpt._widgets[switchMap["incNotes"]].setChecked(True)
|
filterTab.filterOpt._widgets[switchMap["incNotes"]].setChecked(True)
|
||||||
assert build.buildItemFilter(SHARED.project) == {
|
assert sBuild.buildItemFilter(SHARED.project) == {
|
||||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||||
@@ -202,7 +206,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
|
|
||||||
# Switch on inactive docs
|
# Switch on inactive docs
|
||||||
filterTab.filterOpt._widgets[switchMap["incInactive"]].setChecked(True)
|
filterTab.filterOpt._widgets[switchMap["incInactive"]].setChecked(True)
|
||||||
assert build.buildItemFilter(SHARED.project) == {
|
assert sBuild.buildItemFilter(SHARED.project) == {
|
||||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||||
@@ -220,7 +224,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
filterTab._treeMap[C.hChapterDoc].setSelected(True)
|
filterTab._treeMap[C.hChapterDoc].setSelected(True)
|
||||||
filterTab._treeMap[C.hSceneDoc].setSelected(True)
|
filterTab._treeMap[C.hSceneDoc].setSelected(True)
|
||||||
filterTab.includedButton.click()
|
filterTab.includedButton.click()
|
||||||
assert build.buildItemFilter(SHARED.project) == {
|
assert sBuild.buildItemFilter(SHARED.project) == {
|
||||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||||
@@ -239,7 +243,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
filterTab._treeMap[hPlotDoc].setSelected(True) # type: ignore
|
filterTab._treeMap[hPlotDoc].setSelected(True) # type: ignore
|
||||||
filterTab._treeMap[hCharDoc].setSelected(True) # type: ignore
|
filterTab._treeMap[hCharDoc].setSelected(True) # type: ignore
|
||||||
filterTab.excludedButton.click()
|
filterTab.excludedButton.click()
|
||||||
assert build.buildItemFilter(SHARED.project) == {
|
assert sBuild.buildItemFilter(SHARED.project) == {
|
||||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||||
C.hTitlePage: (False, FilterMode.FILTERED),
|
C.hTitlePage: (False, FilterMode.FILTERED),
|
||||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||||
@@ -255,7 +259,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
|
|
||||||
# Switch on novel docs
|
# Switch on novel docs
|
||||||
filterTab.filterOpt._widgets[switchMap["incNovel"]].setChecked(True)
|
filterTab.filterOpt._widgets[switchMap["incNovel"]].setChecked(True)
|
||||||
assert build.buildItemFilter(SHARED.project) == {
|
assert sBuild.buildItemFilter(SHARED.project) == {
|
||||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||||
C.hTitlePage: (True, FilterMode.FILTERED), # Now enabled
|
C.hTitlePage: (True, FilterMode.FILTERED), # Now enabled
|
||||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||||
@@ -273,7 +277,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
filterTab.optTree.clearSelection()
|
filterTab.optTree.clearSelection()
|
||||||
filterTab._treeMap[C.hNovelRoot].setSelected(True)
|
filterTab._treeMap[C.hNovelRoot].setSelected(True)
|
||||||
filterTab.resetButton.click()
|
filterTab.resetButton.click()
|
||||||
assert build.buildItemFilter(SHARED.project) == {
|
assert sBuild.buildItemFilter(SHARED.project) == {
|
||||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||||
C.hTitlePage: (True, FilterMode.FILTERED),
|
C.hTitlePage: (True, FilterMode.FILTERED),
|
||||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||||
@@ -294,7 +298,7 @@ def testToolBuildSettings_Filter(qtbot, nwGUI, projPath, mockRnd):
|
|||||||
filterTab._treeMap[hPlotDoc].setSelected(True) # type: ignore
|
filterTab._treeMap[hPlotDoc].setSelected(True) # type: ignore
|
||||||
filterTab._treeMap[hCharDoc].setSelected(True) # type: ignore
|
filterTab._treeMap[hCharDoc].setSelected(True) # type: ignore
|
||||||
filterTab.resetButton.click()
|
filterTab.resetButton.click()
|
||||||
assert build.buildItemFilter(SHARED.project) == {
|
assert sBuild.buildItemFilter(SHARED.project) == {
|
||||||
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
C.hNovelRoot: (False, FilterMode.SKIPPED),
|
||||||
C.hTitlePage: (True, FilterMode.FILTERED),
|
C.hTitlePage: (True, FilterMode.FILTERED),
|
||||||
C.hChapterDir: (False, FilterMode.SKIPPED),
|
C.hChapterDir: (False, FilterMode.SKIPPED),
|
||||||
@@ -429,6 +433,8 @@ def testToolBuildSettings_Headings(qtbot, nwGUI):
|
|||||||
|
|
||||||
# Edit a Heading
|
# Edit a Heading
|
||||||
# ==============
|
# ==============
|
||||||
|
sBuild = bSettings._build
|
||||||
|
assert sBuild.buildID == build.buildID
|
||||||
|
|
||||||
# Create new format of all bits
|
# Create new format of all bits
|
||||||
headTab.btnChapter.click()
|
headTab.btnChapter.click()
|
||||||
@@ -447,13 +453,13 @@ def testToolBuildSettings_Headings(qtbot, nwGUI):
|
|||||||
headTab.aInsScAbs.trigger()
|
headTab.aInsScAbs.trigger()
|
||||||
assert headTab.editTextBox.toPlainText() == allFmt
|
assert headTab.editTextBox.toPlainText() == allFmt
|
||||||
headTab.btnApply.click()
|
headTab.btnApply.click()
|
||||||
assert build.getStr("headings.fmtChapter") == allFmt
|
assert sBuild.getStr("headings.fmtChapter") == allFmt
|
||||||
|
|
||||||
# Check complex format
|
# Check complex format
|
||||||
headTab.btnChapter.click()
|
headTab.btnChapter.click()
|
||||||
headTab.editTextBox.setPlainText(f"Chapter {nwHeadFmt.CH_NUM}\n{nwHeadFmt.TITLE}\n")
|
headTab.editTextBox.setPlainText(f"Chapter {nwHeadFmt.CH_NUM}\n{nwHeadFmt.TITLE}\n")
|
||||||
headTab.btnApply.click()
|
headTab.btnApply.click()
|
||||||
assert build.getStr("headings.fmtChapter") == (
|
assert sBuild.getStr("headings.fmtChapter") == (
|
||||||
f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}"
|
f"Chapter {nwHeadFmt.CH_NUM}{nwHeadFmt.BR}{nwHeadFmt.TITLE}"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -461,39 +467,42 @@ def testToolBuildSettings_Headings(qtbot, nwGUI):
|
|||||||
headTab.btnPart.click()
|
headTab.btnPart.click()
|
||||||
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
||||||
headTab.btnApply.click()
|
headTab.btnApply.click()
|
||||||
assert build.getStr("headings.fmtPart") == nwHeadFmt.TITLE
|
assert sBuild.getStr("headings.fmtPart") == nwHeadFmt.TITLE
|
||||||
|
|
||||||
headTab.btnChapter.click()
|
headTab.btnChapter.click()
|
||||||
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
||||||
headTab.btnApply.click()
|
headTab.btnApply.click()
|
||||||
assert build.getStr("headings.fmtChapter") == nwHeadFmt.TITLE
|
assert sBuild.getStr("headings.fmtChapter") == nwHeadFmt.TITLE
|
||||||
|
|
||||||
headTab.btnUnnumbered.click()
|
headTab.btnUnnumbered.click()
|
||||||
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
||||||
headTab.btnApply.click()
|
headTab.btnApply.click()
|
||||||
assert build.getStr("headings.fmtUnnumbered") == nwHeadFmt.TITLE
|
assert sBuild.getStr("headings.fmtUnnumbered") == nwHeadFmt.TITLE
|
||||||
|
|
||||||
headTab.btnScene.click()
|
headTab.btnScene.click()
|
||||||
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
||||||
headTab.btnApply.click()
|
headTab.btnApply.click()
|
||||||
assert build.getStr("headings.fmtScene") == nwHeadFmt.TITLE
|
assert sBuild.getStr("headings.fmtScene") == nwHeadFmt.TITLE
|
||||||
|
|
||||||
headTab.btnAScene.click()
|
headTab.btnAScene.click()
|
||||||
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
||||||
headTab.btnApply.click()
|
headTab.btnApply.click()
|
||||||
assert build.getStr("headings.fmtAltScene") == nwHeadFmt.TITLE
|
assert sBuild.getStr("headings.fmtAltScene") == nwHeadFmt.TITLE
|
||||||
|
|
||||||
headTab.btnSection.click()
|
headTab.btnSection.click()
|
||||||
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
headTab.editTextBox.setPlainText(nwHeadFmt.TITLE)
|
||||||
headTab.btnApply.click()
|
headTab.btnApply.click()
|
||||||
assert build.getStr("headings.fmtSection") == nwHeadFmt.TITLE
|
assert sBuild.getStr("headings.fmtSection") == nwHeadFmt.TITLE
|
||||||
|
|
||||||
# Check hide switches
|
# Check hide switches
|
||||||
headTab.swtScene.setChecked(True)
|
headTab.swtScene.setChecked(True)
|
||||||
headTab.swtSection.setChecked(True)
|
headTab.swtSection.setChecked(True)
|
||||||
headTab.saveContent()
|
headTab.saveContent()
|
||||||
assert build.getBool("headings.hideScene") is True
|
sBuild = bSettings._build
|
||||||
assert build.getBool("headings.hideSection") is True
|
assert sBuild.buildID == build.buildID
|
||||||
|
|
||||||
|
assert sBuild.getBool("headings.hideScene") is True
|
||||||
|
assert sBuild.getBool("headings.hideSection") is True
|
||||||
|
|
||||||
# Finish
|
# Finish
|
||||||
button = bSettings.buttonBox.button(QtDialogClose)
|
button = bSettings.buttonBox.button(QtDialogClose)
|
||||||
@@ -510,6 +519,8 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
|
|||||||
build.setValue("text.includeBodyText", False)
|
build.setValue("text.includeBodyText", False)
|
||||||
build.setValue("text.includeSynopsis", False)
|
build.setValue("text.includeSynopsis", False)
|
||||||
build.setValue("text.includeComments", False)
|
build.setValue("text.includeComments", False)
|
||||||
|
build.setValue("text.includeStory", False)
|
||||||
|
build.setValue("text.includeNotes", False)
|
||||||
build.setValue("text.includeKeywords", False)
|
build.setValue("text.includeKeywords", False)
|
||||||
build.setValue("text.ignoredKeywords", "")
|
build.setValue("text.ignoredKeywords", "")
|
||||||
|
|
||||||
@@ -530,6 +541,8 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
|
|||||||
assert fmtTab.incBodyText.isChecked() is False
|
assert fmtTab.incBodyText.isChecked() is False
|
||||||
assert fmtTab.incSynopsis.isChecked() is False
|
assert fmtTab.incSynopsis.isChecked() is False
|
||||||
assert fmtTab.incComments.isChecked() is False
|
assert fmtTab.incComments.isChecked() is False
|
||||||
|
assert fmtTab.incStory.isChecked() is False
|
||||||
|
assert fmtTab.incNotes.isChecked() is False
|
||||||
assert fmtTab.incKeywords.isChecked() is False
|
assert fmtTab.incKeywords.isChecked() is False
|
||||||
assert fmtTab.ignoredKeywords.text() == ""
|
assert fmtTab.ignoredKeywords.text() == ""
|
||||||
|
|
||||||
@@ -539,6 +552,8 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
|
|||||||
fmtTab.incBodyText.setChecked(True)
|
fmtTab.incBodyText.setChecked(True)
|
||||||
fmtTab.incSynopsis.setChecked(True)
|
fmtTab.incSynopsis.setChecked(True)
|
||||||
fmtTab.incComments.setChecked(True)
|
fmtTab.incComments.setChecked(True)
|
||||||
|
fmtTab.incStory.setChecked(True)
|
||||||
|
fmtTab.incNotes.setChecked(True)
|
||||||
fmtTab.incKeywords.setChecked(True)
|
fmtTab.incKeywords.setChecked(True)
|
||||||
|
|
||||||
fmtTab.addNoteHead.setChecked(True)
|
fmtTab.addNoteHead.setChecked(True)
|
||||||
@@ -550,14 +565,18 @@ def testToolBuildSettings_FormatTextContent(qtbot, nwGUI):
|
|||||||
|
|
||||||
# Save values
|
# Save values
|
||||||
fmtTab.saveContent()
|
fmtTab.saveContent()
|
||||||
|
sBuild = bSettings._build
|
||||||
|
assert sBuild.buildID == build.buildID
|
||||||
|
|
||||||
assert build.getBool("text.includeBodyText") is True
|
assert sBuild.getBool("text.includeBodyText") is True
|
||||||
assert build.getBool("text.includeSynopsis") is True
|
assert sBuild.getBool("text.includeSynopsis") is True
|
||||||
assert build.getBool("text.includeComments") is True
|
assert sBuild.getBool("text.includeComments") is True
|
||||||
assert build.getBool("text.includeKeywords") is True
|
assert sBuild.getBool("text.includeStory") is True
|
||||||
assert build.getStr("text.ignoredKeywords") in ("@custom, @object", "@object, @custom")
|
assert sBuild.getBool("text.includeNotes") is True
|
||||||
|
assert sBuild.getBool("text.includeKeywords") is True
|
||||||
|
assert sBuild.getStr("text.ignoredKeywords") in ("@custom, @object", "@object, @custom")
|
||||||
|
|
||||||
assert build.getBool("text.addNoteHeadings") is True
|
assert sBuild.getBool("text.addNoteHeadings") is True
|
||||||
|
|
||||||
# Finish
|
# Finish
|
||||||
button = bSettings.buttonBox.button(QtDialogClose)
|
button = bSettings.buttonBox.button(QtDialogClose)
|
||||||
@@ -615,15 +634,17 @@ def testToolBuildSettings_FormatTextFormat(monkeypatch, qtbot, nwGUI):
|
|||||||
|
|
||||||
# Save values
|
# Save values
|
||||||
fmtTab.saveContent()
|
fmtTab.saveContent()
|
||||||
|
sBuild = bSettings._build
|
||||||
|
assert sBuild.buildID == build.buildID
|
||||||
|
|
||||||
assert build.getStr("format.textFont") == testFont.toString()
|
assert sBuild.getStr("format.textFont") == testFont.toString()
|
||||||
assert build.getFloat("format.lineHeight") == 1.15
|
assert sBuild.getFloat("format.lineHeight") == 1.15
|
||||||
|
|
||||||
assert build.getBool("format.justifyText") is True
|
assert sBuild.getBool("format.justifyText") is True
|
||||||
assert build.getBool("format.stripUnicode") is True
|
assert sBuild.getBool("format.stripUnicode") is True
|
||||||
assert build.getBool("format.replaceTabs") is True
|
assert sBuild.getBool("format.replaceTabs") is True
|
||||||
assert build.getBool("format.keepBreaks") is False
|
assert sBuild.getBool("format.keepBreaks") is False
|
||||||
assert build.getBool("format.showDialogue") is True
|
assert sBuild.getBool("format.showDialogue") is True
|
||||||
|
|
||||||
# Check that the font dialog doesn't fail
|
# Check that the font dialog doesn't fail
|
||||||
with monkeypatch.context() as mp:
|
with monkeypatch.context() as mp:
|
||||||
@@ -674,10 +695,12 @@ def testToolBuildSettings_FormatFirstLineIndent(monkeypatch, qtbot, nwGUI):
|
|||||||
|
|
||||||
# Save values
|
# Save values
|
||||||
fmtTab.saveContent()
|
fmtTab.saveContent()
|
||||||
|
sBuild = bSettings._build
|
||||||
|
assert sBuild.buildID == build.buildID
|
||||||
|
|
||||||
assert build.getBool("format.firstLineIndent") is True
|
assert sBuild.getBool("format.firstLineIndent") is True
|
||||||
assert build.getFloat("format.firstIndentWidth") == 2.0
|
assert sBuild.getFloat("format.firstIndentWidth") == 2.0
|
||||||
assert build.getBool("format.indentFirstPar") is True
|
assert sBuild.getBool("format.indentFirstPar") is True
|
||||||
|
|
||||||
# Finish
|
# Finish
|
||||||
button = bSettings.buttonBox.button(QtDialogClose)
|
button = bSettings.buttonBox.button(QtDialogClose)
|
||||||
@@ -792,15 +815,17 @@ def testToolBuildSettings_FormatOutput(qtbot, nwGUI):
|
|||||||
|
|
||||||
# Save values
|
# Save values
|
||||||
fmtTab.saveContent()
|
fmtTab.saveContent()
|
||||||
|
sBuild = bSettings._build
|
||||||
|
assert sBuild.buildID == build.buildID
|
||||||
|
|
||||||
assert build.getStr("doc.pageHeader") == "Stuff"
|
assert sBuild.getStr("doc.pageHeader") == "Stuff"
|
||||||
assert build.getInt("doc.pageCountOffset") == 1
|
assert sBuild.getInt("doc.pageCountOffset") == 1
|
||||||
assert build.getBool("doc.colorHeadings") is False
|
assert sBuild.getBool("doc.colorHeadings") is False
|
||||||
assert build.getBool("doc.scaleHeadings") is False
|
assert sBuild.getBool("doc.scaleHeadings") is False
|
||||||
assert build.getBool("doc.boldHeadings") is False
|
assert sBuild.getBool("doc.boldHeadings") is False
|
||||||
|
|
||||||
assert build.getBool("html.addStyles") is True
|
assert sBuild.getBool("html.addStyles") is True
|
||||||
assert build.getBool("html.preserveTabs") is True
|
assert sBuild.getBool("html.preserveTabs") is True
|
||||||
|
|
||||||
# Reset header format
|
# Reset header format
|
||||||
fmtTab.btnPageHeader.click()
|
fmtTab.btnPageHeader.click()
|
||||||
|
|||||||
Reference in New Issue
Block a user