18.3 Extending UndoManager
Now that
we've looked at all of the classes and interfaces in
the undo framework, we'll look at a few ideas for
extending the functionality it provides.
In this example, we'll extend
UndoManager to add a few extra features. The first
thing we'll add is the ability to get a list of the
edits stored in the manager. This is a simple task of returning the
contents of the edits vector inherited from
CompoundEdit. We also provide access to an array
of significant undoable edits and an array of significant redoable
edits. These might be useful in a game like chess, in which we want
to provide a list of past moves.
The next major feature we add is support for listeners. At this
writing, the current UndoManager does not have any
way of notifying you when it receives edits. As we saw in an earlier
example, this means that you have to listen to each edit-generating
component if you want to update the user interface to reflect new
undoable or redoable edits as they occur. In our manager, we simply
add the ability to add and remove undoable edit listeners to the undo
manager itself. Each time an edit is added, the undo manager fires an
UndoableEditEvent to any registered listeners.
This way, we can just add the undo manager as a listener to each
edit-generating component and then add a single listener to the undo
manager to update the UI.
The methods of our new undo manager can be divided into two groups,
each supporting one of the two features we're
adding. We'll split the source code listing along
these lines so we can talk about each set of methods independently.
Here's the first half:
// ExtendedUndoManager.java
//
import javax.swing.event.*;
import javax.swing.undo.*;
import java.util.Enumeration;
import java.util.Vector;
// An extension of UndoManager that provides two additional features:
// (1) The ability to add and remove listeners
// (2) The ability to gain more extensive access to the edits being managed
public class ExtendedUndoManager extends UndoManager
implements UndoableEditListener {
private ExtendedUndoableEditSupport support =
new ExtendedUndoableEditSupport( );
private Object source; // The source of the last edit
// Return the complete list of edits in an array.
public synchronized UndoableEdit[] getEdits( ) {
UndoableEdit[] array = new UndoableEdit[edits.size( )];
edits.copyInto(array);
return array;
}
// Return all currently significant undoable edits. The first edit is the next one
// to be undone.
public synchronized UndoableEdit[] getUndoableEdits( ) {
int size = edits.size( );
Vector v = new Vector(size);
for (int i=size-1;i>=0;i--) {
UndoableEdit u = (UndoableEdit)edits.elementAt(i);
if (u.canUndo( ) && u.isSignificant( ))
v.addElement(u);
}
UndoableEdit[] array = new UndoableEdit[v.size( )];
v.copyInto(array);
return array;
}
// Return all currently significant redoable edits. The first edit is the next one
// to be redone.
public synchronized UndoableEdit[] getRedoableEdits( ) {
int size = edits.size( );
Vector v = new Vector(size);
for (int i=0; i<size; i++) {
UndoableEdit u = (UndoableEdit)edits.elementAt(i);
if (u.canRedo( ) && u.isSignificant( ))
v.addElement(u);
}
UndoableEdit[] array = new UndoableEdit[v.size( )];
v.copyInto(array);
return array;
}
The first method here is simple. All we do is copy the edits from the
edits vector into an array and return it. The
other two methods are nearly identical. They are a little more
complicated than they ideally should be because we
don't have access to the current insertion point
into the edits vector (which would split the list
right between the undoable and redoable edits). Instead, we just
iterate over the elements, building up a list of significant undoable
or redoable edits.
Here are the methods we added to support listeners:
// UndoableEditListener Method Support (ExtendedUndoManager.java, part 2)
//
// Add an edit and notify our listeners.
public synchronized boolean addEdit(UndoableEdit anEdit) {
boolean b = super.addEdit(anEdit);
if (b)
support.postEdit(anEdit); // If the edit was added, notify listeners.
return b;
}
// When an edit is sent to us, call addEdit( ) to notify any of our listeners.
public synchronized void undoableEditHappened(UndoableEditEvent ev) {
UndoableEdit ue = ev.getEdit( );
source = ev.getSource( );
addEdit(ue);
}
// Add a listener to be notified each time an edit is added to this manager.
// This makes it easy to update undo/redo menus as edits are added.
public synchronized void addUndoableEditListener(UndoableEditListener l) {
support.addUndoableEditListener(l);
}
// Remove a listener from this manager.
public synchronized void removeUndoableEditListener(UndoableEditListener l) {
support.removeUndoableEditListener(l);
}
// A simple extension of UndoableEditSupport that lets us specify the event
// source each time we post an edit
class ExtendedUndoableEditSupport extends UndoableEditSupport {
// Post an edit to added listeners.
public synchronized void postEdit(UndoableEdit ue) {
realSource = source; // From our enclosing manager object
super.postEdit(ue);
}
}
}
The first method here is a customized implementation of
addEdit( ). For the most part, we leave this
method to the superclass. The only thing we've added
is a call to UndoableEditSupport.postEdit( ). Any
time an edit is added to the undo manager, we notify its listeners.
The idea is that a single listener, probably responsible for updating
an Undo menu, is added to the undo manager.
The next method is the undoableEditHappened( )
method from the UndoableEditListener interface.
This is the method called each time any edit-generating component in
the application fires an UndoableEdit. In this
method, we first store the source of the event
(we'll see how we use this shortly) and then call
addEdit( ).
The next two methods simply use
UndoableEditSupport to manage interested
listeners.
Finally, we define a small inner class called
ExtendedUndoableEditSupport. This is an extension
of UndoableEditSupport that we use to set the
correct event source each time the
ExtendedUndoManager fires an
UndoableEditEvent. Rather than declaring the undo
manager as the source of the event, we use the real source of the
event that was passed to the undo manager's
undoableEditHappened( ) method. Note that
realSource, which is a protected field in
UndoableEditSupport, becomes the source object in
the fired
UndoableEditEvent.
|