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