001/* UndoManager.java -- 002 Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. 003 004This file is part of GNU Classpath. 005 006GNU Classpath is free software; you can redistribute it and/or modify 007it under the terms of the GNU General Public License as published by 008the Free Software Foundation; either version 2, or (at your option) 009any later version. 010 011GNU Classpath is distributed in the hope that it will be useful, but 012WITHOUT ANY WARRANTY; without even the implied warranty of 013MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 014General Public License for more details. 015 016You should have received a copy of the GNU General Public License 017along with GNU Classpath; see the file COPYING. If not, write to the 018Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 01902110-1301 USA. 020 021Linking this library statically or dynamically with other modules is 022making a combined work based on this library. Thus, the terms and 023conditions of the GNU General Public License cover the whole 024combination. 025 026As a special exception, the copyright holders of this library give you 027permission to link this library with independent modules to produce an 028executable, regardless of the license terms of these independent 029modules, and to copy and distribute the resulting executable under 030terms of your choice, provided that you also meet, for each linked 031independent module, the terms and conditions of the license of that 032module. An independent module is a module which is not derived from 033or based on this library. If you modify this library, you may extend 034this exception to your version of the library, but you are not 035obligated to do so. If you do not wish to do so, delete this 036exception statement from your version. */ 037 038 039package javax.swing.undo; 040 041import javax.swing.UIManager; 042import javax.swing.event.UndoableEditEvent; 043import javax.swing.event.UndoableEditListener; 044 045 046/** 047 * A manager for providing an application’s undo/redo 048 * functionality. 049 * 050 * <p>Tyipcally, an application will create only one single instance 051 * of UndoManager. When the user performs an undoable action, for 052 * instance changing the color of an object from green to blue, the 053 * application registers an {@link UndoableEdit} object with the 054 * <code>UndoManager</code>. To implement the “undo” and 055 * “redo” menu commands, the application invokes the 056 * UndoManager’s {@link #undo} and {@link #redo} methods. The 057 * human-readable text of these menu commands is provided by {@link 058 * #getUndoPresentationName} and {@link #getRedoPresentationName}, 059 * respectively. To determine whether the menu item should be 060 * selectable or greyed out, use {@link #canUndo} and {@link 061 * #canRedo}. 062 * 063 * <p>The UndoManager will only keep a specified number of editing 064 * actions, the <em>limit</em>. The value of this parameter can be 065 * retrieved by calling {@link #getLimit} and set with {@link 066 * #setLimit}. If more UndoableEdits are added to the UndoManager, 067 * the oldest actions will be discarded. 068 * 069 * <p>Some applications do not provide separate menu commands for 070 * “undo” and “redo.” Instead, they 071 * have just a single command whose text switches between the two. 072 * Such applications would use an UndoManager with a <code>limit</code> 073 * of 1. The text of this combined menu item is available via 074 * {@link #getUndoOrRedoPresentationName}, and it is implemented 075 * by calling {@link #undoOrRedo}. 076 * 077 * <p><b>Thread Safety:</b> In constrast to the other classes of the 078 * <code>javax.swing.undo</code> package, the public methods of an 079 * <code>UndoManager</code> are safe to call from concurrent threads. 080 * The caller does not need to perform external synchronization, and 081 * {@link javax.swing.event.UndoableEditEvent} sources do not need to 082 * broadcast their events from inside the Swing worker thread. 083 * 084 * @author Sascha Brawer (brawer@dandelis.ch) 085 */ 086public class UndoManager 087 extends CompoundEdit 088 implements UndoableEditListener 089{ 090 /** 091 * The unique ID for serializing instances of this class. Determined 092 * using the <code>serialver</code> tool of Sun JDK 1.4.1_01 on 093 * GNU/Linux. 094 */ 095 static final long serialVersionUID = -2077529998244066750L; 096 097 098 /** 099 * An index into the inherited {@link #edits} Vector that indicates 100 * at which position newly added editing actions would get inserted. 101 * 102 * <p>Normally, the value of <code>indexOfNextAdd</code> equals 103 * the number of UndoableEdits stored by this UndoManager, i.e. 104 * <code>edits.size()</code>. For each call to {@link #undo}, 105 * <code>indexOfNextAdd</code> is decremented by one. For each 106 * call to {@link #redo}, it is incremented again. 107 */ 108 int indexOfNextAdd; 109 110 111 /** 112 * The maximum number of UndoableEdits stored by this UndoManager. 113 */ 114 int limit; 115 116 117 /** 118 * Constructs an UndoManager. 119 * 120 * <p>The <code>limit</code> of the freshly constructed UndoManager 121 * is 100. 122 */ 123 public UndoManager() 124 { 125 limit = 100; 126 } 127 128 129 /** 130 * Returns a string representation for this UndoManager. This may be 131 * useful for debugging purposes. For the text of menu items, please 132 * refer to {@link #getUndoPresentationName}, {@link 133 * #getRedoPresentationName}, and {@link 134 * #getUndoOrRedoPresentationName}. 135 */ 136 public String toString() 137 { 138 return super.toString() 139 + " limit: " + limit 140 + " indexOfNextAdd: " + indexOfNextAdd; 141 } 142 143 144 /** 145 * Puts this UndoManager into a state where it acts as a normal 146 * {@link CompoundEdit}. It is unlikely that an application would 147 * want to do this. 148 */ 149 public synchronized void end() 150 { 151 super.end(); 152 trimEdits(indexOfNextAdd, edits.size() - 1); 153 } 154 155 156 /** 157 * Returns how many edits this UndoManager can maximally hold. 158 * 159 * @see #setLimit 160 */ 161 public synchronized int getLimit() 162 { 163 return limit; 164 } 165 166 167 /** 168 * Changes the maximal number of edits that this UndoManager can 169 * process. If there are currently more edits than the new limit 170 * allows, they will receive a {@link UndoableEdit#die() die} 171 * message in reverse order of addition. 172 * 173 * @param limit the new limit. 174 * 175 * @throws IllegalStateException if {@link #end()} has already been 176 * called on this UndoManager. 177 */ 178 public synchronized void setLimit(int limit) 179 { 180 if (!isInProgress()) 181 throw new IllegalStateException(); 182 183 this.limit = limit; 184 trimForLimit(); 185 } 186 187 188 /** 189 * Discards all editing actions that are currently registered with 190 * this UndoManager. Each {@link UndoableEdit} will receive a {@link 191 * UndoableEdit#die() die message}. 192 */ 193 public synchronized void discardAllEdits() 194 { 195 int size; 196 197 size = edits.size(); 198 for (int i = size - 1; i >= 0; i--) 199 edits.get(i).die(); 200 indexOfNextAdd = 0; 201 edits.clear(); 202 } 203 204 205 /** 206 * Called by various internal methods in order to enforce 207 * the <code>limit</code> value. 208 */ 209 protected void trimForLimit() 210 { 211 int high, s; 212 213 s = edits.size(); 214 215 /* The Sun J2SE1.4.1_01 implementation can be observed to do 216 * nothing (instead of throwing an exception) with a negative or 217 * zero limit. It may be debatable whether this is the best 218 * behavior, but we replicate it for sake of compatibility. 219 */ 220 if (limit <= 0 || s <= limit) 221 return; 222 223 high = Math.min(indexOfNextAdd + limit/2 - 1, s - 1); 224 trimEdits(high + 1, s - 1); 225 trimEdits(0, high - limit); 226 } 227 228 229 /** 230 * Discards a range of edits. All edits in the range <code>[from 231 * .. to]</code> will receive a {@linkplain UndoableEdit#die() die 232 * message} before being removed from the edits array. If 233 * <code>from</code> is greater than <code>to</code>, nothing 234 * happens. 235 * 236 * @param from the lower bound of the range of edits to be 237 * discarded. 238 * 239 * @param to the upper bound of the range of edits to be discarded. 240 */ 241 protected void trimEdits(int from, int to) 242 { 243 if (from > to) 244 return; 245 246 for (int i = to; i >= from; i--) 247 edits.get(i).die(); 248 249 // Remove the range [from .. to] from edits. If from == to, which 250 // is likely to be a very common case, we can do better than 251 // creating a sub-list and clearing it. 252 if (to == from) 253 edits.remove(from); 254 else 255 edits.subList(from, to + 1).clear(); 256 257 if (indexOfNextAdd > to) 258 indexOfNextAdd = indexOfNextAdd - to + from - 1; 259 else if (indexOfNextAdd >= from) 260 indexOfNextAdd = from; 261 } 262 263 264 /** 265 * Determines which significant edit would be undone if {@link 266 * #undo()} was called. 267 * 268 * @return the significant edit that would be undone, or 269 * <code>null</code> if no significant edit would be affected by 270 * calling {@link #undo()}. 271 */ 272 protected UndoableEdit editToBeUndone() 273 { 274 UndoableEdit result; 275 276 for (int i = indexOfNextAdd - 1; i >= 0; i--) 277 { 278 result = edits.get(i); 279 if (result.isSignificant()) 280 return result; 281 } 282 283 return null; 284 } 285 286 287 /** 288 * Determines which significant edit would be redone if {@link 289 * #redo()} was called. 290 * 291 * @return the significant edit that would be redone, or 292 * <code>null</code> if no significant edit would be affected by 293 * calling {@link #redo()}. 294 */ 295 protected UndoableEdit editToBeRedone() 296 { 297 UndoableEdit result; 298 299 for (int i = indexOfNextAdd; i < edits.size(); i++) 300 { 301 result = edits.get(i); 302 if (result.isSignificant()) 303 return result; 304 } 305 306 return null; 307 } 308 309 310 /** 311 * Undoes all editing actions in reverse order of addition, 312 * up to the specified action, 313 * 314 * @param edit the last editing action to be undone. 315 */ 316 protected void undoTo(UndoableEdit edit) 317 throws CannotUndoException 318 { 319 UndoableEdit cur; 320 321 if (!edits.contains(edit)) 322 throw new CannotUndoException(); 323 324 while (true) 325 { 326 indexOfNextAdd -= 1; 327 cur = edits.get(indexOfNextAdd); 328 cur.undo(); 329 if (cur == edit) 330 return; 331 } 332 } 333 334 335 /** 336 * Redoes all editing actions in the same order as they were 337 * added to this UndoManager, up to the specified action. 338 * 339 * @param edit the last editing action to be redone. 340 */ 341 protected void redoTo(UndoableEdit edit) 342 throws CannotRedoException 343 { 344 UndoableEdit cur; 345 346 if (!edits.contains(edit)) 347 throw new CannotRedoException(); 348 349 while (true) 350 { 351 cur = edits.get(indexOfNextAdd); 352 indexOfNextAdd += 1; 353 cur.redo(); 354 if (cur == edit) 355 return; 356 } 357 } 358 359 360 /** 361 * Undoes or redoes the last action. If the last action has already 362 * been undone, it will be re-done, and vice versa. 363 * 364 * <p>This is useful for applications that do not present a separate 365 * undo and redo facility, but just have a single menu item for 366 * undoing and redoing the very last action. Such applications will 367 * use an <code>UndoManager</code> whose <code>limit</code> is 1. 368 */ 369 public synchronized void undoOrRedo() 370 throws CannotRedoException, CannotUndoException 371 { 372 if (indexOfNextAdd == edits.size()) 373 undo(); 374 else 375 redo(); 376 } 377 378 379 /** 380 * Determines whether it would be possible to either undo or redo 381 * this editing action. 382 * 383 * <p>This is useful for applications that do not present a separate 384 * undo and redo facility, but just have a single menu item for 385 * undoing and redoing the very last action. Such applications will 386 * use an <code>UndoManager</code> whose <code>limit</code> is 1. 387 * 388 * @return <code>true</code> to indicate that this action can be 389 * undone or redone; <code>false</code> if neither is possible at 390 * the current time. 391 */ 392 public synchronized boolean canUndoOrRedo() 393 { 394 return indexOfNextAdd == edits.size() ? canUndo() : canRedo(); 395 } 396 397 398 /** 399 * Undoes one significant edit action. If insignificant actions have 400 * been posted after the last signficant action, the insignificant 401 * ones will be undone first. 402 * 403 * <p>However, if {@link #end()} has been called on this 404 * UndoManager, it will behave like a normal {@link 405 * CompoundEdit}. In this case, all actions will be undone in 406 * reverse order of addition. Typical applications will never call 407 * {@link #end()} on their <code>UndoManager</code>. 408 * 409 * @throws CannotUndoException if no action can be undone. 410 * 411 * @see #canUndo() 412 * @see #redo() 413 * @see #undoOrRedo() 414 */ 415 public synchronized void undo() 416 throws CannotUndoException 417 { 418 if (!isInProgress()) 419 { 420 super.undo(); 421 return; 422 } 423 424 UndoableEdit edit = editToBeUndone(); 425 if (edit == null) 426 throw new CannotUndoException(); 427 428 undoTo(edit); 429 } 430 431 432 /** 433 * Determines whether it would be possible to undo this editing 434 * action. 435 * 436 * @return <code>true</code> to indicate that this action can be 437 * undone; <code>false</code> otherwise. 438 * 439 * @see #undo() 440 * @see #canRedo() 441 * @see #canUndoOrRedo() 442 */ 443 public synchronized boolean canUndo() 444 { 445 UndoableEdit edit; 446 447 if (!isInProgress()) 448 return super.canUndo(); 449 450 edit = editToBeUndone(); 451 return edit != null && edit.canUndo(); 452 } 453 454 455 456 /** 457 * Redoes one significant edit action. If insignificant actions have 458 * been posted in between, the insignificant ones will be redone 459 * first. 460 * 461 * <p>However, if {@link #end()} has been called on this 462 * UndoManager, it will behave like a normal {@link 463 * CompoundEdit}. In this case, <em>all</em> actions will be redone 464 * in order of addition. Typical applications will never call {@link 465 * #end()} on their <code>UndoManager</code>. 466 * 467 * @throws CannotRedoException if no action can be redone. 468 * 469 * @see #canRedo() 470 * @see #redo() 471 * @see #undoOrRedo() 472 */ 473 public synchronized void redo() 474 throws CannotRedoException 475 { 476 if (!isInProgress()) 477 { 478 super.redo(); 479 return; 480 } 481 482 UndoableEdit edit = editToBeRedone(); 483 if (edit == null) 484 throw new CannotRedoException(); 485 486 redoTo(edit); 487 } 488 489 490 /** 491 * Determines whether it would be possible to redo this editing 492 * action. 493 * 494 * @return <code>true</code> to indicate that this action can be 495 * redone; <code>false</code> otherwise. 496 * 497 * @see #redo() 498 * @see #canUndo() 499 * @see #canUndoOrRedo() 500 */ 501 public synchronized boolean canRedo() 502 { 503 UndoableEdit edit; 504 505 if (!isInProgress()) 506 return super.canRedo(); 507 508 edit = editToBeRedone(); 509 return edit != null && edit.canRedo(); 510 } 511 512 513 /** 514 * Registers an undoable editing action with this UndoManager. If 515 * the capacity <code>limit</code> is reached, the oldest action 516 * will be discarded (and receives a {@linkplain UndoableEdit#die() 517 * die message}. Equally, any actions that were undone (but not re-done) 518 * will be discarded, too. 519 * 520 * @param edit the editing action that is added to this UndoManager. 521 * 522 * @return <code>true</code> if <code>edit</code> could be 523 * incorporated; <code>false</code> if <code>edit</code> has not 524 * been incorporated because {@link #end()} has already been called 525 * on this <code>UndoManager</code>. 526 */ 527 public synchronized boolean addEdit(UndoableEdit edit) 528 { 529 boolean result; 530 531 // Discard any edits starting at indexOfNextAdd. 532 trimEdits(indexOfNextAdd, edits.size() - 1); 533 534 result = super.addEdit(edit); 535 indexOfNextAdd = edits.size(); 536 trimForLimit(); 537 return result; 538 } 539 540 541 /** 542 * Calculates a localized text for presenting the undo or redo 543 * action to the user, for example in the form of a menu command. 544 * 545 * <p>This is useful for applications that do not present a separate 546 * undo and redo facility, but just have a single menu item for 547 * undoing and redoing the very last action. Such applications will 548 * use an <code>UndoManager</code> whose <code>limit</code> is 1. 549 * 550 * @return the redo presentation name if the last action has already 551 * been undone, or the undo presentation name otherwise. 552 * 553 * @see #getUndoPresentationName() 554 * @see #getRedoPresentationName() 555 */ 556 public synchronized String getUndoOrRedoPresentationName() 557 { 558 if (indexOfNextAdd == edits.size()) 559 return getUndoPresentationName(); 560 else 561 return getRedoPresentationName(); 562 } 563 564 565 /** 566 * Calculates a localized text for presenting the undo action 567 * to the user, for example in the form of a menu command. 568 */ 569 public synchronized String getUndoPresentationName() 570 { 571 UndoableEdit edit; 572 573 if (!isInProgress()) 574 return super.getUndoPresentationName(); 575 576 edit = editToBeUndone(); 577 if (edit == null) 578 return UIManager.getString("AbstractUndoableEdit.undoText"); 579 else 580 return edit.getUndoPresentationName(); 581 } 582 583 584 /** 585 * Calculates a localized text for presenting the redo action 586 * to the user, for example in the form of a menu command. 587 */ 588 public synchronized String getRedoPresentationName() 589 { 590 UndoableEdit edit; 591 592 if (!isInProgress()) 593 return super.getRedoPresentationName(); 594 595 edit = editToBeRedone(); 596 if (edit == null) 597 return UIManager.getString("AbstractUndoableEdit.redoText"); 598 else 599 return edit.getRedoPresentationName(); 600 } 601 602 603 /** 604 * Registers the edit action of an {@link UndoableEditEvent} 605 * with this UndoManager. 606 * 607 * <p><b>Thread Safety:</b> This method may safely be invoked from 608 * concurrent threads. The caller does not need to perform external 609 * synchronization. This means that {@link 610 * javax.swing.event.UndoableEditEvent} sources do not need to broadcast 611 * their events from inside the Swing worker thread. 612 * 613 * @param event the event whose <code>edit</code> will be 614 * passed to {@link #addEdit}. 615 * 616 * @see UndoableEditEvent#getEdit() 617 * @see #addEdit 618 */ 619 public void undoableEditHappened(UndoableEditEvent event) 620 { 621 // Note that this method does not need to be synchronized, 622 // because addEdit will obtain and release the mutex. 623 addEdit(event.getEdit()); 624 } 625}