undo (lx_undo.hpp)

From The Foundry MODO SDK wiki
Jump to: navigation, search
There are security restrictions on this page


Undo System

Most applications provide some kind of undo mechanism, but implementation methods vary considerably in the undo capabilities they provide and tend to be highly dependent on the data types in the application. The nexus Undo system defined here is an architecture for general undo with a simple API.

Concepts and Definitions

Large-scale undo actions consist of a series of atomic actions that are grouped into user-scale undo actions. These all live in the global undo stack.

Action Stack

The Undo System is based on a single global action stack. The action stack is an ordered sequence of actions (defined by a data pointer, a integer code and two function pointers). As new actions are added to the top of the stack, old actions may be pushed off the bottom of the stack and destroyed. How many active items are kept on the action stack determines the number of operations which can be undone.

While the action functions are the smallest units of "undo," they are not necessarily equivalent to what the user sees as a single action. A change of state that happens in a single button click for the user may require multiple atomic actions on the undo stack. Actions grouped together as part of a single logical operation are called a block. The actions in a block are executed sequentially when the user requests an undo or redo.

The following example stack contains 14 actions, logically grouped as 4 blocks. The older actions are on the left, and the newer actions are on the right. There is also a stack pointer (SP) positioned between the third and forth actions. This means that the three actions to the left have been applied to the application state, and the one action to the right has not yet been applied. This is what the stack for an application would look like if the user did four actions and then hit "undo," although there are other ways to arrive at this same configuration. Actions M and N are marked as M-prime and N-prime since they are forward actions and all the rest are reverse actions.

(1) Action Stack
 old                                           new
 [A  B  C]  [D  E  F  G  H]  [I  J  K  L]  [M' N']
                                          |
                                         SP

If the user were to hit the "undo" button at this point, the actions in the previous block would be executed. Actions L, K, J and I would be performed, in that order, and each would generate its inverse which would be stored in the new stack. Note that the "undo" action has set the insertion point (IP) to the same position as the stack pointer. This is the location where new actions will be inserted, so a new action at this point will wipe out the actions waiting to "redo."

(2) Action Stack
                           IP
                            |
 [A  B  C]  [D  E  F  G  H]  [I' J' K' L']  [M' N']
                            |
                           SP

After hitting "undo," the user now does something new consisting of the internal actions O and P. There are two ways that the application can add these actions to the undo stack, as pending actions or as completed actions. Adding them as pending actions means that the forward actions O' and P' will be pushed on the stack, and the stack will execute the forward actions and apply them to the state of the program when the block is closed.

(3) Action Stack
                                   IP
                                    |
 [A  B  C]  [D  E  F  G  H]  [O' P']
                            |
                           SP

Alternatively, the application state can be changed elsewere, and the inverse actions O and P can be pushed on the stack as already complete. The end configuration of the stack is identical in each case, so which to use is a matter of convenience to the application author.

(4) Action Stack
                                  IP
                                   |
 [A  B  C]  [D  E  F  G  H]  [O  P]
                                   |
                                  SP

Undo SDK

Plug-ins can participate in the undo stack by creating objects with an ILxUndo interface. These objects are stored in the undo stack and their Forward() method is called to apply them or redo them, and their Reverse() method is called to undo them.

These undo objects should perform changes to the internal plug-in state only, not the application system state. Application state changes are made with commands which undo themselves. Also note that the state change methods have no error return codes and cannot fail. Any chance of failure must have been dealt with before they were added to the undo stack.

(5) SDK: ILxUndo interface
         LXxMETHOD(  void,
 Forward) (
         LXtObjectID              self);
 
         LXxMETHOD(  void,
 Reverse) (
         LXtObjectID              self);

(6) SDK: Declarations
 #define LXu_UNDO                "17FF7DDF-6F9B-47F1-8335-57A41DB3D3AD"

Empty Undo Python user class.

(7) PY: Undo method
 pass

The global undo service provides the ScriptQuery() method as required from all globals.

(8) SDK: ILxUndoService interface
         LXxMETHOD(  LxResult,
 ScriptQuery) (
         LXtObjectID              self,
         void                   **ppvObj);

The global undo service allows clients to add their actions to the undo state. The state can be queried at any time and may have one of three values.

INVALID
the undo system is not accepting undo objects, and any system state changes at this point are not generally valid.
ACTIVE
new undo objects can be added and will become part of the undo state.
SUSPEND
state changes can be done, but they will not be directly undoable.

(9) SDK: ILxUndoService interface
         LXxMETHOD(  unsigned int,
 State) (
         LXtObjectID              self);

(10) SDK: Declarations
 #define LXiUNDO_INVALID  0
 #define LXiUNDO_ACTIVE   1
 #define LXiUNDO_SUSPEND  2

Undo objects are added to the system using these next two methods. The undo object passed in will be queried for it's ILxUndo interface and that will be held by the undo system.

Apply()
this takes the undo object and fires its Forward() method to apply it to the current system state. If the undo state is ACTIVE the change will be added to the undo stack, and if it's SUSPEND the undo action will be released.
Record()
this takes an undo object and adds it to the undo stack. This is used for changes which have already happened, so the Reverse() method will be fired only if there is an undo.

(11) SDK: ILxUndoService interface
         LXxMETHOD(  LxResult,
 Apply) (
         LXtObjectID              self,
         LXtObjectID              undo);
 
         LXxMETHOD(  LxResult,
 Record) (
         LXtObjectID              self,
         LXtObjectID              undo);

(12) SDK: Declarations
 #define LXu_UNDOSERVICE         "D8CA1EC8-F6A0-463E-AB82-9478A281B2CB"

(13) User Service Class: UndoService method
         unsigned int
 State ()
 {
         return CLxLoc_UndoService::State ();
 }

Empty undo service Python user class.

(14) PY: UndoService method
 pass