As a follow-up question to this post, I was wondering if it is possible to extend the functionality of the cursor so that when pickle is used to dump or save data, the animation of the cursor doesn't freeze.
from PyQt5 import QtCore, QtGui, QtWidgets
import pickle
import gzip
import numpy as np
class ManagerCursor(QtCore.QObject):
def __init__(self, parent=None):
super(ManagerCursor, self).__init__(parent)
self._movie = None
self._widget = None
self._last_cursor = None
def setMovie(self, movie):
if isinstance(self._movie, QtGui.QMovie):
if not self._movie != QtGui.QMovie.NotRunning:
self._movie.stop()
del self._movie
self._movie = movie
self._movie.frameChanged.connect(self.on_frameChanged)
self._movie.started.connect(self.on_started)
self._movie.finished.connect(self.restore_cursor)
def setWidget(self, widget):
self._widget = widget
@QtCore.pyqtSlot()
def on_started(self):
if self._widget is not None:
self._last_cursor = self._widget.cursor()
@QtCore.pyqtSlot()
def restore_cursor(self):
if self._widget is not None:
if self._last_cursor is not None:
self._widget.setCursor(self._last_cursor)
self._last_cursor = None
@QtCore.pyqtSlot()
def start(self):
if self._movie is not None:
self._movie.start()
@QtCore.pyqtSlot()
def stop(self):
if self._movie is not None:
self._movie.stop()
self.restore_cursor()
@QtCore.pyqtSlot()
def on_frameChanged(self):
pixmap = self._movie.currentPixmap()
cursor = QtGui.QCursor(pixmap)
if self._widget is not None:
if self._last_cursor is None:
self._last_cursor = self._widget.cursor()
self._widget.setCursor(cursor)
class Progress(QtWidgets.QDialog):
def __init__(self):
super().__init__()
progress = QtWidgets.QProgressBar()
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(progress)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
start_btn = QtWidgets.QPushButton("start", clicked=self.on_start)
stop_btn = QtWidgets.QPushButton("stop", clicked=self.on_stop)
dump_btn = QtWidgets.QPushButton("dump", clicked=self.dump)
load_btn = QtWidgets.QPushButton("load", clicked=self.load)
self.file = 'test'
text_edit = QtWidgets.QTextEdit()
self.popup = None
self._manager = ManagerCursor(self)
movie = QtGui.QMovie('../assets/comet_resized.gif')
self._manager.setMovie(movie)
self._manager.setWidget(self)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(start_btn)
lay.addWidget(stop_btn)
lay.addWidget(dump_btn)
lay.addWidget(load_btn)
lay.addWidget(text_edit)
lay.addStretch()
@QtCore.pyqtSlot()
def dump(self):
self._manager.start()
self.popup = Progress()
self.popup.show()
data = [np.full(1000, 1000) for i in range(100000)]
with gzip.open(self.file, 'wb') as output_file:
pickle.dump(data, output_file, pickle.HIGHEST_PROTOCOL)
def load(self):
self._manager.start()
self.popup = Progress()
self.popup.show()
with gzip.open(self.file, 'rb') as input_file:
data = pickle.load(input_file)
@QtCore.pyqtSlot()
def on_start(self):
self._manager.start()
@QtCore.pyqtSlot()
def on_stop(self):
self._manager.stop()
if __name__ == '__main__':
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())
Heavy tasks should not be executed in the main thread since they block the event loop by freezing the GUI, it must be executed in another thread.
from functools import partial
import gzip
import pickle
from PyQt5 import QtCore, QtGui, QtWidgets
import numpy as np
class Worker(QtCore.QObject):
dumpStarted = QtCore.pyqtSignal()
dumpFinished = QtCore.pyqtSignal()
dataChanged = QtCore.pyqtSignal(object)
loadStarted = QtCore.pyqtSignal()
loadFinished = QtCore.pyqtSignal()
@QtCore.pyqtSlot(str, object)
def dump(self, filename, data):
self.dumpStarted.emit()
with gzip.open(filename, "wb") as output_file:
pickle.dump(data, output_file, pickle.HIGHEST_PROTOCOL)
self.dumpFinished.emit()
@QtCore.pyqtSlot(str)
def load(self, filename):
self.loadStarted.emit()
with gzip.open(filename, "rb") as input_file:
data = pickle.load(input_file)
self.dataChanged.emit(data)
self.loadFinished.emit()
class ManagerCursor(QtCore.QObject):
def __init__(self, parent=None):
super(ManagerCursor, self).__init__(parent)
self._movie = None
self._widget = None
self._last_cursor = None
def setMovie(self, movie):
if isinstance(self._movie, QtGui.QMovie):
if not self._movie != QtGui.QMovie.NotRunning:
self._movie.stop()
del self._movie
self._movie = movie
self._movie.frameChanged.connect(self.on_frameChanged)
self._movie.started.connect(self.on_started)
self._movie.finished.connect(self.restore_cursor)
def setWidget(self, widget):
self._widget = widget
@QtCore.pyqtSlot()
def on_started(self):
if self._widget is not None:
self._last_cursor = self._widget.cursor()
@QtCore.pyqtSlot()
def restore_cursor(self):
if self._widget is not None:
if self._last_cursor is not None:
self._widget.setCursor(self._last_cursor)
self._last_cursor = None
@QtCore.pyqtSlot()
def start(self):
if self._movie is not None:
self._movie.start()
@QtCore.pyqtSlot()
def stop(self):
if self._movie is not None:
self._movie.stop()
self.restore_cursor()
@QtCore.pyqtSlot()
def on_frameChanged(self):
pixmap = self._movie.currentPixmap()
cursor = QtGui.QCursor(pixmap)
if self._widget is not None:
if self._last_cursor is None:
self._last_cursor = self._widget.cursor()
self._widget.setCursor(cursor)
class Widget(QtWidgets.QWidget):
def __init__(self, parent=None):
super(Widget, self).__init__(parent)
start_btn = QtWidgets.QPushButton("start", clicked=self.on_start)
stop_btn = QtWidgets.QPushButton("stop", clicked=self.on_stop)
dump_btn = QtWidgets.QPushButton("dump", clicked=self.dump)
load_btn = QtWidgets.QPushButton("load", clicked=self.load)
self.file = "test"
text_edit = QtWidgets.QTextEdit()
self.popup = None
self._manager = ManagerCursor(self)
movie = QtGui.QMovie("giphy.gif")
self._manager.setMovie(movie)
self._manager.setWidget(self)
lay = QtWidgets.QVBoxLayout(self)
lay.addWidget(start_btn)
lay.addWidget(stop_btn)
lay.addWidget(dump_btn)
lay.addWidget(load_btn)
lay.addWidget(text_edit)
lay.addStretch()
thread = QtCore.QThread(self)
thread.start()
self._worker = Worker()
self._worker.moveToThread(thread)
self._worker.dumpStarted.connect(self._manager.start)
self._worker.dumpFinished.connect(self._manager.stop)
self._worker.dumpStarted.connect(partial(dump_btn.setEnabled, False))
self._worker.dumpFinished.connect(partial(dump_btn.setEnabled, True))
self._worker.loadStarted.connect(self._manager.start)
self._worker.loadFinished.connect(self._manager.stop)
self._worker.loadStarted.connect(partial(load_btn.setEnabled, False))
self._worker.loadFinished.connect(partial(load_btn.setEnabled, True))
self._worker.dataChanged.connect(self.on_data_changed)
@QtCore.pyqtSlot()
def dump(self):
data = [np.full(1000, 1000) for i in range(100000)]
wrapper = partial(self._worker.dump, self.file, data)
QtCore.QTimer.singleShot(0, wrapper)
@QtCore.pyqtSlot()
def load(self):
wrapper = partial(self._worker.load, self.file)
QtCore.QTimer.singleShot(0, wrapper)
@QtCore.pyqtSlot(object)
def on_data_changed(self, data):
print(data)
@QtCore.pyqtSlot()
def on_start(self):
self._manager.start()
@QtCore.pyqtSlot()
def on_stop(self):
self._manager.stop()
if __name__ == "__main__":
import sys
app = QtWidgets.QApplication(sys.argv)
w = Widget()
w.resize(640, 480)
w.show()
sys.exit(app.exec_())