001: /*
002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
003: *
004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
005: *
006: * The contents of this file are subject to the terms of either the GNU
007: * General Public License Version 2 only ("GPL") or the Common
008: * Development and Distribution License("CDDL") (collectively, the
009: * "License"). You may not use this file except in compliance with the
010: * License. You can obtain a copy of the License at
011: * http://www.netbeans.org/cddl-gplv2.html
012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
013: * specific language governing permissions and limitations under the
014: * License. When distributing the software, include this License Header
015: * Notice in each file and include the License file at
016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
017: * particular file as subject to the "Classpath" exception as provided
018: * by Sun in the GPL Version 2 section of the License file that
019: * accompanied this code. If applicable, add the following below the
020: * License Header, with the fields enclosed by brackets [] replaced by
021: * your own identifying information:
022: * "Portions Copyrighted [year] [name of copyright owner]"
023: *
024: * Contributor(s):
025: *
026: * The Original Software is NetBeans. The Initial Developer of the Original
027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
028: * Microsystems, Inc. All Rights Reserved.
029: *
030: * If you wish your version of this file to be governed by only the CDDL
031: * or only the GPL Version 2, indicate your decision by adding
032: * "[Contributor] elects to include this software in this distribution
033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
034: * single choice of license, a recipient has the option to distribute
035: * your version of this file under either the CDDL, the GPL Version 2 or
036: * to extend the choice of license to its licensees as provided above.
037: * However, if you add GPL Version 2 code and therefore, elected the GPL
038: * Version 2 license, then the option applies only if the new code is
039: * made subject to such option by the copyright holder.
040: */
041: package org.netbeans.modules.visualweb.insync;
042:
043: import java.util.ArrayList;
044:
045: import javax.swing.event.ChangeEvent;
046: import javax.swing.event.ChangeListener;
047: import javax.swing.undo.CannotRedoException;
048: import javax.swing.undo.CannotUndoException;
049:
050: import org.openide.awt.UndoRedo;
051:
052: import org.netbeans.modules.visualweb.insync.models.FacesModel;
053:
054: /**
055: * Undo manager: maintains a list of undoable events, and performs undo/redo as requested by the
056: * user. This class implements the UndoRedo interface from NetBeans, which is used by the
057: * UndoAction. This manager plugs into the IDE via the designer: when a designer form receives
058: * focus, it will set this undo manager as the active undo-redo object for the undo action.
059: *
060: * @author Tor Norbye
061: */
062: public class UndoManager implements UndoRedo {
063:
064: /** Possibly empty list of UndoEvents */
065: private final ArrayList undoStack = new ArrayList();
066:
067: /** Temporarily holds undo events that have been tentatively removed from the undoStack. */
068: private final ArrayList undoStackRemoved = new ArrayList();
069:
070: /**
071: * Pointer to where we are in the undo stack. It points to the next redoable item. Thus, when
072: * there are 5 events, and the current index is 5, we can't redo any events, but we can undo 5
073: * events. When the current index is 0, we can redo five and undo 0.
074: */
075: private int current = 0;
076: private UndoEvent currentEvent;
077: private int depth = 0;
078:
079: public UndoManager() {
080: }
081:
082: /**
083: * Return event that is in progress. After a finishUndoableTask this will return null.
084: */
085: public UndoEvent getCurrentEvent() {
086: return currentEvent;
087: }
088:
089: /**
090: * Inform the undo manager that a new undoable task is beginning.
091: *
092: * @param description A short user visible description of the task - may for example appear in
093: * the tooltip over the undo/redo buttons.
094: * @param model The Model associated with the task; when the task is undone/redone, the model is
095: * synced.
096: */
097: public UndoEvent startUndoableTask(String description, Model model) {
098: if (currentEvent != null) {
099: // XXX no ! I should keep a reference count here to make sure
100: // only the outermost reference gets it
101: depth++;
102: return currentEvent;
103: }
104:
105: //clear undoStackRemoved, so we can add any events we are about to remove from undoStack
106: undoStackRemoved.clear();
107:
108: // Truncate redo "future" when you initiate a new undoable edit
109: if (canRedo()) {
110: // Remove the items from "current" out to the end of the stack;
111: // unfortunately there's no way to set the size of an array list
112: // so we've gotta delete them. Do it in the reverse order so
113: // we don't cause any shifting.
114: for (int i = undoStack.size() - 1; i >= current; i--) {
115: undoStackRemoved.add(0, undoStack.remove(i));
116: }
117: }
118:
119: UndoEvent event = new UndoEvent(description, model);
120: currentEvent = event;
121: undoStack.add(event);
122: current++;
123: depth++;
124:
125: return event;
126: }
127:
128: /** Inform the undo manager that the given event is done. */
129: public void finishUndoableTask(UndoEvent event) {
130: depth--;
131: if (depth > 0) {
132: return;
133: }
134: if (event != null) {
135: if (currentEvent == event) {
136: currentEvent = null;
137: }
138: // Don't record undo events for items that don't
139: // cause any changes!!
140: if (!event.hasChanges() && current > 0) {
141: //make sure "current" is the last item in the stack, and that the last item is the "event"
142: if (current != undoStack.size()
143: || event != undoStack.get(current - 1)) {
144: org.openide.ErrorManager
145: .getDefault()
146: .log(
147: "org.netbeans.modules.visualweb.insync.UndoManager.finishUndoableTask: current event is not last in undo stack"); //NOI18N
148: } else {
149: //remove the event
150: undoStack.remove(event);
151: //add back any removed events
152: undoStack.addAll(undoStackRemoved);
153: }
154: current--;
155: }
156: }
157:
158: fireStateChanged();
159: }
160:
161: private void fireStateChanged() {
162: ChangeEvent event = null;
163: for (int i = listenerList.size() - 1; i >= 0; i--) {
164: ChangeListener listener = (ChangeListener) listenerList
165: .get(i);
166: if (event == null) {
167: event = new ChangeEvent(this );
168: }
169: listener.stateChanged(event);
170: }
171: }
172:
173: /**
174: * This method is called when one of the buffers this undo manager cares about has been edited.
175: * If we detect that this is a user-initiated editing operation of the buffer itself, we flush
176: * the undo queues. Otherwise, the user can go into say the backing file, undo three editing
177: * operations, and type 5 characters, then go back to the design view and try to user our undo
178: * queue, and the undo events are totally unsynchronized.
179: */
180: public void notifyBufferEdited(SourceUnit unit) {
181: if (currentEvent == null) {
182: clearStack();
183: }
184: }
185:
186: public void notifyUndoableEditEvent(SourceUnit unit) {
187: if (currentEvent != null) {
188: currentEvent.notifyBufferUpdated(unit);
189: } else {
190: org.openide.ErrorManager.getDefault().log(
191: "Unexpected undoable event with no current event");
192: }
193: }
194:
195: private void clearStack() {
196: if (undoStack.size() > 0) {
197: undoStack.clear();
198: current = 0;
199: fireStateChanged();
200: }
201: }
202:
203: //------------------------------------------------------------------------------------- UndoRedo
204:
205: public boolean canRedo() {
206: return current < undoStack.size();
207: }
208:
209: public boolean canUndo() {
210: return current > 0;
211: }
212:
213: public String getRedoPresentationName() {
214: if (canRedo()) {
215: UndoEvent event = (UndoEvent) undoStack.get(current);
216: return event.getDescription();
217: } else {
218: return "";
219: }
220: }
221:
222: public String getUndoPresentationName() {
223: if (canUndo()) {
224: UndoEvent event = (UndoEvent) undoStack.get(current - 1);
225: return event.getDescription();
226: } else {
227: return "";
228: }
229: }
230:
231: private ArrayList listenerList = new ArrayList(3);
232:
233: public void addChangeListener(ChangeListener changeListener) {
234: listenerList.add(changeListener);
235: }
236:
237: public void removeChangeListener(ChangeListener changeListener) {
238: listenerList.remove(changeListener);
239: }
240:
241: public void undo() throws CannotUndoException {
242: current--;
243: UndoEvent event = (UndoEvent) undoStack.get(current);
244: currentEvent = event;
245: event.undo();
246: currentEvent = null;
247: syncModel(event);
248:
249: fireStateChanged();
250: }
251:
252: public void redo() throws CannotRedoException {
253: UndoEvent event = (UndoEvent) undoStack.get(current);
254: currentEvent = event;
255: event.redo();
256: currentEvent = null;
257: current++;
258: syncModel(event);
259:
260: fireStateChanged();
261: }
262:
263: private void syncModel(UndoEvent event) {
264: Model model = event.getModel();
265: model.sync();
266: // HACK
267: FacesModel fm = (FacesModel) model;
268: fm.getMarkupUnit().setClean();
269: fm.getJavaUnit().setClean();
270: }
271: }
|