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