1 /* 2 * Copyright (c) 1997, 2013, 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™ 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 public class UndoManager extends CompoundEdit implements UndoableEditListener { 136 int indexOfNextAdd; 137 int limit; 138 139 /** 140 * Creates a new <code>UndoManager</code>. 141 */ UndoManager()142 public UndoManager() { 143 super(); 144 indexOfNextAdd = 0; 145 limit = 100; 146 edits.ensureCapacity(limit); 147 } 148 149 /** 150 * Returns the maximum number of edits this {@code UndoManager} 151 * holds. A value less than 0 indicates the number of edits is not 152 * limited. 153 * 154 * @return the maximum number of edits this {@code UndoManager} holds 155 * @see #addEdit 156 * @see #setLimit 157 */ getLimit()158 public synchronized int getLimit() { 159 return limit; 160 } 161 162 /** 163 * Empties the undo manager sending each edit a <code>die</code> message 164 * in the process. 165 * 166 * @see AbstractUndoableEdit#die 167 */ discardAllEdits()168 public synchronized void discardAllEdits() { 169 for (UndoableEdit e : edits) { 170 e.die(); 171 } 172 edits = new Vector<UndoableEdit>(); 173 indexOfNextAdd = 0; 174 // PENDING(rjrjr) when vector grows a removeRange() method 175 // (expected in JDK 1.2), trimEdits() will be nice and 176 // efficient, and this method can call that instead. 177 } 178 179 /** 180 * Reduces the number of queued edits to a range of size limit, 181 * centered on the index of the next edit. 182 */ trimForLimit()183 protected void trimForLimit() { 184 if (limit >= 0) { 185 int size = edits.size(); 186 // System.out.print("limit: " + limit + 187 // " size: " + size + 188 // " indexOfNextAdd: " + indexOfNextAdd + 189 // "\n"); 190 191 if (size > limit) { 192 int halfLimit = limit/2; 193 int keepFrom = indexOfNextAdd - 1 - halfLimit; 194 int keepTo = indexOfNextAdd - 1 + halfLimit; 195 196 // These are ints we're playing with, so dividing by two 197 // rounds down for odd numbers, so make sure the limit was 198 // honored properly. Note that the keep range is 199 // inclusive. 200 201 if (keepTo - keepFrom + 1 > limit) { 202 keepFrom++; 203 } 204 205 // The keep range is centered on indexOfNextAdd, 206 // but odds are good that the actual edits Vector 207 // isn't. Move the keep range to keep it legal. 208 209 if (keepFrom < 0) { 210 keepTo -= keepFrom; 211 keepFrom = 0; 212 } 213 if (keepTo >= size) { 214 int delta = size - keepTo - 1; 215 keepTo += delta; 216 keepFrom += delta; 217 } 218 219 // System.out.println("Keeping " + keepFrom + " " + keepTo); 220 trimEdits(keepTo+1, size-1); 221 trimEdits(0, keepFrom-1); 222 } 223 } 224 } 225 226 /** 227 * Removes edits in the specified range. 228 * All edits in the given range (inclusive, and in reverse order) 229 * will have <code>die</code> invoked on them and are removed from 230 * the list of edits. This has no effect if 231 * <code>from</code> > <code>to</code>. 232 * 233 * @param from the minimum index to remove 234 * @param to the maximum index to remove 235 */ trimEdits(int from, int to)236 protected void trimEdits(int from, int to) { 237 if (from <= to) { 238 // System.out.println("Trimming " + from + " " + to + " with index " + 239 // indexOfNextAdd); 240 for (int i = to; from <= i; i--) { 241 UndoableEdit e = edits.elementAt(i); 242 // System.out.println("JUM: Discarding " + 243 // e.getUndoPresentationName()); 244 e.die(); 245 // PENDING(rjrjr) when Vector supports range deletion (JDK 246 // 1.2) , we can optimize the next line considerably. 247 edits.removeElementAt(i); 248 } 249 250 if (indexOfNextAdd > to) { 251 // System.out.print("...right..."); 252 indexOfNextAdd -= to-from+1; 253 } else if (indexOfNextAdd >= from) { 254 // System.out.println("...mid..."); 255 indexOfNextAdd = from; 256 } 257 258 // System.out.println("new index " + indexOfNextAdd); 259 } 260 } 261 262 /** 263 * Sets the maximum number of edits this <code>UndoManager</code> 264 * holds. A value less than 0 indicates the number of edits is not 265 * limited. If edits need to be discarded to shrink the limit, 266 * <code>die</code> will be invoked on them in the reverse 267 * order they were added. The default is 100. 268 * 269 * @param l the new limit 270 * @throws RuntimeException if this {@code UndoManager} is not in progress 271 * ({@code end} has been invoked) 272 * @see #isInProgress 273 * @see #end 274 * @see #addEdit 275 * @see #getLimit 276 */ setLimit(int l)277 public synchronized void setLimit(int l) { 278 if (!inProgress) throw new RuntimeException("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called"); 279 limit = l; 280 trimForLimit(); 281 } 282 283 284 /** 285 * Returns the the next significant edit to be undone if <code>undo</code> 286 * is invoked. This returns <code>null</code> if there are no edits 287 * to be undone. 288 * 289 * @return the next significant edit to be undone 290 */ editToBeUndone()291 protected UndoableEdit editToBeUndone() { 292 int i = indexOfNextAdd; 293 while (i > 0) { 294 UndoableEdit edit = edits.elementAt(--i); 295 if (edit.isSignificant()) { 296 return edit; 297 } 298 } 299 300 return null; 301 } 302 303 /** 304 * Returns the the next significant edit to be redone if <code>redo</code> 305 * is invoked. This returns <code>null</code> if there are no edits 306 * to be redone. 307 * 308 * @return the next significant edit to be redone 309 */ editToBeRedone()310 protected UndoableEdit editToBeRedone() { 311 int count = edits.size(); 312 int i = indexOfNextAdd; 313 314 while (i < count) { 315 UndoableEdit edit = edits.elementAt(i++); 316 if (edit.isSignificant()) { 317 return edit; 318 } 319 } 320 321 return null; 322 } 323 324 /** 325 * Undoes all changes from the index of the next edit to 326 * <code>edit</code>, updating the index of the next edit appropriately. 327 * 328 * @throws CannotUndoException if one of the edits throws 329 * <code>CannotUndoException</code> 330 */ undoTo(UndoableEdit edit)331 protected void undoTo(UndoableEdit edit) throws CannotUndoException { 332 boolean done = false; 333 while (!done) { 334 UndoableEdit next = edits.elementAt(--indexOfNextAdd); 335 next.undo(); 336 done = next == edit; 337 } 338 } 339 340 /** 341 * Redoes all changes from the index of the next edit to 342 * <code>edit</code>, updating the index of the next edit appropriately. 343 * 344 * @throws CannotRedoException if one of the edits throws 345 * <code>CannotRedoException</code> 346 */ redoTo(UndoableEdit edit)347 protected void redoTo(UndoableEdit edit) throws CannotRedoException { 348 boolean done = false; 349 while (!done) { 350 UndoableEdit next = edits.elementAt(indexOfNextAdd++); 351 next.redo(); 352 done = next == edit; 353 } 354 } 355 356 /** 357 * Convenience method that invokes one of <code>undo</code> or 358 * <code>redo</code>. If any edits have been undone (the index of 359 * the next edit is less than the length of the edits list) this 360 * invokes <code>redo</code>, otherwise it invokes <code>undo</code>. 361 * 362 * @see #canUndoOrRedo 363 * @see #getUndoOrRedoPresentationName 364 * @throws CannotUndoException if one of the edits throws 365 * <code>CannotUndoException</code> 366 * @throws CannotRedoException if one of the edits throws 367 * <code>CannotRedoException</code> 368 */ undoOrRedo()369 public synchronized void undoOrRedo() throws CannotRedoException, 370 CannotUndoException { 371 if (indexOfNextAdd == edits.size()) { 372 undo(); 373 } else { 374 redo(); 375 } 376 } 377 378 /** 379 * Returns true if it is possible to invoke <code>undo</code> or 380 * <code>redo</code>. 381 * 382 * @return true if invoking <code>canUndoOrRedo</code> is valid 383 * @see #undoOrRedo 384 */ canUndoOrRedo()385 public synchronized boolean canUndoOrRedo() { 386 if (indexOfNextAdd == edits.size()) { 387 return canUndo(); 388 } else { 389 return canRedo(); 390 } 391 } 392 393 /** 394 * Undoes the appropriate edits. If <code>end</code> has been 395 * invoked this calls through to the superclass, otherwise 396 * this invokes <code>undo</code> on all edits between the 397 * index of the next edit and the last significant edit, updating 398 * the index of the next edit appropriately. 399 * 400 * @throws CannotUndoException if one of the edits throws 401 * <code>CannotUndoException</code> or there are no edits 402 * to be undone 403 * @see CompoundEdit#end 404 * @see #canUndo 405 * @see #editToBeUndone 406 */ undo()407 public synchronized void undo() throws CannotUndoException { 408 if (inProgress) { 409 UndoableEdit edit = editToBeUndone(); 410 if (edit == null) { 411 throw new CannotUndoException(); 412 } 413 undoTo(edit); 414 } else { 415 super.undo(); 416 } 417 } 418 419 /** 420 * Returns true if edits may be undone. If <code>end</code> has 421 * been invoked, this returns the value from super. Otherwise 422 * this returns true if there are any edits to be undone 423 * (<code>editToBeUndone</code> returns non-<code>null</code>). 424 * 425 * @return true if there are edits to be undone 426 * @see CompoundEdit#canUndo 427 * @see #editToBeUndone 428 */ canUndo()429 public synchronized boolean canUndo() { 430 if (inProgress) { 431 UndoableEdit edit = editToBeUndone(); 432 return edit != null && edit.canUndo(); 433 } else { 434 return super.canUndo(); 435 } 436 } 437 438 /** 439 * Redoes the appropriate edits. If <code>end</code> has been 440 * invoked this calls through to the superclass. Otherwise 441 * this invokes <code>redo</code> on all edits between the 442 * index of the next edit and the next significant edit, updating 443 * the index of the next edit appropriately. 444 * 445 * @throws CannotRedoException if one of the edits throws 446 * <code>CannotRedoException</code> or there are no edits 447 * to be redone 448 * @see CompoundEdit#end 449 * @see #canRedo 450 * @see #editToBeRedone 451 */ redo()452 public synchronized void redo() throws CannotRedoException { 453 if (inProgress) { 454 UndoableEdit edit = editToBeRedone(); 455 if (edit == null) { 456 throw new CannotRedoException(); 457 } 458 redoTo(edit); 459 } else { 460 super.redo(); 461 } 462 } 463 464 /** 465 * Returns true if edits may be redone. If <code>end</code> has 466 * been invoked, this returns the value from super. Otherwise, 467 * this returns true if there are any edits to be redone 468 * (<code>editToBeRedone</code> returns non-<code>null</code>). 469 * 470 * @return true if there are edits to be redone 471 * @see CompoundEdit#canRedo 472 * @see #editToBeRedone 473 */ canRedo()474 public synchronized boolean canRedo() { 475 if (inProgress) { 476 UndoableEdit edit = editToBeRedone(); 477 return edit != null && edit.canRedo(); 478 } else { 479 return super.canRedo(); 480 } 481 } 482 483 /** 484 * Adds an <code>UndoableEdit</code> to this 485 * <code>UndoManager</code>, if it's possible. This removes all 486 * edits from the index of the next edit to the end of the edits 487 * list. If <code>end</code> has been invoked the edit is not added 488 * and <code>false</code> is returned. If <code>end</code> hasn't 489 * been invoked this returns <code>true</code>. 490 * 491 * @param anEdit the edit to be added 492 * @return true if <code>anEdit</code> can be incorporated into this 493 * edit 494 * @see CompoundEdit#end 495 * @see CompoundEdit#addEdit 496 */ addEdit(UndoableEdit anEdit)497 public synchronized boolean addEdit(UndoableEdit anEdit) { 498 boolean retVal; 499 500 // Trim from the indexOfNextAdd to the end, as we'll 501 // never reach these edits once the new one is added. 502 trimEdits(indexOfNextAdd, edits.size()-1); 503 504 retVal = super.addEdit(anEdit); 505 if (inProgress) { 506 retVal = true; 507 } 508 509 // Maybe super added this edit, maybe it didn't (perhaps 510 // an in progress compound edit took it instead. Or perhaps 511 // this UndoManager is no longer in progress). So make sure 512 // the indexOfNextAdd is pointed at the right place. 513 indexOfNextAdd = edits.size(); 514 515 // Enforce the limit 516 trimForLimit(); 517 518 return retVal; 519 } 520 521 522 /** 523 * Turns this <code>UndoManager</code> into a normal 524 * <code>CompoundEdit</code>. This removes all edits that have 525 * been undone. 526 * 527 * @see CompoundEdit#end 528 */ end()529 public synchronized void end() { 530 super.end(); 531 this.trimEdits(indexOfNextAdd, edits.size()-1); 532 } 533 534 /** 535 * Convenience method that returns either 536 * <code>getUndoPresentationName</code> or 537 * <code>getRedoPresentationName</code>. If the index of the next 538 * edit equals the size of the edits list, 539 * <code>getUndoPresentationName</code> is returned, otherwise 540 * <code>getRedoPresentationName</code> is returned. 541 * 542 * @return undo or redo name 543 */ getUndoOrRedoPresentationName()544 public synchronized String getUndoOrRedoPresentationName() { 545 if (indexOfNextAdd == edits.size()) { 546 return getUndoPresentationName(); 547 } else { 548 return getRedoPresentationName(); 549 } 550 } 551 552 /** 553 * Returns a description of the undoable form of this edit. 554 * If <code>end</code> has been invoked this calls into super. 555 * Otherwise if there are edits to be undone, this returns 556 * the value from the next significant edit that will be undone. 557 * If there are no edits to be undone and <code>end</code> has not 558 * been invoked this returns the value from the <code>UIManager</code> 559 * property "AbstractUndoableEdit.undoText". 560 * 561 * @return a description of the undoable form of this edit 562 * @see #undo 563 * @see CompoundEdit#getUndoPresentationName 564 */ getUndoPresentationName()565 public synchronized String getUndoPresentationName() { 566 if (inProgress) { 567 if (canUndo()) { 568 return editToBeUndone().getUndoPresentationName(); 569 } else { 570 return UIManager.getString("AbstractUndoableEdit.undoText"); 571 } 572 } else { 573 return super.getUndoPresentationName(); 574 } 575 } 576 577 /** 578 * Returns a description of the redoable form of this edit. 579 * If <code>end</code> has been invoked this calls into super. 580 * Otherwise if there are edits to be redone, this returns 581 * the value from the next significant edit that will be redone. 582 * If there are no edits to be redone and <code>end</code> has not 583 * been invoked this returns the value from the <code>UIManager</code> 584 * property "AbstractUndoableEdit.redoText". 585 * 586 * @return a description of the redoable form of this edit 587 * @see #redo 588 * @see CompoundEdit#getRedoPresentationName 589 */ getRedoPresentationName()590 public synchronized String getRedoPresentationName() { 591 if (inProgress) { 592 if (canRedo()) { 593 return editToBeRedone().getRedoPresentationName(); 594 } else { 595 return UIManager.getString("AbstractUndoableEdit.redoText"); 596 } 597 } else { 598 return super.getRedoPresentationName(); 599 } 600 } 601 602 /** 603 * An <code>UndoableEditListener</code> method. This invokes 604 * <code>addEdit</code> with <code>e.getEdit()</code>. 605 * 606 * @param e the <code>UndoableEditEvent</code> the 607 * <code>UndoableEditEvent</code> will be added from 608 * @see #addEdit 609 */ undoableEditHappened(UndoableEditEvent e)610 public void undoableEditHappened(UndoableEditEvent e) { 611 addEdit(e.getEdit()); 612 } 613 614 /** 615 * Returns a string that displays and identifies this 616 * object's properties. 617 * 618 * @return a String representation of this object 619 */ toString()620 public String toString() { 621 return super.toString() + " limit: " + limit + 622 " indexOfNextAdd: " + indexOfNextAdd; 623 } 624 } 625