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:
042: package org.netbeans.modules.xml.xam.ui.undo;
043:
044: import java.io.IOException;
045: import java.util.ArrayList;
046: import java.util.List;
047: import javax.swing.event.UndoableEditListener;
048: import javax.swing.text.AbstractDocument;
049: import javax.swing.undo.CannotRedoException;
050: import javax.swing.undo.CannotUndoException;
051: import org.netbeans.modules.xml.xam.Model;
052: import org.openide.awt.UndoRedo;
053:
054: /**
055: * A proxy for another UndoRedo.Manager instance which removes and then
056: * adds the undoable edit listeners from a Swing document, each time the
057: * undo and redo operations are invoked. Essentially, this allows the
058: * client to avoid firing undo events to the listeners when doing nothing
059: * more than undoing or redoing a change to the document.
060: *
061: * <p>To use this undo manager proxy with <code>CloneableEditorSupport</code>,
062: * it is necessary to subclass <code>CloneableEditorSupport</code> and override
063: * the <code>createUndoRedoManager()</code> method to return an instance of
064: * this class, providing the return value of
065: * <code>super.createUndoRedoManager()</code> to the
066: * <code>QuietUndoManager</code> constructor. In this way, this proxy can
067: * then intercept all new edits and properly manage its state. Note that
068: * setting the document in <code>createUndoRedoManager()</code> will not be
069: * possible since the document will not have been initialized yet. It will
070: * have to happen at a later time.</p>
071: *
072: * @author Nathan Fiedler
073: */
074: public class QuietUndoManager extends CompoundUndoManager {
075: /** silence compiler warnings */
076: private static final long serialVersionUID = 1L;
077: /** Undoable edit listeners are managed for this document; may be null. */
078: private AbstractDocument document;
079: /** If not null, the Model is sync'd after undo/redo. */
080: private Model model;
081: private List<Model> otherModels = new ArrayList<Model>();
082:
083: /**
084: * Creates a new instance of QuietUndoManager.
085: *
086: * @param original UndoRedo.Manager to be proxied.
087: */
088: public QuietUndoManager(UndoRedo.Manager original) {
089: super (original);
090: }
091:
092: /**
093: * Wrap up after performing an undo/redo operation.
094: *
095: * @param listeners undoable edit listeners to be added to document.
096: */
097: private void finish(UndoableEditListener[] listeners) {
098: if (model != null) {
099: // When model is non-null, add ourselves as a listener since
100: // we removed us in the preparation step.
101: model.addUndoableEditListener(this );
102: }
103: if (listeners != null && listeners.length > 0) {
104: for (UndoableEditListener uel : listeners) {
105: document.addUndoableEditListener(uel);
106: }
107: }
108: }
109:
110: /**
111: * Prepare to perform an undo/redo operation.
112: *
113: * @return the set of undoable edit listeners removed from document.
114: */
115: private UndoableEditListener[] prepare() {
116: if (model != null) {
117: // If the model is set, that means we are to sync the model
118: // after performing the undo/redo operation. That, however,
119: // means we cannot be listening to the model lest we receive
120: // additional undoable edits, which would be bad.
121: model.removeUndoableEditListener(this );
122: }
123: if (document == null) {
124: return null;
125: }
126: UndoableEditListener[] listeners = document
127: .getUndoableEditListeners();
128: if (listeners != null && listeners.length > 0) {
129: for (UndoableEditListener uel : listeners) {
130: document.removeUndoableEditListener(uel);
131: }
132: }
133: return listeners;
134: }
135:
136: /**
137: * Set the document for which the undoable edit listeners will be
138: * removed and then added back for each undo/redo operation.
139: *
140: * @param document the document whose listeners will be managed.
141: */
142: public void setDocument(AbstractDocument document) {
143: this .document = document;
144: }
145:
146: /**
147: * Set the Model to be managed when the undo and redo operations
148: * are performed. This should only be set when the model view is
149: * being shown. Call this method with a value of <code>null</code>
150: * when the model view is being hidden.
151: *
152: * @param model Model to be managed; if null, disables management.
153: */
154: public void setModel(Model model) {
155: this .model = model;
156: }
157:
158: /**
159: * Sync the Model so it does not perform the auto-sync at some
160: * later time, causing additional undoable edits from appearing
161: * on the undo queue. That would be pointless anyway, since we
162: * are simply undoing or redoing edits anyway.
163: */
164: private void syncModel() {
165: if (model != null) {
166: try {
167: // Fortunately this method is efficient in that it knows
168: // if the change that occurred was done by the model or
169: // if the change is coming from the document (the model
170: // has a document listener on the Swing document).
171: model.sync();
172: for (Model m : otherModels)
173: m.sync();
174: } catch (IOException ioe) {
175: // Ignore, nothing we can do about it.
176: }
177: }
178: }
179:
180: public void redo() throws CannotRedoException {
181: UndoableEditListener[] listeners = prepare();
182: super .redo();
183: syncModel();
184: finish(listeners);
185: }
186:
187: public void undo() throws CannotUndoException {
188: UndoableEditListener[] listeners = prepare();
189: super .undo();
190: syncModel();
191: finish(listeners);
192: }
193:
194: /**
195: * Allows other models to be synced after undo or redo.
196: */
197: public void addWrapperModel(Model model) {
198: if (!otherModels.contains(model))
199: otherModels.add(model);
200: }
201:
202: /**
203: * Removes other models that were added for sync.
204: */
205: public void removeWrapperModel(Model model) {
206: otherModels.remove(model);
207: }
208: }
|