realXtend/naali — pull request #615 by cvetan5 (2013-02-11 13:13:04+00:00)
Title: Maintaining Undo / Redo stack for EC editor and Scene editor

An Undo / Redo stack manager for EC / Scene editor. It utilizes the Undo framework classes by Qt that implement the command pattern. All actions i.e. commands from EC / Scene editor (excluding pasting content, importing scene(s) and converting local entity to replicated and vice-versa) are represented by a QUndoCommand-derived class. The **undo manager** manipulates the QUndoStack and the UI elements i.e. undo / redo button with two QMenus which contain 5 recent commands that had been done (Undo menu) or undone (Redo menu). A QUndoView is provided to view the contents of QUndoStack, and to walk through them if needed. A helper class called **EntityIdChangeTracker** is used to track IDs of entities that had been deleted / re-added in an EC / Scene editor lifetime, and also to track acknowledging of new created entities so it can keep their valid replicated ID. The reason I did this is that I don't think it is smart to use old entity IDs to redo a "delete entity" action (and for some reason it does not work for replicated entities).

Workflow:
1. When a command is executed, a new instance of the appropriate QUndoCommand-derived class is pushed onto the stack;
2. QUndoStack calls `QUndoCommand::redo()` of the QUndoCommand that had been pushed, except when EditAttributeCommand\ is pushed, where the old attribute value must be stored before setting it to the new value (there is a workaround there to avoid calling `redo()` upon pushing). The UI is updated;

2.1. If for example, a command that will cause a change in the target entity's ID has been pushed, its ID is tracked with EntityIdChangeTracker;
3. When `UndoManager::Undo()` is called, QUndoStack calls `QUndoCommand::undo()` of the topmost command from the stack. The index is changed to point to the next command that will be undone on the next `undo()` call. The UI is updated;
4. If, for example. 4 commands are undone i.e. the index is changed back by 4, QUndoStack calls `QUndoCommand::undo()` repeatedly until the desired index is reached. The UI is updated;

4.1. In the event that a new command is pushed onto the stack, commands that can be redone are removed from the stack and destroyed;
4.2. Entity IDs are retrieved by recursively walking through EntityIdChangeTracker's maps
5. When "unsupported commands" are executed, it results in clearing the undo stack (removing and destroying all QUndoCommands)

Known drawbacks:
- I noticed that when showing the QUndoView, the first command that was pushed onto the stack is inevitably selected, resulting in repeated undo() calls when index 1 is reached. Upon a hastily google search I couldn't find if anyone else has this problem. This should be replaced in the future by a custom undo stack view if QUndoView is indeed buggy.
- Changing a `Color` attribute results in two commands pushed with same values. This is fixed by merging two EditAttributeCommand\ objects that have the same values.
- If two or more entities are selected in EC editor's right pane, and a common attribute value is changed, it will result in pushing multiple QUndoCommands for all target entities.

Possible drawbacks:
- EntityIdChangeTracker worked for me on local and hosted servers as expected, but needs thorough testing. My guess is, if a series of commands that include changing of IDs are undone immediately (undoing 50 commands), it is possible that EntityIdChangeTracker might lose track of those entities in a case where `Scene::EntityAcked(Entity*, entity_id_t)` signal might come late and the tracker will fail to store the acked ID of an unacked entity, thus resulting in a broken undo stack (the undo command will not be able to retrieve the entity ID and undoing / redoing will not have effect)

I would greatly appreciate if you guys test this and give me any notes on this.