1 /*
   2  * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package javax.swing.undo;
  27 
  28 import javax.swing.event.*;
  29 import javax.swing.UIManager;
  30 import java.util.*;
  31 
  32 /**
  33  * {@code UndoManager} manages a list of {@code UndoableEdits},
  34  * providing a way to undo or redo the appropriate edits.  There are
  35  * two ways to add edits to an <code>UndoManager</code>.  Add the edit
  36  * directly using the <code>addEdit</code> method, or add the
  37  * <code>UndoManager</code> to a bean that supports
  38  * <code>UndoableEditListener</code>.  The following examples creates
  39  * an <code>UndoManager</code> and adds it as an
  40  * <code>UndoableEditListener</code> to a <code>JTextField</code>:
  41  * <pre>
  42  *   UndoManager undoManager = new UndoManager();
  43  *   JTextField tf = ...;
  44  *   tf.getDocument().addUndoableEditListener(undoManager);
  45  * </pre>
  46  * <p>
  47  * <code>UndoManager</code> maintains an ordered list of edits and the
  48  * index of the next edit in that list. The index of the next edit is
  49  * either the size of the current list of edits, or if
  50  * <code>undo</code> has been invoked it corresponds to the index
  51  * of the last significant edit that was undone. When
  52  * <code>undo</code> is invoked all edits from the index of the next
  53  * edit to the last significant edit are undone, in reverse order.
  54  * For example, consider an <code>UndoManager</code> consisting of the
  55  * following edits: <b>A</b> <i>b</i> <i>c</i> <b>D</b>.  Edits with a
  56  * upper-case letter in bold are significant, those in lower-case
  57  * and italicized are insignificant.
  58  * <p>
  59  * <a name="figure1"></a>
  60  * <table border=0 summary="">
  61  * <tr><td>
  62  *     <img src="doc-files/UndoManager-1.gif" alt="">
  63  * <tr><td align=center>Figure 1
  64  * </table>
  65  * <p>
  66  * As shown in <a href="#figure1">figure 1</a>, if <b>D</b> was just added, the
  67  * index of the next edit will be 4. Invoking <code>undo</code>
  68  * results in invoking <code>undo</code> on <b>D</b> and setting the
  69  * index of the next edit to 3 (edit <i>c</i>), as shown in the following
  70  * figure.
  71  * <p>
  72  * <a name="figure2"></a>
  73  * <table border=0 summary="">
  74  * <tr><td>
  75  *     <img src="doc-files/UndoManager-2.gif" alt="">
  76  * <tr><td align=center>Figure 2
  77  * </table>
  78  * <p>
  79  * The last significant edit is <b>A</b>, so that invoking
  80  * <code>undo</code> again invokes <code>undo</code> on <i>c</i>,
  81  * <i>b</i>, and <b>A</b>, in that order, setting the index of the
  82  * next edit to 0, as shown in the following figure.
  83  * <p>
  84  * <a name="figure3"></a>
  85  * <table border=0 summary="">
  86  * <tr><td>
  87  *     <img src="doc-files/UndoManager-3.gif" alt="">
  88  * <tr><td align=center>Figure 3
  89  * </table>
  90  * <p>
  91  * Invoking <code>redo</code> results in invoking <code>redo</code> on
  92  * all edits between the index of the next edit and the next
  93  * significant edit (or the end of the list).  Continuing with the previous
  94  * example if <code>redo</code> were invoked, <code>redo</code> would in
  95  * turn be invoked on <b>A</b>, <i>b</i> and <i>c</i>.  In addition
  96  * the index of the next edit is set to 3 (as shown in <a
  97  * href="#figure2">figure 2</a>).
  98  * <p>
  99  * Adding an edit to an <code>UndoManager</code> results in
 100  * removing all edits from the index of the next edit to the end of
 101  * the list.  Continuing with the previous example, if a new edit,
 102  * <i>e</i>, is added the edit <b>D</b> is removed from the list
 103  * (after having <code>die</code> invoked on it).  If <i>c</i> is not
 104  * incorporated by the next edit
 105  * (<code><i>c</i>.addEdit(<i>e</i>)</code> returns true), or replaced
 106  * by it (<code><i>e</i>.replaceEdit(<i>c</i>)</code> returns true),
 107  * the new edit is added after <i>c</i>, as shown in the following
 108  * figure.
 109  * <p>
 110  * <a name="figure4"></a>
 111  * <table border=0 summary="">
 112  * <tr><td>
 113  *     <img src="doc-files/UndoManager-4.gif" alt="">
 114  * <tr><td align=center>Figure 4
 115  * </table>
 116  * <p>
 117  * Once <code>end</code> has been invoked on an <code>UndoManager</code>
 118  * the superclass behavior is used for all <code>UndoableEdit</code>
 119  * methods.  Refer to <code>CompoundEdit</code> for more details on its
 120  * behavior.
 121  * <p>
 122  * Unlike the rest of Swing, this class is thread safe.
 123  * <p>
 124  * <strong>Warning:</strong>
 125  * Serialized objects of this class will not be compatible with
 126  * future Swing releases. The current serialization support is
 127  * appropriate for short term storage or RMI between applications running
 128  * the same version of Swing.  As of 1.4, support for long term storage
 129  * of all JavaBeans&trade;
 130  * has been added to the <code>java.beans</code> package.
 131  * Please see {@link java.beans.XMLEncoder}.
 132  *
 133  * @author Ray Ryan
 134  */
 135 @SuppressWarnings("serial") // Same-version serialization only
 136 public class UndoManager extends CompoundEdit implements UndoableEditListener {
 137     int indexOfNextAdd;
 138     int limit;
 139 
 140     /**
 141      * Creates a new <code>UndoManager</code>.
 142      */
 143     public UndoManager() {
 144         super();
 145         indexOfNextAdd = 0;
 146         limit = 100;
 147         edits.ensureCapacity(limit);
 148     }
 149 
 150     /**
 151      * Returns the maximum number of edits this {@code UndoManager}
 152      * holds. A value less than 0 indicates the number of edits is not
 153      * limited.
 154      *
 155      * @return the maximum number of edits this {@code UndoManager} holds
 156      * @see #addEdit
 157      * @see #setLimit
 158      */
 159     public synchronized int getLimit() {
 160         return limit;
 161     }
 162 
 163     /**
 164      * Empties the undo manager sending each edit a <code>die</code> message
 165      * in the process.
 166      *
 167      * @see AbstractUndoableEdit#die
 168      */
 169     public synchronized void discardAllEdits() {
 170         for (UndoableEdit e : edits) {
 171             e.die();
 172         }
 173         edits = new Vector<UndoableEdit>();
 174         indexOfNextAdd = 0;
 175         // PENDING(rjrjr) when vector grows a removeRange() method
 176         // (expected in JDK 1.2), trimEdits() will be nice and
 177         // efficient, and this method can call that instead.
 178     }
 179 
 180     /**
 181      * Reduces the number of queued edits to a range of size limit,
 182      * centered on the index of the next edit.
 183      */
 184     protected void trimForLimit() {
 185         if (limit >= 0) {
 186             int size = edits.size();
 187 //          System.out.print("limit: " + limit +
 188 //                           " size: " + size +
 189 //                           " indexOfNextAdd: " + indexOfNextAdd +
 190 //                           "\n");
 191 
 192             if (size > limit) {
 193                 int halfLimit = limit/2;
 194                 int keepFrom = indexOfNextAdd - 1 - halfLimit;
 195                 int keepTo   = indexOfNextAdd - 1 + halfLimit;
 196 
 197                 // These are ints we're playing with, so dividing by two
 198                 // rounds down for odd numbers, so make sure the limit was
 199                 // honored properly. Note that the keep range is
 200                 // inclusive.
 201 
 202                 if (keepTo - keepFrom + 1 > limit) {
 203                     keepFrom++;
 204                 }
 205 
 206                 // The keep range is centered on indexOfNextAdd,
 207                 // but odds are good that the actual edits Vector
 208                 // isn't. Move the keep range to keep it legal.
 209 
 210                 if (keepFrom < 0) {
 211                     keepTo -= keepFrom;
 212                     keepFrom = 0;
 213                 }
 214                 if (keepTo >= size) {
 215                     int delta = size - keepTo - 1;
 216                     keepTo += delta;
 217                     keepFrom += delta;
 218                 }
 219 
 220 //              System.out.println("Keeping " + keepFrom + " " + keepTo);
 221                 trimEdits(keepTo+1, size-1);
 222                 trimEdits(0, keepFrom-1);
 223             }
 224         }
 225     }
 226 
 227     /**
 228      * Removes edits in the specified range.
 229      * All edits in the given range (inclusive, and in reverse order)
 230      * will have <code>die</code> invoked on them and are removed from
 231      * the list of edits. This has no effect if
 232      * <code>from</code> &gt; <code>to</code>.
 233      *
 234      * @param from the minimum index to remove
 235      * @param to the maximum index to remove
 236      */
 237     protected void trimEdits(int from, int to) {
 238         if (from <= to) {
 239 //          System.out.println("Trimming " + from + " " + to + " with index " +
 240 //                           indexOfNextAdd);
 241             for (int i = to; from <= i; i--) {
 242                 UndoableEdit e = edits.elementAt(i);
 243 //              System.out.println("JUM: Discarding " +
 244 //                                 e.getUndoPresentationName());
 245                 e.die();
 246                 // PENDING(rjrjr) when Vector supports range deletion (JDK
 247                 // 1.2) , we can optimize the next line considerably.
 248                 edits.removeElementAt(i);
 249             }
 250 
 251             if (indexOfNextAdd > to) {
 252 //              System.out.print("...right...");
 253                 indexOfNextAdd -= to-from+1;
 254             } else if (indexOfNextAdd >= from) {
 255 //              System.out.println("...mid...");
 256                 indexOfNextAdd = from;
 257             }
 258 
 259 //          System.out.println("new index " + indexOfNextAdd);
 260         }
 261     }
 262 
 263     /**
 264      * Sets the maximum number of edits this <code>UndoManager</code>
 265      * holds. A value less than 0 indicates the number of edits is not
 266      * limited. If edits need to be discarded to shrink the limit,
 267      * <code>die</code> will be invoked on them in the reverse
 268      * order they were added.  The default is 100.
 269      *
 270      * @param l the new limit
 271      * @throws RuntimeException if this {@code UndoManager} is not in progress
 272      *                          ({@code end} has been invoked)
 273      * @see #isInProgress
 274      * @see #end
 275      * @see #addEdit
 276      * @see #getLimit
 277      */
 278     public synchronized void setLimit(int l) {
 279         if (!inProgress) throw new RuntimeException("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called");
 280         limit = l;
 281         trimForLimit();
 282     }
 283 
 284 
 285     /**
 286      * Returns the next significant edit to be undone if <code>undo</code>
 287      * is invoked. This returns <code>null</code> if there are no edits
 288      * to be undone.
 289      *
 290      * @return the next significant edit to be undone
 291      */
 292     protected UndoableEdit editToBeUndone() {
 293         int i = indexOfNextAdd;
 294         while (i > 0) {
 295             UndoableEdit edit = edits.elementAt(--i);
 296             if (edit.isSignificant()) {
 297                 return edit;
 298             }
 299         }
 300 
 301         return null;
 302     }
 303 
 304     /**
 305      * Returns the next significant edit to be redone if <code>redo</code>
 306      * is invoked. This returns <code>null</code> if there are no edits
 307      * to be redone.
 308      *
 309      * @return the next significant edit to be redone
 310      */
 311     protected UndoableEdit editToBeRedone() {
 312         int count = edits.size();
 313         int i = indexOfNextAdd;
 314 
 315         while (i < count) {
 316             UndoableEdit edit = edits.elementAt(i++);
 317             if (edit.isSignificant()) {
 318                 return edit;
 319             }
 320         }
 321 
 322         return null;
 323     }
 324 
 325     /**
 326      * Undoes all changes from the index of the next edit to
 327      * <code>edit</code>, updating the index of the next edit appropriately.
 328      *
 329      * @param edit the edit to be undo to
 330      * @throws CannotUndoException if one of the edits throws
 331      *         <code>CannotUndoException</code>
 332      */
 333     protected void undoTo(UndoableEdit edit) throws CannotUndoException {
 334         boolean done = false;
 335         while (!done) {
 336             UndoableEdit next = edits.elementAt(--indexOfNextAdd);
 337             next.undo();
 338             done = next == edit;
 339         }
 340     }
 341 
 342     /**
 343      * Redoes all changes from the index of the next edit to
 344      * <code>edit</code>, updating the index of the next edit appropriately.
 345      *
 346      * @param edit the edit to be redo to
 347      * @throws CannotRedoException if one of the edits throws
 348      *         <code>CannotRedoException</code>
 349      */
 350     protected void redoTo(UndoableEdit edit) throws CannotRedoException {
 351         boolean done = false;
 352         while (!done) {
 353             UndoableEdit next = edits.elementAt(indexOfNextAdd++);
 354             next.redo();
 355             done = next == edit;
 356         }
 357     }
 358 
 359     /**
 360      * Convenience method that invokes one of <code>undo</code> or
 361      * <code>redo</code>. If any edits have been undone (the index of
 362      * the next edit is less than the length of the edits list) this
 363      * invokes <code>redo</code>, otherwise it invokes <code>undo</code>.
 364      *
 365      * @see #canUndoOrRedo
 366      * @see #getUndoOrRedoPresentationName
 367      * @throws CannotUndoException if one of the edits throws
 368      *         <code>CannotUndoException</code>
 369      * @throws CannotRedoException if one of the edits throws
 370      *         <code>CannotRedoException</code>
 371      */
 372     public synchronized void undoOrRedo() throws CannotRedoException,
 373         CannotUndoException {
 374         if (indexOfNextAdd == edits.size()) {
 375             undo();
 376         } else {
 377             redo();
 378         }
 379     }
 380 
 381     /**
 382      * Returns true if it is possible to invoke <code>undo</code> or
 383      * <code>redo</code>.
 384      *
 385      * @return true if invoking <code>canUndoOrRedo</code> is valid
 386      * @see #undoOrRedo
 387      */
 388     public synchronized boolean canUndoOrRedo() {
 389         if (indexOfNextAdd == edits.size()) {
 390             return canUndo();
 391         } else {
 392             return canRedo();
 393         }
 394     }
 395 
 396     /**
 397      * Undoes the appropriate edits.  If <code>end</code> has been
 398      * invoked this calls through to the superclass, otherwise
 399      * this invokes <code>undo</code> on all edits between the
 400      * index of the next edit and the last significant edit, updating
 401      * the index of the next edit appropriately.
 402      *
 403      * @throws CannotUndoException if one of the edits throws
 404      *         <code>CannotUndoException</code> or there are no edits
 405      *         to be undone
 406      * @see CompoundEdit#end
 407      * @see #canUndo
 408      * @see #editToBeUndone
 409      */
 410     public synchronized void undo() throws CannotUndoException {
 411         if (inProgress) {
 412             UndoableEdit edit = editToBeUndone();
 413             if (edit == null) {
 414                 throw new CannotUndoException();
 415             }
 416             undoTo(edit);
 417         } else {
 418             super.undo();
 419         }
 420     }
 421 
 422     /**
 423      * Returns true if edits may be undone.  If <code>end</code> has
 424      * been invoked, this returns the value from super.  Otherwise
 425      * this returns true if there are any edits to be undone
 426      * (<code>editToBeUndone</code> returns non-<code>null</code>).
 427      *
 428      * @return true if there are edits to be undone
 429      * @see CompoundEdit#canUndo
 430      * @see #editToBeUndone
 431      */
 432     public synchronized boolean canUndo() {
 433         if (inProgress) {
 434             UndoableEdit edit = editToBeUndone();
 435             return edit != null && edit.canUndo();
 436         } else {
 437             return super.canUndo();
 438         }
 439     }
 440 
 441     /**
 442      * Redoes the appropriate edits.  If <code>end</code> has been
 443      * invoked this calls through to the superclass.  Otherwise
 444      * this invokes <code>redo</code> on all edits between the
 445      * index of the next edit and the next significant edit, updating
 446      * the index of the next edit appropriately.
 447      *
 448      * @throws CannotRedoException if one of the edits throws
 449      *         <code>CannotRedoException</code> or there are no edits
 450      *         to be redone
 451      * @see CompoundEdit#end
 452      * @see #canRedo
 453      * @see #editToBeRedone
 454      */
 455     public synchronized void redo() throws CannotRedoException {
 456         if (inProgress) {
 457             UndoableEdit edit = editToBeRedone();
 458             if (edit == null) {
 459                 throw new CannotRedoException();
 460             }
 461             redoTo(edit);
 462         } else {
 463             super.redo();
 464         }
 465     }
 466 
 467     /**
 468      * Returns true if edits may be redone.  If <code>end</code> has
 469      * been invoked, this returns the value from super.  Otherwise,
 470      * this returns true if there are any edits to be redone
 471      * (<code>editToBeRedone</code> returns non-<code>null</code>).
 472      *
 473      * @return true if there are edits to be redone
 474      * @see CompoundEdit#canRedo
 475      * @see #editToBeRedone
 476      */
 477     public synchronized boolean canRedo() {
 478         if (inProgress) {
 479             UndoableEdit edit = editToBeRedone();
 480             return edit != null && edit.canRedo();
 481         } else {
 482             return super.canRedo();
 483         }
 484     }
 485 
 486     /**
 487      * Adds an <code>UndoableEdit</code> to this
 488      * <code>UndoManager</code>, if it's possible.  This removes all
 489      * edits from the index of the next edit to the end of the edits
 490      * list.  If <code>end</code> has been invoked the edit is not added
 491      * and <code>false</code> is returned.  If <code>end</code> hasn't
 492      * been invoked this returns <code>true</code>.
 493      *
 494      * @param anEdit the edit to be added
 495      * @return true if <code>anEdit</code> can be incorporated into this
 496      *              edit
 497      * @see CompoundEdit#end
 498      * @see CompoundEdit#addEdit
 499      */
 500     public synchronized boolean addEdit(UndoableEdit anEdit) {
 501         boolean retVal;
 502 
 503         // Trim from the indexOfNextAdd to the end, as we'll
 504         // never reach these edits once the new one is added.
 505         trimEdits(indexOfNextAdd, edits.size()-1);
 506 
 507         retVal = super.addEdit(anEdit);
 508         if (inProgress) {
 509           retVal = true;
 510         }
 511 
 512         // Maybe super added this edit, maybe it didn't (perhaps
 513         // an in progress compound edit took it instead. Or perhaps
 514         // this UndoManager is no longer in progress). So make sure
 515         // the indexOfNextAdd is pointed at the right place.
 516         indexOfNextAdd = edits.size();
 517 
 518         // Enforce the limit
 519         trimForLimit();
 520 
 521         return retVal;
 522     }
 523 
 524 
 525     /**
 526      * Turns this <code>UndoManager</code> into a normal
 527      * <code>CompoundEdit</code>.  This removes all edits that have
 528      * been undone.
 529      *
 530      * @see CompoundEdit#end
 531      */
 532     public synchronized void end() {
 533         super.end();
 534         this.trimEdits(indexOfNextAdd, edits.size()-1);
 535     }
 536 
 537     /**
 538      * Convenience method that returns either
 539      * <code>getUndoPresentationName</code> or
 540      * <code>getRedoPresentationName</code>.  If the index of the next
 541      * edit equals the size of the edits list,
 542      * <code>getUndoPresentationName</code> is returned, otherwise
 543      * <code>getRedoPresentationName</code> is returned.
 544      *
 545      * @return undo or redo name
 546      */
 547     public synchronized String getUndoOrRedoPresentationName() {
 548         if (indexOfNextAdd == edits.size()) {
 549             return getUndoPresentationName();
 550         } else {
 551             return getRedoPresentationName();
 552         }
 553     }
 554 
 555     /**
 556      * Returns a description of the undoable form of this edit.
 557      * If <code>end</code> has been invoked this calls into super.
 558      * Otherwise if there are edits to be undone, this returns
 559      * the value from the next significant edit that will be undone.
 560      * If there are no edits to be undone and <code>end</code> has not
 561      * been invoked this returns the value from the <code>UIManager</code>
 562      * property "AbstractUndoableEdit.undoText".
 563      *
 564      * @return a description of the undoable form of this edit
 565      * @see     #undo
 566      * @see     CompoundEdit#getUndoPresentationName
 567      */
 568     public synchronized String getUndoPresentationName() {
 569         if (inProgress) {
 570             if (canUndo()) {
 571                 return editToBeUndone().getUndoPresentationName();
 572             } else {
 573                 return UIManager.getString("AbstractUndoableEdit.undoText");
 574             }
 575         } else {
 576             return super.getUndoPresentationName();
 577         }
 578     }
 579 
 580     /**
 581      * Returns a description of the redoable form of this edit.
 582      * If <code>end</code> has been invoked this calls into super.
 583      * Otherwise if there are edits to be redone, this returns
 584      * the value from the next significant edit that will be redone.
 585      * If there are no edits to be redone and <code>end</code> has not
 586      * been invoked this returns the value from the <code>UIManager</code>
 587      * property "AbstractUndoableEdit.redoText".
 588      *
 589      * @return a description of the redoable form of this edit
 590      * @see     #redo
 591      * @see     CompoundEdit#getRedoPresentationName
 592      */
 593     public synchronized String getRedoPresentationName() {
 594         if (inProgress) {
 595             if (canRedo()) {
 596                 return editToBeRedone().getRedoPresentationName();
 597             } else {
 598                 return UIManager.getString("AbstractUndoableEdit.redoText");
 599             }
 600         } else {
 601             return super.getRedoPresentationName();
 602         }
 603     }
 604 
 605     /**
 606      * An <code>UndoableEditListener</code> method. This invokes
 607      * <code>addEdit</code> with <code>e.getEdit()</code>.
 608      *
 609      * @param e the <code>UndoableEditEvent</code> the
 610      *        <code>UndoableEditEvent</code> will be added from
 611      * @see #addEdit
 612      */
 613     public void undoableEditHappened(UndoableEditEvent e) {
 614         addEdit(e.getEdit());
 615     }
 616 
 617     /**
 618      * Returns a string that displays and identifies this
 619      * object's properties.
 620      *
 621      * @return a String representation of this object
 622      */
 623     public String toString() {
 624         return super.toString() + " limit: " + limit +
 625             " indexOfNextAdd: " + indexOfNextAdd;
 626     }
 627 }