Source for javax.swing.undo.UndoManager

   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 &#x201c;undo&#x201d; and
  55:  * &#x201c;redo&#x201d; menu commands, the application invokes the
  56:  * UndoManager&#x2019;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:  * &#x201c;undo&#x201d; and &#x201c;redo.&#x201d; 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: }