Skip to content

Commit

Permalink
UndoStack: Introduce undostack
Browse files Browse the repository at this point in the history
Right now we're using QUndoStack which is from Qt library, although it
has limited usage which we want to expand since we want to handle errors
in a proper way without any hacks regarding QUndoStack, so these series
of patches will introduce customly made Undostack, as well as Command
class which will replace QUndoCommand/QUndoStack classes.
  • Loading branch information
tetektoza committed Nov 28, 2023
1 parent 817c4b9 commit c84bd84
Show file tree
Hide file tree
Showing 15 changed files with 238 additions and 80 deletions.
6 changes: 5 additions & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets)
Expand Down Expand Up @@ -43,6 +43,10 @@ set(PROJECT_SOURCES
source/openasdialog.cpp
source/palettewidget.cpp
source/settingsdialog.cpp
source/undostack.cpp
source/undostack.h
source/command.cpp
source/command.h
)

if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
Expand Down
22 changes: 11 additions & 11 deletions source/celview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -70,7 +70,7 @@ void CelScene::contextMenuEvent(QContextMenuEvent *event)
emit this->showContextMenu(event->globalPos());
}

CelView::CelView(std::shared_ptr<QUndoStack> us, QWidget *parent)
CelView::CelView(std::shared_ptr<UndoStack> us, QWidget *parent)
: QWidget(parent)
, undoStack(us)
, ui(new Ui::CelView())
Expand Down Expand Up @@ -214,17 +214,17 @@ void CelView::removeFrames(int startingIndex, int endingIndex)

void CelView::sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath)
{
AddFrameCommand *command;
std::unique_ptr<Command> command;
try {
command = new AddFrameCommand(mode, index, imagefilePath);
command = std::make_unique<AddFrameCommand>(mode, index, imagefilePath);
} catch (...) {
QMessageBox::critical(this, "Error", "Failed to read image file: " + imagefilePath);
return;
}

// send a command to undostack, making adding frame undo/redoable
QObject::connect(command, &AddFrameCommand::added, this, &CelView::insertFrames);
QObject::connect(command, &AddFrameCommand::undoAdded, this, &CelView::removeFrames);
QObject::connect(dynamic_cast<AddFrameCommand *>(command.get()), &AddFrameCommand::added, this, &CelView::insertFrames);
QObject::connect(dynamic_cast<AddFrameCommand *>(command.get()), &AddFrameCommand::undoAdded, this, &CelView::removeFrames);

undoStack->push(command);
}
Expand All @@ -247,19 +247,19 @@ void CelView::sendReplaceCurrentFrameCmd(const QString &imagefilePath)
}

// send a command to undostack, making replacing frame undo/redoable
ReplaceFrameCommand *command = new ReplaceFrameCommand(this->currentFrameIndex, image, this->gfx->getFrameImage(this->currentFrameIndex));
QObject::connect(command, &ReplaceFrameCommand::replaced, this, &CelView::replaceCurrentFrame);
QObject::connect(command, &ReplaceFrameCommand::undoReplaced, this, &CelView::replaceCurrentFrame);
std::unique_ptr<Command> command = std::make_unique<ReplaceFrameCommand>(this->currentFrameIndex, image, this->gfx->getFrameImage(this->currentFrameIndex));
QObject::connect(dynamic_cast<ReplaceFrameCommand *>(command.get()), &ReplaceFrameCommand::replaced, this, &CelView::replaceCurrentFrame);
QObject::connect(dynamic_cast<ReplaceFrameCommand *>(command.get()), &ReplaceFrameCommand::undoReplaced, this, &CelView::replaceCurrentFrame);

undoStack->push(command);
}

void CelView::sendRemoveFrameCmd()
{
// send a command to undostack, making deleting frame undo/redoable
RemoveFrameCommand *command = new RemoveFrameCommand(this->currentFrameIndex, this->gfx->getFrameImage(this->currentFrameIndex));
QObject::connect(command, &RemoveFrameCommand::removed, this, &CelView::removeCurrentFrame);
QObject::connect(command, &RemoveFrameCommand::inserted, this, &CelView::insertImageFile);
std::unique_ptr<Command> command = std::make_unique<RemoveFrameCommand>(this->currentFrameIndex, this->gfx->getFrameImage(this->currentFrameIndex));
QObject::connect(dynamic_cast<RemoveFrameCommand *>(command.get()), &RemoveFrameCommand::removed, this, &CelView::removeCurrentFrame);
QObject::connect(dynamic_cast<RemoveFrameCommand *>(command.get()), &RemoveFrameCommand::inserted, this, &CelView::insertImageFile);

this->undoStack->push(command);
}
Expand Down
6 changes: 3 additions & 3 deletions source/celview.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@
#include <QPoint>
#include <QStringList>
#include <QTimer>
#include <QUndoStack>
#include <QWidget>

#include <memory>
#include <stack>

#include "d1gfx.h"
#include "undostack.h"

#define CEL_SCENE_SPACING 8

Expand Down Expand Up @@ -50,7 +50,7 @@ class CelView : public QWidget {
Q_OBJECT

public:
explicit CelView(std::shared_ptr<QUndoStack> us, QWidget *parent = nullptr);
explicit CelView(std::shared_ptr<UndoStack> us, QWidget *parent = nullptr);
~CelView();

void initialize(D1Gfx *gfx);
Expand Down Expand Up @@ -111,7 +111,7 @@ private slots:
std::stack<int> removedGroupIdxs; // holds indexes of groups that have been removed, used for undo ops
std::stack<int> removedFrameGroupIdxs; // holds group indexes of frames that got removed, used for undo ops

std::shared_ptr<QUndoStack> undoStack;
std::shared_ptr<UndoStack> undoStack;
Ui::CelView *ui;
CelScene *celScene;

Expand Down
11 changes: 11 additions & 0 deletions source/command.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
#include "command.h"

void Command::setObsolete(bool isObsolete)
{
m_isObsolete = isObsolete;
}

bool Command::isObsolete() const
{
return m_isObsolete;
}
15 changes: 15 additions & 0 deletions source/command.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

class Command {
public:
virtual void undo() = 0;
virtual void redo() = 0;

void setObsolete(bool isObsolete);
bool isObsolete() const;

virtual ~Command() = default;

private:
bool m_isObsolete = false;
};
6 changes: 3 additions & 3 deletions source/framecmds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
#include "framecmds.h"
#include "mainwindow.h"

RemoveFrameCommand::RemoveFrameCommand(int currentFrameIndex, const QImage img, QUndoCommand *parent)
RemoveFrameCommand::RemoveFrameCommand(int currentFrameIndex, const QImage img)
: frameIndexToRevert(currentFrameIndex)
, imgToRevert(img)
{
Expand All @@ -22,7 +22,7 @@ void RemoveFrameCommand::redo()
emit this->removed(frameIndexToRevert);
}

ReplaceFrameCommand::ReplaceFrameCommand(int currentFrameIndex, const QImage imgToReplace, const QImage imgToRestore, QUndoCommand *parent)
ReplaceFrameCommand::ReplaceFrameCommand(int currentFrameIndex, const QImage imgToReplace, const QImage imgToRestore)
: frameIndexToReplace(currentFrameIndex)
, imgToReplace(imgToReplace)
, imgToRestore(imgToRestore)
Expand All @@ -40,7 +40,7 @@ void ReplaceFrameCommand::redo()
emit this->replaced(frameIndexToReplace, imgToReplace);
}

AddFrameCommand::AddFrameCommand(IMAGE_FILE_MODE mode, int index, const QString imagefilePath, QUndoCommand *parent)
AddFrameCommand::AddFrameCommand(IMAGE_FILE_MODE mode, int index, const QString imagefilePath)
: startingIndex(index)
, mode(mode)
{
Expand Down
15 changes: 8 additions & 7 deletions source/framecmds.h
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
#pragma once

#include "celview.h"
#include "command.h"

#include <QObject>
#include <QUndoCommand>

class RemoveFrameCommand : public QObject, public QUndoCommand {
class RemoveFrameCommand : public QObject, public Command {
Q_OBJECT

public:
explicit RemoveFrameCommand(int currentFrameIndex, const QImage img, QUndoCommand *parent = nullptr);
explicit RemoveFrameCommand(int currentFrameIndex, const QImage img);
~RemoveFrameCommand() = default;

void undo() override;
Expand All @@ -23,11 +24,11 @@ class RemoveFrameCommand : public QObject, public QUndoCommand {
int frameIndexToRevert = 0;
};

class ReplaceFrameCommand : public QObject, public QUndoCommand {
class ReplaceFrameCommand : public QObject, public Command {
Q_OBJECT

public:
explicit ReplaceFrameCommand(int currentFrameIndex, const QImage imgToReplace, const QImage imgToRestore, QUndoCommand *parent = nullptr);
explicit ReplaceFrameCommand(int currentFrameIndex, const QImage imgToReplace, const QImage imgToRestore);
~ReplaceFrameCommand() = default;

void undo() override;
Expand All @@ -43,11 +44,11 @@ class ReplaceFrameCommand : public QObject, public QUndoCommand {
int frameIndexToReplace = 0;
};

class AddFrameCommand : public QObject, public QUndoCommand {
class AddFrameCommand : public QObject, public Command {
Q_OBJECT

public:
explicit AddFrameCommand(IMAGE_FILE_MODE mode, int index, const QString imagefilePath, QUndoCommand *parent = nullptr);
explicit AddFrameCommand(IMAGE_FILE_MODE mode, int index, const QString imagefilePath);
~AddFrameCommand() = default;

void undo() override;
Expand Down
25 changes: 13 additions & 12 deletions source/levelcelview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,11 @@
#include <QMenu>
#include <QMessageBox>
#include <QMimeData>
#include <utility>

LevelCelView::LevelCelView(std::shared_ptr<QUndoStack> us, QWidget *parent)
LevelCelView::LevelCelView(std::shared_ptr<UndoStack> us, QWidget *parent)
: QWidget(parent)
, undoStack(us)
, undoStack(std::move(us))
, ui(new Ui::LevelCelView())
, celScene(new CelScene(this))
{
Expand Down Expand Up @@ -375,17 +376,17 @@ void LevelCelView::insertFrames(IMAGE_FILE_MODE mode, const QStringList &imagefi

void LevelCelView::sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath)
{
AddFrameCommand *command;
std::unique_ptr<Command> command;
try {
command = new AddFrameCommand(mode, index, imagefilePath);
command = std::make_unique<AddFrameCommand>(mode, index, imagefilePath);
} catch (...) {
QMessageBox::critical(this, "Error", "Failed to read image file: " + imagefilePath);
return;
}

// send a command to undostack, making adding frame undo/redoable
QObject::connect(command, &AddFrameCommand::added, this, static_cast<void (LevelCelView::*)(int startingIndex, const std::vector<QImage> &images, IMAGE_FILE_MODE mode)>(&LevelCelView::insertFrames));
QObject::connect(command, &AddFrameCommand::undoAdded, this, &LevelCelView::removeFrames);
QObject::connect(dynamic_cast<AddFrameCommand *>(command.get()), &AddFrameCommand::added, this, static_cast<void (LevelCelView::*)(int startingIndex, const std::vector<QImage> &images, IMAGE_FILE_MODE mode)>(&LevelCelView::insertFrames));
QObject::connect(dynamic_cast<AddFrameCommand *>(command.get()), &AddFrameCommand::undoAdded, this, &LevelCelView::removeFrames);

undoStack->push(command);
}
Expand Down Expand Up @@ -729,9 +730,9 @@ void LevelCelView::sendReplaceCurrentFrameCmd(const QString &imagefilePath)
}

// send a command to undostack, making replacing frame undo/redoable
ReplaceFrameCommand *command = new ReplaceFrameCommand(this->currentFrameIndex, image, this->gfx->getFrameImage(this->currentFrameIndex));
QObject::connect(command, &ReplaceFrameCommand::replaced, this, &LevelCelView::replaceCurrentFrame);
QObject::connect(command, &ReplaceFrameCommand::undoReplaced, this, &LevelCelView::replaceCurrentFrame);
std::unique_ptr<Command> command = std::make_unique<ReplaceFrameCommand>(this->currentFrameIndex, image, this->gfx->getFrameImage(this->currentFrameIndex));
QObject::connect(dynamic_cast<ReplaceFrameCommand *>(command.get()), &ReplaceFrameCommand::replaced, this, &LevelCelView::replaceCurrentFrame);
QObject::connect(dynamic_cast<ReplaceFrameCommand *>(command.get()), &ReplaceFrameCommand::undoReplaced, this, &LevelCelView::replaceCurrentFrame);

undoStack->push(command);
}
Expand Down Expand Up @@ -800,9 +801,9 @@ void LevelCelView::sendRemoveFrameCmd()
}

// send a command to undostack, making deleting frame undo/redoable
RemoveFrameCommand *command = new RemoveFrameCommand(this->currentFrameIndex, this->gfx->getFrameImage(this->currentFrameIndex));
QObject::connect(command, &RemoveFrameCommand::removed, this, &LevelCelView::removeCurrentFrame);
QObject::connect(command, &RemoveFrameCommand::inserted, this, static_cast<void (LevelCelView::*)(int, const QImage)>(&LevelCelView::insertFrame));
std::unique_ptr<Command> command = std::make_unique<RemoveFrameCommand>(this->currentFrameIndex, this->gfx->getFrameImage(this->currentFrameIndex));
QObject::connect(dynamic_cast<RemoveFrameCommand *>(command.get()), &RemoveFrameCommand::removed, this, &LevelCelView::removeCurrentFrame);
QObject::connect(dynamic_cast<RemoveFrameCommand *>(command.get()), &RemoveFrameCommand::inserted, this, static_cast<void (LevelCelView::*)(int, const QImage)>(&LevelCelView::insertFrame));

this->undoStack->push(command);
}
Expand Down
6 changes: 3 additions & 3 deletions source/levelcelview.h
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
#include <QImage>
#include <QPoint>
#include <QTimer>
#include <QUndoStack>
#include <QWidget>

#include <memory>
Expand All @@ -26,6 +25,7 @@
#include "leveltabframewidget.h"
#include "leveltabsubtilewidget.h"
#include "leveltabtilewidget.h"
#include "undostack.h"

namespace Ui {
class LevelCelView;
Expand All @@ -43,7 +43,7 @@ class LevelCelView : public QWidget {
Q_OBJECT

public:
explicit LevelCelView(std::shared_ptr<QUndoStack> us, QWidget *parent = nullptr);
explicit LevelCelView(std::shared_ptr<UndoStack> us, QWidget *parent = nullptr);
~LevelCelView();

void initialize(D1Gfx *gfx, D1Min *min, D1Til *til, D1Sol *sol, D1Amp *amp);
Expand Down Expand Up @@ -160,7 +160,7 @@ private slots:
void insertFrame(int index, QImage image);

private:
std::shared_ptr<QUndoStack> undoStack;
std::shared_ptr<UndoStack> undoStack;
Ui::LevelCelView *ui;
CelScene *celScene;
LevelTabTileWidget *tabTileWidget = new LevelTabTileWidget();
Expand Down
28 changes: 23 additions & 5 deletions source/mainwindow.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#include "mainwindow.h"

#include <QAction>
#include <QDragEnterEvent>
#include <QFile>
#include <QFileDialog>
Expand All @@ -18,8 +19,6 @@
#include <QStringList>
#include <QTextStream>
#include <QTime>
#include <QUndoCommand>
#include <QUndoStack>
#include <QtWidgets>

#include "config.h"
Expand Down Expand Up @@ -47,11 +46,11 @@ MainWindow::MainWindow()
this->ui->menuFile->insertMenu(firstFileAction, &this->newMenu);

// Initialize 'Undo/Redo' of 'Edit
this->undoStack = std::make_shared<QUndoStack>(this);
this->undoAction = undoStack->createUndoAction(this, "Undo");
this->undoStack = std::make_shared<UndoStack>();
this->undoAction = new QAction("Undo", this);
this->undoAction->setShortcuts(QKeySequence::Undo);
this->ui->menuEdit->addAction(this->undoAction);
this->redoAction = undoStack->createRedoAction(this, "Redo");
this->redoAction = new QAction("Redo", this);
this->redoAction->setShortcuts(QKeySequence::Redo);
this->ui->menuEdit->addAction(this->redoAction);
this->ui->menuEdit->addSeparator();
Expand All @@ -60,6 +59,10 @@ MainWindow::MainWindow()
this->ui->menuEdit->addAction(this->redoAction);
this->ui->menuEdit->addSeparator();

// Bind Undo/Redo actions to appropriate slots
QObject::connect(this->undoAction, SIGNAL(triggered()), this, SLOT(actionUndo_triggered()));
QObject::connect(this->redoAction, SIGNAL(triggered()), this, SLOT(actionRedo_triggered()));

// Initialize 'Frame' submenu of 'Edit'
this->frameMenu.setToolTipsVisible(true);
this->frameMenu.addAction("Insert", this, SLOT(actionInsertFrame_triggered()))->setToolTip("Add new frames before the current one");
Expand Down Expand Up @@ -149,6 +152,7 @@ void MainWindow::updateWindow()
this->palHits->update();
this->palWidget->refresh();
this->undoAction->setEnabled(this->undoStack->canUndo());
this->redoAction->setEnabled(this->undoStack->canRedo());

// update menu options
bool hasFrame = this->gfx->getFrameCount() != 0;
Expand Down Expand Up @@ -1061,6 +1065,20 @@ void MainWindow::closeEvent(QCloseEvent *event)
event->accept();
}

void MainWindow::actionUndo_triggered()
{
this->undoStack->undo();
this->undoAction->setEnabled(this->undoStack->canUndo());
this->redoAction->setEnabled(this->undoStack->canRedo());
}

void MainWindow::actionRedo_triggered()
{
this->undoStack->redo();
this->undoAction->setEnabled(this->undoStack->canUndo());
this->redoAction->setEnabled(this->undoStack->canRedo());
}

void MainWindow::actionInsertFrame_triggered()
{
this->addFrames(false);
Expand Down
Loading

0 comments on commit c84bd84

Please sign in to comment.