jdk/src/share/classes/javax/swing/undo/UndoManager.java
changeset 2 90ce3da70b43
child 1301 15e81207e1f2
--- /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> &gt; <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;
+    }
+}