--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/jdk/src/share/classes/javax/swing/undo/UndoManager.java Sat Dec 01 00:00:00 2007 +0000
@@ -0,0 +1,626 @@
+/*
+ * Copyright 1997-2006 Sun Microsystems, Inc. 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. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.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</code>. Add the edit
+ * directly using the <code>addEdit</code> method, or add the
+ * <code>UndoManager</code> to a bean that supports
+ * <code>UndoableEditListener</code>. The following examples creates
+ * an <code>UndoManager</code> and adds it as an
+ * <code>UndoableEditListener</code> to a <code>JTextField</code>:
+ * <pre>
+ * UndoManager undoManager = new UndoManager();
+ * JTextField tf = ...;
+ * tf.getDocument().addUndoableEditListener(undoManager);
+ * </pre>
+ * <p>
+ * <code>UndoManager</code> 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</code> has been invoked it corresponds to the index
+ * of the last significant edit that was undone. When
+ * <code>undo</code> 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</code> consisting of the
+ * following edits: <b>A</b> <i>b</i> <i>c</i> <b>D</b>. Edits with a
+ * upper-case letter in bold are significant, those in lower-case
+ * and italicized are insignificant.
+ * <p>
+ * <a name="figure1"></a>
+ * <table border=0>
+ * <tr><td>
+ * <img src="doc-files/UndoManager-1.gif">
+ * <tr><td align=center>Figure 1
+ * </table>
+ * <p>
+ * As shown in <a href="#figure1">figure 1</a>, if <b>D</b> was just added, the
+ * index of the next edit will be 4. Invoking <code>undo</code>
+ * results in invoking <code>undo</code> on <b>D</b> and setting the
+ * index of the next edit to 3 (edit <i>c</i>), as shown in the following
+ * figure.
+ * <p>
+ * <a name="figure2"></a>
+ * <table border=0>
+ * <tr><td>
+ * <img src="doc-files/UndoManager-2.gif">
+ * <tr><td align=center>Figure 2
+ * </table>
+ * <p>
+ * The last significant edit is <b>A</b>, so that invoking
+ * <code>undo</code> again invokes <code>undo</code> on <i>c</i>,
+ * <i>b</i>, and <b>A</b>, in that order, setting the index of the
+ * next edit to 0, as shown in the following figure.
+ * <p>
+ * <a name="figure3"></a>
+ * <table border=0>
+ * <tr><td>
+ * <img src="doc-files/UndoManager-3.gif">
+ * <tr><td align=center>Figure 3
+ * </table>
+ * <p>
+ * Invoking <code>redo</code> results in invoking <code>redo</code> 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</code> were invoked, <code>redo</code> would in
+ * turn be invoked on <b>A</b>, <i>b</i> and <i>c</i>. 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</code> 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</i>, is added the edit <b>D</b> is removed from the list
+ * (after having <code>die</code> invoked on it). If <i>c</i> is not
+ * incorporated by the next edit
+ * (<code><i>c</i>.addEdit(<i>e</i>)</code> returns true), or replaced
+ * by it (<code><i>e</i>.replaceEdit(<i>c</i>)</code> returns true),
+ * the new edit is added after <i>c</i>, as shown in the following
+ * figure.
+ * <p>
+ * <a name="figure4"></a>
+ * <table border=0>
+ * <tr><td>
+ * <img src="doc-files/UndoManager-4.gif">
+ * <tr><td align=center>Figure 4
+ * </table>
+ * <p>
+ * Once <code>end</code> has been invoked on an <code>UndoManager</code>
+ * the superclass behavior is used for all <code>UndoableEdit</code>
+ * methods. Refer to <code>CompoundEdit</code> for more details on its
+ * behavior.
+ * <p>
+ * Unlike the rest of Swing, this class is thread safe.
+ * <p>
+ * <strong>Warning:</strong>
+ * 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<sup><font size="-2">TM</font></sup>
+ * has been added to the <code>java.beans</code> 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</code>.
+ */
+ 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</code> message
+ * in the process.
+ *
+ * @see AbstractUndoableEdit#die
+ */
+ public synchronized void discardAllEdits() {
+ Enumeration cursor = edits.elements();
+ while (cursor.hasMoreElements()) {
+ UndoableEdit e = (UndoableEdit)cursor.nextElement();
+ e.die();
+ }
+ edits = new Vector();
+ 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</code> invoked on them and are removed from
+ * the list of edits. This has no effect if
+ * <code>from</code> > <code>to</code>.
+ *
+ * @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 = (UndoableEdit)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</code>
+ * 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</code> 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</code>
+ * is invoked. This returns <code>null</code> 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 = (UndoableEdit)edits.elementAt(--i);
+ if (edit.isSignificant()) {
+ return edit;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Returns the the next significant edit to be redone if <code>redo</code>
+ * is invoked. This returns <code>null</code> 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 = (UndoableEdit)edits.elementAt(i++);
+ if (edit.isSignificant()) {
+ return edit;
+ }
+ }
+
+ return null;
+ }
+
+ /**
+ * Undoes all changes from the index of the next edit to
+ * <code>edit</code>, updating the index of the next edit appropriately.
+ *
+ * @throws CannotUndoException if one of the edits throws
+ * <code>CannotUndoException</code>
+ */
+ protected void undoTo(UndoableEdit edit) throws CannotUndoException {
+ boolean done = false;
+ while (!done) {
+ UndoableEdit next = (UndoableEdit)edits.elementAt(--indexOfNextAdd);
+ next.undo();
+ done = next == edit;
+ }
+ }
+
+ /**
+ * Redoes all changes from the index of the next edit to
+ * <code>edit</code>, updating the index of the next edit appropriately.
+ *
+ * @throws CannotRedoException if one of the edits throws
+ * <code>CannotRedoException</code>
+ */
+ protected void redoTo(UndoableEdit edit) throws CannotRedoException {
+ boolean done = false;
+ while (!done) {
+ UndoableEdit next = (UndoableEdit)edits.elementAt(indexOfNextAdd++);
+ next.redo();
+ done = next == edit;
+ }
+ }
+
+ /**
+ * Convenience method that invokes one of <code>undo</code> or
+ * <code>redo</code>. 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</code>, otherwise it invokes <code>undo</code>.
+ *
+ * @see #canUndoOrRedo
+ * @see #getUndoOrRedoPresentationName
+ * @throws CannotUndoException if one of the edits throws
+ * <code>CannotUndoException</code>
+ * @throws CannotRedoException if one of the edits throws
+ * <code>CannotRedoException</code>
+ */
+ public synchronized void undoOrRedo() throws CannotRedoException,
+ CannotUndoException {
+ if (indexOfNextAdd == edits.size()) {
+ undo();
+ } else {
+ redo();
+ }
+ }
+
+ /**
+ * Returns true if it is possible to invoke <code>undo</code> or
+ * <code>redo</code>.
+ *
+ * @return true if invoking <code>canUndoOrRedo</code> is valid
+ * @see #undoOrRedo
+ */
+ public synchronized boolean canUndoOrRedo() {
+ if (indexOfNextAdd == edits.size()) {
+ return canUndo();
+ } else {
+ return canRedo();
+ }
+ }
+
+ /**
+ * Undoes the appropriate edits. If <code>end</code> has been
+ * invoked this calls through to the superclass, otherwise
+ * this invokes <code>undo</code> 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</code> 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</code> has
+ * been invoked, this returns the value from super. Otherwise
+ * this returns true if there are any edits to be undone
+ * (<code>editToBeUndone</code> returns non-<code>null</code>).
+ *
+ * @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</code> has been
+ * invoked this calls through to the superclass. Otherwise
+ * this invokes <code>redo</code> 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</code> 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</code> has
+ * been invoked, this returns the value from super. Otherwise,
+ * this returns true if there are any edits to be redone
+ * (<code>editToBeRedone</code> returns non-<code>null</code>).
+ *
+ * @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</code> to this
+ * <code>UndoManager</code>, 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</code> has been invoked the edit is not added
+ * and <code>false</code> is returned. If <code>end</code> hasn't
+ * been invoked this returns <code>true</code>.
+ *
+ * @param anEdit the edit to be added
+ * @return true if <code>anEdit</code> 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</code> into a normal
+ * <code>CompoundEdit</code>. 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</code> or
+ * <code>getRedoPresentationName</code>. If the index of the next
+ * edit equals the size of the edits list,
+ * <code>getUndoPresentationName</code> is returned, otherwise
+ * <code>getRedoPresentationName</code> 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</code> 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</code> has not
+ * been invoked this returns the value from the <code>UIManager</code>
+ * 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</code> 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</code> has not
+ * been invoked this returns the value from the <code>UIManager</code>
+ * 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</code> method. This invokes
+ * <code>addEdit</code> with <code>e.getEdit()</code>.
+ *
+ * @param e the <code>UndoableEditEvent</code> the
+ * <code>UndoableEditEvent</code> 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;
+ }
+}