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 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 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      * @throws CannotUndoException if one of the edits throws
 330      *         <code>CannotUndoException</code>
 331      */
 332     protected void undoTo(UndoableEdit edit) throws CannotUndoException {
 333         boolean done = false;
 334         while (!done) {
 335             UndoableEdit next = edits.elementAt(--indexOfNextAdd);
 336             next.undo();
 337             done = next == edit;
 338         }
 339     }
 340 
 341     /**
 342      * Redoes all changes from the index of the next edit to
 343      * <code>edit</code>, updating the index of the next edit appropriately.
 344      *
 345      * @throws CannotRedoException if one of the edits throws
 346      *         <code>CannotRedoException</code>
 347      */
 348     protected void redoTo(UndoableEdit edit) throws CannotRedoException {
 349         boolean done = false;
 350         while (!done) {
 351             UndoableEdit next = edits.elementAt(indexOfNextAdd++);
 352             next.redo();
 353             done = next == edit;
 354         }
 355     }
 356 
 357     /**
 358      * Convenience method that invokes one of <code>undo</code> or
 359      * <code>redo</code>. If any edits have been undone (the index of
 360      * the next edit is less than the length of the edits list) this
 361      * invokes <code>redo</code>, otherwise it invokes <code>undo</code>.
 362      *
 363      * @see #canUndoOrRedo
 364      * @see #getUndoOrRedoPresentationName
 365      * @throws CannotUndoException if one of the edits throws
 366      *         <code>CannotUndoException</code>
 367      * @throws CannotRedoException if one of the edits throws
 368      *         <code>CannotRedoException</code>
 369      */
 370     public synchronized void undoOrRedo() throws CannotRedoException,
 371         CannotUndoException {
 372         if (indexOfNextAdd == edits.size()) {
 373             undo();
 374         } else {
 375             redo();
 376         }
 377     }
 378 
 379     /**
 380      * Returns true if it is possible to invoke <code>undo</code> or
 381      * <code>redo</code>.
 382      *
 383      * @return true if invoking <code>canUndoOrRedo</code> is valid
 384      * @see #undoOrRedo
 385      */
 386     public synchronized boolean canUndoOrRedo() {
 387         if (indexOfNextAdd == edits.size()) {
 388             return canUndo();
 389         } else {
 390             return canRedo();
 391         }
 392     }
 393 
 394     /**
 395      * Undoes the appropriate edits.  If <code>end</code> has been
 396      * invoked this calls through to the superclass, otherwise
 397      * this invokes <code>undo</code> on all edits between the
 398      * index of the next edit and the last significant edit, updating
 399      * the index of the next edit appropriately.
 400      *
 401      * @throws CannotUndoException if one of the edits throws
 402      *         <code>CannotUndoException</code> or there are no edits
 403      *         to be undone
 404      * @see CompoundEdit#end
 405      * @see #canUndo
 406      * @see #editToBeUndone
 407      */
 408     public synchronized void undo() throws CannotUndoException {
 409         if (inProgress) {
 410             UndoableEdit edit = editToBeUndone();
 411             if (edit == null) {
 412                 throw new CannotUndoException();
 413             }
 414             undoTo(edit);
 415         } else {
 416             super.undo();
 417         }
 418     }
 419 
 420     /**
 421      * Returns true if edits may be undone.  If <code>end</code> has
 422      * been invoked, this returns the value from super.  Otherwise
 423      * this returns true if there are any edits to be undone
 424      * (<code>editToBeUndone</code> returns non-<code>null</code>).
 425      *
 426      * @return true if there are edits to be undone
 427      * @see CompoundEdit#canUndo
 428      * @see #editToBeUndone
 429      */
 430     public synchronized boolean canUndo() {
 431         if (inProgress) {
 432             UndoableEdit edit = editToBeUndone();
 433             return edit != null && edit.canUndo();
 434         } else {
 435             return super.canUndo();
 436         }
 437     }
 438 
 439     /**
 440      * Redoes the appropriate edits.  If <code>end</code> has been
 441      * invoked this calls through to the superclass.  Otherwise
 442      * this invokes <code>redo</code> on all edits between the
 443      * index of the next edit and the next significant edit, updating
 444      * the index of the next edit appropriately.
 445      *
 446      * @throws CannotRedoException if one of the edits throws
 447      *         <code>CannotRedoException</code> or there are no edits
 448      *         to be redone
 449      * @see CompoundEdit#end
 450      * @see #canRedo
 451      * @see #editToBeRedone
 452      */
 453     public synchronized void redo() throws CannotRedoException {
 454         if (inProgress) {
 455             UndoableEdit edit = editToBeRedone();
 456             if (edit == null) {
 457                 throw new CannotRedoException();
 458             }
 459             redoTo(edit);
 460         } else {
 461             super.redo();
 462         }
 463     }
 464 
 465     /**
 466      * Returns true if edits may be redone.  If <code>end</code> has
 467      * been invoked, this returns the value from super.  Otherwise,
 468      * this returns true if there are any edits to be redone
 469      * (<code>editToBeRedone</code> returns non-<code>null</code>).
 470      *
 471      * @return true if there are edits to be redone
 472      * @see CompoundEdit#canRedo
 473      * @see #editToBeRedone
 474      */
 475     public synchronized boolean canRedo() {
 476         if (inProgress) {
 477             UndoableEdit edit = editToBeRedone();
 478             return edit != null && edit.canRedo();
 479         } else {
 480             return super.canRedo();
 481         }
 482     }
 483 
 484     /**
 485      * Adds an <code>UndoableEdit</code> to this
 486      * <code>UndoManager</code>, if it's possible.  This removes all
 487      * edits from the index of the next edit to the end of the edits
 488      * list.  If <code>end</code> has been invoked the edit is not added
 489      * and <code>false</code> is returned.  If <code>end</code> hasn't
 490      * been invoked this returns <code>true</code>.
 491      *
 492      * @param anEdit the edit to be added
 493      * @return true if <code>anEdit</code> can be incorporated into this
 494      *              edit
 495      * @see CompoundEdit#end
 496      * @see CompoundEdit#addEdit
 497      */
 498     public synchronized boolean addEdit(UndoableEdit anEdit) {
 499         boolean retVal;
 500 
 501         // Trim from the indexOfNextAdd to the end, as we'll
 502         // never reach these edits once the new one is added.
 503         trimEdits(indexOfNextAdd, edits.size()-1);
 504 
 505         retVal = super.addEdit(anEdit);
 506         if (inProgress) {
 507           retVal = true;
 508         }
 509 
 510         // Maybe super added this edit, maybe it didn't (perhaps
 511         // an in progress compound edit took it instead. Or perhaps
 512         // this UndoManager is no longer in progress). So make sure
 513         // the indexOfNextAdd is pointed at the right place.
 514         indexOfNextAdd = edits.size();
 515 
 516         // Enforce the limit
 517         trimForLimit();
 518 
 519         return retVal;
 520     }
 521 
 522 
 523     /**
 524      * Turns this <code>UndoManager</code> into a normal
 525      * <code>CompoundEdit</code>.  This removes all edits that have
 526      * been undone.
 527      *
 528      * @see CompoundEdit#end
 529      */
 530     public synchronized void end() {
 531         super.end();
 532         this.trimEdits(indexOfNextAdd, edits.size()-1);
 533     }
 534 
 535     /**
 536      * Convenience method that returns either
 537      * <code>getUndoPresentationName</code> or
 538      * <code>getRedoPresentationName</code>.  If the index of the next
 539      * edit equals the size of the edits list,
 540      * <code>getUndoPresentationName</code> is returned, otherwise
 541      * <code>getRedoPresentationName</code> is returned.
 542      *
 543      * @return undo or redo name
 544      */
 545     public synchronized String getUndoOrRedoPresentationName() {
 546         if (indexOfNextAdd == edits.size()) {
 547             return getUndoPresentationName();
 548         } else {
 549             return getRedoPresentationName();
 550         }
 551     }
 552 
 553     /**
 554      * Returns a description of the undoable form of this edit.
 555      * If <code>end</code> has been invoked this calls into super.
 556      * Otherwise if there are edits to be undone, this returns
 557      * the value from the next significant edit that will be undone.
 558      * If there are no edits to be undone and <code>end</code> has not
 559      * been invoked this returns the value from the <code>UIManager</code>
 560      * property "AbstractUndoableEdit.undoText".
 561      *
 562      * @return a description of the undoable form of this edit
 563      * @see     #undo
 564      * @see     CompoundEdit#getUndoPresentationName
 565      */
 566     public synchronized String getUndoPresentationName() {
 567         if (inProgress) {
 568             if (canUndo()) {
 569                 return editToBeUndone().getUndoPresentationName();
 570             } else {
 571                 return UIManager.getString("AbstractUndoableEdit.undoText");
 572             }
 573         } else {
 574             return super.getUndoPresentationName();
 575         }
 576     }
 577 
 578     /**
 579      * Returns a description of the redoable form of this edit.
 580      * If <code>end</code> has been invoked this calls into super.
 581      * Otherwise if there are edits to be redone, this returns
 582      * the value from the next significant edit that will be redone.
 583      * If there are no edits to be redone and <code>end</code> has not
 584      * been invoked this returns the value from the <code>UIManager</code>
 585      * property "AbstractUndoableEdit.redoText".
 586      *
 587      * @return a description of the redoable form of this edit
 588      * @see     #redo
 589      * @see     CompoundEdit#getRedoPresentationName
 590      */
 591     public synchronized String getRedoPresentationName() {
 592         if (inProgress) {
 593             if (canRedo()) {
 594                 return editToBeRedone().getRedoPresentationName();
 595             } else {
 596                 return UIManager.getString("AbstractUndoableEdit.redoText");
 597             }
 598         } else {
 599             return super.getRedoPresentationName();
 600         }
 601     }
 602 
 603     /**
 604      * An <code>UndoableEditListener</code> method. This invokes
 605      * <code>addEdit</code> with <code>e.getEdit()</code>.
 606      *
 607      * @param e the <code>UndoableEditEvent</code> the
 608      *        <code>UndoableEditEvent</code> will be added from
 609      * @see #addEdit
 610      */
 611     public void undoableEditHappened(UndoableEditEvent e) {
 612         addEdit(e.getEdit());
 613     }
 614 
 615     /**
 616      * Returns a string that displays and identifies this
 617      * object's properties.
 618      *
 619      * @return a String representation of this object
 620      */
 621     public String toString() {
 622         return super.toString() + " limit: " + limit +
 623             " indexOfNextAdd: " + indexOfNextAdd;
 624     }
 625 }