Add a setting for using character count as main count (#2323)

This commit is contained in:
Veronica Berglyd Olsen
2025-05-18 16:32:20 +02:00
committed by GitHub
43 changed files with 559 additions and 284 deletions
+20 -4
View File
@@ -919,9 +919,10 @@ class RecentProjects:
puuid = str(entry.get("uuid", "")) puuid = str(entry.get("uuid", ""))
title = str(entry.get("title", "")) title = str(entry.get("title", ""))
words = checkInt(entry.get("words", 0), 0) words = checkInt(entry.get("words", 0), 0)
chars = checkInt(entry.get("chars", 0), 0)
saved = checkInt(entry.get("time", 0), 0) saved = checkInt(entry.get("time", 0), 0)
if path and title: if path and title:
self._setEntry(puuid, path, title, words, saved) self._setEntry(puuid, path, title, words, chars, saved)
except Exception: except Exception:
logger.error("Could not load recent project cache") logger.error("Could not load recent project cache")
logException() logException()
@@ -954,7 +955,14 @@ class RecentProjects:
try: try:
if (remove := self._map.get(data.uuid)) and (remove != str(path)): if (remove := self._map.get(data.uuid)) and (remove != str(path)):
self.remove(remove) self.remove(remove)
self._setEntry(data.uuid, str(path), data.name, sum(data.currCounts), int(saved)) self._setEntry(
data.uuid,
str(path),
data.name,
sum(data.currCounts[:2]),
sum(data.currCounts[2:]),
int(saved),
)
self.saveCache() self.saveCache()
except Exception: except Exception:
pass pass
@@ -967,9 +975,17 @@ class RecentProjects:
self.saveCache() self.saveCache()
return return
def _setEntry(self, puuid: str, path: str, title: str, words: int, saved: int) -> None: def _setEntry(
self, puuid: str, path: str, title: str, words: int, chars: int, saved: int
) -> None:
"""Set an entry in the recent projects record.""" """Set an entry in the recent projects record."""
self._data[path] = {"uuid": puuid, "title": title, "words": words, "time": saved} self._data[path] = {
"uuid": puuid,
"title": title,
"words": words,
"chars": chars,
"time": saved,
}
if puuid: if puuid:
self._map[puuid] = path self._map[puuid] = path
return return
+4
View File
@@ -352,6 +352,10 @@ class nwLabels:
nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Stats", "Words in Text"), nwStats.WORDS_TEXT: QT_TRANSLATE_NOOP("Stats", "Words in Text"),
nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Stats", "Words in Headings"), nwStats.WORDS_TITLE: QT_TRANSLATE_NOOP("Stats", "Words in Headings"),
} }
STATS_DISPLAY: Final[dict[str, str]] = {
nwStats.CHARS: QT_TRANSLATE_NOOP("Stats", "Characters: {0} ({1})"),
nwStats.WORDS: QT_TRANSLATE_NOOP("Stats", "Words: {0} ({1})"),
}
BUILD_FMT: Final[dict[nwBuildFmt, str]] = { BUILD_FMT: Final[dict[nwBuildFmt, str]] = {
nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"), nwBuildFmt.ODT: QT_TRANSLATE_NOOP("Constant", "Open Document (.odt)"),
nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"), nwBuildFmt.FODT: QT_TRANSLATE_NOOP("Constant", "Flat Open Document (.fodt)"),
+30 -23
View File
@@ -53,10 +53,10 @@ class NWItem:
""" """
__slots__ = ( __slots__ = (
"_active", "_charCount", "_class", "_cursorPos", "_expanded", "_active", "_charCount", "_charInit", "_class", "_cursorPos",
"_handle", "_heading", "_import", "_initCount", "_layout", "_name", "_expanded", "_handle", "_heading", "_import", "_layout", "_name",
"_order", "_paraCount", "_parent", "_project", "_root", "_status", "_order", "_paraCount", "_parent", "_project", "_root", "_status",
"_type", "_wordCount", "_type", "_wordCount", "_wordInit",
) )
def __init__(self, project: NWProject, handle: str) -> None: def __init__(self, project: NWProject, handle: str) -> None:
@@ -81,7 +81,8 @@ class NWItem:
self._wordCount = 0 # Current word count self._wordCount = 0 # Current word count
self._paraCount = 0 # Current paragraph count self._paraCount = 0 # Current paragraph count
self._cursorPos = 0 # Last cursor position self._cursorPos = 0 # Last cursor position
self._initCount = 0 # Initial word count self._wordInit = 0 # Initial character count
self._charInit = 0 # Initial word count
return return
@@ -164,9 +165,13 @@ class NWItem:
def paraCount(self) -> int: def paraCount(self) -> int:
return self._paraCount return self._paraCount
@property
def mainCount(self) -> int:
return self._charCount if CONFIG.useCharCount else self._wordCount
@property @property
def initCount(self) -> int: def initCount(self) -> int:
return self._initCount return self._wordInit if CONFIG.useCharCount else self._charInit
@property @property
def cursorPos(self) -> int: def cursorPos(self) -> int:
@@ -257,7 +262,8 @@ class NWItem:
self._paraCount = 0 self._paraCount = 0
self._cursorPos = 0 self._cursorPos = 0
self._initCount = self._wordCount self._wordInit = self._charCount
self._charInit = self._wordCount
return True return True
@@ -265,23 +271,24 @@ class NWItem:
def duplicate(cls, source: NWItem, handle: str) -> NWItem: def duplicate(cls, source: NWItem, handle: str) -> NWItem:
"""Make a copy of an item.""" """Make a copy of an item."""
new = cls(source._project, handle) new = cls(source._project, handle)
new._name = source._name new._name = source._name
new._parent = source._parent new._parent = source._parent
new._root = source._root new._root = source._root
new._order = source._order new._order = source._order
new._type = source._type new._type = source._type
new._class = source._class new._class = source._class
new._layout = source._layout new._layout = source._layout
new._status = source._status new._status = source._status
new._import = source._import new._import = source._import
new._active = source._active new._active = source._active
new._expanded = source._expanded new._expanded = source._expanded
new._heading = source._heading new._heading = source._heading
new._charCount = source._charCount new._charCount = source._charCount
new._wordCount = source._wordCount new._wordCount = source._wordCount
new._paraCount = source._paraCount new._paraCount = source._paraCount
new._cursorPos = source._cursorPos new._cursorPos = source._cursorPos
new._initCount = source._initCount new._wordInit = source._wordInit
new._charInit = source._charInit
return new return new
## ##
+1 -1
View File
@@ -166,7 +166,7 @@ class ProjectNode:
def updateCount(self, propagate: bool = True) -> None: def updateCount(self, propagate: bool = True) -> None:
"""Update counts, and propagate upwards in the tree.""" """Update counts, and propagate upwards in the tree."""
self._count = self._item.wordCount + sum(c._count for c in self._children) # noqa: SLF001 self._count = self._item.mainCount + sum(c._count for c in self._children) # noqa: SLF001
self._cache[C_COUNT_TEXT] = f"{self._count:n}" self._cache[C_COUNT_TEXT] = f"{self._count:n}"
if propagate and (parent := self._parent): if propagate and (parent := self._parent):
parent.updateCount() parent.updateCount()
+6 -6
View File
@@ -367,7 +367,7 @@ class NWProject:
# Often, the index needs to be rebuilt when updating format # Often, the index needs to be rebuilt when updating format
self._index.rebuild() self._index.rebuild()
self.updateWordCounts() self.updateCounts()
self._session.startSession() self._session.startSession()
self.setProjectChanged(False) self.setProjectChanged(False)
self._valid = True self._valid = True
@@ -397,7 +397,7 @@ class NWProject:
else: else:
self._data.incSaveCount() self._data.incSaveCount()
self.updateWordCounts() self.updateCounts()
self.countStatus() self.countStatus()
xmlWriter = self._storage.getXmlWriter() xmlWriter = self._storage.getXmlWriter()
@@ -515,10 +515,10 @@ class NWProject:
# Class Methods # Class Methods
## ##
def updateWordCounts(self) -> None: def updateCounts(self) -> None:
"""Update the total word count values.""" """Update the total word and character count values."""
novel, notes = self._tree.sumWords() wNovel, wNotes, cNovel, cNotes = self._tree.sumCounts()
self._data.setCurrCounts(novel=novel, notes=notes) self._data.setCurrCounts(wNovel=wNovel, wNotes=wNotes, cNovel=cNovel, cNotes=cNotes)
return return
def countStatus(self) -> None: def countStatus(self) -> None:
+42 -24
View File
@@ -66,8 +66,8 @@ class NWProjectData:
self._spellLang = None self._spellLang = None
# Project Dictionaries # Project Dictionaries
self._initCounts = [0, 0] self._initCounts = [0, 0, 0, 0]
self._currCounts = [0, 0] self._currCounts = [0, 0, 0, 0]
self._lastHandle: dict[str, str | None] = { self._lastHandle: dict[str, str | None] = {
"editor": None, "editor": None,
"viewer": None, "viewer": None,
@@ -148,18 +148,18 @@ class NWProjectData:
return self._spellLang return self._spellLang
@property @property
def initCounts(self) -> tuple[int, int]: def initCounts(self) -> tuple[int, int, int, int]:
"""Return the initial count of words for novel and note """Return the initial count of words and characters for novel
documents. and note documents.
""" """
return self._initCounts[0], self._initCounts[1] return self._initCounts[0], self._initCounts[1], self._initCounts[2], self._initCounts[3]
@property @property
def currCounts(self) -> tuple[int, int]: def currCounts(self) -> tuple[int, int, int, int]:
"""Return the current count of words for novel and note """Return the current count of words and characters for novel
documents. and note documents.
""" """
return self._currCounts[0], self._currCounts[1] return self._currCounts[0], self._currCounts[1], self._currCounts[2], self._currCounts[3]
@property @property
def lastHandle(self) -> dict[str, str | None]: def lastHandle(self) -> dict[str, str | None]:
@@ -301,22 +301,40 @@ class NWProjectData:
self._project.setProjectChanged(True) self._project.setProjectChanged(True)
return return
def setInitCounts(self, novel: Any = None, notes: Any = None) -> None: def setInitCounts(
"""Set the word count totals for novel and note files.""" self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
if novel is not None: ) -> None:
self._initCounts[0] = checkInt(novel, 0) """Set the count totals for novel and note files."""
self._currCounts[0] = checkInt(novel, 0) if wNovel is not None:
if notes is not None: count = checkInt(wNovel, 0)
self._initCounts[1] = checkInt(notes, 0) self._initCounts[0] = count
self._currCounts[1] = checkInt(notes, 0) self._currCounts[0] = count
if wNotes is not None:
count = checkInt(wNotes, 0)
self._initCounts[1] = count
self._currCounts[1] = count
if cNovel is not None:
count = checkInt(cNovel, 0)
self._initCounts[2] = count
self._currCounts[2] = count
if cNotes is not None:
count = checkInt(cNotes, 0)
self._initCounts[3] = count
self._currCounts[3] = count
return return
def setCurrCounts(self, novel: Any = None, notes: Any = None) -> None: def setCurrCounts(
"""Set the word count totals for novel and note files.""" self, wNovel: Any = None, wNotes: Any = None, cNovel: Any = None, cNotes: Any = None
if novel is not None: ) -> None:
self._currCounts[0] = checkInt(novel, 0) """Set the count totals for novel and note files."""
if notes is not None: if wNovel is not None:
self._currCounts[1] = checkInt(notes, 0) self._currCounts[0] = checkInt(wNovel, 0)
if wNotes is not None:
self._currCounts[1] = checkInt(wNotes, 0)
if cNovel is not None:
self._currCounts[2] = checkInt(cNovel, 0)
if cNotes is not None:
self._currCounts[3] = checkInt(cNotes, 0)
return return
def setAutoReplace(self, value: dict) -> None: def setAutoReplace(self, value: dict) -> None:
+17 -7
View File
@@ -46,7 +46,7 @@ if TYPE_CHECKING:
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
FILE_VERSION = "1.5" # The current project file format version FILE_VERSION = "1.5" # The current project file format version
FILE_REVISION = "4" # The current project file format revision FILE_REVISION = "5" # The current project file format revision
HEX_VERSION = 0x0105 HEX_VERSION = 0x0105
NUM_VERSION = { NUM_VERSION = {
@@ -109,6 +109,8 @@ class ProjectXMLReader:
Rev 3: Added TEMPLATE class. 2.3. Rev 3: Added TEMPLATE class. 2.3.
Rev 4: Added shape attribute to status and importance entry Rev 4: Added shape attribute to status and importance entry
nodes. 2.5. nodes. 2.5.
Rev 5: Added novelChars and notesChars attributes to content
node. 2.7 RC 1.
""" """
def __init__(self, path: str | Path) -> None: def __init__(self, path: str | Path) -> None:
@@ -286,9 +288,9 @@ class ProjectXMLReader:
elif xItem.tag == "spellLang": # Changed to spellChecking in 1.5 elif xItem.tag == "spellLang": # Changed to spellChecking in 1.5
data.setSpellLang(xItem.text) data.setSpellLang(xItem.text)
elif xItem.tag == "novelWordCount": # Moved to content attribute in 1.5 elif xItem.tag == "novelWordCount": # Moved to content attribute in 1.5
data.setInitCounts(novel=xItem.text) data.setInitCounts(wNovel=xItem.text)
elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5 elif xItem.tag == "notesWordCount": # Moved to content attribute in 1.5
data.setInitCounts(notes=xItem.text) data.setInitCounts(wNotes=xItem.text)
return return
@@ -298,8 +300,13 @@ class ProjectXMLReader:
"""Parse the content section of the XML file.""" """Parse the content section of the XML file."""
logger.debug("Parsing <content> section") logger.debug("Parsing <content> section")
data.setInitCounts(novel=xSection.attrib.get("novelWords", None)) # Moved in 1.5 # Moved in 1.5
data.setInitCounts(notes=xSection.attrib.get("notesWords", None)) # Moved in 1.5 data.setInitCounts(
wNovel=xSection.attrib.get("novelWords", None),
wNotes=xSection.attrib.get("notesWords", None),
cNovel=xSection.attrib.get("novelChars", None),
cNotes=xSection.attrib.get("notesChars", None),
)
for xItem in xSection: for xItem in xSection:
if xItem.tag != "item": if xItem.tag != "item":
@@ -527,10 +534,13 @@ class ProjectXMLWriter:
self._packSingleValue(xImport, "entry", label, attrib=attrib) self._packSingleValue(xImport, "entry", label, attrib=attrib)
# Save Tree Content # Save Tree Content
counts = data.currCounts
contAttr = { contAttr = {
"items": str(len(content)), "items": str(len(content)),
"novelWords": str(data.currCounts[0]), "novelWords": str(counts[0]),
"notesWords": str(data.currCounts[1]), "notesWords": str(counts[1]),
"novelChars": str(counts[2]),
"notesChars": str(counts[3]),
} }
xContent = ET.SubElement(xRoot, "content", attrib=contAttr) xContent = ET.SubElement(xRoot, "content", attrib=contAttr)
+29 -13
View File
@@ -79,29 +79,36 @@ class NWSessionLog:
return False return False
now = time() now = time()
iNovel, iNotes = self._project.data.initCounts iWNovel, iWNotes, iCNovel, iCNotes = self._project.data.initCounts
cNovel, cNotes = self._project.data.currCounts cWNovel, cWNotes, cCNovel, cCNotes = self._project.data.currCounts
iTotal = iNovel + iNotes iWTotal = iWNovel + iWNotes
wDiff = cNovel + cNotes - iTotal iCTotal = iCNovel + iCNotes
wDiff = cWNovel + cWNotes - iWTotal
cDiff = cCNovel + cCNotes - iCTotal
sTime = now - self._start sTime = now - self._start
logger.info("The session lasted %d sec and added %d words", int(sTime), wDiff) logger.info(
if sTime < 300 and wDiff == 0: "The session lasted %d sec and added %d words abd %d characters",
int(sTime), wDiff, cDiff
)
if sTime < 300 and (wDiff == 0 or cDiff == 0):
logger.info("Session too short, skipping log entry") logger.info("Session too short, skipping log entry")
return False return False
try: try:
if not sessFile.exists(): if not sessFile.exists():
with open(sessFile, mode="w", encoding="utf-8") as fObj: with open(sessFile, mode="w", encoding="utf-8") as fObj:
fObj.write(self.createInitial(iTotal)) fObj.write(self.createInitial(iWTotal))
with open(sessFile, mode="a+", encoding="utf-8") as fObj: with open(sessFile, mode="a+", encoding="utf-8") as fObj:
fObj.write(self.createRecord( fObj.write(self.createRecord(
start=formatTimeStamp(self._start), start=formatTimeStamp(self._start),
end=formatTimeStamp(now), end=formatTimeStamp(now),
novel=cNovel, novel=cWNovel,
notes=cNotes, notes=cWNotes,
idle=round(idleTime) idle=round(idleTime),
cnovel=cCNovel,
cnotes=cCNotes,
)) ))
except Exception: except Exception:
@@ -129,10 +136,19 @@ class NWSessionLog:
data = json.dumps({"type": "initial", "offset": total}) data = json.dumps({"type": "initial", "offset": total})
return f"{data}\n" return f"{data}\n"
def createRecord(self, start: str, end: str, novel: int, notes: int, idle: int) -> str: def createRecord(
self, start: str, end: str, novel: int, notes: int, idle: int,
cnovel: int = 0, cnotes: int = 0,
) -> str:
"""Low level function to create a log record.""" """Low level function to create a log record."""
data = json.dumps({ data = json.dumps({
"type": "record", "start": start, "end": end, "type": "record",
"novel": novel, "notes": notes, "idle": idle, "start": start,
"end": end,
"novel": novel,
"notes": notes,
"cnovel": cnovel,
"cnotes": cnotes,
"idle": idle,
}) })
return f"{data}\n" return f"{data}\n"
+9 -5
View File
@@ -410,16 +410,20 @@ class NWTree:
return True return True
def sumWords(self) -> tuple[int, int]: def sumCounts(self) -> tuple[int, int, int, int]:
"""Loop over all entries and add up the word counts.""" """Loop over all entries and add up the word and char counts."""
noteWords = 0
novelWords = 0 novelWords = 0
notesWords = 0
novelChars = 0
notesChars = 0
for item in self._items.values(): for item in self._items.values():
if item.itemLayout == nwItemLayout.NOTE: if item.itemLayout == nwItemLayout.NOTE:
noteWords += item.wordCount notesWords += item.wordCount
notesChars += item.charCount
elif item.itemLayout == nwItemLayout.DOCUMENT: elif item.itemLayout == nwItemLayout.DOCUMENT:
novelWords += item.wordCount novelWords += item.wordCount
return novelWords, noteWords novelChars += item.charCount
return novelWords, notesWords, novelChars, notesChars
## ##
# Tree Item Methods # Tree Item Methods
+20 -9
View File
@@ -226,6 +226,14 @@ class GuiPreferences(NDialog):
self.tr("Turn off to use the Qt font dialog, which may have more options.") self.tr("Turn off to use the Qt font dialog, which may have more options.")
) )
# Use Character Count
self.useCharCount = NSwitch(self)
self.useCharCount.setChecked(CONFIG.useCharCount)
self.mainForm.addRow(
self.tr("Prefer character count over word count"), self.useCharCount,
self.tr("Display character count instead where available.")
)
# Document Style # Document Style
# ============== # ==============
@@ -957,21 +965,24 @@ class GuiPreferences(NDialog):
refreshTree = False refreshTree = False
# Appearance # Appearance
guiLocale = self.guiLocale.currentData() guiLocale = self.guiLocale.currentData()
guiTheme = self.guiTheme.currentData() guiTheme = self.guiTheme.currentData()
iconTheme = self.iconTheme.currentData() iconTheme = self.iconTheme.currentData()
useCharCount = self.useCharCount.isChecked()
updateTheme |= CONFIG.guiTheme != guiTheme updateTheme |= CONFIG.guiTheme != guiTheme
updateTheme |= CONFIG.iconTheme != iconTheme updateTheme |= CONFIG.iconTheme != iconTheme
needsRestart |= CONFIG.guiLocale != guiLocale needsRestart |= CONFIG.guiLocale != guiLocale
needsRestart |= CONFIG.guiFont != self._guiFont needsRestart |= CONFIG.guiFont != self._guiFont
refreshTree |= CONFIG.useCharCount != useCharCount
CONFIG.guiLocale = guiLocale CONFIG.guiLocale = guiLocale
CONFIG.guiTheme = guiTheme CONFIG.guiTheme = guiTheme
CONFIG.iconTheme = iconTheme CONFIG.iconTheme = iconTheme
CONFIG.hideVScroll = self.hideVScroll.isChecked() CONFIG.hideVScroll = self.hideVScroll.isChecked()
CONFIG.hideHScroll = self.hideHScroll.isChecked() CONFIG.hideHScroll = self.hideHScroll.isChecked()
CONFIG.nativeFont = self.nativeFont.isChecked() CONFIG.nativeFont = self.nativeFont.isChecked()
CONFIG.useCharCount = useCharCount
CONFIG.setGuiFont(self._guiFont) CONFIG.setGuiFont(self._guiFont)
# Document Style # Document Style
+29 -19
View File
@@ -55,7 +55,9 @@ from novelwriter import CONFIG, SHARED
from novelwriter.common import ( from novelwriter.common import (
decodeMimeHandles, fontMatcher, minmax, qtAddAction, qtLambda, transferCase decodeMimeHandles, fontMatcher, minmax, qtAddAction, qtLambda, transferCase
) )
from novelwriter.constants import nwConst, nwKeyWords, nwShortcode, nwUnicode from novelwriter.constants import (
nwConst, nwKeyWords, nwLabels, nwShortcode, nwStats, nwUnicode, trStats
)
from novelwriter.core.document import NWDocument from novelwriter.core.document import NWDocument
from novelwriter.enum import ( from novelwriter.enum import (
nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass, nwChange, nwComment, nwDocAction, nwDocInsert, nwDocMode, nwItemClass,
@@ -321,6 +323,7 @@ class GuiDocEditor(QPlainTextEdit):
""" """
# Auto-Replace # Auto-Replace
self._autoReplace.initSettings() self._autoReplace.initSettings()
self.docFooter.initSettings()
# Reload spell check and dictionaries # Reload spell check and dictionaries
SHARED.updateSpellCheckLanguage() SHARED.updateSpellCheckLanguage()
@@ -1233,7 +1236,8 @@ class GuiDocEditor(QPlainTextEdit):
"""Process the word counter's finished signal.""" """Process the word counter's finished signal."""
if self._docHandle and self._nwItem: if self._docHandle and self._nwItem:
logger.debug("Updating word count") logger.debug("Updating word count")
needsRefresh = wCount != self._nwItem.wordCount mCount = cCount if CONFIG.useCharCount else wCount
needsRefresh = mCount != self._nwItem.mainCount
self._nwItem.setCharCount(cCount) self._nwItem.setCharCount(cCount)
self._nwItem.setWordCount(wCount) self._nwItem.setWordCount(wCount)
self._nwItem.setParaCount(pCount) self._nwItem.setParaCount(pCount)
@@ -1241,7 +1245,7 @@ class GuiDocEditor(QPlainTextEdit):
self._nwItem.notifyToRefresh() self._nwItem.notifyToRefresh()
if not self.textCursor().hasSelection(): if not self.textCursor().hasSelection():
# Selection counter should take precedence (#2155) # Selection counter should take precedence (#2155)
self.docFooter.updateWordCount(wCount, False) self.docFooter.updateMainCount(mCount, False)
return return
@pyqtSlot() @pyqtSlot()
@@ -1254,7 +1258,7 @@ class GuiDocEditor(QPlainTextEdit):
self._timerSel.start() self._timerSel.start()
else: else:
self._timerSel.stop() self._timerSel.stop()
self.docFooter.updateWordCount(0, False) self.docFooter.updateMainCount(0, False)
return return
@pyqtSlot() @pyqtSlot()
@@ -1271,8 +1275,7 @@ class GuiDocEditor(QPlainTextEdit):
def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None: def _updateSelCounts(self, cCount: int, wCount: int, pCount: int) -> None:
"""Update the counts on the counter's finished signal.""" """Update the counts on the counter's finished signal."""
if self._docHandle and self._nwItem: if self._docHandle and self._nwItem:
logger.debug("User selected %d words", wCount) self.docFooter.updateMainCount(cCount if CONFIG.useCharCount else wCount, True)
self.docFooter.updateWordCount(wCount, True)
self._timerSel.stop() self._timerSel.stop()
return return
@@ -3045,9 +3048,9 @@ class GuiDocEditFooter(QWidget):
fPx = int(0.9*SHARED.theme.fontPixelSize) fPx = int(0.9*SHARED.theme.fontPixelSize)
# Cached Translations # Cached Translations
self.initSettings()
self._trLineCount = self.tr("Line: {0} ({1})") self._trLineCount = self.tr("Line: {0} ({1})")
self._trWordCount = self.tr("Words: {0} ({1})") self._trSelectCount = self.tr("Selected: {0}")
self._trSelectCount = self.tr("Words: {0} selected")
# Main Widget Settings # Main Widget Settings
self.setContentsMargins(0, 0, 0, 0) self.setContentsMargins(0, 0, 0, 0)
@@ -3108,7 +3111,7 @@ class GuiDocEditFooter(QWidget):
self.updateTheme() self.updateTheme()
# Initialise Info # Initialise Info
self.updateWordCount(0, False) self.updateMainCount(0, False)
logger.debug("Ready: GuiDocEditFooter") logger.debug("Ready: GuiDocEditFooter")
@@ -3118,6 +3121,13 @@ class GuiDocEditFooter(QWidget):
# Methods # Methods
## ##
def initSettings(self) -> None:
"""Apply user settings."""
self._trMainCount = trStats(nwLabels.STATS_DISPLAY[
nwStats.CHARS if CONFIG.useCharCount else nwStats.WORDS
])
return
def updateFont(self) -> None: def updateFont(self) -> None:
"""Update the font settings.""" """Update the font settings."""
self.setFont(SHARED.theme.guiFont) self.setFont(SHARED.theme.guiFont)
@@ -3162,7 +3172,7 @@ class GuiDocEditFooter(QWidget):
self._tItem = SHARED.project.tree[self._docHandle] self._tItem = SHARED.project.tree[self._docHandle]
self.updateInfo() self.updateInfo()
self.updateWordCount(0, False) self.updateMainCount(0, False)
return return
@@ -3193,15 +3203,15 @@ class GuiDocEditFooter(QWidget):
) )
return return
def updateWordCount(self, wCount: int, selection: bool) -> None: def updateMainCount(self, count: int, selection: bool) -> None:
"""Update word counter information.""" """Update main counter information."""
if selection and wCount: if selection and count:
wText = self._trSelectCount.format(f"{wCount:n}") text = self._trSelectCount.format(f"{count:n}")
elif self._tItem: elif self._tItem:
wCount = self._tItem.wordCount count = self._tItem.mainCount
wDiff = wCount - self._tItem.initCount diff = count - self._tItem.initCount
wText = self._trWordCount.format(f"{wCount:n}", f"{wDiff:+n}") text = self._trMainCount.format(f"{count:n}", f"{diff:+n}")
else: else:
wText = self._trWordCount.format("0", "+0") text = self._trMainCount.format("0", "+0")
self.wordsText.setText(wText) self.wordsText.setText(text)
return return
+10 -5
View File
@@ -80,6 +80,7 @@ class GuiNovelView(QWidget):
# Function Mappings # Function Mappings
self.setActive = self.novelBar.setActive self.setActive = self.novelBar.setActive
self.getSelectedHandle = self.novelTree.getSelectedHandle self.getSelectedHandle = self.novelTree.getSelectedHandle
self.refreshCurrentTree = self.novelBar.forceRefreshNovelTree
return return
@@ -209,7 +210,7 @@ class GuiNovelToolBar(QWidget):
# Refresh Button # Refresh Button
self.tbRefresh = NIconToolButton(self, iSz) self.tbRefresh = NIconToolButton(self, iSz)
self.tbRefresh.setToolTip(self.tr("Refresh")) self.tbRefresh.setToolTip(self.tr("Refresh"))
self.tbRefresh.clicked.connect(self._forceRefreshNovelTree) self.tbRefresh.clicked.connect(self.forceRefreshNovelTree)
# More Options Menu # More Options Menu
self.mMore = QMenu(self) self.mMore = QMenu(self)
@@ -274,7 +275,7 @@ class GuiNovelToolBar(QWidget):
self.novelValue.updateTheme() self.novelValue.updateTheme()
self.tbNovel.setVisible(self.novelValue.count() > 1) self.tbNovel.setVisible(self.novelValue.count() > 1)
self._forceRefreshNovelTree() self.forceRefreshNovelTree()
return return
@@ -306,7 +307,7 @@ class GuiNovelToolBar(QWidget):
self.aLastCol[colType].setChecked(True) self.aLastCol[colType].setChecked(True)
self.novelView.novelTree.setLastColType(colType) self.novelView.novelTree.setLastColType(colType)
if doRefresh: if doRefresh:
self._forceRefreshNovelTree() self.forceRefreshNovelTree()
self.novelView.novelTree.resizeColumns() self.novelView.novelTree.resizeColumns()
return return
@@ -324,11 +325,11 @@ class GuiNovelToolBar(QWidget):
return return
## ##
# Private Slots # Public Slots
## ##
@pyqtSlot() @pyqtSlot()
def _forceRefreshNovelTree(self) -> None: def forceRefreshNovelTree(self) -> None:
"""Rebuild the current tree.""" """Rebuild the current tree."""
if tHandle := self.novelValue.handle: if tHandle := self.novelValue.handle:
self.novelView.setCurrentNovel(tHandle) self.novelView.setCurrentNovel(tHandle)
@@ -336,6 +337,10 @@ class GuiNovelToolBar(QWidget):
self._refresh[tHandle] = False self._refresh[tHandle] = False
return return
##
# Private Slots
##
@pyqtSlot(str) @pyqtSlot(str)
def _refreshNovelTree(self, tHandle: str) -> None: def _refreshNovelTree(self, tHandle: str) -> None:
"""Refresh or schedule refresh of a novel tree.""" """Refresh or schedule refresh of a novel tree."""
+14 -6
View File
@@ -33,7 +33,7 @@ from PyQt6.QtWidgets import QApplication, QLabel, QStatusBar, QWidget
from novelwriter import CONFIG, SHARED from novelwriter import CONFIG, SHARED
from novelwriter.common import formatTime from novelwriter.common import formatTime
from novelwriter.constants import nwConst from novelwriter.constants import nwConst, nwLabels, nwStats, trStats
from novelwriter.extensions.modified import NClickableLabel from novelwriter.extensions.modified import NClickableLabel
from novelwriter.extensions.statusled import StatusLED from novelwriter.extensions.statusled import StatusLED
@@ -108,11 +108,22 @@ class GuiMainStatus(QStatusBar):
logger.debug("Ready: GuiMainStatus") logger.debug("Ready: GuiMainStatus")
self.initSettings()
self.updateTheme() self.updateTheme()
self.clearStatus() self.clearStatus()
return return
def initSettings(self) -> None:
"""Apply user settings."""
if CONFIG.useCharCount:
self._trStatsCount = trStats(nwLabels.STATS_DISPLAY[nwStats.CHARS])
self._trStatsTip = self.tr("Total character count (session change)")
else:
self._trStatsCount = trStats(nwLabels.STATS_DISPLAY[nwStats.WORDS])
self._trStatsTip = self.tr("Total word count (session change)")
return
def clearStatus(self) -> None: def clearStatus(self) -> None:
"""Reset all widgets on the status bar to default values.""" """Reset all widgets on the status bar to default values."""
self.setRefTime(-1.0) self.setRefTime(-1.0)
@@ -173,11 +184,8 @@ class GuiMainStatus(QStatusBar):
def setProjectStats(self, pWC: int, sWC: int) -> None: def setProjectStats(self, pWC: int, sWC: int) -> None:
"""Update the current project statistics.""" """Update the current project statistics."""
self.statsText.setText(self.tr("Words: {0} ({1})").format(f"{pWC:n}", f"{sWC:+n}")) self.statsText.setText(self._trStatsCount.format(f"{pWC:n}", f"{sWC:+n}"))
if CONFIG.incNotesWCount: self.statsText.setToolTip(self._trStatsTip)
self.statsText.setToolTip(self.tr("Project word count (session change)"))
else:
self.statsText.setToolTip(self.tr("Novel word count (session change)"))
return return
def updateTime(self, idleTime: float = 0.0) -> None: def updateTime(self, idleTime: float = 0.0) -> None:
+19 -8
View File
@@ -1048,8 +1048,10 @@ class GuiMain(QMainWindow):
self.initMain() self.initMain()
self.saveDocument() self.saveDocument()
if tree: if tree and not theme:
# These are also updated by a theme refresh
SHARED.project.tree.refreshAllItems() SHARED.project.tree.refreshAllItems()
self.novelView.refreshCurrentTree()
if theme: if theme:
SHARED.theme.loadTheme() SHARED.theme.loadTheme()
@@ -1075,6 +1077,7 @@ class GuiMain(QMainWindow):
self.projView.initSettings() self.projView.initSettings()
self.novelView.initSettings() self.novelView.initSettings()
self.outlineView.initSettings() self.outlineView.initSettings()
self.mainStatus.initSettings()
# Force update of word count # Force update of word count
self._lastTotalCount = 0 self._lastTotalCount = 0
@@ -1261,15 +1264,23 @@ class GuiMain(QMainWindow):
if self._lastTotalCount != currentTotalCount: if self._lastTotalCount != currentTotalCount:
self._lastTotalCount = currentTotalCount self._lastTotalCount = currentTotalCount
SHARED.project.updateWordCounts() SHARED.project.updateCounts()
if CONFIG.incNotesWCount: if CONFIG.incNotesWCount:
iTotal = sum(SHARED.project.data.initCounts) if CONFIG.useCharCount:
cTotal = sum(SHARED.project.data.currCounts) iTotal = sum(SHARED.project.data.initCounts[2:])
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal) cTotal = sum(SHARED.project.data.currCounts[2:])
else:
iTotal = sum(SHARED.project.data.initCounts[:2])
cTotal = sum(SHARED.project.data.currCounts[:2])
else: else:
iNovel, _ = SHARED.project.data.initCounts if CONFIG.useCharCount:
cNovel, _ = SHARED.project.data.currCounts iTotal = SHARED.project.data.initCounts[2]
self.mainStatus.setProjectStats(cNovel, cNovel - iNovel) cTotal = SHARED.project.data.currCounts[2]
else:
iTotal = SHARED.project.data.initCounts[0]
cTotal = SHARED.project.data.currCounts[0]
self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
return return
+2 -2
View File
@@ -257,8 +257,8 @@ class _OverviewPage(NScrollablePage):
def updateProjectData(self) -> None: def updateProjectData(self) -> None:
"""Load information about the project.""" """Load information about the project."""
project = SHARED.project project = SHARED.project
project.updateWordCounts() project.updateCounts()
wcNovel, wcNotes = project.data.currCounts wcNovel, wcNotes, _, _ = project.data.currCounts
self.projName.setText(project.data.name) self.projName.setText(project.data.name)
self.projRevisions.setText(f"{project.data.saveCount:n}") self.projRevisions.setText(f"{project.data.saveCount:n}")
+3 -3
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-04-20 19:01:45"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:07:59">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2178" autoCount="285" editTime="96586"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2179" autoCount="285" editTime="96588">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
</project> </project>
@@ -36,7 +36,7 @@
<entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry> <entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="31" novelWords="1016" notesWords="416"> <content items="31" novelWords="1016" notesWords="416" novelChars="5602" notesChars="2285">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sc24b8f" import="ia857f0">Novel</name> <name status="sc24b8f" import="ia857f0">Novel</name>
+53 -36
View File
@@ -1,13 +1,13 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2022-11-07 13:00:48"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:07:59">
<project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="5" autoCount="10" editTime="1000"> <project id="e2be99af-f9bf-4403-857a-c3d1ac25abea" saveCount="2179" autoCount="285" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jane Smith</author> <author>Jane Smith</author>
</project> </project>
<settings> <settings>
<doBackup>yes</doBackup> <doBackup>no</doBackup>
<language>en_GB</language> <language>en_GB</language>
<spellChecking auto="yes">en_GB</spellChecking> <spellChecking auto="yes">None</spellChecking>
<lastHandle> <lastHandle>
<entry key="editor">636b6aa9b697b</entry> <entry key="editor">636b6aa9b697b</entry>
<entry key="viewer">636b6aa9b697b</entry> <entry key="viewer">636b6aa9b697b</entry>
@@ -20,32 +20,33 @@
<entry key="C">D</entry> <entry key="C">D</entry>
</autoReplace> </autoReplace>
<status> <status>
<entry key="sf12341" count="4" red="100" green="100" blue="100" shape="SQUARE">New</entry> <entry key="sf12341" count="8" red="100" green="100" blue="100" shape="SQUARE">New</entry>
<entry key="sf24ce6" count="2" red="200" green="50" blue="0" shape="SQUARE">Notes</entry> <entry key="sf24ce6" count="2" red="200" green="50" blue="0" shape="SQUARE">Notes</entry>
<entry key="sc24b8f" count="3" red="182" green="60" blue="0" shape="SQUARE">Started</entry> <entry key="sc24b8f" count="3" red="182" green="60" blue="0" shape="BARS_1">Started</entry>
<entry key="s90e6c9" count="7" red="193" green="129" blue="0" shape="SQUARE">1st Draft</entry> <entry key="s90e6c9" count="5" red="193" green="129" blue="0" shape="BARS_2">1st Draft</entry>
<entry key="sd51c5b" count="0" red="193" green="129" blue="0" shape="SQUARE">2nd Draft</entry> <entry key="sd51c5b" count="1" red="193" green="129" blue="0" shape="BARS_3">2nd Draft</entry>
<entry key="s8ae72a" count="0" red="193" green="129" blue="0" shape="SQUARE">3rd Draft</entry> <entry key="s8ae72a" count="1" red="193" green="129" blue="0" shape="BARS_4">3rd Draft</entry>
<entry key="s78ea90" count="1" red="58" green="180" blue="58" shape="SQUARE">Finished</entry> <entry key="s78ea90" count="1" red="58" green="180" blue="58" shape="STAR">Finished</entry>
</status> </status>
<importance> <importance>
<entry key="ia857f0" count="5" red="100" green="100" blue="100" shape="SQUARE">None</entry> <entry key="ia857f0" count="5" red="100" green="100" blue="100" shape="SQUARE">None</entry>
<entry key="icfb3a5" count="2" red="0" green="122" blue="188" shape="SQUARE">Minor</entry> <entry key="i4a1d39" count="1" red="220" green="138" blue="221" shape="BLOCK_1">Background</entry>
<entry key="i2d7a54" count="2" red="21" green="0" blue="180" shape="SQUARE">Major</entry> <entry key="icfb3a5" count="1" red="220" green="138" blue="221" shape="BLOCK_2">Minor</entry>
<entry key="i56be10" count="1" red="117" green="0" blue="175" shape="SQUARE">Main</entry> <entry key="i2d7a54" count="2" red="220" green="138" blue="221" shape="BLOCK_3">Major</entry>
<entry key="i56be10" count="1" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="27" novelWords="954" notesWords="409"> <content items="31" novelWords="1016" notesWords="416" novelChars="5602" notesChars="2285">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sc24b8f" import="ia857f0">Novel</name> <name status="sc24b8f" import="ia857f0">Novel</name>
</item> </item>
<item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="53b69b83cdafc" parent="7031beac91f75" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H1" charCount="93" wordCount="19" paraCount="2" cursorPos="119" /> <meta expanded="no" heading="H1" charCount="148" wordCount="29" paraCount="4" cursorPos="178" />
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name> <name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
</item> </item>
<item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="974e400180a99" parent="7031beac91f75" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H0" charCount="251" wordCount="50" paraCount="2" cursorPos="277" /> <meta expanded="no" heading="H0" charCount="233" wordCount="47" paraCount="2" cursorPos="194" />
<name status="sf12341" import="ia857f0" active="yes">Page</name> <name status="sf12341" import="ia857f0" active="yes">Page</name>
</item> </item>
<item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="edca4be2fcaf8" parent="7031beac91f75" root="7031beac91f75" order="2" type="FILE" class="NOVEL" layout="DOCUMENT">
@@ -57,28 +58,28 @@
<name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name> <name status="sf24ce6" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="636b6aa9b697b" parent="6a2d6d5f4f401" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="2687" wordCount="479" paraCount="14" cursorPos="67" /> <meta expanded="no" heading="H3" charCount="2999" wordCount="530" paraCount="16" cursorPos="159" />
<name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Making a Scene</name>
</item> </item>
<item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="bc0cbd2a407f3" parent="6a2d6d5f4f401" root="7031beac91f75" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="465" /> <meta expanded="no" heading="H3" charCount="548" wordCount="108" paraCount="3" cursorPos="650" />
<name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name> <name status="s90e6c9" import="ia857f0" active="yes">Another Scene</name>
</item> </item>
<item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="ba8a28a246524" parent="7031beac91f75" root="7031beac91f75" order="4" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="310" /> <meta expanded="no" heading="H2" charCount="617" wordCount="101" paraCount="3" cursorPos="1182" />
<name status="s78ea90" import="ia857f0" active="yes">Interlude</name> <name status="s78ea90" import="ia857f0" active="yes">Interlude</name>
</item> </item>
<item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE"> <item handle="96b68994dfa3d" parent="7031beac91f75" root="7031beac91f75" order="5" type="FILE" class="NOVEL" layout="NOTE">
<meta expanded="no" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="0" /> <meta expanded="no" heading="H1" charCount="1909" wordCount="346" paraCount="7" cursorPos="1940" />
<name status="sf24ce6" import="ia857f0" active="no">A Note on Structure</name> <name status="sf24ce6" import="ia857f0" active="no">A Note on Structure</name>
</item> </item>
<item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="88706ddc78b1b" parent="7031beac91f75" root="7031beac91f75" order="6" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="yes" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="188" /> <meta expanded="yes" heading="H2" charCount="139" wordCount="28" paraCount="1" cursorPos="356" />
<name status="s90e6c9" import="ia857f0" active="yes">Chapter Two</name> <name status="s90e6c9" import="ia857f0" active="yes">Chapter Two</name>
</item> </item>
<item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="ae7339df26ded" parent="88706ddc78b1b" root="7031beac91f75" order="0" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="0" /> <meta expanded="no" heading="H3" charCount="189" wordCount="37" paraCount="1" cursorPos="237" />
<name status="s90e6c9" import="ia857f0" active="yes">We Found John!</name> <name status="sd51c5b" import="ia857f0" active="yes">We Found John!</name>
</item> </item>
<item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL"> <item handle="e5e47ebf63b1c" parent="None" root="e5e47ebf63b1c" order="1" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
@@ -89,8 +90,8 @@
<name status="sc24b8f" import="ia857f0" active="yes">Title Page</name> <name status="sc24b8f" import="ia857f0" active="yes">Title Page</name>
</item> </item>
<item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT"> <item handle="a520879ca0b45" parent="e5e47ebf63b1c" root="e5e47ebf63b1c" order="1" type="FILE" class="NOVEL" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="104" /> <meta expanded="no" heading="H2" charCount="299" wordCount="55" paraCount="2" cursorPos="387" />
<name status="s90e6c9" import="ia857f0" active="yes">Chapter One</name> <name status="s8ae72a" import="ia857f0" active="yes">Chapter One</name>
</item> </item>
<item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER"> <item handle="f6622b4617424" parent="None" root="f6622b4617424" order="2" type="ROOT" class="CHARACTER">
<meta expanded="yes" /> <meta expanded="yes" />
@@ -101,28 +102,28 @@
<name status="sf12341" import="ia857f0">Main Characters</name> <name status="sf12341" import="ia857f0">Main Characters</name>
</item> </item>
<item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="14298de4d9524" parent="f7e2d9f330615" root="f6622b4617424" order="0" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="24" /> <meta expanded="no" heading="H1" charCount="49" wordCount="9" paraCount="1" cursorPos="23" />
<name status="sf12341" import="icfb3a5" active="yes">John Smith</name> <name status="sf12341" import="i2d7a54" active="yes">John Smith</name>
</item> </item>
<item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE"> <item handle="bb2c23b3c42cc" parent="f7e2d9f330615" root="f6622b4617424" order="1" type="FILE" class="CHARACTER" layout="NOTE">
<meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="25" /> <meta expanded="no" heading="H1" charCount="55" wordCount="9" paraCount="1" cursorPos="31" />
<name status="sf12341" import="i2d7a54" active="yes">Jane Smith</name> <name status="sf12341" import="i56be10" active="yes">Jane Smith</name>
</item> </item>
<item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD"> <item handle="15c4492bd5107" parent="None" root="15c4492bd5107" order="3" type="ROOT" class="WORLD">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sf12341" import="ia857f0">Locations</name> <name status="sf12341" import="ia857f0">Locations</name>
</item> </item>
<item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE"> <item handle="b3e74dbc1f584" parent="15c4492bd5107" root="15c4492bd5107" order="0" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="20" /> <meta expanded="no" heading="H1" charCount="76" wordCount="15" paraCount="1" cursorPos="111" />
<name status="sf12341" import="i56be10" active="yes">Earth</name> <name status="sf12341" import="i2d7a54" active="yes">Earth</name>
</item> </item>
<item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE"> <item handle="f1471bef9f2ae" parent="15c4492bd5107" root="15c4492bd5107" order="1" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="133" /> <meta expanded="no" heading="H1" charCount="115" wordCount="24" paraCount="1" cursorPos="0" />
<name status="sf12341" import="icfb3a5" active="yes">Space</name> <name status="sf12341" import="i4a1d39" active="yes">Space</name>
</item> </item>
<item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE"> <item handle="5eaea4e8cdee8" parent="15c4492bd5107" root="15c4492bd5107" order="2" type="FILE" class="WORLD" layout="NOTE">
<meta expanded="no" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="45" /> <meta expanded="no" heading="H1" charCount="28" wordCount="6" paraCount="1" cursorPos="62" />
<name status="sf12341" import="i2d7a54" active="yes">Mars</name> <name status="sf12341" import="icfb3a5" active="yes">Mars</name>
</item> </item>
<item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE"> <item handle="6827118336ac1" parent="None" root="6827118336ac1" order="4" type="ROOT" class="ARCHIVE">
<meta expanded="yes" /> <meta expanded="yes" />
@@ -136,7 +137,23 @@
<meta expanded="no" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239" /> <meta expanded="no" heading="H3" charCount="232" wordCount="42" paraCount="1" cursorPos="239" />
<name status="s90e6c9" import="ia857f0" active="yes">Old File</name> <name status="s90e6c9" import="ia857f0" active="yes">Old File</name>
</item> </item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="5" type="ROOT" class="TRASH"> <item handle="f4ed1ae756a1f" parent="None" root="f4ed1ae756a1f" order="5" type="ROOT" class="TEMPLATE">
<meta expanded="yes" />
<name status="sf12341" import="ia857f0">Templates</name>
</item>
<item handle="5aec885635c85" parent="f4ed1ae756a1f" root="f4ed1ae756a1f" order="0" type="FILE" class="TEMPLATE" layout="DOCUMENT">
<meta expanded="no" heading="H3" charCount="9" wordCount="2" paraCount="1" cursorPos="78" />
<name status="sf12341" import="ia857f0" active="yes">Scene</name>
</item>
<item handle="2a60782759c6f" parent="f4ed1ae756a1f" root="f4ed1ae756a1f" order="1" type="FILE" class="TEMPLATE" layout="DOCUMENT">
<meta expanded="no" heading="H2" charCount="11" wordCount="2" paraCount="1" cursorPos="81" />
<name status="sf12341" import="ia857f0" active="yes">Chapter</name>
</item>
<item handle="5ee8aebcdebc9" parent="f4ed1ae756a1f" root="f4ed1ae756a1f" order="2" type="FILE" class="TEMPLATE" layout="NOTE">
<meta expanded="no" heading="H1" charCount="53" wordCount="7" paraCount="1" cursorPos="75" />
<name status="sf12341" import="ia857f0" active="yes">Character Note</name>
</item>
<item handle="98acd8c76c93a" parent="None" root="98acd8c76c93a" order="6" type="ROOT" class="TRASH">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sf12341" import="ia857f0">Trash</name> <name status="sf12341" import="ia857f0">Trash</name>
</item> </item>
+4 -4
View File
@@ -1,6 +1,6 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.6b1" hexVersion="0x020600b1" fileVersion="1.5" fileRevision="4" timeStamp="2024-11-20 19:22:22"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:45:09">
<project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="51" autoCount="29" editTime="2456"> <project id="5ac0df12-8b8b-476b-9905-21d33685687c" saveCount="52" autoCount="29" editTime="2465">
<name>Lorem Ipsum</name> <name>Lorem Ipsum</name>
<author>lipsum.com</author> <author>lipsum.com</author>
</project> </project>
@@ -31,7 +31,7 @@
<entry key="id6b1d0" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry> <entry key="id6b1d0" count="0" red="50" green="200" blue="0" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="22" novelWords="3115" notesWords="738"> <content items="22" novelWords="3115" notesWords="738" novelChars="20774" notesChars="5003">
<item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL"> <item handle="b3643d0f92e32" parent="None" root="b3643d0f92e32" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sbaa94f" import="i613591">Novel</name> <name status="sbaa94f" import="i613591">Novel</name>
@@ -116,7 +116,7 @@
<meta expanded="no" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="47" /> <meta expanded="no" heading="H1" charCount="1770" wordCount="259" paraCount="3" cursorPos="47" />
<name status="sbaa94f" import="i613591" active="yes">Ancient Europe</name> <name status="sbaa94f" import="i613591" active="yes">Ancient Europe</name>
</item> </item>
<item handle="1ace7ab1a0fc6" parent="None" root="1ace7ab1a0fc6" order="0" type="ROOT" class="TRASH"> <item handle="1ace7ab1a0fc6" parent="None" root="1ace7ab1a0fc6" order="4" type="ROOT" class="TRASH">
<meta expanded="no" /> <meta expanded="no" />
<name status="sbaa94f" import="i613591">Trash</name> <name status="sbaa94f" import="i613591">Trash</name>
</item> </item>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:48"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:54">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -28,7 +28,7 @@
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry> <entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="12" novelWords="10" notesWords="6"> <content items="12" novelWords="10" notesWords="6" novelChars="45" notesChars="22">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="no" /> <meta expanded="no" />
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:48"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:54">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -28,7 +28,7 @@
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry> <entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="16" novelWords="9" notesWords="0"> <content items="16" novelWords="9" notesWords="0" novelChars="40" notesChars="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="no" /> <meta expanded="no" />
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:46"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:52">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="1" editTime="0">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -28,7 +28,7 @@
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry> <entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="19" novelWords="28" notesWords="0"> <content items="19" novelWords="28" notesWords="0" novelChars="129" notesChars="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="no" /> <meta expanded="no" />
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:46"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:52">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Project A</name> <name>Test Project A</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -28,7 +28,7 @@
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry> <entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="22" novelWords="0" notesWords="0"> <content items="22" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="no" /> <meta expanded="no" />
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:46"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:42:52">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="1" autoCount="0" editTime="0">
<name>Test Project B</name> <name>Test Project B</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -28,7 +28,7 @@
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry> <entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="16" novelWords="0" notesWords="0"> <content items="16" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="no" /> <meta expanded="no" />
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
@@ -1,7 +1,7 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<ns0:Properties xmlns:ns0="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties"> <ns0:Properties xmlns:ns0="http://schemas.openxmlformats.org/officeDocument/2006/extended-properties">
<ns0:TotalTime>40</ns0:TotalTime> <ns0:TotalTime>41</ns0:TotalTime>
<ns0:Application>novelWriter/2.6a3</ns0:Application> <ns0:Application>novelWriter/2.7b1</ns0:Application>
<ns0:Words>4035</ns0:Words> <ns0:Words>4035</ns0:Words>
<ns0:Characters>21296</ns0:Characters> <ns0:Characters>21296</ns0:Characters>
<ns0:CharactersWithSpaces>24964</ns0:CharactersWithSpaces> <ns0:CharactersWithSpaces>24964</ns0:CharactersWithSpaces>
@@ -1,10 +1,10 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> <cp:coreProperties xmlns:cp="http://schemas.openxmlformats.org/package/2006/metadata/core-properties" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<dcterms:created xsi:type="dcterms:W3CDTF">2024-11-20T19:45:15</dcterms:created> <dcterms:created xsi:type="dcterms:W3CDTF">2025-04-29T22:46:36</dcterms:created>
<dcterms:modified xsi:type="dcterms:W3CDTF">2024-11-20T19:45:15</dcterms:modified> <dcterms:modified xsi:type="dcterms:W3CDTF">2025-04-29T22:46:36</dcterms:modified>
<dc:creator>lipsum.com</dc:creator> <dc:creator>lipsum.com</dc:creator>
<dc:title>Lorem Ipsum</dc:title> <dc:title>Lorem Ipsum</dc:title>
<dc:language>en_GB</dc:language> <dc:language>en_GB</dc:language>
<cp:revision>51</cp:revision> <cp:revision>52</cp:revision>
<cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy> <cp:lastModifiedBy>lipsum.com</cp:lastModifiedBy>
</cp:coreProperties> </cp:coreProperties>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:42:59"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:08:41">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="4"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="3" autoCount="2" editTime="4">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -28,7 +28,7 @@
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry> <entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="12" novelWords="173" notesWords="27"> <content items="12" novelWords="173" notesWords="27" novelChars="1007" notesChars="133">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="4" timeStamp="2025-05-11 12:39:41"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2025-04-29 22:01:24">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="2" autoCount="1" editTime="0">
<name>New Project</name> <name>New Project</name>
<author>Jane Doe</author> <author>Jane Doe</author>
@@ -28,7 +28,7 @@
<entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry> <entry key="i000007" count="0" red="220" green="138" blue="221" shape="BLOCK_4">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="8" novelWords="9" notesWords="0"> <content items="8" novelWords="9" notesWords="0" novelChars="40" notesChars="0">
<item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL"> <item handle="0000000000008" parent="None" root="0000000000008" order="0" type="ROOT" class="NOVEL">
<meta expanded="no" /> <meta expanded="no" />
<name status="s000000" import="i000004">Novel</name> <name status="s000000" import="i000004">Novel</name>
+130 -30
View File
@@ -38,10 +38,10 @@
"metaAttr": { "metaAttr": {
"expanded": false, "expanded": false,
"heading": "H1", "heading": "H1",
"charCount": 93, "charCount": 148,
"wordCount": 19, "wordCount": 29,
"paraCount": 2, "paraCount": 4,
"cursorPos": 119 "cursorPos": 178
}, },
"nameAttr": { "nameAttr": {
"status": "sc24b8f", "status": "sc24b8f",
@@ -63,10 +63,10 @@
"metaAttr": { "metaAttr": {
"expanded": false, "expanded": false,
"heading": "H0", "heading": "H0",
"charCount": 251, "charCount": 233,
"wordCount": 50, "wordCount": 47,
"paraCount": 2, "paraCount": 2,
"cursorPos": 277 "cursorPos": 194
}, },
"nameAttr": { "nameAttr": {
"status": "sf12341", "status": "sf12341",
@@ -138,10 +138,10 @@
"metaAttr": { "metaAttr": {
"expanded": false, "expanded": false,
"heading": "H3", "heading": "H3",
"charCount": 2687, "charCount": 2999,
"wordCount": 479, "wordCount": 530,
"paraCount": 14, "paraCount": 16,
"cursorPos": 67 "cursorPos": 159
}, },
"nameAttr": { "nameAttr": {
"status": "s90e6c9", "status": "s90e6c9",
@@ -166,7 +166,7 @@
"charCount": 548, "charCount": 548,
"wordCount": 108, "wordCount": 108,
"paraCount": 3, "paraCount": 3,
"cursorPos": 465 "cursorPos": 650
}, },
"nameAttr": { "nameAttr": {
"status": "s90e6c9", "status": "s90e6c9",
@@ -191,7 +191,7 @@
"charCount": 617, "charCount": 617,
"wordCount": 101, "wordCount": 101,
"paraCount": 3, "paraCount": 3,
"cursorPos": 310 "cursorPos": 1182
}, },
"nameAttr": { "nameAttr": {
"status": "s78ea90", "status": "s78ea90",
@@ -216,7 +216,7 @@
"charCount": 1909, "charCount": 1909,
"wordCount": 346, "wordCount": 346,
"paraCount": 7, "paraCount": 7,
"cursorPos": 0 "cursorPos": 1940
}, },
"nameAttr": { "nameAttr": {
"status": "sf24ce6", "status": "sf24ce6",
@@ -241,7 +241,7 @@
"charCount": 139, "charCount": 139,
"wordCount": 28, "wordCount": 28,
"paraCount": 1, "paraCount": 1,
"cursorPos": 188 "cursorPos": 356
}, },
"nameAttr": { "nameAttr": {
"status": "s90e6c9", "status": "s90e6c9",
@@ -266,10 +266,10 @@
"charCount": 189, "charCount": 189,
"wordCount": 37, "wordCount": 37,
"paraCount": 1, "paraCount": 1,
"cursorPos": 0 "cursorPos": 237
}, },
"nameAttr": { "nameAttr": {
"status": "s90e6c9", "status": "sd51c5b",
"import": "ia857f0", "import": "ia857f0",
"active": true "active": true
} }
@@ -341,10 +341,10 @@
"charCount": 299, "charCount": 299,
"wordCount": 55, "wordCount": 55,
"paraCount": 2, "paraCount": 2,
"cursorPos": 104 "cursorPos": 387
}, },
"nameAttr": { "nameAttr": {
"status": "s90e6c9", "status": "s8ae72a",
"import": "ia857f0", "import": "ia857f0",
"active": true "active": true
} }
@@ -416,11 +416,11 @@
"charCount": 49, "charCount": 49,
"wordCount": 9, "wordCount": 9,
"paraCount": 1, "paraCount": 1,
"cursorPos": 24 "cursorPos": 23
}, },
"nameAttr": { "nameAttr": {
"status": "sf12341", "status": "sf12341",
"import": "icfb3a5", "import": "i2d7a54",
"active": true "active": true
} }
}, },
@@ -441,11 +441,11 @@
"charCount": 55, "charCount": 55,
"wordCount": 9, "wordCount": 9,
"paraCount": 1, "paraCount": 1,
"cursorPos": 25 "cursorPos": 31
}, },
"nameAttr": { "nameAttr": {
"status": "sf12341", "status": "sf12341",
"import": "i2d7a54", "import": "i56be10",
"active": true "active": true
} }
}, },
@@ -491,11 +491,11 @@
"charCount": 76, "charCount": 76,
"wordCount": 15, "wordCount": 15,
"paraCount": 1, "paraCount": 1,
"cursorPos": 20 "cursorPos": 111
}, },
"nameAttr": { "nameAttr": {
"status": "sf12341", "status": "sf12341",
"import": "i56be10", "import": "i2d7a54",
"active": true "active": true
} }
}, },
@@ -516,11 +516,11 @@
"charCount": 115, "charCount": 115,
"wordCount": 24, "wordCount": 24,
"paraCount": 1, "paraCount": 1,
"cursorPos": 133 "cursorPos": 0
}, },
"nameAttr": { "nameAttr": {
"status": "sf12341", "status": "sf12341",
"import": "icfb3a5", "import": "i4a1d39",
"active": true "active": true
} }
}, },
@@ -541,11 +541,11 @@
"charCount": 28, "charCount": 28,
"wordCount": 6, "wordCount": 6,
"paraCount": 1, "paraCount": 1,
"cursorPos": 45 "cursorPos": 62
}, },
"nameAttr": { "nameAttr": {
"status": "sf12341", "status": "sf12341",
"import": "i2d7a54", "import": "icfb3a5",
"active": true "active": true
} }
}, },
@@ -624,13 +624,113 @@
"active": true "active": true
} }
}, },
{
"name": "Templates",
"itemAttr": {
"handle": "f4ed1ae756a1f",
"parent": null,
"root": "f4ed1ae756a1f",
"order": 5,
"type": "ROOT",
"class": "TEMPLATE",
"layout": "NO_LAYOUT"
},
"metaAttr": {
"expanded": true,
"heading": "H0",
"charCount": 0,
"wordCount": 0,
"paraCount": 0,
"cursorPos": 0
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": false
}
},
{
"name": "Scene",
"itemAttr": {
"handle": "5aec885635c85",
"parent": "f4ed1ae756a1f",
"root": "f4ed1ae756a1f",
"order": 0,
"type": "FILE",
"class": "TEMPLATE",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H3",
"charCount": 9,
"wordCount": 2,
"paraCount": 1,
"cursorPos": 78
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": true
}
},
{
"name": "Chapter",
"itemAttr": {
"handle": "2a60782759c6f",
"parent": "f4ed1ae756a1f",
"root": "f4ed1ae756a1f",
"order": 1,
"type": "FILE",
"class": "TEMPLATE",
"layout": "DOCUMENT"
},
"metaAttr": {
"expanded": false,
"heading": "H2",
"charCount": 11,
"wordCount": 2,
"paraCount": 1,
"cursorPos": 81
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": true
}
},
{
"name": "Character Note",
"itemAttr": {
"handle": "5ee8aebcdebc9",
"parent": "f4ed1ae756a1f",
"root": "f4ed1ae756a1f",
"order": 2,
"type": "FILE",
"class": "TEMPLATE",
"layout": "NOTE"
},
"metaAttr": {
"expanded": false,
"heading": "H1",
"charCount": 53,
"wordCount": 7,
"paraCount": 1,
"cursorPos": 75
},
"nameAttr": {
"status": "sf12341",
"import": "ia857f0",
"active": true
}
},
{ {
"name": "Trash", "name": "Trash",
"itemAttr": { "itemAttr": {
"handle": "98acd8c76c93a", "handle": "98acd8c76c93a",
"parent": null, "parent": null,
"root": "98acd8c76c93a", "root": "98acd8c76c93a",
"order": 5, "order": 6,
"type": "ROOT", "type": "ROOT",
"class": "TRASH", "class": "TRASH",
"layout": "NO_LAYOUT" "layout": "NO_LAYOUT"
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2020-05-28 09:59:15"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2020-05-28 09:59:15">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="0" autoCount="0" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -35,7 +35,7 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="22" novelWords="0" notesWords="0"> <content items="22" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="s000002" import="i000007">Novel</name> <name status="s000002" import="i000007">Novel</name>
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2020-06-26 21:20:24"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2020-06-26 21:20:24">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -35,7 +35,7 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="22" novelWords="0" notesWords="0"> <content items="22" novelWords="0" notesWords="0" novelChars="0" notesChars="0">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="s000002" import="i000007">Novel</name> <name status="s000002" import="i000007">Novel</name>
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2021-08-30 23:33:44"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2021-08-30 23:33:44">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -35,7 +35,7 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="25" novelWords="840" notesWords="376"> <content items="25" novelWords="840" notesWords="376" novelChars="0" notesChars="0">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="s000002" import="i000007">Novel</name> <name status="s000002" import="i000007">Novel</name>
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2022-10-25 18:26:15"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2022-10-25 18:26:15">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -35,7 +35,7 @@
<entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry> <entry key="i00000a" count="0" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="25" novelWords="830" notesWords="376"> <content items="25" novelWords="830" notesWords="376" novelChars="0" notesChars="0">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="s000002" import="i000007">Novel</name> <name status="s000002" import="i000007">Novel</name>
+2 -2
View File
@@ -1,5 +1,5 @@
<?xml version='1.0' encoding='utf-8'?> <?xml version='1.0' encoding='utf-8'?>
<novelWriterXML appVersion="2.0-rc1" hexVersion="0x020000c1" fileVersion="1.5" fileRevision="4" timeStamp="2022-10-15 12:12:59"> <novelWriterXML appVersion="2.7b1" hexVersion="0x020700b1" fileVersion="1.5" fileRevision="5" timeStamp="2022-10-15 12:12:59">
<project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000"> <project id="d0f3fe10-c6e6-4310-8bfd-181eb4224eed" saveCount="5" autoCount="10" editTime="1000">
<name>Sample Project</name> <name>Sample Project</name>
<author>Jay Doh</author> <author>Jay Doh</author>
@@ -35,7 +35,7 @@
<entry key="i56be10" count="1" red="117" green="0" blue="175" shape="SQUARE">Main</entry> <entry key="i56be10" count="1" red="117" green="0" blue="175" shape="SQUARE">Main</entry>
</importance> </importance>
</settings> </settings>
<content items="27" novelWords="954" notesWords="409"> <content items="27" novelWords="954" notesWords="409" novelChars="0" notesChars="0">
<item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL"> <item handle="7031beac91f75" parent="None" root="7031beac91f75" order="0" type="ROOT" class="NOVEL">
<meta expanded="yes" /> <meta expanded="yes" />
<name status="sc24b8f" import="ia857f0">Novel</name> <name status="sc24b8f" import="ia857f0">Novel</name>
+10
View File
@@ -26,6 +26,7 @@ import pytest
from PyQt6.QtGui import QIcon from PyQt6.QtGui import QIcon
from novelwriter import CONFIG
from novelwriter.core.item import NWItem from novelwriter.core.item import NWItem
from novelwriter.core.project import NWProject from novelwriter.core.project import NWProject
from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType from novelwriter.enum import nwItemClass, nwItemLayout, nwItemType
@@ -168,6 +169,15 @@ def testCoreItem_Setters(mockGUI, mockRnd, fncPath):
item.setParaCount(1) item.setParaCount(1)
assert item.paraCount == 1 assert item.paraCount == 1
# MainCount
item.setWordCount(123)
item.setCharCount(1234)
CONFIG.useCharCount = False
assert item.mainCount == 123
CONFIG.useCharCount = True
assert item.mainCount == 1234
CONFIG.useCharCount = False
# CursorPos # CursorPos
item.setCursorPos(None) item.setCursorPos(None)
assert item.cursorPos == 0 assert item.cursorPos == 0
+40 -37
View File
@@ -52,8 +52,8 @@ class MockProject:
@pytest.fixture(scope="function", autouse=True) @pytest.fixture(scope="function", autouse=True)
def mockVersion(monkeypatch): def mockVersion(monkeypatch):
"""Mock the version info to prevent diff from failing.""" """Mock the version info to prevent diff from failing."""
monkeypatch.setattr("novelwriter.core.projectxml.__version__", "2.0-rc1") monkeypatch.setattr("novelwriter.core.projectxml.__version__", "2.7b1")
monkeypatch.setattr("novelwriter.core.projectxml.__hexversion__", "0x020000c1") monkeypatch.setattr("novelwriter.core.projectxml.__hexversion__", "0x020700b1")
return return
@@ -137,23 +137,23 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath):
assert xmlReader.state == XMLReadState.PARSED_OK assert xmlReader.state == XMLReadState.PARSED_OK
assert xmlReader.xmlRoot == "novelWriterXML" assert xmlReader.xmlRoot == "novelWriterXML"
assert xmlReader.xmlVersion == 0x0105 assert xmlReader.xmlVersion == 0x0105
assert xmlReader.xmlRevision == 4 assert xmlReader.xmlRevision == 5
assert xmlReader.appVersion == "2.0-rc1" assert xmlReader.appVersion == "2.7b1"
assert xmlReader.hexVersion == 0x020000c1 assert xmlReader.hexVersion == 0x020700b1
# Check loaded data # Check loaded data
assert data.name == "Sample Project" assert data.name == "Sample Project"
assert data.author == "Jane Smith" assert data.author == "Jane Smith"
assert data.saveCount == 5 assert data.saveCount == 2179
assert data.autoCount == 10 assert data.autoCount == 285
assert data.editTime == 1000 assert data.editTime == 1000
assert data.doBackup is True assert data.doBackup is False
assert data.language == "en_GB" assert data.language == "en_GB"
assert data.spellCheck is True assert data.spellCheck is True
assert data.spellLang == "en_GB" assert data.spellLang is None
assert data.initCounts == (954, 409) assert data.initCounts == (1016, 416, 5602, 2285)
assert data.currCounts == (954, 409) assert data.currCounts == (1016, 416, 5602, 2285)
assert data.getLastHandle("editor") == "636b6aa9b697b" assert data.getLastHandle("editor") == "636b6aa9b697b"
assert data.getLastHandle("viewer") == "636b6aa9b697b" assert data.getLastHandle("viewer") == "636b6aa9b697b"
@@ -182,33 +182,36 @@ def testCoreProjectXML_ReadCurrent(monkeypatch, mockGUI, tstPaths, fncPath):
assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58) assert data.itemStatus["s78ea90"].color == QColor(58, 180, 58)
assert data.itemImport["ia857f0"].color == QColor(100, 100, 100) assert data.itemImport["ia857f0"].color == QColor(100, 100, 100)
assert data.itemImport["icfb3a5"].color == QColor(0, 122, 188) assert data.itemImport["i4a1d39"].color == QColor(220, 138, 221)
assert data.itemImport["i2d7a54"].color == QColor(21, 0, 180) assert data.itemImport["icfb3a5"].color == QColor(220, 138, 221)
assert data.itemImport["i56be10"].color == QColor(117, 0, 175) assert data.itemImport["i2d7a54"].color == QColor(220, 138, 221)
assert data.itemImport["i56be10"].color == QColor(220, 138, 221)
assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE assert data.itemStatus["sf12341"].shape == nwStatusShape.SQUARE
assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE assert data.itemStatus["sf24ce6"].shape == nwStatusShape.SQUARE
assert data.itemStatus["sc24b8f"].shape == nwStatusShape.SQUARE assert data.itemStatus["sc24b8f"].shape == nwStatusShape.BARS_1
assert data.itemStatus["s90e6c9"].shape == nwStatusShape.SQUARE assert data.itemStatus["s90e6c9"].shape == nwStatusShape.BARS_2
assert data.itemStatus["sd51c5b"].shape == nwStatusShape.SQUARE assert data.itemStatus["sd51c5b"].shape == nwStatusShape.BARS_3
assert data.itemStatus["s8ae72a"].shape == nwStatusShape.SQUARE assert data.itemStatus["s8ae72a"].shape == nwStatusShape.BARS_4
assert data.itemStatus["s78ea90"].shape == nwStatusShape.SQUARE assert data.itemStatus["s78ea90"].shape == nwStatusShape.STAR
assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE assert data.itemImport["ia857f0"].shape == nwStatusShape.SQUARE
assert data.itemImport["icfb3a5"].shape == nwStatusShape.SQUARE assert data.itemImport["i4a1d39"].shape == nwStatusShape.BLOCK_1
assert data.itemImport["i2d7a54"].shape == nwStatusShape.SQUARE assert data.itemImport["icfb3a5"].shape == nwStatusShape.BLOCK_2
assert data.itemImport["i56be10"].shape == nwStatusShape.SQUARE assert data.itemImport["i2d7a54"].shape == nwStatusShape.BLOCK_3
assert data.itemImport["i56be10"].shape == nwStatusShape.BLOCK_4
assert data.itemStatus["sf12341"].count == 4 assert data.itemStatus["sf12341"].count == 8
assert data.itemStatus["sf24ce6"].count == 2 assert data.itemStatus["sf24ce6"].count == 2
assert data.itemStatus["sc24b8f"].count == 3 assert data.itemStatus["sc24b8f"].count == 3
assert data.itemStatus["s90e6c9"].count == 7 assert data.itemStatus["s90e6c9"].count == 5
assert data.itemStatus["sd51c5b"].count == 0 assert data.itemStatus["sd51c5b"].count == 1
assert data.itemStatus["s8ae72a"].count == 0 assert data.itemStatus["s8ae72a"].count == 1
assert data.itemStatus["s78ea90"].count == 1 assert data.itemStatus["s78ea90"].count == 1
assert data.itemImport["ia857f0"].count == 5 assert data.itemImport["ia857f0"].count == 5
assert data.itemImport["icfb3a5"].count == 2 assert data.itemImport["i4a1d39"].count == 1
assert data.itemImport["icfb3a5"].count == 1
assert data.itemImport["i2d7a54"].count == 2 assert data.itemImport["i2d7a54"].count == 2
assert data.itemImport["i56be10"].count == 1 assert data.itemImport["i56be10"].count == 1
@@ -281,8 +284,8 @@ def testCoreProjectXML_ReadLegacy10(tstPaths, fncPath, mockGUI, mockRnd):
assert data.language is None # Doesn't exist in 1.0 assert data.language is None # Doesn't exist in 1.0
assert data.spellCheck is True assert data.spellCheck is True
assert data.spellLang is None # Doesn't exist in 1.0 assert data.spellLang is None # Doesn't exist in 1.0
assert data.initCounts == (0, 0) assert data.initCounts == (0, 0, 0, 0)
assert data.currCounts == (0, 0) assert data.currCounts == (0, 0, 0, 0)
assert data.getLastHandle("editor") is None # Dropped by conversion assert data.getLastHandle("editor") is None # Dropped by conversion
assert data.getLastHandle("viewer") is None # Dropped by conversion assert data.getLastHandle("viewer") is None # Dropped by conversion
@@ -426,8 +429,8 @@ def testCoreProjectXML_ReadLegacy11(tstPaths, fncPath, mockGUI, mockRnd):
assert data.language is None # Doesn't exist in 1.1 assert data.language is None # Doesn't exist in 1.1
assert data.spellCheck is True assert data.spellCheck is True
assert data.spellLang is None # Doesn't exist in 1.1 assert data.spellLang is None # Doesn't exist in 1.1
assert data.initCounts == (0, 0) assert data.initCounts == (0, 0, 0, 0)
assert data.currCounts == (0, 0) assert data.currCounts == (0, 0, 0, 0)
assert data.getLastHandle("editor") is None # Dropped by conversion assert data.getLastHandle("editor") is None # Dropped by conversion
assert data.getLastHandle("viewer") is None # Dropped by conversion assert data.getLastHandle("viewer") is None # Dropped by conversion
@@ -571,8 +574,8 @@ def testCoreProjectXML_ReadLegacy12(tstPaths, fncPath, mockGUI, mockRnd):
assert data.language == "en_GB" assert data.language == "en_GB"
assert data.spellCheck is True assert data.spellCheck is True
assert data.spellLang == "en_GB" assert data.spellLang == "en_GB"
assert data.initCounts == (840, 376) assert data.initCounts == (840, 376, 0, 0)
assert data.currCounts == (840, 376) assert data.currCounts == (840, 376, 0, 0)
assert data.getLastHandle("editor") is None # Dropped by conversion assert data.getLastHandle("editor") is None # Dropped by conversion
assert data.getLastHandle("viewer") is None # Dropped by conversion assert data.getLastHandle("viewer") is None # Dropped by conversion
@@ -719,8 +722,8 @@ def testCoreProjectXML_ReadLegacy13(tstPaths, fncPath, mockGUI, mockRnd):
assert data.language == "en_GB" assert data.language == "en_GB"
assert data.spellCheck is True assert data.spellCheck is True
assert data.spellLang == "en_GB" assert data.spellLang == "en_GB"
assert data.initCounts == (830, 376) assert data.initCounts == (830, 376, 0, 0)
assert data.currCounts == (830, 376) assert data.currCounts == (830, 376, 0, 0)
assert data.getLastHandle("editor") is None # Dropped by conversion assert data.getLastHandle("editor") is None # Dropped by conversion
assert data.getLastHandle("viewer") is None # Dropped by conversion assert data.getLastHandle("viewer") is None # Dropped by conversion
@@ -867,8 +870,8 @@ def testCoreProjectXML_ReadLegacy14(tstPaths, fncPath, mockGUI, mockRnd):
assert data.language == "en_GB" assert data.language == "en_GB"
assert data.spellCheck is True assert data.spellCheck is True
assert data.spellLang == "en_GB" assert data.spellLang == "en_GB"
assert data.initCounts == (954, 409) assert data.initCounts == (954, 409, 0, 0)
assert data.currCounts == (954, 409) assert data.currCounts == (954, 409, 0, 0)
assert data.getLastHandle("editor") is None # Dropped by conversion assert data.getLastHandle("editor") is None # Dropped by conversion
assert data.getLastHandle("viewer") is None # Dropped by conversion assert data.getLastHandle("viewer") is None # Dropped by conversion
+8 -6
View File
@@ -43,8 +43,8 @@ def testCoreSessions_Main(monkeypatch, mockGUI, fncPath):
assert isinstance(logFile, Path) assert isinstance(logFile, Path)
# Set some mock word counts # Set some mock word counts
project.data.setInitCounts(50, 60) project.data.setInitCounts(50, 60, 500, 600)
project.data.setCurrCounts(160, 150) project.data.setCurrCounts(160, 150, 1600, 1500)
# The project init should already have created the session # The project init should already have created the session
sessLog = project.session sessLog = project.session
@@ -71,17 +71,19 @@ def testCoreSessions_Main(monkeypatch, mockGUI, fncPath):
assert records[1]["type"] == "record" assert records[1]["type"] == "record"
assert records[1]["novel"] == 160 assert records[1]["novel"] == 160
assert records[1]["notes"] == 150 assert records[1]["notes"] == 150
assert records[1]["cnovel"] == 1600
assert records[1]["cnotes"] == 1500
assert records[1]["idle"] == 1 # Should be rounded to full seconds assert records[1]["idle"] == 1 # Should be rounded to full seconds
# Adding another record without changing word count should do nothing # Adding another record without changing word count should do nothing
project.data.setInitCounts(160, 150) project.data.setInitCounts(160, 150, 1600, 1500)
project.data.setCurrCounts(160, 150) project.data.setCurrCounts(160, 150, 1600, 1500)
assert sessLog.appendSession(1.6) is False assert sessLog.appendSession(1.6) is False
assert len(list(sessLog.iterRecords())) == 2 assert len(list(sessLog.iterRecords())) == 2
# But adding when count has changed should # But adding when count has changed should
project.data.setInitCounts(160, 150) project.data.setInitCounts(160, 150, 1600, 1500)
project.data.setCurrCounts(270, 240) project.data.setCurrCounts(270, 240, 2700, 2400)
sessLog._start -= 350.0 # Backdate the session start to allow logging sessLog._start -= 350.0 # Backdate the session start to allow logging
assert sessLog.appendSession(1.6) is True assert sessLog.appendSession(1.6) is True
records = list(sessLog.iterRecords()) records = list(sessLog.iterRecords())
+6 -2
View File
@@ -435,8 +435,8 @@ def testCoreStorage_OldFormatConvert(monkeypatch, mockGUI, fncPath):
sessLogOld.write_text(( sessLogOld.write_text((
"# Offset 150\n" "# Offset 150\n"
"# Start Time End Time Novel Notes Idle\n" "# Start Time End Time Novel Notes Idle\n"
"2021-02-02 02:02:02 2021-02-02 03:03:03 200 200 10\n" "2021-02-02 02:02:02 2021-02-02 03:03:03 200 200 10\n"
"2021-03-03 03:03:03 2021-03-03 04:04:04 300 300 20\n" "2021-03-03 03:03:03 2021-03-03 04:04:04 300 300 20\n"
), encoding="utf-8") ), encoding="utf-8")
assert sessLogOld.exists() is True assert sessLogOld.exists() is True
@@ -496,6 +496,8 @@ def testCoreStorage_OldFormatConvert(monkeypatch, mockGUI, fncPath):
"end": "2021-02-02 03:03:03", "end": "2021-02-02 03:03:03",
"novel": 200, "novel": 200,
"notes": 200, "notes": 200,
"cnovel": 0,
"cnotes": 0,
"idle": 10, "idle": 10,
} }
assert data[2] == { assert data[2] == {
@@ -504,6 +506,8 @@ def testCoreStorage_OldFormatConvert(monkeypatch, mockGUI, fncPath):
"end": "2021-03-03 04:04:04", "end": "2021-03-03 04:04:04",
"novel": 300, "novel": 300,
"notes": 300, "notes": 300,
"cnovel": 0,
"cnotes": 0,
"idle": 20, "idle": 20,
} }
+1 -1
View File
@@ -436,7 +436,7 @@ def testCoreTree_OtherMethods(qtbot, monkeypatch, mockGUI, fncPath, mockRnd):
] ]
# Refresh All # Refresh All
assert tree.sumWords() == (9, 0) assert tree.sumCounts() == (9, 0, 40, 0)
assert tree.model.root.count == 9 assert tree.model.root.count == 9
for node in tree.nodes.values(): for node in tree.nodes.values():
@@ -163,14 +163,17 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True)) mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
prefs.nativeFont.setChecked(True) # Use OS font dialog prefs.nativeFont.setChecked(True) # Use OS font dialog
prefs.guiFontButton.click() prefs.guiFontButton.click()
prefs.hideVScroll.setChecked(True) prefs.hideVScroll.setChecked(True)
prefs.hideHScroll.setChecked(True) prefs.hideHScroll.setChecked(True)
prefs.useCharCount.setChecked(True)
assert CONFIG.guiLocale != "en_US" assert CONFIG.guiLocale != "en_US"
assert CONFIG.guiTheme != "default_dark" assert CONFIG.guiTheme != "default_dark"
assert CONFIG.guiFont.family() != "" assert CONFIG.guiFont.family() != ""
assert CONFIG.hideVScroll is False assert CONFIG.hideVScroll is False
assert CONFIG.hideHScroll is False assert CONFIG.hideHScroll is False
assert CONFIG.useCharCount is False
# Document Style # Document Style
prefs.guiSyntax.setCurrentIndex(prefs.guiSyntax.findData("default_dark")) prefs.guiSyntax.setCurrentIndex(prefs.guiSyntax.findData("default_dark"))
@@ -178,6 +181,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True)) mp.setattr(QFontDialog, "getFont", lambda *a, **k: (QFont(), True))
prefs.nativeFont.setChecked(False) # Use Qt font dialog prefs.nativeFont.setChecked(False) # Use Qt font dialog
prefs.textFontButton.click() prefs.textFontButton.click()
prefs.showFullPath.setChecked(False) prefs.showFullPath.setChecked(False)
prefs.incNotesWCount.setChecked(False) prefs.incNotesWCount.setChecked(False)
@@ -344,6 +348,7 @@ def testDlgPreferences_Settings(qtbot, monkeypatch, nwGUI, fncPath, tstPaths):
assert CONFIG.guiFont == QFont() assert CONFIG.guiFont == QFont()
assert CONFIG.hideVScroll is True assert CONFIG.hideVScroll is True
assert CONFIG.hideHScroll is True assert CONFIG.hideHScroll is True
assert CONFIG.useCharCount is True
# Document Style # Document Style
assert CONFIG.guiSyntax == "default_dark" assert CONFIG.guiSyntax == "default_dark"
+2 -3
View File
@@ -1957,7 +1957,7 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, m
assert docEditor.docFooter.wordsText.text() == "Words: 0 (+0)" assert docEditor.docFooter.wordsText.text() == "Words: 0 (+0)"
# Open a document and populate it # Open a document and populate it
SHARED.project.tree[C.hSceneDoc]._initCount = 0 # type: ignore SHARED.project.tree[C.hSceneDoc]._wordInit = 0 # type: ignore
SHARED.project.tree[C.hSceneDoc]._wordCount = 0 # type: ignore SHARED.project.tree[C.hSceneDoc]._wordCount = 0 # type: ignore
assert nwGUI.openDocument(C.hSceneDoc) is True assert nwGUI.openDocument(C.hSceneDoc) is True
@@ -1981,7 +1981,6 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, m
assert threadPool.objectID() == id(docEditor._wCounterDoc) assert threadPool.objectID() == id(docEditor._wCounterDoc)
docEditor._wCounterDoc.run() docEditor._wCounterDoc.run()
# docEditor._updateDocCounts(cC, wC, pC)
assert SHARED.project.tree[C.hSceneDoc]._charCount == cC # type: ignore assert SHARED.project.tree[C.hSceneDoc]._charCount == cC # type: ignore
assert SHARED.project.tree[C.hSceneDoc]._wordCount == wC # type: ignore assert SHARED.project.tree[C.hSceneDoc]._wordCount == wC # type: ignore
assert SHARED.project.tree[C.hSceneDoc]._paraCount == pC # type: ignore assert SHARED.project.tree[C.hSceneDoc]._paraCount == pC # type: ignore
@@ -1993,7 +1992,7 @@ def testGuiEditor_WordCounters(qtbot, monkeypatch, nwGUI, projPath, ipsumText, m
assert threadPool.objectID() == id(docEditor._wCounterSel) assert threadPool.objectID() == id(docEditor._wCounterSel)
docEditor._wCounterSel.run() docEditor._wCounterSel.run()
assert docEditor.docFooter.wordsText.text() == f"Words: {wC} selected" assert docEditor.docFooter.wordsText.text() == f"Selected: {wC}"
# qtbot.stop() # qtbot.stop()
+1
View File
@@ -184,6 +184,7 @@ def testGuiMain_UpdateTheme(qtbot, nwGUI):
CONFIG.guiSyntax = "default_dark" CONFIG.guiSyntax = "default_dark"
mainTheme.loadTheme() mainTheme.loadTheme()
mainTheme.loadSyntax() mainTheme.loadSyntax()
nwGUI._processConfigChanges(False, True, False, False)
nwGUI._processConfigChanges(True, True, True, True) nwGUI._processConfigChanges(True, True, True, True)
syntax = SHARED.theme.syntaxTheme syntax = SHARED.theme.syntaxTheme
+1 -1
View File
@@ -93,7 +93,7 @@ def testGuiNovelView_Content(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
assert novelTree._getModel() is None assert novelTree._getModel() is None
# Reload # Reload
novelBar._forceRefreshNovelTree() novelView.refreshCurrentTree()
model = novelTree._getModel() model = novelTree._getModel()
assert isinstance(model, NovelModel) assert isinstance(model, NovelModel)
+14
View File
@@ -109,4 +109,18 @@ def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, projPath, mockRnd):
nwGUI._timeTick() nwGUI._timeTick()
assert status.statsText.text() == "Words: 11 (+11)" assert status.statsText.text() == "Words: 11 (+11)"
# Switch to character count
CONFIG.useCharCount = True
status.initSettings()
with monkeypatch.context() as mp:
mp.setattr("novelwriter.guimain.time", lambda *a: 50.0)
CONFIG.incNotesWCount = True
nwGUI._lastTotalCount = 0
nwGUI._timeTick()
assert status.statsText.text() == "Characters: 46 (+46)"
CONFIG.incNotesWCount = False
nwGUI._lastTotalCount = 0
nwGUI._timeTick()
assert status.statsText.text() == "Characters: 40 (+40)"
# qtbot.stop() # qtbot.stop()