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.schema.multiview;
043:
044: import java.awt.event.ActionEvent;
045: import java.awt.event.ActionListener;
046: import java.awt.EventQueue;
047: import java.io.IOException;
048: import java.io.ObjectInput;
049: import java.io.ObjectOutput;
050: import java.util.List;
051: import javax.swing.JComponent;
052: import javax.swing.JEditorPane;
053: import javax.swing.Timer;
054: import javax.swing.event.CaretEvent;
055: import javax.swing.event.CaretListener;
056: import javax.swing.text.Document;
057: import javax.swing.text.StyledDocument;
058: import org.netbeans.core.spi.multiview.CloseOperationState;
059: import org.netbeans.core.spi.multiview.MultiViewElement;
060: import org.netbeans.core.spi.multiview.MultiViewElementCallback;
061: import org.netbeans.core.spi.multiview.MultiViewFactory;
062: import org.netbeans.modules.xml.schema.SchemaDataObject;
063: import org.netbeans.modules.xml.schema.SchemaEditorSupport;
064: import org.netbeans.modules.xml.schema.model.SchemaComponent;
065: import org.netbeans.modules.xml.schema.model.SchemaModel;
066: import org.netbeans.modules.xml.schema.ui.basic.UIUtilities;
067: import org.netbeans.modules.xml.schema.ui.nodes.StructuralSchemaNodeFactory;
068: import org.netbeans.modules.xml.validation.ShowCookie;
069: import org.netbeans.modules.xml.xam.Component;
070: import org.netbeans.modules.xml.xam.spi.Validator.ResultItem;
071: import org.openide.awt.UndoRedo;
072: import org.openide.nodes.Node;
073: import org.openide.nodes.NodeAdapter;
074: import org.openide.nodes.NodeEvent;
075: import org.openide.text.CloneableEditor;
076: import org.openide.text.NbDocument;
077: import org.openide.util.Lookup;
078: import org.openide.util.RequestProcessor;
079: import org.openide.util.lookup.Lookups;
080:
081: /**
082: * The source editor for schema documents.
083: *
084: * @author Jeri Lockhart
085: * @author Nathan Fiedler
086: */
087: public class SchemaSourceMultiViewElement extends CloneableEditor
088: implements MultiViewElement {
089: private static final long serialVersionUID = 4403502726950453345L;
090: private transient JComponent toolbar;
091: private transient MultiViewElementCallback multiViewCallback;
092: private SchemaDataObject schemaDataObject;
093:
094: /**
095: * Constructs a new instance of SchemaSourceMultiViewElement.
096: */
097: public SchemaSourceMultiViewElement() {
098: // Needed for deserialization, do not remove.
099: super ();
100: }
101:
102: /**
103: * Constructs a new instance of SchemaSourceMultiViewElement.
104: *
105: * @param dobj schema data object being edited.
106: */
107: public SchemaSourceMultiViewElement(SchemaDataObject dobj) {
108: super (dobj.getSchemaEditorSupport());
109: this .schemaDataObject = dobj;
110:
111: // Initialize the editor support properly, which only needs to be
112: // done when the editor is created (deserialization is working
113: // due to CloneableEditor.readResolve() initializing the editor).
114: // Note that this relies on the source view being the first in the
115: // array of MultiViewDescription instances in SchemaMultiViewSupport,
116: // since that results in the source view being created and opened
117: // by default, only to be hidden when the DataObject default action
118: // makes the columns view appear.
119: // This initialization fixes CR 6380287 by ensuring that the Node
120: // listener is registered with the DataObject Node delegate.
121: dobj.getSchemaEditorSupport().initializeCloneableEditor(this );
122: initialize();
123: }
124:
125: /**
126: * create lookup, caretlistener, timer
127: */
128: private void initialize() {
129: ShowCookie showCookie = new ShowCookie() {
130: public void show(ResultItem resultItem) {
131: if (isActiveTC()) {
132: try {
133: int position = 0;
134: Component component = resultItem
135: .getComponents();
136: if (component instanceof SchemaComponent) {
137: position = ((SchemaComponent) component)
138: .findPosition();
139: getEditorPane().setCaretPosition(position);
140: return;
141: }
142: int line = resultItem.getLineNumber();
143: position = NbDocument.findLineOffset(
144: (StyledDocument) getEditorPane()
145: .getDocument(), line);
146: getEditorPane().setCaretPosition(position);
147: } catch (Exception ex) {
148: getEditorPane().setCaretPosition(0);
149: //worst case, let open the document in editor
150: //do not throw one exception.
151: }
152: }
153: }
154: };
155:
156: // create and associate lookup
157: Node delegate = schemaDataObject.getNodeDelegate();
158: SourceCookieProxyLookup lookup = new SourceCookieProxyLookup(
159: new Lookup[] { Lookups.fixed(new Object[] {
160: // Need ActionMap in lookup so editor actions work.
161: getActionMap(),
162: // Need the data object registered in the lookup so that the
163: // projectui code will close our open editor windows when the
164: // project is closed.
165: schemaDataObject,
166: // The Show Cookie in lookup to show schema component
167: showCookie, }), }, delegate);
168: associateLookup(lookup);
169: addPropertyChangeListener("activatedNodes", lookup);
170:
171: caretListener = new CaretListener() {
172: public void caretUpdate(CaretEvent e) {
173: timerSelNodes.restart();
174: }
175: };
176:
177: timerSelNodes = new Timer(1, new ActionListener() {
178: public void actionPerformed(ActionEvent e) {
179: if (!isActiveTC() || getEditorPane() == null) {
180: return;
181: }
182: selectElementsAtOffset();
183: }
184: });
185: timerSelNodes.setRepeats(false);
186: }
187:
188: public JComponent getToolbarRepresentation() {
189: Document doc = getEditorPane().getDocument();
190: if (doc instanceof NbDocument.CustomToolbar) {
191: if (toolbar == null) {
192: toolbar = ((NbDocument.CustomToolbar) doc)
193: .createToolbar(getEditorPane());
194: }
195: return toolbar;
196: }
197: return null;
198: }
199:
200: public JComponent getVisualRepresentation() {
201: return this ;
202: }
203:
204: public void setMultiViewCallback(
205: final MultiViewElementCallback callback) {
206: multiViewCallback = callback;
207: }
208:
209: public void requestVisible() {
210: if (multiViewCallback != null)
211: multiViewCallback.requestVisible();
212: else
213: super .requestVisible();
214: }
215:
216: public void requestActive() {
217: if (multiViewCallback != null)
218: multiViewCallback.requestActive();
219: else
220: super .requestActive();
221: }
222:
223: protected String preferredID() {
224: return getClass().getName();
225: }
226:
227: public UndoRedo getUndoRedo() {
228: return schemaDataObject.getSchemaEditorSupport()
229: .getUndoManager();
230: }
231:
232: /**
233: * The close last method should be called only for the last clone.
234: * If there are still existing clones this method must return false. The
235: * implementation from the FormEditor always returns true but this is
236: * not the expected behavior. The intention is to close the editor support
237: * once the last editor has been closed, using the silent close to avoid
238: * displaying a new dialog which is already being displayed via the
239: * close handler.
240: */
241: protected boolean closeLast() {
242: SchemaEditorSupport ses = schemaDataObject
243: .getSchemaEditorSupport();
244: JEditorPane[] editors = ses.getOpenedPanes();
245: if (editors == null || editors.length == 0) {
246: return ses.silentClose();
247: }
248: return false;
249: }
250:
251: public CloseOperationState canCloseElement() {
252: // if this is not the last cloned xml editor component, closing is OK
253: if (!SchemaMultiViewSupport.isLastView(multiViewCallback
254: .getTopComponent())) {
255: return CloseOperationState.STATE_OK;
256: }
257: // return a placeholder state - to be sure our CloseHandler is called
258: return MultiViewFactory.createUnsafeCloseState(
259: "ID_TEXT_CLOSING", // dummy ID // NOI18N
260: MultiViewFactory.NOOP_CLOSE_ACTION,
261: MultiViewFactory.NOOP_CLOSE_ACTION);
262: }
263:
264: public void componentActivated() {
265: JEditorPane p = getEditorPane();
266: if (p != null) {
267: p.addCaretListener(caretListener);
268: }
269: if (timerSelNodes != null) {
270: timerSelNodes.restart();
271: }
272: super .componentActivated();
273: SchemaEditorSupport editor = schemaDataObject
274: .getSchemaEditorSupport();
275: editor.addUndoManagerToDocument();
276: }
277:
278: public void componentDeactivated() {
279: // Note: componentDeactivated() is called when the entire
280: // MultiViewTopComponent is deactivated, _not_ when switching
281: // between the multiview elements.
282: JEditorPane p = getEditorPane();
283: if (p != null) {
284: p.removeCaretListener(caretListener);
285: }
286: synchronized (this ) {
287: if (selectionTask != null) {
288: selectionTask.cancel();
289: selectionTask = null;
290: }
291: }
292: if (timerSelNodes != null) {
293: timerSelNodes.stop();
294: }
295: super .componentDeactivated();
296: SchemaEditorSupport editor = schemaDataObject
297: .getSchemaEditorSupport();
298: // Sync model before having undo manager listen to the model,
299: // lest we get redundant undoable edits added to the queue.
300: editor.syncModel();
301: editor.removeUndoManagerFromDocument();
302: }
303:
304: public void componentOpened() {
305: super .componentOpened();
306: }
307:
308: public void componentClosed() {
309: super .componentClosed();
310: }
311:
312: public void componentShowing() {
313: super .componentShowing();
314: SchemaEditorSupport editor = schemaDataObject
315: .getSchemaEditorSupport();
316: editor.addUndoManagerToDocument();
317: }
318:
319: public void componentHidden() {
320: super .componentHidden();
321: SchemaEditorSupport editor = schemaDataObject
322: .getSchemaEditorSupport();
323: // Sync model before having undo manager listen to the model,
324: // lest we get redundant undoable edits added to the queue.
325: editor.syncModel();
326: editor.removeUndoManagerFromDocument();
327: }
328:
329: public void writeExternal(ObjectOutput out) throws IOException {
330: // The superclass persists things such as the caret position.
331: super .writeExternal(out);
332: out.writeObject(schemaDataObject);
333: }
334:
335: public void readExternal(ObjectInput in) throws IOException,
336: ClassNotFoundException {
337: super .readExternal(in);
338: // Since we are persistent and not created by the descriptor when
339: // deserialized, we need to retrieve the data object for ourselves.
340: Object firstObject = in.readObject();
341: if (firstObject instanceof SchemaDataObject) {
342: schemaDataObject = (SchemaDataObject) firstObject;
343: }
344: initialize();
345: }
346:
347: // node support
348: /** Root node of schema model */
349: private Node rootNode;
350: /** current selection*/
351: private Node selectedNode;
352: /** listens to selected node destroyed event */
353: private NodeAdapter nl;
354: /** Timer which countdowns the "update selected element node" time. */
355: private Timer timerSelNodes;
356: /** Listener on caret movements */
357: private CaretListener caretListener;
358: /* task */
359: private transient RequestProcessor.Task selectionTask = null;
360:
361: /** Selects element at the caret position. */
362: void selectElementsAtOffset() {
363: if (selectionTask != null) {
364: selectionTask.cancel();
365: selectionTask = null;
366: }
367: RequestProcessor rp = new RequestProcessor(
368: "schema source view processor " + hashCode());
369: selectionTask = rp.create(new Runnable() {
370: public void run() {
371: if (!isActiveTC() || schemaDataObject == null
372: || !schemaDataObject.isValid()
373: || schemaDataObject.isTemplate()) {
374: return;
375: }
376: Node n = findNode(getEditorPane().getCaret().getDot());
377: // default to node delegate if node not found
378: if (n == null) {
379: setActivatedNodes(new Node[] { schemaDataObject
380: .getNodeDelegate() });
381: } else {
382: if (selectedNode != n) {
383: if (nl == null) {
384: nl = new NodeAdapter() {
385: public void nodeDestroyed(NodeEvent ev) {
386: if (ev.getNode() == selectedNode) {
387: selectElementsAtOffset();
388: }
389: }
390: };
391: } else if (selectedNode != null) {
392: selectedNode.removeNodeListener(nl);
393: }
394: selectedNode = n;
395: selectedNode.addNodeListener(nl);
396: setActivatedNodes(new Node[] { selectedNode });
397: }
398: }
399: }
400: });
401: if (EventQueue.isDispatchThread()) {
402: selectionTask.run();
403: } else {
404: EventQueue.invokeLater(selectionTask);
405: }
406: }
407:
408: private Node findNode(int offset) {
409: try {
410: SchemaEditorSupport support = schemaDataObject
411: .getSchemaEditorSupport();
412: if (support == null)
413: return null;
414: SchemaModel model = support.getModel();
415: if (model == null
416: || model.getState() != SchemaModel.State.VALID)
417: return null;
418: if (rootNode == null)
419: rootNode = new StructuralSchemaNodeFactory(support
420: .getModel(), schemaDataObject.getNodeDelegate()
421: .getLookup()).createRootNode();
422: if (rootNode == null)
423: return null;
424: SchemaComponent sc = (SchemaComponent) support.getModel()
425: .findComponent(offset);
426: if (sc == null)
427: return null;
428: List<Node> path = UIUtilities
429: .findPathFromRoot(rootNode, sc);
430: if (!path.isEmpty())
431: return path.get(path.size() - 1);
432: } catch (IOException ioe) {
433: }
434: return null;
435: }
436:
437: protected boolean isActiveTC() {
438: return getRegistry().getActivated() == multiViewCallback
439: .getTopComponent();
440: }
441:
442: }
|