KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > javax > swing > undo > UndoManager


1 /*
2  * @(#)UndoManager.java 1.35 03/12/19
3  *
4  * Copyright 2004 Sun Microsystems, Inc. All rights reserved.
5  * SUN PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
6  */

7
8 package javax.swing.undo;
9
10 import javax.swing.event.*;
11 import javax.swing.UIManager JavaDoc;
12 import java.util.*;
13
14 /**
15  * Concrete subclass of <code>CompoundEdit</code>
16  * which can serve as a <code>UndoableEditListener</code>,
17  * consolidating the <code>UndoableEditEvents</code> from a
18  * variety of sources, and undoing or redoing them one at a time.
19  *
20  * Unlike <code>AbstractUndoableEdit</code> and <code>CompoundEdit</code>,
21  * the public methods of this
22  * class are synchronized, and should be safe to call from multiple
23  * threads. This should make <code>UndoManager</code>
24  * a convenient marshall for sets of undoable JavaBeans.
25  * <p>
26  * <strong>Warning:</strong>
27  * Serialized objects of this class will not be compatible with
28  * future Swing releases. The current serialization support is
29  * appropriate for short term storage or RMI between applications running
30  * the same version of Swing. As of 1.4, support for long term storage
31  * of all JavaBeans<sup><font size="-2">TM</font></sup>
32  * has been added to the <code>java.beans</code> package.
33  * Please see {@link java.beans.XMLEncoder}.
34  *
35  * @author Ray Ryan
36  * @version 1.35, 12/19/03
37  */

38 public class UndoManager extends CompoundEdit JavaDoc implements UndoableEditListener {
39     int indexOfNextAdd;
40     int limit;
41
42     public UndoManager() {
43         super();
44         indexOfNextAdd = 0;
45         limit = 100;
46         edits.ensureCapacity(limit);
47     }
48
49     /**
50      * Returns the maximum number of edits this UndoManager will
51      * hold. Default value is 100.
52      *
53      * @see #addEdit
54      * @see #setLimit
55      */

56     public synchronized int getLimit() {
57         return limit;
58     }
59      
60     /**
61      * Empty the undo manager, sending each edit a die message
62      * in the process.
63      */

64     public synchronized void discardAllEdits() {
65         Enumeration cursor = edits.elements();
66         while (cursor.hasMoreElements()) {
67             UndoableEdit JavaDoc e = (UndoableEdit JavaDoc)cursor.nextElement();
68             e.die();
69         }
70         edits = new Vector(limit);
71         indexOfNextAdd = 0;
72         // PENDING(rjrjr) when vector grows a removeRange() method
73
// (expected in JDK 1.2), trimEdits() will be nice and
74
// efficient, and this method can call that instead.
75
}
76
77     /**
78      * Reduce the number of queued edits to a range of size limit,
79      * centered on indexOfNextAdd.
80      */

81     protected void trimForLimit() {
82         if (limit >= 0) {
83             int size = edits.size();
84 // System.out.print("limit: " + limit +
85
// " size: " + size +
86
// " indexOfNextAdd: " + indexOfNextAdd +
87
// "\n");
88

89             if (size > limit) {
90                 int halfLimit = limit/2;
91                 int keepFrom = indexOfNextAdd - 1 - halfLimit;
92                 int keepTo = indexOfNextAdd - 1 + halfLimit;
93
94                 // These are ints we're playing with, so dividing by two
95
// rounds down for odd numbers, so make sure the limit was
96
// honored properly. Note that the keep range is
97
// inclusive.
98

99                 if (keepTo - keepFrom + 1 > limit) {
100                     keepFrom++;
101                 }
102
103                 // The keep range is centered on indexOfNextAdd,
104
// but odds are good that the actual edits Vector
105
// isn't. Move the keep range to keep it legal.
106

107                 if (keepFrom < 0) {
108                     keepTo -= keepFrom;
109                     keepFrom = 0;
110                 }
111                 if (keepTo >= size) {
112                     int delta = size - keepTo - 1;
113                     keepTo += delta;
114                     keepFrom += delta;
115                 }
116
117 // System.out.println("Keeping " + keepFrom + " " + keepTo);
118
trimEdits(keepTo+1, size-1);
119                 trimEdits(0, keepFrom-1);
120             }
121         }
122     }
123         
124     /**
125      * Tell the edits in the given range (inclusive) to die, and
126      * remove them from edits. from > to is a no-op.
127      */

128     protected void trimEdits(int from, int to) {
129         if (from <= to) {
130 // System.out.println("Trimming " + from + " " + to + " with index " +
131
// indexOfNextAdd);
132
for (int i = to; from <= i; i--) {
133                 UndoableEdit JavaDoc e = (UndoableEdit JavaDoc)edits.elementAt(i);
134 // System.out.println("JUM: Discarding " +
135
// e.getUndoPresentationName());
136
e.die();
137                 // PENDING(rjrjr) when Vector supports range deletion (JDK
138
// 1.2) , we can optimize the next line considerably.
139
edits.removeElementAt(i);
140             }
141
142             if (indexOfNextAdd > to) {
143 // System.out.print("...right...");
144
indexOfNextAdd -= to-from+1;
145             } else if (indexOfNextAdd >= from) {
146 // System.out.println("...mid...");
147
indexOfNextAdd = from;
148             }
149
150 // System.out.println("new index " + indexOfNextAdd);
151
}
152     }
153
154     /**
155      * Set the maximum number of edits this UndoManager will hold. If
156      * edits need to be discarded to shrink the limit, they will be
157      * told to die in the reverse of the order that they were added.
158      *
159      * @see #addEdit
160      * @see #getLimit
161      */

162     public synchronized void setLimit(int l) {
163         if (!inProgress) throw new RuntimeException JavaDoc("Attempt to call UndoManager.setLimit() after UndoManager.end() has been called");
164         limit = l;
165         trimForLimit();
166     }
167      
168
169     /**
170      * Returns the the next significant edit to be undone if undo is
171      * called. May return null
172      */

173     protected UndoableEdit JavaDoc editToBeUndone() {
174         int i = indexOfNextAdd;
175         while (i > 0) {
176             UndoableEdit JavaDoc edit = (UndoableEdit JavaDoc)edits.elementAt(--i);
177             if (edit.isSignificant()) {
178                 return edit;
179             }
180         }
181
182         return null;
183     }
184
185     /**
186      * Returns the the next significant edit to be redone if redo is
187      * called. May return null
188      */

189     protected UndoableEdit JavaDoc editToBeRedone() {
190         int count = edits.size();
191         int i = indexOfNextAdd;
192
193         while (i < count) {
194             UndoableEdit JavaDoc edit = (UndoableEdit JavaDoc)edits.elementAt(i++);
195             if (edit.isSignificant()) {
196                 return edit;
197             }
198         }
199
200         return null;
201     }
202
203     /**
204      * Undoes all changes from indexOfNextAdd to edit. Updates indexOfNextAdd accordingly.
205      */

206     protected void undoTo(UndoableEdit JavaDoc edit) throws CannotUndoException JavaDoc {
207         boolean done = false;
208         while (!done) {
209             UndoableEdit JavaDoc next = (UndoableEdit JavaDoc)edits.elementAt(--indexOfNextAdd);
210             next.undo();
211             done = next == edit;
212         }
213     }
214
215     /**
216      * Redoes all changes from indexOfNextAdd to edit. Updates indexOfNextAdd accordingly.
217      */

218     protected void redoTo(UndoableEdit JavaDoc edit) throws CannotRedoException JavaDoc {
219         boolean done = false;
220         while (!done) {
221             UndoableEdit JavaDoc next = (UndoableEdit JavaDoc)edits.elementAt(indexOfNextAdd++);
222             next.redo();
223             done = next == edit;
224         }
225     }
226
227     /**
228      * Undo or redo as appropriate. Suitable for binding to an action
229      * that toggles between these two functions. Only makes sense
230      * to send this if limit == 1.
231      *
232      * @see #canUndoOrRedo
233      * @see #getUndoOrRedoPresentationName
234      */

235     public synchronized void undoOrRedo() throws CannotRedoException JavaDoc,
236         CannotUndoException JavaDoc {
237         if (indexOfNextAdd == edits.size()) {
238             undo();
239         } else {
240             redo();
241         }
242     }
243
244     /**
245      * Return true if calling undoOrRedo will undo or redo. Suitable
246      * for deciding to enable a command that toggles between the two
247      * functions, which only makes sense to use if limit == 1.
248      *
249      * @see #undoOrRedo
250      */

251     public synchronized boolean canUndoOrRedo() {
252         if (indexOfNextAdd == edits.size()) {
253             return canUndo();
254         } else {
255             return canRedo();
256         }
257     }
258
259     /**
260      * If this UndoManager is inProgress, undo the last significant
261      * UndoableEdit before indexOfNextAdd, and all insignificant edits back to
262      * it. Updates indexOfNextAdd accordingly.
263      *
264      * <p>If not inProgress, indexOfNextAdd is ignored and super's routine is
265      * called.</p>
266      *
267      * @see CompoundEdit#end
268      */

269     public synchronized void undo() throws CannotUndoException JavaDoc {
270         if (inProgress) {
271             UndoableEdit JavaDoc edit = editToBeUndone();
272             if (edit == null) {
273                 throw new CannotUndoException JavaDoc();
274             }
275             undoTo(edit);
276         } else {
277             super.undo();
278         }
279     }
280
281     /**
282      * Overridden to preserve usual semantics: returns true if an undo
283      * operation would be successful now, false otherwise
284      */

285     public synchronized boolean canUndo() {
286         if (inProgress) {
287             UndoableEdit JavaDoc edit = editToBeUndone();
288             return edit != null && edit.canUndo();
289         } else {
290             return super.canUndo();
291         }
292     }
293
294     /**
295      * If this <code>UndoManager</code> is <code>inProgress</code>,
296      * redoes the last significant <code>UndoableEdit</code> at
297      * <code>indexOfNextAdd</code> or after, and all insignificant
298      * edits up to it. Updates <code>indexOfNextAdd</code> accordingly.
299      *
300      * <p>If not <code>inProgress</code>, <code>indexOfNextAdd</code>
301      * is ignored and super's routine is called.</p>
302      *
303      * @see CompoundEdit#end
304      */

305     public synchronized void redo() throws CannotRedoException JavaDoc {
306         if (inProgress) {
307             UndoableEdit JavaDoc edit = editToBeRedone();
308             if (edit == null) {
309                 throw new CannotRedoException JavaDoc();
310             }
311             redoTo(edit);
312         } else {
313             super.redo();
314         }
315     }
316
317     /**
318      * Overridden to preserve usual semantics: returns true if a redo
319      * operation would be successful now, false otherwise
320      */

321     public synchronized boolean canRedo() {
322         if (inProgress) {
323             UndoableEdit JavaDoc edit = editToBeRedone();
324             return edit != null && edit.canRedo();
325         } else {
326             return super.canRedo();
327         }
328     }
329
330     /**
331      * If inProgress, inserts anEdit at indexOfNextAdd, and removes
332      * any old edits that were at indexOfNextAdd or later. The die
333      * method is called on each edit that is removed is sent, in the
334      * reverse of the order the edits were added. Updates
335      * indexOfNextAdd.
336      *
337      * <p>If not <code>inProgress</code>, acts as a
338      * <code>CompoundEdit</code>.
339      *
340      * @param anEdit the edit to be added
341      * @see CompoundEdit#end
342      * @see CompoundEdit#addEdit
343      */

344     public synchronized boolean addEdit(UndoableEdit JavaDoc anEdit) {
345         boolean retVal;
346
347         // Trim from the indexOfNextAdd to the end, as we'll
348
// never reach these edits once the new one is added.
349
trimEdits(indexOfNextAdd, edits.size()-1);
350
351         retVal = super.addEdit(anEdit);
352     if (inProgress) {
353       retVal = true;
354     }
355
356         // Maybe super added this edit, maybe it didn't (perhaps
357
// an in progress compound edit took it instead. Or perhaps
358
// this UndoManager is no longer in progress). So make sure
359
// the indexOfNextAdd is pointed at the right place.
360
indexOfNextAdd = edits.size();
361         
362         // Enforce the limit
363
trimForLimit();
364
365         return retVal;
366     }
367
368
369     /**
370      * Sending end() to an UndoManager turns it into a plain old
371      * (ended) CompoundEdit.
372      *
373      * <p> Calls super's end() method (making inProgress false), then
374      * sends die() to the unreachable edits at indexOfNextAdd and
375      * beyond, in the reverse of the order in which they were added.
376      *
377      * @see CompoundEdit#end
378      */

379     public synchronized void end() {
380     super.end();
381         this.trimEdits(indexOfNextAdd, edits.size()-1);
382     }
383
384     /**
385      * Return the appropriate name for a command that toggles between
386      * undo and redo. Only makes sense to use such a command if limit
387      * == 1 and we're not in progress.
388      */

389     public synchronized String JavaDoc getUndoOrRedoPresentationName() {
390         if (indexOfNextAdd == edits.size()) {
391             return getUndoPresentationName();
392         } else {
393             return getRedoPresentationName();
394         }
395     }
396
397     /**
398      * If inProgress, returns getUndoPresentationName of the
399      * significant edit that will be undone when undo() is invoked.
400      * If there is none, returns AbstractUndoableEdit.undoText from the
401      * defaults table.
402      *
403      * <p>If not inProgress, acts as a CompoundEdit</p>
404      *
405      * @see #undo
406      * @see CompoundEdit#getUndoPresentationName
407      */

408     public synchronized String JavaDoc getUndoPresentationName() {
409         if (inProgress) {
410             if (canUndo()) {
411                 return editToBeUndone().getUndoPresentationName();
412             } else {
413                 return UIManager.getString("AbstractUndoableEdit.undoText");
414             }
415         } else {
416             return super.getUndoPresentationName();
417         }
418     }
419
420     /**
421      * If inProgress, returns getRedoPresentationName of the
422      * significant edit that will be redone when redo() is invoked.
423      * If there is none, returns AbstractUndoableEdit.redoText from the
424      * defaults table.
425      *
426      * <p>If not inProgress, acts as a CompoundEdit</p>
427      *
428      * @see #redo
429      * @see CompoundEdit#getUndoPresentationName
430      */

431     public synchronized String JavaDoc getRedoPresentationName() {
432         if (inProgress) {
433             if (canRedo()) {
434                 return editToBeRedone().getRedoPresentationName();
435             } else {
436                 return UIManager.getString("AbstractUndoableEdit.redoText");
437             }
438         } else {
439             return super.getRedoPresentationName();
440         }
441     }
442
443     /**
444      * Called by the UndoabledEdit sources this UndoManager listens
445      * to. Calls addEdit with e.getEdit().
446      *
447      * @see #addEdit
448      */

449     public void undoableEditHappened(UndoableEditEvent e) {
450         addEdit(e.getEdit());
451     }
452
453     /**
454      * Returns a string that displays and identifies this
455      * object's properties.
456      *
457      * @return a String representation of this object
458      */

459     public String JavaDoc toString() {
460         return super.toString() + " limit: " + limit +
461             " indexOfNextAdd: " + indexOfNextAdd;
462     }
463 }
464
Popular Tags