Implemented a working word and char counter that runs in the background at regular intervals.
This commit is contained in:
@@ -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
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user