diff --git a/docs/source/projects.rst b/docs/source/projects.rst index dbf90b6c..cc3808ec 100644 --- a/docs/source/projects.rst +++ b/docs/source/projects.rst @@ -277,3 +277,20 @@ Settings`. For the backup to be able to run, the :guilabel:`Working Title` must be set in :guilabel:`Project Settings`. This value is used to generate the folder name for the zip files. Without it, the backup will not run at all, but produce a warning message. + +.. _a_proj_stats: + +Writing Statistics +================== + +When you work on your project, a log file records when you opened it, when you closed it, and how +many words you added to your novel and note files during the session. You can view this file in the +``meta`` folder in the directory where you saved your project. The file is named +``sessionStats.log``. + +A small tool to view the content of this file is available in the :guilabel:`Tools` menu under +:guilabel:`Writing Statistics`. You can also launch it by pressing :kbd:`F6`. + +The tool will show a list of all your sessions, and a set of filters to apply to it. You can also +export the filtered data to a JSON file or to a CSV file that can be opened by a spreadsheet +application like for instance Libre Office Calc. diff --git a/nw/core/options.py b/nw/core/options.py index 3bdf88f3..a91f6f68 100644 --- a/nw/core/options.py +++ b/nw/core/options.py @@ -58,6 +58,7 @@ class OptionState(): "hideZeros", "hideNegative", "groupByDay", + "histMax", }, "GuiDocSplit": { "spLevel", diff --git a/nw/gui/writingstats.py b/nw/gui/writingstats.py index 40df85c9..6e4a4df8 100644 --- a/nw/gui/writingstats.py +++ b/nw/gui/writingstats.py @@ -36,7 +36,7 @@ from PyQt5.QtCore import Qt from PyQt5.QtGui import QFont, QPixmap from PyQt5.QtWidgets import ( qApp, QDialog, QTreeWidget, QTreeWidgetItem, QDialogButtonBox, QGridLayout, - QLabel, QGroupBox, QMenu, QAction, QFileDialog + QLabel, QGroupBox, QMenu, QAction, QFileDialog, QSpinBox, QHBoxLayout ) from nw.constants import nwConst, nwFiles, nwAlert @@ -207,11 +207,31 @@ class GuiWritingStats(QDialog): self.filterForm.addWidget(self.groupByDay, 4, 1) self.filterForm.setRowStretch(5, 1) + # Settings + self.histMax = QSpinBox(self) + self.histMax.setMinimum(100) + self.histMax.setMaximum(100000) + self.histMax.setSingleStep(100) + self.histMax.setValue( + self.optState.getInt("GuiWritingStats", "histMax", 2000) + ) + self.histMax.valueChanged.connect(self._updateListBox) + + self.optsBox = QHBoxLayout() + self.optsBox.addStretch(1) + self.optsBox.addWidget(QLabel("Word count cap for the histogram"), 0) + self.optsBox.addWidget(self.histMax, 0) + # Buttons - self.buttonBox = QDialogButtonBox(QDialogButtonBox.Close) + self.buttonBox = QDialogButtonBox() self.buttonBox.rejected.connect(self._doClose) + self.btnClose = self.buttonBox.addButton(QDialogButtonBox.Close) + self.btnClose.setAutoDefault(False) + self.btnSave = self.buttonBox.addButton("Save As", QDialogButtonBox.ActionRole) + self.btnSave.setAutoDefault(False) + self.saveMenu = QMenu(self) self.btnSave.setMenu(self.saveMenu) @@ -226,9 +246,10 @@ class GuiWritingStats(QDialog): # Assemble self.outerBox = QGridLayout() self.outerBox.addWidget(self.listBox, 0, 0, 1, 2) - self.outerBox.addWidget(self.infoBox, 1, 0) - self.outerBox.addWidget(self.filterBox, 1, 1) - self.outerBox.addWidget(self.buttonBox, 2, 0, 1, 2) + self.outerBox.addLayout(self.optsBox, 1, 0, 1, 2) + self.outerBox.addWidget(self.infoBox, 2, 0) + self.outerBox.addWidget(self.filterBox, 2, 1) + self.outerBox.addWidget(self.buttonBox, 3, 0, 1, 2) self.outerBox.setRowStretch(0, 1) self.setLayout(self.outerBox) @@ -262,6 +283,7 @@ class GuiWritingStats(QDialog): hideZeros = self.hideZeros.isChecked() hideNegative = self.hideNegative.isChecked() groupByDay = self.groupByDay.isChecked() + histMax = self.histMax.value() self.optState.setValue("GuiWritingStats", "winWidth", winWidth) self.optState.setValue("GuiWritingStats", "winHeight", winHeight) @@ -275,6 +297,7 @@ class GuiWritingStats(QDialog): self.optState.setValue("GuiWritingStats", "hideZeros", hideZeros) self.optState.setValue("GuiWritingStats", "hideNegative", hideNegative) self.optState.setValue("GuiWritingStats", "groupByDay", groupByDay) + self.optState.setValue("GuiWritingStats", "histMax", histMax) self.optState.saveSettings() self.close() @@ -438,8 +461,10 @@ class GuiWritingStats(QDialog): return True - def _updateListBox(self): - """Load/reload the content of the list box. + def _updateListBox(self, dummyVar=None): + """Load/reload the content of the list box. The dummyVar + variable captures the variable sent from the widgets connecting + to it and discards it. """ self.listBox.clear() self.timeFilter = 0.0 @@ -449,6 +474,7 @@ class GuiWritingStats(QDialog): hideZeros = self.hideZeros.isChecked() hideNegative = self.hideNegative.isChecked() groupByDay = self.groupByDay.isChecked() + histMax = self.histMax.value() # Group the data if groupByDay: @@ -509,7 +535,7 @@ class GuiWritingStats(QDialog): sStart = dStart.strftime(nwConst.tStampFmt) self.filterData.append((dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes)) - listMax = max(listMax, dwTotal) + listMax = min(max(listMax, dwTotal), histMax) pcTotal = wcTotal # Populate the list @@ -522,7 +548,7 @@ class GuiWritingStats(QDialog): if nWords > 0 and listMax > 0: theBar = self.barImage.scaled( - int(200*nWords/listMax), + int(200*min(nWords, histMax)/listMax), self.barHeight, Qt.IgnoreAspectRatio, Qt.FastTransformation