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;
043:
044: import java.awt.EventQueue;
045: import java.io.ByteArrayOutputStream;
046: import java.io.CharConversionException;
047: import java.io.IOException;
048: import java.io.InputStream;
049: import java.io.InputStreamReader;
050: import java.io.OutputStream;
051: import java.io.OutputStreamWriter;
052: import java.io.Reader;
053: import java.io.Serializable;
054: import java.io.UnsupportedEncodingException;
055: import java.io.Writer;
056: import java.util.ArrayList;
057: import java.util.List;
058: import java.util.Set;
059: import javax.swing.text.AbstractDocument;
060: import javax.swing.text.BadLocationException;
061: import javax.swing.text.Document;
062: import javax.swing.text.EditorKit;
063: import javax.swing.text.StyledDocument;
064: import org.netbeans.core.api.multiview.MultiViewHandler;
065: import org.netbeans.core.api.multiview.MultiViews;
066: import org.netbeans.core.spi.multiview.CloseOperationHandler;
067: import org.netbeans.core.spi.multiview.CloseOperationState;
068: import org.netbeans.modules.xml.api.EncodingUtil;
069: import org.netbeans.modules.xml.axi.AXIModel;
070: import org.netbeans.modules.xml.axi.AXIModelFactory;
071: import org.netbeans.modules.xml.retriever.catalog.Utilities;
072: import org.netbeans.modules.xml.xam.ModelSource;
073: import org.netbeans.modules.xml.schema.multiview.SchemaMultiViewSupport;
074: import org.netbeans.modules.xml.schema.model.SchemaModel;
075: import org.netbeans.modules.xml.schema.model.SchemaModelFactory;
076: import org.netbeans.modules.xml.schema.ui.basic.SchemaModelCookie;
077: import org.netbeans.modules.xml.xam.ui.undo.QuietUndoManager;
078: import org.openide.DialogDisplayer;
079: import org.openide.ErrorManager;
080: import org.openide.NotifyDescriptor;
081: import org.openide.awt.UndoRedo;
082: import org.openide.cookies.CloseCookie;
083: import org.openide.cookies.EditCookie;
084: import org.openide.cookies.EditorCookie;
085: import org.openide.cookies.LineCookie;
086: import org.openide.cookies.OpenCookie;
087: import org.openide.cookies.PrintCookie;
088: import org.openide.cookies.SaveCookie;
089: import org.openide.filesystems.FileLock;
090: import org.openide.filesystems.FileObject;
091: import org.openide.loaders.DataObject;
092: import org.openide.text.CloneableEditor;
093: import org.openide.text.CloneableEditorSupport;
094: import org.openide.text.CloneableEditorSupport.Pane;
095: import org.openide.text.DataEditorSupport;
096: import org.openide.text.NbDocument;
097: import org.openide.util.NbBundle;
098: import org.openide.util.Task;
099: import org.openide.util.TaskListener;
100: import org.openide.util.UserCancelException;
101: import org.openide.windows.Mode;
102: import org.openide.windows.TopComponent;
103: import org.openide.windows.WindowManager;
104:
105: /**
106: * Editor support for the schema data object.
107: *
108: * @author Jeri Lockhart
109: * @author Todd Fast, todd.fast@sun.com
110: * @author Nathan Fiedler
111: */
112: public class SchemaEditorSupport extends DataEditorSupport implements
113: SchemaModelCookie, OpenCookie, EditCookie,
114: EditorCookie.Observable, LineCookie, CloseCookie, PrintCookie {
115: /** Used for managing the prepareTask listener. */
116: private transient Task prepareTask2;
117: /** Ignore the upcoming call to updateTitles() due to changes being
118: * made to the document which cannot otherwise be ignored. */
119: private transient boolean ignoreUpdateTitles;
120: private transient SchemaModel model;
121:
122: /**
123: * Creates a new instance of SchemaEditorSupport.
124: *
125: * @param dobj schema data object to edit.
126: */
127: public SchemaEditorSupport(SchemaDataObject dobj) {
128: super (dobj, new SchemaEditorEnv(dobj));
129: setMIMEType(SchemaDataLoader.MIME_TYPE);
130: }
131:
132: /**
133: *
134: *
135: */
136: public SchemaEditorEnv getEnv() {
137: return (SchemaEditorEnv) env;
138: }
139:
140: protected Pane createPane() {
141: TopComponent tc = SchemaMultiViewSupport
142: .createMultiView((SchemaDataObject) getDataObject());
143: // Note that initialization of the editor happens separately,
144: // and we only need to handle that during the initial creation
145: // of the text editor.
146:
147: Mode editorMode = WindowManager.getDefault().findMode(
148: SchemaEditorSupport.EDITOR_MODE);
149: if (editorMode != null) {
150: editorMode.dockInto(tc);
151: }
152:
153: return (Pane) tc;
154: }
155:
156: // Change method access to public
157: public void initializeCloneableEditor(CloneableEditor editor) {
158: super .initializeCloneableEditor(editor);
159: // Force the title to update so the * left over from when the
160: // modified data object was discarded is removed from the title.
161: EventQueue.invokeLater(new Runnable() {
162: public void run() {
163: // Have to do this later to avoid infinite loop.
164: updateTitles();
165: }
166: });
167: }
168:
169: /**
170: * Request that the editor support ignore the subsequent call to
171: * updateTitles(), which results from a change being made to the
172: * document which should have otherwise been ignored, but could not.
173: *
174: * @param ignore true to ignore event, false to reset.
175: */
176: public void ignoreUpdateTitles(boolean ignore) {
177: ignoreUpdateTitles = ignore;
178: // Without doing this, the change made to the document causes
179: // the notifyModified() to be called, which is fine and we do
180: // not want to stop that, but it calls updateTitles() which
181: // causes the MultiViewPeer to try to construct the MVE, which
182: // is already in progress, so we want to avoid that if possible.
183: }
184:
185: protected void updateTitles() {
186: // This method is invoked by DataEditorSupport.DataNodeListener
187: // whenever the DataNode displayName property is changed. It is
188: // also called when the CloneableEditorSupport is (un)modified.
189: if (ignoreUpdateTitles) {
190: return;
191: }
192:
193: // Let the superclass handle the CloneableEditor instances.
194: super .updateTitles();
195:
196: // We need to get the title updated on the MultiViewTopComponent.
197: EventQueue.invokeLater(new Runnable() {
198: public void run() {
199: // Create a list of TopComponents associated with the
200: // editor's data object, starting with the active
201: // TopComponent. Add all open TopComponents in any
202: // mode that are associated with the DataObject.
203: // [Note that EDITOR_MODE does not contain editors in
204: // split mode.]
205: List<TopComponent> associatedTCs = new ArrayList<TopComponent>();
206: DataObject targetDO = getDataObject();
207: TopComponent activeTC = TopComponent.getRegistry()
208: .getActivated();
209: if (activeTC != null
210: && targetDO == activeTC.getLookup().lookup(
211: DataObject.class)) {
212: associatedTCs.add(activeTC);
213: }
214: Set openTCs = TopComponent.getRegistry().getOpened();
215: for (Object tc : openTCs) {
216: TopComponent tcc = (TopComponent) tc;
217: if (targetDO == tcc.getLookup().lookup(
218: DataObject.class)) {
219: associatedTCs.add(tcc);
220: }
221: }
222: for (TopComponent tc : associatedTCs) {
223: // Make sure this is a multiview window, and not just some
224: // window that has our DataObject (e.g. Projects, Files).
225: MultiViewHandler mvh = MultiViews
226: .findMultiViewHandler(tc);
227: if (mvh != null) {
228: tc.setHtmlDisplayName(messageHtmlName());
229: String name = messageName();
230: tc.setDisplayName(name);
231: tc.setName(name);
232: tc.setToolTipText(messageToolTip());
233: }
234: }
235: }
236: });
237: }
238:
239: @Override
240: protected UndoRedo.Manager createUndoRedoManager() {
241: // Override so the superclass will use our proxy undo manager
242: // instead of the default, then we can intercept edits.
243: return new QuietUndoManager(super .createUndoRedoManager());
244: // Note we cannot set the document on the undo manager right
245: // now, as CES is probably trying to open the document.
246: }
247:
248: /**
249: * Returns the UndoRedo.Manager instance managed by this editor support.
250: *
251: * @return specialized UndoRedo.Manager instance.
252: */
253: public QuietUndoManager getUndoManager() {
254: return (QuietUndoManager) getUndoRedo();
255: }
256:
257: public SchemaModel getModel() throws IOException {
258: if (model != null)
259: return model;
260: SchemaDataObject dobj = getEnv().getSchemaDataObject();
261: FileObject fobj = dobj.getPrimaryFile();
262: ModelSource modelSource = Utilities.getModelSource(fobj, true);
263: boolean validModelSource = modelSource != null
264: && modelSource.getLookup().lookup(Document.class) != null;
265: if (!validModelSource) {
266: throw new IOException(NbBundle.getMessage(
267: SchemaEditorSupport.class,
268: "MSG_UnableToCreateModel"));
269: }
270: model = SchemaModelFactory.getDefault().getModel(modelSource);
271: return model;
272: }
273:
274: /**
275: * Adds the undo/redo manager to the document as an undoable edit
276: * listener, so it receives the edits onto the queue. The manager
277: * will be removed from the model as an undoable edit listener.
278: *
279: * <p>This method may be called repeatedly.</p>
280: */
281: public void addUndoManagerToDocument() {
282: // This method may be called repeatedly.
283: // Stop the undo manager from listening to the model, as it will
284: // be listening to the document now.
285: QuietUndoManager undo = getUndoManager();
286: StyledDocument doc = getDocument();
287: synchronized (undo) {
288: try {
289: SchemaModel model = getModel();
290: if (model != null) {
291: model.removeUndoableEditListener(undo);
292: }
293: // Must unset the model when no longer listening to it.
294: undo.setModel(null);
295: AXIModel aModel = AXIModelFactory.getDefault()
296: .getModel(model);
297: undo.removeWrapperModel(aModel);
298: } catch (IOException ioe) {
299: // Model is gone, but just removing the listener is not
300: // going to matter anyway.
301: }
302: // Document may be null if the cloned views are not behaving correctly.
303: if (doc != null) {
304: // Ensure the listener is not added twice.
305: doc.removeUndoableEditListener(undo);
306: doc.addUndoableEditListener(undo);
307: // Start the compound mode of the undo manager, such that when
308: // we are hidden, we will treat all of the edits as a single
309: // compound edit. This avoids having the user invoke undo
310: // numerous times when in the model view.
311: undo.beginCompound();
312: }
313: }
314: }
315:
316: /**
317: * Removes the undo/redo manager undoable edit listener from the
318: * document, to stop receiving undoable edits. The manager will
319: * be added to the model as an undoable edit listener.
320: *
321: * <p>This method may be called repeatedly.</p>
322: */
323: public void removeUndoManagerFromDocument() {
324: // This method may be called repeatedly.
325: QuietUndoManager undo = getUndoManager();
326: StyledDocument doc = getDocument();
327: synchronized (undo) {
328: // May be null when closing the editor.
329: if (doc != null) {
330: doc.removeUndoableEditListener(undo);
331: undo.endCompound();
332: }
333: // Have the undo manager listen to the model when it is not
334: // listening to the document.
335: addUndoManagerToModel(undo);
336: }
337: }
338:
339: /**
340: * Add the undo/redo manager undoable edit listener to the model.
341: *
342: * <p>Caller should synchronize on the undo manager prior to calling
343: * this method, to avoid thread concurrency issues.</p>
344: *
345: * @param undo the undo manager.
346: */
347: private void addUndoManagerToModel(QuietUndoManager undo) {
348: // This method may be called repeatedly.
349: try {
350: SchemaModel model = getModel();
351: if (model != null) {
352: // Ensure the listener is not added twice.
353: model.removeUndoableEditListener(undo);
354: model.addUndoableEditListener(undo);
355: // Ensure the model is sync'd when undo/redo is invoked,
356: // otherwise the edits are added to the queue and eventually
357: // cause exceptions.
358: undo.setModel(model);
359: }
360: } catch (IOException ioe) {
361: // Model is gone, nothing will work, return immediately.
362: }
363: }
364:
365: /**
366: * Remove the undo/redo manager undoable edit listener from the model.
367: *
368: * <p>Caller should synchronize on the undo manager prior to calling
369: * this method, to avoid thread concurrency issues.</p>
370: *
371: * @param undo the undo manager.
372: */
373: private void removeUndoManagerFromModel(QuietUndoManager undo) {
374: // This method may be called repeatedly.
375: try {
376: SchemaModel model = getModel();
377: if (model != null) {
378: model.removeUndoableEditListener(undo);
379: undo.setModel(null);
380: }
381: } catch (IOException ioe) {
382: // Model is gone, nothing will work, return immediately.
383: }
384: }
385:
386: /**
387: * Remove the undo manager from both the model and document, such that
388: * any changes made to either will not be added to the undo queue. The
389: * caller should invoke <code>resumeUndoRedo()</code> once the changes
390: * are completed.
391: *
392: * @return a value that must be passed to <code>resumeUndoRedo()</code>.
393: */
394: public boolean suspendUndoRedo() {
395: QuietUndoManager undo = getUndoManager();
396: boolean compound;
397: synchronized (undo) {
398: compound = undo.isCompound();
399: if (compound) {
400: removeUndoManagerFromDocument();
401: }
402: removeUndoManagerFromModel(undo);
403: }
404: return compound;
405: }
406:
407: /**
408: * Add the undo manager as an undoable edit listener to either the
409: * Swing document or the XAM model, and set up the compound mode if
410: * that was in place previously.
411: *
412: * @param value value returned from <code>suspendUndoRedo()</code>
413: */
414: public void resumeUndoRedo(boolean value) {
415: if (value) {
416: addUndoManagerToDocument();
417: } else {
418: QuietUndoManager undo = getUndoManager();
419: synchronized (undo) {
420: addUndoManagerToModel(undo);
421: }
422: }
423: }
424:
425: protected void loadFromStreamToKit(StyledDocument doc,
426: InputStream in, EditorKit kit) throws IOException,
427: BadLocationException {
428: // Detect the encoding to get optimized reader if UTF-8.
429: String enc = EncodingUtil.detectEncoding(in);
430: if (enc == null) {
431: enc = "UTF8"; // NOI18N
432: }
433: try {
434: Reader reader = new InputStreamReader(in, enc);
435: kit.read(reader, doc, 0);
436: } catch (CharConversionException cce) {
437: } catch (UnsupportedEncodingException uee) {
438: }
439: }
440:
441: protected void saveFromKitToStream(StyledDocument doc,
442: EditorKit kit, OutputStream out) throws IOException,
443: BadLocationException {
444: // Detect the encoding, using UTF8 if the encoding is not set.
445: String enc = EncodingUtil.detectEncoding(doc);
446: if (enc == null) {
447: enc = "UTF8"; // NOI18N
448: }
449: try {
450: // Test the encoding on a dummy stream.
451: new OutputStreamWriter(new ByteArrayOutputStream(1), enc);
452: // If that worked, we can go ahead with the encoding.
453: Writer writer = new OutputStreamWriter(out, enc);
454: kit.write(writer, doc, 0, doc.getLength());
455: } catch (UnsupportedEncodingException uee) {
456: // Safest option is to write nothing, preserving the original file.
457: IOException ioex = new IOException("Unsupported encoding "
458: + enc); // NOI18N
459: ErrorManager
460: .getDefault()
461: .annotate(
462: ioex,
463: NbBundle
464: .getMessage(
465: SchemaEditorSupport.class,
466: "MSG_SchemaEditorSupport_Unsupported_Encoding",
467: enc));
468: throw ioex;
469: }
470: }
471:
472: /**
473: * This method allows the close behavior of CloneableEditorSupport to be
474: * invoked from the SourceMultiViewElement. The close method of
475: * CloneableEditorSupport at least clears the undo queue and releases
476: * the swing document.
477: */
478: public boolean silentClose() {
479: return super .close(false);
480: }
481:
482: public void saveDocument() throws IOException {
483: final StyledDocument doc = getDocument();
484: // Save document using encoding declared in XML prolog if possible,
485: // otherwise use UTF-8 (in such case it updates the prolog).
486: String enc = EncodingUtil.detectEncoding(doc);
487: if (enc == null) {
488: enc = "UTF8"; // NOI18N
489: }
490: try {
491: // Test the encoding on a dummy stream.
492: new OutputStreamWriter(new ByteArrayOutputStream(1), enc);
493: if (!checkCharsetConversion(EncodingUtil
494: .getJava2IANAMapping(enc))) {
495: return;
496: }
497: super .saveDocument();
498: getDataObject().setModified(false);
499:
500: } catch (UnsupportedEncodingException uee) {
501: NotifyDescriptor descriptor = new NotifyDescriptor.Confirmation(
502: java.text.MessageFormat.format(NbBundle.getMessage(
503: SchemaEditorSupport.class,
504: "MSG_SchemaEditorSupport_Use_UTF8"),
505: new Object[] { enc }));
506: Object res = DialogDisplayer.getDefault()
507: .notify(descriptor);
508:
509: if (res.equals(NotifyDescriptor.YES_OPTION)) {
510: updateDocumentWithNewEncoding(doc);
511: } else {
512: throw new UserCancelException();
513: }
514: }
515: }
516:
517: /**
518: * update prolog to new valid encoding
519: */
520: private void updateDocumentWithNewEncoding(final StyledDocument doc)
521: throws IOException {
522: try {
523: final int MAX_PROLOG = 1000;
524: int maxPrologLen = Math.min(MAX_PROLOG, doc.getLength());
525: final char prolog[] = doc.getText(0, maxPrologLen)
526: .toCharArray();
527: int prologLen = 0;
528: if (prolog[0] == '<' && prolog[1] == '?'
529: && prolog[2] == 'x') {
530: for (int i = 3; i < maxPrologLen; i++) {
531: if (prolog[i] == '?' && prolog[i + 1] == '>') {
532: prologLen = i + 1;
533: break;
534: }
535: }
536: }
537:
538: final int passPrologLen = prologLen;
539: Runnable edit = new Runnable() {
540: public void run() {
541: try {
542: doc.remove(0, passPrologLen + 1);
543: doc.insertString(0,
544: "<?xml version='1.0' encoding='UTF-8' ?>\n<!-- was: "
545: + new String(prolog, 0,
546: passPrologLen + 1)
547: + " -->", null); // NOI18N
548: } catch (BadLocationException ble) {
549: if (System
550: .getProperty("netbeans.debug.exceptions") != null) // NOI18N
551: ble.printStackTrace();
552: }
553: }
554: };
555: NbDocument.runAtomic(doc, edit);
556:
557: super .saveDocument();
558: getDataObject().setModified(false);
559:
560: } catch (BadLocationException lex) {
561: ErrorManager.getDefault().notify(lex);
562: }
563: }
564:
565: /**
566: * Validate the selected encoding to determine if it is usuable or not.
567: * If there is a problem, prompt the user to confirm the encoding.
568: *
569: * @param encoding the character set encoding to validate.
570: * @return true if encoding can be used, false otherwise.
571: */
572: private boolean checkCharsetConversion(String encoding) {
573: boolean value = true;
574: try {
575: java.nio.charset.CharsetEncoder coder = java.nio.charset.Charset
576: .forName(encoding).newEncoder();
577: if (!coder.canEncode(getDocument().getText(0,
578: getDocument().getLength()))) {
579: Object[] margs = new Object[] {
580: getDataObject().getPrimaryFile().getNameExt(),
581: encoding };
582: String msg = NbBundle.getMessage(
583: SchemaEditorSupport.class,
584: "MSG_SchemaEditorSupport_BadCharConversion",
585: margs);
586: NotifyDescriptor nd = new NotifyDescriptor.Confirmation(
587: msg, NotifyDescriptor.YES_NO_OPTION,
588: NotifyDescriptor.WARNING_MESSAGE);
589: nd.setValue(NotifyDescriptor.NO_OPTION);
590: DialogDisplayer.getDefault().notify(nd);
591: if (nd.getValue() != NotifyDescriptor.YES_OPTION) {
592: value = false;
593: }
594: }
595: } catch (BadLocationException ble) {
596: ErrorManager.getDefault().notify(
597: ErrorManager.INFORMATIONAL, ble);
598: }
599: return value;
600: }
601:
602: /**
603: * Have the schema model sync with the document.
604: */
605: public void syncModel() {
606: try {
607: SchemaModel model = getModel();
608: if (model != null) {
609: model.sync();
610: }
611: } catch (IOException ioe) {
612: // The document cannot be parsed, ignore this error.
613: }
614: }
615:
616: public Task prepareDocument() {
617: Task task = super .prepareDocument();
618: // Avoid listening to the same task more than once.
619: if (task != prepareTask2) {
620: prepareTask2 = task;
621: task.addTaskListener(new TaskListener() {
622: public void taskFinished(Task task) {
623: QuietUndoManager undo = getUndoManager();
624: StyledDocument doc = getDocument();
625: if (doc == null)
626: return;
627: synchronized (undo) {
628: // Now that the document is ready, pass it to the manager.
629: undo.setDocument((AbstractDocument) doc);
630: if (!undo.isCompound()) {
631: // The superclass prepareDocument() adds the undo/redo
632: // manager as a listener -- we need to remove it since
633: // we will initially listen to the model instead.
634: doc.removeUndoableEditListener(undo);
635: // If not listening to document, then listen to model.
636: addUndoManagerToModel(undo);
637: }
638: }
639: prepareTask2 = null;
640: }
641: });
642: }
643: return task;
644: }
645:
646: public Task reloadDocument() {
647: Task task = super .reloadDocument();
648: task.addTaskListener(new TaskListener() {
649: public void taskFinished(Task task) {
650: EventQueue.invokeLater(new Runnable() {
651: public void run() {
652: QuietUndoManager undo = getUndoManager();
653: StyledDocument doc = getDocument();
654: // The superclass reloadDocument() adds the undo
655: // manager as an undoable edit listener.
656: synchronized (undo) {
657: if (!undo.isCompound()) {
658: doc.removeUndoableEditListener(undo);
659: }
660: }
661: }
662: });
663: }
664: });
665: return task;
666: }
667:
668: protected void notifyClosed() {
669: // Stop listening to the undoable edit sources when we are closed.
670: QuietUndoManager undo = getUndoManager();
671: StyledDocument doc = getDocument();
672: synchronized (undo) {
673: // May be null when closing the editor.
674: if (doc != null) {
675: doc.removeUndoableEditListener(undo);
676: undo.endCompound();
677: undo.setDocument(null);
678: }
679: try {
680: SchemaModel model = getModel();
681: if (model != null) {
682: model.removeUndoableEditListener(undo);
683: AXIModel aModel = AXIModelFactory.getDefault()
684: .getModel(model);
685: undo.removeWrapperModel(aModel);
686: }
687: // Must unset the model when no longer listening to it.
688: undo.setModel(null);
689: } catch (IOException ioe) {
690: // Model is gone, but just removing the listener is not
691: // going to matter anyway.
692: }
693: }
694: this .model = null;
695: super .notifyClosed();
696: }
697:
698: /*
699: * Update presence of SaveCookie on first keystroke.
700: */
701: protected boolean notifyModified() {
702: boolean notify = super .notifyModified();
703: if (!notify) {
704: return false;
705: }
706: SchemaDataObject obj = (SchemaDataObject) getDataObject();
707: if (obj.getCookie(SaveCookie.class) == null) {
708: obj.addSaveCookie(new SaveCookie() {
709: public void save() throws java.io.IOException {
710: try {
711: saveDocument();
712: } catch (UserCancelException e) {
713: //just ignore
714: }
715: }
716: });
717: }
718: return true;
719: }
720:
721: /**
722: * Env class extends SchemaEditorSupport.Env.
723: * overrides findSchemaEditorSupport
724: *
725: */
726: protected static class SchemaEditorEnv extends
727: DataEditorSupport.Env {
728:
729: static final long serialVersionUID = 1099957785497677206L;
730:
731: public SchemaEditorEnv(SchemaDataObject obj) {
732: super (obj);
733: }
734:
735: public CloneableEditorSupport findTextEditorSupport() {
736: return getSchemaDataObject().getSchemaEditorSupport();
737: }
738:
739: public SchemaDataObject getSchemaDataObject() {
740: return (SchemaDataObject) getDataObject();
741: }
742:
743: protected FileObject getFile() {
744: return getDataObject().getPrimaryFile();
745: }
746:
747: protected FileLock takeLock() throws IOException {
748: return getDataObject().getPrimaryFile().lock();
749: }
750: }
751:
752: /**
753: * Implementation of CloseOperationHandler for multiview. Ensures the
754: * editors correctly closed, data object is saved, etc. Holds a
755: * reference to DataObject only - to be serializable with the multiview
756: * TopComponent without problems.
757: */
758: public static class CloseHandler implements CloseOperationHandler,
759: Serializable {
760: private static final long serialVersionUID = -3838395157610633251L;
761: private DataObject dataObject;
762:
763: private CloseHandler() {
764: super ();
765: }
766:
767: public CloseHandler(DataObject schemaDO) {
768: dataObject = schemaDO;
769: }
770:
771: private SchemaEditorSupport getSchemaEditorSupport() {
772: return dataObject == null ? null : dataObject
773: .getCookie(SchemaEditorSupport.class);
774: }
775:
776: public boolean resolveCloseOperation(
777: CloseOperationState[] elements) {
778: SchemaEditorSupport schemaEditor = getSchemaEditorSupport();
779: boolean canClose = schemaEditor != null ? schemaEditor
780: .canClose() : true;
781: // during the shutdown sequence this is called twice. The first time
782: // through the multi-view infrastructure. The second time is done through
783: // the TopComponent close. If the file is dirty and the user chooses
784: // to discard changes, the second time will also ask whether the
785: // to save or discard changes.
786: if (canClose) {
787: dataObject.setModified(false);
788: }
789: return canClose;
790: }
791: }
792: }
|