Merge main and 2.7.4
This commit is contained in:
+27
-2
@@ -34,11 +34,14 @@ from PyQt6.QtWidgets import QMessageBox
|
||||
sys.path.insert(1, str(Path(__file__).parent.parent.absolute()))
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT
|
||||
from novelwriter.enum import nwTheme
|
||||
|
||||
from tests.mocked import MockGuiMain, MockTheme
|
||||
from tests.mocked import MockGuiMain
|
||||
from tests.tools import cleanProject
|
||||
|
||||
_TST_ROOT = Path(__file__).parent
|
||||
_SRC_ROOT = _TST_ROOT.parent
|
||||
_TMP_ROOT = _TST_ROOT / "temp"
|
||||
_TMP_CONF = _TMP_ROOT / "conf"
|
||||
|
||||
@@ -59,6 +62,14 @@ def resetConfigVars():
|
||||
CONFIG._dLocale = QLocale("en_GB")
|
||||
CONFIG._manuals = {"manual": _TMP_ROOT / "manual.pdf"}
|
||||
CONFIG.guiLocale = "en_GB"
|
||||
CONFIG.darkTheme = DEF_GUI_DARK
|
||||
CONFIG.lightTheme = DEF_GUI_LIGHT
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
|
||||
# Enable a few settings to ensure better coverage
|
||||
CONFIG.emphLabels = True
|
||||
CONFIG.lineHighlight = True
|
||||
|
||||
return
|
||||
|
||||
|
||||
@@ -75,6 +86,8 @@ def sessionFixture():
|
||||
_TMP_ROOT.mkdir()
|
||||
_TMP_CONF.mkdir()
|
||||
(_TMP_ROOT / "manual.pdf").touch()
|
||||
(_SRC_ROOT / "novelwriter" / "assets"/ "manual.pdf").touch()
|
||||
(_SRC_ROOT / "novelwriter" / "assets"/ "manual_fr.pdf").touch()
|
||||
return
|
||||
|
||||
|
||||
@@ -148,15 +161,27 @@ def projPath(fncPath):
|
||||
@pytest.fixture(scope="function")
|
||||
def mockGUI(qtbot, monkeypatch):
|
||||
"""Create a mock instance of novelWriter's main GUI class."""
|
||||
from novelwriter.gui.theme import GuiTheme
|
||||
|
||||
monkeypatch.setattr(QMessageBox, "exec", lambda *a: None)
|
||||
monkeypatch.setattr(QMessageBox, "result", lambda *a: QMessageBox.StandardButton.Yes)
|
||||
gui = MockGuiMain()
|
||||
theme = MockTheme()
|
||||
theme = GuiTheme()
|
||||
monkeypatch.setattr(SHARED, "_gui", gui)
|
||||
monkeypatch.setattr(SHARED, "_theme", theme)
|
||||
|
||||
return gui
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def mockGUIwithTheme(mockGUI):
|
||||
"""Create a mock instance of novelWriter's main GUI class with the
|
||||
theme instance initialised.
|
||||
"""
|
||||
SHARED.theme.initThemes()
|
||||
return mockGUI
|
||||
|
||||
|
||||
@pytest.fixture(scope="function")
|
||||
def nwGUI(qtbot, monkeypatch, functionFixture):
|
||||
"""Create an instance of the novelWriter GUI."""
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
[
|
||||
"alert_error",
|
||||
"alert_info",
|
||||
"alert_question",
|
||||
"alert_warn",
|
||||
|
||||
"cls_archive",
|
||||
"cls_character",
|
||||
"cls_custom",
|
||||
"cls_entity",
|
||||
"cls_none",
|
||||
"cls_novel",
|
||||
"cls_object",
|
||||
"cls_plot",
|
||||
"cls_template",
|
||||
"cls_timeline",
|
||||
"cls_trash",
|
||||
"cls_world",
|
||||
|
||||
"prj_folder",
|
||||
"prj_document",
|
||||
"prj_title",
|
||||
"prj_chapter",
|
||||
"prj_scene",
|
||||
"prj_note",
|
||||
|
||||
"fmt_bold",
|
||||
"fmt_italic",
|
||||
"fmt_mark",
|
||||
"fmt_strike",
|
||||
"fmt_subscript",
|
||||
"fmt_superscript",
|
||||
"fmt_underline",
|
||||
"fmt_toolbar",
|
||||
|
||||
"search",
|
||||
"search_cancel",
|
||||
"search_case",
|
||||
"search_loop",
|
||||
"search_preserve",
|
||||
"search_project",
|
||||
"search_regex",
|
||||
"search_replace",
|
||||
"search_word",
|
||||
|
||||
"bullet-off",
|
||||
"bullet-on",
|
||||
"unfold-hide",
|
||||
"unfold-show",
|
||||
|
||||
"sb_build",
|
||||
"sb_details",
|
||||
"sb_novel",
|
||||
"sb_outline",
|
||||
"sb_project",
|
||||
"sb_search",
|
||||
"sb_stats",
|
||||
|
||||
"theme_light",
|
||||
"theme_dark",
|
||||
"theme_auto",
|
||||
|
||||
"add",
|
||||
"bookmarks",
|
||||
"browse",
|
||||
"build_settings",
|
||||
"cancel",
|
||||
"checked",
|
||||
"chevron_down",
|
||||
"chevron_left",
|
||||
"chevron_right",
|
||||
"chevron_up",
|
||||
"close",
|
||||
"copy",
|
||||
"document_add",
|
||||
"document",
|
||||
"edit",
|
||||
"exclude",
|
||||
"export",
|
||||
"filter",
|
||||
"fit_height",
|
||||
"fit_width",
|
||||
"folder",
|
||||
"font",
|
||||
"import",
|
||||
"language",
|
||||
"lines",
|
||||
"list",
|
||||
"margin_bottom",
|
||||
"margin_left",
|
||||
"margin_right",
|
||||
"margin_top",
|
||||
"maximise",
|
||||
"minimise",
|
||||
"more_arrow",
|
||||
"more_vertical",
|
||||
"noncheckable",
|
||||
"open",
|
||||
"panel",
|
||||
"pin",
|
||||
"project_copy",
|
||||
"quote",
|
||||
"refresh",
|
||||
"remove",
|
||||
"revert",
|
||||
"settings",
|
||||
"star",
|
||||
"stats",
|
||||
"text",
|
||||
"timer_off",
|
||||
"timer",
|
||||
"unchecked",
|
||||
"view"
|
||||
]
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:07:59">
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="6" timeStamp="2025-04-29 22:07:59">
|
||||
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2179" autoCount="285" editTime="1000">
|
||||
<name>Sample Project</name>
|
||||
<author>Jane Smith</author>
|
||||
@@ -20,20 +20,20 @@
|
||||
<entry key="C">D</entry>
|
||||
</autoReplace>
|
||||
<status>
|
||||
<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="sc24b8f" count="3" red="182" green="60" blue="0" shape="BARS_1">Started</entry>
|
||||
<entry key="s90e6c9" count="5" red="193" green="129" blue="0" shape="BARS_2">1st Draft</entry>
|
||||
<entry key="sd51c5b" count="1" red="193" green="129" blue="0" shape="BARS_3">2nd 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="STAR">Finished</entry>
|
||||
<entry key="sf12341" count="8" color="#646464" shape="SQUARE">New</entry>
|
||||
<entry key="sf24ce6" count="2" color="#c83200" shape="SQUARE">Notes</entry>
|
||||
<entry key="sc24b8f" count="3" color="#b63c00" shape="BARS_1">Started</entry>
|
||||
<entry key="s90e6c9" count="5" color="#c18100" shape="BARS_2">1st Draft</entry>
|
||||
<entry key="sd51c5b" count="1" color="#c18100" shape="BARS_3">2nd Draft</entry>
|
||||
<entry key="s8ae72a" count="1" color="#c18100" shape="BARS_4">3rd Draft</entry>
|
||||
<entry key="s78ea90" count="1" color="#3ab43a" shape="STAR">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="ia857f0" count="5" red="100" green="100" blue="100" shape="SQUARE">None</entry>
|
||||
<entry key="i4a1d39" count="1" red="220" green="138" blue="221" shape="BLOCK_1">Background</entry>
|
||||
<entry key="icfb3a5" count="1" red="220" green="138" blue="221" shape="BLOCK_2">Minor</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>
|
||||
<entry key="ia857f0" count="5" color="#646464" shape="SQUARE">None</entry>
|
||||
<entry key="i4a1d39" count="1" color="#dc8add" shape="BLOCK_1">Background</entry>
|
||||
<entry key="icfb3a5" count="1" color="#dc8add" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i2d7a54" count="2" color="#dc8add" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i56be10" count="1" color="#dc8add" shape="BLOCK_4">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="31" novelWords="1016" notesWords="416" novelChars="5602" notesChars="2285">
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
%%~name: Ancient Europe
|
||||
%%~path: 60bdf227455cc/04468803b92e1
|
||||
%%~kind: WORLD/NOTE
|
||||
%%~hash: 7b746485a1d06c64a5e0bf671e3141806ed51a54
|
||||
%%~date: Unknown/2024-01-30 21:36:44
|
||||
%%~hash: 9a0a98cda777f557ea10670ea1618cde253084b7
|
||||
%%~date: Unknown/2025-06-19 21:02:43
|
||||
# Ancient Europe
|
||||
|
||||
@tag: Europe | Ancient Europe
|
||||
|
||||
%Short: A strange place
|
||||
|
||||
Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.
|
||||
|
||||
Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.
|
||||
|
||||
@@ -1,12 +1,14 @@
|
||||
%%~name: Main
|
||||
%%~path: 6c6afb1247750/2426c6f0ca922
|
||||
%%~kind: PLOT/NOTE
|
||||
%%~hash: db3897d166e246acdb5e25e9bd98c5a40a699ed0
|
||||
%%~date: Unknown/Unknown
|
||||
%%~hash: ffc82e98d37621d83a808a75f06bd36923356706
|
||||
%%~date: Unknown/2025-06-19 21:02:33
|
||||
# Main Plot
|
||||
|
||||
@tag: Main
|
||||
|
||||
%Short: Where it all happens
|
||||
|
||||
Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.
|
||||
|
||||
Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.
|
||||
@@ -1,14 +1,14 @@
|
||||
%%~name: Chapter Two
|
||||
%%~path: 6bd935d2490cd/441420a886d82
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: fd6d46708faa1333f8f7ba0442fc5b35bf1e3f85
|
||||
%%~date: Unknown/Unknown
|
||||
%%~hash: 81d30d3448202c782a6e252c5f7ef030bca3bf7f
|
||||
%%~date: Unknown/2025-06-19 21:01:48
|
||||
## Chapter Two
|
||||
|
||||
@pov: Bod
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.
|
||||
%Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.
|
||||
|
||||
Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
%%~name: Scene Five
|
||||
%%~path: 6bd935d2490cd/47666c91c7ccf
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: d210c26966da6f9edea8726567abf4860b7bb9b7
|
||||
%%~date: Unknown/Unknown
|
||||
%%~hash: 680e26046056165f1560ed1468cf316a3c51f90e
|
||||
%%~date: Unknown/2025-06-19 21:01:59
|
||||
### Scene Five
|
||||
|
||||
@pov: Bod
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
%Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
|
||||
Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet.
|
||||
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
%%~name: Mr. Nobody
|
||||
%%~path: 67a8707f2f249/4c4f28287af27
|
||||
%%~kind: CHARACTER/NOTE
|
||||
%%~hash: 2a9b9751e8207b719bfa6233ebe04e24928ff8a8
|
||||
%%~date: Unknown/2024-01-30 21:36:26
|
||||
%%~hash: 84639e78d265b3a1f2fb8e3211a08c223d95500a
|
||||
%%~date: Unknown/2025-06-19 21:02:21
|
||||
# Nobody Owens
|
||||
|
||||
@tag: Bod | Nobody Owens
|
||||
@plot: Main
|
||||
|
||||
%Short: The Hero
|
||||
|
||||
Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.
|
||||
|
||||
Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
%%~name: Scene One
|
||||
%%~path: 45e6b01ca35c1/88243afbe5ed8
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: a09245a7a772bbe02850b5db109977e336cd9cc1
|
||||
%%~date: Unknown/Unknown
|
||||
%%~hash: d660202d320b3c99dbcc3bd9dd39d464d4f422ae
|
||||
%%~date: Unknown/2025-06-19 21:01:38
|
||||
### Scene One
|
||||
|
||||
@pov: Bod
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.
|
||||
%Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.
|
||||
|
||||
Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada.
|
||||
|
||||
|
||||
@@ -1,11 +1,12 @@
|
||||
%%~name: Prologue
|
||||
%%~path: b3643d0f92e32/88d59a277361b
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: 377a72ba340ff458a00d98af745e9fa0c79fb26c
|
||||
%%~date: Unknown/2024-10-25 19:16:49
|
||||
%%~hash: d43324a30d3f1e5608711ca71515dee429b30925
|
||||
%%~date: Unknown/2025-06-19 21:01:07
|
||||
##! Prologue
|
||||
|
||||
% Synopsis: Explanation from the lipsum.com website.
|
||||
%Synopsis: Explanation from the lipsum.com website.
|
||||
%Note.Purpose: Introduction
|
||||
|
||||
_Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
%%~name: Scene Three
|
||||
%%~path: 6bd935d2490cd/eb103bc70c90c
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: c4eda49e4fe81dc450d547eee0bdabe77fdaaa98
|
||||
%%~date: Unknown/Unknown
|
||||
%%~hash: 59eeb405f5b26cbe1056d1f7684f7ed40ed6685a
|
||||
%%~date: Unknown/2025-06-19 21:01:52
|
||||
### Scene Three
|
||||
|
||||
@pov: Bod
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
|
||||
%Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
|
||||
|
||||
Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis.
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
%%~name: Scene Four
|
||||
%%~path: 6bd935d2490cd/f8c0562e50f1b
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: 9461a279b9fb6ef005ee4d432fcda77ff5bfbd42
|
||||
%%~date: Unknown/Unknown
|
||||
%%~hash: f4848b56d1f41a04de1579d772a4d1ddd3e28e55
|
||||
%%~date: Unknown/2025-06-19 21:01:55
|
||||
### Scene Four
|
||||
|
||||
@pov: Bod
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.
|
||||
%Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.
|
||||
|
||||
Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien.
|
||||
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
%%~name: Scene Two
|
||||
%%~path: 45e6b01ca35c1/f96ec11c6a3da
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: ebe3fbaa16d9d81bc1a139822e3bf39bb357866d
|
||||
%%~date: Unknown/Unknown
|
||||
%%~hash: e95ff5b633e74c9cab8dc32b6a77b3e40d265f2b
|
||||
%%~date: Unknown/2025-06-19 21:01:41
|
||||
### Scene Two
|
||||
|
||||
@pov: Bod
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.
|
||||
%Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.
|
||||
|
||||
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien.
|
||||
|
||||
|
||||
@@ -1,14 +1,14 @@
|
||||
%%~name: Chapter One
|
||||
%%~path: 45e6b01ca35c1/fb609cd8319dc
|
||||
%%~kind: NOVEL/DOCUMENT
|
||||
%%~hash: 5dabeaa7a58238a6ad99ce73176b1bdfad1a71b7
|
||||
%%~date: Unknown/2024-01-30 21:35:57
|
||||
%%~hash: 73a1008ed17a84a48b14b41109a842a6ed300eec
|
||||
%%~date: Unknown/2025-06-19 21:01:19
|
||||
## Chapter One
|
||||
|
||||
@pov: Bod
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.
|
||||
%Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar.
|
||||
|
||||
+24
-24
@@ -1,6 +1,6 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<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="52" autoCount="29" editTime="2465">
|
||||
<novelWriterXML appVersion="2.8a1" hexVersion="0x020800a1" fileVersion="1.5" fileRevision="6" timeStamp="2025-06-19 21:05:02">
|
||||
<project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="55" autoCount="33" editTime="2746">
|
||||
<name>Lorem Ipsum</name>
|
||||
<author>lipsum.com</author>
|
||||
</project>
|
||||
@@ -9,7 +9,7 @@
|
||||
<language>en_GB</language>
|
||||
<spellChecking auto="no">None</spellChecking>
|
||||
<lastHandle>
|
||||
<entry key="editor">7a992350f3eb6</entry>
|
||||
<entry key="editor">04468803b92e1</entry>
|
||||
<entry key="viewer">None</entry>
|
||||
<entry key="novel">b3643d0f92e32</entry>
|
||||
<entry key="outline">None</entry>
|
||||
@@ -19,16 +19,16 @@
|
||||
<entry key="Rep2">Replace Text 2</entry>
|
||||
</autoReplace>
|
||||
<status>
|
||||
<entry key="sbaa94f" count="3" red="100" green="100" blue="100" shape="SQUARE">New</entry>
|
||||
<entry key="s27bf7c" count="0" red="200" green="50" blue="0" shape="SQUARE">Note</entry>
|
||||
<entry key="s92a87b" count="5" red="200" green="150" blue="0" shape="SQUARE">Draft</entry>
|
||||
<entry key="sedd043" count="7" red="50" green="200" blue="0" shape="SQUARE">Finished</entry>
|
||||
<entry key="sbaa94f" count="3" color="base" shape="CIRCLE">New</entry>
|
||||
<entry key="s27bf7c" count="0" color="red" shape="CIRCLE">Note</entry>
|
||||
<entry key="s92a87b" count="5" color="yellow" shape="CIRCLE">Draft</entry>
|
||||
<entry key="sedd043" count="7" color="green" shape="CIRCLE">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i613591" count="7" red="100" green="100" blue="100" shape="SQUARE">New</entry>
|
||||
<entry key="i560cbf" count="0" red="200" green="50" blue="0" shape="SQUARE">Minor</entry>
|
||||
<entry key="i37861c" count="0" red="200" green="150" blue="0" shape="SQUARE">Major</entry>
|
||||
<entry key="id6b1d0" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
|
||||
<entry key="i613591" count="4" color="base" shape="SQUARE">New</entry>
|
||||
<entry key="i560cbf" count="0" color="red" shape="SQUARE">Minor</entry>
|
||||
<entry key="i37861c" count="1" color="yellow" shape="SQUARE">Major</entry>
|
||||
<entry key="id6b1d0" count="2" color="green" shape="SQUARE">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="22" novelWords="3115" notesWords="738" novelChars="20774" notesChars="5003">
|
||||
@@ -45,7 +45,7 @@
|
||||
<name status="sedd043" import="i613591" active="yes">Front Matter</name>
|
||||
</item>
|
||||
<item handle="88d59a277361b" parent="b3643d0f92e32" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="605" wordCount="94" paraCount="2" cursorPos="47" />
|
||||
<meta expanded="no" heading="H2" charCount="605" wordCount="94" paraCount="2" cursorPos="94" />
|
||||
<name status="s92a87b" import="i613591" active="yes">Prologue</name>
|
||||
</item>
|
||||
<item handle="db7e733775d4d" parent="b3643d0f92e32" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
@@ -57,7 +57,7 @@
|
||||
<name status="s92a87b" import="i613591">Chapter One</name>
|
||||
</item>
|
||||
<item handle="fb609cd8319dc" parent="45e6b01ca35c1" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="419" wordCount="67" paraCount="1" cursorPos="59" />
|
||||
<meta expanded="no" heading="H2" charCount="419" wordCount="67" paraCount="1" cursorPos="155" />
|
||||
<name status="s92a87b" import="i613591" active="yes">Chapter One</name>
|
||||
</item>
|
||||
<item handle="88243afbe5ed8" parent="45e6b01ca35c1" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
@@ -65,7 +65,7 @@
|
||||
<name status="sedd043" import="i613591" active="yes">Scene One</name>
|
||||
</item>
|
||||
<item handle="f96ec11c6a3da" parent="45e6b01ca35c1" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="4043" wordCount="600" paraCount="6" cursorPos="2335" />
|
||||
<meta expanded="no" heading="H3" charCount="4043" wordCount="600" paraCount="6" cursorPos="57" />
|
||||
<name status="sedd043" import="i613591" active="yes">Scene Two</name>
|
||||
</item>
|
||||
<item handle="846352075de7d" parent="b3643d0f92e32" root="b3643d0f92e32" order="5" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
@@ -77,19 +77,19 @@
|
||||
<name status="s92a87b" import="i613591">Chapter Two</name>
|
||||
</item>
|
||||
<item handle="441420a886d82" parent="6bd935d2490cd" root="b3643d0f92e32" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H2" charCount="477" wordCount="70" paraCount="1" cursorPos="56" />
|
||||
<meta expanded="no" heading="H2" charCount="477" wordCount="70" paraCount="1" cursorPos="58" />
|
||||
<name status="s92a87b" import="i613591" active="yes">Chapter Two</name>
|
||||
</item>
|
||||
<item handle="eb103bc70c90c" parent="6bd935d2490cd" root="b3643d0f92e32" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="3006" wordCount="439" paraCount="4" cursorPos="57" />
|
||||
<meta expanded="no" heading="H3" charCount="3006" wordCount="439" paraCount="4" cursorPos="59" />
|
||||
<name status="sedd043" import="i613591" active="yes">Scene Three</name>
|
||||
</item>
|
||||
<item handle="f8c0562e50f1b" parent="6bd935d2490cd" root="b3643d0f92e32" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="3839" wordCount="563" paraCount="6" cursorPos="56" />
|
||||
<meta expanded="no" heading="H3" charCount="3839" wordCount="563" paraCount="6" cursorPos="58" />
|
||||
<name status="sedd043" import="i613591" active="yes">Scene Four</name>
|
||||
</item>
|
||||
<item handle="47666c91c7ccf" parent="6bd935d2490cd" root="b3643d0f92e32" order="3" type="FILE" class="NOVEL" layout="DOCUMENT">
|
||||
<meta expanded="no" heading="H3" charCount="3644" wordCount="543" paraCount="5" cursorPos="351" />
|
||||
<meta expanded="no" heading="H3" charCount="3644" wordCount="543" paraCount="5" cursorPos="58" />
|
||||
<name status="sedd043" import="i613591" active="yes">Scene Five</name>
|
||||
</item>
|
||||
<item handle="67a8707f2f249" parent="None" root="67a8707f2f249" order="1" type="ROOT" class="CHARACTER">
|
||||
@@ -97,24 +97,24 @@
|
||||
<name status="sbaa94f" import="i613591">Characters</name>
|
||||
</item>
|
||||
<item handle="4c4f28287af27" parent="67a8707f2f249" root="67a8707f2f249" order="0" type="FILE" class="CHARACTER" layout="NOTE">
|
||||
<meta expanded="no" heading="H1" charCount="1864" wordCount="284" paraCount="3" cursorPos="40" />
|
||||
<name status="sbaa94f" import="i613591" active="yes">Mr. Nobody</name>
|
||||
<meta expanded="no" heading="H1" charCount="1864" wordCount="284" paraCount="3" cursorPos="70" />
|
||||
<name status="sbaa94f" import="id6b1d0" active="yes">Mr. Nobody</name>
|
||||
</item>
|
||||
<item handle="6c6afb1247750" parent="None" root="6c6afb1247750" order="2" type="ROOT" class="PLOT">
|
||||
<meta expanded="yes" />
|
||||
<name status="sbaa94f" import="i613591">Plot</name>
|
||||
</item>
|
||||
<item handle="2426c6f0ca922" parent="6c6afb1247750" root="6c6afb1247750" order="0" type="FILE" class="PLOT" layout="NOTE">
|
||||
<meta expanded="no" heading="H1" charCount="1369" wordCount="195" paraCount="2" cursorPos="1387" />
|
||||
<name status="sbaa94f" import="i613591" active="yes">Main</name>
|
||||
<meta expanded="no" heading="H1" charCount="1369" wordCount="195" paraCount="2" cursorPos="53" />
|
||||
<name status="sbaa94f" import="id6b1d0" active="yes">Main</name>
|
||||
</item>
|
||||
<item handle="60bdf227455cc" parent="None" root="60bdf227455cc" order="3" type="ROOT" class="WORLD">
|
||||
<meta expanded="yes" />
|
||||
<name status="sbaa94f" import="i613591">World</name>
|
||||
</item>
|
||||
<item handle="04468803b92e1" parent="60bdf227455cc" root="60bdf227455cc" order="0" type="FILE" class="WORLD" layout="NOTE">
|
||||
<meta expanded="no" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="47" />
|
||||
<name status="sbaa94f" import="i613591" active="yes">Ancient Europe</name>
|
||||
<meta expanded="no" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="1119" />
|
||||
<name status="sbaa94f" import="i37861c" active="yes">Ancient Europe</name>
|
||||
</item>
|
||||
<item handle="1ace7ab1a0fc6" parent="None" root="1ace7ab1a0fc6" order="4" type="ROOT" class="TRASH">
|
||||
<meta expanded="no" />
|
||||
|
||||
@@ -65,6 +65,9 @@ class MockTheme:
|
||||
self.guiFontBU = QFont()
|
||||
return
|
||||
|
||||
def initThemes(self) -> None:
|
||||
return
|
||||
|
||||
def getPixmap(self, *a) -> QPixmap:
|
||||
return QPixmap()
|
||||
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
[Meta]
|
||||
timestamp = 2025-05-11 13:28:11
|
||||
timestamp = 2025-06-14 17:03:01
|
||||
|
||||
[Main]
|
||||
font =
|
||||
theme = default
|
||||
syntax = default_light
|
||||
lighttheme = default_light
|
||||
darktheme = default_dark
|
||||
thememode = AUTO
|
||||
icons = material_rounded_normal
|
||||
iconcoltree = theme
|
||||
iconcoldocs = False
|
||||
@@ -26,9 +27,9 @@ outlinepane = 500, 150
|
||||
[Project]
|
||||
autosaveproject = 60
|
||||
autosavedoc = 30
|
||||
emphlabels = True
|
||||
emphlabels = False
|
||||
backuppath =
|
||||
backuponclose = False
|
||||
backuponclose = True
|
||||
askbeforebackup = True
|
||||
askbeforeexit = True
|
||||
lastauthor =
|
||||
@@ -39,6 +40,7 @@ width = 700
|
||||
margin = 40
|
||||
tabwidth = 40
|
||||
cursorwidth = 1
|
||||
linehighlight = False
|
||||
focuswidth = 800
|
||||
hidefocusfooter = False
|
||||
justify = False
|
||||
@@ -48,7 +50,7 @@ repsquotes = True
|
||||
repdquotes = True
|
||||
repdash = True
|
||||
repdots = True
|
||||
autoscroll = False
|
||||
autoscroll = True
|
||||
autoscrollpos = 30
|
||||
scrollpastend = True
|
||||
fmtsquoteopen = ‘
|
||||
@@ -81,6 +83,7 @@ showedittoolbar = False
|
||||
showsessiontime = True
|
||||
viewcomments = True
|
||||
viewsynopsis = True
|
||||
viewnotes = True
|
||||
searchcase = False
|
||||
searchword = False
|
||||
searchregex = False
|
||||
@@ -90,3 +93,4 @@ searchmatchcap = False
|
||||
searchprojcase = False
|
||||
searchprojword = False
|
||||
searchprojregex = False
|
||||
|
||||
|
||||
@@ -18,7 +18,8 @@
|
||||
"88d59a277361b": {
|
||||
"T0001": {
|
||||
"meta": {"level": "H2", "title": "Prologue", "line": 1, "tag": "", "counts": [605, 94, 2]},
|
||||
"summary": "Explanation from the lipsum.com website."
|
||||
"summary": "Explanation from the lipsum.com website.",
|
||||
"note.Purpose": "Introduction"
|
||||
},
|
||||
"document": {
|
||||
"footnotes": ["f9kgf"]
|
||||
@@ -92,17 +93,20 @@
|
||||
"4c4f28287af27": {
|
||||
"T0001": {
|
||||
"meta": {"level": "H1", "title": "Nobody Owens", "line": 1, "tag": "bod", "counts": [1864, 284, 3]},
|
||||
"refs": {"main": "@plot"}
|
||||
"refs": {"main": "@plot"},
|
||||
"summary": "The Hero"
|
||||
}
|
||||
},
|
||||
"2426c6f0ca922": {
|
||||
"T0001": {
|
||||
"meta": {"level": "H1", "title": "Main Plot", "line": 1, "tag": "main", "counts": [1369, 195, 2]}
|
||||
"meta": {"level": "H1", "title": "Main Plot", "line": 1, "tag": "main", "counts": [1369, 195, 2]},
|
||||
"summary": "Where it all happens"
|
||||
}
|
||||
},
|
||||
"04468803b92e1": {
|
||||
"T0001": {
|
||||
"meta": {"level": "H1", "title": "Ancient Europe", "line": 1, "tag": "europe", "counts": [1770, 259, 3]}
|
||||
"meta": {"level": "H1", "title": "Ancient Europe", "line": 1, "tag": "europe", "counts": [1770, 259, 3]},
|
||||
"summary": "A strange place"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:54">
|
||||
<novelWriterXML appVersion="2.8a0" hexVersion="0x020800a0" fileVersion="1.5" fileRevision="6" timeStamp="2025-06-12 18:02:57">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
||||
<name>New Project</name>
|
||||
<author>Jane Doe</author>
|
||||
@@ -16,16 +16,16 @@
|
||||
</lastHandle>
|
||||
<autoReplace />
|
||||
<status>
|
||||
<entry key="s000000" count="7" red="120" green="120" blue="120" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" red="205" green="171" blue="143" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" red="143" green="240" blue="164" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" red="249" green="240" blue="107" shape="STAR">Finished</entry>
|
||||
<entry key="s000000" count="7" color="faded" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" color="red" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" color="yellow" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" color="green" shape="STAR">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000004" count="5" red="120" green="120" blue="120" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||
<entry key="i000004" count="5" color="purple" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" color="purple" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" color="purple" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" color="purple" shape="BLOCK_4">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="12" novelWords="10" notesWords="6" novelChars="45" notesChars="22">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:54">
|
||||
<novelWriterXML appVersion="2.8a0" hexVersion="0x020800a0" fileVersion="1.5" fileRevision="6" timeStamp="2025-06-12 18:02:57">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
||||
<name>New Project</name>
|
||||
<author>Jane Doe</author>
|
||||
@@ -16,16 +16,16 @@
|
||||
</lastHandle>
|
||||
<autoReplace />
|
||||
<status>
|
||||
<entry key="s000000" count="6" red="120" green="120" blue="120" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" red="205" green="171" blue="143" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" red="143" green="240" blue="164" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" red="249" green="240" blue="107" shape="STAR">Finished</entry>
|
||||
<entry key="s000000" count="6" color="faded" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" color="red" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" color="yellow" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" color="green" shape="STAR">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000004" count="10" red="120" green="120" blue="120" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||
<entry key="i000004" count="10" color="purple" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" color="purple" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" color="purple" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" color="purple" shape="BLOCK_4">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="16" novelWords="9" notesWords="0" novelChars="40" notesChars="0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:52">
|
||||
<novelWriterXML appVersion="2.8a0" hexVersion="0x020800a0" fileVersion="1.5" fileRevision="6" timeStamp="2025-06-12 18:02:55">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
|
||||
<name>New Project</name>
|
||||
<author>Jane Doe</author>
|
||||
@@ -16,16 +16,16 @@
|
||||
</lastHandle>
|
||||
<autoReplace />
|
||||
<status>
|
||||
<entry key="s000000" count="16" red="120" green="120" blue="120" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" red="205" green="171" blue="143" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" red="143" green="240" blue="164" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" red="249" green="240" blue="107" shape="STAR">Finished</entry>
|
||||
<entry key="s000000" count="16" color="faded" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" color="red" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" color="yellow" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" color="green" shape="STAR">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000004" count="3" red="120" green="120" blue="120" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||
<entry key="i000004" count="3" color="purple" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" color="purple" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" color="purple" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" color="purple" shape="BLOCK_4">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="19" novelWords="28" notesWords="0" novelChars="129" notesChars="0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:52">
|
||||
<novelWriterXML appVersion="2.8a0" hexVersion="0x020800a0" fileVersion="1.5" fileRevision="6" timeStamp="2025-06-12 18:02:55">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
|
||||
<name>Test Project A</name>
|
||||
<author>Jane Doe</author>
|
||||
@@ -16,16 +16,16 @@
|
||||
</lastHandle>
|
||||
<autoReplace />
|
||||
<status>
|
||||
<entry key="s000000" count="15" red="120" green="120" blue="120" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" red="205" green="171" blue="143" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" red="143" green="240" blue="164" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" red="249" green="240" blue="107" shape="STAR">Finished</entry>
|
||||
<entry key="s000000" count="15" color="faded" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" color="red" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" color="yellow" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" color="green" shape="STAR">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000004" count="7" red="120" green="120" blue="120" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||
<entry key="i000004" count="7" color="purple" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" color="purple" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" color="purple" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" color="purple" shape="BLOCK_4">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="22" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:52">
|
||||
<novelWriterXML appVersion="2.8a0" hexVersion="0x020800a0" fileVersion="1.5" fileRevision="6" timeStamp="2025-06-12 18:02:55">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
|
||||
<name>Test Project B</name>
|
||||
<author>Jane Doe</author>
|
||||
@@ -16,16 +16,16 @@
|
||||
</lastHandle>
|
||||
<autoReplace />
|
||||
<status>
|
||||
<entry key="s000000" count="9" red="120" green="120" blue="120" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" red="205" green="171" blue="143" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" red="143" green="240" blue="164" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" red="249" green="240" blue="107" shape="STAR">Finished</entry>
|
||||
<entry key="s000000" count="9" color="faded" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" color="red" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" color="yellow" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" color="green" shape="STAR">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000004" count="7" red="120" green="120" blue="120" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||
<entry key="i000004" count="7" color="purple" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" color="purple" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" color="purple" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" color="purple" shape="BLOCK_4">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="16" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<ns0:Properties xmlns:ns0="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
|
||||
<ns0:TotalTime>41</ns0:TotalTime>
|
||||
<ns0:Application>novelWriter/2.7b1</ns0:Application>
|
||||
<ns0:Words>4035</ns0:Words>
|
||||
<ns0:TotalTime>45</ns0:TotalTime>
|
||||
<ns0:Application>novelWriter/2.8a1</ns0:Application>
|
||||
<ns0:Words>4050</ns0:Words>
|
||||
<ns0:Characters>21296</ns0:Characters>
|
||||
<ns0:CharactersWithSpaces>24964</ns0:CharactersWithSpaces>
|
||||
<ns0:Paragraphs>45</ns0:Paragraphs>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<?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">
|
||||
<dcterms:created xsi:type="dcterms:W3CDTF">2025-04-29T22:46:36</dcterms:created>
|
||||
<dcterms:modified xsi:type="dcterms:W3CDTF">2025-04-29T22:46:36</dcterms:modified>
|
||||
<dcterms:created xsi:type="dcterms:W3CDTF">2025-06-19T21:08:59</dcterms:created>
|
||||
<dcterms:modified xsi:type="dcterms:W3CDTF">2025-06-19T21:08:59</dcterms:modified>
|
||||
<dc:creator>lipsum.com</dc:creator>
|
||||
<dc:title>Lorem Ipsum</dc:title>
|
||||
<dc:language>en_GB</dc:language>
|
||||
<cp:revision>52</cp:revision>
|
||||
<cp:revision>55</cp:revision>
|
||||
<cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy>
|
||||
</cp:coreProperties>
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t>4,035</w:t>
|
||||
<w:t>4,050</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
@@ -48,7 +48,7 @@
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t>27,064</w:t>
|
||||
<w:t>27,164</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
@@ -1250,6 +1250,28 @@
|
||||
<w:t>Main</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="MetaText" />
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:rPr>
|
||||
<w:b />
|
||||
<w:color w:val="813709" />
|
||||
</w:rPr>
|
||||
<w:t>Short Description:</w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t xml:space="preserve"> </w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr>
|
||||
<w:color w:val="813709" />
|
||||
</w:rPr>
|
||||
<w:t>The Hero</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Normal" />
|
||||
@@ -1323,6 +1345,28 @@
|
||||
<w:t>Main</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="MetaText" />
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:rPr>
|
||||
<w:b />
|
||||
<w:color w:val="813709" />
|
||||
</w:rPr>
|
||||
<w:t>Short Description:</w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t xml:space="preserve"> </w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr>
|
||||
<w:color w:val="813709" />
|
||||
</w:rPr>
|
||||
<w:t>Where it all happens</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Normal" />
|
||||
@@ -1396,6 +1440,28 @@
|
||||
<w:t>Ancient Europe</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="MetaText" />
|
||||
</w:pPr>
|
||||
<w:r>
|
||||
<w:rPr>
|
||||
<w:b />
|
||||
<w:color w:val="813709" />
|
||||
</w:rPr>
|
||||
<w:t>Short Description:</w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr />
|
||||
<w:t xml:space="preserve"> </w:t>
|
||||
</w:r>
|
||||
<w:r>
|
||||
<w:rPr>
|
||||
<w:color w:val="813709" />
|
||||
</w:rPr>
|
||||
<w:t>A strange place</w:t>
|
||||
</w:r>
|
||||
</w:p>
|
||||
<w:p>
|
||||
<w:pPr>
|
||||
<w:pStyle w:val="Normal" />
|
||||
|
||||
@@ -9,13 +9,13 @@
|
||||
<w:docVars>
|
||||
<w:docVar w:name="ManuscriptTitleCount" w:val="11" />
|
||||
<w:docVar w:name="ManuscriptParagraphCount" w:val="45" />
|
||||
<w:docVar w:name="ManuscriptAllWords" w:val="4035" />
|
||||
<w:docVar w:name="ManuscriptAllWords" w:val="4050" />
|
||||
<w:docVar w:name="ManuscriptTextWords" w:val="3711" />
|
||||
<w:docVar w:name="ManuscriptTitleWords" w:val="21" />
|
||||
<w:docVar w:name="ManuscriptAllChars" w:val="27064" />
|
||||
<w:docVar w:name="ManuscriptAllChars" w:val="27164" />
|
||||
<w:docVar w:name="ManuscriptTextChars" w:val="24964" />
|
||||
<w:docVar w:name="ManuscriptTitleChars" w:val="123" />
|
||||
<w:docVar w:name="ManuscriptAllWordChars" w:val="23120" />
|
||||
<w:docVar w:name="ManuscriptAllWordChars" w:val="23208" />
|
||||
<w:docVar w:name="ManuscriptTextWordChars" w:val="21296" />
|
||||
<w:docVar w:name="ManuscriptTitleWordChars" w:val="113" />
|
||||
</w:docVars>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:08:41">
|
||||
<novelWriterXML appVersion="2.8a0" hexVersion="0x020800a0" fileVersion="1.5" fileRevision="6" timeStamp="2025-06-12 18:34:07">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="4">
|
||||
<name>New Project</name>
|
||||
<author>Jane Doe</author>
|
||||
@@ -16,16 +16,16 @@
|
||||
</lastHandle>
|
||||
<autoReplace />
|
||||
<status>
|
||||
<entry key="s000000" count="5" red="120" green="120" blue="120" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" red="205" green="171" blue="143" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" red="143" green="240" blue="164" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" red="249" green="240" blue="107" shape="STAR">Finished</entry>
|
||||
<entry key="s000000" count="5" color="faded" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" color="red" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" color="yellow" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" color="green" shape="STAR">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000004" count="7" red="120" green="120" blue="120" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||
<entry key="i000004" count="7" color="purple" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" color="purple" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" color="purple" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" color="purple" shape="BLOCK_4">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="12" novelWords="173" notesWords="27" novelChars="1007" notesChars="133">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:01:24">
|
||||
<novelWriterXML appVersion="2.8a0" hexVersion="0x020800a0" fileVersion="1.5" fileRevision="6" timeStamp="2025-06-12 18:03:05">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0">
|
||||
<name>New Project</name>
|
||||
<author>Jane Doe</author>
|
||||
@@ -16,16 +16,16 @@
|
||||
</lastHandle>
|
||||
<autoReplace />
|
||||
<status>
|
||||
<entry key="s000000" count="5" red="120" green="120" blue="120" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" red="205" green="171" blue="143" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" red="143" green="240" blue="164" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" red="249" green="240" blue="107" shape="STAR">Finished</entry>
|
||||
<entry key="s000000" count="5" color="faded" shape="STAR">New</entry>
|
||||
<entry key="s000001" count="0" color="red" shape="TRIANGLE">Note</entry>
|
||||
<entry key="s000002" count="0" color="yellow" shape="CIRCLE_T">Draft</entry>
|
||||
<entry key="s000003" count="0" color="green" shape="STAR">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000004" count="3" red="120" green="120" blue="120" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
|
||||
<entry key="i000004" count="3" color="purple" shape="SQUARE">New</entry>
|
||||
<entry key="i000005" count="0" color="purple" shape="BLOCK_2">Minor</entry>
|
||||
<entry key="i000006" count="0" color="purple" shape="BLOCK_3">Major</entry>
|
||||
<entry key="i000007" count="0" color="purple" shape="BLOCK_4">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="8" novelWords="9" notesWords="0" novelChars="40" notesChars="0">
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
"H","Title","Document","Line","Status","Chars","Words","Pars","POV","Focus","Characters","Plot","Timeline","Locations","Objects","Entities","Custom","Story","Mentions","Synopsis"
|
||||
"H1","Lorem Ipsum","Lorem Ipsum","1","Finished","259","44","5","","","","","","","","","","","",""
|
||||
"H2","Prologue","Prologue","1","Draft","605","94","2","","","","","","","","","","","","Explanation from the lipsum.com website."
|
||||
"H1","Act One","Act One","1","New","35","6","1","","","","","","","","","","","",""
|
||||
"H2","Chapter One","Chapter One","1","Draft","419","67","1","Bod","","","Main","","Europe","","","","","","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam."
|
||||
"H3","Scene One","Scene One","1","Finished","1197","174","2","Bod","","","Main","","Europe","","","","","","Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur."
|
||||
"H4","Scene One, Section Two","Scene One","13","Finished","1561","230","2","","","","","","","","","","","",""
|
||||
"H3","Scene Two","Scene Two","1","Finished","2034","299","3","Bod","","","Main","","Europe","","","","","","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci."
|
||||
"H4","Scene Two, Section Two","Scene Two","15","Finished","2009","301","3","","","","","","","","","","","",""
|
||||
"H2","Chapter Two","Chapter Two","1","Draft","477","70","1","Bod","","","Main","","Europe","","","","","","Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue."
|
||||
"H3","Scene Three","Scene Three","1","Finished","3006","439","4","Bod","","","Main","","Europe","","","","","","Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos."
|
||||
"H3","Scene Four","Scene Four","1","Finished","3839","563","6","Bod","","","Main","","Europe","","","","","","Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo."
|
||||
"H3","Scene Five","Scene Five","1","Finished","3644","543","5","Bod","","","Main","","Europe","","","","","","Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus."
|
||||
"H","Title","Document","Line","Status","Chars","Words","Pars","POV","Focus","Characters","Plot","Timeline","Locations","Objects","Entities","Custom","Story","Mentions","Synopsis","Note (Purpose)"
|
||||
"H1","Lorem Ipsum","Lorem Ipsum","1","Finished","259","44","5","","","","","","","","","","","","",""
|
||||
"H2","Prologue","Prologue","1","Draft","605","94","2","","","","","","","","","","","","Explanation from the lipsum.com website.","Introduction"
|
||||
"H1","Act One","Act One","1","New","35","6","1","","","","","","","","","","","","",""
|
||||
"H2","Chapter One","Chapter One","1","Draft","419","67","1","Bod","","","Main","","Europe","","","","","","Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.",""
|
||||
"H3","Scene One","Scene One","1","Finished","1197","174","2","Bod","","","Main","","Europe","","","","","","Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.",""
|
||||
"H4","Scene One, Section Two","Scene One","13","Finished","1561","230","2","","","","","","","","","","","","",""
|
||||
"H3","Scene Two","Scene Two","1","Finished","2034","299","3","Bod","","","Main","","Europe","","","","","","Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.",""
|
||||
"H4","Scene Two, Section Two","Scene Two","15","Finished","2009","301","3","","","","","","","","","","","","",""
|
||||
"H2","Chapter Two","Chapter Two","1","Draft","477","70","1","Bod","","","Main","","Europe","","","","","","Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.",""
|
||||
"H3","Scene Three","Scene Three","1","Finished","3006","439","4","Bod","","","Main","","Europe","","","","","","Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.",""
|
||||
"H3","Scene Four","Scene Four","1","Finished","3839","563","6","Bod","","","Main","","Europe","","","","","","Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.",""
|
||||
"H3","Scene Five","Scene Five","1","Finished","3644","543","5","Bod","","","Main","","Europe","","","","","","Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",""
|
||||
|
||||
|
@@ -3,9 +3,9 @@ Lorem Ipsum
|
||||
|
||||
**By lipsum.com**
|
||||
|
||||
Word Count: 4,169
|
||||
Word Count: 4,184
|
||||
|
||||
Character Count: 27,898
|
||||
Character Count: 27,998
|
||||
|
||||
“Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…”
|
||||
|
||||
@@ -162,6 +162,8 @@ Notes: Characters
|
||||
**Tag:** Bod | Nobody Owens
|
||||
**Plot:** Main
|
||||
|
||||
**Short Description:** The Hero
|
||||
|
||||
Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.
|
||||
|
||||
Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.
|
||||
@@ -175,6 +177,8 @@ Notes: Plot
|
||||
|
||||
**Tag:** Main
|
||||
|
||||
**Short Description:** Where it all happens
|
||||
|
||||
Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.
|
||||
|
||||
Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.
|
||||
@@ -186,6 +190,8 @@ Notes: World
|
||||
|
||||
**Tag:** Europe | Ancient Europe
|
||||
|
||||
**Short Description:** A strange place
|
||||
|
||||
Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.
|
||||
|
||||
Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
<title>Lorem Ipsum</title>
|
||||
<meta charset='utf-8'>
|
||||
<meta name='viewport' content='width=device-width, initial-scale=1'>
|
||||
<style>
|
||||
body {color: #000000; font-family: 'Arial'; font-size: 12pt; font-weight: 400; font-style: normal;}
|
||||
p {text-align: left; line-height: 150%; margin-top: 0.00em; margin-bottom: 0.60em;}
|
||||
@@ -20,8 +21,8 @@ h4 {font-size: 1.25em; margin-top: 1.20em; margin-bottom: 0.60em;}
|
||||
<body>
|
||||
<h1 class='title' style='text-align: center;'>Lorem Ipsum</h1>
|
||||
<p style='text-align: center;'><strong>By lipsum.com</strong></p>
|
||||
<p style='text-align: center;'>Word Count: 4,169</p>
|
||||
<p style='text-align: center;'>Character Count: 27,898</p>
|
||||
<p style='text-align: center;'>Word Count: 4,184</p>
|
||||
<p style='text-align: center;'>Character Count: 27,998</p>
|
||||
<p style='text-align: center;'>“Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…”</p>
|
||||
<p style='text-align: center;'>“There is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain…”</p>
|
||||
<p class='comment' style='text-align: justify; page-break-before: always;'><strong><span style='color: #646464'>Comment:</span></strong> <span style='color: #646464'>Exctracted from the lipsum.com website.</span></p>
|
||||
@@ -106,17 +107,20 @@ h4 {font-size: 1.25em; margin-top: 1.20em; margin-bottom: 0.60em;}
|
||||
<h1>Nobody Owens</h1>
|
||||
<p class='meta meta-tag' style='margin-bottom: 0;'><strong><span style='color: #f5871f'>Tag:</span></strong> <span style='color: #4271ae'><a name='tag_bod'>Bod</a></span> | <span style='color: #4271ae'>Nobody Owens</span></p>
|
||||
<p class='meta meta-plot' style='margin-top: 0;'><strong><span style='color: #f5871f'>Plot:</span></strong> <span style='color: #4271ae'><a href='#tag_main'>Main</a></span></p>
|
||||
<p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Short Description:</span></strong> <span style='color: #813709'>The Hero</span></p>
|
||||
<p style='text-align: justify;'>Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</p>
|
||||
<p style='text-align: justify; text-indent: 1.40em;'>Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</p>
|
||||
<p style='text-align: justify; text-indent: 1.40em;'>Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.</p>
|
||||
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: Plot</h1>
|
||||
<h1>Main Plot</h1>
|
||||
<p class='meta meta-tag'><strong><span style='color: #f5871f'>Tag:</span></strong> <span style='color: #4271ae'><a name='tag_main'>Main</a></span></p>
|
||||
<p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Short Description:</span></strong> <span style='color: #813709'>Where it all happens</span></p>
|
||||
<p style='text-align: justify;'>Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.</p>
|
||||
<p style='text-align: justify; text-indent: 1.40em;'>Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</p>
|
||||
<h1 class='title' style='text-align: center; page-break-before: always;'>Notes: World</h1>
|
||||
<h1>Ancient Europe</h1>
|
||||
<p class='meta meta-tag'><strong><span style='color: #f5871f'>Tag:</span></strong> <span style='color: #4271ae'><a name='tag_europe'>Europe</a></span> | <span style='color: #4271ae'>Ancient Europe</span></p>
|
||||
<p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Short Description:</span></strong> <span style='color: #813709'>A strange place</span></p>
|
||||
<p style='text-align: justify;'>Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</p>
|
||||
<p style='text-align: justify; text-indent: 1.40em;'>Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</p>
|
||||
<p style='text-align: justify; text-indent: 1.40em;'>Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</p>
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"meta": {
|
||||
"projectName": "Lorem Ipsum",
|
||||
"novelAuthor": "lipsum.com",
|
||||
"buildTime": 1737835371,
|
||||
"buildTimeStr": "2025-01-25 21:02:51"
|
||||
"buildTime": 1750360261,
|
||||
"buildTimeStr": "2025-06-19 21:11:01"
|
||||
},
|
||||
"text": {
|
||||
"css": [
|
||||
@@ -23,8 +23,8 @@
|
||||
[
|
||||
"<h1 class='title' style='text-align: center;'>Lorem Ipsum</h1>",
|
||||
"<p style='text-align: center;'><strong>By lipsum.com</strong></p>",
|
||||
"<p style='text-align: center;'>Word Count: 4,169</p>",
|
||||
"<p style='text-align: center;'>Character Count: 27,898</p>",
|
||||
"<p style='text-align: center;'>Word Count: 4,184</p>",
|
||||
"<p style='text-align: center;'>Character Count: 27,998</p>",
|
||||
"<p style='text-align: center;'>\u201cNeque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit\u2026\u201d</p>",
|
||||
"<p style='text-align: center;'>\u201cThere is no one who loves pain itself, who seeks after it and wants to have it, simply because it is pain\u2026\u201d</p>"
|
||||
],
|
||||
@@ -135,6 +135,7 @@
|
||||
"<h1>Nobody Owens</h1>",
|
||||
"<p class='meta meta-tag' style='margin-bottom: 0;'><strong><span style='color: #f5871f'>Tag:</span></strong> <span style='color: #4271ae'><a name='tag_bod'>Bod</a></span> | <span style='color: #4271ae'>Nobody Owens</span></p>",
|
||||
"<p class='meta meta-plot' style='margin-top: 0;'><strong><span style='color: #f5871f'>Plot:</span></strong> <span style='color: #4271ae'><a href='#tag_main'>Main</a></span></p>",
|
||||
"<p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Short Description:</span></strong> <span style='color: #813709'>The Hero</span></p>",
|
||||
"<p style='text-align: justify;'>Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</p>",
|
||||
"<p style='text-align: justify; text-indent: 1.40em;'>Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</p>",
|
||||
"<p style='text-align: justify; text-indent: 1.40em;'>Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.</p>"
|
||||
@@ -145,6 +146,7 @@
|
||||
[
|
||||
"<h1>Main Plot</h1>",
|
||||
"<p class='meta meta-tag'><strong><span style='color: #f5871f'>Tag:</span></strong> <span style='color: #4271ae'><a name='tag_main'>Main</a></span></p>",
|
||||
"<p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Short Description:</span></strong> <span style='color: #813709'>Where it all happens</span></p>",
|
||||
"<p style='text-align: justify;'>Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.</p>",
|
||||
"<p style='text-align: justify; text-indent: 1.40em;'>Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</p>"
|
||||
],
|
||||
@@ -154,6 +156,7 @@
|
||||
[
|
||||
"<h1>Ancient Europe</h1>",
|
||||
"<p class='meta meta-tag'><strong><span style='color: #f5871f'>Tag:</span></strong> <span style='color: #4271ae'><a name='tag_europe'>Europe</a></span> | <span style='color: #4271ae'>Ancient Europe</span></p>",
|
||||
"<p class='comment' style='text-align: justify;'><strong><span style='color: #813709'>Short Description:</span></strong> <span style='color: #813709'>A strange place</span></p>",
|
||||
"<p style='text-align: justify;'>Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</p>",
|
||||
"<p style='text-align: justify; text-indent: 1.40em;'>Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</p>",
|
||||
"<p style='text-align: justify; text-indent: 1.40em;'>Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</p>"
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
"meta": {
|
||||
"projectName": "Lorem Ipsum",
|
||||
"novelAuthor": "lipsum.com",
|
||||
"buildTime": 1731001720,
|
||||
"buildTimeStr": "2024-11-07 18:48:40"
|
||||
"buildTime": 1750360135,
|
||||
"buildTimeStr": "2025-06-19 21:08:55"
|
||||
},
|
||||
"text": {
|
||||
"nwd": [
|
||||
@@ -32,7 +32,8 @@
|
||||
[
|
||||
"##! Prologue",
|
||||
"",
|
||||
"% Synopsis: Explanation from the lipsum.com website.",
|
||||
"%Synopsis: Explanation from the lipsum.com website.",
|
||||
"%Note.Purpose: Introduction",
|
||||
"",
|
||||
"_Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.",
|
||||
"",
|
||||
@@ -52,7 +53,7 @@
|
||||
"@plot: Main",
|
||||
"@location: Europe",
|
||||
"",
|
||||
"% Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.",
|
||||
"%Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.",
|
||||
"",
|
||||
"Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar."
|
||||
],
|
||||
@@ -63,7 +64,7 @@
|
||||
"@plot: Main",
|
||||
"@location: Europe",
|
||||
"",
|
||||
"% Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.",
|
||||
"%Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.",
|
||||
"",
|
||||
"Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada.",
|
||||
"",
|
||||
@@ -82,7 +83,7 @@
|
||||
"@plot: Main",
|
||||
"@location: Europe",
|
||||
"",
|
||||
"% Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. ",
|
||||
"%Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. ",
|
||||
"",
|
||||
"Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien.",
|
||||
"",
|
||||
@@ -116,7 +117,7 @@
|
||||
"@plot: Main",
|
||||
"@location: Europe",
|
||||
"",
|
||||
"% Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.",
|
||||
"%Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.",
|
||||
"",
|
||||
"Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at."
|
||||
],
|
||||
@@ -127,7 +128,7 @@
|
||||
"@plot: Main",
|
||||
"@location: Europe",
|
||||
"",
|
||||
"% Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.",
|
||||
"%Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.",
|
||||
"",
|
||||
"Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis.",
|
||||
"",
|
||||
@@ -144,7 +145,7 @@
|
||||
"@plot: Main",
|
||||
"@location: Europe",
|
||||
"",
|
||||
"% Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.",
|
||||
"%Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.",
|
||||
"",
|
||||
"Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien.",
|
||||
"",
|
||||
@@ -165,7 +166,7 @@
|
||||
"@plot: Main",
|
||||
"@location: Europe",
|
||||
"",
|
||||
"% Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
|
||||
"%Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.",
|
||||
"",
|
||||
"Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet.",
|
||||
"",
|
||||
@@ -186,6 +187,8 @@
|
||||
"@tag: Bod | Nobody Owens",
|
||||
"@plot: Main",
|
||||
"",
|
||||
"%Short: The Hero",
|
||||
"",
|
||||
"Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.",
|
||||
"",
|
||||
"Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.",
|
||||
@@ -200,6 +203,8 @@
|
||||
"",
|
||||
"@tag: Main",
|
||||
"",
|
||||
"%Short: Where it all happens",
|
||||
"",
|
||||
"Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.",
|
||||
"",
|
||||
"Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur."
|
||||
@@ -212,6 +217,8 @@
|
||||
"",
|
||||
"@tag: Europe | Ancient Europe",
|
||||
"",
|
||||
"%Short: A strange place",
|
||||
"",
|
||||
"Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.",
|
||||
"",
|
||||
"Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.",
|
||||
|
||||
@@ -20,7 +20,8 @@ The standard chunk of Lorem Ipsum used since the 1500s is reproduced below for t
|
||||
|
||||
##! Prologue
|
||||
|
||||
% Synopsis: Explanation from the lipsum.com website.
|
||||
%Synopsis: Explanation from the lipsum.com website.
|
||||
%Note.Purpose: Introduction
|
||||
|
||||
_Lorem Ipsum_ is simply dummy text[footnote:f9kgf] of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum.
|
||||
|
||||
@@ -38,7 +39,7 @@ See http://lipsum.com
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.
|
||||
%Synopsis: Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam.
|
||||
|
||||
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam quam. Praesent magna nunc, lacinia sit amet quam eget, aliquet ultrices justo. Morbi ornare enim et lorem rutrum finibus ut eu dolor. Aliquam a orci odio. Ut ultrices sem quis massa placerat, eget mollis nisl cursus. Cras vel sagittis justo. Ut non ultricies leo. Maecenas rutrum velit in est varius, et egestas massa pulvinar.
|
||||
|
||||
@@ -48,7 +49,7 @@ Lorem ipsum dolor sit amet, consectetur adipiscing elit. Pellentesque at aliquam
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.
|
||||
%Synopsis: Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur.
|
||||
|
||||
Aenean ut placerat velit. Etiam laoreet ullamcorper risus, eget lobortis enim scelerisque non. Suspendisse id maximus nunc, et mollis sapien. Curabitur vel semper sapien, non pulvinar dolor. Etiam finibus nisi vel mi molestie consectetur. Donec quis ante nunc. Mauris ut leo ipsum. Vestibulum est neque, hendrerit nec neque a, ullamcorper lobortis tellus. Fusce sollicitudin purus quis congue bibendum. Aliquam condimentum ipsum tristique blandit tristique. Donec pulvinar neque ac suscipit malesuada.
|
||||
|
||||
@@ -66,7 +67,7 @@ Curabitur congue, justo quis interdum fermentum, tellus nulla imperdiet sapien,
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.
|
||||
%Synopsis: Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci.
|
||||
|
||||
Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Integer sapien nulla, dictum at lacus a, dignissim consectetur dolor. Nunc vel eleifend lacus, eu dapibus orci. Vestibulum facilisis bibendum aliquam. Aliquam posuere, turpis ac bibendum varius, sem tellus venenatis risus, in elementum massa enim ac lorem. Integer in sem ac diam blandit ultricies ut in nulla. Lorem ipsum dolor sit amet, consectetur adipiscing elit. Etiam sit amet erat est. Curabitur vitae cursus justo, sit amet placerat dolor. Vivamus eu felis hendrerit, tincidunt massa rutrum, maximus arcu. Pellentesque commodo justo odio, vel rutrum nulla tincidunt eu. Integer non neque condimentum, convallis diam non, varius ligula. Aliquam eget sapien mauris. Aenean pharetra nunc nisi, vel maximus ante tristique sit amet. Aliquam risus metus, interdum non odio eu, consectetur lacinia sapien.
|
||||
|
||||
@@ -98,7 +99,7 @@ Ut et consequat enim, quis ornare nibh. In lectus neque, mollis et suscipit et,
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.
|
||||
%Synopsis: Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue.
|
||||
|
||||
Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis pharetra sem vitae dapibus. Curabitur varius lorem non pulvinar congue. Vestibulum pharetra fermentum leo, sed faucibus eros placerat quis. In hac habitasse platea dictumst. Donec metus massa, rutrum quis consequat et, tincidunt ac felis. Duis mollis metus ac nunc tincidunt blandit. Ut aliquet velit eu odio pharetra condimentum. Integer rutrum lacus orci, id venenatis libero accumsan at.
|
||||
|
||||
@@ -108,7 +109,7 @@ Curabitur a elit posuere, varius ex et, convallis neque. Phasellus sagittis phar
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
|
||||
%Synopsis: Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos.
|
||||
|
||||
Aenean ut libero ut lectus porttitor rhoncus vel et massa. Nam pretium, nibh et varius vehicula, urna metus blandit eros, euismod pharetra diam diam et libero. Class aptent taciti sociosqu ad litora torquent per conubia nostra, per inceptos himenaeos. Aenean tincidunt lacus vitae nibh elementum eleifend. Sed rutrum condimentum sem quis blandit. Duis imperdiet libero metus, quis convallis quam faucibus a. Nulla ligula est, semper quis sollicitudin et, pretium id justo. Curabitur pharetra risus eget consectetur commodo. Duis mattis arcu non est condimentum, id venenatis risus volutpat. Pellentesque aliquet mauris non mauris porttitor ultrices. Phasellus ut vestibulum mi. Suspendisse malesuada metus lorem, a malesuada orci rhoncus a. Praesent euismod convallis ante, lacinia tincidunt ex egestas id. Praesent sit amet efficitur sapien. Morbi tincidunt volutpat nunc sed dictum. Aliquam ultrices metus id fermentum lobortis.
|
||||
|
||||
@@ -124,7 +125,7 @@ Aenean dapibus vulputate purus, sit amet tempor nunc suscipit consequat. Orci va
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.
|
||||
%Synopsis: Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo.
|
||||
|
||||
Nam tempor blandit magna laoreet aliquet. Vestibulum auctor posuere leo, ac gravida nisi rhoncus varius. Aenean posuere dolor vitae condimentum volutpat. Donec egestas volutpat risus, quis luctus justo. Nullam viverra dui et auctor pretium. Ut ullamcorper velit urna, sed imperdiet massa convallis a. Suspendisse efficitur, ipsum nec cursus pulvinar, eros urna posuere diam, nec elementum mi felis vitae sapien.
|
||||
|
||||
@@ -144,7 +145,7 @@ Phasellus eu nunc ut nunc faucibus laoreet. Aliquam at magna risus. Praesent lob
|
||||
@plot: Main
|
||||
@location: Europe
|
||||
|
||||
% Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
%Synopsis: Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus.
|
||||
|
||||
Praesent eget est porta, dictum ante in, egestas risus. Mauris risus mauris, consequat aliquam mauris et, feugiat iaculis ipsum. Aliquam arcu ipsum, fermentum ut arcu sed, lobortis euismod sem. Orci varius natoque penatibus et magnis dis parturient montes, nascetur ridiculus mus. In sed felis auctor, rhoncus dui ac, consequat dolor. Integer volutpat libero sed nisl aliquet varius. Suspendisse et lorem sapien. Proin id ultrices nibh, ac suscipit diam. Suspendisse placerat varius porttitor. Curabitur elementum sed enim ultrices imperdiet.
|
||||
|
||||
@@ -163,6 +164,8 @@ Integer egestas maximus leo eu facilisis. Nunc rhoncus dignissim lectus eu lacin
|
||||
@tag: Bod | Nobody Owens
|
||||
@plot: Main
|
||||
|
||||
%Short: The Hero
|
||||
|
||||
Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.
|
||||
|
||||
Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.
|
||||
@@ -175,6 +178,8 @@ Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales
|
||||
|
||||
@tag: Main
|
||||
|
||||
%Short: Where it all happens
|
||||
|
||||
Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.
|
||||
|
||||
Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.
|
||||
@@ -185,6 +190,8 @@ Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam.
|
||||
|
||||
@tag: Europe | Ancient Europe
|
||||
|
||||
%Short: A strange place
|
||||
|
||||
Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.
|
||||
|
||||
Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<office:document xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:fo="urn:oasis:names:tc:opendocument:xmlns:xsl-fo-compatible:1.0" xmlns:loext="urn:org:documentfoundation:names:experimental:office:xmlns:loext:1.0" xmlns:meta="urn:oasis:names:tc:opendocument:xmlns:meta:1.0" xmlns:number="urn:oasis:names:tc:opendocument:xmlns:datastyle:1.0" xmlns:office="urn:oasis:names:tc:opendocument:xmlns:office:1.0" xmlns:style="urn:oasis:names:tc:opendocument:xmlns:style:1.0" xmlns:text="urn:oasis:names:tc:opendocument:xmlns:text:1.0" xmlns:xlink="http://www.w3.org/1999/xlink" office:version="1.3" office:mimetype="application/vnd.oasis.opendocument.text">
|
||||
<office:meta>
|
||||
<meta:creation-date>2025-01-25T16:15:30</meta:creation-date>
|
||||
<meta:generator>novelWriter/2.6rc1</meta:generator>
|
||||
<meta:creation-date>2025-06-19T21:05:16</meta:creation-date>
|
||||
<meta:generator>novelWriter/2.8a1</meta:generator>
|
||||
<meta:initial-creator>lipsum.com</meta:initial-creator>
|
||||
<meta:editing-cycles>51</meta:editing-cycles>
|
||||
<meta:editing-duration>P0DT0H40M56S</meta:editing-duration>
|
||||
<meta:editing-cycles>55</meta:editing-cycles>
|
||||
<meta:editing-duration>P0DT0H45M46S</meta:editing-duration>
|
||||
<dc:title>Lorem Ipsum</dc:title>
|
||||
<dc:date>2025-01-25T16:15:30</dc:date>
|
||||
<dc:date>2025-06-19T21:05:16</dc:date>
|
||||
<dc:creator>lipsum.com</dc:creator>
|
||||
</office:meta>
|
||||
<office:font-face-decls>
|
||||
@@ -152,13 +152,13 @@
|
||||
<text:user-field-decls>
|
||||
<text:user-field-decl office:value-type="float" office:value="19" text:name="ManuscriptTitleCount" />
|
||||
<text:user-field-decl office:value-type="float" office:value="48" text:name="ManuscriptParagraphCount" />
|
||||
<text:user-field-decl office:value-type="float" office:value="4169" text:name="ManuscriptAllWords" />
|
||||
<text:user-field-decl office:value-type="float" office:value="4184" text:name="ManuscriptAllWords" />
|
||||
<text:user-field-decl office:value-type="float" office:value="3815" text:name="ManuscriptTextWords" />
|
||||
<text:user-field-decl office:value-type="float" office:value="54" text:name="ManuscriptTitleWords" />
|
||||
<text:user-field-decl office:value-type="float" office:value="27898" text:name="ManuscriptAllChars" />
|
||||
<text:user-field-decl office:value-type="float" office:value="27998" text:name="ManuscriptAllChars" />
|
||||
<text:user-field-decl office:value-type="float" office:value="25578" text:name="ManuscriptTextChars" />
|
||||
<text:user-field-decl office:value-type="float" office:value="310" text:name="ManuscriptTitleChars" />
|
||||
<text:user-field-decl office:value-type="float" office:value="23826" text:name="ManuscriptAllWordChars" />
|
||||
<text:user-field-decl office:value-type="float" office:value="23914" text:name="ManuscriptAllWordChars" />
|
||||
<text:user-field-decl office:value-type="float" office:value="21806" text:name="ManuscriptTextWordChars" />
|
||||
<text:user-field-decl office:value-type="float" office:value="275" text:name="ManuscriptTitleWordChars" />
|
||||
</text:user-field-decls>
|
||||
@@ -255,17 +255,20 @@
|
||||
<text:h text:style-name="Heading_20_1" text:outline-level="1">Nobody Owens</text:h>
|
||||
<text:p text:style-name="P8"><text:span text:style-name="T8">Tag:</text:span> <text:span text:style-name="T9">Bod</text:span> | <text:span text:style-name="T10">Nobody Owens</text:span></text:p>
|
||||
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Plot:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
|
||||
<text:p text:style-name="P5"><text:span text:style-name="T4">Short Description:</text:span> <text:span text:style-name="T5">The Hero</text:span></text:p>
|
||||
<text:p text:style-name="P6">Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.</text:p>
|
||||
<text:p text:style-name="P3">Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.</text:p>
|
||||
<text:p text:style-name="P3">Nullam scelerisque velit et tortor congue vestibulum a at nisi. Vivamus sodales ut turpis a convallis. In dignissim nibh at luctus sodales. Etiam sit amet rhoncus massa. Phasellus ligula magna, sollicitudin non imperdiet sit amet, volutpat vel magna. Nunc vestibulum tempor lectus, sit amet porta nunc hendrerit in. Curabitur non odio sit amet massa tincidunt facilisis. Integer et luctus nunc, eget euismod leo. Praesent faucibus metus sed purus convallis scelerisque. Fusce viverra lorem et placerat malesuada. In at elit malesuada, ullamcorper risus vitae, sodales dolor. Donec quis elementum lectus. Quisque eu eros at dui imperdiet euismod ut id neque.</text:p>
|
||||
<text:p text:style-name="P7">Notes: Plot</text:p>
|
||||
<text:h text:style-name="Heading_20_1" text:outline-level="1">Main Plot</text:h>
|
||||
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Tag:</text:span> <text:span text:style-name="T9">Main</text:span></text:p>
|
||||
<text:p text:style-name="P5"><text:span text:style-name="T4">Short Description:</text:span> <text:span text:style-name="T5">Where it all happens</text:span></text:p>
|
||||
<text:p text:style-name="P6">Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.</text:p>
|
||||
<text:p text:style-name="P3">Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.</text:p>
|
||||
<text:p text:style-name="P7">Notes: World</text:p>
|
||||
<text:h text:style-name="Heading_20_1" text:outline-level="1">Ancient Europe</text:h>
|
||||
<text:p text:style-name="Text_20_Meta"><text:span text:style-name="T8">Tag:</text:span> <text:span text:style-name="T9">Europe</text:span> | <text:span text:style-name="T10">Ancient Europe</text:span></text:p>
|
||||
<text:p text:style-name="P5"><text:span text:style-name="T4">Short Description:</text:span> <text:span text:style-name="T5">A strange place</text:span></text:p>
|
||||
<text:p text:style-name="P6">Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.</text:p>
|
||||
<text:p text:style-name="P3">Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.</text:p>
|
||||
<text:p text:style-name="P3">Nunc ullamcorper magna quis elit condimentum rhoncus. Aenean dictum pulvinar dolor suscipit interdum. Aliquam elit massa, elementum nec cursus eu, maximus nec ipsum. Donec ullamcorper iaculis dolor eu commodo. Nunc eget tortor quis turpis consectetur varius. Vestibulum nec justo vel tellus venenatis condimentum. Duis auctor iaculis massa. Nunc risus magna, rutrum vitae eros non, tristique mollis enim.</text:p>
|
||||
|
||||
@@ -3,9 +3,9 @@ Lorem Ipsum
|
||||
|
||||
**By lipsum.com**
|
||||
|
||||
Word Count: 4,169
|
||||
Word Count: 4,184
|
||||
|
||||
Character Count: 27,898
|
||||
Character Count: 27,998
|
||||
|
||||
“Neque porro quisquam est qui dolorem ipsum quia dolor sit amet, consectetur, adipisci velit…”
|
||||
|
||||
@@ -162,6 +162,8 @@ Notes: Characters
|
||||
**Tag:** Bod | Nobody Owens
|
||||
**Plot:** Main
|
||||
|
||||
**Short Description:** The Hero
|
||||
|
||||
Pellentesque nec erat ut nulla posuere commodo. Curabitur nisi augue, imperdiet et porta imperdiet, efficitur id leo. Cras finibus arcu at nibh commodo congue. Proin suscipit placerat condimentum. Aenean ante enim, cursus id lorem a, blandit venenatis nibh. Maecenas suscipit porta elit, sit amet porta felis porttitor eu. Sed a dui nibh. Phasellus sed faucibus dui. Pellentesque felis nulla, ultrices non efficitur quis, rutrum id mi. Mauris tempus auctor nisl, in bibendum enim pellentesque sit amet. Proin nunc lacus, imperdiet nec posuere ac, interdum non lectus.
|
||||
|
||||
Suspendisse faucibus est auctor orci mollis luctus. Praesent quis sodales neque. Interdum et malesuada fames ac ante ipsum primis in faucibus. Donec sodales rutrum mattis. In in sem ornare, consequat nulla ac, convallis arcu. Duis ac metus id felis commodo commodo sit amet eget diam. Curabitur rhoncus lacinia leo at sodales. Etiam finibus porta diam a viverra. Praesent nisi urna, volutpat sit amet odio at, vehicula vehicula leo. In non enim eget nisl luctus commodo. Pellentesque pellentesque at lectus at luctus. Quisque nec felis bibendum, lacinia libero ut, lacinia eros. Integer finibus ultricies nibh sit amet placerat.
|
||||
@@ -175,6 +177,8 @@ Notes: Plot
|
||||
|
||||
**Tag:** Main
|
||||
|
||||
**Short Description:** Where it all happens
|
||||
|
||||
Suspendisse vulputate malesuada pellentesque. Aenean sollicitudin cursus mi, vitae ultricies felis ullamcorper eu. Duis luctus risus mi, in accumsan velit cursus ut. Vestibulum eleifend leo in magna eleifend fermentum. Proin nec ornare elit. Phasellus nec interdum risus. In a volutpat augue, quis egestas justo. Morbi porta mauris mattis bibendum imperdiet.
|
||||
|
||||
Mauris ut erat eu lorem malesuada egestas vel vel urna. Maecenas ac semper quam. Maecenas aliquet metus non interdum mattis. Proin consectetur molestie ligula. Aliquam sollicitudin pulvinar urna a pellentesque. Suspendisse ultrices, est mattis scelerisque porta, nisi nisi laoreet nisl, non condimentum quam ante a velit. Proin scelerisque justo augue, nec laoreet ligula egestas at. Etiam enim quam, ultrices non accumsan hendrerit, elementum vel ligula. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Pellentesque habitant morbi tristique senectus et netus et malesuada fames ac turpis egestas. Nam efficitur odio libero, in vestibulum arcu aliquam at. Cras non vehicula augue. Integer lobortis, est vitae aliquam facilisis, metus ligula aliquet eros, at porttitor sem tortor eget massa. Aliquam varius scelerisque neque sed gravida. Aenean eleifend lorem id ante elementum sollicitudin. Proin commodo massa a quam volutpat, mollis fermentum turpis efficitur.
|
||||
@@ -186,6 +190,8 @@ Notes: World
|
||||
|
||||
**Tag:** Europe | Ancient Europe
|
||||
|
||||
**Short Description:** A strange place
|
||||
|
||||
Vivamus sodales risus ac accumsan posuere. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia curae; Nunc vel enim felis. Vestibulum dignissim massa nunc, a auctor magna eleifend et. Proin dignissim sodales erat vitae convallis. Aliquam id tellus dui. Curabitur sollicitudin scelerisque ex sit amet posuere. Nam rutrum felis id rhoncus feugiat. Duis sagittis quam quis purus efficitur, quis rutrum odio iaculis. Maecenas semper ante turpis, at vulputate mi consectetur non. Sed rutrum nibh turpis, quis rhoncus purus ornare quis. Vestibulum at rutrum mauris. Integer dolor nisi, tincidunt eget vehicula ac, ultricies at ligula.
|
||||
|
||||
Aenean semper turpis quis varius rhoncus. Vivamus ac mi eget felis euismod vulputate. Nam eu tempus velit. Etiam ut est porta, finibus erat sit amet, consectetur felis. Nullam consequat felis ut lacus pharetra, in lobortis urna mollis. Nulla varius eros nec lorem rhoncus, sed venenatis risus ultrices. Phasellus pellentesque laoreet neque, ut ultricies lacus vulputate quis. In malesuada dui sit amet est interdum, eget consectetur mi gravida. Cras vel bibendum purus. Quisque commodo tempor arcu, non lacinia sem blandit eleifend. Quisque at neque gravida, porttitor metus a, suscipit diam. Quisque convallis sodales lacus et condimentum. Donec a suscipit diam. Pellentesque eget cursus neque.
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2020-05-28 09:59:15">
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="6" timeStamp="2020-05-28 09:59:15">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000">
|
||||
<name>Sample Project</name>
|
||||
<author>Jay Doh</author>
|
||||
@@ -20,19 +20,19 @@
|
||||
<entry key="C">D</entry>
|
||||
</autoReplace>
|
||||
<status>
|
||||
<entry key="s000000" count="0" red="100" green="100" blue="100" shape="SQUARE">New</entry>
|
||||
<entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
|
||||
<entry key="s000002" count="0" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
|
||||
<entry key="s000003" count="0" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="s000004" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s000005" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s000006" count="0" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
|
||||
<entry key="s000000" count="0" color="#646464" shape="SQUARE">New</entry>
|
||||
<entry key="s000001" count="0" color="#c83200" shape="SQUARE">Notes</entry>
|
||||
<entry key="s000002" count="0" color="#b63c00" shape="SQUARE">Started</entry>
|
||||
<entry key="s000003" count="0" color="#c18100" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="s000004" count="0" color="#c18100" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s000005" count="0" color="#c18100" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s000006" count="0" color="#3ab43a" shape="SQUARE">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000007" count="0" red="100" green="100" blue="100" shape="SQUARE">None</entry>
|
||||
<entry key="i000008" count="0" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
|
||||
<entry key="i000009" count="0" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
|
||||
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||
<entry key="i000007" count="0" color="#646464" shape="SQUARE">None</entry>
|
||||
<entry key="i000008" count="0" color="#007abc" shape="SQUARE">Minor</entry>
|
||||
<entry key="i000009" count="0" color="#1500b4" shape="SQUARE">Major</entry>
|
||||
<entry key="i00000a" count="0" color="#7500af" shape="SQUARE">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="22" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2020-06-26 21:20:24">
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="6" timeStamp="2020-06-26 21:20:24">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
||||
<name>Sample Project</name>
|
||||
<author>Jay Doh</author>
|
||||
@@ -20,19 +20,19 @@
|
||||
<entry key="C">D</entry>
|
||||
</autoReplace>
|
||||
<status>
|
||||
<entry key="s000000" count="0" red="100" green="100" blue="100" shape="SQUARE">New</entry>
|
||||
<entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
|
||||
<entry key="s000002" count="0" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
|
||||
<entry key="s000003" count="0" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="s000004" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s000005" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s000006" count="0" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
|
||||
<entry key="s000000" count="0" color="#646464" shape="SQUARE">New</entry>
|
||||
<entry key="s000001" count="0" color="#c83200" shape="SQUARE">Notes</entry>
|
||||
<entry key="s000002" count="0" color="#b63c00" shape="SQUARE">Started</entry>
|
||||
<entry key="s000003" count="0" color="#c18100" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="s000004" count="0" color="#c18100" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s000005" count="0" color="#c18100" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s000006" count="0" color="#3ab43a" shape="SQUARE">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000007" count="0" red="100" green="100" blue="100" shape="SQUARE">None</entry>
|
||||
<entry key="i000008" count="0" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
|
||||
<entry key="i000009" count="0" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
|
||||
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||
<entry key="i000007" count="0" color="#646464" shape="SQUARE">None</entry>
|
||||
<entry key="i000008" count="0" color="#007abc" shape="SQUARE">Minor</entry>
|
||||
<entry key="i000009" count="0" color="#1500b4" shape="SQUARE">Major</entry>
|
||||
<entry key="i00000a" count="0" color="#7500af" shape="SQUARE">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="22" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2021-08-30 23:33:44">
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="6" timeStamp="2021-08-30 23:33:44">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
||||
<name>Sample Project</name>
|
||||
<author>Jay Doh</author>
|
||||
@@ -20,19 +20,19 @@
|
||||
<entry key="C">D</entry>
|
||||
</autoReplace>
|
||||
<status>
|
||||
<entry key="s000000" count="0" red="100" green="100" blue="100" shape="SQUARE">New</entry>
|
||||
<entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
|
||||
<entry key="s000002" count="0" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
|
||||
<entry key="s000003" count="0" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="s000004" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s000005" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s000006" count="0" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
|
||||
<entry key="s000000" count="0" color="#646464" shape="SQUARE">New</entry>
|
||||
<entry key="s000001" count="0" color="#c83200" shape="SQUARE">Notes</entry>
|
||||
<entry key="s000002" count="0" color="#b63c00" shape="SQUARE">Started</entry>
|
||||
<entry key="s000003" count="0" color="#c18100" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="s000004" count="0" color="#c18100" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s000005" count="0" color="#c18100" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s000006" count="0" color="#3ab43a" shape="SQUARE">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000007" count="0" red="100" green="100" blue="100" shape="SQUARE">None</entry>
|
||||
<entry key="i000008" count="0" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
|
||||
<entry key="i000009" count="0" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
|
||||
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||
<entry key="i000007" count="0" color="#646464" shape="SQUARE">None</entry>
|
||||
<entry key="i000008" count="0" color="#007abc" shape="SQUARE">Minor</entry>
|
||||
<entry key="i000009" count="0" color="#1500b4" shape="SQUARE">Major</entry>
|
||||
<entry key="i00000a" count="0" color="#7500af" shape="SQUARE">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="25" novelWords="840" notesWords="376" novelChars="0" notesChars="0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2022-10-25 18:26:15">
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="6" timeStamp="2022-10-25 18:26:15">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
||||
<name>Sample Project</name>
|
||||
<author>Jay Doh</author>
|
||||
@@ -20,19 +20,19 @@
|
||||
<entry key="C">D</entry>
|
||||
</autoReplace>
|
||||
<status>
|
||||
<entry key="s000000" count="0" red="100" green="100" blue="100" shape="SQUARE">New</entry>
|
||||
<entry key="s000001" count="0" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
|
||||
<entry key="s000002" count="0" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
|
||||
<entry key="s000003" count="0" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="s000004" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s000005" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s000006" count="0" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
|
||||
<entry key="s000000" count="0" color="#646464" shape="SQUARE">New</entry>
|
||||
<entry key="s000001" count="0" color="#c83200" shape="SQUARE">Notes</entry>
|
||||
<entry key="s000002" count="0" color="#b63c00" shape="SQUARE">Started</entry>
|
||||
<entry key="s000003" count="0" color="#c18100" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="s000004" count="0" color="#c18100" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s000005" count="0" color="#c18100" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s000006" count="0" color="#3ab43a" shape="SQUARE">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<entry key="i000007" count="0" red="100" green="100" blue="100" shape="SQUARE">None</entry>
|
||||
<entry key="i000008" count="0" red="0" green="122" blue="188" shape="SQUARE">Minor</entry>
|
||||
<entry key="i000009" count="0" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
|
||||
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||
<entry key="i000007" count="0" color="#646464" shape="SQUARE">None</entry>
|
||||
<entry key="i000008" count="0" color="#007abc" shape="SQUARE">Minor</entry>
|
||||
<entry key="i000009" count="0" color="#1500b4" shape="SQUARE">Major</entry>
|
||||
<entry key="i00000a" count="0" color="#7500af" shape="SQUARE">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="25" novelWords="830" notesWords="376" novelChars="0" notesChars="0">
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2022-10-15 12:12:59">
|
||||
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="6" timeStamp="2022-10-15 12:12:59">
|
||||
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
|
||||
<name>Sample Project</name>
|
||||
<author>Jay Doh</author>
|
||||
@@ -20,19 +20,19 @@
|
||||
<entry key="C">D</entry>
|
||||
</autoReplace>
|
||||
<status>
|
||||
<entry key="sf12341" count="4" 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="sc24b8f" count="3" red="182" green="60" blue="0" shape="SQUARE">Started</entry>
|
||||
<entry key="s90e6c9" count="7" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="sd51c5b" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s8ae72a" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s78ea90" count="1" red="58" green="180" blue="58" shape="SQUARE">Finished</entry>
|
||||
<entry key="sf12341" count="4" color="#646464" shape="SQUARE">New</entry>
|
||||
<entry key="sf24ce6" count="2" color="#c83200" shape="SQUARE">Notes</entry>
|
||||
<entry key="sc24b8f" count="3" color="#b63c00" shape="SQUARE">Started</entry>
|
||||
<entry key="s90e6c9" count="7" color="#c18100" shape="SQUARE">1st Draft</entry>
|
||||
<entry key="sd51c5b" count="0" color="#c18100" shape="SQUARE">2nd Draft</entry>
|
||||
<entry key="s8ae72a" count="0" color="#c18100" shape="SQUARE">3rd Draft</entry>
|
||||
<entry key="s78ea90" count="1" color="#3ab43a" shape="SQUARE">Finished</entry>
|
||||
</status>
|
||||
<importance>
|
||||
<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="i2d7a54" count="2" red="21" green="0" blue="180" shape="SQUARE">Major</entry>
|
||||
<entry key="i56be10" count="1" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
|
||||
<entry key="ia857f0" count="5" color="#646464" shape="SQUARE">None</entry>
|
||||
<entry key="icfb3a5" count="2" color="#007abc" shape="SQUARE">Minor</entry>
|
||||
<entry key="i2d7a54" count="2" color="#1500b4" shape="SQUARE">Major</entry>
|
||||
<entry key="i56be10" count="1" color="#7500af" shape="SQUARE">Main</entry>
|
||||
</importance>
|
||||
</settings>
|
||||
<content items="27" novelWords="954" notesWords="409" novelChars="0" notesChars="0">
|
||||
|
||||
@@ -38,9 +38,10 @@ from novelwriter.common import (
|
||||
fuzzyTime, getFileSize, hexToInt, isHandle, isItemClass, isItemLayout,
|
||||
isItemType, isListInstance, isTitleTag, jsonEncode, makeFileNameSafe,
|
||||
minmax, numberToRoman, openExternalPath, processDialogSymbols,
|
||||
readTextFile, simplified, transferCase, uniqueCompact, xmlElement,
|
||||
xmlIndent, xmlSubElem, yesNo
|
||||
readTextFile, simplified, transferCase, uniqueCompact, utf16CharMap,
|
||||
xmlElement, xmlIndent, xmlSubElem, yesNo
|
||||
)
|
||||
from novelwriter.enum import nwItemClass
|
||||
|
||||
from tests.mocked import causeOSError
|
||||
from tests.tools import writeFile
|
||||
@@ -556,6 +557,14 @@ def testBaseCommon_encodeDecodeMimeHandles(monkeypatch):
|
||||
assert decodeMimeHandles(mimeData) == handles
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_utf16CharMap(monkeypatch):
|
||||
"""Test the utf16CharMap function."""
|
||||
assert utf16CharMap("abc") == [0, 1, 2, 3]
|
||||
assert utf16CharMap("a\u2014b\u2014c") == [0, 1, 2, 3, 4, 5]
|
||||
assert utf16CharMap("a\U0001F605b\U0001F605c") == [0, 1, 3, 4, 6, 7]
|
||||
|
||||
|
||||
@pytest.mark.base
|
||||
def testBaseCommon_jsonEncode():
|
||||
"""Test the jsonEncode function."""
|
||||
@@ -804,6 +813,7 @@ def testBaseCommon_NWConfigParser(fncPath):
|
||||
"list1 = a, b, c\n"
|
||||
"list2 = 17, 18, 19\n"
|
||||
"float1 = 4.2\n"
|
||||
"enum1 = NOVEL\n"
|
||||
f"path1 = {fncPath}\n"
|
||||
))
|
||||
|
||||
@@ -875,3 +885,7 @@ def testBaseCommon_NWConfigParser(fncPath):
|
||||
|
||||
assert cfgParser.rdIntList("nope", "list2", [1]) == [1]
|
||||
assert cfgParser.rdIntList("main", "blabla", [1]) == [1]
|
||||
|
||||
# Read Enum
|
||||
assert cfgParser.rdEnum("main", "enum1", nwItemClass.NO_CLASS) == nwItemClass.NOVEL
|
||||
assert cfgParser.rdEnum("main", "blabla", nwItemClass.NO_CLASS) == nwItemClass.NO_CLASS
|
||||
|
||||
@@ -131,15 +131,15 @@ def testBaseConfig_InitLoadSave(monkeypatch, fncPath, tstPaths):
|
||||
assert tstConf.errorText().startswith("Could not load config file")
|
||||
|
||||
# Change a few settings, save, reset, and reload
|
||||
tstConf.guiTheme = "foo"
|
||||
tstConf.guiSyntax = "bar"
|
||||
tstConf.lightTheme = "foo"
|
||||
tstConf.darkTheme = "bar"
|
||||
assert tstConf.saveConfig() is True
|
||||
|
||||
newConf = Config()
|
||||
newConf.initConfig(confPath=fncPath, dataPath=fncPath)
|
||||
newConf.loadConfig()
|
||||
assert newConf.guiTheme == "foo"
|
||||
assert newConf.guiSyntax == "bar"
|
||||
assert newConf.lightTheme == "foo"
|
||||
assert newConf.darkTheme == "bar"
|
||||
|
||||
# Test Correcting Quote Settings
|
||||
tstConf.fmtDQuoteOpen = '"'
|
||||
|
||||
@@ -547,8 +547,8 @@ def testCoreItem_ClassDefaults(mockGUI):
|
||||
def testCoreItem_PackUnpack(mockGUI, caplog, mockRnd):
|
||||
"""Test packing and unpacking entries for the NWItem class."""
|
||||
project = NWProject()
|
||||
project.data.itemStatus.add(None, "New", (100, 100, 100), "SQUARE", 0)
|
||||
project.data.itemImport.add(None, "New", (100, 100, 100), "SQUARE", 0)
|
||||
project.data.itemStatus.add(None, "New", "#646464", "SQUARE", 0)
|
||||
project.data.itemImport.add(None, "New", "#646464", "SQUARE", 0)
|
||||
|
||||
# Invalid
|
||||
item = NWItem(project, "0000000000000")
|
||||
|
||||
@@ -27,12 +27,11 @@ from shutil import copyfile
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt6.QtGui import QColor
|
||||
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.core.projectdata import NWProjectData
|
||||
from novelwriter.core.projectxml import ProjectXMLReader, ProjectXMLWriter, XMLReadState
|
||||
from novelwriter.core.status import CUSTOM_COL
|
||||
from novelwriter.enum import nwStatusShape
|
||||
|
||||
from tests.mocked import causeOSError
|
||||
@@ -137,7 +136,7 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath):
|
||||
assert xmlReader.state == XMLReadState.PARSED_OK
|
||||
assert xmlReader.xmlRoot == "novelWriterXML"
|
||||
assert xmlReader.xmlVersion == 0x0105
|
||||
assert xmlReader.xmlRevision == 5
|
||||
assert xmlReader.xmlRevision == 6
|
||||
assert xmlReader.appVersion == "2.7b1"
|
||||
assert xmlReader.hexVersion == 0x020700b1
|
||||
|
||||
@@ -173,19 +172,31 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath):
|
||||
assert data.itemImport["i2d7a54"].name == "Major"
|
||||
assert data.itemImport["i56be10"].name == "Main"
|
||||
|
||||
assert data.itemStatus["sf12341"].color == QColor(100, 100, 100)
|
||||
assert data.itemStatus["sf24ce6"].color == QColor(200, 50, 0)
|
||||
assert data.itemStatus["sc24b8f"].color == QColor(182, 60, 0)
|
||||
assert data.itemStatus["s90e6c9"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["sd51c5b"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s8ae72a"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58)
|
||||
assert data.itemStatus["sf12341"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemStatus["sf24ce6"].color.getRgb() == (200, 50, 0, 255)
|
||||
assert data.itemStatus["sc24b8f"].color.getRgb() == (182, 60, 0, 255)
|
||||
assert data.itemStatus["s90e6c9"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["sd51c5b"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s8ae72a"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s78ea90"].color.getRgb() == (58, 180, 58, 255)
|
||||
|
||||
assert data.itemImport["ia857f0"].color == QColor(100, 100, 100)
|
||||
assert data.itemImport["i4a1d39"].color == QColor(220, 138, 221)
|
||||
assert data.itemImport["icfb3a5"].color == QColor(220, 138, 221)
|
||||
assert data.itemImport["i2d7a54"].color == QColor(220, 138, 221)
|
||||
assert data.itemImport["i56be10"].color == QColor(220, 138, 221)
|
||||
assert data.itemImport["ia857f0"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemImport["icfb3a5"].color.getRgb() == (220, 138, 221, 255)
|
||||
assert data.itemImport["i2d7a54"].color.getRgb() == (220, 138, 221, 255)
|
||||
assert data.itemImport["i56be10"].color.getRgb() == (220, 138, 221, 255)
|
||||
|
||||
assert data.itemStatus["sf12341"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["sf24ce6"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["sc24b8f"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s90e6c9"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["sd51c5b"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s8ae72a"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s78ea90"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemImport["ia857f0"].theme == CUSTOM_COL
|
||||
assert data.itemImport["icfb3a5"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i2d7a54"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i56be10"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE
|
||||
assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE
|
||||
@@ -305,18 +316,31 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockGUI, mockRnd):
|
||||
assert data.itemImport["i000009"].name == "Major"
|
||||
assert data.itemImport["i00000a"].name == "Main"
|
||||
|
||||
assert data.itemStatus["s000000"].color == QColor(100, 100, 100)
|
||||
assert data.itemStatus["s000001"].color == QColor(200, 50, 0)
|
||||
assert data.itemStatus["s000002"].color == QColor(182, 60, 0)
|
||||
assert data.itemStatus["s000003"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000004"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000005"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000006"].color == QColor(58, 180, 58)
|
||||
assert data.itemStatus["s000000"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemStatus["s000001"].color.getRgb() == (200, 50, 0, 255)
|
||||
assert data.itemStatus["s000002"].color.getRgb() == (182, 60, 0, 255)
|
||||
assert data.itemStatus["s000003"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000004"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000005"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000006"].color.getRgb() == (58, 180, 58, 255)
|
||||
|
||||
assert data.itemImport["i000007"].color == QColor(100, 100, 100)
|
||||
assert data.itemImport["i000008"].color == QColor(0, 122, 188)
|
||||
assert data.itemImport["i000009"].color == QColor(21, 0, 180)
|
||||
assert data.itemImport["i00000a"].color == QColor(117, 0, 175)
|
||||
assert data.itemImport["i000007"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemImport["i000008"].color.getRgb() == (0, 122, 188, 255)
|
||||
assert data.itemImport["i000009"].color.getRgb() == (21, 0, 180, 255)
|
||||
assert data.itemImport["i00000a"].color.getRgb() == (117, 0, 175, 255)
|
||||
|
||||
assert data.itemStatus["s000000"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000001"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000002"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000003"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000004"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000005"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000006"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemImport["i000007"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i000008"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i000009"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i00000a"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
|
||||
assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
|
||||
@@ -450,18 +474,31 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockGUI, mockRnd):
|
||||
assert data.itemImport["i000009"].name == "Major"
|
||||
assert data.itemImport["i00000a"].name == "Main"
|
||||
|
||||
assert data.itemStatus["s000000"].color == QColor(100, 100, 100)
|
||||
assert data.itemStatus["s000001"].color == QColor(200, 50, 0)
|
||||
assert data.itemStatus["s000002"].color == QColor(182, 60, 0)
|
||||
assert data.itemStatus["s000003"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000004"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000005"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000006"].color == QColor(58, 180, 58)
|
||||
assert data.itemStatus["s000000"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemStatus["s000001"].color.getRgb() == (200, 50, 0, 255)
|
||||
assert data.itemStatus["s000002"].color.getRgb() == (182, 60, 0, 255)
|
||||
assert data.itemStatus["s000003"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000004"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000005"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000006"].color.getRgb() == (58, 180, 58, 255)
|
||||
|
||||
assert data.itemImport["i000007"].color == QColor(100, 100, 100)
|
||||
assert data.itemImport["i000008"].color == QColor(0, 122, 188)
|
||||
assert data.itemImport["i000009"].color == QColor(21, 0, 180)
|
||||
assert data.itemImport["i00000a"].color == QColor(117, 0, 175)
|
||||
assert data.itemImport["i000007"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemImport["i000008"].color.getRgb() == (0, 122, 188, 255)
|
||||
assert data.itemImport["i000009"].color.getRgb() == (21, 0, 180, 255)
|
||||
assert data.itemImport["i00000a"].color.getRgb() == (117, 0, 175, 255)
|
||||
|
||||
assert data.itemStatus["s000000"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000001"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000002"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000003"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000004"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000005"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000006"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemImport["i000007"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i000008"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i000009"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i00000a"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
|
||||
assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
|
||||
@@ -595,18 +632,31 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockGUI, mockRnd):
|
||||
assert data.itemImport["i000009"].name == "Major"
|
||||
assert data.itemImport["i00000a"].name == "Main"
|
||||
|
||||
assert data.itemStatus["s000000"].color == QColor(100, 100, 100)
|
||||
assert data.itemStatus["s000001"].color == QColor(200, 50, 0)
|
||||
assert data.itemStatus["s000002"].color == QColor(182, 60, 0)
|
||||
assert data.itemStatus["s000003"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000004"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000005"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000006"].color == QColor(58, 180, 58)
|
||||
assert data.itemStatus["s000000"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemStatus["s000001"].color.getRgb() == (200, 50, 0, 255)
|
||||
assert data.itemStatus["s000002"].color.getRgb() == (182, 60, 0, 255)
|
||||
assert data.itemStatus["s000003"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000004"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000005"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000006"].color.getRgb() == (58, 180, 58, 255)
|
||||
|
||||
assert data.itemImport["i000007"].color == QColor(100, 100, 100)
|
||||
assert data.itemImport["i000008"].color == QColor(0, 122, 188)
|
||||
assert data.itemImport["i000009"].color == QColor(21, 0, 180)
|
||||
assert data.itemImport["i00000a"].color == QColor(117, 0, 175)
|
||||
assert data.itemImport["i000007"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemImport["i000008"].color.getRgb() == (0, 122, 188, 255)
|
||||
assert data.itemImport["i000009"].color.getRgb() == (21, 0, 180, 255)
|
||||
assert data.itemImport["i00000a"].color.getRgb() == (117, 0, 175, 255)
|
||||
|
||||
assert data.itemStatus["s000000"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000001"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000002"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000003"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000004"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000005"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000006"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemImport["i000007"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i000008"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i000009"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i00000a"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
|
||||
assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
|
||||
@@ -743,18 +793,31 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockGUI, mockRnd):
|
||||
assert data.itemImport["i000009"].name == "Major"
|
||||
assert data.itemImport["i00000a"].name == "Main"
|
||||
|
||||
assert data.itemStatus["s000000"].color == QColor(100, 100, 100)
|
||||
assert data.itemStatus["s000001"].color == QColor(200, 50, 0)
|
||||
assert data.itemStatus["s000002"].color == QColor(182, 60, 0)
|
||||
assert data.itemStatus["s000003"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000004"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000005"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s000006"].color == QColor(58, 180, 58)
|
||||
assert data.itemStatus["s000000"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemStatus["s000001"].color.getRgb() == (200, 50, 0, 255)
|
||||
assert data.itemStatus["s000002"].color.getRgb() == (182, 60, 0, 255)
|
||||
assert data.itemStatus["s000003"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000004"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000005"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s000006"].color.getRgb() == (58, 180, 58, 255)
|
||||
|
||||
assert data.itemImport["i000007"].color == QColor(100, 100, 100)
|
||||
assert data.itemImport["i000008"].color == QColor(0, 122, 188)
|
||||
assert data.itemImport["i000009"].color == QColor(21, 0, 180)
|
||||
assert data.itemImport["i00000a"].color == QColor(117, 0, 175)
|
||||
assert data.itemImport["i000007"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemImport["i000008"].color.getRgb() == (0, 122, 188, 255)
|
||||
assert data.itemImport["i000009"].color.getRgb() == (21, 0, 180, 255)
|
||||
assert data.itemImport["i00000a"].color.getRgb() == (117, 0, 175, 255)
|
||||
|
||||
assert data.itemStatus["s000000"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000001"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000002"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000003"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000004"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000005"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s000006"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemImport["i000007"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i000008"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i000009"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i00000a"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemStatus["s000000"].shape == nwStatusShape.SQUARE
|
||||
assert data.itemStatus["s000001"].shape == nwStatusShape.SQUARE
|
||||
@@ -891,18 +954,31 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockGUI, mockRnd):
|
||||
assert data.itemImport["i2d7a54"].name == "Major"
|
||||
assert data.itemImport["i56be10"].name == "Main"
|
||||
|
||||
assert data.itemStatus["sf12341"].color == QColor(100, 100, 100)
|
||||
assert data.itemStatus["sf24ce6"].color == QColor(200, 50, 0)
|
||||
assert data.itemStatus["sc24b8f"].color == QColor(182, 60, 0)
|
||||
assert data.itemStatus["s90e6c9"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["sd51c5b"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s8ae72a"].color == QColor(193, 129, 0)
|
||||
assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58)
|
||||
assert data.itemStatus["sf12341"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemStatus["sf24ce6"].color.getRgb() == (200, 50, 0, 255)
|
||||
assert data.itemStatus["sc24b8f"].color.getRgb() == (182, 60, 0, 255)
|
||||
assert data.itemStatus["s90e6c9"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["sd51c5b"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s8ae72a"].color.getRgb() == (193, 129, 0, 255)
|
||||
assert data.itemStatus["s78ea90"].color.getRgb() == (58, 180, 58, 255)
|
||||
|
||||
assert data.itemImport["ia857f0"].color == QColor(100, 100, 100)
|
||||
assert data.itemImport["icfb3a5"].color == QColor(0, 122, 188)
|
||||
assert data.itemImport["i2d7a54"].color == QColor(21, 0, 180)
|
||||
assert data.itemImport["i56be10"].color == QColor(117, 0, 175)
|
||||
assert data.itemImport["ia857f0"].color.getRgb() == (100, 100, 100, 255)
|
||||
assert data.itemImport["icfb3a5"].color.getRgb() == (0, 122, 188, 255)
|
||||
assert data.itemImport["i2d7a54"].color.getRgb() == (21, 0, 180, 255)
|
||||
assert data.itemImport["i56be10"].color.getRgb() == (117, 0, 175, 255)
|
||||
|
||||
assert data.itemStatus["sf12341"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["sf24ce6"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["sc24b8f"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s90e6c9"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["sd51c5b"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s8ae72a"].theme == CUSTOM_COL
|
||||
assert data.itemStatus["s78ea90"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemImport["ia857f0"].theme == CUSTOM_COL
|
||||
assert data.itemImport["icfb3a5"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i2d7a54"].theme == CUSTOM_COL
|
||||
assert data.itemImport["i56be10"].theme == CUSTOM_COL
|
||||
|
||||
assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE
|
||||
assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE
|
||||
|
||||
@@ -24,7 +24,7 @@ import pytest
|
||||
|
||||
from PyQt6.QtGui import QColor, QIcon
|
||||
|
||||
from novelwriter.core.status import NWStatus, StatusEntry, _ShapeCache
|
||||
from novelwriter.core.status import CUSTOM_COL, NWStatus, StatusEntry, _ShapeCache
|
||||
from novelwriter.enum import nwStatusShape
|
||||
|
||||
from tests.tools import C
|
||||
@@ -38,11 +38,12 @@ def testCoreStatus_StatusEntry():
|
||||
"""Test the StatusEntry class."""
|
||||
color = QColor(255, 0, 0)
|
||||
icon = NWStatus.createIcon(24, color, nwStatusShape.CIRCLE)
|
||||
entry = StatusEntry("Test", color, nwStatusShape.CIRCLE, icon, 42)
|
||||
entry = StatusEntry("Test", color, CUSTOM_COL, nwStatusShape.CIRCLE, icon, 42)
|
||||
|
||||
# Check values
|
||||
assert entry.name == "Test"
|
||||
assert entry.color is color
|
||||
assert entry.theme == CUSTOM_COL
|
||||
assert entry.shape == nwStatusShape.CIRCLE
|
||||
assert entry.icon is icon
|
||||
assert entry.count == 42
|
||||
@@ -55,6 +56,7 @@ def testCoreStatus_StatusEntry():
|
||||
assert other.name == "Test"
|
||||
assert other.color is not color # Not the same object
|
||||
assert other.color == color # But same colours
|
||||
assert entry.theme == CUSTOM_COL
|
||||
assert other.shape == nwStatusShape.CIRCLE
|
||||
assert other.icon is not icon # Not the same icon, but a copy
|
||||
assert other.count == 42
|
||||
@@ -73,14 +75,14 @@ def testCoreStatus_Internal(mockGUI, mockRnd):
|
||||
assert nStatus._newKey() == statusKeys[1]
|
||||
|
||||
# Key collision, should move to key 3
|
||||
nStatus.add(statusKeys[2], "Crash", (0, 0, 0), "SQUARE", 0)
|
||||
nStatus.add(statusKeys[2], "Crash", "#000000", "SQUARE", 0)
|
||||
assert nStatus._newKey() == statusKeys[3]
|
||||
|
||||
assert nImport._newKey() == importKeys[0]
|
||||
assert nImport._newKey() == importKeys[1]
|
||||
|
||||
# Key collision, should move to key 3
|
||||
nImport.add(importKeys[2], "Crash", (0, 0, 0), "SQUARE", 0)
|
||||
nImport.add(importKeys[2], "Crash", "#000000", "SQUARE", 0)
|
||||
assert nImport._newKey() == importKeys[3]
|
||||
|
||||
# Check Key
|
||||
@@ -119,14 +121,14 @@ def testCoreStatus_Internal(mockGUI, mockRnd):
|
||||
def testCoreStatus_Iterator(mockGUI, mockRnd):
|
||||
"""Test the iterator functions of the NWStatus class."""
|
||||
nStatus = NWStatus(NWStatus.STATUS)
|
||||
nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0)
|
||||
nStatus.add(None, "Note", (200, 50, 0), "CIRCLE", 1)
|
||||
nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 2)
|
||||
nStatus.add(None, "Finished", (50, 200, 0), "CIRCLE", 3)
|
||||
nStatus.add(None, "New", "#646464", "SQUARE", 0)
|
||||
nStatus.add(None, "Note", "#ff3f00", "CIRCLE", 1)
|
||||
nStatus.add(None, "Draft", "#ffaf00", "SQUARE", 2)
|
||||
nStatus.add(None, "Finished", "#3fff00", "CIRCLE", 3)
|
||||
|
||||
# Direct access
|
||||
entry = nStatus[statusKeys[0]]
|
||||
assert entry.color == QColor(100, 100, 100)
|
||||
assert entry.color.getRgb() == (100, 100, 100, 255)
|
||||
assert entry.name == "New"
|
||||
assert entry.count == 0
|
||||
assert isinstance(entry.icon, QIcon)
|
||||
@@ -146,8 +148,8 @@ def testCoreStatus_Iterator(mockGUI, mockRnd):
|
||||
]
|
||||
|
||||
# Content : Colours
|
||||
assert [e.color for _, e in nStatus.iterItems()] == [
|
||||
QColor(100, 100, 100), QColor(200, 50, 0), QColor(200, 150, 0), QColor(50, 200, 0)
|
||||
assert [e.color.getRgb() for _, e in nStatus.iterItems()] == [
|
||||
(100, 100, 100, 255), (255, 63, 0, 255), (255, 175, 0, 255), (63, 255, 0, 255)
|
||||
]
|
||||
|
||||
# Content : Shape
|
||||
@@ -168,27 +170,31 @@ def testCoreStatus_Entries(mockGUI, mockRnd):
|
||||
# ===
|
||||
|
||||
# Has a key
|
||||
nStatus.add(statusKeys[0], "Entry 1", (200, 100, 50), "SQUARE", 0)
|
||||
nStatus.add(statusKeys[0], "Entry 1", "200, 100, 50", "SQUARE", 0)
|
||||
assert nStatus[statusKeys[0]].name == "Entry 1"
|
||||
assert nStatus[statusKeys[0]].color == QColor(200, 100, 50)
|
||||
assert nStatus[statusKeys[0]].color.getRgb() == (200, 100, 50, 255)
|
||||
assert nStatus[statusKeys[0]].theme == CUSTOM_COL
|
||||
assert nStatus[statusKeys[0]].shape == nwStatusShape.SQUARE
|
||||
|
||||
# Doesn't have a key
|
||||
nStatus.add(None, "Entry 2", (210, 110, 60), "SQUARE", 0)
|
||||
nStatus.add(None, "Entry 2", "210, 110, 60", "SQUARE", 0)
|
||||
assert nStatus[statusKeys[1]].name == "Entry 2"
|
||||
assert nStatus[statusKeys[1]].color == QColor(210, 110, 60)
|
||||
assert nStatus[statusKeys[1]].color.getRgb() == (210, 110, 60, 255)
|
||||
assert nStatus[statusKeys[1]].theme == CUSTOM_COL
|
||||
assert nStatus[statusKeys[1]].shape == nwStatusShape.SQUARE
|
||||
|
||||
# Wrong colour spec, unknown shape
|
||||
nStatus.add(None, "Entry 3", "what?", "", 0) # type: ignore
|
||||
nStatus.add(None, "Entry 3", "what?", "", 0)
|
||||
assert nStatus[statusKeys[2]].name == "Entry 3"
|
||||
assert nStatus[statusKeys[2]].color == QColor(100, 100, 100)
|
||||
assert nStatus[statusKeys[2]].color.getRgb() == (0, 0, 0, 255)
|
||||
assert nStatus[statusKeys[2]].theme == CUSTOM_COL
|
||||
assert nStatus[statusKeys[2]].shape == nwStatusShape.SQUARE
|
||||
|
||||
# Wrong colour count
|
||||
nStatus.add(None, "Entry 4", (10, 20), "CIRCLE", 0) # type: ignore
|
||||
# Wrong colour definition
|
||||
nStatus.add(None, "Entry 4", "#stuff#", "CIRCLE", 0)
|
||||
assert nStatus[statusKeys[3]].name == "Entry 4"
|
||||
assert nStatus[statusKeys[3]].color == QColor(100, 100, 100)
|
||||
assert nStatus[statusKeys[3]].color.getRgb() == (0, 0, 0, 255)
|
||||
assert nStatus[statusKeys[3]].theme == CUSTOM_COL
|
||||
assert nStatus[statusKeys[3]].shape == nwStatusShape.CIRCLE
|
||||
|
||||
# Check
|
||||
@@ -202,8 +208,6 @@ def testCoreStatus_Entries(mockGUI, mockRnd):
|
||||
assert nStatus.check("s987654") == statusKeys[0]
|
||||
|
||||
# Name Access
|
||||
# ===========
|
||||
|
||||
assert nStatus[statusKeys[0]].name == "Entry 1"
|
||||
assert nStatus[statusKeys[1]].name == "Entry 2"
|
||||
assert nStatus[statusKeys[2]].name == "Entry 3"
|
||||
@@ -211,17 +215,20 @@ def testCoreStatus_Entries(mockGUI, mockRnd):
|
||||
assert nStatus["blablabla"].name == "Entry 1"
|
||||
|
||||
# Colour Access
|
||||
# =============
|
||||
assert nStatus[statusKeys[0]].color.getRgb() == (200, 100, 50, 255)
|
||||
assert nStatus[statusKeys[1]].color.getRgb() == (210, 110, 60, 255)
|
||||
assert nStatus[statusKeys[2]].color.getRgb() == (0, 0, 0, 255)
|
||||
assert nStatus[statusKeys[3]].color.getRgb() == (0, 0, 0, 255)
|
||||
assert nStatus["blablabla"].color.getRgb() == (200, 100, 50, 255)
|
||||
|
||||
assert nStatus[statusKeys[0]].color == QColor(200, 100, 50)
|
||||
assert nStatus[statusKeys[1]].color == QColor(210, 110, 60)
|
||||
assert nStatus[statusKeys[2]].color == QColor(100, 100, 100)
|
||||
assert nStatus[statusKeys[3]].color == QColor(100, 100, 100)
|
||||
assert nStatus["blablabla"].color == QColor(200, 100, 50)
|
||||
# Theme Access
|
||||
assert nStatus[statusKeys[0]].theme == CUSTOM_COL
|
||||
assert nStatus[statusKeys[1]].theme == CUSTOM_COL
|
||||
assert nStatus[statusKeys[2]].theme == CUSTOM_COL
|
||||
assert nStatus[statusKeys[3]].theme == CUSTOM_COL
|
||||
assert nStatus["blablabla"].theme == CUSTOM_COL
|
||||
|
||||
# Icon Access
|
||||
# ===========
|
||||
|
||||
assert isinstance(nStatus[statusKeys[0]].icon, QIcon)
|
||||
assert isinstance(nStatus[statusKeys[1]].icon, QIcon)
|
||||
assert isinstance(nStatus[statusKeys[2]].icon, QIcon)
|
||||
@@ -229,8 +236,6 @@ def testCoreStatus_Entries(mockGUI, mockRnd):
|
||||
assert isinstance(nStatus["blablabla"].icon, QIcon)
|
||||
|
||||
# Shape Access
|
||||
# ============
|
||||
|
||||
assert nStatus[statusKeys[0]].shape == nwStatusShape.SQUARE
|
||||
assert nStatus[statusKeys[1]].shape == nwStatusShape.SQUARE
|
||||
assert nStatus[statusKeys[2]].shape == nwStatusShape.SQUARE
|
||||
@@ -319,13 +324,64 @@ def testCoreStatus_Entries(mockGUI, mockRnd):
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreStatus_Pack(mockGUI, mockRnd):
|
||||
def testCoreStatus_RefreshIcons_Theme(mockGUIwithTheme, mockRnd):
|
||||
"""Test refreshing the icons with theme colours."""
|
||||
nStatus = NWStatus(NWStatus.STATUS)
|
||||
nStatus.add(None, "New", "default", "SQUARE", 0)
|
||||
nStatus.add(None, "Note", "red", "CIRCLE", 0)
|
||||
nStatus.add(None, "Draft", "yellow", "SQUARE", 0)
|
||||
nStatus.add(None, "Finished", "green", "SQUARE", 0)
|
||||
|
||||
iconsA = [nStatus[statusKeys[i]].icon for i in range(4)]
|
||||
themeA = [nStatus[statusKeys[i]].theme for i in range(4)]
|
||||
|
||||
# Refreshing the icons should generate new ones
|
||||
nStatus.refreshIcons()
|
||||
iconsB = [nStatus[statusKeys[i]].icon for i in range(4)]
|
||||
themeB = [nStatus[statusKeys[i]].theme for i in range(4)]
|
||||
|
||||
for before, after in zip(iconsA, iconsB, strict=False):
|
||||
assert before is not after
|
||||
|
||||
assert themeA == themeB
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreStatus_RefreshIcons_Custom(mockGUIwithTheme, mockRnd):
|
||||
"""Test refreshing the icons with custom colours."""
|
||||
nStatus = NWStatus(NWStatus.STATUS)
|
||||
nStatus.add(None, "New", "#707070", "SQUARE", 0)
|
||||
nStatus.add(None, "Note", "#ff0000", "CIRCLE", 0)
|
||||
nStatus.add(None, "Draft", "#ffff00", "SQUARE", 0)
|
||||
nStatus.add(None, "Finished", "#00ff00", "SQUARE", 0)
|
||||
|
||||
iconsA = [nStatus[statusKeys[i]].icon for i in range(4)]
|
||||
themeA = [nStatus[statusKeys[i]].theme for i in range(4)]
|
||||
colorA = [nStatus[statusKeys[i]].color.getRgb() for i in range(4)]
|
||||
|
||||
# Refreshing the icons should generate new ones
|
||||
nStatus.refreshIcons()
|
||||
iconsB = [nStatus[statusKeys[i]].icon for i in range(4)]
|
||||
themeB = [nStatus[statusKeys[i]].theme for i in range(4)]
|
||||
colorB = [nStatus[statusKeys[i]].color.getRgb() for i in range(4)]
|
||||
|
||||
for before, after in zip(iconsA, iconsB, strict=False):
|
||||
assert before is not after
|
||||
|
||||
# But they should have the same colour value (#2452)
|
||||
assert themeA == [CUSTOM_COL, CUSTOM_COL, CUSTOM_COL, CUSTOM_COL]
|
||||
assert themeB == [CUSTOM_COL, CUSTOM_COL, CUSTOM_COL, CUSTOM_COL]
|
||||
assert colorA == colorB
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testCoreStatus_Pack(mockGUIwithTheme, mockRnd):
|
||||
"""Test data packing of the NWStatus class."""
|
||||
nStatus = NWStatus(NWStatus.STATUS)
|
||||
nStatus.add(None, "New", (100, 100, 100), "SQUARE", 0)
|
||||
nStatus.add(None, "Note", (200, 50, 0), "CIRCLE", 0)
|
||||
nStatus.add(None, "Draft", (200, 150, 0), "SQUARE", 0)
|
||||
nStatus.add(None, "Finished", (50, 200, 0), "SQUARE", 0)
|
||||
nStatus.add(None, "New", "#646464", "SQUARE", 0)
|
||||
nStatus.add(None, "Note", "#c83200", "CIRCLE", 0)
|
||||
nStatus.add(None, "Draft", "#c89600", "SQUARE", 0)
|
||||
nStatus.add(None, "Finished", "default", "SQUARE", 0)
|
||||
|
||||
countTo = [3, 5, 7, 9]
|
||||
for i, n in enumerate(countTo):
|
||||
@@ -337,33 +393,25 @@ def testCoreStatus_Pack(mockGUI, mockRnd):
|
||||
("New", {
|
||||
"key": statusKeys[0],
|
||||
"count": "3",
|
||||
"red": "100",
|
||||
"green": "100",
|
||||
"blue": "100",
|
||||
"color": "#646464",
|
||||
"shape": "SQUARE",
|
||||
}),
|
||||
("Note", {
|
||||
"key": statusKeys[1],
|
||||
"count": "5",
|
||||
"red": "200",
|
||||
"green": "50",
|
||||
"blue": "0",
|
||||
"color": "#c83200",
|
||||
"shape": "CIRCLE",
|
||||
}),
|
||||
("Draft", {
|
||||
"key": statusKeys[2],
|
||||
"count": "7",
|
||||
"red": "200",
|
||||
"green": "150",
|
||||
"blue": "0",
|
||||
"color": "#c89600",
|
||||
"shape": "SQUARE",
|
||||
}),
|
||||
("Finished", {
|
||||
"key": statusKeys[3],
|
||||
"count": "9",
|
||||
"red": "50",
|
||||
"green": "200",
|
||||
"blue": "0",
|
||||
"color": "default",
|
||||
"shape": "SQUARE",
|
||||
}),
|
||||
]
|
||||
|
||||
@@ -27,10 +27,11 @@ from PyQt6.QtGui import QAction, QFont, QFontDatabase, QKeyEvent
|
||||
from PyQt6.QtWidgets import QFileDialog, QFontDialog
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.config import DEF_GUI
|
||||
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_TREECOL
|
||||
from novelwriter.constants import nwUnicode
|
||||
from novelwriter.dialogs.preferences import GuiPreferences
|
||||
from novelwriter.dialogs.quotes import GuiQuoteSelect
|
||||
from novelwriter.gui.theme import ThemeEntry
|
||||
from novelwriter.types import QtDialogCancel, QtDialogSave, QtModNone
|
||||
|
||||
KEY_DELAY = 1
|
||||
@@ -55,15 +56,11 @@ def testDlgPreferences_Main(qtbot, monkeypatch, nwGUI, tstPaths):
|
||||
assert "en_GB" in languages
|
||||
|
||||
# Check GUI Themes
|
||||
themes = [prefs.guiTheme.itemData(i) for i in range(prefs.guiTheme.count())]
|
||||
assert len(themes) >= 5
|
||||
assert DEF_GUI in themes
|
||||
themes = [prefs.lightTheme.itemData(i) for i in range(prefs.lightTheme.count())]
|
||||
assert DEF_GUI_LIGHT in themes
|
||||
|
||||
# Check GUI Syntax
|
||||
syntax = [prefs.guiSyntax.itemData(i) for i in range(prefs.guiSyntax.count())]
|
||||
assert len(syntax) >= 10
|
||||
assert "default_dark" in syntax
|
||||
assert "default_light" in syntax
|
||||
themes = [prefs.darkTheme.itemData(i) for i in range(prefs.darkTheme.count())]
|
||||
assert DEF_GUI_DARK in themes
|
||||
|
||||
# Check Spell Checking
|
||||
spelling = [prefs.spellLanguage.itemData(i) for i in range(prefs.spellLanguage.count())]
|
||||
@@ -124,7 +121,7 @@ def testDlgPreferences_Actions(qtbot, monkeypatch, nwGUI):
|
||||
button = prefs.buttonBox.button(QtDialogSave)
|
||||
assert button is not None
|
||||
button.click()
|
||||
assert signal.args == [False, False, False, False]
|
||||
assert len(signal.args) == 4
|
||||
|
||||
# Check Close Button
|
||||
prefs.show()
|
||||
@@ -151,6 +148,12 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
||||
(fncPath / "nw_en_US.qm").touch()
|
||||
(fncPath / "project_en_US.json").touch()
|
||||
CONFIG._nwLangPath = fncPath
|
||||
SHARED.theme._allThemes = {
|
||||
"theme1": ThemeEntry(name="Theme 1", dark=False, path=fncPath),
|
||||
"theme2": ThemeEntry(name="Theme 2", dark=False, path=fncPath),
|
||||
"theme3": ThemeEntry(name="Theme 3", dark=True, path=fncPath),
|
||||
"theme4": ThemeEntry(name="Theme 4", dark=True, path=fncPath),
|
||||
}
|
||||
|
||||
prefs = GuiPreferences(nwGUI)
|
||||
with qtbot.waitExposed(prefs):
|
||||
@@ -158,7 +161,8 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
||||
|
||||
# Appearance
|
||||
prefs.guiLocale.setCurrentIndex(prefs.guiLocale.findData("en_US"))
|
||||
prefs.guiTheme.setCurrentIndex(prefs.guiTheme.findData("default_dark"))
|
||||
prefs.lightTheme.setCurrentIndex(prefs.lightTheme.findData("theme1"))
|
||||
prefs.darkTheme.setCurrentIndex(prefs.darkTheme.findData("theme3"))
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
|
||||
prefs.nativeFont.setChecked(True) # Use OS font dialog
|
||||
@@ -169,14 +173,14 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
||||
prefs.useCharCount.setChecked(True)
|
||||
|
||||
assert CONFIG.guiLocale != "en_US"
|
||||
assert CONFIG.guiTheme != "default_dark"
|
||||
assert CONFIG.lightTheme == "default_light"
|
||||
assert CONFIG.darkTheme == "default_dark"
|
||||
assert CONFIG.guiFont.family() != ""
|
||||
assert CONFIG.hideVScroll is False
|
||||
assert CONFIG.hideHScroll is False
|
||||
assert CONFIG.useCharCount is False
|
||||
|
||||
# Document Style
|
||||
prefs.guiSyntax.setCurrentIndex(prefs.guiSyntax.findData("default_dark"))
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
|
||||
prefs.nativeFont.setChecked(False) # Use Qt font dialog
|
||||
@@ -185,7 +189,6 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
||||
prefs.showFullPath.setChecked(False)
|
||||
prefs.incNotesWCount.setChecked(False)
|
||||
|
||||
assert CONFIG.guiSyntax != "default_dark"
|
||||
assert CONFIG.textFont.family() != ""
|
||||
assert CONFIG.showFullPath is True
|
||||
assert CONFIG.incNotesWCount is True
|
||||
@@ -195,7 +198,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
||||
prefs.iconColDocs.setChecked(True)
|
||||
prefs.emphLabels.setChecked(False)
|
||||
|
||||
assert CONFIG.iconColTree == "theme"
|
||||
assert CONFIG.iconColTree == DEF_TREECOL
|
||||
assert CONFIG.iconColDocs is False
|
||||
assert CONFIG.emphLabels is True
|
||||
|
||||
@@ -247,22 +250,24 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
||||
prefs.spellLanguage.setCurrentIndex(prefs.spellLanguage.findData("de"))
|
||||
prefs.autoSelect.setChecked(False)
|
||||
prefs.cursorWidth.setValue(5)
|
||||
prefs.lineHighlight.setChecked(False)
|
||||
prefs.showTabsNSpaces.setChecked(True)
|
||||
prefs.showLineEndings.setChecked(True)
|
||||
|
||||
assert CONFIG.spellLanguage != "de"
|
||||
assert CONFIG.autoSelect is True
|
||||
assert CONFIG.cursorWidth == 1
|
||||
assert CONFIG.lineHighlight is True
|
||||
assert CONFIG.showTabsNSpaces is False
|
||||
assert CONFIG.showLineEndings is False
|
||||
|
||||
# Editor Scrolling
|
||||
prefs.scrollPastEnd.setChecked(False)
|
||||
prefs.autoScroll.setChecked(True)
|
||||
prefs.autoScroll.setChecked(False)
|
||||
prefs.autoScrollPos.stepUp()
|
||||
|
||||
assert CONFIG.scrollPastEnd is True
|
||||
assert CONFIG.autoScroll is False
|
||||
assert CONFIG.autoScroll is True
|
||||
assert CONFIG.autoScrollPos == 30
|
||||
|
||||
# Text Highlighting
|
||||
@@ -347,14 +352,14 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
||||
|
||||
# Appearance
|
||||
assert CONFIG.guiLocale == "en_US"
|
||||
assert CONFIG.guiTheme == "default_dark"
|
||||
assert CONFIG.lightTheme == "theme1"
|
||||
assert CONFIG.darkTheme == "theme3"
|
||||
assert CONFIG.guiFont == QFont()
|
||||
assert CONFIG.hideVScroll is True
|
||||
assert CONFIG.hideHScroll is True
|
||||
assert CONFIG.useCharCount is True
|
||||
|
||||
# Document Style
|
||||
assert CONFIG.guiSyntax == "default_dark"
|
||||
assert CONFIG.textFont == QFont()
|
||||
assert CONFIG.showFullPath is False
|
||||
assert CONFIG.incNotesWCount is False
|
||||
@@ -390,12 +395,13 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
|
||||
assert CONFIG.spellLanguage == "de"
|
||||
assert CONFIG.autoSelect is False
|
||||
assert CONFIG.cursorWidth == 5
|
||||
assert CONFIG.lineHighlight is False
|
||||
assert CONFIG.showTabsNSpaces is True
|
||||
assert CONFIG.showLineEndings is True
|
||||
|
||||
# Editor Scrolling
|
||||
assert CONFIG.scrollPastEnd is False
|
||||
assert CONFIG.autoScroll is True
|
||||
assert CONFIG.autoScroll is False
|
||||
assert CONFIG.autoScrollPos == 31
|
||||
|
||||
# Text Highlighting
|
||||
|
||||
@@ -214,22 +214,22 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
|
||||
|
||||
assert update[0][0] == C.sNew
|
||||
assert update[0][1].name == "New"
|
||||
assert update[0][1].color == QColor(120, 120, 120)
|
||||
assert update[0][1].color.getRgb() == (108, 108, 108, 255)
|
||||
assert update[0][1].shape == nwStatusShape.STAR
|
||||
|
||||
assert update[1][0] == C.sDraft
|
||||
assert update[1][1].name == "Draft"
|
||||
assert update[1][1].color == QColor(143, 240, 164)
|
||||
assert update[1][1].color.getRgb() == (163, 156, 52, 255)
|
||||
assert update[1][1].shape == nwStatusShape.CIRCLE_T
|
||||
|
||||
assert update[2][0] == C.sFinished
|
||||
assert update[2][1].name == "Finished"
|
||||
assert update[2][1].color == QColor(249, 240, 107)
|
||||
assert update[2][1].color.getRgb() == (41, 102, 41, 255)
|
||||
assert update[2][1].shape == nwStatusShape.STAR
|
||||
|
||||
assert update[3][0] is None
|
||||
assert update[3][1].name == "Final"
|
||||
assert update[3][1].color == QColor(20, 30, 40)
|
||||
assert update[3][1].color.getRgb() == (20, 30, 40, 255)
|
||||
assert update[3][1].shape == nwStatusShape.CIRCLE
|
||||
|
||||
# Move items, none selected -> no change
|
||||
@@ -289,22 +289,22 @@ def testDlgProjSettings_StatusImport(qtbot, monkeypatch, nwGUI, projPath, mockRn
|
||||
|
||||
assert update[0][0] == C.iNew
|
||||
assert update[0][1].name == "New"
|
||||
assert update[0][1].color == QColor(120, 120, 120)
|
||||
assert update[0][1].color.getRgb() == (179, 90, 179, 255)
|
||||
assert update[0][1].shape == nwStatusShape.SQUARE
|
||||
|
||||
assert update[1][0] == C.iMajor
|
||||
assert update[1][1].name == "Major"
|
||||
assert update[1][1].color == QColor(220, 138, 221)
|
||||
assert update[1][1].color.getRgb() == (179, 90, 179, 255)
|
||||
assert update[1][1].shape == nwStatusShape.BLOCK_3
|
||||
|
||||
assert update[2][0] == C.iMain
|
||||
assert update[2][1].name == "Main"
|
||||
assert update[2][1].color == QColor(220, 138, 221)
|
||||
assert update[2][1].color.getRgb() == (179, 90, 179, 255)
|
||||
assert update[2][1].shape == nwStatusShape.BLOCK_4
|
||||
|
||||
assert update[3][0] is None
|
||||
assert update[3][1].name == "Final"
|
||||
assert update[3][1].color == QColor(20, 30, 40)
|
||||
assert update[3][1].color.getRgb() == (20, 30, 40, 255)
|
||||
assert update[3][1].shape == nwStatusShape.TRIANGLE
|
||||
|
||||
# Check Project
|
||||
@@ -354,10 +354,10 @@ def testDlgProjSettings_StatusImportExport(qtbot, monkeypatch, nwGUI, projPath,
|
||||
|
||||
assert expFile.is_file() is True
|
||||
assert expFile.read_text().split() == [
|
||||
"STAR,#787878,New",
|
||||
"TRIANGLE,#cdab8f,Note",
|
||||
"CIRCLE_T,#8ff0a4,Draft",
|
||||
"STAR,#f9f06b,Finished",
|
||||
"STAR,#6c6c6c,New",
|
||||
"TRIANGLE,#a62a2d,Note",
|
||||
"CIRCLE_T,#a39c34,Draft",
|
||||
"STAR,#296629,Finished",
|
||||
]
|
||||
|
||||
# Import Error
|
||||
|
||||
@@ -222,12 +222,24 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
||||
"</p>\n"
|
||||
)
|
||||
|
||||
# Keywords
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToHtml_ConvertMeta(mockGUI):
|
||||
"""Test meta data formats in the ToHtml class."""
|
||||
project = NWProject()
|
||||
html = ToHtml(project)
|
||||
html.initDocument()
|
||||
html._isNovel = True
|
||||
html._isFirst = True
|
||||
|
||||
# Keywords Disabled
|
||||
html.setKeywords(False)
|
||||
html._text = "@char: Bod, Jane\n"
|
||||
html.tokenizeText()
|
||||
html.doConvert()
|
||||
assert html._pages[-1] == ""
|
||||
|
||||
# Keywords Enabled
|
||||
html.setKeywords(True)
|
||||
html._text = "@char: Bod, Jane\n"
|
||||
html.tokenizeText()
|
||||
@@ -239,7 +251,8 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
||||
"<span style='color: #4271ae'><a href='#tag_jane'>Jane</a></span></p>\n"
|
||||
)
|
||||
|
||||
# Tags
|
||||
# Tags w/Colour
|
||||
html.setStyles(True)
|
||||
html._text = "@tag: Bod\n"
|
||||
html.tokenizeText()
|
||||
html.doConvert()
|
||||
@@ -257,7 +270,25 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
||||
"<span style='color: #4271ae'>Nobody Owens</span></p>\n"
|
||||
)
|
||||
|
||||
# Multiple Keywords
|
||||
# Tags wo/Colour
|
||||
html.setStyles(False)
|
||||
html._text = "@tag: Bod\n"
|
||||
html.tokenizeText()
|
||||
html.doConvert()
|
||||
assert html._pages[-1] == (
|
||||
"<p class='meta meta-tag'><strong>Tag:</strong> <a name='tag_bod'>Bod</a></p>\n"
|
||||
)
|
||||
|
||||
html._text = "@tag: Bod | Nobody Owens\n"
|
||||
html.tokenizeText()
|
||||
html.doConvert()
|
||||
assert html._pages[-1] == (
|
||||
"<p class='meta meta-tag'><strong>Tag:</strong> "
|
||||
"<a name='tag_bod'>Bod</a> | Nobody Owens</p>\n"
|
||||
)
|
||||
|
||||
# Multiple Keywords w/Colour
|
||||
html.setStyles(True)
|
||||
html._isFirst = False
|
||||
html.setKeywords(True)
|
||||
html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
|
||||
@@ -276,6 +307,23 @@ def testFmtToHtml_ConvertParagraphs(mockGUI):
|
||||
"<span style='color: #4271ae'><a href='#tag_europe'>Europe</a></span></p>\n"
|
||||
)
|
||||
|
||||
# Multiple Keywords wo/Colour
|
||||
html.setStyles(False)
|
||||
html._isFirst = False
|
||||
html.setKeywords(True)
|
||||
html._text = "## Chapter\n\n@pov: Bod\n@plot: Main\n@location: Europe\n\n"
|
||||
html.tokenizeText()
|
||||
html.doConvert()
|
||||
assert html._pages[-1] == (
|
||||
"<h1 style='page-break-before: always;'>Chapter</h1>\n"
|
||||
"<p class='meta meta-pov' style='margin-bottom: 0;'>"
|
||||
"<strong>Point of View:</strong> <a href='#tag_bod'>Bod</a></p>\n"
|
||||
"<p class='meta meta-plot' style='margin-bottom: 0; margin-top: 0;'>"
|
||||
"<strong>Plot:</strong> <a href='#tag_main'>Main</a></p>\n"
|
||||
"<p class='meta meta-location' style='margin-top: 0;'>"
|
||||
"<strong>Locations:</strong> <a href='#tag_europe'>Europe</a></p>\n"
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testFmtToHtml_Alignment(mockGUI):
|
||||
@@ -485,17 +533,6 @@ def testFmtToHtml_ConvertDirect(mockGUI):
|
||||
# =========
|
||||
|
||||
html.setLinkHeadings(False)
|
||||
|
||||
# Align Left
|
||||
html.setStyles(False)
|
||||
html._blocks = [
|
||||
(BlockTyp.PART, tMeta, "A Title", [], BlockFmt.LEFT),
|
||||
]
|
||||
html.doConvert()
|
||||
assert html._pages[-1] == (
|
||||
"<h1 class='title'>A Title</h1>\n"
|
||||
)
|
||||
|
||||
html.setStyles(True)
|
||||
|
||||
# Align Left
|
||||
@@ -709,33 +746,57 @@ def testFmtToHtml_Save(mockGUI, fncPath):
|
||||
|
||||
# Check Files
|
||||
# ===========
|
||||
|
||||
# HTML
|
||||
hStyle = html.getStyleSheet()
|
||||
htmlDoc = (
|
||||
|
||||
# HTML w/Styles
|
||||
html.setStyles(True)
|
||||
htmlDocCSS = (
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"<title></title>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"<meta name='viewport' content='width=device-width, initial-scale=1'>\n"
|
||||
"<style>\n"
|
||||
"{htmlStyle:s}\n"
|
||||
"{style}\n"
|
||||
"</style>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"{bodyText:s}\n"
|
||||
"{body}\n"
|
||||
"</body>\n"
|
||||
"</html>\n"
|
||||
).format(
|
||||
htmlStyle="\n".join(hStyle),
|
||||
bodyText="".join(resText).rstrip()
|
||||
style="\n".join(hStyle),
|
||||
body="".join(resText).rstrip()
|
||||
)
|
||||
|
||||
saveFile = fncPath / "outFile.htm"
|
||||
html.saveDocument(saveFile)
|
||||
assert saveFile.read_text(encoding="utf-8") == htmlDoc
|
||||
assert saveFile.read_text(encoding="utf-8") == htmlDocCSS
|
||||
|
||||
# JSON + HTML
|
||||
# HTML wo/Styles
|
||||
html.setStyles(False)
|
||||
htmlDocCSS = (
|
||||
"<!DOCTYPE html>\n"
|
||||
"<html>\n"
|
||||
"<head>\n"
|
||||
"<title></title>\n"
|
||||
"<meta charset='utf-8'>\n"
|
||||
"</head>\n"
|
||||
"<body>\n"
|
||||
"{body}\n"
|
||||
"</body>\n"
|
||||
"</html>\n"
|
||||
).format(
|
||||
body="".join(resText).rstrip()
|
||||
)
|
||||
|
||||
saveFile = fncPath / "outFile.htm"
|
||||
html.saveDocument(saveFile)
|
||||
assert saveFile.read_text(encoding="utf-8") == htmlDocCSS
|
||||
|
||||
# JSON + HTML w/Styles
|
||||
html.setStyles(True)
|
||||
saveFile = fncPath / "outFile.json"
|
||||
html.saveDocument(saveFile)
|
||||
data = json.loads(saveFile.read_text(encoding="utf-8"))
|
||||
|
||||
@@ -783,7 +783,7 @@ def testFmtToken_MetaFormat(mockGUI):
|
||||
tokens._text = "% synopsis: The synopsis\n"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [(
|
||||
BlockTyp.COMMENT, "", "Synopsis: The synopsis", [
|
||||
BlockTyp.SUMMARY, "", "Synopsis: The synopsis", [
|
||||
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"),
|
||||
(9, TextFmt.COL_E, ""), (9, TextFmt.B_E, ""),
|
||||
(10, TextFmt.COL_B, "note"), (22, TextFmt.COL_E, "")
|
||||
@@ -800,13 +800,52 @@ def testFmtToken_MetaFormat(mockGUI):
|
||||
tokens._text = "% short: A short description\n"
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [(
|
||||
BlockTyp.COMMENT, "", "Short Description: A short description", [
|
||||
BlockTyp.SUMMARY, "", "Short Description: A short description", [
|
||||
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"),
|
||||
(18, TextFmt.COL_E, ""), (18, TextFmt.B_E, ""),
|
||||
(19, TextFmt.COL_B, "note"), (38, TextFmt.COL_E, ""),
|
||||
], BlockFmt.NONE
|
||||
)]
|
||||
|
||||
# Story Comments
|
||||
tokens.setCommentType(nwComment.STORY, False)
|
||||
tokens.setCommentType(nwComment.NOTE, False)
|
||||
tokens._text = (
|
||||
"%Story.Stuff: Stuff happens\n"
|
||||
"%Note.More: That stuff that happened\n"
|
||||
"%Note.Other: That other stuff that happened\n"
|
||||
)
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == []
|
||||
|
||||
tokens.setCommentType(nwComment.STORY, True)
|
||||
tokens.setCommentType(nwComment.NOTE, True)
|
||||
tokens._text = (
|
||||
"%Story.Stuff: Stuff happens\n"
|
||||
"%Note.More: That stuff that happened\n"
|
||||
"%Note.Other: That other stuff that happened\n"
|
||||
)
|
||||
tokens.tokenizeText()
|
||||
assert tokens._blocks == [(
|
||||
BlockTyp.NOTE, "", "Story Structure (Stuff): Stuff happens", [
|
||||
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"),
|
||||
(24, TextFmt.COL_E, ""), (24, TextFmt.B_E, ""),
|
||||
(25, TextFmt.COL_B, "note"), (38, TextFmt.COL_E, "")
|
||||
], BlockFmt.Z_BTM
|
||||
), (
|
||||
BlockTyp.NOTE, "", "Note (More): That stuff that happened", [
|
||||
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"),
|
||||
(12, TextFmt.COL_E, ""), (12, TextFmt.B_E, ""),
|
||||
(13, TextFmt.COL_B, "note"), (37, TextFmt.COL_E, "")
|
||||
], BlockFmt.Z_TOP | BlockFmt.Z_BTM
|
||||
), (
|
||||
BlockTyp.NOTE, "", "Note (Other): That other stuff that happened", [
|
||||
(0, TextFmt.B_B, ""), (0, TextFmt.COL_B, "modifier"),
|
||||
(13, TextFmt.COL_E, ""), (13, TextFmt.B_E, ""),
|
||||
(14, TextFmt.COL_B, "note"), (44, TextFmt.COL_E, "")
|
||||
], BlockFmt.Z_TOP
|
||||
)]
|
||||
|
||||
# Keyword
|
||||
tokens.setKeywords(False)
|
||||
tokens._text = "@char: Bod\n"
|
||||
@@ -919,6 +958,11 @@ def testFmtToken_ExtractFormats(mockGUI):
|
||||
assert text == "Text with strikethrough in it."
|
||||
assert fmt == [(10, TextFmt.D_B, ""), (23, TextFmt.D_E, "")]
|
||||
|
||||
# Plain mark
|
||||
text, fmt = tokens._extractFormats("Text with ==a highlight== in it.")
|
||||
assert text == "Text with a highlight in it."
|
||||
assert fmt == [(10, TextFmt.M_B, ""), (21, TextFmt.M_E, "")]
|
||||
|
||||
# Nested bold/italics
|
||||
text, fmt = tokens._extractFormats("Text with **bold and _italics_** in it.")
|
||||
assert text == "Text with bold and italics in it."
|
||||
@@ -1632,7 +1676,7 @@ def testFmtToken_TextIndent(mockGUI):
|
||||
]
|
||||
assert tokens._blocks == [
|
||||
(BlockTyp.HEAD3, TM1, "Scene Two", [], BlockFmt.NONE),
|
||||
(BlockTyp.COMMENT, "", "Synopsis: Stuff happens.", tFmt, BlockFmt.NONE),
|
||||
(BlockTyp.SUMMARY, "", "Synopsis: Stuff happens.", tFmt, BlockFmt.NONE),
|
||||
]
|
||||
assert tokens._noIndent is True
|
||||
|
||||
|
||||
@@ -514,32 +514,36 @@ def testGuiEditor_SpellChecking(qtbot, monkeypatch, nwGUI, projPath, ipsumText,
|
||||
# Run SpellCheck
|
||||
# ==============
|
||||
SHARED.project.data.setSpellCheck(True)
|
||||
LORAX = "Lorax\U0001F03A"
|
||||
|
||||
cursor = docEditor.textCursor()
|
||||
cursor.setPosition(16)
|
||||
data = cursor.block().userData()
|
||||
assert cursor.block().text().startswith("Lorem")
|
||||
assert isinstance(data, TextBlockData)
|
||||
data._spellErrors = [(0, 5)]
|
||||
data._spellErrors = [(0, 5, "Lorem")]
|
||||
|
||||
# No known position
|
||||
assert docEditor._qDocument.spellErrorAtPos(-1) == ("", -1, -1, [])
|
||||
assert docEditor._qDocument.spellErrorAtPos(-1) == ("", -1, [])
|
||||
|
||||
# With Suggestion
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(SHARED.spelling, "suggestWords", lambda *a: ["Lorax"])
|
||||
mp.setattr(SHARED.spelling, "suggestWords", lambda *a: [LORAX])
|
||||
|
||||
ctxMenu = getMenuForPos(docEditor, 16)
|
||||
assert ctxMenu is not None
|
||||
actions = [x.text() for x in ctxMenu.actions() if x.text()]
|
||||
assert "Spelling Suggestion(s)" in actions
|
||||
assert f"{nwUnicode.U_ENDASH} Lorax" in actions
|
||||
assert f"{nwUnicode.U_ENDASH} {LORAX}" in actions
|
||||
ctxMenu.actions()[7].trigger()
|
||||
QApplication.processEvents()
|
||||
assert docEditor.getText() == text.replace("Lorem", "Lorax", 1)
|
||||
assert docEditor.getText() == text.replace("Lorem", LORAX, 1)
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
# Update Entry
|
||||
data._spellErrors = [(0, 7, LORAX)]
|
||||
|
||||
# Without Suggestion
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(SHARED.spelling, "suggestWords", lambda *a: [])
|
||||
@@ -548,7 +552,7 @@ def testGuiEditor_SpellChecking(qtbot, monkeypatch, nwGUI, projPath, ipsumText,
|
||||
assert ctxMenu is not None
|
||||
actions = [x.text() for x in ctxMenu.actions() if x.text()]
|
||||
assert f"{nwUnicode.U_ENDASH} No Suggestions" in actions
|
||||
assert docEditor.getText() == text.replace("Lorem", "Lorax", 1)
|
||||
assert docEditor.getText() == text.replace("Lorem", LORAX, 1)
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
@@ -562,11 +566,11 @@ def testGuiEditor_SpellChecking(qtbot, monkeypatch, nwGUI, projPath, ipsumText,
|
||||
assert "Ignore Word" in actions
|
||||
assert "Add Word to Dictionary" in actions
|
||||
|
||||
assert "Lorax" not in SHARED.spelling._userDict
|
||||
assert LORAX not in SHARED.spelling._userDict
|
||||
ctxMenu.actions()[7].trigger() # Ignore
|
||||
assert "Lorax" not in SHARED.spelling._userDict
|
||||
assert LORAX not in SHARED.spelling._userDict
|
||||
ctxMenu.actions()[8].trigger() # Add
|
||||
assert "Lorax" in SHARED.spelling._userDict
|
||||
assert LORAX in SHARED.spelling._userDict
|
||||
ctxMenu.setObjectName("")
|
||||
ctxMenu.deleteLater()
|
||||
|
||||
@@ -674,9 +678,16 @@ def testGuiEditor_Actions(qtbot, nwGUI, projPath, ipsumText, mockRnd):
|
||||
assert docEditor.docAction(nwDocAction.UNDO) is True
|
||||
assert docEditor.getText() == text
|
||||
|
||||
# Mark
|
||||
docEditor.setCursorPosition(50)
|
||||
assert docEditor.docAction(nwDocAction.MD_MARK) is True
|
||||
assert docEditor.getText() == text.replace("consectetur", "==consectetur==")
|
||||
assert docEditor.docAction(nwDocAction.UNDO) is True
|
||||
assert docEditor.getText() == text
|
||||
|
||||
# Redo
|
||||
assert docEditor.docAction(nwDocAction.REDO) is True
|
||||
assert docEditor.getText() == text.replace("consectetur", "~~consectetur~~")
|
||||
assert docEditor.getText() == text.replace("consectetur", "==consectetur==")
|
||||
assert docEditor.docAction(nwDocAction.UNDO) is True
|
||||
assert docEditor.getText() == text
|
||||
|
||||
@@ -2067,7 +2078,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
origText = docEditor.getText()
|
||||
|
||||
# Select the Word "est"
|
||||
docEditor.setCursorPosition(645)
|
||||
docEditor.setCursorPosition(663)
|
||||
docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
|
||||
cursor = docEditor.textCursor()
|
||||
assert cursor.selectedText() == "est"
|
||||
@@ -2080,11 +2091,11 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
# Find next by enter key
|
||||
monkeypatch.setattr(docSearch.searchBox, "hasFocus", lambda: True)
|
||||
qtbot.keyClick(docSearch.searchBox, Qt.Key.Key_Return, delay=KEY_DELAY)
|
||||
assert abs(docEditor.getCursorPosition() - 1299) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 1317) < 3
|
||||
|
||||
# Find next by button
|
||||
qtbot.mouseClick(docSearch.searchButton, QtMouseLeft, delay=KEY_DELAY)
|
||||
assert abs(docEditor.getCursorPosition() - 1513) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 1531) < 3
|
||||
|
||||
# Activate loop search
|
||||
docSearch.toggleLoop.activate(QAction.ActionEvent.Trigger)
|
||||
@@ -2093,7 +2104,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
|
||||
# Find next by menu Search > Find Next
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 647) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 665) < 3
|
||||
|
||||
# Close search
|
||||
docSearch.cancelSearch.activate(QAction.ActionEvent.Trigger)
|
||||
@@ -2132,13 +2143,13 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
# Set valid RegEx
|
||||
docSearch.setSearchText(r"\bSus")
|
||||
qtbot.mouseClick(docSearch.searchButton, QtMouseLeft, delay=KEY_DELAY)
|
||||
assert abs(docEditor.getCursorPosition() - 223) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 241) < 3
|
||||
|
||||
# Find next and then prev
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 324) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 342) < 3
|
||||
nwGUI.mainMenu.aFindPrev.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 223) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 241) < 3
|
||||
|
||||
# Make RegEx case sensitive
|
||||
docSearch.toggleCase.activate(QAction.ActionEvent.Trigger)
|
||||
@@ -2147,11 +2158,11 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
|
||||
# Find next/prev (one result)
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 626) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 644) < 3
|
||||
nwGUI.mainMenu.aFindPrev.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 626) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 644) < 3
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 626) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 644) < 3
|
||||
|
||||
# Trigger replace
|
||||
nwGUI.mainMenu.aReplace.activate(QAction.ActionEvent.Trigger)
|
||||
@@ -2168,22 +2179,22 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
assert CONFIG.searchMatchCap is True
|
||||
|
||||
# Replace "Sus" with "Foo" via menu
|
||||
docEditor.setCursorPosition(605)
|
||||
docEditor.setCursorPosition(623)
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
nwGUI.mainMenu.aReplaceNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[623:634] == "Foopendisse"
|
||||
assert docEditor.getText()[641:652] == "Foopendisse"
|
||||
|
||||
# Find next/prev to loop file
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 223) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 241) < 3
|
||||
nwGUI.mainMenu.aFindPrev.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 1805) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 1823) < 3
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 223) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 241) < 3
|
||||
|
||||
# Replace "sus" with "foo" via replace button
|
||||
qtbot.mouseClick(docSearch.replaceButton, QtMouseLeft, delay=KEY_DELAY)
|
||||
assert docEditor.getText()[220:228] == "foocipit"
|
||||
assert docEditor.getText()[238:246] == "foocipit"
|
||||
|
||||
# Revert last two replaces
|
||||
assert docEditor.docAction(nwDocAction.UNDO)
|
||||
@@ -2197,7 +2208,7 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
|
||||
# Close search and select "est" again
|
||||
docSearch.cancelSearch.activate(QAction.ActionEvent.Trigger)
|
||||
docEditor.setCursorPosition(645)
|
||||
docEditor.setCursorPosition(663)
|
||||
docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
|
||||
cursor = docEditor.textCursor()
|
||||
assert cursor.selectedText() == "est"
|
||||
@@ -2214,9 +2225,9 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
|
||||
# Only one match
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 647) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 665) < 3
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 647) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 665) < 3
|
||||
|
||||
# Enable next doc search
|
||||
docSearch.toggleProject.activate(QAction.ActionEvent.Trigger)
|
||||
@@ -2227,9 +2238,9 @@ def testGuiEditor_Search(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.docHandle == "2426c6f0ca922" # Next document
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 620) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 651) < 3
|
||||
nwGUI.mainMenu.aFindNext.activate(QAction.ActionEvent.Trigger)
|
||||
assert abs(docEditor.getCursorPosition() - 1127) < 3
|
||||
assert abs(docEditor.getCursorPosition() - 1157) < 3
|
||||
|
||||
# Next doc, no match
|
||||
assert CONFIG.searchNextFile is True
|
||||
|
||||
@@ -0,0 +1,631 @@
|
||||
"""
|
||||
novelWriter – GUI Syntax Highlighter Tester
|
||||
===========================================
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright (C) 2020 Veronica Berglyd Olsen and novelWriter contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt6.QtGui import QTextCharFormat, QTextCursor, QTextDocument
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.core.item import NWItem
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme
|
||||
from novelwriter.gui.dochighlight import BLOCK_META, BLOCK_TITLE, GuiDocHighlighter, TextBlockData
|
||||
from novelwriter.types import QtKeepAnchor
|
||||
|
||||
R_HANDLE = "3456789abcdef"
|
||||
T_HANDLE = "0123456789abc"
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def syntax(nwGUI):
|
||||
"""Create a syntax object for use with testing."""
|
||||
CONFIG.lightTheme = "tomorrow"
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
|
||||
CONFIG.dialogStyle = 3
|
||||
CONFIG.fmtDQuoteOpen = "\u201c"
|
||||
CONFIG.fmtDQuoteClose = "\u201d"
|
||||
CONFIG.altDialogOpen = "::"
|
||||
CONFIG.altDialogClose = "::"
|
||||
|
||||
theme = SHARED.theme
|
||||
theme.loadTheme(force=True)
|
||||
assert theme._guiPalette.base().color().getRgb() == (0xff, 0xff, 0xff, 0xff)
|
||||
assert theme.syntaxTheme.text.getRgb() == (0x4d, 0x4d, 0x4c, 0xff)
|
||||
|
||||
doc = QTextDocument()
|
||||
syntax = GuiDocHighlighter(doc)
|
||||
|
||||
# Add a Mock Item
|
||||
tRoot = NWItem(SHARED.project, R_HANDLE)
|
||||
tRoot.setClass(nwItemClass.NOVEL)
|
||||
tRoot.setType(nwItemType.ROOT)
|
||||
|
||||
tItem = NWItem(SHARED.project, T_HANDLE)
|
||||
tItem.setParent(R_HANDLE)
|
||||
tItem.setLayout(nwItemLayout.NOTE)
|
||||
tItem.setClass(nwItemClass.NOVEL)
|
||||
tItem.setType(nwItemType.FILE)
|
||||
|
||||
SHARED.project.tree.add(tRoot)
|
||||
SHARED.project.tree.add(tItem)
|
||||
|
||||
yield syntax
|
||||
|
||||
|
||||
def getFragments(
|
||||
syntax: GuiDocHighlighter
|
||||
) -> tuple[list[tuple[int, str]], list[QTextCharFormat]]:
|
||||
"""Extract all syntax highlighter fragments from a document."""
|
||||
pieces = []
|
||||
formats = []
|
||||
doc = syntax.document()
|
||||
cursor = QTextCursor(doc)
|
||||
assert doc is not None
|
||||
for b in range(doc.blockCount()):
|
||||
block = doc.findBlockByNumber(b)
|
||||
first = block.position()
|
||||
syntax.rehighlightBlock(block)
|
||||
if layout := block.layout():
|
||||
for fmt in layout.formats():
|
||||
cursor.setPosition(first + fmt.start)
|
||||
cursor.setPosition(first + fmt.start + fmt.length, QtKeepAnchor)
|
||||
pieces.append((b, fmt.start, fmt.length, cursor.selectedText()))
|
||||
formats.append(fmt.format)
|
||||
return pieces, formats
|
||||
|
||||
|
||||
def maxOrd(text: str) -> int:
|
||||
"""Get the max character value of a string."""
|
||||
return max(ord(c) for c in text)
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocHighlighter_Basic(syntax):
|
||||
"""Test the basic functionality of the syntax highlighter."""
|
||||
# Alternate Spell Check
|
||||
assert syntax._spellCheck is False
|
||||
syntax.setSpellCheck(True)
|
||||
assert syntax._spellCheck is True
|
||||
|
||||
# Check Handle
|
||||
assert syntax._tHandle is None
|
||||
syntax.setHandle(T_HANDLE)
|
||||
assert syntax._tHandle == T_HANDLE
|
||||
assert syntax._isNovel is False
|
||||
assert syntax._isInactive is False
|
||||
|
||||
tItem = SHARED.project.tree[T_HANDLE]
|
||||
assert tItem is not None
|
||||
tItem.setLayout(nwItemLayout.DOCUMENT)
|
||||
tItem.setClass(nwItemClass.ARCHIVE)
|
||||
syntax.setHandle(T_HANDLE)
|
||||
assert syntax._tHandle == T_HANDLE
|
||||
assert syntax._isNovel is True
|
||||
assert syntax._isInactive is True
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocHighlighter_Keywords(syntax):
|
||||
"""Test highlighting of keywords."""
|
||||
theme = SHARED.theme
|
||||
doc = syntax.document()
|
||||
assert doc is not None
|
||||
|
||||
# Settings
|
||||
syntax._tHandle = T_HANDLE
|
||||
|
||||
colKey = theme.syntaxTheme.key.getRgb()
|
||||
colTag = theme.syntaxTheme.tag.getRgb()
|
||||
colOpt = theme.syntaxTheme.opt.getRgb()
|
||||
colErr = theme.syntaxTheme.error.getRgb()
|
||||
|
||||
# Ascii
|
||||
doc.setPlainText(
|
||||
"@tag: Bob | Robert\n"
|
||||
"@char: Someone\n"
|
||||
)
|
||||
syntax.rehighlightByType(BLOCK_META)
|
||||
assert maxOrd(doc.toPlainText()) <= 0x7f
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 4, "@tag"), (0, 6, 3, "Bob"), (0, 12, 6, "Robert"),
|
||||
(1, 0, 5, "@char"), (1, 7, 7, "Someone"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colKey
|
||||
assert formats[1].foreground().color().getRgb() == colTag
|
||||
assert formats[2].foreground().color().getRgb() == colOpt
|
||||
assert formats[3].foreground().color().getRgb() == colKey
|
||||
assert formats[4].underlineColor().getRgb() == colErr
|
||||
|
||||
# # Unicode <= 0xFFFF
|
||||
doc.setPlainText(
|
||||
"@tag: Zoë | Zoë Smith\n"
|
||||
"@char: Олексій\n"
|
||||
)
|
||||
syntax.rehighlightByType(BLOCK_META)
|
||||
assert 0x7f < maxOrd(doc.toPlainText()) <= 0xffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 4, "@tag"), (0, 6, 3, "Zoë"), (0, 12, 9, "Zoë Smith"),
|
||||
(1, 0, 5, "@char"), (1, 7, 7, "Олексій"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colKey
|
||||
assert formats[1].foreground().color().getRgb() == colTag
|
||||
assert formats[2].foreground().color().getRgb() == colOpt
|
||||
assert formats[3].foreground().color().getRgb() == colKey
|
||||
assert formats[4].underlineColor().getRgb() == colErr
|
||||
|
||||
# # Unicode > 0xFFFF
|
||||
doc.setPlainText(
|
||||
"@tag: 😄 | Smiley 😄\n"
|
||||
"@char: 😎😎😎\n"
|
||||
)
|
||||
syntax.rehighlightByType(BLOCK_META)
|
||||
assert 0xffff < maxOrd(doc.toPlainText()) <= 0xffffffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 4, "@tag"), (0, 6, 2, "😄"), (0, 11, 9, "Smiley 😄"),
|
||||
(1, 0, 5, "@char"), (1, 7, 6, "😎😎😎"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colKey
|
||||
assert formats[1].foreground().color().getRgb() == colTag
|
||||
assert formats[2].foreground().color().getRgb() == colOpt
|
||||
assert formats[3].foreground().color().getRgb() == colKey
|
||||
assert formats[4].underlineColor().getRgb() == colErr
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocHighlighter_Titles(syntax):
|
||||
"""Test highlighting of titles."""
|
||||
theme = SHARED.theme
|
||||
doc = syntax.document()
|
||||
assert doc is not None
|
||||
|
||||
# Settings
|
||||
syntax._tHandle = T_HANDLE
|
||||
|
||||
colHeadMark = theme.syntaxTheme.headH.getRgb()
|
||||
colHeadText = theme.syntaxTheme.head.getRgb()
|
||||
|
||||
# Ascii
|
||||
doc.setPlainText(
|
||||
"# Heading 1\n\n"
|
||||
"## Heading 2\n\n"
|
||||
"### Heading 3\n\n"
|
||||
"#### Heading 4\n\n"
|
||||
"#! Heading A1\n\n"
|
||||
"##! Heading A2\n\n"
|
||||
"###! Heading A3\n\n"
|
||||
)
|
||||
syntax.rehighlightByType(BLOCK_TITLE)
|
||||
assert maxOrd(doc.toPlainText()) <= 0x7f
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 1, "#"), (0, 1, 10, " Heading 1"),
|
||||
(2, 0, 2, "##"), (2, 2, 10, " Heading 2"),
|
||||
(4, 0, 3, "###"), (4, 3, 10, " Heading 3"),
|
||||
(6, 0, 4, "####"), (6, 4, 10, " Heading 4"),
|
||||
(8, 0, 2, "#!"), (8, 2, 11, " Heading A1"),
|
||||
(10, 0, 3, "##!"), (10, 3, 11, " Heading A2"),
|
||||
(12, 0, 4, "###!"), (12, 4, 11, " Heading A3"),
|
||||
]
|
||||
for i in range(0, len(formats), 2):
|
||||
assert formats[i].foreground().color().getRgb() == colHeadMark
|
||||
assert formats[i+1].foreground().color().getRgb() == colHeadText
|
||||
|
||||
# Unicode <= 0xFFFF
|
||||
doc.setPlainText(
|
||||
"# Ȟǣđ 1\n\n"
|
||||
"## Ȟǣđ 2\n\n"
|
||||
"### Ȟǣđ 3\n\n"
|
||||
"#### Ȟǣđ 4\n\n"
|
||||
"#! Ȟǣđ A1\n\n"
|
||||
"##! Ȟǣđ A2\n\n"
|
||||
"###! Ȟǣđ A3\n\n"
|
||||
)
|
||||
syntax.rehighlightByType(BLOCK_TITLE)
|
||||
assert 0x7f < maxOrd(doc.toPlainText()) <= 0xffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 1, "#"), (0, 1, 6, " Ȟǣđ 1"),
|
||||
(2, 0, 2, "##"), (2, 2, 6, " Ȟǣđ 2"),
|
||||
(4, 0, 3, "###"), (4, 3, 6, " Ȟǣđ 3"),
|
||||
(6, 0, 4, "####"), (6, 4, 6, " Ȟǣđ 4"),
|
||||
(8, 0, 2, "#!"), (8, 2, 7, " Ȟǣđ A1"),
|
||||
(10, 0, 3, "##!"), (10, 3, 7, " Ȟǣđ A2"),
|
||||
(12, 0, 4, "###!"), (12, 4, 7, " Ȟǣđ A3"),
|
||||
]
|
||||
for i in range(0, len(formats), 2):
|
||||
assert formats[i].foreground().color().getRgb() == colHeadMark
|
||||
assert formats[i+1].foreground().color().getRgb() == colHeadText
|
||||
|
||||
# Unicode > 0xFFFF
|
||||
doc.setPlainText(
|
||||
"# 😇😎 1\n\n"
|
||||
"## 😇😎 2\n\n"
|
||||
"### 😇😎 3\n\n"
|
||||
"#### 😇😎 4\n\n"
|
||||
"#! 😇😎 A1\n\n"
|
||||
"##! 😇😎 A2\n\n"
|
||||
"###! 😇😎 A3\n\n"
|
||||
)
|
||||
syntax.rehighlightByType(BLOCK_TITLE)
|
||||
assert 0xffff < maxOrd(doc.toPlainText()) <= 0xffffffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 1, "#"), (0, 1, 7, " 😇😎 1"),
|
||||
(2, 0, 2, "##"), (2, 2, 7, " 😇😎 2"),
|
||||
(4, 0, 3, "###"), (4, 3, 7, " 😇😎 3"),
|
||||
(6, 0, 4, "####"), (6, 4, 7, " 😇😎 4"),
|
||||
(8, 0, 2, "#!"), (8, 2, 8, " 😇😎 A1"),
|
||||
(10, 0, 3, "##!"), (10, 3, 8, " 😇😎 A2"),
|
||||
(12, 0, 4, "###!"), (12, 4, 8, " 😇😎 A3"),
|
||||
]
|
||||
for i in range(0, len(formats), 2):
|
||||
assert formats[i].foreground().color().getRgb() == colHeadMark
|
||||
assert formats[i+1].foreground().color().getRgb() == colHeadText
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocHighlighter_Comments(syntax):
|
||||
"""Test highlighting of comments."""
|
||||
theme = SHARED.theme
|
||||
doc = syntax.document()
|
||||
assert doc is not None
|
||||
|
||||
# Settings
|
||||
syntax._tHandle = T_HANDLE
|
||||
|
||||
colHidden = theme.syntaxTheme.hidden.getRgb()
|
||||
colMod = theme.syntaxTheme.mod.getRgb()
|
||||
colValue = theme.syntaxTheme.val.getRgb()
|
||||
colNote = theme.syntaxTheme.note.getRgb()
|
||||
|
||||
# Ascii
|
||||
doc.setPlainText(
|
||||
"% Plain\n"
|
||||
"%~ Ignored\n"
|
||||
"%Synopsis: Synopsis\n"
|
||||
"%Note.Stuff: Note\n"
|
||||
)
|
||||
syntax.rehighlight()
|
||||
assert maxOrd(doc.toPlainText()) <= 0x7f
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 7, "% Plain"),
|
||||
(1, 0, 10, "%~ Ignored"),
|
||||
(2, 0, 10, "%Synopsis:"), (2, 10, 9, " Synopsis"),
|
||||
(3, 0, 6, "%Note."), (3, 6, 6, "Stuff:"), (3, 12, 5, " Note"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colHidden
|
||||
assert formats[1].foreground().color().getRgb() == colHidden
|
||||
assert formats[1].fontStrikeOut() is True
|
||||
assert formats[2].foreground().color().getRgb() == colMod
|
||||
assert formats[3].foreground().color().getRgb() == colNote
|
||||
assert formats[4].foreground().color().getRgb() == colMod
|
||||
assert formats[5].foreground().color().getRgb() == colValue
|
||||
assert formats[6].foreground().color().getRgb() == colNote
|
||||
|
||||
# Unicode <= 0xFFFF
|
||||
doc.setPlainText(
|
||||
"% Рівнина\n"
|
||||
"%~ Ігноровано\n"
|
||||
"%Synopsis: Синопсис\n"
|
||||
"%Note.Stuff: Примітка\n"
|
||||
)
|
||||
syntax.rehighlight()
|
||||
assert 0x7f < maxOrd(doc.toPlainText()) <= 0xffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 9, "% Рівнина"),
|
||||
(1, 0, 13, "%~ Ігноровано"),
|
||||
(2, 0, 10, "%Synopsis:"), (2, 10, 9, " Синопсис"),
|
||||
(3, 0, 6, "%Note."), (3, 6, 6, "Stuff:"), (3, 12, 9, " Примітка"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colHidden
|
||||
assert formats[1].foreground().color().getRgb() == colHidden
|
||||
assert formats[1].fontStrikeOut() is True
|
||||
assert formats[2].foreground().color().getRgb() == colMod
|
||||
assert formats[3].foreground().color().getRgb() == colNote
|
||||
assert formats[4].foreground().color().getRgb() == colMod
|
||||
assert formats[5].foreground().color().getRgb() == colValue
|
||||
assert formats[6].foreground().color().getRgb() == colNote
|
||||
|
||||
# Unicode > 0xFFFF
|
||||
doc.setPlainText(
|
||||
"% 😎😎\n"
|
||||
"%~ 🙈🙈🙈\n"
|
||||
"%Synopsis: 😍😍😍😍\n"
|
||||
"%Note.Stuff: 😡😡😡😡😡\n"
|
||||
)
|
||||
syntax.rehighlight()
|
||||
assert 0xffff < maxOrd(doc.toPlainText()) <= 0xffffffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 6, "% 😎😎"),
|
||||
(1, 0, 9, "%~ 🙈🙈🙈"),
|
||||
(2, 0, 10, "%Synopsis:"), (2, 10, 9, " 😍😍😍😍"),
|
||||
(3, 0, 6, "%Note."), (3, 6, 6, "Stuff:"), (3, 12, 11, " 😡😡😡😡😡"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colHidden
|
||||
assert formats[1].foreground().color().getRgb() == colHidden
|
||||
assert formats[1].fontStrikeOut() is True
|
||||
assert formats[2].foreground().color().getRgb() == colMod
|
||||
assert formats[3].foreground().color().getRgb() == colNote
|
||||
assert formats[4].foreground().color().getRgb() == colMod
|
||||
assert formats[5].foreground().color().getRgb() == colValue
|
||||
assert formats[6].foreground().color().getRgb() == colNote
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocHighlighter_Special(syntax):
|
||||
"""Test highlighting of special commands."""
|
||||
theme = SHARED.theme
|
||||
doc = syntax.document()
|
||||
assert doc is not None
|
||||
|
||||
# Settings
|
||||
syntax._tHandle = T_HANDLE
|
||||
|
||||
colErr = theme.syntaxTheme.error.getRgb()
|
||||
colCode = theme.syntaxTheme.code.getRgb()
|
||||
colValue = theme.syntaxTheme.val.getRgb()
|
||||
|
||||
# Ascii
|
||||
doc.setPlainText(
|
||||
"[NewPage]\n"
|
||||
"[New Page]\n"
|
||||
"[VSpace]\n"
|
||||
"[VSpace:123]\n"
|
||||
"[VSpace:Meh]\n"
|
||||
)
|
||||
syntax.rehighlight()
|
||||
assert maxOrd(doc.toPlainText()) <= 0x7f
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 9, "[NewPage]"),
|
||||
(1, 0, 10, "[New Page]"),
|
||||
(2, 0, 8, "[VSpace]"),
|
||||
(3, 0, 8, "[VSpace:"), (3, 8, 3, "123"), (3, 11, 1, "]"),
|
||||
(4, 0, 8, "[VSpace:"), (4, 8, 3, "Meh"), (4, 11, 1, "]"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colCode
|
||||
assert formats[1].foreground().color().getRgb() == colCode
|
||||
assert formats[2].foreground().color().getRgb() == colCode
|
||||
assert formats[3].foreground().color().getRgb() == colCode
|
||||
assert formats[4].foreground().color().getRgb() == colValue
|
||||
assert formats[5].foreground().color().getRgb() == colCode
|
||||
assert formats[6].foreground().color().getRgb() == colCode
|
||||
assert formats[7].underlineColor().getRgb() == colErr
|
||||
assert formats[8].foreground().color().getRgb() == colCode
|
||||
|
||||
# Unicode <= 0xFFFF
|
||||
doc.setPlainText(
|
||||
"[NewPage]\n"
|
||||
"[New Page]\n"
|
||||
"[VSpace]\n"
|
||||
"[VSpace:123]\n"
|
||||
"[VSpace:⅘]\n"
|
||||
)
|
||||
syntax.rehighlight()
|
||||
assert 0x7f < maxOrd(doc.toPlainText()) <= 0xffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 9, "[NewPage]"),
|
||||
(1, 0, 10, "[New Page]"),
|
||||
(2, 0, 8, "[VSpace]"),
|
||||
(3, 0, 8, "[VSpace:"), (3, 8, 3, "123"), (3, 11, 1, "]"),
|
||||
(4, 0, 8, "[VSpace:"), (4, 8, 1, "⅘"), (4, 9, 1, "]"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colCode
|
||||
assert formats[1].foreground().color().getRgb() == colCode
|
||||
assert formats[2].foreground().color().getRgb() == colCode
|
||||
assert formats[3].foreground().color().getRgb() == colCode
|
||||
assert formats[4].foreground().color().getRgb() == colValue
|
||||
assert formats[5].foreground().color().getRgb() == colCode
|
||||
assert formats[6].foreground().color().getRgb() == colCode
|
||||
assert formats[7].underlineColor().getRgb() == colErr
|
||||
assert formats[8].foreground().color().getRgb() == colCode
|
||||
|
||||
# Unicode > 0xFFFF
|
||||
doc.setPlainText(
|
||||
"[NewPage]\n"
|
||||
"[New Page]\n"
|
||||
"[VSpace]\n"
|
||||
"[VSpace:123]\n"
|
||||
"[VSpace:🙈🙈]\n"
|
||||
)
|
||||
syntax.rehighlight()
|
||||
assert 0xffff < maxOrd(doc.toPlainText()) <= 0xffffffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 9, "[NewPage]"),
|
||||
(1, 0, 10, "[New Page]"),
|
||||
(2, 0, 8, "[VSpace]"),
|
||||
(3, 0, 8, "[VSpace:"), (3, 8, 3, "123"), (3, 11, 1, "]"),
|
||||
(4, 0, 8, "[VSpace:"), (4, 8, 4, "🙈🙈"), (4, 12, 1, "]"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colCode
|
||||
assert formats[1].foreground().color().getRgb() == colCode
|
||||
assert formats[2].foreground().color().getRgb() == colCode
|
||||
assert formats[3].foreground().color().getRgb() == colCode
|
||||
assert formats[4].foreground().color().getRgb() == colValue
|
||||
assert formats[5].foreground().color().getRgb() == colCode
|
||||
assert formats[6].foreground().color().getRgb() == colCode
|
||||
assert formats[7].underlineColor().getRgb() == colErr
|
||||
assert formats[8].foreground().color().getRgb() == colCode
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiDocHighlighter_Text(monkeypatch, syntax):
|
||||
"""Test highlighting of text."""
|
||||
theme = SHARED.theme
|
||||
doc = syntax.document()
|
||||
assert doc is not None
|
||||
|
||||
# Settings
|
||||
syntax._tHandle = T_HANDLE
|
||||
syntax._isNovel = True
|
||||
syntax.setSpellCheck(True)
|
||||
monkeypatch.setattr(SHARED.spelling, "checkWord", lambda *a: False)
|
||||
|
||||
colHidden = theme.syntaxTheme.hidden.getRgb()
|
||||
colEmph = theme.syntaxTheme.emph.getRgb()
|
||||
colLink = theme.syntaxTheme.link.getRgb()
|
||||
colSpell = theme.syntaxTheme.spell.getRgb()
|
||||
colCode = theme.syntaxTheme.code.getRgb()
|
||||
colDialogue = theme.syntaxTheme.dialN.getRgb()
|
||||
colAltDialogue = theme.syntaxTheme.dialA.getRgb()
|
||||
|
||||
# Ascii
|
||||
doc.setPlainText(
|
||||
"Text **bold** text _italic_ text ~~strike~~ text [b]bold[/b], http://example.com\n\n"
|
||||
)
|
||||
syntax.rehighlight()
|
||||
assert maxOrd(doc.toPlainText()) <= 0x7f
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 4, "Text"),
|
||||
(0, 5, 2, "**"), (0, 7, 4, "bold"), (0, 11, 2, "**"),
|
||||
(0, 14, 4, "text"),
|
||||
(0, 19, 1, "_"), (0, 20, 6, "italic"), (0, 26, 1, "_"),
|
||||
(0, 28, 4, "text"),
|
||||
(0, 33, 2, "~~"), (0, 35, 6, "strike"), (0, 41, 2, "~~"),
|
||||
(0, 44, 4, "text"),
|
||||
(0, 49, 3, "[b]"), (0, 52, 4, "bold"), (0, 56, 4, "[/b]"),
|
||||
(0, 62, 18, "http://example.com"),
|
||||
]
|
||||
assert formats[0].underlineColor().getRgb() == colSpell # Text
|
||||
assert formats[1].foreground().color().getRgb() == colHidden # **
|
||||
assert formats[2].foreground().color().getRgb() == colEmph # bold
|
||||
assert formats[2].underlineColor().getRgb() == colSpell
|
||||
assert formats[3].foreground().color().getRgb() == colHidden # **
|
||||
assert formats[4].underlineColor().getRgb() == colSpell # text
|
||||
assert formats[5].foreground().color().getRgb() == colHidden # _
|
||||
assert formats[6].foreground().color().getRgb() == colEmph # italic
|
||||
assert formats[6].underlineColor().getRgb() == colSpell
|
||||
assert formats[7].foreground().color().getRgb() == colHidden # _
|
||||
assert formats[8].underlineColor().getRgb() == colSpell # text
|
||||
assert formats[9].foreground().color().getRgb() == colHidden # ~~
|
||||
assert formats[10].foreground().color().getRgb() == colHidden # strike
|
||||
assert formats[10].fontStrikeOut() is True
|
||||
assert formats[10].underlineColor().getRgb() == colSpell
|
||||
assert formats[11].foreground().color().getRgb() == colHidden # ~~
|
||||
assert formats[12].underlineColor().getRgb() == colSpell # text
|
||||
assert formats[13].foreground().color().getRgb() == colCode # [b]
|
||||
assert formats[14].underlineColor().getRgb() == colSpell # bold
|
||||
assert formats[15].foreground().color().getRgb() == colCode # [/b]
|
||||
assert formats[16].foreground().color().getRgb() == colLink # http://example.com
|
||||
|
||||
# Spell Check
|
||||
data = doc.findBlockByNumber(0).userData()
|
||||
assert isinstance(data, TextBlockData)
|
||||
assert data.metaData == [(62, 80, "http://example.com", "url")]
|
||||
assert data.spellErrors == [
|
||||
(0, 4, "Text"), (7, 11, "bold"),
|
||||
(14, 18, "text"), (20, 26, "italic"),
|
||||
(28, 32, "text"), (35, 41, "strike"),
|
||||
(44, 48, "text"), (52, 56, "bold"),
|
||||
]
|
||||
|
||||
# Unicode <= 0xFFFF
|
||||
doc.setPlainText(
|
||||
"\u201cDialogue,\u201d and then ::dialogue::, http://example.com\n\n"
|
||||
)
|
||||
syntax.rehighlight()
|
||||
assert 0x7f < maxOrd(doc.toPlainText()) <= 0xffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 1, "\u201c"), (0, 1, 8, "Dialogue"), (0, 9, 2, ",\u201d"),
|
||||
(0, 12, 3, "and"), (0, 16, 4, "then"),
|
||||
(0, 21, 2, "::"), (0, 23, 8, "dialogue"), (0, 31, 2, "::"),
|
||||
(0, 35, 18, "http://example.com"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colDialogue # Quote
|
||||
assert formats[1].foreground().color().getRgb() == colDialogue # Dialogue
|
||||
assert formats[1].underlineColor().getRgb() == colSpell
|
||||
assert formats[2].foreground().color().getRgb() == colDialogue # Quote
|
||||
assert formats[3].underlineColor().getRgb() == colSpell # and
|
||||
assert formats[4].underlineColor().getRgb() == colSpell # then
|
||||
assert formats[5].foreground().color().getRgb() == colAltDialogue # ::
|
||||
assert formats[6].foreground().color().getRgb() == colAltDialogue # dialogue
|
||||
assert formats[6].underlineColor().getRgb() == colSpell
|
||||
assert formats[7].foreground().color().getRgb() == colAltDialogue # ::
|
||||
assert formats[8].foreground().color().getRgb() == colLink # http://example.com
|
||||
|
||||
# Spell Check
|
||||
data = doc.findBlockByNumber(0).userData()
|
||||
assert isinstance(data, TextBlockData)
|
||||
assert data.metaData == [(35, 53, "http://example.com", "url")]
|
||||
assert data.spellErrors == [
|
||||
(1, 9, "Dialogue"), (12, 15, "and"),
|
||||
(16, 20, "then"), (23, 31, "dialogue"),
|
||||
]
|
||||
|
||||
# Unicode > 0xFFFF
|
||||
doc.setPlainText(
|
||||
"\u201c😁 Grinning 😁,\u201d and then ::🙊 shush 🙊::, http://example.com\n\n"
|
||||
)
|
||||
syntax.rehighlight()
|
||||
assert 0xffff < maxOrd(doc.toPlainText()) <= 0xffffffff
|
||||
|
||||
pieces, formats = getFragments(syntax)
|
||||
assert pieces == [
|
||||
(0, 0, 4, "\u201c😁 "), (0, 4, 8, "Grinning"), (0, 12, 5, " 😁,\u201d"),
|
||||
(0, 18, 3, "and"), (0, 22, 4, "then"),
|
||||
(0, 27, 5, "::🙊 "), (0, 32, 5, "shush"), (0, 37, 5, " 🙊::"),
|
||||
(0, 44, 18, "http://example.com"),
|
||||
]
|
||||
assert formats[0].foreground().color().getRgb() == colDialogue # Quote😁
|
||||
assert formats[1].foreground().color().getRgb() == colDialogue # Grinning
|
||||
assert formats[1].underlineColor().getRgb() == colSpell
|
||||
assert formats[2].foreground().color().getRgb() == colDialogue # 😁Quote
|
||||
assert formats[3].underlineColor().getRgb() == colSpell # and
|
||||
assert formats[4].underlineColor().getRgb() == colSpell # then
|
||||
assert formats[5].foreground().color().getRgb() == colAltDialogue # ::🙊
|
||||
assert formats[6].foreground().color().getRgb() == colAltDialogue # shush
|
||||
assert formats[6].underlineColor().getRgb() == colSpell
|
||||
assert formats[7].foreground().color().getRgb() == colAltDialogue # 🙊::
|
||||
assert formats[8].foreground().color().getRgb() == colLink # http://example.com
|
||||
|
||||
# Spell Check
|
||||
data = doc.findBlockByNumber(0).userData()
|
||||
assert isinstance(data, TextBlockData)
|
||||
assert data.metaData == [(40, 58, "http://example.com", "url")]
|
||||
assert data.spellErrors == [
|
||||
(4, 12, "Grinning"), (18, 21, "and"),
|
||||
(22, 26, "then"), (32, 37, "shush"),
|
||||
]
|
||||
@@ -223,17 +223,23 @@ def testGuiViewer_Main(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
CONFIG.showFullPath = True
|
||||
|
||||
# Document footer show/hide synopsis
|
||||
assert nwGUI.viewDocument("f96ec11c6a3da") is True
|
||||
nwGUI.viewDocument("f96ec11c6a3da")
|
||||
assert len(docViewer.toPlainText()) == 4314
|
||||
docViewer.docFooter._doToggleSynopsis(False)
|
||||
assert len(docViewer.toPlainText()) == 4098
|
||||
|
||||
# Document footer show/hide comments
|
||||
assert nwGUI.viewDocument("846352075de7d") is True
|
||||
nwGUI.viewDocument("846352075de7d")
|
||||
assert len(docViewer.toPlainText()) == 683
|
||||
docViewer.docFooter._doToggleComments(False)
|
||||
assert len(docViewer.toPlainText()) == 634
|
||||
|
||||
# Document footer show/hide notes
|
||||
nwGUI.viewDocument("88d59a277361b")
|
||||
assert len(docViewer.toPlainText()) == 900
|
||||
docViewer.docFooter._doToggleNotes(False)
|
||||
assert len(docViewer.toPlainText()) == 871
|
||||
|
||||
# Crash the HTML rendering
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(ToQTextDocument, "doConvert", causeException)
|
||||
|
||||
@@ -224,7 +224,7 @@ def testGuiViewerPanel_Tags(qtbot, monkeypatch, caplog, nwGUI, projPath, mockRnd
|
||||
|
||||
# Update Labels
|
||||
assert charTab.topLevelItem(0).text(charTab.C_IMPORT) == "New"
|
||||
SHARED.project.data.itemImport.add(C.iNew, "Stuff", (100, 100, 100), "SQUARE", 0)
|
||||
SHARED.project.data.itemImport.add(C.iNew, "Stuff", "#646464", "SQUARE", 0)
|
||||
viewPanel.updateStatusLabels("i")
|
||||
assert charTab.topLevelItem(0).text(charTab.C_IMPORT) == "Stuff"
|
||||
|
||||
|
||||
@@ -24,6 +24,7 @@ import shutil
|
||||
|
||||
from pathlib import Path
|
||||
from shutil import copyfile
|
||||
from unittest.mock import Mock
|
||||
|
||||
import pytest
|
||||
|
||||
@@ -32,9 +33,10 @@ from PyQt6.QtGui import QPalette
|
||||
from PyQt6.QtWidgets import QInputDialog, QMessageBox
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT
|
||||
from novelwriter.constants import nwFiles
|
||||
from novelwriter.dialogs.editlabel import GuiEditLabel
|
||||
from novelwriter.enum import nwDocAction, nwDocMode, nwFocus, nwItemType, nwView
|
||||
from novelwriter.enum import nwDocAction, nwDocMode, nwFocus, nwItemType, nwTheme, nwView
|
||||
from novelwriter.gui.doceditor import GuiDocEditor
|
||||
from novelwriter.gui.noveltree import GuiNovelView
|
||||
from novelwriter.gui.outline import GuiOutlineView
|
||||
@@ -179,21 +181,35 @@ def testGuiMain_ProjectTreeItems(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
@pytest.mark.gui
|
||||
def testGuiMain_UpdateTheme(qtbot, nwGUI):
|
||||
"""Test updating the theme in the GUI."""
|
||||
mainTheme = SHARED.theme
|
||||
CONFIG.guiTheme = "default_dark"
|
||||
CONFIG.guiSyntax = "default_dark"
|
||||
mainTheme.loadTheme()
|
||||
mainTheme.loadSyntax()
|
||||
theme = SHARED.theme
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
CONFIG.darkTheme = DEF_GUI_DARK
|
||||
CONFIG.lightTheme = DEF_GUI_LIGHT
|
||||
theme.loadTheme()
|
||||
assert theme.isDarkTheme is True
|
||||
|
||||
nwGUI._processConfigChanges(False, True, False, False)
|
||||
nwGUI._processConfigChanges(True, True, True, True)
|
||||
|
||||
# Check editor syntax
|
||||
syntax = SHARED.theme.syntaxTheme
|
||||
|
||||
assert nwGUI.docEditor.palette().color(QPalette.ColorRole.Window) == syntax.back
|
||||
assert nwGUI.docEditor.docHeader.palette().color(QPalette.ColorRole.Window) == syntax.back
|
||||
assert nwGUI.docViewer.palette().color(QPalette.ColorRole.Window) == syntax.back
|
||||
assert nwGUI.docViewer.docHeader.palette().color(QPalette.ColorRole.Window) == syntax.back
|
||||
|
||||
# Update by check
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
nwGUI.checkThemeUpdate()
|
||||
assert theme.isDarkTheme is False
|
||||
|
||||
# Through change event
|
||||
event = Mock()
|
||||
event.type.return_value = 210
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
nwGUI.changeEvent(event)
|
||||
assert theme.isDarkTheme is True
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
|
||||
@@ -251,8 +267,7 @@ def testGuiMain_Editing(qtbot, monkeypatch, nwGUI, projPath, tstPaths, mockRnd):
|
||||
# Change some settings
|
||||
CONFIG.hideHScroll = True
|
||||
CONFIG.hideVScroll = True
|
||||
CONFIG.autoScrollPos = 80
|
||||
CONFIG.autoScroll = True
|
||||
CONFIG.autoScroll = False
|
||||
|
||||
# Add a Character File
|
||||
nwGUI._changeView(nwView.PROJECT)
|
||||
|
||||
@@ -68,154 +68,155 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
assert docEditor.docAction(nwDocAction.COPY) is False
|
||||
|
||||
assert nwGUI.openProject(prjLipsum) is True
|
||||
x = 72
|
||||
|
||||
# Split By Chapter
|
||||
assert nwGUI.openDocument("4c4f28287af27") is True
|
||||
docEditor.setCursorPosition(57)
|
||||
cleanText = docEditor.getText()[54:101]
|
||||
docEditor.setCursorPosition(x+3)
|
||||
cleanText = docEditor.getText()[x:x+47]
|
||||
|
||||
# Bold
|
||||
mainMenu.aFmtBold.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "**Pellentesque** nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:105] == fmtStr
|
||||
assert docEditor.getText()[x:x+51] == fmtStr
|
||||
mainMenu.aFmtBold.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Italic
|
||||
mainMenu.aFmtItalic.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "_Pellentesque_ nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:103] == fmtStr
|
||||
assert docEditor.getText()[x:x+49] == fmtStr
|
||||
mainMenu.aFmtItalic.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Strikethrough
|
||||
mainMenu.aFmtStrike.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "~~Pellentesque~~ nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:105] == fmtStr
|
||||
assert docEditor.getText()[x:x+51] == fmtStr
|
||||
mainMenu.aFmtStrike.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Should get us back to plain
|
||||
mainMenu.aFmtBold.activate(QAction.ActionEvent.Trigger)
|
||||
mainMenu.aFmtItalic.activate(QAction.ActionEvent.Trigger)
|
||||
mainMenu.aFmtItalic.activate(QAction.ActionEvent.Trigger)
|
||||
mainMenu.aFmtBold.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Double Quotes
|
||||
mainMenu.aFmtDQuote.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "“Pellentesque” nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:103] == fmtStr
|
||||
assert docEditor.getText()[x:x+49] == fmtStr
|
||||
mainMenu.aEditUndo.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Single Quotes
|
||||
mainMenu.aFmtSQuote.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "‘Pellentesque’ nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:103] == fmtStr
|
||||
assert docEditor.getText()[x:x+49] == fmtStr
|
||||
mainMenu.aEditUndo.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Block Formats
|
||||
# =============
|
||||
# cSpell:ignore Pellentesque erat nulla posuere commodo
|
||||
docEditor.setCursorPosition(57)
|
||||
docEditor.setCursorPosition(x+3)
|
||||
|
||||
# Header 1
|
||||
mainMenu.aFmtHead1.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "# Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:103] == fmtStr
|
||||
assert docEditor.getText()[x:x+49] == fmtStr
|
||||
|
||||
# Header 2
|
||||
mainMenu.aFmtHead2.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "## Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:104] == fmtStr
|
||||
assert docEditor.getText()[x:x+50] == fmtStr
|
||||
|
||||
# Header 3
|
||||
mainMenu.aFmtHead3.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "### Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:105] == fmtStr
|
||||
assert docEditor.getText()[x:x+51] == fmtStr
|
||||
|
||||
# Header 4
|
||||
mainMenu.aFmtHead4.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "#### Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:106] == fmtStr
|
||||
assert docEditor.getText()[x:x+52] == fmtStr
|
||||
|
||||
# Title Format
|
||||
mainMenu.aFmtTitle.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "#! Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:104] == fmtStr
|
||||
assert docEditor.getText()[x:x+50] == fmtStr
|
||||
|
||||
# Unnumbered Chapter
|
||||
mainMenu.aFmtUnNum.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "##! Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:105] == fmtStr
|
||||
assert docEditor.getText()[x:x+51] == fmtStr
|
||||
|
||||
# Hard Scene
|
||||
mainMenu.aFmtHardSc.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "###! Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:106] == fmtStr
|
||||
assert docEditor.getText()[x:x+52] == fmtStr
|
||||
|
||||
# Clear Format
|
||||
mainMenu.aFmtNoFormat.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Comment On
|
||||
mainMenu.aFmtComment.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "% Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:103] == fmtStr
|
||||
assert docEditor.getText()[x:x+49] == fmtStr
|
||||
|
||||
# Comment Off
|
||||
mainMenu.aFmtComment.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Check comment with no space before text
|
||||
docEditor.setCursorPosition(54)
|
||||
docEditor.setCursorPosition(x)
|
||||
docEditor.insertText("%")
|
||||
fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:102] == fmtStr
|
||||
assert docEditor.getText()[x:x+48] == fmtStr
|
||||
|
||||
mainMenu.aFmtNoFormat.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Undo/Redo
|
||||
mainMenu.aEditUndo.activate(QAction.ActionEvent.Trigger)
|
||||
fmtStr = "%Pellentesque nec erat ut nulla posuere commodo."
|
||||
assert docEditor.getText()[54:102] == fmtStr
|
||||
assert docEditor.getText()[x:x+48] == fmtStr
|
||||
mainMenu.aEditRedo.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:101] == cleanText
|
||||
assert docEditor.getText()[x:x+47] == cleanText
|
||||
|
||||
# Cut, Copy and Paste
|
||||
docEditor.setCursorPosition(54)
|
||||
docEditor.setCursorPosition(x)
|
||||
docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
|
||||
|
||||
mainMenu.aEditCut.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:104] == (
|
||||
assert docEditor.getText()[x:x+50] == (
|
||||
" nec erat ut nulla posuere commodo. Curabitur nisi"
|
||||
)
|
||||
|
||||
mainMenu.aEditPaste.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:104] == (
|
||||
assert docEditor.getText()[x:x+50] == (
|
||||
"Pellentesque nec erat ut nulla posuere commodo. Cu"
|
||||
)
|
||||
|
||||
docEditor.setCursorPosition(54)
|
||||
docEditor.setCursorPosition(x)
|
||||
docEditor._makeSelection(QTextCursor.SelectionType.WordUnderCursor)
|
||||
|
||||
mainMenu.aEditCopy.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:104] == (
|
||||
assert docEditor.getText()[x:x+50] == (
|
||||
"Pellentesque nec erat ut nulla posuere commodo. Cu"
|
||||
)
|
||||
|
||||
docEditor.setCursorPosition(54)
|
||||
docEditor.setCursorPosition(x)
|
||||
mainMenu.aEditPaste.activate(QAction.ActionEvent.Trigger)
|
||||
assert docEditor.getText()[54:104] == (
|
||||
assert docEditor.getText()[x:x+50] == (
|
||||
"PellentesquePellentesque nec erat ut nulla posuere"
|
||||
)
|
||||
mainMenu.aEditUndo.activate(QAction.ActionEvent.Trigger)
|
||||
|
||||
# Select Paragraph/All
|
||||
docEditor.setCursorPosition(57)
|
||||
docEditor.setCursorPosition(x+3)
|
||||
mainMenu.aSelectPar.activate(QAction.ActionEvent.Trigger)
|
||||
cursor = docEditor.textCursor()
|
||||
assert cursor.selectedText() == (
|
||||
@@ -228,10 +229,10 @@ def testGuiMainMenu_EditFormat(qtbot, monkeypatch, nwGUI, prjLipsum):
|
||||
"nunc lacus, imperdiet nec posuere ac, interdum non lectus."
|
||||
)
|
||||
|
||||
docEditor.setCursorPosition(57)
|
||||
docEditor.setCursorPosition(x+3)
|
||||
mainMenu.aSelectAll.activate(QAction.ActionEvent.Trigger)
|
||||
cursor = docEditor.textCursor()
|
||||
assert len(cursor.selectedText()) == 1910
|
||||
assert len(cursor.selectedText()) == 1928
|
||||
|
||||
# Clear the Text
|
||||
docEditor.clear()
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
"""
|
||||
novelWriter – Side Bar Class Tester
|
||||
===================================
|
||||
|
||||
This file is a part of novelWriter
|
||||
Copyright (C) 2025 Veronica Berglyd Olsen and novelWriter contributors
|
||||
|
||||
This program is free software: you can redistribute it and/or modify
|
||||
it under the terms of the GNU General Public License as published by
|
||||
the Free Software Foundation, either version 3 of the License, or
|
||||
(at your option) any later version.
|
||||
|
||||
This program is distributed in the hope that it will be useful, but
|
||||
WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
|
||||
General Public License for more details.
|
||||
|
||||
You should have received a copy of the GNU General Public License
|
||||
along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.constants import nwLabels
|
||||
from novelwriter.enum import nwTheme
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiSideBar_CycleColourTheme(nwGUI):
|
||||
"""Test theme cycle feature on the side bar."""
|
||||
CONFIG.themeMode = nwTheme.AUTO
|
||||
sidebar = nwGUI.sideBar
|
||||
sidebar.mainGui.checkThemeUpdate = lambda *a: None
|
||||
|
||||
# Run 3 Cycles
|
||||
for _ in range(3):
|
||||
|
||||
# Cycle Light
|
||||
sidebar._cycleColurTheme()
|
||||
assert CONFIG.themeMode == nwTheme.LIGHT
|
||||
assert sidebar.tbTheme.toolTip() == nwLabels.THEME_MODE_LABEL[nwTheme.LIGHT]
|
||||
|
||||
# Cycle Dark
|
||||
sidebar._cycleColurTheme()
|
||||
assert CONFIG.themeMode == nwTheme.DARK
|
||||
assert sidebar.tbTheme.toolTip() == nwLabels.THEME_MODE_LABEL[nwTheme.DARK]
|
||||
|
||||
# Cycle Auto
|
||||
sidebar._cycleColurTheme()
|
||||
assert CONFIG.themeMode == nwTheme.AUTO
|
||||
assert sidebar.tbTheme.toolTip() == nwLabels.THEME_MODE_LABEL[nwTheme.AUTO]
|
||||
@@ -31,7 +31,7 @@ from tests.tools import C, buildTestProject
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
|
||||
"""Test the the various features of the status bar."""
|
||||
"""Test the various features of the status bar."""
|
||||
buildTestProject(nwGUI, projPath)
|
||||
cHandle = SHARED.project.newFile("A Note", C.hCharRoot)
|
||||
newDoc = SHARED.project.storage.getDocument(cHandle)
|
||||
|
||||
+461
-294
@@ -20,344 +20,402 @@ along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import json
|
||||
|
||||
from configparser import ConfigParser
|
||||
from pathlib import Path
|
||||
from unittest.mock import MagicMock, Mock
|
||||
|
||||
import pytest
|
||||
|
||||
from PyQt6.QtGui import QColor, QIcon, QPalette, QPixmap
|
||||
from PyQt6.QtCore import Qt
|
||||
from PyQt6.QtGui import QColor, QFont, QFontDatabase, QIcon, QPalette, QPixmap, QStyleHints
|
||||
|
||||
from novelwriter import CONFIG, SHARED
|
||||
from novelwriter.common import NWConfigParser
|
||||
from novelwriter.config import DEF_GUI
|
||||
from novelwriter import CONFIG
|
||||
from novelwriter.config import DEF_GUI_DARK, DEF_GUI_LIGHT, DEF_ICONS
|
||||
from novelwriter.constants import nwLabels
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
|
||||
from novelwriter.gui.theme import _listConf
|
||||
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType, nwTheme
|
||||
from novelwriter.gui.theme import (
|
||||
STYLES_BIG_TOOLBUTTON, STYLES_FLAT_TABS, STYLES_MIN_TOOLBUTTON, GuiTheme,
|
||||
ThemeMeta, _listContent
|
||||
)
|
||||
|
||||
from tests.mocked import causeOSError
|
||||
from tests.tools import writeFile
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Main(qtbot, nwGUI, tstPaths):
|
||||
"""Test the theme class init."""
|
||||
mainTheme = SHARED.theme
|
||||
def testGuiTheme_ParseColor():
|
||||
"""Test the colour parsing."""
|
||||
theme = GuiTheme()
|
||||
|
||||
# Methods
|
||||
# =======
|
||||
# Pre-Populate
|
||||
theme._qColors["red"] = QColor(255, 0, 0)
|
||||
theme._qColors["green"] = QColor(0, 255, 0)
|
||||
theme._qColors["blue"] = QColor(0, 0, 255)
|
||||
theme._qColors["grey"] = QColor(127, 127, 127)
|
||||
|
||||
mSize = mainTheme.getTextWidth("m")
|
||||
assert mSize > 0
|
||||
assert mainTheme.getTextWidth("m", mainTheme.guiFont) == mSize
|
||||
# By Name
|
||||
assert theme.parseColor("red").getRgb() == (255, 0, 0, 255)
|
||||
assert theme.parseColor("green").getRgb() == (0, 255, 0, 255)
|
||||
assert theme.parseColor("blue").getRgb() == (0, 0, 255, 255)
|
||||
assert theme.parseColor("bob").getRgb() == (0, 0, 0, 255)
|
||||
|
||||
# Scan for Themes
|
||||
# ===============
|
||||
# CSS Format
|
||||
assert theme.parseColor("#ff0000").getRgb() == (255, 0, 0, 255)
|
||||
assert theme.parseColor("#ff00007f").getRgb() == (255, 0, 0, 127)
|
||||
assert theme.parseColor("#ff00").getRgb() == (0, 0, 0, 255) # Too short -> ignored
|
||||
assert theme.parseColor("#ff00007f15").getRgb() == (0, 0, 0, 255) # Too long -> ignored
|
||||
|
||||
result = {}
|
||||
_listConf({}, Path("not_a_path"), ".conf")
|
||||
assert result == {}
|
||||
# Name + Alpha
|
||||
assert theme.parseColor("red:255").getRgb() == (255, 0, 0, 255)
|
||||
assert theme.parseColor("red:127").getRgb() == (255, 0, 0, 127)
|
||||
assert theme.parseColor("red:512").getRgb() == (255, 0, 0, 255) # Value truncated
|
||||
|
||||
themeOne = tstPaths.cnfDir / "themes" / "themeone.conf"
|
||||
themeTwo = tstPaths.cnfDir / "themes" / "themetwo.conf"
|
||||
writeFile(themeOne, "# Stuff")
|
||||
writeFile(themeTwo, "# Stuff")
|
||||
# Name + Lighter
|
||||
assert theme.parseColor("grey:L100").getRgb() == (127, 127, 127, 255)
|
||||
assert theme.parseColor("grey:L150").getRgb() == (190, 190, 190, 255)
|
||||
assert theme.parseColor("grey:L50").getRgb() == (63, 63, 63, 255)
|
||||
|
||||
_listConf(result, tstPaths.cnfDir / "themes", ".conf")
|
||||
assert result["themeone"] == themeOne
|
||||
assert result["themetwo"] == themeTwo
|
||||
# Name + Darker
|
||||
assert theme.parseColor("grey:D100").getRgb() == (127, 127, 127, 255)
|
||||
assert theme.parseColor("grey:D150").getRgb() == (85, 85, 85, 255)
|
||||
assert theme.parseColor("grey:D50").getRgb() == (254, 254, 254, 255)
|
||||
|
||||
# Parse Colours
|
||||
# =============
|
||||
|
||||
parser = NWConfigParser()
|
||||
parser["Palette"] = {
|
||||
"colour1": "100, 150, 200", # Valid
|
||||
"colour2": "100, 150, 200, 250", # With alpha
|
||||
"colour3": "100, 150, 200, 250, 300", # Too many values
|
||||
"colour4": "250, 250", # Missing blue
|
||||
"colour5": "-10, 127, 300", # Invalid red and blue
|
||||
"colour6": "bob, 127, 255", # Invalid red
|
||||
}
|
||||
|
||||
# Test the parser for several valid and invalid values
|
||||
assert mainTheme._parseColor(parser, "Palette", "colour1").getRgb() == (100, 150, 200, 255)
|
||||
assert mainTheme._parseColor(parser, "Palette", "colour2").getRgb() == (100, 150, 200, 250)
|
||||
assert mainTheme._parseColor(parser, "Palette", "colour3").getRgb() == (100, 150, 200, 250)
|
||||
assert mainTheme._parseColor(parser, "Palette", "colour4").getRgb() == (250, 250, 0, 255)
|
||||
assert mainTheme._parseColor(parser, "Palette", "colour5").getRgb() == (0, 0, 0, 0)
|
||||
assert mainTheme._parseColor(parser, "Palette", "colour6").getRgb() == (0, 127, 255, 255)
|
||||
|
||||
# The palette should load with the parsed values
|
||||
mainTheme._setPalette(parser, "Palette", "colour1", QPalette.ColorRole.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 255)
|
||||
mainTheme._setPalette(parser, "Palette", "colour2", QPalette.ColorRole.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250)
|
||||
mainTheme._setPalette(parser, "Palette", "colour3", QPalette.ColorRole.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (100, 150, 200, 250)
|
||||
mainTheme._setPalette(parser, "Palette", "colour4", QPalette.ColorRole.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (250, 250, 0, 255)
|
||||
mainTheme._setPalette(parser, "Palette", "colour5", QPalette.ColorRole.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 0)
|
||||
mainTheme._setPalette(parser, "Palette", "colour6", QPalette.ColorRole.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 127, 255, 255)
|
||||
|
||||
# Non-existing value should return default colour
|
||||
mainTheme._setPalette(parser, "Palette", "stuff", QPalette.ColorRole.Window)
|
||||
assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == (0, 0, 0, 255)
|
||||
|
||||
# qtbot.stop()
|
||||
# Values
|
||||
assert theme.parseColor("255, 0, 0").getRgb() == (255, 0, 0, 255)
|
||||
assert theme.parseColor("255, 0, 0, 255").getRgb() == (255, 0, 0, 255)
|
||||
assert theme.parseColor("255, 0, 0, 127").getRgb() == (255, 0, 0, 127)
|
||||
assert theme.parseColor("255, 0, 0, 127, 42").getRgb() == (255, 0, 0, 127) # Truncated
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Theme(qtbot, monkeypatch, nwGUI, tstPaths):
|
||||
"""Test the theme part of the class."""
|
||||
mainTheme = SHARED.theme
|
||||
def testGuiTheme_ScanThemes(monkeypatch):
|
||||
"""Test the themes scanning."""
|
||||
theme = GuiTheme()
|
||||
|
||||
# List Themes
|
||||
# Load built-in themes
|
||||
files = []
|
||||
_listContent(files, CONFIG.assetPath("themes"), ".conf")
|
||||
assert len(files) > 0
|
||||
|
||||
# Block reading theme files
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(ConfigParser, "read", causeOSError)
|
||||
theme._scanThemes(files)
|
||||
assert theme.colourThemes == {}
|
||||
|
||||
# Read all themes correctly
|
||||
theme._scanThemes(files)
|
||||
assert len(theme.colourThemes) > 0
|
||||
|
||||
dark = theme.colourThemes[DEF_GUI_DARK]
|
||||
light = theme.colourThemes[DEF_GUI_LIGHT]
|
||||
|
||||
assert dark.name == "Default Dark Theme"
|
||||
assert dark.dark is True
|
||||
assert light.name == "Default Light Theme"
|
||||
assert light.dark is False
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_LoadThemes(monkeypatch):
|
||||
"""Test loading themes."""
|
||||
theme = GuiTheme()
|
||||
theme.iconCache = MagicMock()
|
||||
CONFIG.lightTheme = DEF_GUI_LIGHT
|
||||
CONFIG.darkTheme = DEF_GUI_DARK
|
||||
|
||||
# Load built-in themes
|
||||
files = []
|
||||
_listContent(files, CONFIG.assetPath("themes"), ".conf")
|
||||
theme._scanThemes(files)
|
||||
assert DEF_GUI_LIGHT in theme._allThemes
|
||||
assert DEF_GUI_DARK in theme._allThemes
|
||||
assert theme._currentTheme == ""
|
||||
|
||||
# Load light theme
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
theme.loadTheme()
|
||||
assert theme._currentTheme == DEF_GUI_LIGHT
|
||||
|
||||
# Load dark theme
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
theme.loadTheme()
|
||||
assert theme._currentTheme == DEF_GUI_DARK
|
||||
|
||||
# Let auto switch back to light, then dark
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(CONFIG, "verQtValue", 0x060500)
|
||||
mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Light)
|
||||
CONFIG.themeMode = nwTheme.AUTO
|
||||
theme.loadTheme()
|
||||
assert theme._currentTheme == DEF_GUI_LIGHT
|
||||
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(CONFIG, "verQtValue", 0x060500)
|
||||
mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Dark)
|
||||
CONFIG.themeMode = nwTheme.AUTO
|
||||
theme.loadTheme()
|
||||
assert theme._currentTheme == DEF_GUI_DARK
|
||||
|
||||
# Error Cases
|
||||
# ===========
|
||||
|
||||
# Block the reading of the files
|
||||
# Invalid light theme
|
||||
CONFIG.lightTheme = "not_a_theme"
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
theme.loadTheme()
|
||||
assert theme._currentTheme == DEF_GUI_LIGHT
|
||||
|
||||
# Invalid dark theme
|
||||
CONFIG.darkTheme = "not_a_theme"
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
theme.loadTheme()
|
||||
assert theme._currentTheme == DEF_GUI_DARK
|
||||
|
||||
# Clear meta and check exit early cases
|
||||
theme._meta = ThemeMeta()
|
||||
assert theme._meta.name == ""
|
||||
|
||||
# Reload dark should not load anything
|
||||
CONFIG.darkTheme = DEF_GUI_DARK
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
theme.loadTheme()
|
||||
assert theme._meta.name == ""
|
||||
|
||||
# Force reload, but fail parsing
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert mainTheme.listThemes() == []
|
||||
mp.setattr(ConfigParser, "read", causeOSError)
|
||||
CONFIG.darkTheme = DEF_GUI_DARK
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
theme.loadTheme(force=True)
|
||||
assert theme._meta.name == ""
|
||||
|
||||
# Load the theme info, default themes first
|
||||
themesList = mainTheme.listThemes()
|
||||
assert themesList[0] == ("default_dark", "Default Dark Theme")
|
||||
assert themesList[1] == ("default_light", "Default Light Theme")
|
||||
assert themesList[2] == ("cyberpunk_night", "Cyberpunk Night")
|
||||
assert themesList[3] == ("dracula", "Dracula")
|
||||
# Invalid theme, and defaults are missing
|
||||
del theme._allThemes[DEF_GUI_DARK]
|
||||
del theme._allThemes[DEF_GUI_LIGHT]
|
||||
|
||||
# A second call should returned the cached list
|
||||
assert mainTheme.listThemes() == mainTheme._themeList
|
||||
CONFIG.lightTheme = "not_a_theme"
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
theme.loadTheme(force=True)
|
||||
assert theme._meta.name == ""
|
||||
|
||||
# Check handling of broken theme settings
|
||||
CONFIG.guiTheme = "not_a_theme"
|
||||
availThemes = mainTheme._availThemes
|
||||
mainTheme._availThemes = {}
|
||||
assert mainTheme.loadTheme() is False
|
||||
mainTheme._availThemes = availThemes
|
||||
CONFIG.darkTheme = "not_a_theme"
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
theme.loadTheme(force=True)
|
||||
assert theme._meta.name == ""
|
||||
|
||||
# Check handling of unreadable file
|
||||
CONFIG.guiTheme = DEF_GUI
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert mainTheme.loadTheme() is False
|
||||
|
||||
# Load Default Theme
|
||||
# ==================
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_SpecialColors(tstPaths):
|
||||
"""Test handling special cases for colours."""
|
||||
theme = GuiTheme()
|
||||
theme.iconCache = MagicMock()
|
||||
|
||||
if sys.platform != "win32":
|
||||
# Set a mock colour for the window background
|
||||
mainTheme._guiPalette.color(QPalette.ColorRole.Window).setRgb(0, 0, 0, 0)
|
||||
|
||||
# Load the default theme
|
||||
CONFIG.guiTheme = DEF_GUI
|
||||
assert mainTheme.loadTheme() is True
|
||||
|
||||
# This should load a standard palette
|
||||
wCol = QPalette().color(QPalette.ColorRole.Window).getRgb()
|
||||
assert mainTheme._guiPalette.color(QPalette.ColorRole.Window).getRgb() == wCol
|
||||
|
||||
# Mock Dark Theme
|
||||
# ===============
|
||||
|
||||
mockTheme: Path = tstPaths.cnfDir / "themes" / "test.conf"
|
||||
mockTheme.write_text(
|
||||
testTheme: Path = tstPaths.cnfDir / "themes" / "test.conf"
|
||||
testTheme.write_text(
|
||||
"[Main]\n"
|
||||
"name = Test\n"
|
||||
"mode = light\n"
|
||||
"\n"
|
||||
"[Base]\n"
|
||||
"default = #cccccc\n"
|
||||
"faded = #949494\n"
|
||||
"red = #ff0000\n"
|
||||
"orange = #ff7f00\n"
|
||||
"yellow = #ffff00\n"
|
||||
"green = #00ff00\n"
|
||||
"cyan = #00ffff\n"
|
||||
"blue = #0000ff\n"
|
||||
"purple = #ff00ff\n"
|
||||
"\n"
|
||||
"[Project]\n"
|
||||
"root = blue\n"
|
||||
"folder = yellow\n"
|
||||
"file = default\n"
|
||||
"title = green\n"
|
||||
"chapter = red\n"
|
||||
"scene = blue\n"
|
||||
"note = yellow\n"
|
||||
"\n"
|
||||
"[Palette]\n"
|
||||
"window = 0, 0, 0\n"
|
||||
"text = 255, 255, 255\n"
|
||||
"window = #000000\n"
|
||||
"text = #ffffff\n"
|
||||
)
|
||||
mainTheme._availThemes["test"] = mockTheme
|
||||
theme._scanThemes([testTheme])
|
||||
assert len(theme.colourThemes) == 1
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
CONFIG.lightTheme = "test"
|
||||
|
||||
CONFIG.guiTheme = "test"
|
||||
assert mainTheme.loadTheme() is True
|
||||
assert mainTheme._guiPalette.window().color().getRgb() == (0, 0, 0, 255)
|
||||
assert mainTheme._guiPalette.text().color().getRgb() == (255, 255, 255, 255)
|
||||
assert mainTheme._guiPalette.light().color().getRgb() == (57, 57, 57, 255)
|
||||
assert mainTheme.isDarkTheme is True
|
||||
# Load theme
|
||||
theme.loadTheme()
|
||||
|
||||
# Load Default Light Theme
|
||||
# ========================
|
||||
# Since window is black, a lighter version should be generated
|
||||
assert theme._guiPalette.light().color().getRgb() == (57, 57, 57, 255)
|
||||
|
||||
CONFIG.guiTheme = "default_light"
|
||||
assert mainTheme.loadTheme() is True
|
||||
# Reload with project override to red
|
||||
CONFIG.iconColTree = "red"
|
||||
CONFIG.iconColDocs = True
|
||||
theme.loadTheme(force=True)
|
||||
|
||||
# Check a few values
|
||||
assert mainTheme._guiPalette.color(
|
||||
QPalette.ColorRole.Window
|
||||
).getRgb() == (239, 239, 239, 255)
|
||||
assert mainTheme._guiPalette.color(
|
||||
QPalette.ColorRole.WindowText
|
||||
).getRgb() == (0, 0, 0, 255)
|
||||
assert mainTheme._guiPalette.color(
|
||||
QPalette.ColorRole.Base
|
||||
).getRgb() == (255, 255, 255, 255)
|
||||
assert mainTheme._guiPalette.color(
|
||||
QPalette.ColorRole.AlternateBase
|
||||
).getRgb() == (224, 224, 224, 255)
|
||||
assert theme.getBaseColor("root").getRgb() == (255, 0, 0, 255)
|
||||
assert theme.getBaseColor("folder").getRgb() == (255, 0, 0, 255)
|
||||
assert theme.getBaseColor("file").getRgb() == (204, 204, 204, 255)
|
||||
assert theme.getBaseColor("title").getRgb() == (0, 255, 0, 255)
|
||||
assert theme.getBaseColor("chapter").getRgb() == (255, 0, 0, 255)
|
||||
assert theme.getBaseColor("scene").getRgb() == (0, 0, 255, 255)
|
||||
assert theme.getBaseColor("note").getRgb() == (255, 255, 0, 255)
|
||||
|
||||
# Load Default Dark Theme
|
||||
# =======================
|
||||
|
||||
CONFIG.guiTheme = "default_dark"
|
||||
assert mainTheme.loadTheme() is True
|
||||
|
||||
# Check a few values
|
||||
assert mainTheme._guiPalette.color(
|
||||
QPalette.ColorRole.Window).getRgb() == (54, 54, 54, 255)
|
||||
assert mainTheme._guiPalette.color(
|
||||
QPalette.ColorRole.WindowText).getRgb() == (204, 204, 204, 255)
|
||||
assert mainTheme._guiPalette.color(
|
||||
QPalette.ColorRole.Base).getRgb() == (62, 62, 62, 255)
|
||||
assert mainTheme._guiPalette.color(
|
||||
QPalette.ColorRole.AlternateBase).getRgb() == (78, 78, 78, 255)
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_Syntax(qtbot, monkeypatch, nwGUI):
|
||||
"""Test the syntax part of the class."""
|
||||
mainTheme = SHARED.theme
|
||||
|
||||
# List Themes
|
||||
# ===========
|
||||
|
||||
# Block the reading of the files
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert mainTheme.listThemes() == []
|
||||
|
||||
# Load the syntax info
|
||||
syntaxList = mainTheme.listSyntax()
|
||||
assert syntaxList[0] == ("default_dark", "Default Dark")
|
||||
assert syntaxList[1] == ("default_light", "Default Light")
|
||||
|
||||
# A second call should returned the cached list
|
||||
assert mainTheme.listSyntax() == mainTheme._syntaxList
|
||||
|
||||
# Check handling of broken theme settings
|
||||
availSyntax = mainTheme._availSyntax
|
||||
mainTheme._availSyntax = {}
|
||||
CONFIG.guiSyntax = "not_a_syntax"
|
||||
assert mainTheme.loadSyntax() is False
|
||||
mainTheme._availSyntax = availSyntax
|
||||
|
||||
# Check handling of unreadable file
|
||||
CONFIG.guiSyntax = "default_light"
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert mainTheme.loadSyntax() is False
|
||||
|
||||
# Load Default Light Syntax
|
||||
# =========================
|
||||
|
||||
# Load the default syntax
|
||||
CONFIG.guiSyntax = "default_light"
|
||||
assert mainTheme.loadSyntax() is True
|
||||
|
||||
# Check some values
|
||||
assert mainTheme.syntaxMeta.name == "Default Light"
|
||||
assert mainTheme.syntaxTheme.back == QColor(255, 255, 255)
|
||||
assert mainTheme.syntaxTheme.text == QColor(0, 0, 0)
|
||||
assert mainTheme.syntaxTheme.link == QColor(0, 0, 200)
|
||||
|
||||
# Load Default Dark Theme
|
||||
# =======================
|
||||
|
||||
# Load the default syntax
|
||||
CONFIG.guiSyntax = "default_dark"
|
||||
assert mainTheme.loadSyntax() is True
|
||||
|
||||
# Check some values
|
||||
assert mainTheme.syntaxMeta.name == "Default Dark"
|
||||
assert mainTheme.syntaxTheme.back == QColor(42, 42, 42)
|
||||
assert mainTheme.syntaxTheme.text == QColor(204, 204, 204)
|
||||
assert mainTheme.syntaxTheme.link == QColor(102, 153, 204)
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_IconThemes(qtbot, caplog, monkeypatch, nwGUI, tstPaths):
|
||||
"""Test the icon cache class."""
|
||||
iconCache = SHARED.theme.iconCache
|
||||
|
||||
# Load Theme
|
||||
# ==========
|
||||
|
||||
# Invalid theme name
|
||||
availThemes = iconCache._availThemes
|
||||
iconCache._availThemes = {}
|
||||
assert iconCache.loadTheme("not_a_theme") is False
|
||||
iconCache._availThemes = availThemes
|
||||
|
||||
# Check handling of unreadable file
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert iconCache.loadTheme("material_rounded_normal") is False
|
||||
|
||||
# Load working theme file
|
||||
assert iconCache.loadTheme("material_rounded_normal") is True
|
||||
assert iconCache.themeMeta.name == "Material Symbols - Rounded"
|
||||
|
||||
# Load with project colour override
|
||||
purple = iconCache._svgColors["purple"]
|
||||
assert iconCache._svgColors["root"] != purple
|
||||
assert iconCache._svgColors["folder"] != purple
|
||||
assert iconCache._svgColors["file"] != purple
|
||||
assert iconCache._svgColors["title"] != purple
|
||||
assert iconCache._svgColors["chapter"] != purple
|
||||
assert iconCache._svgColors["scene"] != purple
|
||||
assert iconCache._svgColors["note"] != purple
|
||||
assert theme.getRawBaseColor("root") == b"#ff0000"
|
||||
assert theme.getRawBaseColor("folder") == b"#ff0000"
|
||||
assert theme.getRawBaseColor("file") == b"#cccccc"
|
||||
assert theme.getRawBaseColor("title") == b"#00ff00"
|
||||
assert theme.getRawBaseColor("chapter") == b"#ff0000"
|
||||
assert theme.getRawBaseColor("scene") == b"#0000ff"
|
||||
assert theme.getRawBaseColor("note") == b"#ffff00"
|
||||
|
||||
# Reload with project override to purple, also for docs
|
||||
CONFIG.iconColTree = "purple"
|
||||
assert iconCache.loadTheme("material_rounded_normal") is True
|
||||
assert iconCache._svgColors["root"] == purple
|
||||
assert iconCache._svgColors["folder"] == purple
|
||||
assert iconCache._svgColors["file"] == purple
|
||||
assert iconCache._svgColors["title"] == purple
|
||||
assert iconCache._svgColors["chapter"] == purple
|
||||
assert iconCache._svgColors["scene"] == purple
|
||||
assert iconCache._svgColors["note"] == purple
|
||||
CONFIG.iconColDocs = False
|
||||
theme.loadTheme(force=True)
|
||||
|
||||
# Change some colours
|
||||
iconCache.setIconColor("root", QColor(255, 255, 255))
|
||||
assert iconCache._svgColors["root"] != purple
|
||||
assert iconCache._svgColors["root"] == b"#ffffff"
|
||||
assert theme.getBaseColor("root").getRgb() == (255, 0, 255, 255)
|
||||
assert theme.getBaseColor("folder").getRgb() == (255, 0, 255, 255)
|
||||
assert theme.getBaseColor("file").getRgb() == (255, 0, 255, 255)
|
||||
assert theme.getBaseColor("title").getRgb() == (255, 0, 255, 255)
|
||||
assert theme.getBaseColor("chapter").getRgb() == (255, 0, 255, 255)
|
||||
assert theme.getBaseColor("scene").getRgb() == (255, 0, 255, 255)
|
||||
assert theme.getBaseColor("note").getRgb() == (255, 0, 255, 255)
|
||||
|
||||
# List Themes
|
||||
# ===========
|
||||
|
||||
# Load error returns empty list
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
themes = iconCache.listThemes()
|
||||
assert themes == []
|
||||
|
||||
# Successful read
|
||||
themes = iconCache.listThemes()
|
||||
assert len(themes) > 1
|
||||
assert "material_rounded_normal" in dict(themes)
|
||||
|
||||
# Load error doesn't matter on second read since list is cached
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
assert iconCache.listThemes() == themes
|
||||
|
||||
# qtbot.stop()
|
||||
assert theme.getRawBaseColor("root") == b"#ff00ff"
|
||||
assert theme.getRawBaseColor("folder") == b"#ff00ff"
|
||||
assert theme.getRawBaseColor("file") == b"#ff00ff"
|
||||
assert theme.getRawBaseColor("title") == b"#ff00ff"
|
||||
assert theme.getRawBaseColor("chapter") == b"#ff00ff"
|
||||
assert theme.getRawBaseColor("scene") == b"#ff00ff"
|
||||
assert theme.getRawBaseColor("note") == b"#ff00ff"
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_LoadIcons(qtbot, nwGUI):
|
||||
def testGuiTheme_Methods(monkeypatch):
|
||||
"""Test other themes methods."""
|
||||
theme = GuiTheme()
|
||||
theme.iconCache = MagicMock()
|
||||
CONFIG.darkTheme = DEF_GUI_DARK
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
|
||||
# Init theme
|
||||
assert theme.colourThemes == {}
|
||||
theme.initThemes()
|
||||
assert theme._meta.name == "Default Dark Theme"
|
||||
|
||||
# Text width
|
||||
theme.guiFont = QFontDatabase.systemFont(QFontDatabase.SystemFont.GeneralFont)
|
||||
assert theme.getTextWidth("MMMMM") > theme.getTextWidth("MMM")
|
||||
font = QFont(theme.guiFont)
|
||||
font.setPointSizeF(0.5*font.pointSizeF())
|
||||
assert theme.getTextWidth("MMMMM", font) < theme.getTextWidth("MMMMM")
|
||||
|
||||
# Detect desktop mode Qt 6.5+
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(CONFIG, "verQtValue", 0x060500)
|
||||
|
||||
mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Light)
|
||||
assert theme.isDesktopDarkMode() is False
|
||||
|
||||
mp.setattr(QStyleHints, "colorScheme", lambda *a: Qt.ColorScheme.Dark)
|
||||
assert theme.isDesktopDarkMode() is True
|
||||
|
||||
# Detect desktop mode Qt 6.4
|
||||
mockWhite = Mock()
|
||||
mockWhite.color.return_value = QColor(255, 255, 255)
|
||||
|
||||
mockBlack = Mock()
|
||||
mockBlack.color.return_value = QColor(0, 0, 0)
|
||||
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr(CONFIG, "verQtValue", 0x060400)
|
||||
|
||||
mp.setattr(QPalette, "window", lambda *a: mockWhite)
|
||||
mp.setattr(QPalette, "windowText", lambda *a: mockBlack)
|
||||
assert theme.isDesktopDarkMode() is False
|
||||
|
||||
mp.setattr(QPalette, "window", lambda *a: mockBlack)
|
||||
mp.setattr(QPalette, "windowText", lambda *a: mockWhite)
|
||||
assert theme.isDesktopDarkMode() is True
|
||||
|
||||
# Stylesheets
|
||||
assert theme.getStyleSheet(STYLES_FLAT_TABS) != ""
|
||||
assert theme.getStyleSheet(STYLES_MIN_TOOLBUTTON) != ""
|
||||
assert theme.getStyleSheet(STYLES_BIG_TOOLBUTTON) != ""
|
||||
assert theme.getStyleSheet("stuff") == ""
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_ScanIcons(monkeypatch):
|
||||
"""Test the icon theme scanning."""
|
||||
theme = GuiTheme()
|
||||
|
||||
# Load built-in themes
|
||||
files = []
|
||||
_listContent(files, CONFIG.assetPath("icons"), ".icons")
|
||||
assert len(files) > 0
|
||||
|
||||
# Block reading theme files
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
theme.iconCache._scanThemes(files)
|
||||
assert theme.iconCache.iconThemes == {}
|
||||
|
||||
# Read all themes correctly
|
||||
theme.iconCache._scanThemes(files)
|
||||
assert len(theme.iconCache.iconThemes) > 0
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_IconThemes(monkeypatch):
|
||||
"""Test loading icon theme."""
|
||||
CONFIG.lightTheme = DEF_GUI_LIGHT
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
CONFIG.iconTheme = DEF_ICONS
|
||||
|
||||
theme = GuiTheme()
|
||||
|
||||
# Init should load default theme
|
||||
theme.initThemes()
|
||||
assert theme.iconCache._meta.name == "Material Symbols - Rounded"
|
||||
assert DEF_ICONS in theme.iconCache.iconThemes
|
||||
|
||||
# Load default theme directly
|
||||
theme.iconCache._meta = ThemeMeta()
|
||||
theme.iconCache.loadTheme("DEF_ICONS")
|
||||
assert theme.iconCache._meta.name == "Material Symbols - Rounded"
|
||||
|
||||
# Failed loading should load nothing
|
||||
theme.iconCache._meta = ThemeMeta()
|
||||
with monkeypatch.context() as mp:
|
||||
mp.setattr("builtins.open", causeOSError)
|
||||
theme.iconCache.loadTheme("DEF_ICONS")
|
||||
assert theme.iconCache._meta.name == ""
|
||||
|
||||
# Reload with non-existent theme should reload default
|
||||
theme.iconCache._meta = ThemeMeta()
|
||||
theme.iconCache.loadTheme("not_a_theme")
|
||||
assert theme.iconCache._meta.name == "Material Symbols - Rounded"
|
||||
|
||||
# If default theme is missing, load nothing
|
||||
del theme.iconCache._allThemes[DEF_ICONS]
|
||||
theme.iconCache._meta = ThemeMeta()
|
||||
theme.iconCache.loadTheme("not_a_theme")
|
||||
assert theme.iconCache._meta.name == ""
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_LoadIcons():
|
||||
"""Test the icon cache class."""
|
||||
iconCache = SHARED.theme.iconCache
|
||||
assert iconCache.loadTheme("material_rounded_normal") is True
|
||||
theme = GuiTheme()
|
||||
theme.initThemes()
|
||||
iconCache = theme.iconCache
|
||||
|
||||
# Load Icons
|
||||
# ==========
|
||||
@@ -455,14 +513,13 @@ def testGuiTheme_LoadIcons(qtbot, nwGUI):
|
||||
nwItemType.NO_TYPE, nwItemClass.NOVEL, nwItemLayout.DOCUMENT, hLevel="H0"
|
||||
) == iconCache._noIcon
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
def testGuiTheme_LoadDecorations(qtbot, monkeypatch, nwGUI):
|
||||
def testGuiTheme_LoadDecorations(monkeypatch):
|
||||
"""Test the icon cache class."""
|
||||
iconCache = SHARED.theme.iconCache
|
||||
assert iconCache.loadTheme("material_rounded_normal") is True
|
||||
theme = GuiTheme()
|
||||
theme.initThemes()
|
||||
iconCache = theme.iconCache
|
||||
|
||||
# Load Decorations
|
||||
# ================
|
||||
@@ -520,4 +577,114 @@ def testGuiTheme_LoadDecorations(qtbot, monkeypatch, nwGUI):
|
||||
assert iconCache.getHeaderDecorationNarrow(5) == iconCache._headerDecNarrow[5]
|
||||
assert iconCache.getHeaderDecorationNarrow(6) == iconCache._headerDecNarrow[5]
|
||||
|
||||
# qtbot.stop()
|
||||
|
||||
THEMES = []
|
||||
_listContent(THEMES, CONFIG.assetPath("themes"), ".conf")
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.parametrize("theme", [a.stem for a in THEMES])
|
||||
def testGuiTheme_CheckTheme(theme):
|
||||
"""Test loading all themes."""
|
||||
themes = GuiTheme()
|
||||
themes.iconCache = MagicMock()
|
||||
themes._scanThemes(THEMES)
|
||||
|
||||
assert theme in themes.colourThemes
|
||||
current = themes.colourThemes[theme]
|
||||
if current.dark:
|
||||
CONFIG.darkTheme = theme
|
||||
CONFIG.themeMode = nwTheme.DARK
|
||||
else:
|
||||
CONFIG.lightTheme = theme
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
|
||||
# Check loading
|
||||
themes.loadTheme()
|
||||
assert themes._meta.name == current.name
|
||||
assert themes.isDarkTheme == current.dark
|
||||
|
||||
# Check completeness
|
||||
parser = ConfigParser()
|
||||
parser.read(current.path, encoding="utf-8")
|
||||
|
||||
sections = ["Main", "Base", "Project", "Palette", "GUI", "Syntax"]
|
||||
assert sorted(parser.sections()) == sorted(sections)
|
||||
|
||||
structure = {
|
||||
"Main": [
|
||||
"name", "mode", "author", # The rest are not required
|
||||
],
|
||||
"Base": [
|
||||
"base", "default", "faded", "red", "orange", "yellow", "green",
|
||||
"cyan", "blue", "purple",
|
||||
],
|
||||
"Project": [
|
||||
"root", "folder", "file", "title", "chapter", "scene", "note",
|
||||
"active", "inactive", "disabled",
|
||||
],
|
||||
"Palette": [
|
||||
"window", "windowtext", "base", "alternatebase", "text",
|
||||
"tooltipbase", "tooltiptext", "button", "buttontext", "brighttext",
|
||||
"highlight", "highlightedtext", "link", "linkvisited", "accent",
|
||||
],
|
||||
"GUI": [
|
||||
"helptext", "fadedtext", "errortext",
|
||||
],
|
||||
"Syntax": [
|
||||
"background", "text", "line", "link", "headertext", "headertag",
|
||||
"emphasis", "dialog", "altdialog", "hidden", "note", "shortcode",
|
||||
"keyword", "tag", "value", "optional", "spellcheckline",
|
||||
"errorline", "replacetag", "modifier", "texthighlight",
|
||||
],
|
||||
}
|
||||
optional = ["credit", "url"]
|
||||
missing = []
|
||||
for section, options in structure.items():
|
||||
missing.extend(opt for opt in options if opt not in parser[section])
|
||||
assert missing == [], "Missing options in theme file"
|
||||
|
||||
# Check deprecated
|
||||
deprecated = []
|
||||
for section in sections:
|
||||
deprecated.extend(
|
||||
opt for opt in parser[section]
|
||||
if opt not in structure[section] and opt not in optional
|
||||
)
|
||||
assert deprecated == [], "Deprecated options in theme file"
|
||||
|
||||
|
||||
ICONS = []
|
||||
_listContent(ICONS, CONFIG.assetPath("icons"), ".icons")
|
||||
|
||||
|
||||
@pytest.mark.gui
|
||||
@pytest.mark.parametrize("icons", [a.stem for a in ICONS])
|
||||
def testGuiTheme_CheckIcons(icons, tstPaths):
|
||||
"""Test loading all icons."""
|
||||
keysFile: Path = tstPaths.filesDir / "all_icons.json"
|
||||
iconKeys = json.loads(keysFile.read_text(encoding="utf-8"))
|
||||
assert isinstance(iconKeys, list)
|
||||
|
||||
CONFIG.lightTheme = DEF_GUI_LIGHT
|
||||
CONFIG.themeMode = nwTheme.LIGHT
|
||||
|
||||
themes = GuiTheme()
|
||||
themes.initThemes()
|
||||
iconCache = themes.iconCache
|
||||
|
||||
assert icons in iconCache.iconThemes
|
||||
current = iconCache.iconThemes[icons]
|
||||
CONFIG.iconTheme = icons
|
||||
|
||||
# Check loading
|
||||
iconCache.loadTheme(icons)
|
||||
assert iconCache._meta.name == current.name
|
||||
|
||||
# Check completeness
|
||||
missing = [key for key in iconKeys if key not in iconCache._svgData]
|
||||
assert missing == [], "Missing keys in icons file"
|
||||
|
||||
# Check deprecated
|
||||
deprecated = [key for key in iconCache._svgData if key not in iconKeys]
|
||||
assert deprecated == [], "Deprecated keys in icons file"
|
||||
|
||||
@@ -157,6 +157,15 @@ def testTextPatterns_Markdown():
|
||||
assert allMatches(regEx, "one ~two~~ three") == []
|
||||
assert allMatches(regEx, "one~~two~~three") == []
|
||||
|
||||
# Mark
|
||||
regEx = REGEX_PATTERNS.markdownMark
|
||||
assert allMatches(regEx, "one ==two== three") == [
|
||||
[("==two==", 4, 11), ("==", 4, 6), ("two", 6, 9), ("==", 9, 11)]
|
||||
]
|
||||
assert allMatches(regEx, "one ==two= three") == []
|
||||
assert allMatches(regEx, "one =two== three") == []
|
||||
assert allMatches(regEx, "one==two==three") == []
|
||||
|
||||
|
||||
@pytest.mark.core
|
||||
def testTextPatterns_ShortcodesPlain():
|
||||
|
||||
Reference in New Issue
Block a user