Allow display of character count in editor footer and status bar

This commit is contained in:
Veronica Berglyd Olsen
2025-05-18 16:27:30 +02:00
parent 51c7fb2614
commit 916039b420
5 changed files with 87 additions and 52 deletions
+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)"),
+26 -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
@@ -170,7 +171,7 @@ class NWItem:
@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:
@@ -261,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
@@ -269,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
## ##
+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
+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:
+14 -4
View File
@@ -1077,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
@@ -1265,11 +1266,20 @@ class GuiMain(QMainWindow):
SHARED.project.updateCounts() SHARED.project.updateCounts()
if CONFIG.incNotesWCount: if CONFIG.incNotesWCount:
iTotal = sum(SHARED.project.data.initCounts[:2]) if CONFIG.useCharCount:
cTotal = sum(SHARED.project.data.currCounts[:2]) iTotal = sum(SHARED.project.data.initCounts[2:])
cTotal = sum(SHARED.project.data.currCounts[2:])
else:
iTotal = sum(SHARED.project.data.initCounts[:2])
cTotal = sum(SHARED.project.data.currCounts[:2])
else: else:
iTotal = SHARED.project.data.initCounts[0] if CONFIG.useCharCount:
cTotal = SHARED.project.data.currCounts[0] iTotal = SHARED.project.data.initCounts[2]
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) self.mainStatus.setProjectStats(cTotal, cTotal - iTotal)
return return