1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113
|
#pragma once
#include "debugging/debugging.h"
#include <list>
#include "Operation.h"
namespace undo
{
/**
* greebo: The UndoSystem keeps track of Undoable and Redoable operations,
* which are kept in a chain-like data structure.
*
* Each named operation in this stack contains a snapshot of the undoable objects
* that have been touched between start() and finish().
*
* On start() a new Operation is allocated and on save(IUndoable)
* the IUndoable's memento is actually stored within the allocated Operation.
* The method finish() commits the operation to the stack.
*/
class UndoStack
{
private:
// The list of Operations that can be undone
//! Note: using std::list instead of vector/deque, to avoid copying of undos
std::list<Operation::Ptr> _stack;
// The pending undo operation (will be committed on finish, if not empty)
Operation::Ptr _pending;
public:
bool empty() const
{
return _stack.empty();
}
std::size_t size() const
{
return _stack.size();
}
const Operation::Ptr& back() const
{
return _stack.back();
}
const Operation::Ptr& front() const
{
return _stack.front();
}
void pop_front()
{
_stack.pop_front();
}
void pop_back()
{
_stack.pop_back();
}
void clear()
{
_stack.clear();
}
// Allocate a new Operation to work with
void start(const std::string& command)
{
// When starting an operation, we create one and declare it as pending
// It will not be added to the stack, it still might end up empty
// We will also replace any previously pending operation with the new one
// even though it should not happen by design
ASSERT_MESSAGE(!_pending, "undo operation already started");
_pending = std::make_shared<Operation>(command);
}
void cancel()
{
// discard the pending operation without storing it in the stack
_pending.reset();
}
// Finish the current undo operation
bool finish(const std::string& command)
{
if (!_pending || _pending->empty())
{
// The started operation has not been filled with any data
// so just discard it without doing anything
_pending.reset();
return false;
}
// Rename the last undo operation (it may be "unnamed" till now)
_pending->setName(command);
// Move the pending operation into its place
_stack.emplace_back(std::move(_pending));
return true;
}
// Store an Undoable's state into the active operation
void save(IUndoable& undoable)
{
assert(_pending);
_pending->save(undoable);
}
};
} // namespace
|