pythonpyqtpyqt5qcursor

Is there a way to create a custom animated / gif QCursor?


I am trying to create a custum animated cursor that replaces the regular cursor for when lengthy processes are taking place similar to here and here, however, I woud like mine to be animated, for example using a gif, just as it is the case with the standard Qt.WaitCursor. How can I do this? I found this solution regarding animated system tray icons, however, I didn't manage to adapt it for it to work with the cursor icon.

As a side note: When I try to execute pm.setAlphaChannel(bm) as stated in the first link it does not work for me and I get the following error:

'AttributeError: QPixmap' object has no attribute 'setAlphaChannel'

Which is odd because according to the documentation, QPixmap does have a setAlphaChannel method.


Solution

  • One possible solution is to create a class that handles the update of the cursor of a given widget. In the following example the cursor is set when the start button is pressed and the cursor is restored when the stop button is pressed:

    from PyQt5 import QtCore, QtGui, QtWidgets
    
    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)
    
            self._manager = ManagerCursor(self)
            movie = QtGui.QMovie("loading-icon.gif")
            self._manager.setMovie(movie)
            self._manager.setWidget(self)
    
            lay = QtWidgets.QVBoxLayout(self)
            lay.addWidget(start_btn)
            lay.addWidget(stop_btn)
            lay.addStretch()
    
        @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_())