Interfacing with the Undo System

From The Foundry MODO SDK wiki
Jump to: navigation, search

To handle your own undos you need to declare your undo object class.

class CMyUndo : public CLxImpl_Undo
{
    public:
        void         undo_Forward ()     LXx_OVERRIDE;
        void         undo_Reverse ()     LXx_OVERRIDE;
};

This object represents your undo action and will be managed by the undo system. The forward method will be called when your action is applied ("done") or redone, and reverse will be called when your action is undone.

Let's say, for example, that your undo state is a global string value, like the name of something. When you change the name you want to register the undo action to change it back. If the action is undone the name will go back to the original name, and if the action is redone then the name will change to the new name again. Since this action effectively performs a swap, the forward and reverse actions are essentially the same. Here's an example of such an undo object:

class CSwapName : public CLxImpl_Undo
{
    public:
        std::string  old_name;

                void
        undo_Reverse ()     LXx_OVERRIDE
        {
                std::string   temp;

                temp = global_string;
                global_string = old_name;
                old_name = temp;
        }

                void
        undo_Forward ()     LXx_OVERRIDE
        {
                undo_Reverse ();
        }
};

You register your undo using the Undo service. First you have to allocate your undo object, which you can do with a spawner . This will give you both the ILxUnknownID for the COM object, and the pointer to your undo class. You then initialize the state of the new undo object and add it to the undo system.

This example changes the name and registers an undo action to change it back. In this case UndoService::Record() is used to register an undo action for a change that has already happened. Note that since we're not using wrappers for the undo object we have to release it when done. The service has added its reference count so it won't be deleted.

        void
SetGlobalName_Undoable (
        std::string        &name)
{
        CLxUser_UndoService undoSvc;
        CSwapName          *undo;
        ILxUnknownID        obj;

        undo = SpawnUndo (obj);

        undo->old_name = global_string;
        global_string = name;

        undoSvc.Record (obj);
        lx::ObjRelease (obj);
}

Because the action is a swap this function can be simplified by creating the action in an "undone" state. Adding it to the undo system by calling UndoService::Apply() performs the forward action and adds it to the system. This means there is only one implementation of the code that performs the swap.

        void
SetGlobalName_Undoable (
        std::string        &name)
{
        CLxUser_UndoService undoSvc;
        CSwapName          *undo;
        ILxUnknownID        obj;

        undo = SpawnUndo (obj);
        undo->old_name = name;

        undoSvc.Apply (obj);
        lx::ObjRelease (obj);
}

If the undoable change of state is as a result of your own command, then you know that it's always OK to register undo actions. Sometimes your change of state is the result of something more indirect, such as listener events or other state-change methods. In that case you have to make sure that the event is not the result of the user undoing or redoing other changes, in which case it's invalid to add undo events. This is easily tested with the undo service:

void changeEvent ()
{
        CLxUser_UndoService   undoSvc;

        if (undoSvc.State () == LXiUNDO_ACTIVE)
                performUndoableChange ();
}

If you get an event when undos are active, you respond by changing your state and registering the undo. If, however, you get this same event notification as result of an undo you don't have to change state or register undos. In that case your own undo action will already be fired as part of the same undo.