GNU Classpath (0.95) | |
Frames | No Frames |
1: /* UndoManager.java -- 2: Copyright (C) 2002, 2004, 2005, 2006, Free Software Foundation, Inc. 3: 4: This file is part of GNU Classpath. 5: 6: GNU Classpath is free software; you can redistribute it and/or modify 7: it under the terms of the GNU General Public License as published by 8: the Free Software Foundation; either version 2, or (at your option) 9: any later version. 10: 11: GNU Classpath is distributed in the hope that it will be useful, but 12: WITHOUT ANY WARRANTY; without even the implied warranty of 13: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 14: General Public License for more details. 15: 16: You should have received a copy of the GNU General Public License 17: along with GNU Classpath; see the file COPYING. If not, write to the 18: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 19: 02110-1301 USA. 20: 21: Linking this library statically or dynamically with other modules is 22: making a combined work based on this library. Thus, the terms and 23: conditions of the GNU General Public License cover the whole 24: combination. 25: 26: As a special exception, the copyright holders of this library give you 27: permission to link this library with independent modules to produce an 28: executable, regardless of the license terms of these independent 29: modules, and to copy and distribute the resulting executable under 30: terms of your choice, provided that you also meet, for each linked 31: independent module, the terms and conditions of the license of that 32: module. An independent module is a module which is not derived from 33: or based on this library. If you modify this library, you may extend 34: this exception to your version of the library, but you are not 35: obligated to do so. If you do not wish to do so, delete this 36: exception statement from your version. */ 37: 38: 39: package javax.swing.undo; 40: 41: import javax.swing.UIManager; 42: import javax.swing.event.UndoableEditEvent; 43: import javax.swing.event.UndoableEditListener; 44: 45: 46: /** 47: * A manager for providing an application’s undo/redo 48: * functionality. 49: * 50: * <p>Tyipcally, an application will create only one single instance 51: * of UndoManager. When the user performs an undoable action, for 52: * instance changing the color of an object from green to blue, the 53: * application registers an {@link UndoableEdit} object with the 54: * <code>UndoManager</code>. To implement the “undo” and 55: * “redo” menu commands, the application invokes the 56: * UndoManager’s {@link #undo} and {@link #redo} methods. The 57: * human-readable text of these menu commands is provided by {@link 58: * #getUndoPresentationName} and {@link #getRedoPresentationName}, 59: * respectively. To determine whether the menu item should be 60: * selectable or greyed out, use {@link #canUndo} and {@link 61: * #canRedo}. 62: * 63: * <p>The UndoManager will only keep a specified number of editing 64: * actions, the <em>limit</em>. The value of this parameter can be 65: * retrieved by calling {@link #getLimit} and set with {@link 66: * #setLimit}. If more UndoableEdits are added to the UndoManager, 67: * the oldest actions will be discarded. 68: * 69: * <p>Some applications do not provide separate menu commands for 70: * “undo” and “redo.” Instead, they 71: * have just a single command whose text switches between the two. 72: * Such applications would use an UndoManager with a <code>limit</code> 73: * of 1. The text of this combined menu item is available via 74: * {@link #getUndoOrRedoPresentationName}, and it is implemented 75: * by calling {@link #undoOrRedo}. 76: * 77: * <p><b>Thread Safety:</b> In constrast to the other classes of the 78: * <code>javax.swing.undo</code> package, the public methods of an 79: * <code>UndoManager</code> are safe to call from concurrent threads. 80: * The caller does not need to perform external synchronization, and 81: * {@link javax.swing.event.UndoableEditEvent} sources do not need to 82: * broadcast their events from inside the Swing worker thread. 83: * 84: * @author Sascha Brawer (brawer@dandelis.ch) 85: */ 86: public class UndoManager 87: extends CompoundEdit 88: implements UndoableEditListener 89: { 90: /** 91: * The unique ID for serializing instances of this class. Determined 92: * using the <code>serialver</code> tool of Sun JDK 1.4.1_01 on 93: * GNU/Linux. 94: */ 95: static final long serialVersionUID = -2077529998244066750L; 96: 97: 98: /** 99: * 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: ((UndoableEdit) 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: ((UndoableEdit) 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 = (UndoableEdit) 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 = (UndoableEdit) 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 = (UndoableEdit) 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 = (UndoableEdit) 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: }
GNU Classpath (0.95) |