Implemented a working word and char counter that runs in the background at regular intervals.

This commit is contained in:
Veronica K. B. Olsen
2019-04-22 14:15:28 +02:00
parent 7c2eff8b9b
commit 03d1cee2b3
3 changed files with 127 additions and 65 deletions
+1
View File
@@ -66,6 +66,7 @@ class Config:
self.doReplaceDash = True self.doReplaceDash = True
self.doReplaceDots = True self.doReplaceDots = True
self.replaceQuotes = ["",""] self.replaceQuotes = ["",""]
self.wordCountTimer = 5.0
# Check if config file exists # Check if config file exists
if path.isfile(path.join(self.confPath,self.confFile)): if path.isfile(path.join(self.confPath,self.confFile)):
+53 -65
View File
@@ -16,10 +16,11 @@ import nw
from time import time from time import time
from PyQt5.QtWidgets import QWidget, QTextEdit, QHBoxLayout, QVBoxLayout, QFrame, QSplitter, QToolBar, QAction, QScrollArea from PyQt5.QtWidgets import QWidget, QTextEdit, QHBoxLayout, QVBoxLayout, QFrame, QSplitter, QToolBar, QAction, QScrollArea
from PyQt5.QtCore import Qt, QSize, QSizeF from PyQt5.QtCore import Qt, QSize, QSizeF, QTimer, QThread, pyqtSignal
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QTextFormat, QTextBlockFormat from PyQt5.QtGui import QIcon, QFont, QTextCursor, QTextFormat, QTextBlockFormat
from nw.gui.dochighlight import GuiDocHighlighter from nw.gui.dochighlight import GuiDocHighlighter
from nw.gui.wordcounter import WordCounter
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
@@ -34,12 +35,7 @@ class GuiDocEditor(QWidget):
self.charCount = 0 self.charCount = 0
self.wordCount = 0 self.wordCount = 0
self.lineCount = 0 self.lineCount = 0
self.lastEdit = 0
# Internal Temps
self.blockWords = {}
self.currEditBlock = -1
self.currEditStart = 0
self.currEditFinal = 0
self.outerBox = QVBoxLayout() self.outerBox = QVBoxLayout()
self.guiEditor = QTextEdit() self.guiEditor = QTextEdit()
@@ -68,30 +64,28 @@ class GuiDocEditor(QWidget):
self.theDoc.setDocumentMargin(0) self.theDoc.setDocumentMargin(0)
self.theDoc.contentsChange.connect(self._docChange) self.theDoc.contentsChange.connect(self._docChange)
# Set Up Word Count Thread and Timer
self.wcInterval = self.mainConf.wordCountTimer
self.wcTimer = QTimer()
self.wcTimer.setInterval(int(self.wcInterval*1000))
self.wcTimer.timeout.connect(self._runCounter)
self.wCounter = WordCounter(self)
self.wCounter.finished.connect(self._updateCounts)
logger.debug("DocEditor initialisation complete") logger.debug("DocEditor initialisation complete")
return return
##
# Class Methods
##
def setText(self, theText): def setText(self, theText):
self.guiEditor.setPlainText(theText) self.guiEditor.setPlainText(theText)
self.lastEdit = time()
self.charCount = self.theDoc.characterCount() self._runCounter()
self.wordCount = 0 self.wcTimer.start()
self.currEditBlock = -1
self.currEditWords = 0
tStart = time()
self.blockWords = {}
for n in range(self.theDoc.blockCount()):
self._countWords(self.theDoc.findBlockByNumber(n))
self.wordCount = sum(self.blockWords.values())
logger.verbose("Doc word count took %.3f µs" % ((time()-tStart)*1e6))
self.theParent.statusBar.setCharCount(self.charCount)
self.theParent.statusBar.setWordCount(self.wordCount)
return True return True
def getText(self): def getText(self):
@@ -110,27 +104,22 @@ class GuiDocEditor(QWidget):
self.guiEditor.setViewportMargins(tM,mTB,0,mTB) self.guiEditor.setViewportMargins(tM,mTB,0,mTB)
return return
##
# Document Events and Maintenance
##
def _docChange(self, thePos, charsRemoved, charsAdded): def _docChange(self, thePos, charsRemoved, charsAdded):
self.lastEdit = time()
tStart = time() if not self.wcTimer.isActive():
self.wcTimer.start()
self.charCount = self.theDoc.characterCount()
self.lineCount = self.theDoc.lineCount()
self.theParent.statusBar.setCharCount(self.charCount)
currBlock = self.theDoc.findBlock(thePos)
self._countWords(currBlock)
self.wordCount = sum(self.blockWords.values())
self.theParent.statusBar.setWordCount(self.wordCount)
if self.mainConf.doReplace: if self.mainConf.doReplace:
self._docAutoReplace(currBlock) self._docAutoReplace(self.theDoc.findBlock(thePos))
logger.verbose("Doc change signal took %.3f µs" % ((time()-self.lastEdit)*1e6))
logger.verbose("Doc change signal took %.3f µs" % ((time()-tStart)*1e6))
return return
def _docAutoReplace(self, theBlock): def _docAutoReplace(self, theBlock):
"""Autoreplace text elements based on main configuration.
"""
if not theBlock.isValid(): if not theBlock.isValid():
return return
@@ -177,34 +166,33 @@ class GuiDocEditor(QWidget):
return return
def _countWords(self, theBlock): def _runCounter(self):
"""Count the number of words in a given block, but only if it's a text block. I.e. we skip """Decide whether to run the word counter, or stop the timer due to inactivity.
blocks starting with @ and %, and we also skip the ### in titles. Dashes are replaced with
spaces before the count to ensure, for instance, 'word1—word2' is counted as two words.
""" """
sinceActive = time()-self.lastEdit
if not theBlock.isValid(): if sinceActive > 5*self.wcInterval:
return logger.verbose("Stopping word count timer due to no activity over the last %.3f seconds" % sinceActive)
self.wcTimer.stop()
theText = theBlock.text() elif self.wCounter.isRunning():
theLen = len(theText) logger.verbose("Word counter thread is busy")
self.blockWords[theBlock.blockNumber()] = 0
if theLen == 0:
return 0
if theText[0] == "@" or theText[0] == "%":
return 0
if theText[0] == "#":
nWords = -1
else: else:
nWords = 0 logger.verbose("Starting word counter")
self.wCounter.start()
return
theBuff = theText.replace(""," ").replace(""," ") def _updateCounts(self):
nWords += len(theBuff.split()) """Slot for the word counter's finished signal
"""
logger.verbose("Updating word counts")
self.charCount = self.wCounter.charCount
self.wordCount = self.wCounter.wordCount
self.theParent.statusBar.setCharCount(self.charCount)
self.theParent.statusBar.setWordCount(self.wordCount)
return
self.blockWords[theBlock.blockNumber()] = nWords ##
# GUI Builder
return nWords ##
def _buildTabToolBar(self): def _buildTabToolBar(self):
toolBar = self.editToolBar toolBar = self.editToolBar
+73
View File
@@ -0,0 +1,73 @@
# -*- coding: utf-8 -*-
"""novelWriter GUI Document Word Counter
novelWriter GUI Document Word Counter
===================================
A thread for counting words and characters in a document
File History:
Created: 2019-04-22 [0.0.1]
"""
import logging
import nw
from time import time
from PyQt5.QtWidgets import QWidget, QTextEdit, QHBoxLayout, QVBoxLayout, QFrame, QSplitter, QToolBar, QAction, QScrollArea
from PyQt5.QtCore import Qt, QSize, QSizeF, QTimer, QThread, pyqtSignal
from PyQt5.QtGui import QIcon, QFont, QTextCursor, QTextFormat, QTextBlockFormat
from nw.gui.dochighlight import GuiDocHighlighter
logger = logging.getLogger(__name__)
class WordCounter(QThread):
def __init__(self, theParent):
QThread.__init__(self, theParent)
self.theParent = theParent
self.charCount = 0
self.wordCount = 0
return
def run(self):
self.charCount = 0
self.wordCount = 0
for n in range(self.theParent.theDoc.blockCount()):
theBlock = self.theParent.theDoc.findBlockByNumber(n)
if not theBlock.isValid():
continue
theText = theBlock.text()
theLen = len(theText)
if theLen == 0:
continue
if theText[0] == "@" or theText[0] == "%":
continue
if theText[0:5] == "#### ":
self.wordCount -= 1
self.charCount -= 5
elif theText[0:4] == "### ":
self.wordCount -= 1
self.charCount -= 4
elif theText[0:3] == "## ":
self.wordCount -= 1
self.charCount -= 3
elif theText[0:2] == "# ":
self.wordCount -= 1
self.charCount -= 2
theBuff = theText.replace(""," ").replace(""," ")
self.wordCount += len(theBuff.split())
self.charCount += theLen
pass
## END Class _WordCounter