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™ 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> > <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 }