624 lines · 21.8 KB · Java
RawDownload
/* * Copyright (c) 1997, 2008, 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</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() {        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</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 = 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 = 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 = 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 = 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 = 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

Lines 1–500 of 624.