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.core.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.core.XSLTDataEditorSupport;
042: import org.netbeans.modules.xslt.core.XSLTDataObject;
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 XSLTSourceMultiViewElement extends CloneableEditor
059: implements MultiViewElement {
060:
061: private static final long serialVersionUID = 1L;
062: static final String PREFERED_ID = "XsltSourceView"; // NOI18N
063: private transient MultiViewElementCallback myMultiViewObserver;
064: private XSLTDataObject myDataObject;
065: private transient JToolBar myToolBar;
066:
067: // for deserialization
068: private XSLTSourceMultiViewElement() {
069: super ();
070: }
071:
072: public XSLTSourceMultiViewElement(XSLTDataObject 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 XsltMultiViewSupport,
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 XSLTDataObject getDataObject() {
093: return myDataObject;
094: }
095:
096: private void initialize() {
097: associateLookup(new ProxyLookup(new Lookup[] { // # 67257
098: Lookups.fixed(new Object[] {
099: getActionMap(), // # 85512
100: getDataObject(),
101: getDataObject().getNodeDelegate() }),
102: getDataObject().getLookup() })); // # 117029
103: }
104:
105: public void writeExternal(ObjectOutput out) throws IOException {
106: super .writeExternal(out);
107: out.writeObject(myDataObject);
108: }
109:
110: /**
111: * we are using Externalization semantics so that we can get a hook to call
112: * initialize() upon deserialization
113: */
114: public void readExternal(ObjectInput in) throws IOException,
115: ClassNotFoundException {
116: super .readExternal(in);
117: Object obj = in.readObject();
118: if (obj instanceof XSLTDataObject) {
119: myDataObject = (XSLTDataObject) obj;
120: }
121: initialize();
122: }
123:
124: public int getPersistenceType() {
125: return TopComponent.PERSISTENCE_ONLY_OPENED;
126: }
127:
128: ////////////////////////////////////////////////////////////////////////////
129: /////////////////////////// MultiViewElement //////////////////////////////
130: ////////////////////////////////////////////////////////////////////////////
131:
132: /**
133: * Adds the undo/redo manager to the document as an undoable
134: * edit listener, so it receives the edits onto the queue.
135: */
136: private void addUndoManager() {
137: XSLTDataEditorSupport editor = getDataObject()
138: .getEditorSupport();
139: QuietUndoManager undo = editor.getUndoManager();
140: StyledDocument doc = editor.getDocument();
141: // Unlikely to be null, but could be if the cloned views are not
142: // behaving correctly.
143: if (doc != null) {
144: // Ensure the listener is not added twice.
145: doc.removeUndoableEditListener(undo);
146: doc.addUndoableEditListener(undo);
147: // Start the compound mode of the undo manager, such that when
148: // we are hidden, we will treat all of the edits as a single
149: // compound edit. This avoids having the user invoke undo
150: // numerous times when in the model view.
151: undo.beginCompound();
152: }
153: }
154:
155: /**
156: * Removes the undo/redo manager undoable edit listener from the
157: * document, to stop receiving undoable edits.
158: */
159: private void removeUndoManager() {
160: XSLTDataEditorSupport editor = getDataObject()
161: .getEditorSupport();
162: StyledDocument doc = editor.getDocument();
163: // May be null when closing the editor.
164: if (doc != null) {
165: QuietUndoManager undo = editor.getUndoManager();
166: doc.removeUndoableEditListener(undo);
167: undo.endCompound();
168: }
169: }
170:
171: private boolean isLastView() {
172: boolean oneOrLess = true;
173: Enumeration en = ((CloneableTopComponent) myMultiViewObserver
174: .getTopComponent()).getReference().getComponents();
175: if (en.hasMoreElements()) {
176: en.nextElement();
177: if (en.hasMoreElements()) {
178: oneOrLess = false;
179: }
180: }
181:
182: return oneOrLess;
183: }
184:
185: private Action[] getNodeActions() {
186: if (myMultiViewObserver == null) {
187: return null;
188: }
189: Node[] activeNodes = myMultiViewObserver.getTopComponent()
190: .getActivatedNodes();
191: ;
192: if (activeNodes != null && activeNodes.length > 0) {
193: return activeNodes[0].getActions(true);
194: }
195: return null;
196: }
197:
198: public CloseOperationState canCloseElement() {
199: boolean lastView = isLastView();
200: if (!lastView) {
201: return CloseOperationState.STATE_OK;
202: }
203:
204: //
205: // not sure if we need to be intelligent here; other MV examples suggest
206: // that you can just return dummy UnSafeCloseState() and delegate to the
207: // closeHandler - for now we will be more intelligent and redundant here
208: //
209: boolean modified = cloneableEditorSupport().isModified();
210: if (!modified) {
211: return CloseOperationState.STATE_OK;
212: } else {
213: return MultiViewFactory.createUnsafeCloseState(
214: "Data Object Modified", // NOI18N
215: MultiViewFactory.NOOP_CLOSE_ACTION,
216: MultiViewFactory.NOOP_CLOSE_ACTION);
217: }
218: }
219:
220: @Override
221: public Action[] getActions() {
222: Action[] retAction;
223:
224: if (myMultiViewObserver != null) {
225: Action[] defActions = myMultiViewObserver
226: .createDefaultActions();
227: Action[] nodeActions = getNodeActions();
228: if (nodeActions != null && nodeActions.length > 0) {
229: List<Action> actionsList = new ArrayList<Action>();
230: actionsList.addAll(Arrays.asList(defActions));
231: actionsList.addAll(Arrays.asList(nodeActions));
232:
233: retAction = new Action[actionsList.size()];
234: retAction = actionsList.toArray(retAction);
235: } else {
236: retAction = defActions;
237: }
238: } else {
239: retAction = super .getActions();
240: }
241: return retAction;
242: }
243:
244: public void componentActivated() {
245: super .componentActivated();
246: // Set our activated nodes to kick Undo/Redo into action.
247: // Need to do it twice in the event we are switching from another
248: // multiview element that has the same activated nodes, in which
249: // case no events are fired and so the UndoAction does not
250: // register for changes with our undo manager.
251: setActivatedNodes(new Node[0]);
252: setActivatedNodes(new Node[] { getDataObject()
253: .getNodeDelegate() });
254: addUndoManager();
255: }
256:
257: public void componentClosed() {
258: super .componentClosed();
259:
260: /*
261: * Avoid memory leak. The first call is good it seems.
262: *
263: * The second is like a hack. But this works and could be a problem
264: * only when this MultiviewElement will be reused after reopening.
265: * It seems this is not a case - each time when editor is opened it is
266: * instantiated.
267: */
268: setMultiViewCallback(null);
269: if (getParent() != null) {
270: getParent().remove(this );
271: }
272:
273: }
274:
275: public JComponent getToolbarRepresentation() {
276: Document doc = getEditorPane().getDocument();
277: if (doc instanceof NbDocument.CustomToolbar) {
278: if (myToolBar == null) {
279: myToolBar = ((NbDocument.CustomToolbar) doc)
280: .createToolbar(getEditorPane());
281: }
282: return myToolBar;
283: }
284: return null;
285: }
286:
287: public JComponent getVisualRepresentation() {
288: return this ;
289: }
290:
291: public void componentDeactivated() {
292: super .componentDeactivated();
293: removeUndoManager();
294: getDataObject().getEditorSupport().syncModel();
295: }
296:
297: public void componentHidden() {
298: super .componentHidden();
299: removeUndoManager();
300: getDataObject().getEditorSupport().syncModel();
301: }
302:
303: public void componentOpened() {
304: super .componentOpened();
305: }
306:
307: public void componentShowing() {
308: super .componentShowing();
309: addUndoManager();
310: }
311:
312: public void setMultiViewCallback(
313: final MultiViewElementCallback callback) {
314: myMultiViewObserver = callback;
315: }
316:
317: public void requestVisible() {
318: if (myMultiViewObserver != null) {
319: myMultiViewObserver.requestVisible();
320: } else {
321: super .requestVisible();
322: }
323: }
324:
325: public void requestActive() {
326: if (myMultiViewObserver != null) {
327: myMultiViewObserver.requestActive();
328: } else {
329: super .requestActive();
330: }
331: }
332:
333: public UndoRedo getUndoRedo() {
334: XSLTDataEditorSupport editor = myDataObject.getEditorSupport();
335: return editor.getUndoManager();
336: }
337:
338: protected String preferredID() {
339: return PREFERED_ID;
340: }
341:
342: /**
343: * The close last method should be called only for the last clone.
344: * If there are still existing clones this method must return false. The
345: * implementation from the FormEditor always returns true but this is
346: * not the expected behavior. The intention is to close the editor support
347: * once the last editor has been closed, using the silent close to avoid
348: * displaying a new dialog which is already being displayed via the
349: * close handler.
350: */
351: protected boolean closeLast() {
352: XSLTDataEditorSupport editor = getDataObject()
353: .getEditorSupport();
354: JEditorPane[] editors = editor.getOpenedPanes();
355: if (editors == null || editors.length == 0) {
356: return editor.silentClose();
357: }
358: return false;
359: }
360: }
|