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:
committed by
GitHub
parent
b9311e84ce
commit
3c8cb34b0a
+12
-4
@@ -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):
|
||||
|
||||
+13
-11
@@ -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
|
||||
|
||||
##
|
||||
|
||||
+29
-29
@@ -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),
|
||||
))
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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()
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
##
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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
|
||||
|
||||
+24
-3
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"]
|
||||
|
||||
@@ -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])
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 <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
|
||||
Reference in New Issue
Block a user