001: /*
002: * The contents of this file are subject to the terms of the Common Development
003: * and Distribution License (the License). You may not use this file except in
004: * compliance with the License.
005: *
006: * You can obtain a copy of the License at http://www.netbeans.org/cddl.html
007: * or http://www.netbeans.org/cddl.txt.
008: *
009: * When distributing Covered Code, include this CDDL Header Notice in each file
010: * and include the License file at http://www.netbeans.org/cddl.txt.
011: * If applicable, add the following below the CDDL Header, with the fields
012: * enclosed by brackets [] replaced by your own identifying information:
013: * "Portions Copyrighted [year] [name of copyright owner]"
014: *
015: * The Original Software is NetBeans. The Initial Developer of the Original
016: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2007 Sun
017: * Microsystems, Inc. All Rights Reserved.
018: */
019: package org.netbeans.modules.xslt.tmap.multiview.source;
020:
021: import java.io.IOException;
022: import java.io.ObjectInput;
023: import java.io.ObjectOutput;
024: import java.util.ArrayList;
025: import java.util.Arrays;
026: import java.util.Enumeration;
027: import java.util.List;
028: import javax.swing.Action;
029:
030: import javax.swing.JComponent;
031: import javax.swing.JEditorPane;
032: import javax.swing.JToolBar;
033: import javax.swing.text.Document;
034: import javax.swing.text.StyledDocument;
035:
036: import org.netbeans.core.spi.multiview.CloseOperationState;
037: import org.netbeans.core.spi.multiview.MultiViewElement;
038: import org.netbeans.core.spi.multiview.MultiViewElementCallback;
039: import org.netbeans.core.spi.multiview.MultiViewFactory;
040: import org.netbeans.modules.xml.xam.ui.undo.QuietUndoManager;
041: import org.netbeans.modules.xslt.tmap.TMapDataEditorSupport;
042: import org.netbeans.modules.xslt.tmap.TMapDataObject;
043: import org.openide.awt.UndoRedo;
044: import org.openide.nodes.Node;
045: import org.openide.text.CloneableEditor;
046: import org.openide.text.NbDocument;
047: import org.openide.util.Lookup;
048: import org.openide.util.lookup.Lookups;
049: import org.openide.util.lookup.ProxyLookup;
050: import org.openide.windows.CloneableTopComponent;
051: import org.openide.windows.TopComponent;
052:
053: /**
054: *
055: * @author Vitaly Bychkov
056: * @version 1.0
057: */
058: public class TMapSourceMultiViewElement extends CloneableEditor
059: implements MultiViewElement {
060:
061: private static final long serialVersionUID = 1L;
062: static final String PREFERED_ID = "TMapSourceView"; // NOI18N
063: private transient MultiViewElementCallback myMultiViewObserver;
064: private TMapDataObject myDataObject;
065: private transient JToolBar myToolBar;
066:
067: // for deserialization
068: private TMapSourceMultiViewElement() {
069: super ();
070: }
071:
072: public TMapSourceMultiViewElement(TMapDataObject dataObject) {
073: super (dataObject.getEditorSupport());
074: myDataObject = dataObject;
075: // ================================================================
076: // Initialize the editor support properly, which only needs to be
077: // done when the editor is created (deserialization is working
078: // due to CloneableEditor.readResolve() initializing the editor).
079: // Note that this relies on the source view being the first in the
080: // array of MultiViewDescription instances in TMapMultiViewSupport,
081: // since that results in the source view being created and opened
082: // by default, only to be hidden when the DataObject default action
083: // makes the columns view appear.
084: // This initialization fixes CR 6349089 by ensuring that the Node
085: // listener is registered with the DataObject Node delegate.
086: getDataObject().getEditorSupport().initializeCloneableEditor(
087: this );
088:
089: initialize();
090: }
091:
092: private TMapDataObject getDataObject() {
093: return myDataObject;
094: }
095:
096: private void initialize() {
097: Node delegate = myDataObject.getNodeDelegate();
098: SourceCookieProxyLookup lookup = new SourceCookieProxyLookup(
099: new Lookup[] { Lookups.fixed(new Object[] {
100: // Need ActionMap in lookup so editor actions work.
101: getActionMap(),
102: // Need the data object registered in the lookup so that the
103: // projectui code will close our open editor windows when the
104: // project is closed.
105: myDataObject }), }, delegate);
106: associateLookup(lookup);
107: addPropertyChangeListener("activatedNodes", lookup);
108:
109: // associateLookup(new ProxyLookup(new Lookup[] { // # 67257
110: // Lookups.fixed(new Object[] {
111: // getActionMap(), // # 85512
112: // getDataObject(),
113: // getDataObject().getNodeDelegate() }),
114: // getDataObject().getLookup() })); // # 117029
115: }
116:
117: public void writeExternal(ObjectOutput out) throws IOException {
118: super .writeExternal(out);
119: out.writeObject(myDataObject);
120: }
121:
122: /**
123: * we are using Externalization semantics so that we can get a hook to call
124: * initialize() upon deserialization
125: */
126: public void readExternal(ObjectInput in) throws IOException,
127: ClassNotFoundException {
128: super .readExternal(in);
129: Object obj = in.readObject();
130: if (obj instanceof TMapDataObject) {
131: myDataObject = (TMapDataObject) obj;
132: }
133: initialize();
134: }
135:
136: public int getPersistenceType() {
137: return TopComponent.PERSISTENCE_ONLY_OPENED;
138: }
139:
140: ////////////////////////////////////////////////////////////////////////////
141: /////////////////////////// MultiViewElement //////////////////////////////
142: ////////////////////////////////////////////////////////////////////////////
143:
144: /**
145: * Adds the undo/redo manager to the document as an undoable
146: * edit listener, so it receives the edits onto the queue.
147: */
148: private void addUndoManager() {
149: TMapDataEditorSupport editor = getDataObject()
150: .getEditorSupport();
151: QuietUndoManager undo = editor.getUndoManager();
152: StyledDocument doc = editor.getDocument();
153: // Unlikely to be null, but could be if the cloned views are not
154: // behaving correctly.
155: if (doc != null) {
156: // Ensure the listener is not added twice.
157: doc.removeUndoableEditListener(undo);
158: doc.addUndoableEditListener(undo);
159: // Start the compound mode of the undo manager, such that when
160: // we are hidden, we will treat all of the edits as a single
161: // compound edit. This avoids having the user invoke undo
162: // numerous times when in the model view.
163: undo.beginCompound();
164: }
165: }
166:
167: /**
168: * Removes the undo/redo manager undoable edit listener from the
169: * document, to stop receiving undoable edits.
170: */
171: private void removeUndoManager() {
172: TMapDataEditorSupport editor = getDataObject()
173: .getEditorSupport();
174: StyledDocument doc = editor.getDocument();
175: // May be null when closing the editor.
176: if (doc != null) {
177: QuietUndoManager undo = editor.getUndoManager();
178: doc.removeUndoableEditListener(undo);
179: undo.endCompound();
180: }
181: }
182:
183: private boolean isLastView() {
184: boolean oneOrLess = true;
185: Enumeration en = ((CloneableTopComponent) myMultiViewObserver
186: .getTopComponent()).getReference().getComponents();
187: if (en.hasMoreElements()) {
188: en.nextElement();
189: if (en.hasMoreElements()) {
190: oneOrLess = false;
191: }
192: }
193:
194: return oneOrLess;
195: }
196:
197: private Action[] getNodeActions() {
198: if (myMultiViewObserver == null) {
199: return null;
200: }
201: Node[] activeNodes = myMultiViewObserver.getTopComponent()
202: .getActivatedNodes();
203: ;
204: if (activeNodes != null && activeNodes.length > 0) {
205: return activeNodes[0].getActions(true);
206: }
207: return null;
208: }
209:
210: public CloseOperationState canCloseElement() {
211: boolean lastView = isLastView();
212: if (!lastView) {
213: return CloseOperationState.STATE_OK;
214: }
215:
216: //
217: // not sure if we need to be intelligent here; other MV examples suggest
218: // that you can just return dummy UnSafeCloseState() and delegate to the
219: // closeHandler - for now we will be more intelligent and redundant here
220: //
221: boolean modified = cloneableEditorSupport().isModified();
222: if (!modified) {
223: return CloseOperationState.STATE_OK;
224: } else {
225: return MultiViewFactory.createUnsafeCloseState(
226: "Data Object Modified", // NOI18N
227: MultiViewFactory.NOOP_CLOSE_ACTION,
228: MultiViewFactory.NOOP_CLOSE_ACTION);
229: }
230: }
231:
232: @Override
233: public Action[] getActions() {
234: Action[] retAction;
235:
236: if (myMultiViewObserver != null) {
237: Action[] defActions = myMultiViewObserver
238: .createDefaultActions();
239: Action[] nodeActions = getNodeActions();
240: if (nodeActions != null && nodeActions.length > 0) {
241: List<Action> actionsList = new ArrayList<Action>();
242: actionsList.addAll(Arrays.asList(defActions));
243: actionsList.addAll(Arrays.asList(nodeActions));
244:
245: retAction = new Action[actionsList.size()];
246: retAction = actionsList.toArray(retAction);
247: } else {
248: retAction = defActions;
249: }
250: } else {
251: retAction = super .getActions();
252: }
253: return retAction;
254: }
255:
256: public void componentActivated() {
257: super .componentActivated();
258: // Set our activated nodes to kick Undo/Redo into action.
259: // Need to do it twice in the event we are switching from another
260: // multiview element that has the same activated nodes, in which
261: // case no events are fired and so the UndoAction does not
262: // register for changes with our undo manager.
263: // setActivatedNodes(new Node[0]);
264: // setActivatedNodes(new Node[] { getDataObject().getNodeDelegate() });
265: // addUndoManager();
266: TMapDataEditorSupport editor = getDataObject()
267: .getEditorSupport();
268: editor.addUndoManagerToDocument();
269: }
270:
271: public void componentClosed() {
272: super .componentClosed();
273:
274: /*
275: * Avoid memory leak. The first call is good it seems.
276: *
277: * The second is like a hack. But this works and could be a problem
278: * only when this MultiviewElement will be reused after reopening.
279: * It seems this is not a case - each time when editor is opened it is
280: * instantiated.
281: */
282: setMultiViewCallback(null);
283: if (getParent() != null) {
284: getParent().remove(this );
285: }
286:
287: }
288:
289: public JComponent getToolbarRepresentation() {
290: Document doc = getEditorPane().getDocument();
291: if (doc instanceof NbDocument.CustomToolbar) {
292: if (myToolBar == null) {
293: myToolBar = ((NbDocument.CustomToolbar) doc)
294: .createToolbar(getEditorPane());
295: }
296: return myToolBar;
297: }
298: return null;
299: }
300:
301: public JComponent getVisualRepresentation() {
302: return this ;
303: }
304:
305: public void componentDeactivated() {
306: super .componentDeactivated();
307: // removeUndoManager();
308: TMapDataEditorSupport editor = getDataObject()
309: .getEditorSupport();
310: // Sync model before having undo manager listen to the model,
311: // lest we get redundant undoable edits added to the queue.
312: editor.syncModel();
313: editor.removeUndoManagerFromDocument();
314: }
315:
316: public void componentHidden() {
317: super .componentHidden();
318: // removeUndoManager();
319: // getDataObject().getEditorSupport().syncModel();
320: TMapDataEditorSupport editor = getDataObject()
321: .getEditorSupport();
322: // Sync model before having undo manager listen to the model,
323: // lest we get redundant undoable edits added to the queue.
324: editor.syncModel();
325: editor.removeUndoManagerFromDocument();
326: }
327:
328: public void componentOpened() {
329: super .componentOpened();
330: }
331:
332: public void componentShowing() {
333: super .componentShowing();
334: // addUndoManager();
335: TMapDataEditorSupport editor = getDataObject()
336: .getEditorSupport();
337: editor.addUndoManagerToDocument();
338: }
339:
340: public void setMultiViewCallback(
341: final MultiViewElementCallback callback) {
342: myMultiViewObserver = callback;
343: }
344:
345: public void requestVisible() {
346: if (myMultiViewObserver != null) {
347: myMultiViewObserver.requestVisible();
348: } else {
349: super .requestVisible();
350: }
351: }
352:
353: public void requestActive() {
354: if (myMultiViewObserver != null) {
355: myMultiViewObserver.requestActive();
356: } else {
357: super .requestActive();
358: }
359: }
360:
361: public UndoRedo getUndoRedo() {
362: TMapDataEditorSupport editor = myDataObject.getEditorSupport();
363: return editor.getUndoManager();
364: }
365:
366: protected String preferredID() {
367: return PREFERED_ID;
368: }
369:
370: /**
371: * The close last method should be called only for the last clone.
372: * If there are still existing clones this method must return false. The
373: * implementation from the FormEditor always returns true but this is
374: * not the expected behavior. The intention is to close the editor support
375: * once the last editor has been closed, using the silent close to avoid
376: * displaying a new dialog which is already being displayed via the
377: * close handler.
378: */
379: protected boolean closeLast() {
380: TMapDataEditorSupport editor = getDataObject()
381: .getEditorSupport();
382: JEditorPane[] editors = editor.getOpenedPanes();
383: if (editors == null || editors.length == 0) {
384: return editor.silentClose();
385: }
386: return false;
387: }
388: }
|