Fully implemented undo for: add/delete/edit of keys

Also takes into account if more than one key is being modified at once and such being handled as a collection of commands in a multi-command so it looks hand behaves correct from the users perspective.

Closes #6
This commit is contained in:
Daniel Collin 2012-12-30 19:07:33 +01:00
parent f4c315c137
commit 49af1595f0
4 changed files with 499 additions and 18 deletions

429
ogl_editor/src/Commands.c Normal file
View File

@ -0,0 +1,429 @@
#include "Commands.h"
#include "RemoteConnection.h"
#include "Types.h"
#include "../../sync/sync.h"
#include "../../sync/track.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static struct sync_track** s_syncTracks;
static struct TrackData* s_trackData;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct Command
{
void* userData;
void (*exec)(void* userData);
void (*undo)(void* userData);
struct Command* next;
struct Command* prev;
} Command;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
typedef struct CommandList
{
Command* first;
Command* last;
} CommandList;
static CommandList s_undoStack;
static CommandList s_redoStack;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void CommandList_addEntry(CommandList* commandList, Command* command);
//static void CommandList_delEntry(CommandList* commandList, Command* command);
static void CommandList_clear(CommandList* commandList);
static bool CommandList_isEmpty(CommandList* list);
static void CommandList_pop(CommandList* commandList);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct MultiCommandData
{
CommandList list;
};
static struct MultiCommandData* s_multiCommand;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_init(struct sync_track** syncTracks, struct TrackData* trackData)
{
s_syncTracks = syncTracks;
s_trackData = trackData;
memset(&s_undoStack, 0, sizeof(CommandList));
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void execCommand(Command* command)
{
// set if we have multi command recording enabled)
if (s_multiCommand)
{
CommandList_addEntry(&s_multiCommand->list, command);
}
else
{
CommandList_addEntry(&s_undoStack, command);
command->exec(command->userData);
}
CommandList_clear(&s_redoStack);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_beginMulti()
{
s_multiCommand = malloc(sizeof(struct MultiCommandData));
memset(s_multiCommand, 0, sizeof(struct MultiCommandData));
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void execMultiCommand(void* userData)
{
Command* command;
struct MultiCommandData* data = (struct MultiCommandData*)userData;
for (command = data->list.first; command; command = command->next)
command->exec(command->userData);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void undoMultiCommand(void* userData)
{
Command* command;
struct MultiCommandData* data = (struct MultiCommandData*)userData;
for (command = data->list.first; command; command = command->next)
command->undo(command->userData);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_endMulti()
{
Command* command;
// Check if any command was added during multi command
if (CommandList_isEmpty(&s_multiCommand->list))
{
free(s_multiCommand);
s_multiCommand = 0;
return;
}
command = malloc(sizeof(Command));
memset(command, 0, sizeof(Command));
command->userData = s_multiCommand;
command->exec = execMultiCommand;
command->undo = undoMultiCommand;
s_multiCommand = 0;
execCommand(command);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct DeleteKeyData
{
int track;
int row;
struct track_key oldKey;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void execDeleteKey(void* userData)
{
struct DeleteKeyData* data = (struct DeleteKeyData*)userData;
struct sync_track* t = s_syncTracks[data->track];
int idx = sync_find_key(t, data->row);
data->oldKey = t->keys[idx];
sync_del_key(t, data->row);
RemoteConnection_sendDeleteKeyCommand(t->name, data->row);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void undoDeleteKey(void* userData)
{
struct DeleteKeyData* data = (struct DeleteKeyData*)userData;
struct sync_track* t = s_syncTracks[data->track];
sync_set_key(t, &data->oldKey);
RemoteConnection_sendSetKeyCommand(t->name, &data->oldKey);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_deleteKey(int track, int row)
{
struct DeleteKeyData* data;
Command* command;
struct sync_track* t = s_syncTracks[track];
if (!is_key_frame(t, row))
return;
command = malloc(sizeof(Command));
memset(command, 0, sizeof(Command));
command->userData = data = malloc(sizeof(struct DeleteKeyData));
command->exec = execDeleteKey;
command->undo = undoDeleteKey;
data->track = track;
data->row = row;
execCommand(command);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct UpdateKeyData
{
int track;
struct track_key key;
struct track_key oldKey;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void execUpdateKey(void* userData)
{
struct UpdateKeyData* data = (struct UpdateKeyData*)userData;
struct sync_track* t = s_syncTracks[data->track];
int idx = sync_find_key(t, data->key.row);
data->oldKey = t->keys[idx];
sync_set_key(t, &data->key);
RemoteConnection_sendSetKeyCommand(t->name, &data->key);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void undoUpdateKey(void* userData)
{
struct UpdateKeyData* data = (struct UpdateKeyData*)userData;
struct sync_track* t = s_syncTracks[data->track];
sync_set_key(t, &data->oldKey);
RemoteConnection_sendSetKeyCommand(t->name, &data->oldKey);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_updateKey(int track, struct track_key* key)
{
struct UpdateKeyData* data;
Command* command;
command = malloc(sizeof(Command));
memset(command, 0, sizeof(Command));
command->userData = data = malloc(sizeof(struct UpdateKeyData));
command->exec = execUpdateKey;
command->undo = undoUpdateKey;
data->track = track;
data->key = *key;
execCommand(command);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
struct InsertKeyData
{
int track;
struct track_key key;
};
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void execInsertKey(void* userData)
{
struct InsertKeyData* data = (struct InsertKeyData*)userData;
struct sync_track* t = s_syncTracks[data->track];
sync_set_key(t, &data->key);
RemoteConnection_sendSetKeyCommand(t->name, &data->key);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void undoInsertKey(void* userData)
{
struct InsertKeyData* data = (struct InsertKeyData*)userData;
struct sync_track* t = s_syncTracks[data->track];
sync_del_key(t, data->key.row);
RemoteConnection_sendDeleteKeyCommand(t->name, data->key.row);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_addOrUpdateKey(int track, struct track_key* key)
{
struct InsertKeyData* data;
Command* command;
struct sync_track* t = s_syncTracks[track];
if (is_key_frame(t, key->row))
{
Commands_updateKey(track, key);
return;
}
command = malloc(sizeof(Command));
memset(command, 0, sizeof(Command));
command->userData = data = malloc(sizeof(struct InsertKeyData));
command->exec = execInsertKey;
command->undo = undoInsertKey;
data->track = track;
data->key = *key;
execCommand(command);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_undo()
{
Command* command;
if (CommandList_isEmpty(&s_undoStack))
return;
command = s_undoStack.last;
CommandList_pop(&s_undoStack);
command->prev = 0;
command->next = 0;
CommandList_addEntry(&s_redoStack, command);
command->undo(command->userData);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_redo()
{
Command* command;
if (CommandList_isEmpty(&s_redoStack))
return;
command = s_redoStack.last;
CommandList_pop(&s_redoStack);
CommandList_addEntry(&s_undoStack, command);
command->exec(command->userData);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void CommandList_addEntry(CommandList* list, Command* command)
{
if (list->last)
{
list->last->next = command;
command->prev = list->last;
list->last = command;
}
else
{
list->first = command;
list->last = command;
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void CommandList_unlinkEntry(CommandList* list, Command* command)
{
Command* prev;
Command* next;
prev = command->prev;
next = command->next;
if (prev)
{
if (next)
{
prev->next = next;
next->prev = prev;
}
else
{
prev->next = 0;
list->last = prev;
}
}
else
{
if (next)
{
next->prev = 0;
list->first = next;
}
else
{
list->first = 0;
list->last = 0;
}
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void CommandList_delEntry(CommandList* list, Command* command)
{
CommandList_unlinkEntry(list, command);
free(command->userData);
free(command);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void CommandList_clear(CommandList* list)
{
while (list->last)
{
CommandList_delEntry(list, list->last);
}
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void CommandList_pop(CommandList* list)
{
CommandList_unlinkEntry(list, list->last);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static bool CommandList_isEmpty(CommandList* list)
{
return (!list->first && !list->last);
}

29
ogl_editor/src/Commands.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef _OGLEDITOR_COMMANDS_H_
#define _OGLEDITOR_COMMANDS_H_
struct sync_track;
struct track_key;
struct TrackData;
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_init(struct sync_track** syncTracks, struct TrackData* trackData);
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
int Commands_needsSave();
void Commands_undo();
void Commands_redo();
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Commands_deleteKey(int track, int row);
void Commands_addOrUpdateKey(int track, struct track_key* key);
void Commands_toogleBookmark(int track, int row);
void Commands_updateKey(int track, struct track_key* key);
void Commands_beginMulti(); // Used (for example) when changing many value at the same time
void Commands_endMulti();
#endif

View File

@ -11,6 +11,7 @@
#include "minmax.h"
#include "TrackData.h"
#include "RemoteConnection.h"
#include "Commands.h"
#include "MinecraftiaFont.h"
#include "../../sync/sync.h"
#include "../../sync/base.h"
@ -488,6 +489,8 @@ static void deleteArea(int rowPos, int track, int bufferWidth, int bufferHeight)
const int track_count = getTrackCount();
struct sync_track** tracks = getTracks();
Commands_beginMulti();
for (i = 0; i < bufferWidth; ++i)
{
struct sync_track* t;
@ -503,13 +506,11 @@ static void deleteArea(int rowPos, int track, int bufferWidth, int bufferHeight)
{
int row = rowPos + j;
if (is_key_frame(t, row))
{
sync_del_key(t, row);
RemoteConnection_sendDeleteKeyCommand(t->name, row);
}
Commands_deleteKey(trackIndex, row);
}
}
Commands_endMulti();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -519,6 +520,8 @@ static void biasSelection(float value, int selectLeft, int selectRight, int sele
int track, row;
struct sync_track** tracks = getTracks();
Commands_beginMulti();
for (track = selectLeft; track <= selectRight; ++track)
{
struct sync_track* t = tracks[track];
@ -533,11 +536,11 @@ static void biasSelection(float value, int selectLeft, int selectRight, int sele
newKey = t->keys[idx];
newKey.value += value;
sync_set_key(t, &newKey);
RemoteConnection_sendSetKeyCommand(t->name, &newKey);
Commands_updateKey(track, &newKey);
}
}
Commands_endMulti();
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -572,11 +575,15 @@ static void endEditing()
track_name = track->name;
Commands_addOrUpdateKey(active_track, &key);
/*
sync_set_key(track, &key);
rlog(R_INFO, "Setting key %f at %d row %d (name %s)\n", key.value, active_track, key.row, track_name);
RemoteConnection_sendSetKeyCommand(track_name, &key);
*/
is_editing = false;
s_editorData.trackData.editText = 0;
@ -876,6 +883,16 @@ bool Editor_keyDown(int key, int keyCode, int modifiers)
handled_key = true;
}
if (key == 'z' || key == 'Z')
{
if (modifiers & EMGUI_KEY_SHIFT)
Commands_redo();
else
Commands_undo();
handled_key = true;
}
// Handle paste of data
if (key == 'v' && (modifiers & EMGUI_KEY_COMMAND))
@ -1204,17 +1221,23 @@ static void setWindowTitle(const char* path)
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
static void onFinishedLoad(const char* path)
{
Editor_update();
setWindowTitle(path);
setMostRecentFile(path);
//Commands_init(getTracks(), getTrackData());
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
void Editor_loadRecentFile(int id)
{
char path[2048];
strcpy(path, s_recentFiles[id]); // must be unique buffer when doing set mostRecent
if (LoadSave_loadRocketXML(path, getTrackData()))
{
Editor_update();
setWindowTitle(path);
setMostRecentFile(path);
}
onFinishedLoad(path);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -1224,11 +1247,7 @@ static void onOpen()
char currentFile[2048];
if (LoadSave_loadRocketXMLDialog(currentFile, getTrackData()))
{
Editor_update();
setWindowTitle(currentFile);
setMostRecentFile(currentFile);
}
onFinishedLoad(currentFile);
}
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@ -1,4 +1,5 @@
#include "TrackData.h"
#include "Commands.h"
#include "rlog.h"
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
@ -14,6 +15,9 @@ int TrackData_createGetTrack(TrackData* trackData, const char* name)
trackData->tracks[index].color = TrackData_getNextColor(trackData);
}
if (trackData->syncData.tracks)
Commands_init(trackData->syncData.tracks, trackData);
return index;
}