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.bpel.core.multiview;
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:
029: import javax.swing.Action;
030: import javax.swing.JComponent;
031: import javax.swing.JEditorPane;
032: import javax.swing.JToolBar;
033: import javax.swing.SwingUtilities;
034: import javax.swing.event.CaretEvent;
035: import javax.swing.event.CaretListener;
036: import javax.swing.text.Document;
037: import javax.swing.text.StyledDocument;
038:
039: import org.netbeans.core.spi.multiview.CloseOperationState;
040: import org.netbeans.core.spi.multiview.MultiViewElement;
041: import org.netbeans.core.spi.multiview.MultiViewElementCallback;
042: import org.netbeans.core.spi.multiview.MultiViewFactory;
043: import org.netbeans.modules.bpel.core.BPELDataEditorSupport;
044: import org.netbeans.modules.bpel.core.BPELDataObject;
045: import org.netbeans.modules.bpel.core.util.BPELValidationController;
046: import org.netbeans.modules.bpel.editors.api.nodes.FactoryAccess;
047: import org.netbeans.modules.bpel.editors.api.nodes.NodeType;
048: import org.netbeans.modules.bpel.editors.api.utils.Util;
049: import org.netbeans.modules.bpel.model.api.BpelEntity;
050: import org.netbeans.modules.bpel.model.api.BpelModel;
051: import org.netbeans.modules.bpel.model.api.events.ArrayUpdateEvent;
052: import org.netbeans.modules.bpel.model.api.events.ChangeEvent;
053: import org.netbeans.modules.bpel.model.api.events.ChangeEventListener;
054: import org.netbeans.modules.bpel.model.api.events.EntityInsertEvent;
055: import org.netbeans.modules.bpel.model.api.events.EntityRemoveEvent;
056: import org.netbeans.modules.bpel.model.api.events.EntityUpdateEvent;
057: import org.netbeans.modules.bpel.model.api.events.PropertyRemoveEvent;
058: import org.netbeans.modules.bpel.model.api.events.PropertyUpdateEvent;
059: import org.netbeans.modules.soa.ui.nodes.InstanceRef;
060: import org.netbeans.modules.soa.ui.nodes.NodeFactory;
061: import org.netbeans.modules.xml.xam.Nameable;
062: import org.netbeans.modules.xml.xam.ui.multiview.CookieProxyLookup;
063: import org.netbeans.modules.xml.xam.ui.undo.QuietUndoManager;
064: import org.openide.awt.UndoRedo;
065: import org.openide.nodes.Node;
066: import org.openide.text.CloneableEditor;
067: import org.openide.text.NbDocument;
068: import org.openide.util.Lookup;
069: import org.openide.util.RequestProcessor;
070: import org.openide.util.lookup.Lookups;
071: import org.openide.util.lookup.ProxyLookup;
072: import org.openide.windows.CloneableTopComponent;
073: import org.openide.windows.TopComponent;
074:
075: /**
076: *
077: * @author ads
078: */
079: public class BPELSourceMultiViewElement extends CloneableEditor
080: implements MultiViewElement {
081:
082: private static final int CARET_CHANGE_TASK_DELAY = 500;
083:
084: private static final long serialVersionUID = 1L;
085:
086: static final String PREFERED_ID = "BpelSourceView"; // NOI18N
087:
088: // for deserialization
089: private BPELSourceMultiViewElement() {
090: super ();
091: }
092:
093: public BPELSourceMultiViewElement(BPELDataObject dataObject) {
094: super (dataObject.getEditorSupport());
095: myDataObject = dataObject;
096: // ================================================================
097: // Initialize the editor support properly, which only needs to be
098: // done when the editor is created (deserialization is working
099: // due to CloneableEditor.readResolve() initializing the editor).
100: // Note that this relies on the source view being the first in the
101: // array of MultiViewDescription instances in BpelMultiViewSupport,
102: // since that results in the source view being created and opened
103: // by default, only to be hidden when the DataObject default action
104: // makes the columns view appear.
105: // This initialization fixes CR 6349089 by ensuring that the Node
106: // listener is registered with the DataObject Node delegate.
107: getDataObject().getEditorSupport().initializeCloneableEditor(
108: this );
109:
110: initialize();
111: }
112:
113: public void writeExternal(ObjectOutput out) throws IOException {
114: super .writeExternal(out);
115: out.writeObject(myDataObject);
116: }
117:
118: /**
119: * we are using Externalization semantics so that we can get a hook to call
120: * initialize() upon deserialization
121: */
122: public void readExternal(ObjectInput in) throws IOException,
123: ClassNotFoundException {
124: super .readExternal(in);
125: Object obj = in.readObject();
126: if (obj instanceof BPELDataObject) {
127: myDataObject = (BPELDataObject) obj;
128: }
129: initialize();
130: }
131:
132: public int getPersistenceType() {
133: return TopComponent.PERSISTENCE_ONLY_OPENED;
134: }
135:
136: public CloseOperationState canCloseElement() {
137: boolean lastView = isLastView();
138: if (!lastView) {
139: return CloseOperationState.STATE_OK;
140: }
141:
142: //
143: // not sure if we need to be intelligent here; other MV examples suggest
144: // that you can just return dummy UnSafeCloseState() and delegate to the
145: // closeHandler - for now we will be more intelligent and redundant here
146: //
147: boolean modified = cloneableEditorSupport().isModified();
148: if (!modified) {
149: return CloseOperationState.STATE_OK;
150: } else {
151: return MultiViewFactory.createUnsafeCloseState(
152: "Data Object Modified", // NOI18N
153: MultiViewFactory.NOOP_CLOSE_ACTION,
154: MultiViewFactory.NOOP_CLOSE_ACTION);
155: }
156: }
157:
158: @Override
159: public Action[] getActions() {
160: Action[] retAction;
161:
162: if (myMultiViewObserver != null) {
163: Action[] defActions = myMultiViewObserver
164: .createDefaultActions();
165: Action[] nodeActions = getNodeActions();
166: if (nodeActions != null && nodeActions.length > 0) {
167: List<Action> actionsList = new ArrayList<Action>();
168: actionsList.addAll(Arrays.asList(defActions));
169: actionsList.addAll(Arrays.asList(nodeActions));
170:
171: retAction = new Action[actionsList.size()];
172: retAction = actionsList.toArray(retAction);
173: } else {
174: retAction = defActions;
175: }
176: } else {
177: retAction = super .getActions();
178: }
179: return retAction;
180: }
181:
182: public void componentActivated() {
183: super .componentActivated();
184: // Set our activated nodes to kick Undo/Redo into action.
185: // Need to do it twice in the event we are switching from another
186: // multiview element that has the same activated nodes, in which
187: // case no events are fired and so the UndoAction does not
188: // register for changes with our undo manager.
189: // setActivatedNodes(new Node[0]);
190: // setActivatedNodes(new Node[] { getDataObject().getNodeDelegate() });
191: setCaretAssocActiveNodes();
192: BPELDataEditorSupport editor = getDataObject()
193: .getEditorSupport();
194: editor.addUndoManagerToDocument();
195: // addUndoManager();
196:
197: getValidationController().triggerValidation(true);
198: }
199:
200: public void componentClosed() {
201: super .componentClosed();
202: removeCaretPositionListener();
203:
204: /*
205: * Avoid memory leak. The first call is good it seems.
206: *
207: * The second is like a hack. But this works and could be a problem
208: * only when this MultiviewElement will be reused after reopening.
209: * It seems this is not a case - each time when editor is opened it is
210: * instantiated.
211: */
212: setMultiViewCallback(null);
213: if (getParent() != null) {
214: getParent().remove(this );
215: }
216:
217: }
218:
219: public void componentDeactivated() {
220: super .componentDeactivated();
221: // removeCaretPositionListener();
222: // removeUndoManager();
223: BPELDataEditorSupport editor = getDataObject()
224: .getEditorSupport();
225: // Sync model before having undo manager listen to the model,
226: // lest we get redundant undoable edits added to the queue.
227: editor.syncModel();
228: editor.removeUndoManagerFromDocument();
229:
230: }
231:
232: public void componentHidden() {
233: super .componentHidden();
234: // removeCaretPositionListener();
235: BPELDataEditorSupport editor = getDataObject()
236: .getEditorSupport();
237: // Sync model before having undo manager listen to the model,
238: // lest we get redundant undoable edits added to the queue.
239: editor.syncModel();
240: editor.removeUndoManagerFromDocument();
241: // removeUndoManager();
242: }
243:
244: public void componentOpened() {
245: super .componentOpened();
246: addCaretPositionListener();
247: }
248:
249: public void componentShowing() {
250: super .componentShowing();
251: /*BPELDataEditorSupport editor = getDataObject().getEditorSupport();
252: // If the bpel model is valid, discard the edits on the editor
253: // support's undo queue. The idea is to keep our undo/redo model as
254: // simple as can be. Otherwise, the two undo managers would need to
255: // be kept in sync, making the undo/redo code vastly more complicated.
256:
257: BpelModel model = editor.getBpelModel();
258: if (model.getState().equals(Model.State.VALID)) {
259: editor.getUndoManager().discardAllEdits();
260: }*/
261: // addUndoManager();
262: BPELDataEditorSupport editor = getDataObject()
263: .getEditorSupport();
264: editor.addUndoManagerToDocument();
265: }
266:
267: public JComponent getToolbarRepresentation() {
268: Document doc = getEditorPane().getDocument();
269: if (doc instanceof NbDocument.CustomToolbar) {
270: if (myToolBar == null) {
271: myToolBar = ((NbDocument.CustomToolbar) doc)
272: .createToolbar(getEditorPane());
273: }
274: return myToolBar;
275: }
276: return null;
277: }
278:
279: public JComponent getVisualRepresentation() {
280: return this ;
281: }
282:
283: public void setMultiViewCallback(
284: final MultiViewElementCallback callback) {
285: myMultiViewObserver = callback;
286: }
287:
288: public void requestVisible() {
289: if (myMultiViewObserver != null) {
290: myMultiViewObserver.requestVisible();
291: } else {
292: super .requestVisible();
293: }
294: }
295:
296: public void requestActive() {
297: if (myMultiViewObserver != null) {
298: myMultiViewObserver.requestActive();
299: } else {
300: super .requestActive();
301: }
302: }
303:
304: public UndoRedo getUndoRedo() {
305: BPELDataEditorSupport editor = myDataObject.getEditorSupport();
306: return editor.getUndoManager();
307: }
308:
309: /**
310: * The close last method should be called only for the last clone.
311: * If there are still existing clones this method must return false. The
312: * implementation from the FormEditor always returns true but this is
313: * not the expected behavior. The intention is to close the editor support
314: * once the last editor has been closed, using the silent close to avoid
315: * displaying a new dialog which is already being displayed via the
316: * close handler.
317: */
318: protected boolean closeLast() {
319: BPELDataEditorSupport editor = getDataObject()
320: .getEditorSupport();
321: JEditorPane[] editors = editor.getOpenedPanes();
322: if (editors == null || editors.length == 0) {
323: return editor.silentClose();
324: }
325: return false;
326: }
327:
328: protected String preferredID() {
329: return PREFERED_ID;
330: }
331:
332: private BPELDataObject getDataObject() {
333: return myDataObject;
334: }
335:
336: private void initialize() {
337: // create and associate lookup
338: Node delegate = myDataObject.getNodeDelegate();
339: SourceCookieProxyLookup lookup = new SourceCookieProxyLookup(
340: new Lookup[] { Lookups.fixed(new Object[] {
341: // Need ActionMap in lookup so editor actions work.
342: getActionMap(),
343: // Need the data object registered in the lookup so that the
344: // projectui code will close our open editor windows when the
345: // project is closed.
346: myDataObject }), getDataObject().getLookup(), },
347: delegate);
348: associateLookup(lookup);
349: addPropertyChangeListener("activatedNodes", lookup);
350: // associateLookup(new ProxyLookup(new Lookup[] { // # 67257
351: // Lookups.fixed(new Object[] {
352: // getActionMap(), // # 85512
353: // getDataObject(),
354: // getDataObject().getNodeDelegate() }),
355: // getDataObject().getLookup() })); // # 117029
356: }
357:
358: private BPELValidationController getValidationController() {
359: return (BPELValidationController) getDataObject().getLookup()
360: .lookup(BPELValidationController.class);
361: }
362:
363: /**
364: * Adds the undo/redo manager to the document as an undoable
365: * edit listener, so it receives the edits onto the queue.
366: */
367: private void addUndoManager() {
368: BPELDataEditorSupport editor = getDataObject()
369: .getEditorSupport();
370: QuietUndoManager undo = editor.getUndoManager();
371: StyledDocument doc = editor.getDocument();
372: // Unlikely to be null, but could be if the cloned views are not
373: // behaving correctly.
374: if (doc != null) {
375: // Ensure the listener is not added twice.
376: doc.removeUndoableEditListener(undo);
377: doc.addUndoableEditListener(undo);
378: // Start the compound mode of the undo manager, such that when
379: // we are hidden, we will treat all of the edits as a single
380: // compound edit. This avoids having the user invoke undo
381: // numerous times when in the model view.
382: undo.beginCompound();
383: }
384: }
385:
386: /**
387: * Removes the undo/redo manager undoable edit listener from the
388: * document, to stop receiving undoable edits.
389: */
390: private void removeUndoManager() {
391: BPELDataEditorSupport editor = getDataObject()
392: .getEditorSupport();
393: StyledDocument doc = editor.getDocument();
394: // May be null when closing the editor.
395: if (doc != null) {
396: QuietUndoManager undo = editor.getUndoManager();
397: doc.removeUndoableEditListener(undo);
398: undo.endCompound();
399: }
400: }
401:
402: private boolean isLastView() {
403: boolean oneOrLess = true;
404: Enumeration en = ((CloneableTopComponent) myMultiViewObserver
405: .getTopComponent()).getReference().getComponents();
406: if (en.hasMoreElements()) {
407: en.nextElement();
408: if (en.hasMoreElements()) {
409: oneOrLess = false;
410: }
411: }
412:
413: return oneOrLess;
414: }
415:
416: private Action[] getNodeActions() {
417: if (myMultiViewObserver == null) {
418: return null;
419: }
420: Node[] activeNodes = myMultiViewObserver.getTopComponent()
421: .getActivatedNodes();
422: ;
423: if (activeNodes != null && activeNodes.length > 0) {
424: return activeNodes[0].getActions(true);
425: }
426: return null;
427: }
428:
429: private void addCaretPositionListener() {
430: // BPELDataEditorSupport editor = getDataObject().getEditorSupport();
431: // JEditorPane[] editors = editor.getOpenedPanes();
432: // if (editors == null || editors.length == 0) {
433: // return;
434: // }
435: JEditorPane editorPane = getEditorPane();
436:
437: if (myCaretPositionListener != null && editorPane != null) {
438: editorPane.removeCaretListener(myCaretPositionListener);
439: }
440:
441: if (myCaretPositionListener == null) {
442: myCaretPositionListener = new CaretListener() {
443: public void caretUpdate(final CaretEvent e) {
444: selectElement();
445: }
446: };
447: }
448: editorPane.addCaretListener(myCaretPositionListener);
449:
450: BPELDataEditorSupport editorSupport = getDataObject()
451: .getEditorSupport();
452: BpelModel model = editorSupport != null ? editorSupport
453: .getBpelModel() : null;
454: if (model != null) {
455: if (myBpelModelListener != null) {
456: model.removeEntityChangeListener(myBpelModelListener);
457: }
458:
459: if (myBpelModelListener == null) {
460: myBpelModelListener = new ChangeEventListener() {
461:
462: private void handleEvent(ChangeEvent event) {
463: if (event == null) {
464: return;
465: }
466: if (event.isLastInAtomic()) {
467: selectElement(0);
468: }
469: }
470:
471: public void notifyPropertyRemoved(
472: PropertyRemoveEvent event) {
473: handleEvent(event);
474: }
475:
476: public void notifyEntityInserted(
477: EntityInsertEvent event) {
478: handleEvent(event);
479: }
480:
481: public void notifyPropertyUpdated(
482: PropertyUpdateEvent event) {
483: handleEvent(event);
484: }
485:
486: public void notifyEntityRemoved(
487: EntityRemoveEvent event) {
488: handleEvent(event);
489: }
490:
491: public void notifyEntityUpdated(
492: EntityUpdateEvent event) {
493: handleEvent(event);
494: }
495:
496: public void notifyArrayUpdated(
497: ArrayUpdateEvent event) {
498: handleEvent(event);
499: }
500: };
501: }
502:
503: model.addEntityChangeListener(myBpelModelListener);
504: }
505: }
506:
507: private void setCaretAssocActiveNodes() {
508: // BPELDataEditorSupport editor = getDataObject().getEditorSupport();
509: // JEditorPane[] editors = editor.getOpenedPanes();
510: // if (editors == null || editors.length == 0) {
511: // return;
512: // }
513:
514: selectElement();
515: //// JEditorPane editorPane = getEditorPane();
516: //// if (editorPane != null) {
517: //// setActivatedNodes(editorPane.getCaretPosition());
518: //// }
519: }
520:
521: private void setActivatedNodes(final int cursor) {
522: BPELDataEditorSupport editorSupport = getDataObject()
523: .getEditorSupport();
524: if (editorSupport == null) {
525: return;
526: }
527:
528: final BpelModel model = editorSupport.getBpelModel();
529: if (model == null) {
530: return;
531: }
532:
533: SwingUtilities.invokeLater(new Runnable() {
534: public void run() {
535: BpelEntity foundedEntity = model.findElement(cursor);
536: if (foundedEntity == null) {
537: return;
538: }
539:
540: // NodeFactory nodeFactory = (NodeFactory)getDataObject().getLookup().lookup(NodeFactory.class);
541: NodeFactory nodeFactory = FactoryAccess
542: .getPropertyNodeFactory();
543: assert nodeFactory != null;
544:
545: NodeType nodeType = Util
546: .getBasicNodeType(foundedEntity);
547: if (nodeType == null) {
548: return;
549: }
550:
551: nodeType = NodeType.UNKNOWN_TYPE.equals(nodeType) ? NodeType.DEFAULT_BPEL_ENTITY_NODE
552: : nodeType;
553: final Node node = nodeFactory.createNode(nodeType,
554: foundedEntity, getDataObject().getLookup());
555: if (node == null) {
556: return;
557: }
558:
559: // System.out.println("set active node");
560:
561: final TopComponent tc = myMultiViewObserver == null ? null
562: : myMultiViewObserver.getTopComponent();
563: if (tc != null) {
564: //// tc.setActivatedNodes(new Node[] {node});
565: setActivatedNodes(new Node[] { node });
566: }
567: }
568: });
569: // setActivatedNodes(new Node[] {node});
570: // setActivatedNodes(new Node[] {node, getDataObject().getNodeDelegate()});
571: // Node[] tmpNodes = getActivatedNodes();
572: // System.out.println("tmpNodes: "+tmpNodes);
573: }
574:
575: private void removeCaretPositionListener() {
576:
577: JEditorPane editorPane = getEditorPane();
578: if (editorPane != null && myCaretPositionListener != null) {
579: editorPane.removeCaretListener(myCaretPositionListener);
580: }
581: myCaretPositionListener = null;
582:
583: BPELDataEditorSupport editorSupport = getDataObject()
584: .getEditorSupport();
585: BpelModel model = editorSupport != null ? editorSupport
586: .getBpelModel() : null;
587:
588: if (myBpelModelListener != null && model != null) {
589: model.removeEntityChangeListener(myBpelModelListener);
590: }
591:
592: myBpelModelListener = null;
593: }
594:
595: private void selectElement(int delay) {
596: if (myPreviousTask != null) {
597: myPreviousTask.cancel();
598: }
599: if (myPreviousTask != null
600: && !myPreviousTask.isFinished()
601: && RequestProcessor.getDefault()
602: .isRequestProcessorThread()) // issue 125 439
603: {
604: myPreviousTask.waitFinished();
605: myPreviousTask = null;
606: }
607:
608: final JEditorPane curEditorPane = getEditorPane();
609: if (curEditorPane != null && !curEditorPane.isShowing()) {
610: return;
611: }
612:
613: if (delay <= 0) {
614: setActivatedNodes(curEditorPane.getCaret().getDot());
615: } else {
616: myPreviousTask = RequestProcessor.getDefault().post(
617: new Runnable() {
618: public void run() {
619: setActivatedNodes(curEditorPane.getCaret()
620: .getDot());
621: }
622: }, delay);
623: }
624: }
625:
626: private void selectElement() {
627: selectElement(CARET_CHANGE_TASK_DELAY);
628: }
629:
630: private transient MultiViewElementCallback myMultiViewObserver;
631:
632: private BPELDataObject myDataObject;
633:
634: private transient JToolBar myToolBar;
635:
636: private CaretListener myCaretPositionListener;
637:
638: private ChangeEventListener myBpelModelListener;
639:
640: private transient RequestProcessor.Task myPreviousTask;
641: }
|