Не понимаю, как реализовать отмену действий Undo с помощью паттерна "Команда" (в графическом редакторе)

Не понимаю, как реализовать отмену действий(Undo) с помощью паттерна "Команда" (в графическом редакторе). Прописал несколько классов, необходимых для реализации отмены действия, но пока не совсем разобрался, что в них должно быть.

Данная программа рисует только прямоугольники, также присутствует кнопка отмены(пока без логики):

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

import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import copy

#класс прямоугольник
class Rectangle:
    def __init__(self, frame):
        self.frame = frame

    def draw(self, painter):
        painter.rect(self.frame.x1, self.frame.y1, self.frame.x2, self.frame.y2)

class Frame:
    def __init__(self, x1, y1, x2, y2):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2

# класс для отрисовки
class Painter:
    def __init__(self):
        super().__init__()
        self.pen_color = QtGui.QColor("green")
        self.pen_width = 1
        self.brush_color = QtGui.QColor("green")

    def set_port(self, painter):
        self.painter = painter

    def rect(self, x1, y1, x2, y2):
        xmax = max(x1, x2)
        xmin = min(x1, x2)
        ymax = max(y1, y2)
        ymin = min(y1, y2)
        w = xmax - xmin
        h = ymax - ymin

        pen = QPen(self.pen_color, self.pen_width)
        self.painter.addRect(xmin, ymin, w, h, pen, self.brush_color)

class GraphicsScene(QGraphicsScene):
    clicked = pyqtSignal(QPointF)
    released = pyqtSignal(QPointF)
    moved = pyqtSignal(QPointF)

    def mousePressEvent(self, event):
        sp = event.scenePos()
        self.clicked.emit(sp)
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        sp = event.scenePos()
        self.released.emit(sp)
        super().mouseReleaseEvent(event)


class Editor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(520, 550)

        self.grview = QGraphicsView()
        self.grview.scale(1, -1)

        self.scene = GraphicsScene()
        self.setMouseTracking(True)
        self.scene.setSceneRect(-250, -250, 500, 500)

        self.grview.setScene(self.scene)
        self.toolbar()

        self.command_history = CommandHistory()

        self.pen = QPen(Qt.red)
        self.brush = QBrush(Qt.green)
        self.scene.clicked.connect(self.point1)
        self.scene.released.connect(self.point2)
        self.scene.moved.connect(self.point2)
        self.undo_action.triggered.connect(self.undo)
        self.clear_action.triggered.connect(self.clear)
        self.mass_rect = []
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        layout = QVBoxLayout(self.centralWidget)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.grview)
        self.show()

    def toolbar(self):
        self.figure_toolbar = QToolBar("figure")
        self.figure_toolbar.setIconSize(QSize(25, 25))
        self.basicToolBar = self.addToolBar(self.figure_toolbar)
        # кнопка undo
        self.undo_action = QAction(QIcon("images/undo.ico"), 'undo', self)
        self.undo_action.setStatusTip("undo")
        self.figure_toolbar.addAction(self.undo_action)
        # кнопка очистки
        self.clear_action = QAction(QIcon("images/clear.png"), 'claer', self) 
        self.claer_action.setStatusTip("clear")
        self.figure_toolbar.addAction(self.clear_action)

    def point1(self, p):
        self.x1 = p.x()
        self.y1 = p.y()

    def point2(self, p):
        self.x2 = p.x()
        self.y2 = p.y()
        self.create_rect(self.x1, self.y1, self.x2, self.y2)

    def create_rect(self, x1, y1, x2, y2):
        #отрисовка фигур
        painter = Painter()
        painter.set_port(self.scene)
        frame = Frame(x1, y1, x2, y2)
        rect = Rectangle(frame)
        self.mass_rect.append(rect)
        for object in self.mass_rect:
            object.draw(painter)

    def undo(self):
        command = self.command_history.pop()

    def clear(self):
        self.scene.clear()


# реализация шаблона команда
class Command:
    def __init__(self, editor):
        self.editor = copy.copy(editor)
    def undo(self):
        pass

class UndoCommand(Command):
    def __init__(self, editor):
        super().__init__(editor)

    def execute(self):
        pass

class CommandHistory:
    def __init__(self):
        self.history = []

    def push(self, command):
        self.history.append(command)

    def pop(self):
        return self.history.pop(-1)


def main():
    app = QtWidgets.QApplication(sys.argv)
    g = Editor()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

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

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

я не знаю правильно ли вас понял, но попробуйте так:

import sys
from PyQt5 import QtWidgets, QtCore, QtGui
from PyQt5.QtCore import *
from PyQt5.QtGui import *
from PyQt5.QtWidgets import *
import copy


class Rectangle:
    """ класс прямоугольник """ 
    
    def __init__(self, frame):
        self.frame = frame

    def draw(self, painter):
        painter.rect(self.frame.x1, self.frame.y1, self.frame.x2, self.frame.y2)


class Frame:
    def __init__(self, x1, y1, x2, y2):
        self.x1 = x1
        self.y1 = y1
        self.x2 = x2
        self.y2 = y2


class Painter:
    """ класс для отрисовки """
    
    def __init__(self):
        super().__init__()
        self.pen_color = QtGui.QColor("green")
        self.pen_width = 1
        self.brush_color = QtGui.QColor("green")

    def set_port(self, painter):
        self.painter = painter

    def rect(self, x1, y1, x2, y2):
        xmax = max(x1, x2)
        xmin = min(x1, x2)
        ymax = max(y1, y2)
        ymin = min(y1, y2)
        w = xmax - xmin
        h = ymax - ymin

        pen = QPen(self.pen_color, self.pen_width)
        self.painter.addRect(xmin, ymin, w, h, pen, self.brush_color)


class Command:
    """ реализация шаблона команда """
    
    def __init__(self, editor):
        self.editor = copy.copy(editor)
        
    def undo(self):
        pass


class UndoCommand(Command):
    def __init__(self, editor):
        super().__init__(editor)

    def execute(self):
        pass

class CommandHistory:
    def __init__(self, mass_rect):                                   # + , mass_rect
        self.history = mass_rect                                     # - [], + mass_rect

    def push(self, command):
        self.history.append(command)

    def pop(self):
#        return self.history.pop(-1)                                 # -       
        if self.history:                                             # +
            self.history.pop(-1)
            return self.history
        else:                                                        # +++
            self.history = []
            return self.history
        
        
class GraphicsScene(QGraphicsScene):
    clicked = pyqtSignal(QPointF)
    released = pyqtSignal(QPointF)
    moved = pyqtSignal(QPointF)

    def mousePressEvent(self, event):
        sp = event.scenePos()
        self.clicked.emit(sp)
        super().mousePressEvent(event)

    def mouseReleaseEvent(self, event):
        sp = event.scenePos()
        self.released.emit(sp)
        super().mouseReleaseEvent(event)


class Editor(QMainWindow):
    def __init__(self):
        super().__init__()
        self.resize(520, 550)

        self.grview = QGraphicsView()
        self.grview.scale(1, -1)

        self.scene = GraphicsScene()
        self.setMouseTracking(True)
        self.scene.setSceneRect(-250, -250, 500, 500)

        self.grview.setScene(self.scene)
        self.toolbar()

#        self.command_history = CommandHistory()                           # перенес см. ниже

        self.pen = QPen(Qt.red)
        self.brush = QBrush(Qt.green)
        
        self.scene.clicked.connect(self.point1)
        self.scene.released.connect(self.point2)
#?        self.scene.moved.connect(self.point2)
        self.undo_action.triggered.connect(self.undo)
        
        self.mass_rect = []
        self.centralWidget = QWidget()
        self.setCentralWidget(self.centralWidget)
        layout = QVBoxLayout(self.centralWidget)
        layout.setContentsMargins(0, 0, 0, 0)
        layout.addWidget(self.grview)
        self.show()
        
        self.command_history = CommandHistory(self.mass_rect)                # + self.mass_rect

    def toolbar(self):
        self.figure_toolbar = QToolBar("figure")
        self.figure_toolbar.setIconSize(QSize(25, 25))
        self.basicToolBar = self.addToolBar(self.figure_toolbar)
        # кнопка line
        self.undo_action = QAction(QIcon("images/undo.png"), 'undo', self)  # undo.ico установите свое
        self.undo_action.setStatusTip("undo")
        self.figure_toolbar.addAction(self.undo_action)

    def point1(self, p):
        self.x1 = p.x()
        self.y1 = p.y()

    def point2(self, p):
        self.x2 = p.x()
        self.y2 = p.y()
        self.create_rect(self.x1, self.y1, self.x2, self.y2)

    def create_rect(self, x1, y1, x2, y2):
        #отрисовка фигур
        self.painter = Painter()
        self.painter.set_port(self.scene)
        frame = Frame(x1, y1, x2, y2)
        rect = Rectangle(frame)
        self.mass_rect.append(rect)
        
# +++ vvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvvv
        self._draw(self.mass_rect, self.painter)
        
    def _draw(self, mass_rect, painter):
        if mass_rect:
            for object in mass_rect:                 
                object.draw(painter)        

    def undo(self):
        command = self.command_history.pop()
        self.mass_rect = command                     
        self.scene.clear()                           
        if self.mass_rect:                           
            self._draw(self.mass_rect, self.painter)       
# +++ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

def main():
    app = QtWidgets.QApplication(sys.argv)
    g = Editor()
    sys.exit(app.exec_())


if __name__ == "__main__":
    main()

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

→ Ссылка