Skip to content

Commit

Permalink
Everywhere: Integrate UndoMacro with the rest of the application
Browse files Browse the repository at this point in the history
  • Loading branch information
tetektoza committed Jan 28, 2024
1 parent acc0892 commit 6138786
Show file tree
Hide file tree
Showing 12 changed files with 323 additions and 112 deletions.
41 changes: 40 additions & 1 deletion source/mainwindow.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,13 @@ MainWindow::MainWindow()
this->redoAction = new QAction("Redo", this);
this->redoAction->setShortcuts(QKeySequence::Redo);
this->ui->menuEdit->addAction(this->redoAction);

// Initialize widgets used for Undo/Redo
this->m_progressDialog = std::make_unique<QProgressDialog>(this);
this->m_progressDialog->close();
this->m_progressDialog->setAutoReset(true);
this->m_progressDialog->reset();
this->m_progressDialog->setWindowModality(Qt::WindowModal);
this->ui->menuEdit->addSeparator();

this->ui->menuEdit->addAction(this->undoAction);
Expand Down Expand Up @@ -748,7 +755,7 @@ void MainWindow::openPalFiles(QStringList filePaths, PaletteWidget *widget)
this->palWidget->selectPath(firstFound);
}
} else if (widget == this->trnUniqueWidget) {
for (QString path : filePaths) {
for (const QString &path : filePaths) {
if (this->loadTrnUnique(path) && firstFound.isEmpty()) {
firstFound = path;
}
Expand Down Expand Up @@ -1681,6 +1688,38 @@ void MainWindow::on_actionClose_Translation_triggered()
this->trnWidget->selectPath(D1Trn::IDENTITY_PATH);
}

void MainWindow::setupUndoMacroWidget(std::unique_ptr<UserData> &userData, enum OperationType opType)
{
this->m_currMacroOpType = opType;
int prevMaximum = this->m_progressDialog->maximum();

this->m_progressDialog->reset();
this->m_progressDialog->show();
this->m_progressDialog->setLabelText(userData->labelText());
this->m_progressDialog->setCancelButtonText(userData->cancelButtonText());
this->m_progressDialog->setMinimum(userData->min());
this->m_progressDialog->setMaximum(userData->max());

if (opType == OperationType::Undo && prevMaximum == userData->max())
this->m_currProgDialogPos = this->m_progressDialog->maximum() - m_currProgDialogPos;
else if (this->m_currProgDialogPos == prevMaximum || prevMaximum != userData->max())
this->m_currProgDialogPos = 0;
}

void MainWindow::updateUndoMacroWidget(bool &result)
{
this->m_currProgDialogPos++;

this->m_progressDialog->setValue(m_currProgDialogPos);
if (this->m_progressDialog->wasCanceled()) {
this->m_progressDialog->reset();
result = true;
return;
}

QCoreApplication::processEvents();
}

#if defined(Q_OS_WIN)
#define OS_TYPE "Windows"
#elif defined(Q_OS_QNX)
Expand Down
9 changes: 9 additions & 0 deletions source/mainwindow.h
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,10 @@ public slots:
void actionReplaceTile_triggered();
void actionDelTile_triggered();

// slots used for UndoMacro signals
void setupUndoMacroWidget(std::unique_ptr<UserData> &userData, enum OperationType opType);
void updateUndoMacroWidget(bool &result);

void buildRecentFilesMenu();
void addRecentFile(QString filePath);
void on_actionClear_History_triggered();
Expand Down Expand Up @@ -197,6 +201,11 @@ private slots:
QMap<QString, D1Trn *> trns; // key: path, value: pointer to translation
QMap<QString, D1Trn *> trnsUnique; // key: path, value: pointer to translation

std::unique_ptr<QProgressDialog> m_progressDialog;

// Palette hits are instantiated in main window to make them available to the three PaletteWidgets
QPointer<D1PalHits> palHits;

int m_currProgDialogPos { 0 };
enum OperationType m_currMacroOpType;
};
10 changes: 10 additions & 0 deletions source/undostack/command.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,13 @@ bool Command::isObsolete() const
{
return m_isObsolete;
}

void Command::setMacroID(unsigned int macroID)
{
m_macroID = macroID;
}

unsigned int Command::macroID() const
{
return m_macroID;
}
3 changes: 3 additions & 0 deletions source/undostack/command.h
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,12 @@ class Command {

void setObsolete(bool isObsolete);
bool isObsolete() const;
void setMacroID(unsigned int macroID);
unsigned int macroID() const;

virtual ~Command() = default;

private:
unsigned int m_macroID { 0 };
bool m_isObsolete = false;
};
32 changes: 6 additions & 26 deletions source/undostack/framecmds.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,39 +40,19 @@ void ReplaceFrameCommand::redo()
emit this->replaced(frameIndexToReplace, imgToReplace);
}

AddFrameCommand::AddFrameCommand(IMAGE_FILE_MODE mode, int index, const QString imagefilePath)
: startingIndex(index)
, mode(mode)
AddFrameCommand::AddFrameCommand(int index, QImage &img, IMAGE_FILE_MODE mode)
: m_index(index)
, m_image(std::move(img))
, m_mode(mode)
{
QImageReader reader = QImageReader(imagefilePath);
int numImages = 0;

// FIXME: this loop should have some sort of a progress bar, we support
// status bar, but if user loads a .gif which could contain up to hundreds
// of frames, loading might take quite a bit
while (true) {
QImage image = reader.read();
if (image.isNull()) {
break;
}

images.emplace_back(image);
numImages++;
}

if (mode != IMAGE_FILE_MODE::AUTO && numImages == 0) {
throw std::exception();
}

endingIndex = startingIndex + numImages;
}

void AddFrameCommand::undo()
{
emit this->undoAdded(startingIndex, endingIndex);
emit this->undoAdded(m_index);
}

void AddFrameCommand::redo()
{
emit this->added(startingIndex, images, mode);
emit this->added(m_index, m_image, m_mode);
}
13 changes: 6 additions & 7 deletions source/undostack/framecmds.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,18 @@ class AddFrameCommand : public QObject, public Command {
Q_OBJECT

public:
explicit AddFrameCommand(IMAGE_FILE_MODE mode, int index, const QString imagefilePath);
explicit AddFrameCommand(int index, QImage &image, IMAGE_FILE_MODE mode);
~AddFrameCommand() = default;

void undo() override;
void redo() override;

signals:
void undoAdded(int startingIndex, int endingIndex);
void added(int startingIndex, const std::vector<QImage> &images, IMAGE_FILE_MODE mode);
void undoAdded(int index);
void added(int index, const QImage &image, IMAGE_FILE_MODE mode);

private:
std::vector<QImage> images;
int startingIndex = 0;
int endingIndex = 0;
IMAGE_FILE_MODE mode;
QImage m_image;
int m_index = 0;
IMAGE_FILE_MODE m_mode;
};
126 changes: 112 additions & 14 deletions source/undostack/undostack.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#include "undostack.h"
#include <QDebug>

/**
* @brief Pushes new commands onto the commands stack (Undostack)
Expand All @@ -19,12 +20,7 @@ void UndoStack::push(std::unique_ptr<Command> cmd)
*/
}

// Erase any command that was set to obsolete
std::erase_if(m_cmds, [](const auto &cmd) { return cmd->isObsolete(); });

// Drop any command that's after currently undo'd index
if (m_cmds.begin() + (m_undoPos + 1) <= m_cmds.end())
m_cmds.erase(m_cmds.begin() + (m_undoPos + 1), m_cmds.end());
eraseRedundantCmds();

m_cmds.push_back(std::move(cmd));
m_canUndo = true;
Expand All @@ -44,13 +40,31 @@ void UndoStack::undo()
// Erase any command that was previously set as obsolete
std::erase_if(m_cmds, [](const auto &cmd) { return cmd->isObsolete(); });

if (m_undoPos == 0)
m_canUndo = false;
/* If current processed command has a macroID higher than 0, then it means it's a macro.
* So we need to start going through commands backwards in a loop
*/
unsigned int macroID = m_cmds[m_undoPos]->macroID();
if (macroID > 0) {
emit initializeWidget(m_macros[macroID - 1].userdata(), OperationType::Undo);

m_cmds[m_undoPos]->undo();
while (m_cmds[m_undoPos]->macroID() > 0) {
bool result = false;
m_cmds[m_undoPos]->undo();
m_undoPos--;
emit updateWidget(result);
if (result || m_undoPos < 0 || m_cmds[m_undoPos + 1]->macroID() != m_cmds[m_undoPos]->macroID()) {
break;
}
}
} else {
m_cmds[m_undoPos]->undo();
m_undoPos--;
}

if (m_undoPos < 0)
m_canUndo = false;

m_canRedo = true;
m_undoPos--;
}

/**
Expand All @@ -65,12 +79,27 @@ void UndoStack::redo()
// erase any command that was previously set as obsolete
std::erase_if(m_cmds, [](const auto &cmd) { return cmd->isObsolete(); });

m_cmds[m_undoPos + 1]->redo();
unsigned int macroID = m_cmds[m_undoPos + 1]->macroID();
if (macroID > 0) {
emit initializeWidget(m_macros[macroID - 1].userdata(), OperationType::Redo);

while (m_cmds[m_undoPos + 1]->macroID() > 0) {
bool result = false;
m_cmds[m_undoPos + 1]->redo();
m_undoPos++;
emit updateWidget(result);
if (result || m_undoPos + 1 >= m_cmds.size() || m_cmds[m_undoPos + 1]->macroID() != m_cmds[m_undoPos]->macroID()) {
break;
}
}
} else {
m_cmds[m_undoPos + 1]->redo();
m_undoPos++;
}

m_undoPos++;
m_canUndo = true;

if (m_undoPos + 1 > m_cmds.size() - 1)
if (m_undoPos + 1 >= m_cmds.size())
m_canRedo = false;
}

Expand Down Expand Up @@ -99,7 +128,76 @@ bool UndoStack::canUndo() const
*/
void UndoStack::clear()
{
m_undoPos = 0;
m_undoPos = -1;
m_canUndo = m_canRedo = false;
m_cmds.clear();
m_macros.clear();
}

/**
* @brief Adds a macro to undo stack
*
* This function is being called whenever someone wants to insert
* a macro in the undo stack. It calls redo() on all commands contained
* in the macro, and updates undo stack position accordingly, it also
* sets macroIDs depending on the macros' vector size.
*
*/
void UndoStack::addMacro(UndoMacroFactory &macroFactory)
{
eraseRedundantCmds();

emit initializeWidget(macroFactory.userdata(), OperationType::Redo);
m_macros.emplace_back(std::move(macroFactory.userdata()), std::make_pair<int, int>(m_cmds.size(), (m_cmds.size() + macroFactory.cmds().size()) - 1));

for (auto &cmd : macroFactory.cmds()) {
bool result = false;
cmd->redo();
m_undoPos++;

emit updateWidget(result);
if (result) {
m_canRedo = true;
break;
}
}

// For each command that will be inserted set a macroID so it is located in the same span
std::for_each(macroFactory.cmds().begin(), macroFactory.cmds().end(), [&](const std::unique_ptr<Command> &cmd) {
cmd->setMacroID(m_macros.size());
});

m_cmds.insert(m_cmds.end(), std::make_move_iterator(macroFactory.cmds().begin()), std::make_move_iterator(macroFactory.cmds().end()));
m_canUndo = true;
}

/**
* @brief Erases redundant commands and macros from the undo stack
*
* This function erases redundant commands and macros from both vectors
* in undostack. Redundant in this case means commands which will no longer
* be available, i.e. command being obsolete (having obsolete flag set to true), or
* all commands + macros after currently pushed command - upon insertion undo stack removes all
* commands + macros that are after current undo stack position (were undo'd) and are possible
* to redo.
*
*/
void UndoStack::eraseRedundantCmds()
{
// Erase any command that was set to obsolete
std::erase_if(m_cmds, [](const auto &cmd) { return cmd->isObsolete(); });

if (m_cmds.begin() + (m_undoPos + 1) < m_cmds.end()) {
// Drop any command that's after currently undo'd index
m_cmds.erase(m_cmds.begin() + (m_undoPos + 1), m_cmds.end());

// Drop any macro that's after currently undo'd index
std::erase_if(m_macros, [&](const auto &macro) { return macro.beginIndex() > m_undoPos; });

// If undoPos is currently on a macro, then update it's ending index because we could have removed some of
// it's commands
unsigned int macroID = m_cmds[m_undoPos]->macroID();
if (macroID > 0)
m_macros[macroID - 1].setLastIndex(m_undoPos);
}
}
22 changes: 20 additions & 2 deletions source/undostack/undostack.h
Original file line number Diff line number Diff line change
@@ -1,12 +1,22 @@
#pragma once

#include "command.h"
#include "undomacro.h"

#include <QObject>
#include <array>
#include <memory>
#include <utility>
#include <vector>

class UndoStack {
enum OperationType {
Undo,
Redo
};

class UndoStack : public QObject {
Q_OBJECT

public:
UndoStack() = default;
~UndoStack() = default;
Expand All @@ -19,10 +29,18 @@ class UndoStack {
[[nodiscard]] bool canRedo() const;

void clear();
void addMacro(UndoMacroFactory &macroFactory);

signals:
void updateWidget(bool &userCancelled);
void initializeWidget(std::unique_ptr<UserData> &userData, enum OperationType opType);

private:
bool m_canUndo = false;
bool m_canRedo = false;
int8_t m_undoPos = 0;
int8_t m_undoPos { -1 };
std::vector<std::unique_ptr<Command>> m_cmds; // holds all the commands on the stack
std::vector<UndoMacro> m_macros;

void eraseRedundantCmds();
};
Loading

0 comments on commit 6138786

Please sign in to comment.