0

I'm building a simple speed reader in Python as an exercise. Basically a Spritz clone.

What's the correct way to stop the printword() function once I run it? Should I put it in a thread? With threading or QThread? I'm a bit lost.

#!/usr/bin/env python3

from time import sleep

import sys
from PyQt4 import QtGui, QtCore    

class Fastreader(QtGui.QWidget):

    def __init__(self):
        super(Fastreader, self).__init__()
        self.initUI()

    def initUI(self):

        self.le = QtGui.QLineEdit(self)
        self.le.setAlignment(QtCore.Qt.AlignCenter)
        self.le.move(20, 20)

        self.btn = QtGui.QPushButton('Play', self)
        self.btn.move(20, 50)
        self.btn.clicked.connect(self.printword)

        self.btn = QtGui.QPushButton('Stop', self)
        self.btn.move(120, 50)

        self.setGeometry(300, 300, 225, 80)
        self.setWindowTitle('fastreader')
        self.show()

    def printword(self):

        cb = QtGui.QApplication.clipboard()
        text = cb.text()

        words_per_minute = 200

        sleeptime = 60.0/words_per_minute

        for word in text.split(" "):
                self.le.setText(word)
                self.le.repaint()
                sleep(sleeptime)

def main():

    app = QtGui.QApplication(sys.argv)
    ex = Fastreader()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()

Thanks a bunch!

2 Answers2

0

I would use timers with a callback, to avoid the use of threads or sleep in Qt. You could try something like this:

def initUI(self):
    ...
    self.btn = QtGui.QPushButton('Play', self)
    self.btn.move(20, 50)
    self.btn.clicked.connect(self.play)

    self.btn = QtGui.QPushButton('Stop', self)
    self.btn.move(120, 50)
    self.btn.clicked.connect(self.stop)
    ...

def play(self):
    cb = QtGui.QApplication.clipboard()
    text = cb.text()
    self.words = text.split(' ') 
    self.current_word = 0

    words_per_minute = 200

    # setInterval apparently takes msecs
    sleeptime = 60000.0 / words_per_minute

    self.timer = QtCore.QTimer(self)
    self.timer.setInterval(sleeptime)
    self.timer.timeout.connect(self.printword)
    self.timer.start()

def stop(self):
    self.timer.stop()

def printword(self):
    word = self.words[self.current_word]
    self.le.setText(word)

    self.current_word += 1
    if self.current_word == len(self.words):
        self.timer.stop()

You don't even have to call self.le.repaint() this way.

user2746752
  • 1,028
  • 1
  • 13
  • 25
0

Here is my solution of using QThread, please check. And I also used the slot and signal mechanism to transfer the data between the main thread and the worker thread.

Well, initially I thought it would be easy to change it as this way. But it turned out quite some changes need to be done. But just consider it as an example of using QThread.

from time import sleep

import sys
from PyQt4.QtCore import *
from PyQt4.QtGui import *


class Fastreader(QWidget):

    def __init__(self):
        super(Fastreader, self).__init__()
        self.initUI()

    def initUI(self):
        self.le = QLineEdit(self)
        self.le.setAlignment(Qt.AlignCenter)
        self.le.move(20, 20)

        self.btnplay = QPushButton('Play', self)
        self.btnplay.move(20, 50)

        self.btnstop = QPushButton('Stop', self)
        self.btnstop.move(120, 50)

        self.mthread = QThread() # New thread to run the Measurement Engine
        self.worker = PrintWord() # Measurement Engine Object

        # Transfer worker to the new thread. Calls to the worker will execute in mthread.
        self.worker.moveToThread(self.mthread)
        self.mthread.finished.connect(self.worker.deleteLater) # Cleanup after thread finished

        self.btnplay.clicked.connect(self.worker.printword)
        self.btnstop.clicked.connect(self.worker.kill, Qt.DirectConnection)

        self.worker.wordshow.connect(self.showword)
        self.mthread.start() 

        self.setGeometry(300, 300, 225, 80)
        self.setWindowTitle('fastreader')
        self.show()

    @pyqtSlot(str)
    def showword(self, word):
        self.le.setText(word)
#        self.le.repaint()

    def closeEvent(self, event):
        """ Example how to capture all window close events and perform an action before closing. It reimplements the default closeEvent"""
        print("Close Event Received")

        # Cleanup mthread.
        self.mthread.quit()
        self.mthread.wait()

        # Accept the event, so that the window really closed. 
        # ignore() wold leave the window alive, for example if there is unsaved data. 
#        event.accept()

class PrintWord(QObject):
    wordshow   = pyqtSignal(str)              # started measurement loop

    def __init__(self):
        self.dokill = False # Flag to interrupt the loop if needed
        QObject.__init__(self) # Don't forget to call base class constructor
        self.printword()

    def printword(self):

        cb = QApplication.clipboard()
        text = cb.text()

        words_per_minute = 200

        sleeptime = 60.0/words_per_minute
        self.dokill = False
        for word in text.split(" "):
                if self.dokill:
                    break
                self.wordshow.emit(word)
                sleep(sleeptime)
#                print word

    @pyqtSlot()
    def kill(self):
        """ Set loop interruption flag to exit the measurment loop """
        self.dokill = True

def main():
    app = QApplication(sys.argv)
    ex = Fastreader()
    sys.exit(app.exec_())

if __name__ == '__main__':
    main()
MaThMaX
  • 1,995
  • 1
  • 12
  • 23
  • Thank you very much for your time! Even after extensive testing I could not get your suggestion to work. But I'll look more into QThread in the future. – Markus Preislinger Jun 04 '16 at 20:32
  • @MarkusPreislinger , may I know what the error is? I could run the code successfully on my side. I'm using Python 3.3 and pyqt 4 – MaThMaX Jun 05 '16 at 02:07
  • When starting nothing happens - no window comes up. Sometimes it suddenly comes up, but when I press something "Segmentation fault (core dumped)". I tried debugging it [with this](http://stackoverflow.com/questions/1736015/debugging-a-pyqt4-app), but gave up after some time. Python 3.4.3+, PyQt 4.8.6, 4.2.0-36-generic – Markus Preislinger Jun 05 '16 at 12:07