Project tree status and total word count changes (#882)

* Fix some inconsistencies in source file docstrings
* Remove the option to show status text in project tree
* Change how total word counts are calculated and propagated
* Fix tests and improve coverage
* Add setting in preferences
* Add missing word count timer to config file
This commit is contained in:
Veronica Berglyd Olsen
2021-09-10 00:38:45 +02:00
committed by GitHub
parent b9311e84ce
commit 3c8cb34b0a
19 changed files with 246 additions and 134 deletions
+12 -4
View File
@@ -4,7 +4,7 @@ novelWriter Common Functions
Various common functions Various common functions
File History: File History:
Created: 2019-05-12 [0.1.0] Created: 2019-05-12 [0.1]
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen Copyright 20182021, Veronica Berglyd Olsen
@@ -441,9 +441,10 @@ class NWConfigParser(ConfigParser):
CNF_STR = 0 CNF_STR = 0
CNF_INT = 1 CNF_INT = 1
CNF_BOOL = 2 CNF_FLOAT = 2
CNF_S_LST = 3 CNF_BOOL = 3
CNF_I_LST = 4 CNF_S_LST = 4
CNF_I_LST = 5
def __init__(self): def __init__(self):
super().__init__() super().__init__()
@@ -458,6 +459,11 @@ class NWConfigParser(ConfigParser):
""" """
return self._parseLine(section, option, default, self.CNF_INT) return self._parseLine(section, option, default, self.CNF_INT)
def rdFlt(self, section, option, default):
"""Read float value.
"""
return self._parseLine(section, option, default, self.CNF_FLOAT)
def rdBool(self, section, option, default): def rdBool(self, section, option, default):
"""Read boolean value. """Read boolean value.
""" """
@@ -503,6 +509,8 @@ class NWConfigParser(ConfigParser):
return self.get(section, option) return self.get(section, option)
elif type == self.CNF_INT: elif type == self.CNF_INT:
return self.getint(section, option) return self.getint(section, option)
elif type == self.CNF_FLOAT:
return self.getfloat(section, option)
elif type == self.CNF_BOOL: elif type == self.CNF_BOOL:
return self.getboolean(section, option) return self.getboolean(section, option)
elif type in (self.CNF_I_LST, self.CNF_S_LST): elif type in (self.CNF_I_LST, self.CNF_S_LST):
+13 -11
View File
@@ -108,7 +108,6 @@ class Config:
# Features # Features
self.hideVScroll = False # Hide vertical scroll bars on main widgets self.hideVScroll = False # Hide vertical scroll bars on main widgets
self.hideHScroll = False # Hide horizontal scroll bars on main widgets self.hideHScroll = False # Hide horizontal scroll bars on main widgets
self.fullStatus = True # Show the full status text in the project tree
self.emphLabels = True # Add emphasis to H1 and H2 item labels self.emphLabels = True # Add emphasis to H1 and H2 item labels
# Project # Project
@@ -145,6 +144,7 @@ class Config:
self.wordCountTimer = 5.0 # Interval for word count update in seconds self.wordCountTimer = 5.0 # Interval for word count update in seconds
self.bigDocLimit = 800 # Size threshold for heavy editor features in kilobytes self.bigDocLimit = 800 # Size threshold for heavy editor features in kilobytes
self.incNotesWCount = True # The status bar word count includes notes
self.highlightQuotes = True # Highlight text in quotes self.highlightQuotes = True # Highlight text in quotes
self.allowOpenSQuote = False # Allow open-ended single quotes self.allowOpenSQuote = False # Allow open-ended single quotes
@@ -179,9 +179,9 @@ class Config:
self.askBeforeBackup = True self.askBeforeBackup = True
# State # State
self.showRefPanel = True self.showRefPanel = True # The reference panel for the viewer is visible
self.viewComments = True self.viewComments = True # Comments are shown in the viewer
self.viewSynopsis = True self.viewSynopsis = True # Synopsis is shown in the viewer
# Check Qt5 Versions # Check Qt5 Versions
verQt = splitVersionNumber(QT_VERSION_STR) verQt = splitVersionNumber(QT_VERSION_STR)
@@ -466,7 +466,6 @@ class Config:
cnfSec = "Project" cnfSec = "Project"
self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj) self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj)
self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc) self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc)
self.fullStatus = theConf.rdBool(cnfSec, "fullstatus", self.fullStatus)
self.emphLabels = theConf.rdBool(cnfSec, "emphlabels", self.emphLabels) self.emphLabels = theConf.rdBool(cnfSec, "emphlabels", self.emphLabels)
# Editor # Editor
@@ -498,7 +497,9 @@ class Config:
self.showTabsNSpaces = theConf.rdBool(cnfSec, "showtabsnspaces", self.showTabsNSpaces) self.showTabsNSpaces = theConf.rdBool(cnfSec, "showtabsnspaces", self.showTabsNSpaces)
self.showLineEndings = theConf.rdBool(cnfSec, "showlineendings", self.showLineEndings) self.showLineEndings = theConf.rdBool(cnfSec, "showlineendings", self.showLineEndings)
self.showMultiSpaces = theConf.rdBool(cnfSec, "showmultispaces", self.showMultiSpaces) self.showMultiSpaces = theConf.rdBool(cnfSec, "showmultispaces", self.showMultiSpaces)
self.wordCountTimer = theConf.rdFlt(cnfSec, "wordcounttimer", self.wordCountTimer)
self.bigDocLimit = theConf.rdInt(cnfSec, "bigdoclimit", self.bigDocLimit) self.bigDocLimit = theConf.rdInt(cnfSec, "bigdoclimit", self.bigDocLimit)
self.incNotesWCount = theConf.rdBool(cnfSec, "incnoteswcount", self.incNotesWCount)
self.showFullPath = theConf.rdBool(cnfSec, "showfullpath", self.showFullPath) self.showFullPath = theConf.rdBool(cnfSec, "showfullpath", self.showFullPath)
self.highlightQuotes = theConf.rdBool(cnfSec, "highlightquotes", self.highlightQuotes) self.highlightQuotes = theConf.rdBool(cnfSec, "highlightquotes", self.highlightQuotes)
self.allowOpenSQuote = theConf.rdBool(cnfSec, "allowopensquote", self.allowOpenSQuote) self.allowOpenSQuote = theConf.rdBool(cnfSec, "allowopensquote", self.allowOpenSQuote)
@@ -588,7 +589,6 @@ class Config:
theConf["Project"] = { theConf["Project"] = {
"autosaveproject": str(self.autoSaveProj), "autosaveproject": str(self.autoSaveProj),
"autosavedoc": str(self.autoSaveDoc), "autosavedoc": str(self.autoSaveDoc),
"fullstatus": str(self.fullStatus),
"emphlabels": str(self.emphLabels), "emphlabels": str(self.emphLabels),
} }
@@ -620,7 +620,9 @@ class Config:
"showtabsnspaces": str(self.showTabsNSpaces), "showtabsnspaces": str(self.showTabsNSpaces),
"showlineendings": str(self.showLineEndings), "showlineendings": str(self.showLineEndings),
"showmultispaces": str(self.showMultiSpaces), "showmultispaces": str(self.showMultiSpaces),
"wordcounttimer": str(self.wordCountTimer),
"bigdoclimit": str(self.bigDocLimit), "bigdoclimit": str(self.bigDocLimit),
"incnoteswcount": str(self.incNotesWCount),
"showfullpath": str(self.showFullPath), "showfullpath": str(self.showFullPath),
"highlightquotes": str(self.highlightQuotes), "highlightquotes": str(self.highlightQuotes),
"allowopensquote": str(self.allowOpenSQuote), "allowopensquote": str(self.allowOpenSQuote),
@@ -821,7 +823,7 @@ class Config:
return True return True
def setDocPanePos(self, panePos): def setDocPanePos(self, panePos):
self.docPanePos = [int(x/self.guiScale) for x in panePos] self.docPanePos = [int(x/self.guiScale) for x in panePos]
self.confChanged = True self.confChanged = True
return True return True
@@ -832,22 +834,22 @@ class Config:
def setOutlinePanePos(self, panePos): def setOutlinePanePos(self, panePos):
self.outlnPanePos = [int(x/self.guiScale) for x in panePos] self.outlnPanePos = [int(x/self.guiScale) for x in panePos]
self.confChanged = True self.confChanged = True
return True return True
def setShowRefPanel(self, checkState): def setShowRefPanel(self, checkState):
self.showRefPanel = checkState self.showRefPanel = checkState
self.confChanged = True self.confChanged = True
return self.showRefPanel return self.showRefPanel
def setViewComments(self, viewState): def setViewComments(self, viewState):
self.viewComments = viewState self.viewComments = viewState
self.confChanged = True self.confChanged = True
return self.viewComments return self.viewComments
def setViewSynopsis(self, viewState): def setViewSynopsis(self, viewState):
self.viewSynopsis = viewState self.viewSynopsis = viewState
self.confChanged = True self.confChanged = True
return self.viewSynopsis return self.viewSynopsis
## ##
+29 -29
View File
@@ -99,9 +99,11 @@ class NWProject():
self.lastEdited = None # The handle of the last file to be edited self.lastEdited = None # The handle of the last file to be edited
self.lastViewed = None # The handle of the last file to be viewed self.lastViewed = None # The handle of the last file to be viewed
self.lastWCount = 0 # The project word count from last session self.lastWCount = 0 # The project word count from last session
self.lastNovelWC = 0 # The novel files word count from last session
self.lastNotesWC = 0 # The note files word count from last session
self.currWCount = 0 # The project word count in current session self.currWCount = 0 # The project word count in current session
self.novelWCount = 0 # Total number of words in novel files self.currNovelWC = 0 # The novel files word count in cutrent session
self.notesWCount = 0 # Total number of words in note files self.currNotesWC = 0 # The note files word count in cutrent session
self.doBackup = True # Run project backup on exit self.doBackup = True # Run project backup on exit
# Internal Mapping # Internal Mapping
@@ -226,9 +228,11 @@ class NWProject():
self.lastEdited = None self.lastEdited = None
self.lastViewed = None self.lastViewed = None
self.lastWCount = 0 self.lastWCount = 0
self.lastNovelWC = 0
self.lastNotesWC = 0
self.currWCount = 0 self.currWCount = 0
self.novelWCount = 0 self.currNovelWC = 0
self.notesWCount = 0 self.currNotesWC = 0
return return
@@ -566,9 +570,9 @@ class NWProject():
elif xItem.tag == "lastWordCount": elif xItem.tag == "lastWordCount":
self.lastWCount = checkInt(xItem.text, 0, False) self.lastWCount = checkInt(xItem.text, 0, False)
elif xItem.tag == "novelWordCount": elif xItem.tag == "novelWordCount":
self.novelWCount = checkInt(xItem.text, 0, False) self.lastNovelWC = checkInt(xItem.text, 0, False)
elif xItem.tag == "notesWordCount": elif xItem.tag == "notesWordCount":
self.notesWCount = checkInt(xItem.text, 0, False) self.lastNotesWC = checkInt(xItem.text, 0, False)
elif xItem.tag == "status": elif xItem.tag == "status":
self.statusItems.unpackXML(xItem) self.statusItems.unpackXML(xItem)
elif xItem.tag == "importance": elif xItem.tag == "importance":
@@ -610,8 +614,8 @@ class NWProject():
self._scanProjectFolder() self._scanProjectFolder()
self._loadProjectLocalisation() self._loadProjectLocalisation()
self.updateWordCounts()
self.currWCount = self.lastWCount
self.projOpened = time() self.projOpened = time()
self.projAltered = False self.projAltered = False
@@ -652,11 +656,8 @@ class NWProject():
"timeStamp": formatTimeStamp(saveTime), "timeStamp": formatTimeStamp(saveTime),
}) })
self.updateWordCounts()
editTime = int(self.editTime + saveTime - self.projOpened) editTime = int(self.editTime + saveTime - self.projOpened)
wcNovel, wcNotes = self.projTree.sumWords()
self.novelWCount = wcNovel
self.notesWCount = wcNotes
self.setProjectWordCount(wcNovel + wcNotes)
# Save Project Meta # Save Project Meta
xProject = etree.SubElement(nwXML, "project") xProject = etree.SubElement(nwXML, "project")
@@ -677,8 +678,8 @@ class NWProject():
self._packProjectValue(xSettings, "lastEdited", self.lastEdited) self._packProjectValue(xSettings, "lastEdited", self.lastEdited)
self._packProjectValue(xSettings, "lastViewed", self.lastViewed) self._packProjectValue(xSettings, "lastViewed", self.lastViewed)
self._packProjectValue(xSettings, "lastWordCount", self.currWCount) self._packProjectValue(xSettings, "lastWordCount", self.currWCount)
self._packProjectValue(xSettings, "novelWordCount", wcNovel) self._packProjectValue(xSettings, "novelWordCount", self.currNovelWC)
self._packProjectValue(xSettings, "notesWordCount", wcNotes) self._packProjectValue(xSettings, "notesWordCount", self.currNotesWC)
self._packProjectKeyValue(xSettings, "autoReplace", self.autoReplace) self._packProjectKeyValue(xSettings, "autoReplace", self.autoReplace)
xTitleFmt = etree.SubElement(xSettings, "titleFormat") xTitleFmt = etree.SubElement(xSettings, "titleFormat")
@@ -1067,14 +1068,6 @@ class NWProject():
self.setProjectChanged(True) self.setProjectChanged(True)
return True return True
def setProjectWordCount(self, theCount):
"""Set the current project word count.
"""
if self.currWCount != theCount:
self.currWCount = theCount
self.setProjectChanged(True)
return True
def setStatusColours(self, newCols): def setStatusColours(self, newCols):
"""Update the list of novel file status flags. Also iterate """Update the list of novel file status flags. Also iterate
through the project and replace keys that have been renamed. through the project and replace keys that have been renamed.
@@ -1145,11 +1138,6 @@ class NWProject():
return authString return authString
def getSessionWordCount(self):
"""Returns the number of words added or removed this session.
"""
return self.currWCount - self.lastWCount
def getCurrentEditTime(self): def getCurrentEditTime(self):
"""Get the total project edit time, including the time spent in """Get the total project edit time, including the time spent in
the current session. the current session.
@@ -1202,6 +1190,18 @@ class NWProject():
# Class Methods # Class Methods
## ##
def updateWordCounts(self):
"""Update the total word count values.
"""
wcNovel, wcNotes = self.projTree.sumWords()
wcTotal = wcNovel + wcNotes
if wcTotal != self.currWCount:
self.currNovelWC = wcNovel
self.currNotesWC = wcNotes
self.currWCount = wcTotal
self.setProjectChanged(True)
return
def countStatus(self): def countStatus(self):
"""Count how many times the various status flags are used in the """Count how many times the various status flags are used in the
project tree. The counts themselves are kept in the NWStatus project tree. The counts themselves are kept in the NWStatus
@@ -1460,7 +1460,7 @@ class NWProject():
isFile = os.path.isfile(sessionFile) isFile = os.path.isfile(sessionFile)
nowTime = time() nowTime = time()
sessDiff = self.getSessionWordCount() sessDiff = self.currWCount - self.lastWCount
sessTime = nowTime - self.projOpened sessTime = nowTime - self.projOpened
logger.info("The session lasted %d sec and added %d words", int(sessTime), sessDiff) logger.info("The session lasted %d sec and added %d words", int(sessTime), sessDiff)
@@ -1481,8 +1481,8 @@ class NWProject():
outFile.write("%-19s %-19s %8d %8d %8d\n" % ( outFile.write("%-19s %-19s %8d %8d %8d\n" % (
formatTimeStamp(self.projOpened), formatTimeStamp(self.projOpened),
formatTimeStamp(nowTime), formatTimeStamp(nowTime),
self.novelWCount, self.currNovelWC,
self.notesWCount, self.currNotesWC,
int(idleTime), int(idleTime),
)) ))
+1 -1
View File
@@ -190,7 +190,7 @@ class NWTree():
return True return True
def sumWords(self): def sumWords(self):
"""Loops over all entries and adds up the word counts. """Loop over all entries and add up the word counts.
""" """
noteWords = 0 noteWords = 0
novelWords = 0 novelWords = 0
+10 -11
View File
@@ -244,14 +244,6 @@ class GuiPreferencesGeneral(QWidget):
# ============ # ============
self.mainForm.addGroupLabel(self.tr("GUI Settings")) self.mainForm.addGroupLabel(self.tr("GUI Settings"))
self.fullStatus = QSwitch()
self.fullStatus.setChecked(self.mainConf.fullStatus)
self.mainForm.addRow(
self.tr("Show status text in project tree"),
self.fullStatus,
self.tr("If disabled, only the icon is shown."),
)
self.emphLabels = QSwitch() self.emphLabels = QSwitch()
self.emphLabels.setChecked(self.mainConf.emphLabels) self.emphLabels.setChecked(self.mainConf.emphLabels)
self.mainForm.addRow( self.mainForm.addRow(
@@ -295,7 +287,6 @@ class GuiPreferencesGeneral(QWidget):
guiDark = self.guiDark.isChecked() guiDark = self.guiDark.isChecked()
guiFont = self.guiFont.text() guiFont = self.guiFont.text()
guiFontSize = self.guiFontSize.value() guiFontSize = self.guiFontSize.value()
fullStatus = self.fullStatus.isChecked()
emphLabels = self.emphLabels.isChecked() emphLabels = self.emphLabels.isChecked()
# Check if restart is needed # Check if restart is needed
@@ -309,7 +300,6 @@ class GuiPreferencesGeneral(QWidget):
# Check if refreshing project tree is needed # Check if refreshing project tree is needed
refreshTree = False refreshTree = False
refreshTree |= self.mainConf.fullStatus != fullStatus
refreshTree |= self.mainConf.emphLabels != emphLabels refreshTree |= self.mainConf.emphLabels != emphLabels
self.mainConf.guiLang = guiLang self.mainConf.guiLang = guiLang
@@ -318,7 +308,6 @@ class GuiPreferencesGeneral(QWidget):
self.mainConf.guiDark = guiDark self.mainConf.guiDark = guiDark
self.mainConf.guiFont = guiFont self.mainConf.guiFont = guiFont
self.mainConf.guiFontSize = guiFontSize self.mainConf.guiFontSize = guiFontSize
self.mainConf.fullStatus = fullStatus
self.mainConf.emphLabels = emphLabels self.mainConf.emphLabels = emphLabels
self.mainConf.showFullPath = self.showFullPath.isChecked() self.mainConf.showFullPath = self.showFullPath.isChecked()
self.mainConf.hideVScroll = self.hideVScroll.isChecked() self.mainConf.hideVScroll = self.hideVScroll.isChecked()
@@ -758,6 +747,15 @@ class GuiPreferencesEditor(QWidget):
theUnit=self.tr("seconds") theUnit=self.tr("seconds")
) )
# Include Notes in Word Count
self.incNotesWCount = QSwitch()
self.incNotesWCount.setChecked(self.mainConf.incNotesWCount)
self.mainForm.addRow(
self.tr("Include project notes in total word count"),
self.incNotesWCount,
self.tr("Affects the word count shown on the status bar.")
)
# Writing Guides # Writing Guides
# ============== # ==============
self.mainForm.addGroupLabel(self.tr("Writing Guides")) self.mainForm.addGroupLabel(self.tr("Writing Guides"))
@@ -826,6 +824,7 @@ class GuiPreferencesEditor(QWidget):
# Word Count # Word Count
self.mainConf.wordCountTimer = self.wordCountTimer.value() self.mainConf.wordCountTimer = self.wordCountTimer.value()
self.mainConf.incNotesWCount = self.incNotesWCount.isChecked()
# Writing Guides # Writing Guides
self.mainConf.showTabsNSpaces = self.showTabsNSpaces.isChecked() self.mainConf.showTabsNSpaces = self.showTabsNSpaces.isChecked()
+1 -1
View File
@@ -4,7 +4,7 @@ novelWriter GUI Updates
A dialog box for checking for latest updates A dialog box for checking for latest updates
File History: File History:
Created: 2021-08-21 [1.5-alpah0] Created: 2021-08-21 [1.5a0]
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen Copyright 20182021, Veronica Berglyd Olsen
+2 -2
View File
@@ -8,8 +8,8 @@ Created: 2018-09-29 [0.0.1] GuiDocEditor
Created: 2019-04-22 [0.0.1] BackgroundWordCounter Created: 2019-04-22 [0.0.1] BackgroundWordCounter
Created: 2019-09-29 [0.2.1] GuiDocEditSearch Created: 2019-09-29 [0.2.1] GuiDocEditSearch
Created: 2020-04-25 [0.4.5] GuiDocEditHeader Created: 2020-04-25 [0.4.5] GuiDocEditHeader
Rewritten: 2020-06-15 [0.9.0] GuiDocEditSearch Rewritten: 2020-06-15 [0.9] GuiDocEditSearch
Created: 2020-06-27 [0.10.0] GuiDocEditFooter Created: 2020-06-27 [0.10] GuiDocEditFooter
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
This file is a part of novelWriter This file is a part of novelWriter
+1 -1
View File
@@ -7,7 +7,7 @@ File History:
Created: 2019-05-10 [0.0.1] GuiDocViewer Created: 2019-05-10 [0.0.1] GuiDocViewer
Created: 2019-10-31 [0.3.2] GuiDocViewDetails Created: 2019-10-31 [0.3.2] GuiDocViewDetails
Created: 2020-04-25 [0.4.5] GuiDocViewHeader Created: 2020-04-25 [0.4.5] GuiDocViewHeader
Created: 2020-06-09 [0.8.0] GuiDocViewFooter Created: 2020-06-09 [0.8] GuiDocViewFooter
Created: 2020-09-08 [1.0b1] GuiDocViewHistory Created: 2020-09-08 [1.0b1] GuiDocViewHistory
This file is a part of novelWriter This file is a part of novelWriter
+7 -36
View File
@@ -5,7 +5,7 @@ GUI classes for the main window project tree
File History: File History:
Created: 2018-09-29 [0.0.1] GuiProjectTree Created: 2018-09-29 [0.0.1] GuiProjectTree
Created: 2020-06-04 [0.7.0] GuiProjectTreeMenu Created: 2020-06-04 [0.7] GuiProjectTreeMenu
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen Copyright 20182021, Veronica Berglyd Olsen
@@ -51,7 +51,7 @@ class GuiProjectTree(QTreeWidget):
novelItemChanged = pyqtSignal() novelItemChanged = pyqtSignal()
noteItemChanged = pyqtSignal() noteItemChanged = pyqtSignal()
projectWordCountChanged = pyqtSignal(int, int) wordCountsChanged = pyqtSignal()
def __init__(self, theParent): def __init__(self, theParent):
QTreeWidget.__init__(self, theParent) QTreeWidget.__init__(self, theParent)
@@ -86,8 +86,7 @@ class GuiProjectTree(QTreeWidget):
self.setIndentation(iPx) self.setIndentation(iPx)
self.setColumnCount(4) self.setColumnCount(4)
self.setHeaderLabels([ self.setHeaderLabels([
self.tr("Project Tree"), self.tr("Words"), "", self.tr("Project Tree"), self.tr("Words"), "", ""
self.tr("Status") if self.mainConf.fullStatus else ""
]) ])
treeHeadItem = self.headerItem() treeHeadItem = self.headerItem()
@@ -305,7 +304,7 @@ class GuiProjectTree(QTreeWidget):
nwItem.setWordCount(wC) nwItem.setWordCount(wC)
nwItem.setParaCount(pC) nwItem.setParaCount(pC)
self.propagateCount(tHandle, wC) self.propagateCount(tHandle, wC)
self.projectWordCount() self.wordCountsChanged.emit()
return True return True
@@ -533,7 +532,7 @@ class GuiProjectTree(QTreeWidget):
self.theIndex.deleteHandle(tHandle) self.theIndex.deleteHandle(tHandle)
self._deleteTreeItem(tHandle) self._deleteTreeItem(tHandle)
self._setTreeChanged(True) self._setTreeChanged(True)
self.projectWordCount() self.wordCountsChanged.emit()
else: else:
# The file is not already in the trash folder, so we # The file is not already in the trash folder, so we
@@ -630,11 +629,7 @@ class GuiProjectTree(QTreeWidget):
trItem.setText(self.C_NAME, nwItem.itemName) trItem.setText(self.C_NAME, nwItem.itemName)
trItem.setIcon(self.C_EXPORT, expIcon) trItem.setIcon(self.C_EXPORT, expIcon)
trItem.setIcon(self.C_STATUS, statIcon) trItem.setIcon(self.C_STATUS, statIcon)
trItem.setToolTip(self.C_STATUS, nwItem.itemStatus)
if self.mainConf.fullStatus:
trItem.setText(self.C_STATUS, nwItem.itemStatus)
else:
trItem.setToolTip(self.C_STATUS, nwItem.itemStatus)
if self.mainConf.emphLabels and nwItem.itemLayout == nwItemLayout.DOCUMENT: if self.mainConf.emphLabels and nwItem.itemLayout == nwItemLayout.DOCUMENT:
if hLevel in ("H1", "H2"): if hLevel in ("H1", "H2"):
@@ -674,25 +669,6 @@ class GuiProjectTree(QTreeWidget):
return return
def projectWordCount(self):
"""Sum up the word counts for all root items and set the
relevant values in the project and on the status bar. This call
is a fast way of getting this number, and depends on the
propagateCount function being called when it should to maintain
the correct count.
"""
nWords = 0
for n in range(self.topLevelItemCount()):
tItem = self.topLevelItem(n)
nWords += int(tItem.data(self.C_COUNT, Qt.UserRole))
self.theProject.setProjectWordCount(nWords)
sWords = self.theProject.getSessionWordCount()
self.projectWordCountChanged.emit(nWords, sWords)
return
def buildTree(self): def buildTree(self):
"""Build the entire project tree from scratch. This depends on """Build the entire project tree from scratch. This depends on
the save project item iterator in the project class which will the save project item iterator in the project class which will
@@ -702,11 +678,6 @@ class GuiProjectTree(QTreeWidget):
logger.debug("Building the project tree ...") logger.debug("Building the project tree ...")
self.clearTree() self.clearTree()
self.setHeaderLabels([
self.tr("Project Tree"), self.tr("Words"), "",
self.tr("Status") if self.mainConf.fullStatus else ""
])
iCount = 0 iCount = 0
for nwItem in self.theProject.getProjectItems(): for nwItem in self.theProject.getProjectItems():
iCount += 1 iCount += 1
@@ -819,7 +790,7 @@ class GuiProjectTree(QTreeWidget):
"""Slot for updating the word count of a specific item. """Slot for updating the word count of a specific item.
""" """
self.propagateCount(tHandle, wCount) self.propagateCount(tHandle, wCount)
self.projectWordCount() self.wordCountsChanged.emit()
return return
## ##
+14 -11
View File
@@ -4,7 +4,8 @@ novelWriter GUI Main Window Status Bar
GUI class for the main window status bar GUI class for the main window status bar
File History: File History:
Created: 2019-04-20 [0.0.1] Created: 2019-04-20 [0.0.1] GuiMainStatus
Created: 2020-05-17 [0.5.1] StatusLED
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen Copyright 20182021, Veronica Berglyd Olsen
@@ -28,7 +29,7 @@ import novelwriter
from time import time from time import time
from PyQt5.QtCore import QLocale, pyqtSlot from PyQt5.QtCore import pyqtSlot, QLocale
from PyQt5.QtGui import QColor, QPainter from PyQt5.QtGui import QColor, QPainter
from PyQt5.QtWidgets import qApp, QStatusBar, QLabel, QAbstractButton from PyQt5.QtWidgets import qApp, QStatusBar, QLabel, QAbstractButton
@@ -125,7 +126,7 @@ class GuiMainStatus(QStatusBar):
""" """
self.setRefTime(None) self.setRefTime(None)
self.setLanguage(None, "") self.setLanguage(None, "")
self.doUpdateProjectStats(0, 0) self.setProjectStats(0, 0)
self.setProjectStatus(nwState.NONE) self.setProjectStatus(nwState.NONE)
self.setDocumentStatus(nwState.NONE) self.setDocumentStatus(nwState.NONE)
self.updateTime() self.updateTime()
@@ -176,6 +177,16 @@ class GuiMainStatus(QStatusBar):
return return
def setProjectStats(self, pWC, sWC):
"""Update the current project statistics.
"""
self.statsText.setText(self.tr("Words: {0} ({1})").format(f"{pWC:n}", f"{sWC:+n}"))
if self.mainConf.incNotesWCount:
self.statsText.setToolTip(self.tr("Project word count (session change)"))
else:
self.statsText.setToolTip(self.tr("Novel word count (session change)"))
return
def updateTime(self, idleTime=0.0): def updateTime(self, idleTime=0.0):
"""Update the session clock. """Update the session clock.
""" """
@@ -211,14 +222,6 @@ class GuiMainStatus(QStatusBar):
return return
@pyqtSlot(int, int)
def doUpdateProjectStats(self, pWC, sWC):
"""Update the current project statistics.
"""
self.statsText.setText(self.tr("Words: {0} ({1})").format(f"{pWC:n}", f"{sWC:+n}"))
self.statsText.setToolTip(self.tr("Project word count (session change)"))
return
@pyqtSlot(bool) @pyqtSlot(bool)
def doUpdateProjectStatus(self, isChanged): def doUpdateProjectStatus(self, isChanged):
"""Slot for updating the project status. """Slot for updating the project status.
+1 -1
View File
@@ -5,7 +5,7 @@ Classes managing and caching themes and icons
File History: File History:
Created: 2019-05-18 [0.1.3] GuiTheme Created: 2019-05-18 [0.1.3] GuiTheme
Created: 2019-11-08 [0.4.0] GuiIcons Created: 2019-11-08 [0.4] GuiIcons
This file is a part of novelWriter This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen Copyright 20182021, Veronica Berglyd Olsen
+24 -3
View File
@@ -124,7 +124,7 @@ class GuiMain(QMainWindow):
self.treeView.itemSelectionChanged.connect(self._treeSingleClick) self.treeView.itemSelectionChanged.connect(self._treeSingleClick)
self.treeView.itemDoubleClicked.connect(self._treeDoubleClick) self.treeView.itemDoubleClicked.connect(self._treeDoubleClick)
self.treeView.novelItemChanged.connect(self._treeNovelItemChanged) self.treeView.novelItemChanged.connect(self._treeNovelItemChanged)
self.treeView.projectWordCountChanged.connect(self.statusBar.doUpdateProjectStats) self.treeView.wordCountsChanged.connect(self._updateStatusWordCount)
# Minor GUI Elements # Minor GUI Elements
self.statusIcons = [] self.statusIcons = []
@@ -529,7 +529,7 @@ class GuiMain(QMainWindow):
self.docEditor.toggleSpellCheck(self.theProject.spellCheck) self.docEditor.toggleSpellCheck(self.theProject.spellCheck)
self.mainMenu.setAutoOutline(self.theProject.autoOutline) self.mainMenu.setAutoOutline(self.theProject.autoOutline)
self.statusBar.setRefTime(self.theProject.projOpened) self.statusBar.setRefTime(self.theProject.projOpened)
self.statusBar.doUpdateProjectStats(self.theProject.currWCount, 0) self._updateStatusWordCount()
# Restore previously open documents, if any # Restore previously open documents, if any
if self.theProject.lastEdited is not None: if self.theProject.lastEdited is not None:
@@ -903,13 +903,13 @@ class GuiMain(QMainWindow):
tItem.setParaCount(pC) tItem.setParaCount(pC)
self.treeView.propagateCount(tItem.itemHandle, wC) self.treeView.propagateCount(tItem.itemHandle, wC)
self.treeView.setTreeItemValues(tItem.itemHandle) self.treeView.setTreeItemValues(tItem.itemHandle)
self.treeView.projectWordCount()
tEnd = time() tEnd = time()
self.setStatus( self.setStatus(
self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}") self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}")
) )
self.docEditor.updateTagHighLighting() self.docEditor.updateTagHighLighting()
self._updateStatusWordCount()
qApp.restoreOverrideCursor() qApp.restoreOverrideCursor()
if not beQuiet: if not beQuiet:
@@ -980,6 +980,7 @@ class GuiMain(QMainWindow):
self.novelView.initTree() self.novelView.initTree()
self.projView.initOutline() self.projView.initOutline()
self.projMeta.initDetails() self.projMeta.initDetails()
self._updateStatusWordCount()
return return
@@ -1527,6 +1528,26 @@ class GuiMain(QMainWindow):
return return
@pyqtSlot()
def _updateStatusWordCount(self):
"""Update the word count on the status bar.
"""
if not self.hasProject:
self.statusBar.setProjectStats(0, 0)
logger.verbose("Updating total word count")
self.theProject.updateWordCounts()
if self.mainConf.incNotesWCount:
currWords = self.theProject.currWCount
diffWords = currWords - self.theProject.lastWCount
else:
currWords = self.theProject.currNovelWC
diffWords = currWords - self.theProject.lastNovelWC
self.statusBar.setProjectStats(currWords, diffWords)
return
@pyqtSlot() @pyqtSlot()
def _treeSingleClick(self): def _treeSingleClick(self):
"""Single click on a project tree item just updates the details """Single click on a project tree item just updates the details
+3 -2
View File
@@ -1,5 +1,5 @@
[Main] [Main]
timestamp = 2021-08-30 21:27:27 timestamp = 2021-09-10 00:23:50
theme = default theme = default
syntax = default_light syntax = default_light
icons = typicons_light icons = typicons_light
@@ -26,7 +26,6 @@ fullscreen = False
[Project] [Project]
autosaveproject = 60 autosaveproject = 60
autosavedoc = 30 autosavedoc = 30
fullstatus = True
emphlabels = True emphlabels = True
[Editor] [Editor]
@@ -57,7 +56,9 @@ spellcheck = en
showtabsnspaces = False showtabsnspaces = False
showlineendings = False showlineendings = False
showmultispaces = True showmultispaces = True
wordcounttimer = 5.0
bigdoclimit = 800 bigdoclimit = 800
incnoteswcount = True
showfullpath = True showfullpath = True
highlightquotes = True highlightquotes = True
allowopensquote = False allowopensquote = False
@@ -1,5 +1,5 @@
[Main] [Main]
timestamp = 2021-08-30 21:45:17 timestamp = 2021-09-10 00:23:52
theme = default theme = default
syntax = default_light syntax = default_light
icons = typicons_light icons = typicons_light
@@ -26,7 +26,6 @@ fullscreen = False
[Project] [Project]
autosaveproject = 40 autosaveproject = 40
autosavedoc = 20 autosavedoc = 20
fullstatus = True
emphlabels = True emphlabels = True
[Editor] [Editor]
@@ -57,7 +56,9 @@ spellcheck = en
showtabsnspaces = True showtabsnspaces = True
showlineendings = True showlineendings = True
showmultispaces = True showmultispaces = True
wordcounttimer = 5.0
bigdoclimit = 500 bigdoclimit = 500
incnoteswcount = True
showfullpath = False showfullpath = False
highlightquotes = False highlightquotes = False
allowopensquote = False allowopensquote = False
+9
View File
@@ -482,6 +482,7 @@ def testBaseCommon_NWConfigParser(fncDir):
"boolopt4 = 0\n" "boolopt4 = 0\n"
"list1 = a, b, c\n" "list1 = a, b, c\n"
"list2 = 17, 18, 19\n" "list2 = 17, 18, 19\n"
"float1 = 4.2\n"
)) ))
cfgParser = NWConfigParser() cfgParser = NWConfigParser()
@@ -516,6 +517,14 @@ def testBaseCommon_NWConfigParser(fncDir):
assert cfgParser.rdInt("nope", "intopt1", 13) == 13 assert cfgParser.rdInt("nope", "intopt1", 13) == 13
assert cfgParser.rdInt("main", "blabla", 13) == 13 assert cfgParser.rdInt("main", "blabla", 13) == 13
# Read Float
assert cfgParser.rdFlt("main", "intopt1", 13.0) == 42.0
assert cfgParser.rdFlt("main", "float1", 13.0) == 4.2
assert cfgParser.rdInt("main", "stropt", 13.0) == 13.0
assert cfgParser.rdInt("nope", "intopt1", 13.0) == 13.0
assert cfgParser.rdInt("main", "blabla", 13.0) == 13.0
# Read String List # Read String List
assert cfgParser.rdStrList("main", "list1", []) == [] assert cfgParser.rdStrList("main", "list1", []) == []
assert cfgParser.rdStrList("main", "list1", ["x"]) == ["a"] assert cfgParser.rdStrList("main", "list1", ["x"]) == ["a"]
+12 -12
View File
@@ -519,24 +519,24 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir):
# Flag Setters # Flag Setters
# ============ # ============
assert not tmpConf.setShowRefPanel(False) assert tmpConf.setShowRefPanel(False) is False
assert not tmpConf.showRefPanel assert tmpConf.showRefPanel is False
assert tmpConf.setShowRefPanel(True) assert tmpConf.setShowRefPanel(True) is True
assert not tmpConf.setViewComments(False) assert tmpConf.setViewComments(False) is False
assert not tmpConf.viewComments assert tmpConf.viewComments is False
assert tmpConf.setViewComments(True) assert tmpConf.setViewComments(True) is True
assert not tmpConf.setViewSynopsis(False) assert tmpConf.setViewSynopsis(False) is False
assert not tmpConf.viewSynopsis assert tmpConf.viewSynopsis is False
assert tmpConf.setViewSynopsis(True) assert tmpConf.setViewSynopsis(True) is True
# Check Final File # Check Final File
# ================ # ================
assert tmpConf.confChanged assert tmpConf.confChanged is True
assert tmpConf.saveConfig() assert tmpConf.saveConfig() is True
assert not tmpConf.confChanged assert tmpConf.confChanged is False
copyfile(confFile, testFile) copyfile(confFile, testFile)
assert cmpFiles(testFile, compFile, [2, 9, 10]) assert cmpFiles(testFile, compFile, [2, 9, 10])
+3 -6
View File
@@ -868,12 +868,9 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0] assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0]
assert theProject.importItems._theCounts == [3, 0, 0, 1, 0] assert theProject.importItems._theCounts == [3, 0, 0, 1, 0]
# Check word counts # Session stats
theProject.currWCount = 200 theProject.currWCount = 200
theProject.lastWCount = 100 theProject.lastWCount = 100
assert theProject.getSessionWordCount() == 100
# Session stats
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("os.path.isdir", lambda *a, **k: False) mp.setattr("os.path.isdir", lambda *a, **k: False)
assert not theProject._appendSessionStats(idleTime=0) assert not theProject._appendSessionStats(idleTime=0)
@@ -888,8 +885,8 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir):
statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS) statsFile = os.path.join(theProject.projMeta, nwFiles.SESS_STATS)
theProject.projOpened = 1600002000 theProject.projOpened = 1600002000
theProject.novelWCount = 200 theProject.currNovelWC = 200
theProject.notesWCount = 100 theProject.currNotesWC = 100
with monkeypatch.context() as mp: with monkeypatch.context() as mp:
mp.setattr("novelwriter.core.project.time", lambda: 1600005600) mp.setattr("novelwriter.core.project.time", lambda: 1600005600)
+1 -1
View File
@@ -249,7 +249,7 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir):
testFile = os.path.join(outDir, "guiPreferences_novelwriter.conf") testFile = os.path.join(outDir, "guiPreferences_novelwriter.conf")
compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf") compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf")
copyfile(projFile, testFile) copyfile(projFile, testFile)
ignoreLines = [2, 7, 9, 10, 15, 16, 17, 18, 19, 20, 21, 22, 23, 33, 34] ignoreLines = [2, 7, 9, 10, 15, 16, 17, 18, 19, 20, 21, 22, 23, 32, 33]
assert cmpFiles(testFile, compFile, ignoreLines) assert cmpFiles(testFile, compFile, ignoreLines)
# Clean up # Clean up
+100
View File
@@ -0,0 +1,100 @@
"""
novelWriter Main Status Bar Class Tester
==========================================
This file is a part of novelWriter
Copyright 20182021, Veronica Berglyd Olsen
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import time
import pytest
from PyQt5.QtWidgets import QMessageBox
from novelwriter.core import NWDoc
from novelwriter.enum import nwItemClass, nwState
@pytest.mark.gui
def testGuiStatusBar_Main(qtbot, monkeypatch, nwGUI, fncProj):
"""Test the the various features of the status bar.
"""
monkeypatch.setattr(QMessageBox, "question", lambda *a: QMessageBox.Yes)
nwGUI.theProject.projTree.setSeed(42)
assert nwGUI.newProject({"projPath": fncProj}) is True
cHandle = nwGUI.theProject.newFile("A Note", nwItemClass.CHARACTER, "71ee45a3c0db9")
newDoc = NWDoc(nwGUI.theProject, cHandle)
newDoc.writeDocument("# A Note\n\n")
nwGUI.treeView.revealNewTreeItem(cHandle)
nwGUI.rebuildIndex(beQuiet=True)
# Reference Time
refTime = time.time()
nwGUI.statusBar.setRefTime(refTime)
assert nwGUI.statusBar.refTime == refTime
# Project Status
nwGUI.statusBar.setProjectStatus(nwState.NONE)
assert nwGUI.statusBar.projIcon._theCol == nwGUI.statusBar.projIcon._colNone
nwGUI.statusBar.setProjectStatus(nwState.BAD)
assert nwGUI.statusBar.projIcon._theCol == nwGUI.statusBar.projIcon._colBad
nwGUI.statusBar.setProjectStatus(nwState.GOOD)
assert nwGUI.statusBar.projIcon._theCol == nwGUI.statusBar.projIcon._colGood
# Document Status
nwGUI.statusBar.setDocumentStatus(nwState.NONE)
assert nwGUI.statusBar.docIcon._theCol == nwGUI.statusBar.docIcon._colNone
nwGUI.statusBar.setDocumentStatus(nwState.BAD)
assert nwGUI.statusBar.docIcon._theCol == nwGUI.statusBar.docIcon._colBad
nwGUI.statusBar.setDocumentStatus(nwState.GOOD)
assert nwGUI.statusBar.docIcon._theCol == nwGUI.statusBar.docIcon._colGood
# Idle Status
nwGUI.statusBar.mainConf.stopWhenIdle = False
nwGUI.statusBar.setUserIdle(True)
nwGUI.statusBar.updateTime()
assert nwGUI.statusBar.userIdle is False
assert nwGUI.statusBar.timeText.text() == "00:00:00"
nwGUI.statusBar.mainConf.stopWhenIdle = True
nwGUI.statusBar.setUserIdle(True)
nwGUI.statusBar.updateTime(5)
assert nwGUI.statusBar.userIdle is True
assert nwGUI.statusBar.timeText.text() != "00:00:00"
nwGUI.statusBar.setUserIdle(False)
nwGUI.statusBar.updateTime(5)
assert nwGUI.statusBar.userIdle is False
assert nwGUI.statusBar.timeText.text() != "00:00:00"
# Language
nwGUI.statusBar.setLanguage("None", "None")
assert nwGUI.statusBar.langText.text() == "None"
nwGUI.statusBar.setLanguage("en", "None")
assert nwGUI.statusBar.langText.text() == "American English"
# Project Stats
nwGUI.statusBar.mainConf.incNotesWCount = False
nwGUI._updateStatusWordCount()
assert nwGUI.statusBar.statsText.text() == "Words: 6 (+6)"
nwGUI.statusBar.mainConf.incNotesWCount = True
nwGUI._updateStatusWordCount()
assert nwGUI.statusBar.statsText.text() == "Words: 8 (+8)"
# qtbot.stopForInteraction()
# END Test testGuiStatusBar_Init