Qt threading done right (thread pool for word counter)
This commit is contained in:
+44
-37
@@ -12,6 +12,7 @@
|
|||||||
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
|
Created: 2020-04-25 [0.4.5] GuiDocEditHeader
|
||||||
Rewritten: 2020-06-15 [0.9.0] GuiDocEditSearch
|
Rewritten: 2020-06-15 [0.9.0] GuiDocEditSearch
|
||||||
Created: 2020-06-27 [0.10.0] GuiDocEditFooter
|
Created: 2020-06-27 [0.10.0] GuiDocEditFooter
|
||||||
|
Rewritten: 2020-10-07 [1.0b3] BackgroundWordCounter
|
||||||
|
|
||||||
This file is a part of novelWriter
|
This file is a part of novelWriter
|
||||||
Copyright 2018–2020, Veronica Berglyd Olsen
|
Copyright 2018–2020, Veronica Berglyd Olsen
|
||||||
@@ -36,7 +37,8 @@ import logging
|
|||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt5.QtCore import (
|
from PyQt5.QtCore import (
|
||||||
Qt, QSize, QThread, QTimer, pyqtSlot, QRegExp, QRegularExpression, QPointF
|
Qt, QSize, QTimer, pyqtSlot, pyqtSignal, QRegExp, QRegularExpression,
|
||||||
|
QPointF, QObject, QRunnable
|
||||||
)
|
)
|
||||||
from PyQt5.QtGui import (
|
from PyQt5.QtGui import (
|
||||||
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette,
|
QTextCursor, QTextOption, QKeySequence, QFont, QColor, QPalette,
|
||||||
@@ -135,14 +137,15 @@ class GuiDocEditor(QTextEdit):
|
|||||||
activated=self._followTag
|
activated=self._followTag
|
||||||
)
|
)
|
||||||
|
|
||||||
# Set Up Word Count Thread and Timer
|
# Set Up Word Counter
|
||||||
self.wcInterval = self.mainConf.wordCountTimer
|
self.wcInterval = self.mainConf.wordCountTimer
|
||||||
self.wcTimer = QTimer()
|
self.wcTimer = QTimer()
|
||||||
self.wcTimer.setInterval(int(self.wcInterval*1000))
|
self.wcTimer.setInterval(int(self.wcInterval*1000))
|
||||||
self.wcTimer.timeout.connect(self._runCounter)
|
self.wcTimer.timeout.connect(self._runCounter)
|
||||||
|
|
||||||
self.wCounter = BackgroundWordCounter(self)
|
self.wCounter = BackgroundWordCounter(self)
|
||||||
self.wCounter.finished.connect(self._updateCounts)
|
self.wCounter.setAutoDelete(False)
|
||||||
|
self.wCounter.signals.countsReady.connect(self._updateCounts)
|
||||||
|
|
||||||
self.initEditor()
|
self.initEditor()
|
||||||
|
|
||||||
@@ -912,21 +915,18 @@ class GuiDocEditor(QTextEdit):
|
|||||||
"""Decide whether to run the word counter, or stop the timer due
|
"""Decide whether to run the word counter, or stop the timer due
|
||||||
to inactivity.
|
to inactivity.
|
||||||
"""
|
"""
|
||||||
sinceActive = time()-self.lastEdit
|
if self.wCounter.isRunning():
|
||||||
if sinceActive > 5*self.wcInterval:
|
logger.verbose("Word counter is busy")
|
||||||
logger.debug(
|
return
|
||||||
"Stopping word count timer: no activity last %.1f seconds" % sinceActive
|
|
||||||
)
|
if time() - self.lastEdit < 5*self.wcInterval:
|
||||||
self.wcTimer.stop()
|
logger.verbose("Running word counter")
|
||||||
elif self.wCounter.isRunning():
|
self.theParent.threadPool.start(self.wCounter)
|
||||||
logger.verbose("Word counter thread is busy")
|
|
||||||
else:
|
|
||||||
logger.verbose("Starting word counter")
|
|
||||||
self.wCounter.start()
|
|
||||||
return
|
return
|
||||||
|
|
||||||
@pyqtSlot()
|
@pyqtSlot(int, int, int)
|
||||||
def _updateCounts(self):
|
def _updateCounts(self, cCount, wCount, pCount):
|
||||||
"""Slot for the word counter's finished signal
|
"""Slot for the word counter's finished signal
|
||||||
"""
|
"""
|
||||||
theItem = self.nwDocument.getCurrentItem()
|
theItem = self.nwDocument.getCurrentItem()
|
||||||
@@ -935,19 +935,17 @@ class GuiDocEditor(QTextEdit):
|
|||||||
|
|
||||||
logger.verbose("Updating word count")
|
logger.verbose("Updating word count")
|
||||||
|
|
||||||
self.charCount = self.wCounter.charCount
|
self.charCount = cCount
|
||||||
self.wordCount = self.wCounter.wordCount
|
self.wordCount = wCount
|
||||||
self.paraCount = self.wCounter.paraCount
|
self.paraCount = pCount
|
||||||
theItem.setCharCount(self.charCount)
|
theItem.setCharCount(cCount)
|
||||||
theItem.setWordCount(self.wordCount)
|
theItem.setWordCount(wCount)
|
||||||
theItem.setParaCount(self.paraCount)
|
theItem.setParaCount(pCount)
|
||||||
|
|
||||||
self.theParent.treeView.propagateCount(self.theHandle, self.wordCount)
|
self.theParent.treeView.propagateCount(self.theHandle, wCount)
|
||||||
self.theParent.treeView.projectWordCount()
|
self.theParent.treeView.projectWordCount()
|
||||||
self.theParent.treeMeta.updateCounts(
|
self.theParent.treeMeta.updateCounts(self.theHandle, cCount, wCount, pCount)
|
||||||
self.theHandle, self.charCount, self.wordCount, self.paraCount
|
self._checkDocSize(self.qDocument.characterCount())
|
||||||
)
|
|
||||||
self._checkDocSize(self.charCount)
|
|
||||||
self.docFooter.updateCounts()
|
self.docFooter.updateCounts()
|
||||||
|
|
||||||
return
|
return
|
||||||
@@ -1521,33 +1519,42 @@ class GuiDocEditor(QTextEdit):
|
|||||||
# END Class GuiDocEditor
|
# END Class GuiDocEditor
|
||||||
|
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
# The Off GUI Thread Word Counter
|
# The Off-GUI Thread Word Counter
|
||||||
# Runs the word counter in the background for the DocEditor
|
# A runnable for the word counter to be run in the thread pool off the main GUI thread.
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
|
|
||||||
class BackgroundWordCounter(QThread):
|
class BackgroundWordCounter(QRunnable):
|
||||||
|
|
||||||
def __init__(self, docEditor):
|
def __init__(self, docEditor):
|
||||||
QThread.__init__(self, docEditor)
|
QRunnable.__init__(self)
|
||||||
self.docEditor = docEditor
|
self.docEditor = docEditor
|
||||||
self.charCount = 0
|
self.signals = BackgroundWordCounterSignals()
|
||||||
self.wordCount = 0
|
self._isRunning = False
|
||||||
self.paraCount = 0
|
|
||||||
return
|
return
|
||||||
|
|
||||||
|
def isRunning(self):
|
||||||
|
return self._isRunning
|
||||||
|
|
||||||
|
@pyqtSlot()
|
||||||
def run(self):
|
def run(self):
|
||||||
"""Overloaded run function for the word counter, forwarding the
|
"""Overloaded run function for the word counter, forwarding the
|
||||||
call to the function that does the actual counting.
|
call to the function that does the actual counting.
|
||||||
"""
|
"""
|
||||||
|
self._isRunning = True
|
||||||
theText = self.docEditor.getText()
|
theText = self.docEditor.getText()
|
||||||
cC, wC, pC = countWords(theText)
|
cC, wC, pC = countWords(theText)
|
||||||
self.charCount = cC
|
self.signals.countsReady.emit(cC, wC, pC)
|
||||||
self.wordCount = wC
|
self._isRunning = False
|
||||||
self.paraCount = pC
|
|
||||||
return
|
return
|
||||||
|
|
||||||
## END Class BackgroundWordCounter
|
## END Class BackgroundWordCounter
|
||||||
|
|
||||||
|
class BackgroundWordCounterSignals(QObject):
|
||||||
|
|
||||||
|
countsReady = pyqtSignal(int, int, int)
|
||||||
|
|
||||||
|
# END Class BackgroundWordCounterSignals
|
||||||
|
|
||||||
# =============================================================================================== #
|
# =============================================================================================== #
|
||||||
# The Embedded Document Search/Replace Feature
|
# The Embedded Document Search/Replace Feature
|
||||||
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
|
# Only used by DocEditor, and is at a fixed position in the QTextEdit's viewport
|
||||||
|
|||||||
+2
-1
@@ -32,7 +32,7 @@ import os
|
|||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from time import time
|
from time import time
|
||||||
|
|
||||||
from PyQt5.QtCore import Qt, QTimer
|
from PyQt5.QtCore import Qt, QTimer, QThreadPool
|
||||||
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
|
from PyQt5.QtGui import QIcon, QPixmap, QColor, QKeySequence, QCursor
|
||||||
from PyQt5.QtWidgets import (
|
from PyQt5.QtWidgets import (
|
||||||
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
|
qApp, QMainWindow, QVBoxLayout, QWidget, QSplitter, QFileDialog, QShortcut,
|
||||||
@@ -60,6 +60,7 @@ class GuiMain(QMainWindow):
|
|||||||
logger.debug("Initialising GUI ...")
|
logger.debug("Initialising GUI ...")
|
||||||
self.setObjectName("GuiMain")
|
self.setObjectName("GuiMain")
|
||||||
self.mainConf = nw.CONFIG
|
self.mainConf = nw.CONFIG
|
||||||
|
self.threadPool = QThreadPool()
|
||||||
|
|
||||||
# Some runtime info useful for debugging
|
# Some runtime info useful for debugging
|
||||||
logger.info("OS: %s" % self.mainConf.osType)
|
logger.info("OS: %s" % self.mainConf.osType)
|
||||||
|
|||||||
Reference in New Issue
Block a user