Merge pull request #656 from vkbo/session_idle

Session Idle Time
This commit is contained in:
Veronica K. Berglyd Olsen
2021-02-09 20:36:10 +00:00
committed by GitHub
15 changed files with 351 additions and 132 deletions
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.2"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg5582">
<metadata
id="metadata5588">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs5586" />
<path
d="m 6.7499997,3 c -1.656,0 -3,1.3440001 -3,3.0000001 V 17.999999 c 0,1.656 1.344,3.000001 3,3.000001 1.6560002,0 2.9999998,-1.344001 2.9999998,-3.000001 V 6.0000001 C 9.7499995,4.3440001 8.4059999,3 6.7499997,3 Z M 17.249999,3 c -1.656,0 -3.000001,1.3440001 -3.000001,3.0000001 V 17.999999 c 0,1.656 1.344001,3.000001 3.000001,3.000001 1.656,0 3.000001,-1.344001 3.000001,-3.000001 V 6.0000001 C 20.25,4.3440001 18.905999,3 17.249999,3 Z"
id="path5580"
style="fill:#aeaeae;fill-opacity:1;stroke-width:1.49999" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+31
View File
@@ -0,0 +1,31 @@
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
<svg
xmlns:dc="http://purl.org/dc/elements/1.1/"
xmlns:cc="http://creativecommons.org/ns#"
xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
xmlns:svg="http://www.w3.org/2000/svg"
xmlns="http://www.w3.org/2000/svg"
version="1.2"
width="24"
height="24"
viewBox="0 0 24 24"
id="svg5582">
<metadata
id="metadata5588">
<rdf:RDF>
<cc:Work
rdf:about="">
<dc:format>image/svg+xml</dc:format>
<dc:type
rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
<dc:title></dc:title>
</cc:Work>
</rdf:RDF>
</metadata>
<defs
id="defs5586" />
<path
d="m 6.7499997,3 c -1.656,0 -3,1.3440001 -3,3.0000001 V 17.999999 c 0,1.656 1.344,3.000001 3,3.000001 1.6560002,0 2.9999998,-1.344001 2.9999998,-3.000001 V 6.0000001 C 9.7499995,4.3440001 8.4059999,3 6.7499997,3 Z M 17.249999,3 c -1.656,0 -3.000001,1.3440001 -3.000001,3.0000001 V 17.999999 c 0,1.656 1.344001,3.000001 3.000001,3.000001 1.656,0 3.000001,-1.344001 3.000001,-3.000001 V 6.0000001 C 20.25,4.3440001 18.905999,3 17.249999,3 Z"
id="path5580"
style="fill:#000000;fill-opacity:0.780392;stroke-width:1.49999" />
</svg>

After

Width:  |  Height:  |  Size: 1.2 KiB

+11
View File
@@ -144,6 +144,9 @@ class Config:
self.allowOpenDQuote = True # Allow open-ended double quotes self.allowOpenDQuote = True # Allow open-ended double quotes
self.highlightEmph = True # Add colour to text emphasis self.highlightEmph = True # Add colour to text emphasis
self.stopWhenIdle = True # Stop the status bar clock when the user is idle
self.userIdleTime = 300 # Time of inactivity to consider user idle
## User-Selected Symbols ## User-Selected Symbols
self.fmtApostrophe = nwUnicode.U_RSQUO self.fmtApostrophe = nwUnicode.U_RSQUO
self.fmtSingleQuotes = [nwUnicode.U_LSQUO, nwUnicode.U_RSQUO] self.fmtSingleQuotes = [nwUnicode.U_LSQUO, nwUnicode.U_RSQUO]
@@ -532,6 +535,12 @@ class Config:
self.highlightEmph = self._parseLine( self.highlightEmph = self._parseLine(
cnfParse, cnfSec, "highlightemph", self.CNF_BOOL, self.highlightEmph cnfParse, cnfSec, "highlightemph", self.CNF_BOOL, self.highlightEmph
) )
self.stopWhenIdle = self._parseLine(
cnfParse, cnfSec, "stopwhenidle", self.CNF_BOOL, self.stopWhenIdle
)
self.userIdleTime = self._parseLine(
cnfParse, cnfSec, "useridletime", self.CNF_INT, self.userIdleTime
)
## Backup ## Backup
cnfSec = "Backup" cnfSec = "Backup"
@@ -672,6 +681,8 @@ class Config:
cnfParse.set(cnfSec, "allowopensquote", str(self.allowOpenSQuote)) cnfParse.set(cnfSec, "allowopensquote", str(self.allowOpenSQuote))
cnfParse.set(cnfSec, "allowopendquote", str(self.allowOpenDQuote)) cnfParse.set(cnfSec, "allowopendquote", str(self.allowOpenDQuote))
cnfParse.set(cnfSec, "highlightemph", str(self.highlightEmph)) cnfParse.set(cnfSec, "highlightemph", str(self.highlightEmph))
cnfParse.set(cnfSec, "stopwhenidle", str(self.stopWhenIdle))
cnfParse.set(cnfSec, "useridletime", str(self.userIdleTime))
## Backup ## Backup
cnfSec = "Backup" cnfSec = "Backup"
+2
View File
@@ -47,6 +47,7 @@ class OptionState():
"widthCol0", "widthCol0",
"widthCol1", "widthCol1",
"widthCol2", "widthCol2",
"widthCol3",
"sortCol", "sortCol",
"sortOrder", "sortOrder",
"incNovel", "incNovel",
@@ -54,6 +55,7 @@ class OptionState():
"hideZeros", "hideZeros",
"hideNegative", "hideNegative",
"groupByDay", "groupByDay",
"showIdleTime",
"histMax", "histMax",
}, },
"GuiDocSplit": { "GuiDocSplit": {
+7 -6
View File
@@ -726,12 +726,12 @@ class NWProject():
return True return True
def closeProject(self): def closeProject(self, idleTime=0):
"""Close the current project and clear all meta data. """Close the current project and clear all meta data.
""" """
self.optState.saveSettings() self.optState.saveSettings()
self.projTree.writeToCFile() self.projTree.writeToCFile()
self._appendSessionStats() self._appendSessionStats(idleTime)
self._clearLockFile() self._clearLockFile()
self.clearProject() self.clearProject()
self.lockedBy = None self.lockedBy = None
@@ -1389,7 +1389,7 @@ class NWProject():
return True return True
def _appendSessionStats(self): def _appendSessionStats(self, idleTime):
"""Append session statistics to the sessions log file. """Append session statistics to the sessions log file.
""" """
if not self.ensureFolderStructure(): if not self.ensureFolderStructure():
@@ -1404,15 +1404,16 @@ class NWProject():
# It's a new file, so add a header # It's a new file, so add a header
if self.lastWCount > 0: if self.lastWCount > 0:
outFile.write("# Offset %d\n" % self.lastWCount) outFile.write("# Offset %d\n" % self.lastWCount)
outFile.write("# %-17s %-19s %8s %8s\n" % ( outFile.write("# %-17s %-19s %8s %8s %8s\n" % (
"Start Time", "End Time", "Novel", "Notes" "Start Time", "End Time", "Novel", "Notes", "Idle"
)) ))
outFile.write("%-19s %-19s %8d %8d\n" % ( outFile.write("%-19s %-19s %8d %8d %8d\n" % (
formatTimeStamp(self.projOpened), formatTimeStamp(self.projOpened),
formatTimeStamp(time()), formatTimeStamp(time()),
self.novelWCount, self.novelWCount,
self.notesWCount, self.notesWCount,
int(idleTime),
)) ))
except Exception: except Exception:
+15 -10
View File
@@ -91,6 +91,7 @@ class GuiDocEditor(QTextEdit):
self.wordCount = 0 # Word count self.wordCount = 0 # Word count
self.paraCount = 0 # Paragraph count self.paraCount = 0 # Paragraph count
self.lastEdit = 0 # Time stamp of last edit self.lastEdit = 0 # Time stamp of last edit
self.lastActive = 0 # Time stamp of last activity
self.lastFind = None # Position of the last found search word self.lastFind = None # Position of the last found search word
self.bigDoc = False # Flag for very large document size self.bigDoc = False # Flag for very large document size
self.doReplace = False # Switch to temporarily disable auto-replace self.doReplace = False # Switch to temporarily disable auto-replace
@@ -169,15 +170,16 @@ class GuiDocEditor(QTextEdit):
self.clear() self.clear()
self.wcTimer.stop() self.wcTimer.stop()
self.theHandle = None self.theHandle = None
self.charCount = 0 self.charCount = 0
self.wordCount = 0 self.wordCount = 0
self.paraCount = 0 self.paraCount = 0
self.lastEdit = 0 self.lastEdit = 0
self.lastFind = None self.lastActive = 0
self.bigDoc = False self.lastFind = None
self.doReplace = False self.bigDoc = False
self.queuePos = None self.doReplace = False
self.queuePos = None
self.setDocumentChanged(False) self.setDocumentChanged(False)
self.docHeader.setTitleFromHandle(self.theHandle) self.docHeader.setTitleFromHandle(self.theHandle)
@@ -319,6 +321,7 @@ class GuiDocEditor(QTextEdit):
logger.debug("Document highlighted in %.3f ms" % (1000*(afTime-bfTime))) logger.debug("Document highlighted in %.3f ms" % (1000*(afTime-bfTime)))
self.lastEdit = time() self.lastEdit = time()
self.lastActive = time()
self._runCounter() self._runCounter()
self.wcTimer.start() self.wcTimer.start()
self.theHandle = tHandle self.theHandle = tHandle
@@ -722,6 +725,7 @@ class GuiDocEditor(QTextEdit):
return False return False
self._allowAutoReplace(True) self._allowAutoReplace(True)
self.lastActive = time()
return True return True
@@ -839,6 +843,7 @@ class GuiDocEditor(QTextEdit):
* The undo/redo/select all sequences bypasses the docAction * The undo/redo/select all sequences bypasses the docAction
pathway from the menu, so we redirect them back from here. pathway from the menu, so we redirect them back from here.
""" """
self.lastActive = time()
isReturn = keyEvent.key() == Qt.Key_Return isReturn = keyEvent.key() == Qt.Key_Return
isReturn |= keyEvent.key() == Qt.Key_Enter isReturn |= keyEvent.key() == Qt.Key_Enter
if isReturn and self.docSearch.anyFocus(): if isReturn and self.docSearch.anyFocus():
@@ -1083,7 +1088,7 @@ class GuiDocEditor(QTextEdit):
logger.verbose("Word counter is busy") logger.verbose("Word counter is busy")
return return
if time() - self.lastEdit < 5*self.wcInterval: if time() - self.lastEdit < 5 * self.wcInterval:
logger.verbose("Running word counter") logger.verbose("Running word counter")
self.theParent.threadPool.start(self.wCounter) self.theParent.threadPool.start(self.wCounter)
+31
View File
@@ -358,6 +358,33 @@ class GuiPreferencesProjects(QWidget):
"If off, backups will run in the background." "If off, backups will run in the background."
) )
# Session Timer
# =============
self.mainForm.addGroupLabel("Session Timer")
## Pause when idle
self.stopWhenIdle = QSwitch()
self.stopWhenIdle.setChecked(self.mainConf.stopWhenIdle)
self.mainForm.addRow(
"Pause the session timer when not writing",
self.stopWhenIdle,
"Also pauses when the application window does not have focus."
)
## Inactive time for idle
self.userIdleTime = QDoubleSpinBox()
self.userIdleTime.setMinimum(0.5)
self.userIdleTime.setMaximum(600.0)
self.userIdleTime.setSingleStep(0.5)
self.userIdleTime.setDecimals(1)
self.userIdleTime.setValue(self.mainConf.userIdleTime/60.0)
self.mainForm.addRow(
"Editor inactive time before pausing timer",
self.userIdleTime,
"User activity includes typing and changing the content.",
theUnit="minutes"
)
return return
def saveValues(self): def saveValues(self):
@@ -372,6 +399,10 @@ class GuiPreferencesProjects(QWidget):
self.mainConf.backupOnClose = self.backupOnClose.isChecked() self.mainConf.backupOnClose = self.backupOnClose.isChecked()
self.mainConf.askBeforeBackup = self.askBeforeBackup.isChecked() self.mainConf.askBeforeBackup = self.askBeforeBackup.isChecked()
# Session Timer
self.mainConf.stopWhenIdle = self.stopWhenIdle.isChecked()
self.mainConf.userIdleTime = round(self.userIdleTime.value() * 60)
self.mainConf.confChanged = True self.mainConf.confChanged = True
return return
+27 -14
View File
@@ -29,7 +29,6 @@ import logging
from time import time from time import time
from PyQt5.QtCore import QTimer
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
@@ -49,6 +48,7 @@ class GuiMainStatus(QStatusBar):
self.theParent = theParent self.theParent = theParent
self.theTheme = theParent.theTheme self.theTheme = theParent.theTheme
self.refTime = None self.refTime = None
self.userIdle = False
colNone = QColor(*self.theTheme.statNone) colNone = QColor(*self.theTheme.statNone)
colTrue = QColor(*self.theTheme.statUnsaved) colTrue = QColor(*self.theTheme.statUnsaved)
@@ -97,9 +97,12 @@ class GuiMainStatus(QStatusBar):
## The Session Clock ## The Session Clock
### Set the mimimum width so the label doesn't rescale every second ### Set the mimimum width so the label doesn't rescale every second
self.timePixmap = self.theTheme.getPixmap("status_time", (iPx, iPx))
self.idlePixmap = self.theTheme.getPixmap("status_idle", (iPx, iPx))
self.timeIcon = QLabel() self.timeIcon = QLabel()
self.timeText = QLabel("") self.timeText = QLabel("")
self.timeIcon.setPixmap(self.theTheme.getPixmap("status_time", (iPx, iPx))) self.timeIcon.setPixmap(self.timePixmap)
self.timeText.setToolTip("Session Time") self.timeText.setToolTip("Session Time")
self.timeText.setMinimumWidth(self.theTheme.getTextWidth("00:00:00:")) self.timeText.setMinimumWidth(self.theTheme.getTextWidth("00:00:00:"))
self.timeIcon.setContentsMargins(0, 0, 0, 0) self.timeIcon.setContentsMargins(0, 0, 0, 0)
@@ -110,12 +113,6 @@ class GuiMainStatus(QStatusBar):
# Other Settings # Other Settings
self.setSizeGripEnabled(True) self.setSizeGripEnabled(True)
# Start the Clock
self.sessionTimer = QTimer()
self.sessionTimer.setInterval(1000)
self.sessionTimer.timeout.connect(self._updateTime)
self.sessionTimer.start()
logger.debug("GuiMainStatus initialisation complete") logger.debug("GuiMainStatus initialisation complete")
self.clearStatus() self.clearStatus()
@@ -130,7 +127,7 @@ class GuiMainStatus(QStatusBar):
self.setStats(0, 0) self.setStats(0, 0)
self.setProjectStatus(None) self.setProjectStatus(None)
self.setDocumentStatus(None) self.setDocumentStatus(None)
self._updateTime() self.updateTime()
return True return True
## ##
@@ -182,17 +179,33 @@ class GuiMainStatus(QStatusBar):
self.statsText.setToolTip("Project word count (session change)") self.statsText.setToolTip("Project word count (session change)")
return return
## def setUserIdle(self, userIdle):
# Internal Functions """Change the idle status icon.
## """
if not self.mainConf.stopWhenIdle:
userIdle = False
def _updateTime(self): if self.userIdle != userIdle:
if userIdle:
self.timeIcon.setPixmap(self.idlePixmap)
else:
self.timeIcon.setPixmap(self.timePixmap)
self.userIdle = userIdle
return
def updateTime(self, idleTime=0.0):
"""Update the session clock. """Update the session clock.
""" """
if self.refTime is None: if self.refTime is None:
self.timeText.setText("00:00:00") self.timeText.setText("00:00:00")
else: else:
self.timeText.setText(formatTime(round(time() - self.refTime))) if self.mainConf.stopWhenIdle:
sessTime = round(time() - self.refTime - idleTime)
else:
sessTime = round(time() - self.refTime)
self.timeText.setText(formatTime(sessTime))
return return
# END Class GuiMainStatus # END Class GuiMainStatus
+1
View File
@@ -539,6 +539,7 @@ class GuiIcons:
"proj_nwx" : (None, None), "proj_nwx" : (None, None),
"status_lang" : (None, None), "status_lang" : (None, None),
"status_time" : (None, None), "status_time" : (None, None),
"status_idle" : (None, None),
"status_stats" : (None, None), "status_stats" : (None, None),
"status_lines" : (None, None), "status_lines" : (None, None),
"doc_h0" : (QStyle.SP_FileIcon, "x-office-document"), "doc_h0" : (QStyle.SP_FileIcon, "x-office-document"),
+79 -26
View File
@@ -48,8 +48,9 @@ class GuiWritingStats(QDialog):
C_TIME = 0 C_TIME = 0
C_LENGTH = 1 C_LENGTH = 1
C_COUNT = 2 C_IDLE = 2
C_BAR = 3 C_COUNT = 3
C_BAR = 4
FMT_JSON = 0 FMT_JSON = 0
FMT_CSV = 1 FMT_CSV = 1
@@ -89,16 +90,21 @@ class GuiWritingStats(QDialog):
wCol2 = self.mainConf.pxInt( wCol2 = self.mainConf.pxInt(
self.optState.getInt("GuiWritingStats", "widthCol2", 80) self.optState.getInt("GuiWritingStats", "widthCol2", 80)
) )
wCol3 = self.mainConf.pxInt(
self.optState.getInt("GuiWritingStats", "widthCol3", 80)
)
self.listBox = QTreeWidget() self.listBox = QTreeWidget()
self.listBox.setHeaderLabels(["Session Start", "Length", "Words", "Histogram"]) self.listBox.setHeaderLabels(["Session Start", "Length", "Idle", "Words", "Histogram"])
self.listBox.setIndentation(0) self.listBox.setIndentation(0)
self.listBox.setColumnWidth(self.C_TIME, wCol0) self.listBox.setColumnWidth(self.C_TIME, wCol0)
self.listBox.setColumnWidth(self.C_LENGTH, wCol1) self.listBox.setColumnWidth(self.C_LENGTH, wCol1)
self.listBox.setColumnWidth(self.C_COUNT, wCol2) self.listBox.setColumnWidth(self.C_IDLE, wCol2)
self.listBox.setColumnWidth(self.C_COUNT, wCol3)
hHeader = self.listBox.headerItem() hHeader = self.listBox.headerItem()
hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight) hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight) hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)
sortValid = (Qt.AscendingOrder, Qt.DescendingOrder) sortValid = (Qt.AscendingOrder, Qt.DescendingOrder)
@@ -127,6 +133,10 @@ class GuiWritingStats(QDialog):
self.labelTotal.setFont(self.theTheme.guiFontFixed) self.labelTotal.setFont(self.theTheme.guiFontFixed)
self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.labelTotal.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.labelIdleT = QLabel(formatTime(0))
self.labelIdleT.setFont(self.theTheme.guiFontFixed)
self.labelIdleT.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.labelFilter = QLabel(formatTime(0)) self.labelFilter = QLabel(formatTime(0))
self.labelFilter.setFont(self.theTheme.guiFontFixed) self.labelFilter.setFont(self.theTheme.guiFontFixed)
self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
@@ -144,16 +154,18 @@ class GuiWritingStats(QDialog):
self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight) self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)
self.infoForm.addWidget(QLabel("Total Time:"), 0, 0) self.infoForm.addWidget(QLabel("Total Time:"), 0, 0)
self.infoForm.addWidget(QLabel("Filtered Time:"), 1, 0) self.infoForm.addWidget(QLabel("Idle Time:"), 1, 0)
self.infoForm.addWidget(QLabel("Novel Word Count:"), 2, 0) self.infoForm.addWidget(QLabel("Filtered Time:"), 2, 0)
self.infoForm.addWidget(QLabel("Notes Word Count:"), 3, 0) self.infoForm.addWidget(QLabel("Novel Word Count:"), 3, 0)
self.infoForm.addWidget(QLabel("Total Word Count:"), 4, 0) self.infoForm.addWidget(QLabel("Notes Word Count:"), 4, 0)
self.infoForm.addWidget(QLabel("Total Word Count:"), 5, 0)
self.infoForm.addWidget(self.labelTotal, 0, 1) self.infoForm.addWidget(self.labelTotal, 0, 1)
self.infoForm.addWidget(self.labelFilter, 1, 1) self.infoForm.addWidget(self.labelIdleT, 1, 1)
self.infoForm.addWidget(self.novelWords, 2, 1) self.infoForm.addWidget(self.labelFilter, 2, 1)
self.infoForm.addWidget(self.notesWords, 3, 1) self.infoForm.addWidget(self.novelWords, 3, 1)
self.infoForm.addWidget(self.totalWords, 4, 1) self.infoForm.addWidget(self.notesWords, 4, 1)
self.infoForm.setRowStretch(5, 1) self.infoForm.addWidget(self.totalWords, 5, 1)
self.infoForm.setRowStretch(6, 1)
# Filter Options # Filter Options
sPx = self.theTheme.baseIconSize sPx = self.theTheme.baseIconSize
@@ -192,17 +204,25 @@ class GuiWritingStats(QDialog):
) )
self.groupByDay.clicked.connect(self._updateListBox) self.groupByDay.clicked.connect(self._updateListBox)
self.showIdleTime = QSwitch(width=2*sPx, height=sPx)
self.showIdleTime.setChecked(
self.optState.getBool("GuiWritingStats", "showIdleTime", False)
)
self.showIdleTime.clicked.connect(self._updateListBox)
self.filterForm.addWidget(QLabel("Count novel files"), 0, 0) self.filterForm.addWidget(QLabel("Count novel files"), 0, 0)
self.filterForm.addWidget(QLabel("Count note files"), 1, 0) self.filterForm.addWidget(QLabel("Count note files"), 1, 0)
self.filterForm.addWidget(QLabel("Hide zero word count"), 2, 0) self.filterForm.addWidget(QLabel("Hide zero word count"), 2, 0)
self.filterForm.addWidget(QLabel("Hide negative word count"), 3, 0) self.filterForm.addWidget(QLabel("Hide negative word count"), 3, 0)
self.filterForm.addWidget(QLabel("Group entries by day"), 4, 0) self.filterForm.addWidget(QLabel("Group entries by day"), 4, 0)
self.filterForm.addWidget(QLabel("Show idle time column"), 5, 0)
self.filterForm.addWidget(self.incNovel, 0, 1) self.filterForm.addWidget(self.incNovel, 0, 1)
self.filterForm.addWidget(self.incNotes, 1, 1) self.filterForm.addWidget(self.incNotes, 1, 1)
self.filterForm.addWidget(self.hideZeros, 2, 1) self.filterForm.addWidget(self.hideZeros, 2, 1)
self.filterForm.addWidget(self.hideNegative, 3, 1) self.filterForm.addWidget(self.hideNegative, 3, 1)
self.filterForm.addWidget(self.groupByDay, 4, 1) self.filterForm.addWidget(self.groupByDay, 4, 1)
self.filterForm.setRowStretch(5, 1) self.filterForm.addWidget(self.showIdleTime, 5, 1)
self.filterForm.setRowStretch(6, 1)
# Settings # Settings
self.histMax = QSpinBox(self) self.histMax = QSpinBox(self)
@@ -278,6 +298,7 @@ class GuiWritingStats(QDialog):
widthCol0 = self.mainConf.rpxInt(self.listBox.columnWidth(0)) widthCol0 = self.mainConf.rpxInt(self.listBox.columnWidth(0))
widthCol1 = self.mainConf.rpxInt(self.listBox.columnWidth(1)) widthCol1 = self.mainConf.rpxInt(self.listBox.columnWidth(1))
widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2)) widthCol2 = self.mainConf.rpxInt(self.listBox.columnWidth(2))
widthCol3 = self.mainConf.rpxInt(self.listBox.columnWidth(3))
sortCol = self.listBox.sortColumn() sortCol = self.listBox.sortColumn()
sortOrder = self.listBox.header().sortIndicatorOrder() sortOrder = self.listBox.header().sortIndicatorOrder()
incNovel = self.incNovel.isChecked() incNovel = self.incNovel.isChecked()
@@ -285,6 +306,7 @@ class GuiWritingStats(QDialog):
hideZeros = self.hideZeros.isChecked() hideZeros = self.hideZeros.isChecked()
hideNegative = self.hideNegative.isChecked() hideNegative = self.hideNegative.isChecked()
groupByDay = self.groupByDay.isChecked() groupByDay = self.groupByDay.isChecked()
showIdleTime = self.showIdleTime.isChecked()
histMax = self.histMax.value() histMax = self.histMax.value()
self.optState.setValue("GuiWritingStats", "winWidth", winWidth) self.optState.setValue("GuiWritingStats", "winWidth", winWidth)
@@ -292,6 +314,7 @@ class GuiWritingStats(QDialog):
self.optState.setValue("GuiWritingStats", "widthCol0", widthCol0) self.optState.setValue("GuiWritingStats", "widthCol0", widthCol0)
self.optState.setValue("GuiWritingStats", "widthCol1", widthCol1) self.optState.setValue("GuiWritingStats", "widthCol1", widthCol1)
self.optState.setValue("GuiWritingStats", "widthCol2", widthCol2) self.optState.setValue("GuiWritingStats", "widthCol2", widthCol2)
self.optState.setValue("GuiWritingStats", "widthCol3", widthCol3)
self.optState.setValue("GuiWritingStats", "sortCol", sortCol) self.optState.setValue("GuiWritingStats", "sortCol", sortCol)
self.optState.setValue("GuiWritingStats", "sortOrder", sortOrder) self.optState.setValue("GuiWritingStats", "sortOrder", sortOrder)
self.optState.setValue("GuiWritingStats", "incNovel", incNovel) self.optState.setValue("GuiWritingStats", "incNovel", incNovel)
@@ -299,6 +322,7 @@ class GuiWritingStats(QDialog):
self.optState.setValue("GuiWritingStats", "hideZeros", hideZeros) self.optState.setValue("GuiWritingStats", "hideZeros", hideZeros)
self.optState.setValue("GuiWritingStats", "hideNegative", hideNegative) self.optState.setValue("GuiWritingStats", "hideNegative", hideNegative)
self.optState.setValue("GuiWritingStats", "groupByDay", groupByDay) self.optState.setValue("GuiWritingStats", "groupByDay", groupByDay)
self.optState.setValue("GuiWritingStats", "showIdleTime", showIdleTime)
self.optState.setValue("GuiWritingStats", "histMax", histMax) self.optState.setValue("GuiWritingStats", "histMax", histMax)
self.optState.saveSettings() self.optState.saveSettings()
@@ -347,23 +371,25 @@ class GuiWritingStats(QDialog):
with open(savePath, mode="w", encoding="utf8") as outFile: with open(savePath, mode="w", encoding="utf8") as outFile:
if dataFmt == self.FMT_JSON: if dataFmt == self.FMT_JSON:
jsonData = [] jsonData = []
for _, sD, tT, wD, wA, wB in self.filterData: for _, sD, tT, wD, wA, wB, tI in self.filterData:
jsonData.append({ jsonData.append({
"date": sD, "date": sD,
"length": tT, "length": tT,
"newWords": wD, "newWords": wD,
"novelWords": wA, "novelWords": wA,
"noteWords": wB, "noteWords": wB,
"idleTime": tI,
}) })
json.dump(jsonData, outFile, indent=2) json.dump(jsonData, outFile, indent=2)
wSuccess = True wSuccess = True
if dataFmt == self.FMT_CSV: if dataFmt == self.FMT_CSV:
outFile.write( outFile.write(
'"Date","Length (sec)","Words Changed","Novel Words","Note Words"\n' '"Date","Length (sec)","Words Changed",'
'"Novel Words","Note Words","Idle Time (sec)"\n'
) )
for _, sD, tT, wD, wA, wB in self.filterData: for _, sD, tT, wD, wA, wB, tI in self.filterData:
outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB}\n') outFile.write(f'"{sD}",{tT:.0f},{wD},{wA},{wB},{tI}\n')
wSuccess = True wSuccess = True
except Exception as e: except Exception as e:
@@ -401,6 +427,7 @@ class GuiWritingStats(QDialog):
ttNovel = 0 ttNovel = 0
ttNotes = 0 ttNotes = 0
ttTime = 0 ttTime = 0
ttIdle = 0
logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS) logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
if not os.path.isfile(logFile): if not os.path.isfile(logFile):
@@ -419,7 +446,7 @@ class GuiWritingStats(QDialog):
continue continue
inData = inLine.split() inData = inLine.split()
if len(inData) != 6: if len(inData) < 6:
continue continue
dStart = datetime.strptime( dStart = datetime.strptime(
@@ -429,16 +456,21 @@ class GuiWritingStats(QDialog):
"%s %s" % (inData[2], inData[3]), nwConst.FMT_TSTAMP "%s %s" % (inData[2], inData[3]), nwConst.FMT_TSTAMP
) )
sIdle = 0
if len(inData) > 6:
sIdle = checkInt(inData[6], 0)
tDiff = dEnd - dStart tDiff = dEnd - dStart
sDiff = tDiff.total_seconds() sDiff = tDiff.total_seconds()
ttTime += sDiff ttTime += sDiff
ttIdle += sIdle
wcNovel = int(inData[4]) wcNovel = int(inData[4])
wcNotes = int(inData[5]) wcNotes = int(inData[5])
ttNovel = wcNovel ttNovel = wcNovel
ttNotes = wcNotes ttNotes = wcNotes
self.logData.append((dStart, sDiff, wcNovel, wcNotes)) self.logData.append((dStart, sDiff, wcNovel, wcNotes, sIdle))
except Exception as e: except Exception as e:
self.theParent.makeAlert( self.theParent.makeAlert(
@@ -448,12 +480,17 @@ class GuiWritingStats(QDialog):
ttWords = ttNovel + ttNotes ttWords = ttNovel + ttNotes
self.labelTotal.setText(formatTime(round(ttTime))) self.labelTotal.setText(formatTime(round(ttTime)))
self.labelIdleT.setText(formatTime(round(ttIdle)))
self.novelWords.setText(f"{ttNovel:n}") self.novelWords.setText(f"{ttNovel:n}")
self.notesWords.setText(f"{ttNotes:n}") self.notesWords.setText(f"{ttNotes:n}")
self.totalWords.setText(f"{ttWords:n}") self.totalWords.setText(f"{ttWords:n}")
return True return True
##
# Slots
##
def _updateListBox(self, dummyVar=None): def _updateListBox(self, dummyVar=None):
"""Load/reload the content of the list box. The dummyVar """Load/reload the content of the list box. The dummyVar
variable captures the variable sent from the widgets connecting variable captures the variable sent from the widgets connecting
@@ -474,25 +511,28 @@ class GuiWritingStats(QDialog):
tempData = [] tempData = []
sessDate = None sessDate = None
sessTime = 0 sessTime = 0
sIdle = 0
lstNovel = 0 lstNovel = 0
lstNotes = 0 lstNotes = 0
for n, (dStart, sDiff, wcNovel, wcNotes) in enumerate(self.logData): for n, (dStart, sDiff, wcNovel, wcNotes, sIdle) in enumerate(self.logData):
if n == 0: if n == 0:
sessDate = dStart.date() sessDate = dStart.date()
if sessDate != dStart.date(): if sessDate != dStart.date():
tempData.append((sessDate, sessTime, lstNovel, lstNotes)) tempData.append((sessDate, sessTime, lstNovel, lstNotes, sIdle))
sessDate = dStart.date() sessDate = dStart.date()
sessTime = sDiff sessTime = sDiff
sIdle = sIdle
lstNovel = wcNovel lstNovel = wcNovel
lstNotes = wcNotes lstNotes = wcNotes
else: else:
sessTime += sDiff sessTime += sDiff
sIdle += sIdle
lstNovel = wcNovel lstNovel = wcNovel
lstNotes = wcNotes lstNotes = wcNotes
if sessDate is not None: if sessDate is not None:
tempData.append((sessDate, sessTime, lstNovel, lstNotes)) tempData.append((sessDate, sessTime, lstNovel, lstNotes, sIdle))
else: else:
tempData = self.logData tempData = self.logData
@@ -502,7 +542,7 @@ class GuiWritingStats(QDialog):
pcTotal = 0 pcTotal = 0
listMax = 0 listMax = 0
isFirst = True isFirst = True
for dStart, sDiff, wcNovel, wcNotes in tempData: for dStart, sDiff, wcNovel, wcNotes, sIdle in tempData:
wcTotal = 0 wcTotal = 0
if incNovel: if incNovel:
@@ -528,16 +568,24 @@ class GuiWritingStats(QDialog):
else: else:
sStart = dStart.strftime(nwConst.FMT_TSTAMP) sStart = dStart.strftime(nwConst.FMT_TSTAMP)
self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes)) self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes, sIdle))
listMax = min(max(listMax, dwTotal), histMax) listMax = min(max(listMax, dwTotal), histMax)
pcTotal = wcTotal pcTotal = wcTotal
# Populate the list # Populate the list
for _, sStart, sDiff, nWords, _, _ in self.filterData: showIdleTime = self.showIdleTime.isChecked()
for _, sStart, sDiff, nWords, _, _, sIdle in self.filterData:
if showIdleTime:
idleEntry = formatTime(sIdle)
else:
sRatio = sIdle/sDiff if sDiff > 0.0 else 0.0
idleEntry = "%d %%" % round(100.0 * sRatio)
newItem = QTreeWidgetItem() newItem = QTreeWidgetItem()
newItem.setText(self.C_TIME, sStart) newItem.setText(self.C_TIME, sStart)
newItem.setText(self.C_LENGTH, formatTime(round(sDiff))) newItem.setText(self.C_LENGTH, formatTime(round(sDiff)))
newItem.setText(self.C_IDLE, idleEntry)
newItem.setText(self.C_COUNT, f"{nWords:n}") newItem.setText(self.C_COUNT, f"{nWords:n}")
if nWords > 0 and listMax > 0: if nWords > 0 and listMax > 0:
@@ -550,12 +598,17 @@ class GuiWritingStats(QDialog):
newItem.setData(self.C_BAR, Qt.DecorationRole, theBar) newItem.setData(self.C_BAR, Qt.DecorationRole, theBar)
newItem.setTextAlignment(self.C_LENGTH, Qt.AlignRight) newItem.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
newItem.setTextAlignment(self.C_IDLE, Qt.AlignRight)
newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight) newItem.setTextAlignment(self.C_COUNT, Qt.AlignRight)
newItem.setTextAlignment(self.C_BAR, Qt.AlignLeft | Qt.AlignVCenter) newItem.setTextAlignment(self.C_BAR, Qt.AlignLeft | Qt.AlignVCenter)
newItem.setFont(self.C_TIME, self.theTheme.guiFontFixed) newItem.setFont(self.C_TIME, self.theTheme.guiFontFixed)
newItem.setFont(self.C_LENGTH, self.theTheme.guiFontFixed) newItem.setFont(self.C_LENGTH, self.theTheme.guiFontFixed)
newItem.setFont(self.C_COUNT, self.theTheme.guiFontFixed) newItem.setFont(self.C_COUNT, self.theTheme.guiFontFixed)
if showIdleTime:
newItem.setFont(self.C_IDLE, self.theTheme.guiFontFixed)
else:
newItem.setFont(self.C_IDLE, self.theTheme.guiFont)
self.listBox.addTopLevelItem(newItem) self.listBox.addTopLevelItem(newItem)
self.timeFilter += sDiff self.timeFilter += sDiff
+38 -2
View File
@@ -86,6 +86,8 @@ class GuiMain(QMainWindow):
self.theIndex = NWIndex(self.theProject, self) self.theIndex = NWIndex(self.theProject, self)
self.hasProject = False self.hasProject = False
self.isFocusMode = False self.isFocusMode = False
self.idleRefTime = time()
self.idleTime = 0.0
# Prepare Main Window # Prepare Main Window
self.resize(*self.mainConf.getWinSize()) self.resize(*self.mainConf.getWinSize())
@@ -241,6 +243,12 @@ class GuiMain(QMainWindow):
self.asDocTimer = QTimer() self.asDocTimer = QTimer()
self.asDocTimer.timeout.connect(self._autoSaveDocument) self.asDocTimer.timeout.connect(self._autoSaveDocument)
# Main Clock
self.mainTimer = QTimer()
self.mainTimer.setInterval(1000)
self.mainTimer.timeout.connect(self._timeTick)
self.mainTimer.start()
# Shortcuts and Actions # Shortcuts and Actions
self._connectMenuActions() self._connectMenuActions()
@@ -412,7 +420,11 @@ class GuiMain(QMainWindow):
self.closeDocument() self.closeDocument()
self.docViewer.clearNavHistory() self.docViewer.clearNavHistory()
self.projView.closeOutline() self.projView.closeOutline()
self.theProject.closeProject()
self.theProject.closeProject(self.idleTime)
self.idleRefTime = time()
self.idleTime = 0.0
self.theIndex.clearIndex() self.theIndex.clearIndex()
self.clearGUI() self.clearGUI()
self.hasProject = False self.hasProject = False
@@ -477,7 +489,9 @@ class GuiMain(QMainWindow):
return False return False
# Project is loaded # Project is loaded
self.hasProject = True self.hasProject = True
self.idleRefTime = time()
self.idleTime = 0.0
# Load the tag index # Load the tag index
self.theIndex.loadIndex() self.theIndex.loadIndex()
@@ -1414,6 +1428,28 @@ class GuiMain(QMainWindow):
# Slots # Slots
## ##
@pyqtSlot()
def _timeTick(self):
"""Triggered on every tick of the timer.
"""
if not self.hasProject:
return
currTime = time()
editIdle = currTime - self.docEditor.lastActive > self.mainConf.userIdleTime
userIdle = qApp.applicationState() != Qt.ApplicationActive
if editIdle or userIdle:
self.idleTime += currTime - self.idleRefTime
self.statusBar.setUserIdle(True)
else:
self.statusBar.setUserIdle(False)
self.idleRefTime = currTime
self.statusBar.updateTime(idleTime=self.idleTime)
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
+4 -2
View File
@@ -1,12 +1,12 @@
[Main] [Main]
timestamp = 2020-10-11 22:50:45 timestamp = 2021-02-09 21:07:03
theme = default theme = default
syntax = default_light syntax = default_light
icons = typicons_colour_light icons = typicons_colour_light
guidark = False guidark = False
guifont = guifont =
guifontsize = 11 guifontsize = 11
lastnotes = 1.0 lastnotes = 0x0
[Sizes] [Sizes]
geometry = 1200, 650 geometry = 1200, 650
@@ -56,6 +56,8 @@ highlightquotes = True
allowopensquote = False allowopensquote = False
allowopendquote = True allowopendquote = True
highlightemph = True highlightemph = True
stopwhenidle = True
useridletime = 300
[Backup] [Backup]
backuppath = backuppath =
@@ -1,18 +1,18 @@
[Main] [Main]
timestamp = 2020-06-29 17:34:15 timestamp = 2021-02-09 21:07:17
theme = default theme = default
syntax = default_light syntax = default_light
icons = typicons_colour_light icons = typicons_colour_light
guidark = True guidark = True
guifont = Cantarell guifont = Sans
guifontsize = 12 guifontsize = 12
lastnotes = 1.0 lastnotes = 0x0
[Sizes] [Sizes]
geometry = 1100, 650 geometry = 1200, 650
treecols = 120, 30, 50 treecols = 200, 50, 30
novelcols = 200, 50 novelcols = 200, 50
projcols = 140, 55, 140 projcols = 200, 60, 140
mainpane = 300, 800 mainpane = 300, 800
docpane = 400, 400 docpane = 400, 400
viewpane = 500, 150 viewpane = 500, 150
@@ -26,7 +26,7 @@ autosaveproject = 40
autosavedoc = 20 autosavedoc = 20
[Editor] [Editor]
textfont = Cantarell textfont = None
textsize = 13 textsize = 13
fixedwidth = False fixedwidth = False
width = 700 width = 700
@@ -56,6 +56,8 @@ highlightquotes = False
allowopensquote = False allowopensquote = False
allowopendquote = True allowopendquote = True
highlightemph = False highlightemph = False
stopwhenidle = True
useridletime = 300
[Backup] [Backup]
backuppath = some/dir backuppath = some/dir
+5 -5
View File
@@ -859,12 +859,12 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
# Session stats # Session stats
monkeypatch.setattr("os.path.isdir", lambda *args, **kwargs: False) monkeypatch.setattr("os.path.isdir", lambda *args, **kwargs: False)
assert not theProject._appendSessionStats() assert not theProject._appendSessionStats(idleTime=0)
monkeypatch.undo() monkeypatch.undo()
# Block open # Block open
monkeypatch.setattr("builtins.open", causeOSError) monkeypatch.setattr("builtins.open", causeOSError)
assert not theProject._appendSessionStats() assert not theProject._appendSessionStats(idleTime=0)
monkeypatch.undo() monkeypatch.undo()
# Write entry # Write entry
@@ -876,13 +876,13 @@ def testCoreProject_Methods(monkeypatch, nwMinimal, dummyGUI, tmpDir):
theProject.notesWCount = 100 theProject.notesWCount = 100
monkeypatch.setattr("nw.core.project.time", lambda: 1600005600) monkeypatch.setattr("nw.core.project.time", lambda: 1600005600)
assert theProject._appendSessionStats() assert theProject._appendSessionStats(idleTime=99)
monkeypatch.undo() monkeypatch.undo()
assert readFile(statsFile) == ( assert readFile(statsFile) == (
"# Offset 100\n" "# Offset 100\n"
"# Start Time End Time Novel Notes\n" "# Start Time End Time Novel Notes Idle\n"
"%s %s 200 100\n" "%s %s 200 100 99\n"
) % (formatTimeStamp(1600002000), formatTimeStamp(1600005600)) ) % (formatTimeStamp(1600002000), formatTimeStamp(1600005600))
# Pack XML Value # Pack XML Value
+60 -60
View File
@@ -72,7 +72,7 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
# Make a test log file # Make a test log file
writeFile(sessFile, ( writeFile(sessFile, (
"# Offset 123\n" "# Offset 123\n"
"# Start Time End Time Novel Notes\n" "# Start Time End Time Novel Notes Idle\n"
"2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n" "2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n"
"2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n" "2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n"
"2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n" "2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n"
@@ -86,8 +86,8 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
# Make sure a faulty file can still be read # Make sure a faulty file can still be read
writeFile(sessFile, ( writeFile(sessFile, (
"# Offset abc123\n" "# Offset abc123\n"
"# Start Time End Time Novel Notes\n" "# Start Time End Time Novel Notes Idle\n"
"2020-01-01 21:00:00 2020-01-01 21:00:05 6 0\n" "2020-01-01 21:00:00 2020-01-01 21:00:05 6 0 50\n"
"2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n" "2020-01-03 21:00:00 2020-01-03 21:00:15 125 0\n"
"2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n" "2020-01-03 21:30:00 2020-01-03 21:30:15 125 5\n"
"2020-01-06 21:00:00 2020-01-06 21:00:10 125\n" "2020-01-06 21:00:00 2020-01-06 21:00:10 125\n"
@@ -101,17 +101,17 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
writeFile(sessFile, ( writeFile(sessFile, (
"# Offset 1075\n" "# Offset 1075\n"
"# Start Time End Time Novel Notes\n" "# Start Time End Time Novel Notes Idle\n"
"2021-01-31 19:00:00 2021-01-31 19:30:00 700 375\n" "2021-01-31 19:00:00 2021-01-31 19:30:00 700 375 0\n"
"2021-02-01 19:00:00 2021-02-01 19:30:00 700 375\n" "2021-02-01 19:00:00 2021-02-01 19:30:00 700 375 10\n"
"2021-02-01 20:00:00 2021-02-01 20:30:00 600 275\n" "2021-02-01 20:00:00 2021-02-01 20:30:00 600 275 20\n"
"2021-02-02 19:00:00 2021-02-02 19:30:00 750 425\n" "2021-02-02 19:00:00 2021-02-02 19:30:00 750 425 30\n"
"2021-02-02 20:00:00 2021-02-02 20:30:00 690 365\n" "2021-02-02 20:00:00 2021-02-02 20:30:00 690 365 40\n"
"2021-02-03 19:00:00 2021-02-03 19:30:00 680 355\n" "2021-02-03 19:00:00 2021-02-03 19:30:00 680 355 50\n"
"2021-02-04 19:00:00 2021-02-04 19:30:00 700 375\n" "2021-02-04 19:00:00 2021-02-04 19:30:00 700 375 60\n"
"2021-02-05 19:00:00 2021-02-05 19:30:00 500 175\n" "2021-02-05 19:00:00 2021-02-05 19:30:00 500 175 70\n"
"2021-02-06 19:00:00 2021-02-06 19:30:00 600 275\n" "2021-02-06 19:00:00 2021-02-06 19:30:00 600 275 80\n"
"2021-02-07 19:00:00 2021-02-07 19:30:00 600 275\n" "2021-02-07 19:00:00 2021-02-07 19:30:00 600 275 90\n"
)) ))
sessLog.populateGUI() sessLog.populateGUI()
@@ -156,28 +156,28 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert jsonData == [ assert jsonData == [
{ {
"date": "2021-01-31 19:00:00", "length": 1800.0, "date": "2021-01-31 19:00:00", "length": 1800.0,
"newWords": 1, "novelWords": 700, "noteWords": 375 "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
}, { }, {
"date": "2021-02-01 20:00:00", "length": 1800.0, "date": "2021-02-01 20:00:00", "length": 1800.0,
"newWords": -200, "novelWords": 600, "noteWords": 275 "newWords": -200, "novelWords": 600, "noteWords": 275, "idleTime": 20
}, { }, {
"date": "2021-02-02 19:00:00", "length": 1800.0, "date": "2021-02-02 19:00:00", "length": 1800.0,
"newWords": 300, "novelWords": 750, "noteWords": 425 "newWords": 300, "novelWords": 750, "noteWords": 425, "idleTime": 30
}, { }, {
"date": "2021-02-02 20:00:00", "length": 1800.0, "date": "2021-02-02 20:00:00", "length": 1800.0,
"newWords": -120, "novelWords": 690, "noteWords": 365 "newWords": -120, "novelWords": 690, "noteWords": 365, "idleTime": 40
}, { }, {
"date": "2021-02-03 19:00:00", "length": 1800.0, "date": "2021-02-03 19:00:00", "length": 1800.0,
"newWords": -20, "novelWords": 680, "noteWords": 355 "newWords": -20, "novelWords": 680, "noteWords": 355, "idleTime": 50
}, { }, {
"date": "2021-02-04 19:00:00", "length": 1800.0, "date": "2021-02-04 19:00:00", "length": 1800.0,
"newWords": 40, "novelWords": 700, "noteWords": 375 "newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60
}, { }, {
"date": "2021-02-05 19:00:00", "length": 1800.0, "date": "2021-02-05 19:00:00", "length": 1800.0,
"newWords": -400, "novelWords": 500, "noteWords": 175 "newWords": -400, "novelWords": 500, "noteWords": 175, "idleTime": 70
}, { }, {
"date": "2021-02-06 19:00:00", "length": 1800.0, "date": "2021-02-06 19:00:00", "length": 1800.0,
"newWords": 200, "novelWords": 600, "noteWords": 275 "newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80
} }
] ]
@@ -206,28 +206,28 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert jsonData == [ assert jsonData == [
{ {
"date": "2021-01-31 19:00:00", "length": 1800.0, "date": "2021-01-31 19:00:00", "length": 1800.0,
"newWords": 1, "novelWords": 700, "noteWords": 375 "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
}, { }, {
"date": "2021-02-01 20:00:00", "length": 1800.0, "date": "2021-02-01 20:00:00", "length": 1800.0,
"newWords": -100, "novelWords": 600, "noteWords": 275 "newWords": -100, "novelWords": 600, "noteWords": 275, "idleTime": 20
}, { }, {
"date": "2021-02-02 19:00:00", "length": 1800.0, "date": "2021-02-02 19:00:00", "length": 1800.0,
"newWords": 150, "novelWords": 750, "noteWords": 425 "newWords": 150, "novelWords": 750, "noteWords": 425, "idleTime": 30
}, { }, {
"date": "2021-02-02 20:00:00", "length": 1800.0, "date": "2021-02-02 20:00:00", "length": 1800.0,
"newWords": -60, "novelWords": 690, "noteWords": 365 "newWords": -60, "novelWords": 690, "noteWords": 365, "idleTime": 40
}, { }, {
"date": "2021-02-03 19:00:00", "length": 1800.0, "date": "2021-02-03 19:00:00", "length": 1800.0,
"newWords": -10, "novelWords": 680, "noteWords": 355 "newWords": -10, "novelWords": 680, "noteWords": 355, "idleTime": 50
}, { }, {
"date": "2021-02-04 19:00:00", "length": 1800.0, "date": "2021-02-04 19:00:00", "length": 1800.0,
"newWords": 20, "novelWords": 700, "noteWords": 375 "newWords": 20, "novelWords": 700, "noteWords": 375, "idleTime": 60
}, { }, {
"date": "2021-02-05 19:00:00", "length": 1800.0, "date": "2021-02-05 19:00:00", "length": 1800.0,
"newWords": -200, "novelWords": 500, "noteWords": 175 "newWords": -200, "novelWords": 500, "noteWords": 175, "idleTime": 70
}, { }, {
"date": "2021-02-06 19:00:00", "length": 1800.0, "date": "2021-02-06 19:00:00", "length": 1800.0,
"newWords": 100, "novelWords": 600, "noteWords": 275 "newWords": 100, "novelWords": 600, "noteWords": 275, "idleTime": 80
} }
] ]
@@ -254,28 +254,28 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert jsonData == [ assert jsonData == [
{ {
"date": "2021-01-31 19:00:00", "length": 1800.0, "date": "2021-01-31 19:00:00", "length": 1800.0,
"newWords": 1, "novelWords": 700, "noteWords": 375 "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
}, { }, {
"date": "2021-02-01 20:00:00", "length": 1800.0, "date": "2021-02-01 20:00:00", "length": 1800.0,
"newWords": -100, "novelWords": 600, "noteWords": 275 "newWords": -100, "novelWords": 600, "noteWords": 275, "idleTime": 20
}, { }, {
"date": "2021-02-02 19:00:00", "length": 1800.0, "date": "2021-02-02 19:00:00", "length": 1800.0,
"newWords": 150, "novelWords": 750, "noteWords": 425 "newWords": 150, "novelWords": 750, "noteWords": 425, "idleTime": 30
}, { }, {
"date": "2021-02-02 20:00:00", "length": 1800.0, "date": "2021-02-02 20:00:00", "length": 1800.0,
"newWords": -60, "novelWords": 690, "noteWords": 365 "newWords": -60, "novelWords": 690, "noteWords": 365, "idleTime": 40
}, { }, {
"date": "2021-02-03 19:00:00", "length": 1800.0, "date": "2021-02-03 19:00:00", "length": 1800.0,
"newWords": -10, "novelWords": 680, "noteWords": 355 "newWords": -10, "novelWords": 680, "noteWords": 355, "idleTime": 50
}, { }, {
"date": "2021-02-04 19:00:00", "length": 1800.0, "date": "2021-02-04 19:00:00", "length": 1800.0,
"newWords": 20, "novelWords": 700, "noteWords": 375 "newWords": 20, "novelWords": 700, "noteWords": 375, "idleTime": 60
}, { }, {
"date": "2021-02-05 19:00:00", "length": 1800.0, "date": "2021-02-05 19:00:00", "length": 1800.0,
"newWords": -200, "novelWords": 500, "noteWords": 175 "newWords": -200, "novelWords": 500, "noteWords": 175, "idleTime": 70
}, { }, {
"date": "2021-02-06 19:00:00", "length": 1800.0, "date": "2021-02-06 19:00:00", "length": 1800.0,
"newWords": 100, "novelWords": 600, "noteWords": 275 "newWords": 100, "novelWords": 600, "noteWords": 275, "idleTime": 80
} }
] ]
@@ -300,16 +300,16 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert jsonData == [ assert jsonData == [
{ {
"date": "2021-01-31 19:00:00", "length": 1800.0, "date": "2021-01-31 19:00:00", "length": 1800.0,
"newWords": 1, "novelWords": 700, "noteWords": 375 "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
}, { }, {
"date": "2021-02-02 19:00:00", "length": 1800.0, "date": "2021-02-02 19:00:00", "length": 1800.0,
"newWords": 300, "novelWords": 750, "noteWords": 425 "newWords": 300, "novelWords": 750, "noteWords": 425, "idleTime": 30
}, { }, {
"date": "2021-02-04 19:00:00", "length": 1800.0, "date": "2021-02-04 19:00:00", "length": 1800.0,
"newWords": 40, "novelWords": 700, "noteWords": 375 "newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60
}, { }, {
"date": "2021-02-06 19:00:00", "length": 1800.0, "date": "2021-02-06 19:00:00", "length": 1800.0,
"newWords": 200, "novelWords": 600, "noteWords": 275 "newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80
} }
] ]
@@ -338,34 +338,34 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert jsonData == [ assert jsonData == [
{ {
"date": "2021-01-31 19:00:00", "length": 1800.0, "date": "2021-01-31 19:00:00", "length": 1800.0,
"newWords": 1, "novelWords": 700, "noteWords": 375 "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 0
}, { }, {
"date": "2021-02-01 19:00:00", "length": 1800.0, "date": "2021-02-01 19:00:00", "length": 1800.0,
"newWords": 0, "novelWords": 700, "noteWords": 375 "newWords": 0, "novelWords": 700, "noteWords": 375, "idleTime": 10
}, { }, {
"date": "2021-02-01 20:00:00", "length": 1800.0, "date": "2021-02-01 20:00:00", "length": 1800.0,
"newWords": -200, "novelWords": 600, "noteWords": 275 "newWords": -200, "novelWords": 600, "noteWords": 275, "idleTime": 20
}, { }, {
"date": "2021-02-02 19:00:00", "length": 1800.0, "date": "2021-02-02 19:00:00", "length": 1800.0,
"newWords": 300, "novelWords": 750, "noteWords": 425 "newWords": 300, "novelWords": 750, "noteWords": 425, "idleTime": 30
}, { }, {
"date": "2021-02-02 20:00:00", "length": 1800.0, "date": "2021-02-02 20:00:00", "length": 1800.0,
"newWords": -120, "novelWords": 690, "noteWords": 365 "newWords": -120, "novelWords": 690, "noteWords": 365, "idleTime": 40
}, { }, {
"date": "2021-02-03 19:00:00", "length": 1800.0, "date": "2021-02-03 19:00:00", "length": 1800.0,
"newWords": -20, "novelWords": 680, "noteWords": 355 "newWords": -20, "novelWords": 680, "noteWords": 355, "idleTime": 50
}, { }, {
"date": "2021-02-04 19:00:00", "length": 1800.0, "date": "2021-02-04 19:00:00", "length": 1800.0,
"newWords": 40, "novelWords": 700, "noteWords": 375 "newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 60
}, { }, {
"date": "2021-02-05 19:00:00", "length": 1800.0, "date": "2021-02-05 19:00:00", "length": 1800.0,
"newWords": -400, "novelWords": 500, "noteWords": 175 "newWords": -400, "novelWords": 500, "noteWords": 175, "idleTime": 70
}, { }, {
"date": "2021-02-06 19:00:00", "length": 1800.0, "date": "2021-02-06 19:00:00", "length": 1800.0,
"newWords": 200, "novelWords": 600, "noteWords": 275 "newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 80
}, { }, {
"date": "2021-02-07 19:00:00", "length": 1800.0, "date": "2021-02-07 19:00:00", "length": 1800.0,
"newWords": 0, "novelWords": 600, "noteWords": 275 "newWords": 0, "novelWords": 600, "noteWords": 275, "idleTime": 90
} }
] ]
@@ -391,28 +391,28 @@ def testGuiWritingStats_Dialog(qtbot, monkeypatch, nwGUI, fncDir, fncProj):
assert jsonData == [ assert jsonData == [
{ {
"date": "2021-01-31", "length": 1800.0, "date": "2021-01-31", "length": 1800.0,
"newWords": 1, "novelWords": 700, "noteWords": 375 "newWords": 1, "novelWords": 700, "noteWords": 375, "idleTime": 10
}, { }, {
"date": "2021-02-01", "length": 3600.0, "date": "2021-02-01", "length": 3600.0,
"newWords": -200, "novelWords": 600, "noteWords": 275 "newWords": -200, "novelWords": 600, "noteWords": 275, "idleTime": 30
}, { }, {
"date": "2021-02-02", "length": 3600.0, "date": "2021-02-02", "length": 3600.0,
"newWords": 180, "novelWords": 690, "noteWords": 365 "newWords": 180, "novelWords": 690, "noteWords": 365, "idleTime": 50
}, { }, {
"date": "2021-02-03", "length": 1800.0, "date": "2021-02-03", "length": 1800.0,
"newWords": -20, "novelWords": 680, "noteWords": 355 "newWords": -20, "novelWords": 680, "noteWords": 355, "idleTime": 60
}, { }, {
"date": "2021-02-04", "length": 1800.0, "date": "2021-02-04", "length": 1800.0,
"newWords": 40, "novelWords": 700, "noteWords": 375 "newWords": 40, "novelWords": 700, "noteWords": 375, "idleTime": 70
}, { }, {
"date": "2021-02-05", "length": 1800.0, "date": "2021-02-05", "length": 1800.0,
"newWords": -400, "novelWords": 500, "noteWords": 175 "newWords": -400, "novelWords": 500, "noteWords": 175, "idleTime": 80
}, { }, {
"date": "2021-02-06", "length": 1800.0, "date": "2021-02-06", "length": 1800.0,
"newWords": 200, "novelWords": 600, "noteWords": 275 "newWords": 200, "novelWords": 600, "noteWords": 275, "idleTime": 90
}, { }, {
"date": "2021-02-07", "length": 1800.0, "date": "2021-02-07", "length": 1800.0,
"newWords": 0, "novelWords": 600, "noteWords": 275 "newWords": 0, "novelWords": 600, "noteWords": 275, "idleTime": 90
} }
] ]