001: /*
002: * Licensed to the Apache Software Foundation (ASF) under one or more
003: * contributor license agreements. See the NOTICE file distributed with
004: * this work for additional information regarding copyright ownership.
005: * The ASF licenses this file to You under the Apache License, Version 2.0
006: * (the "License"); you may not use this file except in compliance with
007: * the License. You may obtain a copy of the License at
008: *
009: * http://www.apache.org/licenses/LICENSE-2.0
010: *
011: * Unless required by applicable law or agreed to in writing, software
012: * distributed under the License is distributed on an "AS IS" BASIS,
013: * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014: * See the License for the specific language governing permissions and
015: * limitations under the License.
016: */
017: /**
018: * @author Evgeniya G. Maenkova
019: * @version $Revision$
020: */package javax.swing.undo;
021:
022: import javax.swing.event.UndoableEditEvent;
023: import javax.swing.event.UndoableEditListener;
024:
025: public class UndoManager extends CompoundEdit implements
026: UndoableEditListener {
027: /**
028: * <b>Note:</b> The <code>serialVersionUID</code> fields are explicitly
029: * declared as a performance optimization, not as a guarantee of
030: * serialization compatibility.
031: */
032: private static final long serialVersionUID = -8731438423915672404L;
033:
034: /**
035: * Index points to edit from the array of edits.
036: * All new edits will be added to this place.
037: * All redo, undo will look at the edit with this index.
038: */
039: int indexOfNextAdd;
040:
041: static final int DEFAULT_LIMIT = 100;
042:
043: /**
044: * The maximum number of edits that manager will contain.
045: * Default value is 100.
046: */
047: int limit;
048:
049: public UndoManager() {
050: indexOfNextAdd = 0;
051: limit = DEFAULT_LIMIT;
052: edits.ensureCapacity(DEFAULT_LIMIT);
053: }
054:
055: @Override
056: public synchronized boolean addEdit(final UndoableEdit anEdit) {
057: if (inProgress) {
058: // we need to remove edits only when indexOfNextAdd < size - 1
059: if (indexOfNextAdd < edits.size()) {
060: // remove all edit starting from indexOfNextAdd -> END
061: // and send die in reverse order
062: trimEdits(indexOfNextAdd, edits.size() - 1);
063: }
064: // insert new edit at indexOfNextAdd and increase index by one
065: edits.insertElementAt(anEdit, indexOfNextAdd++);
066: return true;
067: }
068:
069: // acts as CompoundEdit
070: return false;
071: }
072:
073: protected void undoTo(final UndoableEdit edit) {
074: int index = edits.indexOf(edit);
075:
076: if (index < 0) { // Fix for HARMONY-2612
077: throw new CannotUndoException();
078: }
079:
080: for (int i = indexOfNextAdd - 1; i >= index; i--) {
081: UndoableEdit e = edits.get(i);
082: e.undo();
083: }
084: indexOfNextAdd = index;
085: }
086:
087: protected void redoTo(final UndoableEdit edit) {
088: int index = edits.indexOf(edit);
089:
090: if (index < 0) { // Fix for HARMONY-2612
091: throw new CannotRedoException();
092: }
093:
094: for (int i = indexOfNextAdd; i <= index; i++) {
095: UndoableEdit e = edits.get(i);
096: e.redo();
097: }
098: indexOfNextAdd = index + 1;
099: }
100:
101: protected UndoableEdit editToBeUndone() {
102: // find first significant edit starting from indexOfNextAdd - 1 -> start
103: for (int i = indexOfNextAdd - 1; i >= 0; i--) {
104: UndoableEdit edit = edits.get(i);
105: if (edit.isSignificant()) {
106: return edit;
107: }
108: }
109: return null;
110: }
111:
112: protected UndoableEdit editToBeRedone() {
113: // find first significant edit starting from indexOfNextAdd -> end
114: for (int i = indexOfNextAdd; i < edits.size(); i++) {
115: UndoableEdit edit = edits.get(i);
116: if (edit.isSignificant()) {
117: return edit;
118: }
119: }
120: return null;
121: }
122:
123: public void undoableEditHappened(final UndoableEditEvent e) {
124: addEdit(e.getEdit());
125: }
126:
127: /*
128: * The format of the string is based on 1.5 release behavior
129: * which can be revealed using the following code:
130: *
131: * Object obj = new UndoManager();
132: * System.out.println(obj.toString());
133: */
134: @Override
135: public String toString() {
136: return super .toString() + " limit: " + limit
137: + " indexOfNextAdd: " + indexOfNextAdd;
138: }
139:
140: @Override
141: public synchronized String getUndoPresentationName() {
142: if (inProgress) {
143: UndoableEdit undoEdit = editToBeUndone();
144: if (undoEdit == null) {
145: return getUndoName();
146: }
147: return undoEdit.getUndoPresentationName();
148: } else {
149: return super .getUndoPresentationName();
150: }
151: }
152:
153: public synchronized String getUndoOrRedoPresentationName() {
154: if (indexOfNextAdd == 1) {
155: return getUndoPresentationName();
156: } else {
157: return getRedoPresentationName();
158: }
159: }
160:
161: @Override
162: public synchronized String getRedoPresentationName() {
163: if (inProgress) {
164: UndoableEdit redoEdit = editToBeRedone();
165: if (redoEdit == null) {
166: return getRedoName();
167: }
168: return redoEdit.getRedoPresentationName();
169: } else {
170: return super .getRedoPresentationName();
171: }
172: }
173:
174: protected void trimEdits(final int from, final int to) {
175: // we will use this method to remove edits
176: // and to call die in reverse order they were added
177:
178: // kill all edits in the given range (inclusive)
179: // and remove all edits in the given range (inclusive) from edits
180: // we must go from end to start (to -> from)
181: for (int i = to; i >= from; i--) {
182: edits.get(i).die();
183: edits.remove(i);
184: }
185: }
186:
187: public synchronized void setLimit(final int l) {
188: limit = l;
189: trimForLimit();
190: }
191:
192: public synchronized boolean canUndoOrRedo() {
193: // checks that undo can be called if indexOfNextAdd == 1
194: if (indexOfNextAdd == 1) {
195: return canUndo();
196: } else {
197: return canRedo();
198: }
199: }
200:
201: @Override
202: public synchronized boolean canUndo() {
203: if (inProgress) {
204: // find significant edit and call canUndo
205: UndoableEdit edit = editToBeUndone();
206: return (edit == null) ? false : edit.canUndo();
207: }
208: // if not in progress then call super.canUndo
209: return super .canUndo();
210: }
211:
212: @Override
213: public synchronized boolean canRedo() {
214: if (inProgress) {
215: // find significant edit and call canRedo
216: UndoableEdit edit = editToBeRedone();
217: return (edit == null) ? false : edit.canRedo();
218: }
219: // if not in progress then call super.canRedo
220: return super .canRedo();
221: }
222:
223: public synchronized void undoOrRedo() {
224: // do nothing if we are not in progress
225: if (inProgress) {
226: // if there is no any edit, throw exception
227: if (edits.isEmpty()) {
228: throw new CannotUndoException();
229: }
230: // flag is indexOfNextAdd
231: // before undo and after redo it's equal to 1
232: if (indexOfNextAdd == 1) {
233: undo();
234: } else {
235: redo();
236: }
237: }
238: }
239:
240: @Override
241: public synchronized void undo() {
242: if (inProgress) {
243: // undo first significant edit before indexOfNextAdd
244: UndoableEdit significantEdit = editToBeUndone();
245: if (significantEdit == null) {
246: // throw exception if there is no one significant edit
247: throw new CannotUndoException();
248: }
249: undoTo(significantEdit);
250: } else {
251: // acts as a CompoundEdit
252: super .undo();
253: }
254: }
255:
256: protected void trimForLimit() {
257: // check that we need to trim
258: if ((limit > 0) && (getLimit() < edits.size())) {
259: // indexOfNextAdd is a center for trimming
260: int beginning = indexOfNextAdd - limit / 2;
261: if (beginning < 0) {
262: beginning = 0;
263: }
264: // trim from 0 to beginning
265: trimEdits(0, beginning - 1);
266: // our array was shifted to left
267: // so we need to trim edit starting from limit to end
268: trimEdits(limit, edits.size() - 1);
269: }
270: }
271:
272: @Override
273: public synchronized void redo() {
274: if (inProgress) {
275: // redoes last significant edit at index or later
276: UndoableEdit significantEdit = editToBeRedone();
277: if (significantEdit == null) {
278: // throw exception if there is no significant edit
279: throw new CannotRedoException();
280: }
281: redoTo(significantEdit);
282: } else {
283: // acts as CompoundEdit
284: super .redo();
285: }
286: }
287:
288: @Override
289: public synchronized void end() {
290: // calls super's end
291: super .end();
292: // and trim from indexOfNextAdd to End
293: trimEdits(indexOfNextAdd, edits.size() - 1);
294: }
295:
296: public synchronized void discardAllEdits() {
297: indexOfNextAdd = 0;
298: for (int i = 0; i < edits.size(); i++) {
299: edits.get(i).die();
300: }
301: edits.removeAllElements();
302: return;
303: }
304:
305: public synchronized int getLimit() {
306: return limit;
307: }
308:
309: }
|