![]() ![]() |
![]() |
![]() ![]() ![]() ![]() |
![]() |
This document describes the design of the Undo API in Swing.
Many developers have asked for an Undo/Redo application service. Undo operations are so common and so general that Undo and Redo capabilities have been added to the Swing toolkit.
Our goal in creating an Undo/Redo service was to design an architecture which:
Swing uses the JFC approach to Undo and Redo operations. When a component performs an undoable edit, it informs its UndoableEditListeners with an UndoableEditEvent. The listeners can ask the event for its UndoableEdit, which can be told to undo() and redo().
To provide a simple, one-shot Undo/Redo command, the only UndoableEdit that the listener is expected to hold onto is the last one that it has received. If an application needs to provide an Undo/Redo service that is less limited, the listener can hold onto all (or the last few) edits it has received, in the order that it received them. Then the listener can work back and forth along the queue.
JFC provides standard implementations of the UndoableEditListener and UndoableEditEvent interfaces, but specialized versions can be written for any application
This section provides details about the Swing Undo API.
In the Swing Undo API, the listener and the event are as straightforward, as usual:
public interface UndoableEditListener extends java.util.EventListener {
void undoableEditHappened(UndoableEditEvent e);
}
public class UndoableEditEvent extends java.util.EventObject {
UndoableEdit getEdit();
}
An UndoableEdit knows how to undo and redo some operations. It is possible for a long-lived UndoableEdit to become invalid, so an UndoableEdit must be able to report that it can no longer undo or redo. Edits should provide useful human-readable descriptions of themselves, suitable for display in a history log or as the Undo or Redo items in an Edit menu.
An application can ask Edits whether they can coalesce (see the addEdit() and replaceEdit() documentation for details). This capability allows the collapsing of sets of similar edits. It also makes it easier to implement complicated chains of undoable operations as chains of simpler ones.
Finally, Swing's Undo/Redo services make it possible to specify that an edit is insignificant -- that is, to specify that a particular edit should be done or undone as a side effect of a more important edit, rather than being presented to users as an undoable action in its own right. (For more on this topic, see Using Insignificant Edits, below.)
public interface UndoableEdit {
/**
* Undo the edit that was made.
*/
public void undo() throws CannotUndoException;
/**
* True if it is still possible to undo this operation.
*/
public boolean canUndo();
/**
* Re-apply this edit, assuming that it has been undone.
*/
public void redo() throws CannotRedoException;
/**
* True if it is still possible to redo this operation.
*/
public boolean canRedo();
/**
* This UndoableEdit should absorb an Edit if it can. Return true
* if anEdit has been incorporated, false if it has not.
*
* Typically the receiver is already in the queue of a
* JUndoManager (or other UndoableEditListener), and is being
* given a chance to incorporate anEdit rather than letting it be
* added to the queue in turn.
*
* If true is returned, from now on anEdit must return false from
* canUndo() and canRedo(), and must throw the appropriate
* exception on undo() or redo().
*/
public boolean addEdit(UndoableEdit anEdit);
/**
* Return true if this UndoableEdit should replace anEdit. The
* receiver should incorporate an Edit's state before returning true.
*
* This message is the opposite of addEdit--anEdit has typically
* already been queued in a JUndoManager (or other
* UndoableEditListener), and the receiver is being given a chance
* to take its place.
*
* If true is returned, from now on anEdit must return false from
* canUndo() and canRedo(), and must throw the appropriate
* exception on undo() or redo().
*/
public boolean replaceEdit(UndoableEdit anEdit);
/**
* Return false if this change is insignificant--for example one
* that maintains the user's selection, but does not change any
* model state. This status can be used by an UndoableEditListener
* (like JUndoManager) when deciding which ChangeEvents to present
* to the user as Undo/Redo options, and which to perform as side
* effects of undoing or redoing other events.
*/
public boolean isSignificant();
/**
* Provide a localized, human readable description of the change
* suitable for use in, say, a change log.
*/
public String getPresentationName();
/**
* Provide a localized, human readable description of the undoable
* form of this change, e.g. for use as an Undo menu item. Typically
* derived from getDescription();
*/
public String getUndoPresentationName();
/**
* Provide a localized, human readable description of the redoable
* form of this change, e.g. for use as a Redo menu item. Typically
* derived from getPresentationName();
*/
public String getRedoPresentationName();
}
JFC provides a set of basic classes to implement typical undo behavior. They are:
After a JUndoManager has been told to end(),
its index is ignored, and its undo()
and redo() methods behave as those
in a JCompoundEdit. This behavior is useful when smaller, contained undoable
editing sessions are being conducted. these editing sessions can subsequently
be submitted to an application's main change manager by either the click
of an OK button inside a dialog box or the pressing of the Return key in
a spreadsheet cell.
Using significant and insignificant edits is a common technique for maintaining the user's selection, or properly moving focus, as she uses Undo and Redo commands.
For example, read the following scenario of shifting focus. (As you read, please understand that the various edit objects mentioned below are presented for example purposes only; they are not standard JFC classes.)
tookFocusEdit.
deletedSomethingEdit.
tookFocusEdit.
replaceSelWithTextEdit.
=> replaceSelWithTextEdit*
tookFocusEdit
deletedSomethingEdit*
tookFocusEdit
replaceSelWithTextEdit*
=> tookFocusEdit
deletedSomethingEdit*
tookFocusEdit
replaceSelWithTextEdit*
tookFocusEdit
deletedSomethingEdit*
=> tookFocusEdit
=> replaceSelWithTextEdit*
tookFocusEdit
deletedSomethingEdit*
tookFocusEdit
Think of this set of operations in combination with coalescing. If the focus edits know how to add or replace each other, the queue won't contain a record of the 25 other beans the user clicked in between deleting her graphic and typing her text.
Version 0.5. Last modified 09/04/97.
Copyright © 1995-97 Sun
Microsystems, Inc. All Rights Reserved.