Skip to content

Commit

Permalink
FrameCmds: Make insert/add frame option to be undoable/redoable
Browse files Browse the repository at this point in the history
This commit makes "insert/add frame" option to be undoable, it allows to
add/insert frame and then undo or redo the same action.
  • Loading branch information
tetektoza committed Nov 13, 2023
1 parent 6a24e17 commit ac1b038
Show file tree
Hide file tree
Showing 6 changed files with 194 additions and 60 deletions.
59 changes: 42 additions & 17 deletions source/celview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ void CelView::insertImageFiles(IMAGE_FILE_MODE mode, const QStringList &imagefil
if (append) {
// append the frame(s)
for (int i = 0; i < imagefilePaths.count(); i++) {
this->insertFrame(mode, this->gfx->getFrameCount(), imagefilePaths[i]);
this->sendAddFrameCmd(mode, this->gfx->getFrameCount(), imagefilePaths[i]);
}
int deltaFrameCount = this->gfx->getFrameCount() - prevFrameCount;
if (deltaFrameCount == 0) {
Expand All @@ -155,8 +155,10 @@ void CelView::insertImageFiles(IMAGE_FILE_MODE mode, const QStringList &imagefil
this->updateGroupIndex();
} else {
// insert the frame(s)
int j = 0;
for (int i = imagefilePaths.count() - 1; i >= 0; i--) {
this->insertFrame(mode, this->currentFrameIndex, imagefilePaths[i]);
this->sendAddFrameCmd(mode, this->currentFrameIndex + j, imagefilePaths[i]);
j++;
}
int deltaFrameCount = this->gfx->getFrameCount() - prevFrameCount;
if (deltaFrameCount == 0) {
Expand Down Expand Up @@ -198,27 +200,50 @@ void CelView::insertImageFile(int frameIdx, const QImage img)
this->displayFrame();
}

void CelView::insertFrame(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath)
void CelView::insertFrames(IMAGE_FILE_MODE mode, int startingIndex, const std::vector<QImage> &images)
{
QImageReader reader = QImageReader(imagefilePath);
int numImages = 0;
int prevFrameCount = this->gfx->getFrameCount();

// 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;
}
for (int idx = 0; idx < images.size(); idx++) {
this->gfx->insertFrame(startingIndex + idx, images[idx]);
}

this->gfx->insertFrame(index + numImages, image);
numImages++;
int deltaFrameCount = this->gfx->getFrameCount() - prevFrameCount;
if (deltaFrameCount == 0) {
return; // no new frame -> done
}

if (mode != IMAGE_FILE_MODE::AUTO && numImages == 0) {
QMessageBox::critical(this, "Error", "Failed read image file: " + imagefilePath);
this->updateGroupIndex();

// update the view
this->initialize(this->gfx);
this->displayFrame();
}

void CelView::removeFrames(int startingIndex, int endingIndex)
{
for (int idx = startingIndex; idx < endingIndex; idx++)
this->removeCurrentFrame(idx);
}

void CelView::sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath)
{
AddFrameCommand *command;
try {
command = new AddFrameCommand(mode, index, imagefilePath);
}
catch (...)
{
QMessageBox::critical(this, "Error", "Failed to read image file: " + imagefilePath);
delete command;
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);

undoStack->push(command);
}

void CelView::replaceCurrentFrame(int frameIdx, const QImage &image)
Expand Down
4 changes: 4 additions & 0 deletions source/celview.h
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,8 @@ class CelView : public QWidget {

void initialize(D1Gfx *gfx);
void sendRemoveFrameCmd();
void sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath);
void updateCurrentFrameIndex(int frameIdx);
int getCurrentFrameIndex();
void framePixelClicked(unsigned x, unsigned y);
void insertImageFiles(IMAGE_FILE_MODE mode, const QStringList &imagefilePaths, bool append);
Expand All @@ -71,7 +73,9 @@ class CelView : public QWidget {

private:
void update();
void removeFrames(int startingIndex, int endingIndex);
void insertFrame(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath);
void insertFrames(IMAGE_FILE_MODE mode, int startingIndex, const std::vector<QImage> &images);
void setGroupIndex();

private slots:
Expand Down
40 changes: 40 additions & 0 deletions source/framecmds.cpp
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
#include <QImageReader>

#include <stdexcept>

#include "framecmds.h"
#include "mainwindow.h"

RemoveFrameCommand::RemoveFrameCommand(int currentFrameIndex, const QImage img, QUndoCommand *parent) :
frameIndexToRevert(currentFrameIndex), imgToRevert(img)
Expand Down Expand Up @@ -32,3 +37,38 @@ void ReplaceFrameCommand::redo()
emit this->replaced(frameIndexToReplace, imgToReplace);
}


AddFrameCommand::AddFrameCommand(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath, QUndoCommand *parent) : startingIndex(index), 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);
}

void AddFrameCommand::redo()
{
emit this->added(mode, startingIndex, images);
}
21 changes: 21 additions & 0 deletions source/framecmds.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,3 +45,24 @@ class ReplaceFrameCommand : public QObject, public QUndoCommand {
QImage imgToRestore;
int frameIndexToReplace = 0;
};

class AddFrameCommand : public QObject, public QUndoCommand {
Q_OBJECT

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

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

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

private:
std::vector<QImage> images;
int startingIndex = 0;
int endingIndex = 0;
IMAGE_FILE_MODE mode;
};
125 changes: 83 additions & 42 deletions source/levelcelview.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@

#include <algorithm>
#include <set>
#include <iostream>

#include "d1image.h"
#include "mainwindow.h"
Expand Down Expand Up @@ -280,6 +279,8 @@ void LevelCelView::assignFrames(const QImage &image, int subtileIndex, int frame

void LevelCelView::insertFrame(IMAGE_FILE_MODE mode, int index, const QImage &image)
{
// FIXME: investigate if adding multiple frames, and having a frame that is not in
// proper dimensions will screw up frame list, especially in appending operations
if ((image.width() % MICRO_WIDTH) != 0 || (image.height() % MICRO_HEIGHT) != 0) {
QMessageBox::critical(this, tr("Error!"), tr("Wrong frame dimensions!\n"
"Image should have dimensions %1x%2px (w x h).\n"
Expand All @@ -304,51 +305,26 @@ void LevelCelView::insertFrame(IMAGE_FILE_MODE mode, int index, const QImage &im
this->assignFrames(image, -1, index);
}

void LevelCelView::insertFrames(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath)
void LevelCelView::insertFrames(IMAGE_FILE_MODE mode, int startingIndex, const std::vector<QImage> &images)
{
QImageReader reader = QImageReader(imagefilePath);
int numImages = 0;
int prevFrameCount = this->gfx->getFrameCount();

while (true) {
QImage image = reader.read();
if (image.isNull()) {
break;
}
this->insertFrame(mode, index + numImages, image);
numImages++;
for (int idx = 0; idx < images.size(); idx++) {
this->insertFrame(mode, startingIndex + idx, images[idx]);
}

if (mode != IMAGE_FILE_MODE::AUTO && numImages == 0) {
QMessageBox::critical(this, "Error", "Failed read image file: " + imagefilePath);
int deltaFrameCount = this->gfx->getFrameCount() - prevFrameCount;
if (deltaFrameCount == 0) {
return; // no new frame -> done
}
}

void LevelCelView::insertFrames(IMAGE_FILE_MODE mode, const QStringList &imagefilePaths, bool append)
{
int prevFrameCount = this->gfx->getFrameCount();

if (append) {
// append the frame(s)
for (int i = 0; i < imagefilePaths.count(); i++) {
this->insertFrames(mode, this->gfx->getFrameCount(), imagefilePaths[i]);
}
int deltaFrameCount = this->gfx->getFrameCount() - prevFrameCount;
if (deltaFrameCount == 0) {
return; // no new frame -> done
}
// jump to the first appended frame
this->currentFrameIndex = prevFrameCount;
} else {
// insert the frame(s)
for (int i = imagefilePaths.count() - 1; i >= 0; i--) {
this->insertFrames(mode, this->currentFrameIndex, imagefilePaths[i]);
}
int deltaFrameCount = this->gfx->getFrameCount() - prevFrameCount;
if (deltaFrameCount == 0) {
return; // no new frame -> done
}
// If we are inserting (not appending), then we have to
// shift all tiles references higher than inserted frame's index to the right.
// Otherwise, if we are appending - just update currentFrameIndex to the one first
// appended frame
if (startingIndex + 1 != this->gfx->getFrameCount()) {
// shift references
unsigned refIndex = this->currentFrameIndex + 1;
unsigned refIndex = startingIndex + 1;
// shift frame indices of the subtiles
for (int i = 0; i < this->min->getSubtileCount(); i++) {
QList<quint16> &frameIndices = this->min->getCelFrameIndices(i);
Expand All @@ -359,11 +335,65 @@ void LevelCelView::insertFrames(IMAGE_FILE_MODE mode, const QStringList &imagefi
}
}
}
else {
this->currentFrameIndex = prevFrameCount;
}

// If this function is used in undo stack and this operation came after redo,
// then we have to renew previously used indices of frames
if (!tilesAndFramesIdxStack.empty()) {
for (int idx = 0; idx < images.size(); idx++) {
auto& vec = tilesAndFramesIdxStack.top();
for (auto& pair : vec) {
QList<quint16> &frameIndices = this->min->getCelFrameIndices(pair.first);
frameIndices[pair.second] = (startingIndex + idx) + 1;
}

tilesAndFramesIdxStack.pop();
}
}

// update the view
this->update();
this->displayFrame();
}

void LevelCelView::insertFrames(IMAGE_FILE_MODE mode, const QStringList &imagefilePaths, bool append)
{
if (append) {
// append the frame(s)
for (int i = 0; i < imagefilePaths.count(); i++) {
this->sendAddFrameCmd(mode, this->gfx->getFrameCount(), imagefilePaths[i]);
}
} else {
// insert the frame(s)
for (int i = imagefilePaths.count() - 1; i >= 0; i--) {
this->sendAddFrameCmd(mode, this->currentFrameIndex, imagefilePaths[i]);
}
}
}

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

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

undoStack->push(command);
}

void LevelCelView::insertFrame(int index, const QImage image)
{
Expand Down Expand Up @@ -730,12 +760,12 @@ void LevelCelView::removeFrame(int frameIndex)
if (this->gfx->getFrameCount() == this->currentFrameIndex) {
this->currentFrameIndex = std::max(0, this->currentFrameIndex - 1);
}
// shift references
// - shift frame indices of the subtiles
unsigned refIndex = frameIndex + 1;

tilesAndFramesIdxStack.push(std::vector<std::pair<int, int>>());

// shift references
// - shift frame indices of the subtiles
for (int i = 0; i < this->min->getSubtileCount(); i++) {
QList<quint16> &frameIndices = this->min->getCelFrameIndices(i);
for (int n = 0; n < frameIndices.count(); n++) {
Expand Down Expand Up @@ -794,6 +824,17 @@ void LevelCelView::removeCurrentFrame(int frameIdx)
this->displayFrame();
}

void LevelCelView::removeFrames(int startingIdx, int endingIndex)
{
for (int idx = startingIdx; idx < endingIndex; idx++) {
this->removeCurrentFrame(idx);
}

// update the view
this->update();
this->displayFrame();
}

void LevelCelView::createSubtile()
{
this->min->createSubtile();
Expand Down Expand Up @@ -1721,7 +1762,7 @@ void LevelCelView::on_frameIndexEdit_returnPressed()
this->currentFrameIndex = frameIndex;

if (this->mode == TILESET_MODE::SUBTILE) {
this->min->getCelFrameIndices(this->currentSubtileIndex)[this->editIndex] = this->currentFrameIndex;
this->min->getCelFrameIndices(this->currentSubtileIndex)[this->editIndex] = this->currentFrameIndex + 1;
} else {
this->mode = TILESET_MODE::FREE;
this->update();
Expand Down
5 changes: 4 additions & 1 deletion source/levelcelview.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ class LevelCelView : public QWidget {

void insertImageFiles(IMAGE_FILE_MODE mode, const QStringList &imagefilePaths, bool append);

void sendAddFrameCmd(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath);

void sendReplaceCurrentFrameCmd(const QString &imagefilePath);
void replaceCurrentFrame(int frameIdx, const QImage &image);

Expand Down Expand Up @@ -87,8 +89,8 @@ class LevelCelView : public QWidget {
void collectFrameUsers(int frameIndex, QList<int> &users) const;
void collectSubtileUsers(int subtileIndex, QList<int> &users) const;
void insertFrame(IMAGE_FILE_MODE mode, int index, const QImage &image);
void insertFrames(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath);
void insertFrames(IMAGE_FILE_MODE mode, const QStringList &imagefilePaths, bool append);
void insertFrames(IMAGE_FILE_MODE mode, int startingIndex, const std::vector<QImage> &images);
void insertSubtile(int subtileIndex, const QImage &image);
void insertSubtiles(IMAGE_FILE_MODE mode, int index, const QImage &image);
void insertSubtiles(IMAGE_FILE_MODE mode, int index, const QString &imagefilePath);
Expand All @@ -100,6 +102,7 @@ class LevelCelView : public QWidget {
void assignFrames(const QImage &image, int subtileIndex, int frameIndex);
void assignSubtiles(const QImage &image, int tileIndex, int subtileIndex);
void removeFrame(int frameIndex);
void removeFrames(int startingIdx, int endingIndex);
void removeSubtile(int subtileIndex);
void removeUnusedFrames(QString &report);
void removeUnusedSubtiles(QString &report);
Expand Down

0 comments on commit ac1b038

Please sign in to comment.