The UndoManager.java Java example source code
/*
* Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package javax.swing.undo;
import javax.swing.event.*;
import javax.swing.UIManager;
import java.util.*;
/**
* {@code UndoManager} manages a list of {@code UndoableEdits},
* providing a way to undo or redo the appropriate edits. There are
* two ways to add edits to an <code>UndoManager. Add the edit
* directly using the <code>addEdit method, or add the
* <code>UndoManager to a bean that supports
* <code>UndoableEditListener. The following examples creates
* an <code>UndoManager and adds it as an
* <code>UndoableEditListener to a JTextField
:
* <pre>
* UndoManager undoManager = new UndoManager();
* JTextField tf = ...;
* tf.getDocument().addUndoableEditListener(undoManager);
* </pre>
* <p>
* <code>UndoManager maintains an ordered list of edits and the
* index of the next edit in that list. The index of the next edit is
* either the size of the current list of edits, or if
* <code>undo has been invoked it corresponds to the index
* of the last significant edit that was undone. When
* <code>undo is invoked all edits from the index of the next
* edit to the last significant edit are undone, in reverse order.
* For example, consider an <code>UndoManager consisting of the
* following edits: <b>A b c D. Edits with a
* upper-case letter in bold are significant, those in lower-case
* and italicized are insignificant.
* <p>
* <a name="figure1">
* <table border=0 summary="">
* <tr>
* <img src="doc-files/UndoManager-1.gif" alt="">
* <tr> | Figure 1
* </table>
* <p>
* As shown in <a href="#figure1">figure 1, if D was just added, the
* index of the next edit will be 4. Invoking <code>undo
* results in invoking <code>undo on D and setting the
* index of the next edit to 3 (edit <i>c), as shown in the following
* figure.
* <p>
* <a name="figure2">
* <table border=0 summary="">
* <tr> |
* <img src="doc-files/UndoManager-2.gif" alt="">
* <tr> | Figure 2
* </table>
* <p>
* The last significant edit is <b>A, so that invoking
* <code>undo again invokes undo on c,
* <i>b, and A, in that order, setting the index of the
* next edit to 0, as shown in the following figure.
* <p>
* <a name="figure3">
* <table border=0 summary="">
* <tr> |
* <img src="doc-files/UndoManager-3.gif" alt="">
* <tr> | Figure 3
* </table>
* <p>
* Invoking <code>redo results in invoking redo on
* all edits between the index of the next edit and the next
* significant edit (or the end of the list). Continuing with the previous
* example if <code>redo were invoked, redo would in
* turn be invoked on <b>A, b and c. In addition
* the index of the next edit is set to 3 (as shown in <a
* href="#figure2">figure 2</a>).
* <p>
* Adding an edit to an <code>UndoManager results in
* removing all edits from the index of the next edit to the end of
* the list. Continuing with the previous example, if a new edit,
* <i>e, is added the edit D is removed from the list
* (after having <code>die invoked on it). If c is not
* incorporated by the next edit
* (<code>c.addEdit(e) returns true), or replaced
* by it (<code>e.replaceEdit(c) returns true),
* the new edit is added after <i>c, as shown in the following
* figure.
* <p>
* <a name="figure4">
* <table border=0 summary="">
* <tr> |
* <img src="doc-files/UndoManager-4.gif" alt="">
* <tr> | Figure 4
* </table>
* <p>
* Once <code>end has been invoked on an UndoManager
* the superclass behavior is used for all <code>UndoableEdit
* methods. Refer to <code>CompoundEdit for more details on its
* behavior.
* <p>
* Unlike the rest of Swing, this class is thread safe.
* <p>
* <strong>Warning:
* Serialized objects of this class will not be compatible with
* future Swing releases. The current serialization support is
* appropriate for short term storage or RMI between applications running
* the same version of Swing. As of 1.4, support for long term storage
* of all JavaBeans™
* has been added to the <code>java.beans package.
* Please see {@link java.beans.XMLEncoder}.
*
* @author Ray Ryan
*/
public class UndoManager extends CompoundEdit implements UndoableEditListener {
int indexOfNextAdd;
int limit;
/**
* Creates a new <code>UndoManager.
*/
public UndoManager() {
super();
indexOfNextAdd = 0;
limit = 100;
edits.ensureCapacity(limit);
}
/**
* Returns the maximum number of edits this {@code UndoManager}
* holds. A value less than 0 indicates the number of edits is not
* limited.
*
* @return the maximum number of edits this {@code UndoManager} holds
* @see #addEdit
* @see #setLimit
*/
public synchronized int getLimit() {
return limit;
}
/**
* Empties the undo manager sending each edit a <code>die message
* in the process.
*
* @see AbstractUndoableEdit#die
*/
public synchronized void discardAllEdits() {
for (UndoableEdit e : edits) {
e.die();
}
edits = new Vector<UndoableEdit>();
indexOfNextAdd = 0;
// PENDING(rjrjr) when vector grows a removeRange() method
// (expected in JDK 1.2), trimEdits() will be nice and
// efficient, and this method can call that instead.
}
/**
* Reduces the number of queued edits to a range of size limit,
* centered on the index of the next edit.
*/
protected void trimForLimit() {
if (limit >= 0) {
int size = edits.size();
// System.out.print("limit: " + limit +
// " size: " + size +
// " indexOfNextAdd: " + indexOfNextAdd +
// "\n");
if (size > limit) {
int halfLimit = limit/2;
int keepFrom = indexOfNextAdd - 1 - halfLimit;
int keepTo = indexOfNextAdd - 1 + halfLimit;
// These are ints we're playing with, so dividing by two
// rounds down for odd numbers, so make sure the limit was
// honored properly. Note that the keep range is
// inclusive.
if (keepTo - keepFrom + 1 > limit) {
keepFrom++;
}
// The keep range is centered on indexOfNextAdd,
// but odds are good that the actual edits Vector
// isn't. Move the keep range to keep it legal.
if (keepFrom < 0) {
keepTo -= keepFrom;
keepFrom = 0;
}
if (keepTo >= size) {
int delta = size - keepTo - 1;
keepTo += delta;
keepFrom += delta;
}
// System.out.println("Keeping " + keepFrom + " " + keepTo);
trimEdits(keepTo+1, size-1);
trimEdits(0, keepFrom-1);
}
}
}
/**
* Removes edits in the specified range.
* All edits in the given range (inclusive, and in reverse order)
* will have <code>die invoked on them and are removed from
* the list of edits. This has no effect if
* <code>from > to .
*
* @param from the minimum index to remove
* @param to the maximum index to remove
*/
protected void trimEdits(int from, int to) {
if (from <= to) {
// System.out.println("Trimming " + from + " " + to + " with index " +
// indexOfNextAdd);
for (int i = to; from <= i; i--) {
UndoableEdit e = edits.elementAt(i);
// System.out.println("JUM: Discarding " +
// e.getUndoPresentationName());
e.die();
// PENDING(rjrjr) when Vector supports range deletion (JDK
// 1.2) , we can optimize the next line considerably.
edits.removeElementAt(i);
}
if (indexOfNextAdd > to) {
// System.out.print("...right...");
indexOfNextAdd -= to-from+1;
} else if (indexOfNextAdd >= from) {
// System.out.println("...mid...");
indexOfNextAdd = from;
}
// System.out.println("new index " + indexOfNextAdd);
}
}
/**
* Sets the maximum number of edits this <code>UndoManager
* holds. A value less than 0 indicates the number of edits is not
* limited. If edits need to be discarded to shrink the limit,
* <code>die will be invoked on them in the reverse
* order they were added. The default is 100.
*
* @param l the new limit
* @throws RuntimeException if this {@code UndoManager} is not in progress
* ({@code end} has been invoked)
* @see #isInProgress
* @see #end
* @see #addEdit
* @see #getLimit
*/
public synchronized void setLimit(int l) {
if (!inProgress) throw new RuntimeException("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called");
limit = l;
trimForLimit();
}
/**
* Returns the the next significant edit to be undone if <code>undo
* is invoked. This returns <code>null if there are no edits
* to be undone.
*
* @return the next significant edit to be undone
*/
protected UndoableEdit editToBeUndone() {
int i = indexOfNextAdd;
while (i > 0) {
UndoableEdit edit = edits.elementAt(--i);
if (edit.isSignificant()) {
return edit;
}
}
return null;
}
/**
* Returns the the next significant edit to be redone if <code>redo
* is invoked. This returns <code>null if there are no edits
* to be redone.
*
* @return the next significant edit to be redone
*/
protected UndoableEdit editToBeRedone() {
int count = edits.size();
int i = indexOfNextAdd;
while (i < count) {
UndoableEdit edit = edits.elementAt(i++);
if (edit.isSignificant()) {
return edit;
}
}
return null;
}
/**
* Undoes all changes from the index of the next edit to
* <code>edit, updating the index of the next edit appropriately.
*
* @throws CannotUndoException if one of the edits throws
* <code>CannotUndoException
*/
protected void undoTo(UndoableEdit edit) throws CannotUndoException {
boolean done = false;
while (!done) {
UndoableEdit next = edits.elementAt(--indexOfNextAdd);
next.undo();
done = next == edit;
}
}
/**
* Redoes all changes from the index of the next edit to
* <code>edit, updating the index of the next edit appropriately.
*
* @throws CannotRedoException if one of the edits throws
* <code>CannotRedoException
*/
protected void redoTo(UndoableEdit edit) throws CannotRedoException {
boolean done = false;
while (!done) {
UndoableEdit next = edits.elementAt(indexOfNextAdd++);
next.redo();
done = next == edit;
}
}
/**
* Convenience method that invokes one of <code>undo or
* <code>redo. If any edits have been undone (the index of
* the next edit is less than the length of the edits list) this
* invokes <code>redo, otherwise it invokes undo .
*
* @see #canUndoOrRedo
* @see #getUndoOrRedoPresentationName
* @throws CannotUndoException if one of the edits throws
* <code>CannotUndoException
* @throws CannotRedoException if one of the edits throws
* <code>CannotRedoException
*/
public synchronized void undoOrRedo() throws CannotRedoException,
CannotUndoException {
if (indexOfNextAdd == edits.size()) {
undo();
} else {
redo();
}
}
/**
* Returns true if it is possible to invoke <code>undo or
* <code>redo.
*
* @return true if invoking <code>canUndoOrRedo is valid
* @see #undoOrRedo
*/
public synchronized boolean canUndoOrRedo() {
if (indexOfNextAdd == edits.size()) {
return canUndo();
} else {
return canRedo();
}
}
/**
* Undoes the appropriate edits. If <code>end has been
* invoked this calls through to the superclass, otherwise
* this invokes <code>undo on all edits between the
* index of the next edit and the last significant edit, updating
* the index of the next edit appropriately.
*
* @throws CannotUndoException if one of the edits throws
* <code>CannotUndoException or there are no edits
* to be undone
* @see CompoundEdit#end
* @see #canUndo
* @see #editToBeUndone
*/
public synchronized void undo() throws CannotUndoException {
if (inProgress) {
UndoableEdit edit = editToBeUndone();
if (edit == null) {
throw new CannotUndoException();
}
undoTo(edit);
} else {
super.undo();
}
}
/**
* Returns true if edits may be undone. If <code>end has
* been invoked, this returns the value from super. Otherwise
* this returns true if there are any edits to be undone
* (<code>editToBeUndone returns non-null ).
*
* @return true if there are edits to be undone
* @see CompoundEdit#canUndo
* @see #editToBeUndone
*/
public synchronized boolean canUndo() {
if (inProgress) {
UndoableEdit edit = editToBeUndone();
return edit != null && edit.canUndo();
} else {
return super.canUndo();
}
}
/**
* Redoes the appropriate edits. If <code>end has been
* invoked this calls through to the superclass. Otherwise
* this invokes <code>redo on all edits between the
* index of the next edit and the next significant edit, updating
* the index of the next edit appropriately.
*
* @throws CannotRedoException if one of the edits throws
* <code>CannotRedoException or there are no edits
* to be redone
* @see CompoundEdit#end
* @see #canRedo
* @see #editToBeRedone
*/
public synchronized void redo() throws CannotRedoException {
if (inProgress) {
UndoableEdit edit = editToBeRedone();
if (edit == null) {
throw new CannotRedoException();
}
redoTo(edit);
} else {
super.redo();
}
}
/**
* Returns true if edits may be redone. If <code>end has
* been invoked, this returns the value from super. Otherwise,
* this returns true if there are any edits to be redone
* (<code>editToBeRedone returns non-null ).
*
* @return true if there are edits to be redone
* @see CompoundEdit#canRedo
* @see #editToBeRedone
*/
public synchronized boolean canRedo() {
if (inProgress) {
UndoableEdit edit = editToBeRedone();
return edit != null && edit.canRedo();
} else {
return super.canRedo();
}
}
/**
* Adds an <code>UndoableEdit to this
* <code>UndoManager, if it's possible. This removes all
* edits from the index of the next edit to the end of the edits
* list. If <code>end has been invoked the edit is not added
* and <code>false is returned. If end hasn't
* been invoked this returns <code>true.
*
* @param anEdit the edit to be added
* @return true if <code>anEdit can be incorporated into this
* edit
* @see CompoundEdit#end
* @see CompoundEdit#addEdit
*/
public synchronized boolean addEdit(UndoableEdit anEdit) {
boolean retVal;
// Trim from the indexOfNextAdd to the end, as we'll
// never reach these edits once the new one is added.
trimEdits(indexOfNextAdd, edits.size()-1);
retVal = super.addEdit(anEdit);
if (inProgress) {
retVal = true;
}
// Maybe super added this edit, maybe it didn't (perhaps
// an in progress compound edit took it instead. Or perhaps
// this UndoManager is no longer in progress). So make sure
// the indexOfNextAdd is pointed at the right place.
indexOfNextAdd = edits.size();
// Enforce the limit
trimForLimit();
return retVal;
}
/**
* Turns this <code>UndoManager into a normal
* <code>CompoundEdit. This removes all edits that have
* been undone.
*
* @see CompoundEdit#end
*/
public synchronized void end() {
super.end();
this.trimEdits(indexOfNextAdd, edits.size()-1);
}
/**
* Convenience method that returns either
* <code>getUndoPresentationName or
* <code>getRedoPresentationName. If the index of the next
* edit equals the size of the edits list,
* <code>getUndoPresentationName is returned, otherwise
* <code>getRedoPresentationName is returned.
*
* @return undo or redo name
*/
public synchronized String getUndoOrRedoPresentationName() {
if (indexOfNextAdd == edits.size()) {
return getUndoPresentationName();
} else {
return getRedoPresentationName();
}
}
/**
* Returns a description of the undoable form of this edit.
* If <code>end has been invoked this calls into super.
* Otherwise if there are edits to be undone, this returns
* the value from the next significant edit that will be undone.
* If there are no edits to be undone and <code>end has not
* been invoked this returns the value from the <code>UIManager
* property "AbstractUndoableEdit.undoText".
*
* @return a description of the undoable form of this edit
* @see #undo
* @see CompoundEdit#getUndoPresentationName
*/
public synchronized String getUndoPresentationName() {
if (inProgress) {
if (canUndo()) {
return editToBeUndone().getUndoPresentationName();
} else {
return UIManager.getString("AbstractUndoableEdit.undoText");
}
} else {
return super.getUndoPresentationName();
}
}
/**
* Returns a description of the redoable form of this edit.
* If <code>end has been invoked this calls into super.
* Otherwise if there are edits to be redone, this returns
* the value from the next significant edit that will be redone.
* If there are no edits to be redone and <code>end has not
* been invoked this returns the value from the <code>UIManager
* property "AbstractUndoableEdit.redoText".
*
* @return a description of the redoable form of this edit
* @see #redo
* @see CompoundEdit#getRedoPresentationName
*/
public synchronized String getRedoPresentationName() {
if (inProgress) {
if (canRedo()) {
return editToBeRedone().getRedoPresentationName();
} else {
return UIManager.getString("AbstractUndoableEdit.redoText");
}
} else {
return super.getRedoPresentationName();
}
}
/**
* An <code>UndoableEditListener method. This invokes
* <code>addEdit with e.getEdit() .
*
* @param e the <code>UndoableEditEvent the
* <code>UndoableEditEvent will be added from
* @see #addEdit
*/
public void undoableEditHappened(UndoableEditEvent e) {
addEdit(e.getEdit());
}
/**
* Returns a string that displays and identifies this
* object's properties.
*
* @return a String representation of this object
*/
public String toString() {
return super.toString() + " limit: " + limit +
" indexOfNextAdd: " + indexOfNextAdd;
}
}
Other Java examples (source code examples)
Here is a short list of links related to this Java UndoManager.java source code file:
|