Undo Management

This section describes the ViewKit ObjectPak undo manager, which supports reversing, or "undoing," actions.

Overview

The VkMenuUndoManager class is the basis of ObjectPak's undo manager. The ViewKit ObjectPak undo manager provides an easy-to-use method for users to undo commands that they issue to your application.

The user interface to the ObjectPak undo manager is a single menu item that you add to one of your application's menus. By default, the label of that menu item is "Undo: last_command", where last_command is the name of the last command the user issued. Whenever the user issues a command, the undo manager automatically updates the menu item to reflect the latest command. To undo the command, the user simply selects the undo manager's menu item.

Default behavior

By default, ObjectPak's undo manager provides multi-level undo support. The undo manager keeps commands on a stack. When the user undoes a command, the undo manager pops it from the stack, revealing the previously executed command. Once a user has undone at least one command, executing any new command clears the undo stack. Also, executing any non-undoable command clears the undo stack. If you choose, you can also force the undo manager to provide only single-level undo support, where it remembers only the last command the user issued.

You can use the undo manager to support undoing any command, regardless of whether the user issues the command through a menu or through other interface methods (for example, pushbuttons). The undo manager also supports undoing command classes as implemented by the VkAction(3) and VkMenuActionObject(3) classes described in "Command Classes" . In most cases, all you need to provide for each command is a callback function that reverses the effects of that command.

Using the Undo Manager

The programmatic interface to the undo manager is simple to use. Because the VkMenuUndoManager class is a subclass of VkMenuItem, you can add it to a menu and manipulate it as you would any other menu item.

To add undo support for an undoable menu item (VkMenuAction(3) and VkMenuToggle(3) items), simply provide an undo callback function (a function that reverses the effects of the item's action) when you either statically or dynamically define the menu item. Similarly, to add undo support for a command class (VkAction and VkMenuActionObject objects), you provide a member function to undo the effects of the command. For those action that are not implemented in your application as menu items or action classes, you can add undo callbacks directly to the undo stack.

Instantiating ViewKit's undo manager

Do not directly instantiate a VkMenuUndoManager object in your program. If you provide an undo callback to any menu item or if you use a subclass of VkAction or VkMenuActionObject in your program, ObjectPak automatically creates an instance of VkMenuUndoManager named "Undo". ("Command Classes" describes the VkAction and VkMenuActionObject classes.) The <Vk/VkMenuItem.h> header file provides theUndoManager, a global pointer to this instance. To access the ObjectPak undo manager, simply use this global pointer.1

Adding the undo manager to a menu

You add the undo manager to a menu just as you would any other menu item: using the VkMenu::add() function of the menu object to which you want to add the undo manager. For example, the following line adds the undo manager to a menu pane specified by the variable edit:

edit->add(theUndoManager);

You cannot include the undo manager in a static menu description; however, you can add the undo manager to a statically-defined menu after creating the menu. To specify the position of the undo manager within the menu, include a position parameter when you add the undo manager. For example, the following line adds the undo manager to the top of a menu pane specified by the variable edit:

edit->add(theUndoManager, 0);

Providing undo support for menu item actions

To add undo support for an undoable menu item (VkMenuAction and VkMenuToggle items), simply provide an undo callback function when you define the menu item. The undo callback function should reverse the effects of the item's action.

Example

For example, the following static description describes a "Cut" menu item that executes the callback function cutCallback() when the user selects the item and undoCutCallback() when the user undoes the command:

class EditWindow: public VkWindow {
private:
static VkMenuDesc editPane[];
static void cutCallback(Widget, XtPointer, XtPointer);
static void undoCutCallback(Widget, XtPointer, XtPointer);
// ...
};
VkMenuDesc EditWindow::editPane[] = {
{ ACTION, "Cut", &EditWindow::cutCallback,
NULL, NULL, &EditWindow::undoCutCallback },
{ END }
};
You could do the same thing by adding the menu item dynamically:
class EditWindow: public VkWindow {
private:
static VkSubMenu *editMenu;
static void cutCallback(Widget, XtPointer, XtPointer);
static void undoCutCallback(Widget, XtPointer, XtPointer);
// ...
};
EditWindow::EditWindow(char *name) : VkWindow(name)
{
// ...
editMenu->addAction("Cut", &EditWindow::cutCallback,
&EditWindow::undoCutCallback, this);
}

Providing undo support for non-menu item actions

Sometimes, you might want to provide undo support for an action not implemented as a menu item (for example, an action invoked by a pushbutton). ViewKit ObjectPak allows you to do this by adding the action directly to the undo stack using VkMenuUndoManager::add():

void add(const char *name,
XtCallbackProc undoCallback,
XtPointer clientData)

The name argument provides a name for the action to appear in the undo manager's menu item. The undoCallback argument must be an Xt-style callback function that the undo manager can call to undo the action. The undo manager passes the clientData argument to the undo callback function as client data when it invokes the callback. Following ObjectPak conventions as described in "Using Xt Callbacks with Components" , you should pass the this pointer as client data so that the callback function can retrieve the pointer, cast it to the expected component type, and call a corresponding member function.


Note: add() simply adds an action to the undo stack; it does not "register" a permanent undo callback for an action. Once the undo stack is cleared, the undo information for that action is deleted. If you later perform the action again and you want to provide undo support for that action, you must use add() again to add the action to the undo stack.


Example 30. shows a simple example of adding an action to the undo stack. The MyComponent constructor creates a pushbutton as part of its widget hierarchy and registers actionCallback() as the button's activation callback function. actionCallback(), in addition to performing an action, adds undoActionCallback() to the undo stack.

Example 30. Adding a Non-menu Item Directly to the Undo Stack

Code

MyComponent: public VkComponent {
public:
MyComponent(const char *, Widget);
void actionCallback(Widget, XtPointer, XtPointer);
void undoActionCallback(Widget, XtPointer, XtPointer);
// ...
};
MyComponent::MyComponent(const char *, Widget parent)
{
// ...
Widget button = XmCreatePushButton(viewWidget, "button", NULL, 0);
XtAddCallback(button, XmNactivateCallback,
&MyWindow::actionCallback, (XtPointer) this);
// ...
}
void MyComponent::actionCallback(Widget w, XtPointer clientData,
XtPointer callData)
{
// Perform action...
theUndoManager->add("Action", &MyComponent::undoActionCallback, this);
}

Providing undo support for command class objects

The ViewKit ObjectPak classes that support command classes, VkAction and VkMenuActionObject, both require you to override the pure virtual function undoit(), which the undo manager calls to undo an action implemented as a command class. "Command Classes" describes how to use VkAction and VkMenuActionObject to implement command classes.

Enabling and disabling multi-level undo support

By default, VkMenuUndoManager provides multi-level undo support. The undo manager keeps commands on a stack. When the user undoes a command, the undo manager pops it from the stack, revealing the previously executed command. Once a user has undone at least one command, executing any new command clears the undo stack. Also, executing any undoable command clears the undo stack.

Supporting multi-level undo in your application can be difficult. If you prefer to support undoing only the last command executed, you can change the behavior of the undo manager with the VkMenuUndoManager::multiLevel() function:

void multiLevel(Boolean flag)

If flag is FALSE, the undo manager remembers only the last command executed.

Clearing the undo stack

You can force the undo manager to clear its command stack with the VkMenuUndoManager::reset() function:

void reset()

Examining the undo stack

You can examine the contents of the undo manager's command stack using VkMenuUndoManager::historyList():

VkComponentList *historyList()

historyList() returns a list of objects representing commands that have been executed and are available to be undone. Commands are listed in order of execution; the last command executed is the last item in the list. All of the objects in the list are subclasses of VkMenuItem. Commands added directly to the undo stack (as described in "Using the Undo Manager" ) or commands implemented as VkAction objects (as described in "Command Classes" ) appear as VkMenuActionStub objects. VkMenuActionStub is an empty subclass of VkMenuAction.

Setting the undo manager menu item label

The label that the undo manager menu item displays is of the form Undo_label:Command_label. Undo_label is the value of the labelXmNlabelString resource of the undo manager. By default, this value is "Undo". You can change this string (for example, for a German-language app-defaults file) by providing a different value for the XmNlabelString resource. For example, you could set the resource as follows:

*Undo.labelString: Annul

Determining last executed commands

Command_label is the label for the last executed command registered with the undo manager, determined as follows:

  • For commands executed by menu items--VkMenuAction, VkMenuToggle, or VkMenuActionObject (described in "Command Classes" ) objects--the label is the item's XmNlabelString resource.
  • For VkAction objects (described in "Command Classes" ), the undo manager uses the object's "labelString" resource if one is defined, otherwise it uses the VkAction object's name as the label.
  • For actions that you add directly to the undo stack (described in "Using the Undo Manager" ), the undo manager uses the action name that you provided when you added the action.

Example of Using ViewKit's Undo Manager

Example 31. shows an example of using the undo manager.

Example 31. Using the Undo Manager

Code

////////////////////////////////////////////////////////////////
// Simple example to exercise Vk undo facilities
///////////////////////////////////////////////////////////////
#include <Vk/VkApp.h>
#include <Vk/VkWindow.h>
#include <Vk/VkMenu.h>
#include <Vk/VkMenuItem.h>
#include <Vk/VkSubMenu.h>
#include <stream.h>
#include <Xm/Label.h>
#include <Xm/RowColumn.h>
#include <Xm/PushB.h>
class MyWindow: public VkWindow {
private:
static void pushCallback( Widget, XtPointer, XtPointer);
static void undoPushCallback( Widget, XtPointer, XtPointer);
static void oneCallback( Widget, XtPointer , XtPointer);
static void twoCallback( Widget, XtPointer , XtPointer);
static void threeCallback( Widget, XtPointer , XtPointer);
static void undoOneCallback( Widget, XtPointer , XtPointer);
static void undoTwoCallback( Widget, XtPointer , XtPointer);
static void undoThreeCallback( Widget, XtPointer , XtPointer);
static void quitCallback( Widget, XtPointer , XtPointer);
void quit();
void one();
void two();
void three();
void undoOne();
void undoTwo();
void undoThree();
static VkMenuDesc appMenuPane[];
static VkMenuDesc mainMenuPane[];
public:
MyWindow( const char *name);
~MyWindow( );
virtual const char* className();
};
MyWindow::MyWindow( const char *name) : VkWindow( name)
{
Widget rc = XmCreateRowColumn(mainWindowWidget(), "rc", NULL, 0);
Widget label = XmCreateLabel(rc, "an undo test", NULL, 0);
Widget pb = XmCreatePushButton(rc, "push", NULL, 0);
XtAddCallback(pb, XmNactivateCallback, &MyWindow::pushCallback,
(XtPointer) this);
XtManageChild(label);
XtManageChild(pb);
setMenuBar(mainMenuPane);
VkSubMenu * editMenuPane = addMenuPane("Edit");
editMenuPane->add(theUndoManager);
addView(rc);
}
MyWindow::~MyWindow()
{
}
const char* MyWindow::className()
{
return "MyWindow";
}
// The menu bar is essentially a set of cascading menu panes, so the
// top level of the menu tree is always defined as a list of submenus
VkMenuDesc MyWindow::mainMenuPane[] = {
{ SUBMENU, "Application", NULL, MyWindow::appMenuPane},
{ END}
};
VkMenuDesc MyWindow::appMenuPane[] = {
{ ACTION, "Command One", &MyWindow::oneCallback, NULL, NULL,
&MyWindow::undoOneCallback },
{ ACTION, "Command Two", &MyWindow::twoCallback, NULL, NULL,
&MyWindow::undoTwoCallback },
{ ACTION, "Command Three", &MyWindow::threeCallback, NULL, NULL,
&MyWindow::undoThreeCallback },
{ SEPARATOR },
{ CONFIRMFIRSTACTION, "Quit", &MyWindow::quitCallback},
{ END},
};
void MyWindow::one()
{
cout << "Command One executed" << "\n" << flush;
}
void MyWindow::two()
{
cout << "Command Two executed" << "\n" << flush;
}
void MyWindow::three()
{
cout << "Command Three executed" << "\n" << flush;
}
void MyWindow::undoOne()
{
cout << "Undoing Command One" << "\n" << flush;
}
void MyWindow::undoTwo()
{
cout << "UNdoing Command Two" << "\n" << flush;
}
void MyWindow::undoThree()
{
cout << "Undoing Command Three" << "\n" << flush;
}
void MyWindow::oneCallback( Widget, XtPointer clientData, XtPointer)
{
MyWindow *obj = (MyWindow *) clientData;
obj->one();
}
void MyWindow::twoCallback( Widget, XtPointer clientData, XtPointer)
{
MyWindow *obj = (MyWindow *) clientData;
obj->two();
}
void MyWindow::threeCallback( Widget, XtPointer clientData, XtPointer)
{
MyWindow *obj = (MyWindow *) clientData;
obj->three();
}
void MyWindow::undoOneCallback( Widget, XtPointer clientData, XtPointer)
{
MyWindow *obj = (MyWindow *) clientData;
obj->undoOne();
}
void MyWindow::undoTwoCallback( Widget, XtPointer clientData, XtPointer)
{
MyWindow *obj = (MyWindow *) clientData;
obj->undoTwo();
}
void MyWindow::undoThreeCallback( Widget, XtPointer clientData, XtPointer)
{
MyWindow *obj = (MyWindow *) clientData;
obj->undoThree();
}
void MyWindow::quitCallback ( Widget, XtPointer clientData, XtPointer )
{
MyWindow *obj = (MyWindow*) clientData;
delete obj;
}
void MyWindow::pushCallback( Widget, XtPointer clientData, XtPointer)
{
cout << "doing a push command\n" << flush;
theUndoManager->add("Push", &MyWindow::undoPushCallback, (XtPointer) clientData);
}

void MyWindow::undoPushCallback( Widget, XtPointer clientData, XtPointer)
{
cout << "undoing the push command\n" << flush;
}
main(int argc, char **argv)
{
VkApp *app = new VkApp("Menudemo", &argc, argv);
MyWindow *win = new MyWindow("MenuWindow");
win->show();
app->run();
}