1 /* 2 * Copyright (c) 1997, 2017, 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 import sun.swing.text.UndoableEditLockSupport; 32 33 /** 34 * {@code UndoManager} manages a list of {@code UndoableEdits}, 35 * providing a way to undo or redo the appropriate edits. There are 36 * two ways to add edits to an <code>UndoManager</code>. Add the edit 37 * directly using the <code>addEdit</code> method, or add the 38 * <code>UndoManager</code> to a bean that supports 39 * <code>UndoableEditListener</code>. The following examples creates 40 * an <code>UndoManager</code> and adds it as an 41 * <code>UndoableEditListener</code> to a <code>JTextField</code>: 42 * <pre> 43 * UndoManager undoManager = new UndoManager(); 44 * JTextField tf = ...; 45 * tf.getDocument().addUndoableEditListener(undoManager); 46 * </pre> 47 * <p> 48 * <code>UndoManager</code> maintains an ordered list of edits and the 49 * index of the next edit in that list. The index of the next edit is 50 * either the size of the current list of edits, or if 51 * <code>undo</code> has been invoked it corresponds to the index 52 * of the last significant edit that was undone. When 53 * <code>undo</code> is invoked all edits from the index of the next 54 * edit to the last significant edit are undone, in reverse order. 55 * For example, consider an <code>UndoManager</code> consisting of the 56 * following edits: <b>A</b> <i>b</i> <i>c</i> <b>D</b>. Edits with a 57 * upper-case letter in bold are significant, those in lower-case 58 * and italicized are insignificant. 59 * <p> 60 * <a id="figure1"></a> 61 * <table class="borderless"> 62 * <caption style="display:none">Figure 1</caption> 63 * <tr><td> 64 * <img src="doc-files/UndoManager-1.gif" alt=""> 65 * <tr><td style="text-align:center">Figure 1 66 * </table> 67 * <p> 68 * As shown in <a href="#figure1">figure 1</a>, if <b>D</b> was just added, the 69 * index of the next edit will be 4. Invoking <code>undo</code> 70 * results in invoking <code>undo</code> on <b>D</b> and setting the 71 * index of the next edit to 3 (edit <i>c</i>), as shown in the following 72 * figure. 73 * <p> 74 * <a id="figure2"></a> 75 * <table class="borderless"> 76 * <caption style="display:none">Figure 2</caption> 77 * <tr><td> 78 * <img src="doc-files/UndoManager-2.gif" alt=""> 79 * <tr><td style="text-align:center">Figure 2 80 * </table> 81 * <p> 82 * The last significant edit is <b>A</b>, so that invoking 83 * <code>undo</code> again invokes <code>undo</code> on <i>c</i>, 84 * <i>b</i>, and <b>A</b>, in that order, setting the index of the 85 * next edit to 0, as shown in the following figure. 86 * <p> 87 * <a id="figure3"></a> 88 * <table class="borderless"> 89 * <caption style="display:none">Figure 3</caption> 90 * <tr><td> 91 * <img src="doc-files/UndoManager-3.gif" alt=""> 92 * <tr><td style="text-align:center">Figure 3 93 * </table> 94 * <p> 95 * Invoking <code>redo</code> results in invoking <code>redo</code> on 96 * all edits between the index of the next edit and the next 97 * significant edit (or the end of the list). Continuing with the previous 98 * example if <code>redo</code> were invoked, <code>redo</code> would in 99 * turn be invoked on <b>A</b>, <i>b</i> and <i>c</i>. In addition 100 * the index of the next edit is set to 3 (as shown in <a 101 * href="#figure2">figure 2</a>). 102 * <p> 103 * Adding an edit to an <code>UndoManager</code> results in 104 * removing all edits from the index of the next edit to the end of 105 * the list. Continuing with the previous example, if a new edit, 106 * <i>e</i>, is added the edit <b>D</b> is removed from the list 107 * (after having <code>die</code> invoked on it). If <i>c</i> is not 108 * incorporated by the next edit 109 * (<code><i>c</i>.addEdit(<i>e</i>)</code> returns true), or replaced 110 * by it (<code><i>e</i>.replaceEdit(<i>c</i>)</code> returns true), 111 * the new edit is added after <i>c</i>, as shown in the following 112 * figure. 113 * <p> 114 * <a id="figure4"></a> 115 * <table class="borderless"> 116 * <caption style="display:none">Figure 4</caption> 117 * <tr><td> 118 * <img src="doc-files/UndoManager-4.gif" alt=""> 119 * <tr><td style="text-align:center">Figure 4 120 * </table> 121 * <p> 122 * Once <code>end</code> has been invoked on an <code>UndoManager</code> 123 * the superclass behavior is used for all <code>UndoableEdit</code> 124 * methods. Refer to <code>CompoundEdit</code> for more details on its 125 * behavior. 126 * <p> 127 * Unlike the rest of Swing, this class is thread safe. 128 * <p> 129 * <strong>Warning:</strong> 130 * Serialized objects of this class will not be compatible with 131 * future Swing releases. The current serialization support is 132 * appropriate for short term storage or RMI between applications running 133 * the same version of Swing. As of 1.4, support for long term storage 134 * of all JavaBeans™ 135 * has been added to the <code>java.beans</code> package. 136 * Please see {@link java.beans.XMLEncoder}. 137 * 138 * @author Ray Ryan 139 */ 140 @SuppressWarnings("serial") // Same-version serialization only 141 public class UndoManager extends CompoundEdit implements UndoableEditListener { 142 private enum Action { 143 UNDO, 144 REDO, 145 ANY 146 } 147 int indexOfNextAdd; 148 int limit; 149 150 /** 151 * Creates a new <code>UndoManager</code>. 152 */ 153 public UndoManager() { 154 super(); 155 indexOfNextAdd = 0; 156 limit = 100; 157 edits.ensureCapacity(limit); 158 } 159 160 /** 161 * Returns the maximum number of edits this {@code UndoManager} 162 * holds. A value less than 0 indicates the number of edits is not 163 * limited. 164 * 165 * @return the maximum number of edits this {@code UndoManager} holds 166 * @see #addEdit 167 * @see #setLimit 168 */ 169 public synchronized int getLimit() { 170 return limit; 171 } 172 173 /** 174 * Empties the undo manager sending each edit a <code>die</code> message 175 * in the process. 176 * 177 * @see AbstractUndoableEdit#die 178 */ 179 public synchronized void discardAllEdits() { 180 for (UndoableEdit e : edits) { 181 e.die(); 182 } 183 edits = new Vector<UndoableEdit>(); 184 indexOfNextAdd = 0; 185 // PENDING(rjrjr) when vector grows a removeRange() method 186 // (expected in JDK 1.2), trimEdits() will be nice and 187 // efficient, and this method can call that instead. 188 } 189 190 /** 191 * Reduces the number of queued edits to a range of size limit, 192 * centered on the index of the next edit. 193 */ 194 protected void trimForLimit() { 195 if (limit >= 0) { 196 int size = edits.size(); 197 // System.out.print("limit: " + limit + 198 // " size: " + size + 199 // " indexOfNextAdd: " + indexOfNextAdd + 200 // "\n"); 201 202 if (size > limit) { 203 int halfLimit = limit/2; 204 int keepFrom = indexOfNextAdd - 1 - halfLimit; 205 int keepTo = indexOfNextAdd - 1 + halfLimit; 206 207 // These are ints we're playing with, so dividing by two 208 // rounds down for odd numbers, so make sure the limit was 209 // honored properly. Note that the keep range is 210 // inclusive. 211 212 if (keepTo - keepFrom + 1 > limit) { 213 keepFrom++; 214 } 215 216 // The keep range is centered on indexOfNextAdd, 217 // but odds are good that the actual edits Vector 218 // isn't. Move the keep range to keep it legal. 219 220 if (keepFrom < 0) { 221 keepTo -= keepFrom; 222 keepFrom = 0; 223 } 224 if (keepTo >= size) { 225 int delta = size - keepTo - 1; 226 keepTo += delta; 227 keepFrom += delta; 228 } 229 230 // System.out.println("Keeping " + keepFrom + " " + keepTo); 231 trimEdits(keepTo+1, size-1); 232 trimEdits(0, keepFrom-1); 233 } 234 } 235 } 236 237 /** 238 * Removes edits in the specified range. 239 * All edits in the given range (inclusive, and in reverse order) 240 * will have <code>die</code> invoked on them and are removed from 241 * the list of edits. This has no effect if 242 * <code>from</code> > <code>to</code>. 243 * 244 * @param from the minimum index to remove 245 * @param to the maximum index to remove 246 */ 247 protected void trimEdits(int from, int to) { 248 if (from <= to) { 249 // System.out.println("Trimming " + from + " " + to + " with index " + 250 // indexOfNextAdd); 251 for (int i = to; from <= i; i--) { 252 UndoableEdit e = edits.elementAt(i); 253 // System.out.println("JUM: Discarding " + 254 // e.getUndoPresentationName()); 255 e.die(); 256 // PENDING(rjrjr) when Vector supports range deletion (JDK 257 // 1.2) , we can optimize the next line considerably. 258 edits.removeElementAt(i); 259 } 260 261 if (indexOfNextAdd > to) { 262 // System.out.print("...right..."); 263 indexOfNextAdd -= to-from+1; 264 } else if (indexOfNextAdd >= from) { 265 // System.out.println("...mid..."); 266 indexOfNextAdd = from; 267 } 268 269 // System.out.println("new index " + indexOfNextAdd); 270 } 271 } 272 273 /** 274 * Sets the maximum number of edits this <code>UndoManager</code> 275 * holds. A value less than 0 indicates the number of edits is not 276 * limited. If edits need to be discarded to shrink the limit, 277 * <code>die</code> will be invoked on them in the reverse 278 * order they were added. The default is 100. 279 * 280 * @param l the new limit 281 * @throws RuntimeException if this {@code UndoManager} is not in progress 282 * ({@code end} has been invoked) 283 * @see #isInProgress 284 * @see #end 285 * @see #addEdit 286 * @see #getLimit 287 */ 288 public synchronized void setLimit(int l) { 289 if (!inProgress) throw new RuntimeException("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called"); 290 limit = l; 291 trimForLimit(); 292 } 293 294 295 /** 296 * Returns the next significant edit to be undone if <code>undo</code> 297 * is invoked. This returns <code>null</code> if there are no edits 298 * to be undone. 299 * 300 * @return the next significant edit to be undone 301 */ 302 protected UndoableEdit editToBeUndone() { 303 int i = indexOfNextAdd; 304 while (i > 0) { 305 UndoableEdit edit = edits.elementAt(--i); 306 if (edit.isSignificant()) { 307 return edit; 308 } 309 } 310 311 return null; 312 } 313 314 /** 315 * Returns the next significant edit to be redone if <code>redo</code> 316 * is invoked. This returns <code>null</code> if there are no edits 317 * to be redone. 318 * 319 * @return the next significant edit to be redone 320 */ 321 protected UndoableEdit editToBeRedone() { 322 int count = edits.size(); 323 int i = indexOfNextAdd; 324 325 while (i < count) { 326 UndoableEdit edit = edits.elementAt(i++); 327 if (edit.isSignificant()) { 328 return edit; 329 } 330 } 331 332 return null; 333 } 334 335 /** 336 * Undoes all changes from the index of the next edit to 337 * <code>edit</code>, updating the index of the next edit appropriately. 338 * 339 * @param edit the edit to be undo to 340 * @throws CannotUndoException if one of the edits throws 341 * <code>CannotUndoException</code> 342 */ 343 protected void undoTo(UndoableEdit edit) throws CannotUndoException { 344 boolean done = false; 345 while (!done) { 346 UndoableEdit next = edits.elementAt(--indexOfNextAdd); 347 next.undo(); 348 done = next == edit; 349 } 350 } 351 352 /** 353 * Redoes all changes from the index of the next edit to 354 * <code>edit</code>, updating the index of the next edit appropriately. 355 * 356 * @param edit the edit to be redo to 357 * @throws CannotRedoException if one of the edits throws 358 * <code>CannotRedoException</code> 359 */ 360 protected void redoTo(UndoableEdit edit) throws CannotRedoException { 361 boolean done = false; 362 while (!done) { 363 UndoableEdit next = edits.elementAt(indexOfNextAdd++); 364 next.redo(); 365 done = next == edit; 366 } 367 } 368 369 /** 370 * Convenience method that invokes one of <code>undo</code> or 371 * <code>redo</code>. If any edits have been undone (the index of 372 * the next edit is less than the length of the edits list) this 373 * invokes <code>redo</code>, otherwise it invokes <code>undo</code>. 374 * 375 * @see #canUndoOrRedo 376 * @see #getUndoOrRedoPresentationName 377 * @throws CannotUndoException if one of the edits throws 378 * <code>CannotUndoException</code> 379 * @throws CannotRedoException if one of the edits throws 380 * <code>CannotRedoException</code> 381 */ 382 public void undoOrRedo() throws CannotRedoException, CannotUndoException { 383 tryUndoOrRedo(Action.ANY); 384 } 385 386 /** 387 * Returns true if it is possible to invoke <code>undo</code> or 388 * <code>redo</code>. 389 * 390 * @return true if invoking <code>canUndoOrRedo</code> is valid 391 * @see #undoOrRedo 392 */ 393 public synchronized boolean canUndoOrRedo() { 394 if (indexOfNextAdd == edits.size()) { 395 return canUndo(); 396 } else { 397 return canRedo(); 398 } 399 } 400 401 /** 402 * Undoes the appropriate edits. If <code>end</code> has been 403 * invoked this calls through to the superclass, otherwise 404 * this invokes <code>undo</code> on all edits between the 405 * index of the next edit and the last significant edit, updating 406 * the index of the next edit appropriately. 407 * 408 * @throws CannotUndoException if one of the edits throws 409 * <code>CannotUndoException</code> or there are no edits 410 * to be undone 411 * @see CompoundEdit#end 412 * @see #canUndo 413 * @see #editToBeUndone 414 */ 415 public void undo() throws CannotUndoException { 416 tryUndoOrRedo(Action.UNDO); 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 */ 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 */ 452 public void redo() throws CannotRedoException { 453 tryUndoOrRedo(Action.REDO); 454 } 455 456 private void tryUndoOrRedo(Action action) { 457 UndoableEditLockSupport lockSupport = null; 458 boolean undo; 459 synchronized (this) { 460 if (action == Action.ANY) { 461 undo = indexOfNextAdd == edits.size(); 462 } else { 463 undo = action == Action.UNDO; 464 } 465 if (inProgress) { 466 UndoableEdit edit = undo ? editToBeUndone() : editToBeRedone(); 467 if (edit == null) { 468 throw undo ? new CannotUndoException() : 469 new CannotRedoException(); 470 } 471 lockSupport = getEditLockSupport(edit); 472 if (lockSupport == null) { 473 if (undo) { 474 undoTo(edit); 475 } else { 476 redoTo(edit); 477 } 478 return; 479 } 480 } else { 481 if (undo) { 482 super.undo(); 483 } else { 484 super.redo(); 485 } 486 return; 487 } 488 } 489 // the edit synchronization is required 490 while (true) { 491 lockSupport.lockEdit(); 492 UndoableEditLockSupport editLockSupport = null; 493 try { 494 synchronized (this) { 495 if (action == Action.ANY) { 496 undo = indexOfNextAdd == edits.size(); 497 } 498 if (inProgress) { 499 UndoableEdit edit = undo ? editToBeUndone() : 500 editToBeRedone(); 501 if (edit == null) { 502 throw undo ? new CannotUndoException() : 503 new CannotRedoException(); 504 } 505 editLockSupport = getEditLockSupport(edit); 506 if (editLockSupport == null || 507 editLockSupport == lockSupport) { 508 if (undo) { 509 undoTo(edit); 510 } else { 511 redoTo(edit); 512 } 513 return; 514 } 515 } else { 516 if (undo) { 517 super.undo(); 518 } else { 519 super.redo(); 520 } 521 return; 522 } 523 } 524 } finally { 525 if (lockSupport != null) { 526 lockSupport.unlockEdit(); 527 } 528 lockSupport = editLockSupport; 529 } 530 } 531 } 532 533 private UndoableEditLockSupport getEditLockSupport(UndoableEdit anEdit) { 534 return anEdit instanceof UndoableEditLockSupport ? 535 (UndoableEditLockSupport)anEdit : null; 536 } 537 538 /** 539 * Returns true if edits may be redone. If <code>end</code> has 540 * been invoked, this returns the value from super. Otherwise, 541 * this returns true if there are any edits to be redone 542 * (<code>editToBeRedone</code> returns non-<code>null</code>). 543 * 544 * @return true if there are edits to be redone 545 * @see CompoundEdit#canRedo 546 * @see #editToBeRedone 547 */ 548 public synchronized boolean canRedo() { 549 if (inProgress) { 550 UndoableEdit edit = editToBeRedone(); 551 return edit != null && edit.canRedo(); 552 } else { 553 return super.canRedo(); 554 } 555 } 556 557 /** 558 * Adds an <code>UndoableEdit</code> to this 559 * <code>UndoManager</code>, if it's possible. This removes all 560 * edits from the index of the next edit to the end of the edits 561 * list. If <code>end</code> has been invoked the edit is not added 562 * and <code>false</code> is returned. If <code>end</code> hasn't 563 * been invoked this returns <code>true</code>. 564 * 565 * @param anEdit the edit to be added 566 * @return true if <code>anEdit</code> can be incorporated into this 567 * edit 568 * @see CompoundEdit#end 569 * @see CompoundEdit#addEdit 570 */ 571 public synchronized boolean addEdit(UndoableEdit anEdit) { 572 boolean retVal; 573 574 // Trim from the indexOfNextAdd to the end, as we'll 575 // never reach these edits once the new one is added. 576 trimEdits(indexOfNextAdd, edits.size()-1); 577 578 retVal = super.addEdit(anEdit); 579 if (inProgress) { 580 retVal = true; 581 } 582 583 // Maybe super added this edit, maybe it didn't (perhaps 584 // an in progress compound edit took it instead. Or perhaps 585 // this UndoManager is no longer in progress). So make sure 586 // the indexOfNextAdd is pointed at the right place. 587 indexOfNextAdd = edits.size(); 588 589 // Enforce the limit 590 trimForLimit(); 591 592 return retVal; 593 } 594 595 596 /** 597 * Turns this <code>UndoManager</code> into a normal 598 * <code>CompoundEdit</code>. This removes all edits that have 599 * been undone. 600 * 601 * @see CompoundEdit#end 602 */ 603 public synchronized void end() { 604 super.end(); 605 this.trimEdits(indexOfNextAdd, edits.size()-1); 606 } 607 608 /** 609 * Convenience method that returns either 610 * <code>getUndoPresentationName</code> or 611 * <code>getRedoPresentationName</code>. If the index of the next 612 * edit equals the size of the edits list, 613 * <code>getUndoPresentationName</code> is returned, otherwise 614 * <code>getRedoPresentationName</code> is returned. 615 * 616 * @return undo or redo name 617 */ 618 public synchronized String getUndoOrRedoPresentationName() { 619 if (indexOfNextAdd == edits.size()) { 620 return getUndoPresentationName(); 621 } else { 622 return getRedoPresentationName(); 623 } 624 } 625 626 /** 627 * Returns a description of the undoable form of this edit. 628 * If <code>end</code> has been invoked this calls into super. 629 * Otherwise if there are edits to be undone, this returns 630 * the value from the next significant edit that will be undone. 631 * If there are no edits to be undone and <code>end</code> has not 632 * been invoked this returns the value from the <code>UIManager</code> 633 * property "AbstractUndoableEdit.undoText". 634 * 635 * @return a description of the undoable form of this edit 636 * @see #undo 637 * @see CompoundEdit#getUndoPresentationName 638 */ 639 public synchronized String getUndoPresentationName() { 640 if (inProgress) { 641 if (canUndo()) { 642 return editToBeUndone().getUndoPresentationName(); 643 } else { 644 return UIManager.getString("AbstractUndoableEdit.undoText"); 645 } 646 } else { 647 return super.getUndoPresentationName(); 648 } 649 } 650 651 /** 652 * Returns a description of the redoable form of this edit. 653 * If <code>end</code> has been invoked this calls into super. 654 * Otherwise if there are edits to be redone, this returns 655 * the value from the next significant edit that will be redone. 656 * If there are no edits to be redone and <code>end</code> has not 657 * been invoked this returns the value from the <code>UIManager</code> 658 * property "AbstractUndoableEdit.redoText". 659 * 660 * @return a description of the redoable form of this edit 661 * @see #redo 662 * @see CompoundEdit#getRedoPresentationName 663 */ 664 public synchronized String getRedoPresentationName() { 665 if (inProgress) { 666 if (canRedo()) { 667 return editToBeRedone().getRedoPresentationName(); 668 } else { 669 return UIManager.getString("AbstractUndoableEdit.redoText"); 670 } 671 } else { 672 return super.getRedoPresentationName(); 673 } 674 } 675 676 /** 677 * An <code>UndoableEditListener</code> method. This invokes 678 * <code>addEdit</code> with <code>e.getEdit()</code>. 679 * 680 * @param e the <code>UndoableEditEvent</code> the 681 * <code>UndoableEditEvent</code> will be added from 682 * @see #addEdit 683 */ 684 public void undoableEditHappened(UndoableEditEvent e) { 685 addEdit(e.getEdit()); 686 } 687 688 /** 689 * Returns a string that displays and identifies this 690 * object's properties. 691 * 692 * @return a String representation of this object 693 */ 694 public String toString() { 695 return super.toString() + " limit: " + limit + 696 " indexOfNextAdd: " + indexOfNextAdd; 697 } 698 }