001: /*
002: * Copyright (C) 2005 Jeff Tassin
003: *
004: * This library is free software; you can redistribute it and/or
005: * modify it under the terms of the GNU Lesser General Public
006: * License as published by the Free Software Foundation; either
007: * version 2.1 of the License, or (at your option) any later version.
008: *
009: * This library is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
012: * Lesser General Public License for more details.
013: *
014: * You should have received a copy of the GNU Lesser General Public
015: * License along with this library; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package com.jeta.swingbuilder.gui.undo;
020:
021: import java.util.Collection;
022: import java.util.Enumeration;
023: import java.util.Iterator;
024: import java.util.Vector;
025:
026: import javax.swing.undo.CannotRedoException;
027: import javax.swing.undo.CannotUndoException;
028: import javax.swing.undo.CompoundEdit;
029: import javax.swing.undo.UndoableEdit;
030:
031: import com.jeta.swingbuilder.gui.commands.FormUndoableEdit;
032: import com.jeta.swingbuilder.gui.editor.FormEditor;
033: import com.jeta.swingbuilder.gui.formmgr.AbstractFormManager;
034:
035: /**
036: * This is the UndoManager for an editor.
037: *
038: * @author Jeff Tassin
039: */
040: public class EditorUndoManager extends CompoundEdit {
041: /**
042: * @directed
043: */
044: private FormEditor m_editor;
045:
046: /**
047: * Keeps track of undo managers for each form.
048: */
049: private AbstractFormManager m_formmgr;
050:
051: private int m_indexOfNextAdd;
052: private int m_limit;
053:
054: /**
055: * ctor
056: */
057: public EditorUndoManager(AbstractFormManager formmgr,
058: FormEditor editor) {
059: m_formmgr = formmgr;
060: m_editor = editor;
061:
062: m_indexOfNextAdd = 0;
063: m_limit = 100;
064: edits.ensureCapacity(m_limit);
065: }
066:
067: private boolean equals(FormUndoableEdit edit1,
068: FormUndoableEdit edit2) {
069: if (edit1 instanceof UndoableEditProxy) {
070: if (edit2 instanceof UndoableEditProxy) {
071: return ((UndoableEditProxy) edit1).getDelegate() == ((UndoableEditProxy) edit2)
072: .getDelegate();
073: } else {
074: return ((UndoableEditProxy) edit1).getDelegate() == edit2;
075: }
076: } else if (edit2 instanceof UndoableEditProxy) {
077: UndoableEditProxy proxy = (UndoableEditProxy) edit2;
078: return proxy.getDelegate() == edit1;
079: } else {
080: return edit1 == edit2;
081: }
082: }
083:
084: /**
085: * @return the number of edits in the queue
086: */
087: public int size() {
088: return edits.size();
089: }
090:
091: /**
092: * This is for testing only
093: */
094: public Collection getEdits() {
095: return edits;
096: }
097:
098: /**
099: * This should only be called during testing
100: */
101: public int getIndexOfNextAdd() {
102: return m_indexOfNextAdd;
103: }
104:
105: /**
106: * Notifies this manager that an undo edit happened on another editor.
107: */
108: public void undoHappened(FormEditor editor, FormUndoableEdit edit) {
109: if (equals(edit, (FormUndoableEdit) editToBeUndone())) {
110: undo(false);
111: } else {
112: /**
113: * We need to lock all edits with the same form id because we are
114: * not out of sync with the other editor. This can happen if we
115: * delete a nested form with edits from this editor and edit that
116: * form in aonther edit.
117: */
118: Collection edits = getEdits();
119: Iterator iter = edits.iterator();
120: while (iter.hasNext()) {
121: UndoableEditProxy myedit = (UndoableEditProxy) iter
122: .next();
123: if (edit.getFormId().equals(myedit.getFormId())) {
124: myedit.lock(true);
125: }
126: }
127: }
128: }
129:
130: /**
131: * Notifies this manager that a redo edit happened on another editor.
132: */
133: public void redoHappened(FormEditor editor, FormUndoableEdit edit) {
134: if (equals(edit, (FormUndoableEdit) editToBeRedone())) {
135: redo(false);
136: } else {
137: /**
138: * We need to lock all edits with the same form id because we are
139: * not out of sync with the other editor. This can happen if we
140: * delete a nested form with edits from this editor and edit that
141: * form in aonther edit.
142: */
143: Collection edits = getEdits();
144: Iterator iter = edits.iterator();
145: while (iter.hasNext()) {
146: UndoableEditProxy myedit = (UndoableEditProxy) iter
147: .next();
148: if (edit.getFormId().equals(myedit.getFormId())) {
149: myedit.lock(true);
150: }
151: }
152: }
153: }
154:
155: public synchronized void discardAllEdits() {
156: Enumeration cursor = edits.elements();
157: while (cursor.hasMoreElements()) {
158: UndoableEdit e = (UndoableEdit) cursor.nextElement();
159: e.die();
160: }
161: edits = new Vector(m_limit);
162: m_indexOfNextAdd = 0;
163: }
164:
165: protected void trimForLimit() {
166: if (m_limit > 0) {
167: int size = edits.size();
168: if (size > m_limit) {
169: int halfLimit = m_limit / 2;
170: int keepFrom = m_indexOfNextAdd - 1 - halfLimit;
171: int keepTo = m_indexOfNextAdd - 1 + halfLimit;
172:
173: if (keepTo - keepFrom + 1 > m_limit) {
174: keepFrom++;
175: }
176:
177: if (keepFrom < 0) {
178: keepTo -= keepFrom;
179: keepFrom = 0;
180: }
181: if (keepTo >= size) {
182: int delta = size - keepTo - 1;
183: keepTo += delta;
184: keepFrom += delta;
185: }
186:
187: trimEdits(keepTo + 1, size - 1);
188: trimEdits(0, keepFrom - 1);
189: }
190: }
191: }
192:
193: protected void trimEdits(int from, int to) {
194: if (from <= to) {
195: for (int i = to; from <= i; i--) {
196: UndoableEdit e = (UndoableEdit) edits.elementAt(i);
197: e.die();
198: edits.removeElementAt(i);
199: }
200:
201: if (m_indexOfNextAdd > to) {
202: m_indexOfNextAdd -= to - from + 1;
203: } else if (m_indexOfNextAdd >= from) {
204: m_indexOfNextAdd = from;
205: }
206: }
207: }
208:
209: public UndoableEdit editToBeUndone() {
210: int i = m_indexOfNextAdd;
211: while (i > 0) {
212: UndoableEdit edit = (UndoableEdit) edits.elementAt(--i);
213: if (edit.isSignificant()) {
214: return edit;
215: }
216: }
217: return null;
218: }
219:
220: public UndoableEdit editToBeRedone() {
221: int count = edits.size();
222: int i = m_indexOfNextAdd;
223:
224: while (i < count) {
225: UndoableEdit edit = (UndoableEdit) edits.elementAt(i++);
226: if (edit.isSignificant()) {
227: return edit;
228: }
229: }
230:
231: return null;
232: }
233:
234: protected void undoTo(UndoableEdit edit, boolean shouldUndo)
235: throws CannotUndoException {
236: boolean done = false;
237: while (!done) {
238: UndoableEdit next = (UndoableEdit) edits
239: .elementAt(--m_indexOfNextAdd);
240: if (shouldUndo)
241: next.undo();
242:
243: done = next == edit;
244: }
245: }
246:
247: protected void redoTo(UndoableEdit edit, boolean shouldRedo)
248: throws CannotRedoException {
249: boolean done = false;
250: while (!done) {
251: UndoableEdit next = (UndoableEdit) edits
252: .elementAt(m_indexOfNextAdd++);
253: if (shouldRedo)
254: next.redo();
255: done = next == edit;
256: }
257: }
258:
259: public synchronized void undoOrRedo() throws CannotRedoException,
260: CannotUndoException {
261: if (m_indexOfNextAdd == edits.size()) {
262: undo();
263: } else {
264: redo();
265: }
266: }
267:
268: public synchronized boolean canUndoOrRedo() {
269: if (m_indexOfNextAdd == edits.size()) {
270: return canUndo();
271: } else {
272: return canRedo();
273: }
274: }
275:
276: public synchronized void undo() throws CannotUndoException {
277: undo(true);
278: }
279:
280: /**
281: * Undoes the current edit.
282: *
283: * @param shouldUndo
284: * flag that indicates if the undo on the edit should be invoked.
285: * If false, this operation only changes the indexOfNextAdd
286: * pointer.
287: */
288: public synchronized void undo(boolean shouldUndo)
289: throws CannotUndoException {
290: if (isInProgress()) {
291: UndoableEdit edit = editToBeUndone();
292: if (edit == null) {
293: throw new CannotUndoException();
294: }
295: undoTo(edit, shouldUndo);
296: } else {
297: super .undo();
298: }
299: }
300:
301: public synchronized boolean canUndo() {
302: if (isInProgress()) {
303: UndoableEdit edit = editToBeUndone();
304: return edit != null && edit.canUndo();
305: } else {
306: return super .canUndo();
307: }
308: }
309:
310: public synchronized void redo() throws CannotRedoException {
311: redo(true);
312: }
313:
314: /**
315: * Redoes the current edit.
316: *
317: * @param shouldRedo
318: * flag that indicates if the redo on the edit should be invoked.
319: * If false, this operation only changes the indexOfNextAdd
320: * pointer.
321: */
322: public synchronized void redo(boolean shouldRedo)
323: throws CannotRedoException {
324: if (isInProgress()) {
325: UndoableEdit edit = editToBeRedone();
326: if (edit == null) {
327: throw new CannotRedoException();
328: }
329: redoTo(edit, shouldRedo);
330: } else {
331: super .redo();
332: }
333: }
334:
335: public synchronized boolean canRedo() {
336: if (isInProgress()) {
337: UndoableEdit edit = editToBeRedone();
338: return edit != null && edit.canRedo();
339: } else {
340: return super .canRedo();
341: }
342: }
343:
344: public synchronized boolean addEdit(UndoableEdit anEdit) {
345: assert (!(anEdit instanceof UndoableEditProxy));
346: boolean retVal;
347:
348: trimEdits(m_indexOfNextAdd, edits.size() - 1);
349:
350: retVal = super .addEdit(new UndoableEditProxy(
351: (FormUndoableEdit) anEdit));
352: if (isInProgress()) {
353: retVal = true;
354: }
355:
356: m_indexOfNextAdd = edits.size();
357: trimForLimit();
358:
359: return retVal;
360: }
361:
362: public synchronized void end() {
363: super .end();
364: trimEdits(m_indexOfNextAdd, edits.size() - 1);
365: }
366:
367: }
|