From 3c8cb34b0a5cd3f5f4dcff461246ec90a056145d Mon Sep 17 00:00:00 2001 From: Veronica Berglyd Olsen <1619840+vkbo@users.noreply.github.com> Date: Fri, 10 Sep 2021 00:38:45 +0200 Subject: [PATCH] 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 --- novelwriter/common.py | 16 ++- novelwriter/config.py | 24 +++-- novelwriter/core/project.py | 58 +++++----- novelwriter/core/tree.py | 2 +- novelwriter/dialogs/preferences.py | 21 ++-- novelwriter/dialogs/updates.py | 2 +- novelwriter/gui/doceditor.py | 4 +- novelwriter/gui/docviewer.py | 2 +- novelwriter/gui/projtree.py | 43 ++------ novelwriter/gui/statusbar.py | 25 +++-- novelwriter/gui/theme.py | 2 +- novelwriter/guimain.py | 27 ++++- tests/reference/baseConfig_novelwriter.conf | 5 +- .../reference/guiPreferences_novelwriter.conf | 5 +- tests/test_base/test_base_common.py | 9 ++ tests/test_base/test_base_config.py | 24 ++--- tests/test_core/test_core_project.py | 9 +- tests/test_dialogs/test_dlg_preferences.py | 2 +- tests/test_gui/test_gui_statusbar.py | 100 ++++++++++++++++++ 19 files changed, 246 insertions(+), 134 deletions(-) create mode 100644 tests/test_gui/test_gui_statusbar.py diff --git a/novelwriter/common.py b/novelwriter/common.py index da61c8b8..f3b91c32 100644 --- a/novelwriter/common.py +++ b/novelwriter/common.py @@ -4,7 +4,7 @@ novelWriter – Common Functions Various common functions File History: -Created: 2019-05-12 [0.1.0] +Created: 2019-05-12 [0.1] This file is a part of novelWriter Copyright 2018–2021, Veronica Berglyd Olsen @@ -441,9 +441,10 @@ class NWConfigParser(ConfigParser): CNF_STR = 0 CNF_INT = 1 - CNF_BOOL = 2 - CNF_S_LST = 3 - CNF_I_LST = 4 + CNF_FLOAT = 2 + CNF_BOOL = 3 + CNF_S_LST = 4 + CNF_I_LST = 5 def __init__(self): super().__init__() @@ -458,6 +459,11 @@ class NWConfigParser(ConfigParser): """ 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): """Read boolean value. """ @@ -503,6 +509,8 @@ class NWConfigParser(ConfigParser): return self.get(section, option) elif type == self.CNF_INT: return self.getint(section, option) + elif type == self.CNF_FLOAT: + return self.getfloat(section, option) elif type == self.CNF_BOOL: return self.getboolean(section, option) elif type in (self.CNF_I_LST, self.CNF_S_LST): diff --git a/novelwriter/config.py b/novelwriter/config.py index 85d4d35c..16f8e27d 100644 --- a/novelwriter/config.py +++ b/novelwriter/config.py @@ -108,7 +108,6 @@ class Config: # Features self.hideVScroll = False # Hide vertical 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 # Project @@ -145,6 +144,7 @@ class Config: self.wordCountTimer = 5.0 # Interval for word count update in seconds 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.allowOpenSQuote = False # Allow open-ended single quotes @@ -179,9 +179,9 @@ class Config: self.askBeforeBackup = True # State - self.showRefPanel = True - self.viewComments = True - self.viewSynopsis = True + self.showRefPanel = True # The reference panel for the viewer is visible + self.viewComments = True # Comments are shown in the viewer + self.viewSynopsis = True # Synopsis is shown in the viewer # Check Qt5 Versions verQt = splitVersionNumber(QT_VERSION_STR) @@ -466,7 +466,6 @@ class Config: cnfSec = "Project" self.autoSaveProj = theConf.rdInt(cnfSec, "autosaveproject", self.autoSaveProj) self.autoSaveDoc = theConf.rdInt(cnfSec, "autosavedoc", self.autoSaveDoc) - self.fullStatus = theConf.rdBool(cnfSec, "fullstatus", self.fullStatus) self.emphLabels = theConf.rdBool(cnfSec, "emphlabels", self.emphLabels) # Editor @@ -498,7 +497,9 @@ class Config: self.showTabsNSpaces = theConf.rdBool(cnfSec, "showtabsnspaces", self.showTabsNSpaces) self.showLineEndings = theConf.rdBool(cnfSec, "showlineendings", self.showLineEndings) 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.incNotesWCount = theConf.rdBool(cnfSec, "incnoteswcount", self.incNotesWCount) self.showFullPath = theConf.rdBool(cnfSec, "showfullpath", self.showFullPath) self.highlightQuotes = theConf.rdBool(cnfSec, "highlightquotes", self.highlightQuotes) self.allowOpenSQuote = theConf.rdBool(cnfSec, "allowopensquote", self.allowOpenSQuote) @@ -588,7 +589,6 @@ class Config: theConf["Project"] = { "autosaveproject": str(self.autoSaveProj), "autosavedoc": str(self.autoSaveDoc), - "fullstatus": str(self.fullStatus), "emphlabels": str(self.emphLabels), } @@ -620,7 +620,9 @@ class Config: "showtabsnspaces": str(self.showTabsNSpaces), "showlineendings": str(self.showLineEndings), "showmultispaces": str(self.showMultiSpaces), + "wordcounttimer": str(self.wordCountTimer), "bigdoclimit": str(self.bigDocLimit), + "incnoteswcount": str(self.incNotesWCount), "showfullpath": str(self.showFullPath), "highlightquotes": str(self.highlightQuotes), "allowopensquote": str(self.allowOpenSQuote), @@ -821,7 +823,7 @@ class Config: return True 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 return True @@ -832,22 +834,22 @@ class Config: def setOutlinePanePos(self, panePos): self.outlnPanePos = [int(x/self.guiScale) for x in panePos] - self.confChanged = True + self.confChanged = True return True def setShowRefPanel(self, checkState): self.showRefPanel = checkState - self.confChanged = True + self.confChanged = True return self.showRefPanel def setViewComments(self, viewState): self.viewComments = viewState - self.confChanged = True + self.confChanged = True return self.viewComments def setViewSynopsis(self, viewState): self.viewSynopsis = viewState - self.confChanged = True + self.confChanged = True return self.viewSynopsis ## diff --git a/novelwriter/core/project.py b/novelwriter/core/project.py index 401accc6..e85f556e 100644 --- a/novelwriter/core/project.py +++ b/novelwriter/core/project.py @@ -99,9 +99,11 @@ class NWProject(): 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.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.novelWCount = 0 # Total number of words in novel files - self.notesWCount = 0 # Total number of words in note files + self.currNovelWC = 0 # The novel files word count in cutrent session + self.currNotesWC = 0 # The note files word count in cutrent session self.doBackup = True # Run project backup on exit # Internal Mapping @@ -226,9 +228,11 @@ class NWProject(): self.lastEdited = None self.lastViewed = None self.lastWCount = 0 + self.lastNovelWC = 0 + self.lastNotesWC = 0 self.currWCount = 0 - self.novelWCount = 0 - self.notesWCount = 0 + self.currNovelWC = 0 + self.currNotesWC = 0 return @@ -566,9 +570,9 @@ class NWProject(): elif xItem.tag == "lastWordCount": self.lastWCount = checkInt(xItem.text, 0, False) elif xItem.tag == "novelWordCount": - self.novelWCount = checkInt(xItem.text, 0, False) + self.lastNovelWC = checkInt(xItem.text, 0, False) elif xItem.tag == "notesWordCount": - self.notesWCount = checkInt(xItem.text, 0, False) + self.lastNotesWC = checkInt(xItem.text, 0, False) elif xItem.tag == "status": self.statusItems.unpackXML(xItem) elif xItem.tag == "importance": @@ -610,8 +614,8 @@ class NWProject(): self._scanProjectFolder() self._loadProjectLocalisation() + self.updateWordCounts() - self.currWCount = self.lastWCount self.projOpened = time() self.projAltered = False @@ -652,11 +656,8 @@ class NWProject(): "timeStamp": formatTimeStamp(saveTime), }) + self.updateWordCounts() 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 xProject = etree.SubElement(nwXML, "project") @@ -677,8 +678,8 @@ class NWProject(): self._packProjectValue(xSettings, "lastEdited", self.lastEdited) self._packProjectValue(xSettings, "lastViewed", self.lastViewed) self._packProjectValue(xSettings, "lastWordCount", self.currWCount) - self._packProjectValue(xSettings, "novelWordCount", wcNovel) - self._packProjectValue(xSettings, "notesWordCount", wcNotes) + self._packProjectValue(xSettings, "novelWordCount", self.currNovelWC) + self._packProjectValue(xSettings, "notesWordCount", self.currNotesWC) self._packProjectKeyValue(xSettings, "autoReplace", self.autoReplace) xTitleFmt = etree.SubElement(xSettings, "titleFormat") @@ -1067,14 +1068,6 @@ class NWProject(): self.setProjectChanged(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): """Update the list of novel file status flags. Also iterate through the project and replace keys that have been renamed. @@ -1145,11 +1138,6 @@ class NWProject(): return authString - def getSessionWordCount(self): - """Returns the number of words added or removed this session. - """ - return self.currWCount - self.lastWCount - def getCurrentEditTime(self): """Get the total project edit time, including the time spent in the current session. @@ -1202,6 +1190,18 @@ class NWProject(): # 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): """Count how many times the various status flags are used in the project tree. The counts themselves are kept in the NWStatus @@ -1460,7 +1460,7 @@ class NWProject(): isFile = os.path.isfile(sessionFile) nowTime = time() - sessDiff = self.getSessionWordCount() + sessDiff = self.currWCount - self.lastWCount sessTime = nowTime - self.projOpened 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" % ( formatTimeStamp(self.projOpened), formatTimeStamp(nowTime), - self.novelWCount, - self.notesWCount, + self.currNovelWC, + self.currNotesWC, int(idleTime), )) diff --git a/novelwriter/core/tree.py b/novelwriter/core/tree.py index 0eeebdad..5b6fe6d1 100644 --- a/novelwriter/core/tree.py +++ b/novelwriter/core/tree.py @@ -190,7 +190,7 @@ class NWTree(): return True 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 novelWords = 0 diff --git a/novelwriter/dialogs/preferences.py b/novelwriter/dialogs/preferences.py index 333a2be1..cd757f95 100644 --- a/novelwriter/dialogs/preferences.py +++ b/novelwriter/dialogs/preferences.py @@ -244,14 +244,6 @@ class GuiPreferencesGeneral(QWidget): # ============ 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.setChecked(self.mainConf.emphLabels) self.mainForm.addRow( @@ -295,7 +287,6 @@ class GuiPreferencesGeneral(QWidget): guiDark = self.guiDark.isChecked() guiFont = self.guiFont.text() guiFontSize = self.guiFontSize.value() - fullStatus = self.fullStatus.isChecked() emphLabels = self.emphLabels.isChecked() # Check if restart is needed @@ -309,7 +300,6 @@ class GuiPreferencesGeneral(QWidget): # Check if refreshing project tree is needed refreshTree = False - refreshTree |= self.mainConf.fullStatus != fullStatus refreshTree |= self.mainConf.emphLabels != emphLabels self.mainConf.guiLang = guiLang @@ -318,7 +308,6 @@ class GuiPreferencesGeneral(QWidget): self.mainConf.guiDark = guiDark self.mainConf.guiFont = guiFont self.mainConf.guiFontSize = guiFontSize - self.mainConf.fullStatus = fullStatus self.mainConf.emphLabels = emphLabels self.mainConf.showFullPath = self.showFullPath.isChecked() self.mainConf.hideVScroll = self.hideVScroll.isChecked() @@ -758,6 +747,15 @@ class GuiPreferencesEditor(QWidget): 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 # ============== self.mainForm.addGroupLabel(self.tr("Writing Guides")) @@ -826,6 +824,7 @@ class GuiPreferencesEditor(QWidget): # Word Count self.mainConf.wordCountTimer = self.wordCountTimer.value() + self.mainConf.incNotesWCount = self.incNotesWCount.isChecked() # Writing Guides self.mainConf.showTabsNSpaces = self.showTabsNSpaces.isChecked() diff --git a/novelwriter/dialogs/updates.py b/novelwriter/dialogs/updates.py index 64901452..8e75391e 100644 --- a/novelwriter/dialogs/updates.py +++ b/novelwriter/dialogs/updates.py @@ -4,7 +4,7 @@ novelWriter – GUI Updates A dialog box for checking for latest updates File History: -Created: 2021-08-21 [1.5-alpah0] +Created: 2021-08-21 [1.5a0] This file is a part of novelWriter Copyright 2018–2021, Veronica Berglyd Olsen diff --git a/novelwriter/gui/doceditor.py b/novelwriter/gui/doceditor.py index 74fa8c1f..6ea766b7 100644 --- a/novelwriter/gui/doceditor.py +++ b/novelwriter/gui/doceditor.py @@ -8,8 +8,8 @@ Created: 2018-09-29 [0.0.1] GuiDocEditor Created: 2019-04-22 [0.0.1] BackgroundWordCounter Created: 2019-09-29 [0.2.1] GuiDocEditSearch Created: 2020-04-25 [0.4.5] GuiDocEditHeader -Rewritten: 2020-06-15 [0.9.0] GuiDocEditSearch -Created: 2020-06-27 [0.10.0] GuiDocEditFooter +Rewritten: 2020-06-15 [0.9] GuiDocEditSearch +Created: 2020-06-27 [0.10] GuiDocEditFooter Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter This file is a part of novelWriter diff --git a/novelwriter/gui/docviewer.py b/novelwriter/gui/docviewer.py index 823ee845..c5dc0924 100644 --- a/novelwriter/gui/docviewer.py +++ b/novelwriter/gui/docviewer.py @@ -7,7 +7,7 @@ File History: Created: 2019-05-10 [0.0.1] GuiDocViewer Created: 2019-10-31 [0.3.2] GuiDocViewDetails 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 This file is a part of novelWriter diff --git a/novelwriter/gui/projtree.py b/novelwriter/gui/projtree.py index 18217039..0b7ff5e0 100644 --- a/novelwriter/gui/projtree.py +++ b/novelwriter/gui/projtree.py @@ -5,7 +5,7 @@ GUI classes for the main window project tree File History: 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 Copyright 2018–2021, Veronica Berglyd Olsen @@ -51,7 +51,7 @@ class GuiProjectTree(QTreeWidget): novelItemChanged = pyqtSignal() noteItemChanged = pyqtSignal() - projectWordCountChanged = pyqtSignal(int, int) + wordCountsChanged = pyqtSignal() def __init__(self, theParent): QTreeWidget.__init__(self, theParent) @@ -86,8 +86,7 @@ class GuiProjectTree(QTreeWidget): self.setIndentation(iPx) self.setColumnCount(4) self.setHeaderLabels([ - self.tr("Project Tree"), self.tr("Words"), "", - self.tr("Status") if self.mainConf.fullStatus else "" + self.tr("Project Tree"), self.tr("Words"), "", "" ]) treeHeadItem = self.headerItem() @@ -305,7 +304,7 @@ class GuiProjectTree(QTreeWidget): nwItem.setWordCount(wC) nwItem.setParaCount(pC) self.propagateCount(tHandle, wC) - self.projectWordCount() + self.wordCountsChanged.emit() return True @@ -533,7 +532,7 @@ class GuiProjectTree(QTreeWidget): self.theIndex.deleteHandle(tHandle) self._deleteTreeItem(tHandle) self._setTreeChanged(True) - self.projectWordCount() + self.wordCountsChanged.emit() else: # 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.setIcon(self.C_EXPORT, expIcon) trItem.setIcon(self.C_STATUS, statIcon) - - if self.mainConf.fullStatus: - trItem.setText(self.C_STATUS, nwItem.itemStatus) - else: - trItem.setToolTip(self.C_STATUS, nwItem.itemStatus) + trItem.setToolTip(self.C_STATUS, nwItem.itemStatus) if self.mainConf.emphLabels and nwItem.itemLayout == nwItemLayout.DOCUMENT: if hLevel in ("H1", "H2"): @@ -674,25 +669,6 @@ class GuiProjectTree(QTreeWidget): 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): """Build the entire project tree from scratch. This depends on the save project item iterator in the project class which will @@ -702,11 +678,6 @@ class GuiProjectTree(QTreeWidget): logger.debug("Building the project tree ...") self.clearTree() - self.setHeaderLabels([ - self.tr("Project Tree"), self.tr("Words"), "", - self.tr("Status") if self.mainConf.fullStatus else "" - ]) - iCount = 0 for nwItem in self.theProject.getProjectItems(): iCount += 1 @@ -819,7 +790,7 @@ class GuiProjectTree(QTreeWidget): """Slot for updating the word count of a specific item. """ self.propagateCount(tHandle, wCount) - self.projectWordCount() + self.wordCountsChanged.emit() return ## diff --git a/novelwriter/gui/statusbar.py b/novelwriter/gui/statusbar.py index db92b388..9f3993da 100644 --- a/novelwriter/gui/statusbar.py +++ b/novelwriter/gui/statusbar.py @@ -4,7 +4,8 @@ novelWriter – GUI Main Window Status Bar GUI class for the main window status bar 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 Copyright 2018–2021, Veronica Berglyd Olsen @@ -28,7 +29,7 @@ import novelwriter from time import time -from PyQt5.QtCore import QLocale, pyqtSlot +from PyQt5.QtCore import pyqtSlot, QLocale from PyQt5.QtGui import QColor, QPainter from PyQt5.QtWidgets import qApp, QStatusBar, QLabel, QAbstractButton @@ -125,7 +126,7 @@ class GuiMainStatus(QStatusBar): """ self.setRefTime(None) self.setLanguage(None, "") - self.doUpdateProjectStats(0, 0) + self.setProjectStats(0, 0) self.setProjectStatus(nwState.NONE) self.setDocumentStatus(nwState.NONE) self.updateTime() @@ -176,6 +177,16 @@ class GuiMainStatus(QStatusBar): 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): """Update the session clock. """ @@ -211,14 +222,6 @@ class GuiMainStatus(QStatusBar): 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) def doUpdateProjectStatus(self, isChanged): """Slot for updating the project status. diff --git a/novelwriter/gui/theme.py b/novelwriter/gui/theme.py index 700f83a7..4ac80965 100644 --- a/novelwriter/gui/theme.py +++ b/novelwriter/gui/theme.py @@ -5,7 +5,7 @@ Classes managing and caching themes and icons File History: 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 Copyright 2018–2021, Veronica Berglyd Olsen diff --git a/novelwriter/guimain.py b/novelwriter/guimain.py index a374e8d1..658be71d 100644 --- a/novelwriter/guimain.py +++ b/novelwriter/guimain.py @@ -124,7 +124,7 @@ class GuiMain(QMainWindow): self.treeView.itemSelectionChanged.connect(self._treeSingleClick) self.treeView.itemDoubleClicked.connect(self._treeDoubleClick) self.treeView.novelItemChanged.connect(self._treeNovelItemChanged) - self.treeView.projectWordCountChanged.connect(self.statusBar.doUpdateProjectStats) + self.treeView.wordCountsChanged.connect(self._updateStatusWordCount) # Minor GUI Elements self.statusIcons = [] @@ -529,7 +529,7 @@ class GuiMain(QMainWindow): self.docEditor.toggleSpellCheck(self.theProject.spellCheck) self.mainMenu.setAutoOutline(self.theProject.autoOutline) self.statusBar.setRefTime(self.theProject.projOpened) - self.statusBar.doUpdateProjectStats(self.theProject.currWCount, 0) + self._updateStatusWordCount() # Restore previously open documents, if any if self.theProject.lastEdited is not None: @@ -903,13 +903,13 @@ class GuiMain(QMainWindow): tItem.setParaCount(pC) self.treeView.propagateCount(tItem.itemHandle, wC) self.treeView.setTreeItemValues(tItem.itemHandle) - self.treeView.projectWordCount() tEnd = time() self.setStatus( self.tr("Indexing completed in {0} ms").format(f"{(tEnd - tStart)*1000.0:.1f}") ) self.docEditor.updateTagHighLighting() + self._updateStatusWordCount() qApp.restoreOverrideCursor() if not beQuiet: @@ -980,6 +980,7 @@ class GuiMain(QMainWindow): self.novelView.initTree() self.projView.initOutline() self.projMeta.initDetails() + self._updateStatusWordCount() return @@ -1527,6 +1528,26 @@ class GuiMain(QMainWindow): 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() def _treeSingleClick(self): """Single click on a project tree item just updates the details diff --git a/tests/reference/baseConfig_novelwriter.conf b/tests/reference/baseConfig_novelwriter.conf index fbcab310..2b966644 100644 --- a/tests/reference/baseConfig_novelwriter.conf +++ b/tests/reference/baseConfig_novelwriter.conf @@ -1,5 +1,5 @@ [Main] -timestamp = 2021-08-30 21:27:27 +timestamp = 2021-09-10 00:23:50 theme = default syntax = default_light icons = typicons_light @@ -26,7 +26,6 @@ fullscreen = False [Project] autosaveproject = 60 autosavedoc = 30 -fullstatus = True emphlabels = True [Editor] @@ -57,7 +56,9 @@ spellcheck = en showtabsnspaces = False showlineendings = False showmultispaces = True +wordcounttimer = 5.0 bigdoclimit = 800 +incnoteswcount = True showfullpath = True highlightquotes = True allowopensquote = False diff --git a/tests/reference/guiPreferences_novelwriter.conf b/tests/reference/guiPreferences_novelwriter.conf index 9cfbf046..e498b229 100644 --- a/tests/reference/guiPreferences_novelwriter.conf +++ b/tests/reference/guiPreferences_novelwriter.conf @@ -1,5 +1,5 @@ [Main] -timestamp = 2021-08-30 21:45:17 +timestamp = 2021-09-10 00:23:52 theme = default syntax = default_light icons = typicons_light @@ -26,7 +26,6 @@ fullscreen = False [Project] autosaveproject = 40 autosavedoc = 20 -fullstatus = True emphlabels = True [Editor] @@ -57,7 +56,9 @@ spellcheck = en showtabsnspaces = True showlineendings = True showmultispaces = True +wordcounttimer = 5.0 bigdoclimit = 500 +incnoteswcount = True showfullpath = False highlightquotes = False allowopensquote = False diff --git a/tests/test_base/test_base_common.py b/tests/test_base/test_base_common.py index bbb0b0db..c8a64ba2 100644 --- a/tests/test_base/test_base_common.py +++ b/tests/test_base/test_base_common.py @@ -482,6 +482,7 @@ def testBaseCommon_NWConfigParser(fncDir): "boolopt4 = 0\n" "list1 = a, b, c\n" "list2 = 17, 18, 19\n" + "float1 = 4.2\n" )) cfgParser = NWConfigParser() @@ -516,6 +517,14 @@ def testBaseCommon_NWConfigParser(fncDir): assert cfgParser.rdInt("nope", "intopt1", 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 assert cfgParser.rdStrList("main", "list1", []) == [] assert cfgParser.rdStrList("main", "list1", ["x"]) == ["a"] diff --git a/tests/test_base/test_base_config.py b/tests/test_base/test_base_config.py index 244648b1..996d11c5 100644 --- a/tests/test_base/test_base_config.py +++ b/tests/test_base/test_base_config.py @@ -519,24 +519,24 @@ def testBaseConfig_SettersGetters(tmpConf, tmpDir, outDir, refDir): # Flag Setters # ============ - assert not tmpConf.setShowRefPanel(False) - assert not tmpConf.showRefPanel - assert tmpConf.setShowRefPanel(True) + assert tmpConf.setShowRefPanel(False) is False + assert tmpConf.showRefPanel is False + assert tmpConf.setShowRefPanel(True) is True - assert not tmpConf.setViewComments(False) - assert not tmpConf.viewComments - assert tmpConf.setViewComments(True) + assert tmpConf.setViewComments(False) is False + assert tmpConf.viewComments is False + assert tmpConf.setViewComments(True) is True - assert not tmpConf.setViewSynopsis(False) - assert not tmpConf.viewSynopsis - assert tmpConf.setViewSynopsis(True) + assert tmpConf.setViewSynopsis(False) is False + assert tmpConf.viewSynopsis is False + assert tmpConf.setViewSynopsis(True) is True # Check Final File # ================ - assert tmpConf.confChanged - assert tmpConf.saveConfig() - assert not tmpConf.confChanged + assert tmpConf.confChanged is True + assert tmpConf.saveConfig() is True + assert tmpConf.confChanged is False copyfile(confFile, testFile) assert cmpFiles(testFile, compFile, [2, 9, 10]) diff --git a/tests/test_core/test_core_project.py b/tests/test_core/test_core_project.py index 3726a1d4..35e3ca2d 100644 --- a/tests/test_core/test_core_project.py +++ b/tests/test_core/test_core_project.py @@ -868,12 +868,9 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, mockGUI, tmpDir): assert theProject.statusItems._theCounts == [1, 1, 1, 2, 0] assert theProject.importItems._theCounts == [3, 0, 0, 1, 0] - # Check word counts + # Session stats theProject.currWCount = 200 theProject.lastWCount = 100 - assert theProject.getSessionWordCount() == 100 - - # Session stats with monkeypatch.context() as mp: mp.setattr("os.path.isdir", lambda *a, **k: False) 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) theProject.projOpened = 1600002000 - theProject.novelWCount = 200 - theProject.notesWCount = 100 + theProject.currNovelWC = 200 + theProject.currNotesWC = 100 with monkeypatch.context() as mp: mp.setattr("novelwriter.core.project.time", lambda: 1600005600) diff --git a/tests/test_dialogs/test_dlg_preferences.py b/tests/test_dialogs/test_dlg_preferences.py index a9c54403..4b3cf847 100644 --- a/tests/test_dialogs/test_dlg_preferences.py +++ b/tests/test_dialogs/test_dlg_preferences.py @@ -249,7 +249,7 @@ def testDlgPreferences_Main(qtbot, monkeypatch, fncDir, outDir, refDir): testFile = os.path.join(outDir, "guiPreferences_novelwriter.conf") compFile = os.path.join(refDir, "guiPreferences_novelwriter.conf") 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) # Clean up diff --git a/tests/test_gui/test_gui_statusbar.py b/tests/test_gui/test_gui_statusbar.py new file mode 100644 index 00000000..aa863c66 --- /dev/null +++ b/tests/test_gui/test_gui_statusbar.py @@ -0,0 +1,100 @@ +""" +novelWriter – Main Status Bar Class Tester +========================================== + +This file is a part of novelWriter +Copyright 2018–2021, 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 . +""" + +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