1 /*
2  * Copyright (c) 1997, 2013, 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&trade;
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 public class UndoManager extends CompoundEdit implements UndoableEditListener {
136     int indexOfNextAdd;
137     int limit;
138 
139     /**
140      * Creates a new <code>UndoManager</code>.
141      */
UndoManager()142     public UndoManager() {
143         super();
144         indexOfNextAdd = 0;
145         limit = 100;
146         edits.ensureCapacity(limit);
147     }
148 
149     /**
150      * Returns the maximum number of edits this {@code UndoManager}
151      * holds. A value less than 0 indicates the number of edits is not
152      * limited.
153      *
154      * @return the maximum number of edits this {@code UndoManager} holds
155      * @see #addEdit
156      * @see #setLimit
157      */
getLimit()158     public synchronized int getLimit() {
159         return limit;
160     }
161 
162     /**
163      * Empties the undo manager sending each edit a <code>die</code> message
164      * in the process.
165      *
166      * @see AbstractUndoableEdit#die
167      */
discardAllEdits()168     public synchronized void discardAllEdits() {
169         for (UndoableEdit e : edits) {
170             e.die();
171         }
172         edits = new Vector<UndoableEdit>();
173         indexOfNextAdd = 0;
174         // PENDING(rjrjr) when vector grows a removeRange() method
175         // (expected in JDK 1.2), trimEdits() will be nice and
176         // efficient, and this method can call that instead.
177     }
178 
179     /**
180      * Reduces the number of queued edits to a range of size limit,
181      * centered on the index of the next edit.
182      */
trimForLimit()183     protected void trimForLimit() {
184         if (limit >= 0) {
185             int size = edits.size();
186 //          System.out.print("limit: " + limit +
187 //                           " size: " + size +
188 //                           " indexOfNextAdd: " + indexOfNextAdd +
189 //                           "\n");
190 
191             if (size > limit) {
192                 int halfLimit = limit/2;
193                 int keepFrom = indexOfNextAdd - 1 - halfLimit;
194                 int keepTo   = indexOfNextAdd - 1 + halfLimit;
195 
196                 // These are ints we're playing with, so dividing by two
197                 // rounds down for odd numbers, so make sure the limit was
198                 // honored properly. Note that the keep range is
199                 // inclusive.
200 
201                 if (keepTo - keepFrom + 1 > limit) {
202                     keepFrom++;
203                 }
204 
205                 // The keep range is centered on indexOfNextAdd,
206                 // but odds are good that the actual edits Vector
207                 // isn't. Move the keep range to keep it legal.
208 
209                 if (keepFrom < 0) {
210                     keepTo -= keepFrom;
211                     keepFrom = 0;
212                 }
213                 if (keepTo >= size) {
214                     int delta = size - keepTo - 1;
215                     keepTo += delta;
216                     keepFrom += delta;
217                 }
218 
219 //              System.out.println("Keeping " + keepFrom + " " + keepTo);
220                 trimEdits(keepTo+1, size-1);
221                 trimEdits(0, keepFrom-1);
222             }
223         }
224     }
225 
226     /**
227      * Removes edits in the specified range.
228      * All edits in the given range (inclusive, and in reverse order)
229      * will have <code>die</code> invoked on them and are removed from
230      * the list of edits. This has no effect if
231      * <code>from</code> &gt; <code>to</code>.
232      *
233      * @param from the minimum index to remove
234      * @param to the maximum index to remove
235      */
trimEdits(int from, int to)236     protected void trimEdits(int from, int to) {
237         if (from <= to) {
238 //          System.out.println("Trimming " + from + " " + to + " with index " +
239 //                           indexOfNextAdd);
240             for (int i = to; from <= i; i--) {
241                 UndoableEdit e = edits.elementAt(i);
242 //              System.out.println("JUM: Discarding " +
243 //                                 e.getUndoPresentationName());
244                 e.die();
245                 // PENDING(rjrjr) when Vector supports range deletion (JDK
246                 // 1.2) , we can optimize the next line considerably.
247                 edits.removeElementAt(i);
248             }
249 
250             if (indexOfNextAdd > to) {
251 //              System.out.print("...right...");
252                 indexOfNextAdd -= to-from+1;
253             } else if (indexOfNextAdd >= from) {
254 //              System.out.println("...mid...");
255                 indexOfNextAdd = from;
256             }
257 
258 //          System.out.println("new index " + indexOfNextAdd);
259         }
260     }
261 
262     /**
263      * Sets the maximum number of edits this <code>UndoManager</code>
264      * holds. A value less than 0 indicates the number of edits is not
265      * limited. If edits need to be discarded to shrink the limit,
266      * <code>die</code> will be invoked on them in the reverse
267      * order they were added.  The default is 100.
268      *
269      * @param l the new limit
270      * @throws RuntimeException if this {@code UndoManager} is not in progress
271      *                          ({@code end} has been invoked)
272      * @see #isInProgress
273      * @see #end
274      * @see #addEdit
275      * @see #getLimit
276      */
setLimit(int l)277     public synchronized void setLimit(int l) {
278         if (!inProgress) throw new RuntimeException("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called");
279         limit = l;
280         trimForLimit();
281     }
282 
283 
284     /**
285      * Returns the the next significant edit to be undone if <code>undo</code>
286      * is invoked. This returns <code>null</code> if there are no edits
287      * to be undone.
288      *
289      * @return the next significant edit to be undone
290      */
editToBeUndone()291     protected UndoableEdit editToBeUndone() {
292         int i = indexOfNextAdd;
293         while (i > 0) {
294             UndoableEdit edit = edits.elementAt(--i);
295             if (edit.isSignificant()) {
296                 return edit;
297             }
298         }
299 
300         return null;
301     }
302 
303     /**
304      * Returns the the next significant edit to be redone if <code>redo</code>
305      * is invoked. This returns <code>null</code> if there are no edits
306      * to be redone.
307      *
308      * @return the next significant edit to be redone
309      */
editToBeRedone()310     protected UndoableEdit editToBeRedone() {
311         int count = edits.size();
312         int i = indexOfNextAdd;
313 
314         while (i < count) {
315             UndoableEdit edit = edits.elementAt(i++);
316             if (edit.isSignificant()) {
317                 return edit;
318             }
319         }
320 
321         return null;
322     }
323 
324     /**
325      * Undoes all changes from the index of the next edit to
326      * <code>edit</code>, updating the index of the next edit appropriately.
327      *
328      * @throws CannotUndoException if one of the edits throws
329      *         <code>CannotUndoException</code>
330      */
undoTo(UndoableEdit edit)331     protected void undoTo(UndoableEdit edit) throws CannotUndoException {
332         boolean done = false;
333         while (!done) {
334             UndoableEdit next = edits.elementAt(--indexOfNextAdd);
335             next.undo();
336             done = next == edit;
337         }
338     }
339 
340     /**
341      * Redoes all changes from the index of the next edit to
342      * <code>edit</code>, updating the index of the next edit appropriately.
343      *
344      * @throws CannotRedoException if one of the edits throws
345      *         <code>CannotRedoException</code>
346      */
redoTo(UndoableEdit edit)347     protected void redoTo(UndoableEdit edit) throws CannotRedoException {
348         boolean done = false;
349         while (!done) {
350             UndoableEdit next = edits.elementAt(indexOfNextAdd++);
351             next.redo();
352             done = next == edit;
353         }
354     }
355 
356     /**
357      * Convenience method that invokes one of <code>undo</code> or
358      * <code>redo</code>. If any edits have been undone (the index of
359      * the next edit is less than the length of the edits list) this
360      * invokes <code>redo</code>, otherwise it invokes <code>undo</code>.
361      *
362      * @see #canUndoOrRedo
363      * @see #getUndoOrRedoPresentationName
364      * @throws CannotUndoException if one of the edits throws
365      *         <code>CannotUndoException</code>
366      * @throws CannotRedoException if one of the edits throws
367      *         <code>CannotRedoException</code>
368      */
undoOrRedo()369     public synchronized void undoOrRedo() throws CannotRedoException,
370         CannotUndoException {
371         if (indexOfNextAdd == edits.size()) {
372             undo();
373         } else {
374             redo();
375         }
376     }
377 
378     /**
379      * Returns true if it is possible to invoke <code>undo</code> or
380      * <code>redo</code>.
381      *
382      * @return true if invoking <code>canUndoOrRedo</code> is valid
383      * @see #undoOrRedo
384      */
canUndoOrRedo()385     public synchronized boolean canUndoOrRedo() {
386         if (indexOfNextAdd == edits.size()) {
387             return canUndo();
388         } else {
389             return canRedo();
390         }
391     }
392 
393     /**
394      * Undoes the appropriate edits.  If <code>end</code> has been
395      * invoked this calls through to the superclass, otherwise
396      * this invokes <code>undo</code> on all edits between the
397      * index of the next edit and the last significant edit, updating
398      * the index of the next edit appropriately.
399      *
400      * @throws CannotUndoException if one of the edits throws
401      *         <code>CannotUndoException</code> or there are no edits
402      *         to be undone
403      * @see CompoundEdit#end
404      * @see #canUndo
405      * @see #editToBeUndone
406      */
undo()407     public synchronized void undo() throws CannotUndoException {
408         if (inProgress) {
409             UndoableEdit edit = editToBeUndone();
410             if (edit == null) {
411                 throw new CannotUndoException();
412             }
413             undoTo(edit);
414         } else {
415             super.undo();
416         }
417     }
418 
419     /**
420      * Returns true if edits may be undone.  If <code>end</code> has
421      * been invoked, this returns the value from super.  Otherwise
422      * this returns true if there are any edits to be undone
423      * (<code>editToBeUndone</code> returns non-<code>null</code>).
424      *
425      * @return true if there are edits to be undone
426      * @see CompoundEdit#canUndo
427      * @see #editToBeUndone
428      */
canUndo()429     public synchronized boolean canUndo() {
430         if (inProgress) {
431             UndoableEdit edit = editToBeUndone();
432             return edit != null && edit.canUndo();
433         } else {
434             return super.canUndo();
435         }
436     }
437 
438     /**
439      * Redoes the appropriate edits.  If <code>end</code> has been
440      * invoked this calls through to the superclass.  Otherwise
441      * this invokes <code>redo</code> on all edits between the
442      * index of the next edit and the next significant edit, updating
443      * the index of the next edit appropriately.
444      *
445      * @throws CannotRedoException if one of the edits throws
446      *         <code>CannotRedoException</code> or there are no edits
447      *         to be redone
448      * @see CompoundEdit#end
449      * @see #canRedo
450      * @see #editToBeRedone
451      */
redo()452     public synchronized void redo() throws CannotRedoException {
453         if (inProgress) {
454             UndoableEdit edit = editToBeRedone();
455             if (edit == null) {
456                 throw new CannotRedoException();
457             }
458             redoTo(edit);
459         } else {
460             super.redo();
461         }
462     }
463 
464     /**
465      * Returns true if edits may be redone.  If <code>end</code> has
466      * been invoked, this returns the value from super.  Otherwise,
467      * this returns true if there are any edits to be redone
468      * (<code>editToBeRedone</code> returns non-<code>null</code>).
469      *
470      * @return true if there are edits to be redone
471      * @see CompoundEdit#canRedo
472      * @see #editToBeRedone
473      */
canRedo()474     public synchronized boolean canRedo() {
475         if (inProgress) {
476             UndoableEdit edit = editToBeRedone();
477             return edit != null && edit.canRedo();
478         } else {
479             return super.canRedo();
480         }
481     }
482 
483     /**
484      * Adds an <code>UndoableEdit</code> to this
485      * <code>UndoManager</code>, if it's possible.  This removes all
486      * edits from the index of the next edit to the end of the edits
487      * list.  If <code>end</code> has been invoked the edit is not added
488      * and <code>false</code> is returned.  If <code>end</code> hasn't
489      * been invoked this returns <code>true</code>.
490      *
491      * @param anEdit the edit to be added
492      * @return true if <code>anEdit</code> can be incorporated into this
493      *              edit
494      * @see CompoundEdit#end
495      * @see CompoundEdit#addEdit
496      */
addEdit(UndoableEdit anEdit)497     public synchronized boolean addEdit(UndoableEdit anEdit) {
498         boolean retVal;
499 
500         // Trim from the indexOfNextAdd to the end, as we'll
501         // never reach these edits once the new one is added.
502         trimEdits(indexOfNextAdd, edits.size()-1);
503 
504         retVal = super.addEdit(anEdit);
505         if (inProgress) {
506           retVal = true;
507         }
508 
509         // Maybe super added this edit, maybe it didn't (perhaps
510         // an in progress compound edit took it instead. Or perhaps
511         // this UndoManager is no longer in progress). So make sure
512         // the indexOfNextAdd is pointed at the right place.
513         indexOfNextAdd = edits.size();
514 
515         // Enforce the limit
516         trimForLimit();
517 
518         return retVal;
519     }
520 
521 
522     /**
523      * Turns this <code>UndoManager</code> into a normal
524      * <code>CompoundEdit</code>.  This removes all edits that have
525      * been undone.
526      *
527      * @see CompoundEdit#end
528      */
end()529     public synchronized void end() {
530         super.end();
531         this.trimEdits(indexOfNextAdd, edits.size()-1);
532     }
533 
534     /**
535      * Convenience method that returns either
536      * <code>getUndoPresentationName</code> or
537      * <code>getRedoPresentationName</code>.  If the index of the next
538      * edit equals the size of the edits list,
539      * <code>getUndoPresentationName</code> is returned, otherwise
540      * <code>getRedoPresentationName</code> is returned.
541      *
542      * @return undo or redo name
543      */
getUndoOrRedoPresentationName()544     public synchronized String getUndoOrRedoPresentationName() {
545         if (indexOfNextAdd == edits.size()) {
546             return getUndoPresentationName();
547         } else {
548             return getRedoPresentationName();
549         }
550     }
551 
552     /**
553      * Returns a description of the undoable form of this edit.
554      * If <code>end</code> has been invoked this calls into super.
555      * Otherwise if there are edits to be undone, this returns
556      * the value from the next significant edit that will be undone.
557      * If there are no edits to be undone and <code>end</code> has not
558      * been invoked this returns the value from the <code>UIManager</code>
559      * property "AbstractUndoableEdit.undoText".
560      *
561      * @return a description of the undoable form of this edit
562      * @see     #undo
563      * @see     CompoundEdit#getUndoPresentationName
564      */
getUndoPresentationName()565     public synchronized String getUndoPresentationName() {
566         if (inProgress) {
567             if (canUndo()) {
568                 return editToBeUndone().getUndoPresentationName();
569             } else {
570                 return UIManager.getString("AbstractUndoableEdit.undoText");
571             }
572         } else {
573             return super.getUndoPresentationName();
574         }
575     }
576 
577     /**
578      * Returns a description of the redoable form of this edit.
579      * If <code>end</code> has been invoked this calls into super.
580      * Otherwise if there are edits to be redone, this returns
581      * the value from the next significant edit that will be redone.
582      * If there are no edits to be redone and <code>end</code> has not
583      * been invoked this returns the value from the <code>UIManager</code>
584      * property "AbstractUndoableEdit.redoText".
585      *
586      * @return a description of the redoable form of this edit
587      * @see     #redo
588      * @see     CompoundEdit#getRedoPresentationName
589      */
getRedoPresentationName()590     public synchronized String getRedoPresentationName() {
591         if (inProgress) {
592             if (canRedo()) {
593                 return editToBeRedone().getRedoPresentationName();
594             } else {
595                 return UIManager.getString("AbstractUndoableEdit.redoText");
596             }
597         } else {
598             return super.getRedoPresentationName();
599         }
600     }
601 
602     /**
603      * An <code>UndoableEditListener</code> method. This invokes
604      * <code>addEdit</code> with <code>e.getEdit()</code>.
605      *
606      * @param e the <code>UndoableEditEvent</code> the
607      *        <code>UndoableEditEvent</code> will be added from
608      * @see #addEdit
609      */
undoableEditHappened(UndoableEditEvent e)610     public void undoableEditHappened(UndoableEditEvent e) {
611         addEdit(e.getEdit());
612     }
613 
614     /**
615      * Returns a string that displays and identifies this
616      * object's properties.
617      *
618      * @return a String representation of this object
619      */
toString()620     public String toString() {
621         return super.toString() + " limit: " + limit +
622             " indexOfNextAdd: " + indexOfNextAdd;
623     }
624 }
625