001    /* UndoManager.java --
002       Copyright (C) 2002, 2004, 2005, 2006,  Free Software Foundation, Inc.
003    
004    This file is part of GNU Classpath.
005    
006    GNU Classpath is free software; you can redistribute it and/or modify
007    it under the terms of the GNU General Public License as published by
008    the Free Software Foundation; either version 2, or (at your option)
009    any later version.
010    
011    GNU Classpath is distributed in the hope that it will be useful, but
012    WITHOUT ANY WARRANTY; without even the implied warranty of
013    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
014    General Public License for more details.
015    
016    You should have received a copy of the GNU General Public License
017    along with GNU Classpath; see the file COPYING.  If not, write to the
018    Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
019    02110-1301 USA.
020    
021    Linking this library statically or dynamically with other modules is
022    making a combined work based on this library.  Thus, the terms and
023    conditions of the GNU General Public License cover the whole
024    combination.
025    
026    As a special exception, the copyright holders of this library give you
027    permission to link this library with independent modules to produce an
028    executable, regardless of the license terms of these independent
029    modules, and to copy and distribute the resulting executable under
030    terms of your choice, provided that you also meet, for each linked
031    independent module, the terms and conditions of the license of that
032    module.  An independent module is a module which is not derived from
033    or based on this library.  If you modify this library, you may extend
034    this exception to your version of the library, but you are not
035    obligated to do so.  If you do not wish to do so, delete this
036    exception statement from your version. */
037    
038    
039    package javax.swing.undo;
040    
041    import javax.swing.UIManager;
042    import javax.swing.event.UndoableEditEvent;
043    import 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 &#x201c;undo&#x201d; and
055     * &#x201c;redo&#x201d; menu commands, the application invokes the
056     * UndoManager&#x2019;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     * &#x201c;undo&#x201d; and &#x201c;redo.&#x201d; 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     */
086    public 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    }