Как при рисование (с помощью мыши) сделать действия "undo" и "redo" PyQt QPainter

Я хочу при рисование сделать undo/redo. Допустим я нарисовал несколько линий и захотел последнюю отменить, а затем вернуть, как это можно реализовать к примеру клавишами Ctrl+Z/Ctrl+Y?

Вот код

import sys
from PyQt5.QtWidgets import QWidget, QApplication
from PyQt5.QtGui import QPainter, QColor, QMouseEvent, QPen, QPixmap, QPainterPath
from PyQt5.QtCore import Qt, QPoint

        
class Drawing(QWidget):
    def __init__(self, parent):
        super().__init__(parent)

        self.parent = parent
        self.drawingPath = None
        self.resize(500, 500)
        
        self.image = QPixmap(500, 500)
        self.image.fill(Qt.white)

          
    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawPixmap(QPoint(), self.image)
        if self.drawingPath:
            painter.setPen(QPen(QColor(0, 0, 0), 10, Qt.SolidLine))
            painter.drawPath(self.drawingPath)
            
           
    def mousePressEvent(self, event):
        if event.button() == Qt.LeftButton:
            self.drawingPath = QPainterPath()
            self.drawingPath.moveTo(event.pos())
            self.update()
            
            
    def mouseMoveEvent(self, event):
        if event.buttons() and Qt.LeftButton and self.drawingPath:     
            self.drawingPath.lineTo(event.pos())
            self.update()

            
    def mouseReleaseEvent(self, event):
        if event.button() == Qt.LeftButton and self.drawingPath:
            painter = QPainter(self.image)
            painter.setPen(QPen(QColor(0, 0, 0), 10, Qt.SolidLine))
            painter.drawPath(self.drawingPath)        
            self.drawingPath = None
            self.update()
            

class Main(QWidget):
    def __init__(self):
        super().__init__()
        
        self.drawing = Drawing(self)
        self.resize(500, 500)
        self.show()
        self.drawing.show()
        
        
if __name__ == '__main__':
    app = QApplication(sys.argv)
    main = Main()
    sys.exit(app.exec_())

Ответы (1 шт):

Автор решения: S. Nick

Класс QUndoCommand - это базовый класс всех команд, хранящихся в QUndoStack.

Больше https://doc.qt.io/qt-5/qundocommand.html

Класс QUndoStack - это стек объектов QUndoCommand.

Больше https://doc.qt.io/qt-5/qundostack.html

Вы можете также пользоваться клавишами Ctrl+Z / Ctrl+Y

import sys
from PyQt5.Qt import *


class UndoCommand(QUndoCommand):
    def __init__(self, parent):
        super().__init__()
        self.parent = parent
        self.mPrevImage = parent.image.copy()
        self.mCurrImage = parent.image.copy()

    def undo(self):
        self.mCurrImage = self.parent.image.copy()
        self.parent.image = self.mPrevImage
        self.parent.update()

    def redo(self):
        self.parent.image = self.mCurrImage
        self.parent.update()


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()

        self.mUndoStack = QUndoStack(self)
        self.mUndoStack.setUndoLimit(20)

        self.mUndoStack.canUndoChanged.connect(self.can_undo_changed)
        self.mUndoStack.canRedoChanged.connect(self.can_redo_changed)

        self.actionUndo = self.menuBar().addAction("Undo")
        self.actionUndo.triggered.connect(self.mUndoStack.undo)
        self.actionRedo = self.menuBar().addAction("Redo")
        self.actionRedo.triggered.connect(self.mUndoStack.redo)

        self.can_undo_changed(self.mUndoStack.canUndo())
        self.can_redo_changed(self.mUndoStack.canRedo())

        self.image = QPixmap(500, 500)
        self.image.fill(Qt.darkGreen)        
        self.is_pressed = False
        self.drawingPath = None

    def can_undo_changed(self, enabled):
        self.actionUndo.setEnabled(enabled)

    def can_redo_changed(self, enabled):
        self.actionRedo.setEnabled(enabled)

    def make_undo_command(self):
        self.mUndoStack.push(UndoCommand(self))

    def draw(self, parent):
        painter = QPainter(parent)
        if self.drawingPath:
            painter.setPen(QPen(QColor(0, 0, 0), 5, Qt.SolidLine))
            painter.drawPath(self.drawingPath)

    def paintEvent(self, event):
        painter = QPainter(self)
        painter.drawPixmap(QPoint(), self.image)
        if self.is_pressed:
            self.draw(self)

    def mousePressEvent(self, event):
        self.is_pressed = True
        if event.button() == Qt.LeftButton:
            self.drawingPath = QPainterPath()
            self.drawingPath.moveTo(event.pos())
            self.update()

    def mouseMoveEvent(self, event):
        if event.buttons() and Qt.LeftButton and self.drawingPath:     
            self.drawingPath.lineTo(event.pos())
            self.update()

    def mouseReleaseEvent(self, event):
        self.is_pressed = False
        self.make_undo_command()
        self.draw(self.image)
        self.update()
        
    def keyPressEvent(self, event):
        if event.key() == Qt.Key_Z and event.modifiers() == Qt.ControlModifier:  
            self.mUndoStack.undo()
        elif event.key() == Qt.Key_Y and event.modifiers() == Qt.ControlModifier:  
            self.mUndoStack.redo()


if __name__ == '__main__':
    app = QApplication(sys.argv)
    w = MainWindow()
    w.resize(500, 500)
    w.show()
    sys.exit(app.exec_())

введите сюда описание изображения

→ Ссылка