예제 #1
0
 def updateTime(self, idleTime=0.0):
     """Update the session clock.
     """
     if self.refTime is None:
         self.timeText.setText("00:00:00")
     else:
         if self.mainConf.stopWhenIdle:
             sessTime = round(time() - self.refTime - idleTime)
         else:
             sessTime = round(time() - self.refTime)
         self.timeText.setText(formatTime(sessTime))
     return
예제 #2
0
def testBaseCommon_FormatTime():
    """Test the formatTime function.
    """
    assert formatTime("1") == "ERROR"
    assert formatTime(1.0) == "ERROR"
    assert formatTime(1) == "00:00:01"
    assert formatTime(59) == "00:00:59"
    assert formatTime(60) == "00:01:00"
    assert formatTime(180) == "00:03:00"
    assert formatTime(194) == "00:03:14"
    assert formatTime(3540) == "00:59:00"
    assert formatTime(3599) == "00:59:59"
    assert formatTime(3600) == "01:00:00"
    assert formatTime(11640) == "03:14:00"
    assert formatTime(11655) == "03:14:15"
    assert formatTime(86399) == "23:59:59"
    assert formatTime(86400) == "1-00:00:00"
    assert formatTime(360000) == "4-04:00:00"
예제 #3
0
    def _updateListBox(self):
        """Load/reload the content of the list box.
        """
        self.listBox.clear()
        self.timeFilter = 0.0

        incNovel = self.incNovel.isChecked()
        incNotes = self.incNotes.isChecked()
        hideZeros = self.hideZeros.isChecked()
        hideNegative = self.hideNegative.isChecked()
        groupByDay = self.groupByDay.isChecked()
        histMax = self.histMax.value()

        # Group the data
        if groupByDay:
            tempData = []
            sessDate = None
            sessTime = 0
            sessIdle = 0
            lstNovel = 0
            lstNotes = 0

            for n, (dStart, sDiff, wcNovel, wcNotes,
                    sIdle) in enumerate(self.logData):
                if n == 0:
                    sessDate = dStart.date()
                if sessDate != dStart.date():
                    tempData.append(
                        (sessDate, sessTime, lstNovel, lstNotes, sessIdle))
                    sessDate = dStart.date()
                    sessTime = sDiff
                    sessIdle = sIdle
                    lstNovel = wcNovel
                    lstNotes = wcNotes
                else:
                    sessTime += sDiff
                    sessIdle += sIdle
                    lstNovel = wcNovel
                    lstNotes = wcNotes

            if sessDate is not None:
                tempData.append(
                    (sessDate, sessTime, lstNovel, lstNotes, sessIdle))

        else:
            tempData = self.logData

        # Calculate Word Diff
        self.filterData = []
        pcTotal = 0
        listMax = 0
        isFirst = True
        for dStart, sDiff, wcNovel, wcNotes, sIdle in tempData:

            wcTotal = 0
            if incNovel:
                wcTotal += wcNovel
            if incNotes:
                wcTotal += wcNotes

            dwTotal = wcTotal - pcTotal
            if hideZeros and dwTotal == 0:
                continue
            if hideNegative and dwTotal < 0:
                pcTotal = wcTotal
                continue

            if isFirst:
                # Subtract the offset from the first list entry
                dwTotal -= self.wordOffset
                dwTotal = max(dwTotal, 1)  # Don't go zero or negative
                isFirst = False

            if groupByDay:
                sStart = dStart.strftime(nwConst.FMT_DSTAMP)
            else:
                sStart = dStart.strftime(nwConst.FMT_TSTAMP)

            self.filterData.append(
                (dStart, sStart, sDiff, dwTotal, wcNovel, wcNotes, sIdle))
            listMax = min(max(listMax, dwTotal), histMax)
            pcTotal = wcTotal

        # Populate the list
        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.setText(self.C_TIME, sStart)
            newItem.setText(self.C_LENGTH, formatTime(round(sDiff)))
            newItem.setText(self.C_IDLE, idleEntry)
            newItem.setText(self.C_COUNT, f"{nWords:n}")

            if nWords > 0 and listMax > 0:
                theBar = self.barImage.scaled(
                    int(200 * min(nWords, histMax) / listMax), self.barHeight,
                    Qt.IgnoreAspectRatio, Qt.FastTransformation)
                newItem.setData(self.C_BAR, Qt.DecorationRole, theBar)

            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_BAR,
                                     Qt.AlignLeft | Qt.AlignVCenter)

            newItem.setFont(self.C_TIME, self.theTheme.guiFontFixed)
            newItem.setFont(self.C_LENGTH, 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.timeFilter += sDiff

        self.labelFilter.setText(formatTime(round(self.timeFilter)))

        return True
예제 #4
0
    def __init__(self, theParent):
        QDialog.__init__(self, theParent)

        logger.debug("Initialising GuiWritingStats ...")
        self.setObjectName("GuiWritingStats")

        self.mainConf = novelwriter.CONFIG
        self.theParent = theParent
        self.theTheme = theParent.theTheme
        self.theProject = theParent.theProject
        self.optState = theParent.theProject.optState

        self.logData = []
        self.filterData = []
        self.timeFilter = 0.0
        self.wordOffset = 0

        self.setWindowTitle(self.tr("Writing Statistics"))
        self.setMinimumWidth(self.mainConf.pxInt(420))
        self.setMinimumHeight(self.mainConf.pxInt(400))
        self.resize(
            self.mainConf.pxInt(
                self.optState.getInt("GuiWritingStats", "winWidth", 550)),
            self.mainConf.pxInt(
                self.optState.getInt("GuiWritingStats", "winHeight", 500)))

        # List Box
        wCol0 = self.mainConf.pxInt(
            self.optState.getInt("GuiWritingStats", "widthCol0", 180))
        wCol1 = self.mainConf.pxInt(
            self.optState.getInt("GuiWritingStats", "widthCol1", 80))
        wCol2 = self.mainConf.pxInt(
            self.optState.getInt("GuiWritingStats", "widthCol2", 80))
        wCol3 = self.mainConf.pxInt(
            self.optState.getInt("GuiWritingStats", "widthCol3", 80))

        self.listBox = QTreeWidget()
        self.listBox.setHeaderLabels([
            self.tr("Session Start"),
            self.tr("Length"),
            self.tr("Idle"),
            self.tr("Words"),
            self.tr("Histogram"),
        ])
        self.listBox.setIndentation(0)
        self.listBox.setColumnWidth(self.C_TIME, wCol0)
        self.listBox.setColumnWidth(self.C_LENGTH, wCol1)
        self.listBox.setColumnWidth(self.C_IDLE, wCol2)
        self.listBox.setColumnWidth(self.C_COUNT, wCol3)

        hHeader = self.listBox.headerItem()
        hHeader.setTextAlignment(self.C_LENGTH, Qt.AlignRight)
        hHeader.setTextAlignment(self.C_IDLE, Qt.AlignRight)
        hHeader.setTextAlignment(self.C_COUNT, Qt.AlignRight)

        sortCol = checkIntRange(
            self.optState.getInt("GuiWritingStats", "sortCol", 0), 0, 2, 0)
        sortOrder = checkIntTuple(
            self.optState.getInt("GuiWritingStats", "sortOrder",
                                 Qt.DescendingOrder),
            (Qt.AscendingOrder, Qt.DescendingOrder), Qt.DescendingOrder)
        self.listBox.sortByColumn(sortCol, sortOrder)
        self.listBox.setSortingEnabled(True)

        # Word Bar
        self.barHeight = int(round(0.5 * self.theTheme.fontPixelSize))
        self.barWidth = self.mainConf.pxInt(200)
        self.barImage = QPixmap(self.barHeight, self.barHeight)
        self.barImage.fill(self.palette().highlight().color())

        # Session Info
        self.infoBox = QGroupBox(self.tr("Sum Totals"), self)
        self.infoForm = QGridLayout(self)
        self.infoBox.setLayout(self.infoForm)

        self.labelTotal = QLabel(formatTime(0))
        self.labelTotal.setFont(self.theTheme.guiFontFixed)
        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.setFont(self.theTheme.guiFontFixed)
        self.labelFilter.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        self.novelWords = QLabel("0")
        self.novelWords.setFont(self.theTheme.guiFontFixed)
        self.novelWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        self.notesWords = QLabel("0")
        self.notesWords.setFont(self.theTheme.guiFontFixed)
        self.notesWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        self.totalWords = QLabel("0")
        self.totalWords.setFont(self.theTheme.guiFontFixed)
        self.totalWords.setAlignment(Qt.AlignVCenter | Qt.AlignRight)

        lblTTime = QLabel(self.tr("Total Time:"))
        lblITime = QLabel(self.tr("Idle Time:"))
        lblFTime = QLabel(self.tr("Filtered Time:"))
        lblNvCount = QLabel(self.tr("Novel Word Count:"))
        lblNtCount = QLabel(self.tr("Notes Word Count:"))
        lblTtCount = QLabel(self.tr("Total Word Count:"))

        self.infoForm.addWidget(lblTTime, 0, 0)
        self.infoForm.addWidget(lblITime, 1, 0)
        self.infoForm.addWidget(lblFTime, 2, 0)
        self.infoForm.addWidget(lblNvCount, 3, 0)
        self.infoForm.addWidget(lblNtCount, 4, 0)
        self.infoForm.addWidget(lblTtCount, 5, 0)

        self.infoForm.addWidget(self.labelTotal, 0, 1)
        self.infoForm.addWidget(self.labelIdleT, 1, 1)
        self.infoForm.addWidget(self.labelFilter, 2, 1)
        self.infoForm.addWidget(self.novelWords, 3, 1)
        self.infoForm.addWidget(self.notesWords, 4, 1)
        self.infoForm.addWidget(self.totalWords, 5, 1)

        self.infoForm.setRowStretch(6, 1)

        # Filter Options
        sPx = self.theTheme.baseIconSize

        self.filterBox = QGroupBox(self.tr("Filters"), self)
        self.filterForm = QGridLayout(self)
        self.filterBox.setLayout(self.filterForm)

        self.incNovel = QSwitch(width=2 * sPx, height=sPx)
        self.incNovel.setChecked(
            self.optState.getBool("GuiWritingStats", "incNovel", True))
        self.incNovel.clicked.connect(self._updateListBox)

        self.incNotes = QSwitch(width=2 * sPx, height=sPx)
        self.incNotes.setChecked(
            self.optState.getBool("GuiWritingStats", "incNotes", True))
        self.incNotes.clicked.connect(self._updateListBox)

        self.hideZeros = QSwitch(width=2 * sPx, height=sPx)
        self.hideZeros.setChecked(
            self.optState.getBool("GuiWritingStats", "hideZeros", True))
        self.hideZeros.clicked.connect(self._updateListBox)

        self.hideNegative = QSwitch(width=2 * sPx, height=sPx)
        self.hideNegative.setChecked(
            self.optState.getBool("GuiWritingStats", "hideNegative", False))
        self.hideNegative.clicked.connect(self._updateListBox)

        self.groupByDay = QSwitch(width=2 * sPx, height=sPx)
        self.groupByDay.setChecked(
            self.optState.getBool("GuiWritingStats", "groupByDay", False))
        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(self.tr("Count novel files")), 0, 0)
        self.filterForm.addWidget(QLabel(self.tr("Count note files")), 1, 0)
        self.filterForm.addWidget(QLabel(self.tr("Hide zero word count")), 2,
                                  0)
        self.filterForm.addWidget(QLabel(self.tr("Hide negative word count")),
                                  3, 0)
        self.filterForm.addWidget(QLabel(self.tr("Group entries by day")), 4,
                                  0)
        self.filterForm.addWidget(QLabel(self.tr("Show idle time")), 5, 0)
        self.filterForm.addWidget(self.incNovel, 0, 1)
        self.filterForm.addWidget(self.incNotes, 1, 1)
        self.filterForm.addWidget(self.hideZeros, 2, 1)
        self.filterForm.addWidget(self.hideNegative, 3, 1)
        self.filterForm.addWidget(self.groupByDay, 4, 1)
        self.filterForm.addWidget(self.showIdleTime, 5, 1)
        self.filterForm.setRowStretch(6, 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(self.tr("Word count cap for the histogram")), 0)
        self.optsBox.addWidget(self.histMax, 0)

        # Buttons
        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(self.tr("Save As"),
                                                QDialogButtonBox.ActionRole)
        self.btnSave.setAutoDefault(False)

        self.saveMenu = QMenu(self)
        self.btnSave.setMenu(self.saveMenu)

        self.saveJSON = QAction(self.tr("JSON Data File (.json)"), self)
        self.saveJSON.triggered.connect(lambda: self._saveData(self.FMT_JSON))
        self.saveMenu.addAction(self.saveJSON)

        self.saveCSV = QAction(self.tr("CSV Data File (.csv)"), self)
        self.saveCSV.triggered.connect(lambda: self._saveData(self.FMT_CSV))
        self.saveMenu.addAction(self.saveCSV)

        # Assemble
        self.outerBox = QGridLayout()
        self.outerBox.addWidget(self.listBox, 0, 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)

        logger.debug("GuiWritingStats initialisation complete")

        return
예제 #5
0
    def _loadLogFile(self):
        """Load the content of the log file into a buffer.
        """
        logger.debug("Loading session log file")

        self.logData = []
        self.wordOffset = 0

        ttNovel = 0
        ttNotes = 0
        ttTime = 0
        ttIdle = 0

        logFile = os.path.join(self.theProject.projMeta, nwFiles.SESS_STATS)
        if not os.path.isfile(logFile):
            logger.info("This project has no writing stats logfile")
            return False

        try:
            with open(logFile, mode="r", encoding="utf-8") as inFile:
                for inLine in inFile:
                    if inLine.startswith("#"):
                        if inLine.startswith("# Offset"):
                            self.wordOffset = checkInt(inLine[9:].strip(), 0)
                            logger.verbose(
                                "Initial word count when log was started is %d"
                                % self.wordOffset)
                        continue

                    inData = inLine.split()
                    if len(inData) < 6:
                        continue

                    dStart = datetime.fromisoformat(" ".join(inData[0:2]))
                    dEnd = datetime.fromisoformat(" ".join(inData[2:4]))

                    sIdle = 0
                    if len(inData) > 6:
                        sIdle = checkInt(inData[6], 0)

                    tDiff = dEnd - dStart
                    sDiff = tDiff.total_seconds()
                    ttTime += sDiff
                    ttIdle += sIdle

                    wcNovel = int(inData[4])
                    wcNotes = int(inData[5])
                    ttNovel = wcNovel
                    ttNotes = wcNotes

                    self.logData.append(
                        (dStart, sDiff, wcNovel, wcNotes, sIdle))

        except Exception as exc:
            self.theParent.makeAlert(
                self.tr("Failed to read session log file."),
                nwAlert.ERROR,
                exception=exc)
            return False

        ttWords = ttNovel + ttNotes
        self.labelTotal.setText(formatTime(round(ttTime)))
        self.labelIdleT.setText(formatTime(round(ttIdle)))
        self.novelWords.setText(f"{ttNovel:n}")
        self.notesWords.setText(f"{ttNotes:n}")
        self.totalWords.setText(f"{ttWords:n}")

        return True