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: package org.netbeans.modules.xml.schema.multiview;
042:
043: import java.awt.BorderLayout;
044: import java.awt.Color;
045: import java.awt.EventQueue;
046: import java.awt.event.ActionEvent;
047: import java.beans.PropertyChangeEvent;
048: import java.beans.PropertyChangeListener;
049: import java.io.IOException;
050: import java.util.logging.Level;
051: import java.util.logging.Logger;
052: import javax.swing.AbstractAction;
053: import javax.swing.Action;
054: import javax.swing.ActionMap;
055: import javax.swing.InputMap;
056: import javax.swing.JButton;
057: import javax.swing.JComponent;
058: import javax.swing.JToolBar;
059: import javax.swing.KeyStroke;
060: import javax.swing.SwingUtilities;
061: import javax.swing.UIManager;
062: import javax.swing.text.BadLocationException;
063: import javax.swing.text.DefaultEditorKit;
064: import javax.swing.text.StyledDocument;
065: import org.netbeans.core.spi.multiview.CloseOperationState;
066: import org.netbeans.core.spi.multiview.MultiViewElement;
067: import org.netbeans.core.spi.multiview.MultiViewElementCallback;
068: import org.netbeans.core.spi.multiview.MultiViewFactory;
069: import org.netbeans.modules.xml.axi.AXIModel;
070: import org.netbeans.modules.xml.axi.AXIModelFactory;
071: import org.netbeans.modules.xml.schema.SchemaDataObject;
072: import org.netbeans.modules.xml.schema.SchemaEditorSupport;
073: import org.netbeans.modules.xml.schema.model.SchemaComponent;
074: import org.netbeans.modules.xml.schema.model.SchemaModel;
075: import org.netbeans.modules.xml.schema.ui.basic.SchemaColumnsCategory;
076: import org.netbeans.modules.xml.schema.ui.basic.SchemaSettings;
077: import org.netbeans.modules.xml.schema.ui.basic.SchemaSettings.ViewMode;
078: import org.netbeans.modules.xml.schema.ui.basic.SchemaTreeCategory;
079: import org.netbeans.modules.xml.validation.ShowCookie;
080: import org.netbeans.modules.xml.validation.ValidateAction;
081: import org.netbeans.modules.xml.xam.Component;
082: import org.netbeans.modules.xml.xam.Model.State;
083: import org.netbeans.modules.xml.xam.spi.Validator.ResultItem;
084: import org.netbeans.modules.xml.xam.ui.category.Category;
085: import org.netbeans.modules.xml.xam.ui.category.CategoryPane;
086: import org.netbeans.modules.xml.xam.ui.category.DefaultCategoryPane;
087: import org.netbeans.modules.xml.xam.ui.search.SearchManager;
088: import org.netbeans.modules.xml.xam.ui.multiview.ActivatedNodesMediator;
089: import org.netbeans.modules.xml.xam.ui.multiview.CookieProxyLookup;
090: import org.netbeans.modules.xml.xam.ui.undo.QuietUndoManager;
091: import org.openide.ErrorManager;
092: import org.openide.actions.FindAction;
093: import org.openide.explorer.ExplorerManager;
094: import org.openide.util.HelpCtx;
095: import org.openide.util.NbBundle;
096: import org.openide.windows.TopComponent;
097: import org.openide.awt.UndoRedo;
098: import org.openide.explorer.ExplorerUtils;
099: import org.openide.nodes.Node;
100: import org.openide.util.Lookup;
101: import org.openide.util.WeakListeners;
102: import org.openide.util.actions.CallbackSystemAction;
103: import org.openide.util.actions.SystemAction;
104: import org.openide.util.lookup.Lookups;
105:
106: /**
107: *
108: * @author Jeri Lockhart
109: * @author Todd Fast, todd.fast@sun.com
110: * @author Nathan Fiedler
111: */
112: public class SchemaColumnViewMultiViewElement extends TopComponent
113: implements MultiViewElement, PropertyChangeListener,
114: ExplorerManager.Provider {
115: /** silence compiler warnings */
116: private static final long serialVersionUID = 1L;
117: /** Used in recovery from malformed model. */
118: private static final String EMPTY_DOC = "<schema xmlns=\"http://www.w3.org/2001/XMLSchema\"/>";
119: private SchemaDataObject schemaDataObject;
120: private SchemaModel schemaModel;
121: private String errorMessage;
122: private transient MultiViewElementCallback multiViewCallback;
123: private CategoryPane categoryPane;
124: private transient JToolBar toolbar;
125: private transient javax.swing.JLabel errorLabel = new javax.swing.JLabel();
126: private ExplorerManager manager;
127:
128: /**
129: * Nullary constructor for deserialization.
130: */
131: public SchemaColumnViewMultiViewElement() {
132: }
133:
134: public SchemaColumnViewMultiViewElement(
135: SchemaDataObject schemaDataObject) {
136: super ();
137: this .schemaDataObject = schemaDataObject;
138: try {
139: initialize();
140: } catch (IOException ex) {
141: ErrorManager.getDefault().log(
142: ErrorManager.ERROR,
143: NbBundle.getMessage(
144: SchemaColumnViewMultiViewElement.class,
145: "LBL_ColView_not_created"));
146: }
147: }
148:
149: private void initialize() throws IOException {
150: SchemaEditorSupport editor = schemaDataObject
151: .getSchemaEditorSupport();
152: manager = new ExplorerManager();
153: // Install our own actions.
154: CallbackSystemAction globalFindAction = (CallbackSystemAction) SystemAction
155: .get(FindAction.class);
156: Object mapKey = globalFindAction.getActionMapKey();
157: ActionMap map = getActionMap();
158: map.put(mapKey, new ColumnViewFindAction());
159: map.put(DefaultEditorKit.copyAction, ExplorerUtils
160: .actionCopy(manager));
161: map.put(DefaultEditorKit.cutAction, ExplorerUtils
162: .actionCut(manager));
163: map.put(DefaultEditorKit.pasteAction, ExplorerUtils
164: .actionPaste(manager));
165: map.put("delete", ExplorerUtils.actionDelete(manager, false));
166:
167: // Define the keyboard shortcuts for the actions.
168: InputMap keys = getInputMap(JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
169: KeyStroke key = (KeyStroke) globalFindAction
170: .getValue(Action.ACCELERATOR_KEY);
171: if (key == null) {
172: key = KeyStroke.getKeyStroke("control F");
173: }
174: keys.put(key, mapKey);
175:
176: ShowCookie showCookie = new ShowCookie() {
177:
178: public void show(ResultItem resultItem) {
179: final Component component = resultItem.getComponents();
180: if (categoryPane != null
181: && component instanceof SchemaComponent) {
182: EventQueue.invokeLater(new Runnable() {
183: public void run() {
184: categoryPane.getCategory().showComponent(
185: (SchemaComponent) component);
186: }
187: });
188: }
189: }
190: };
191: Node delegate = schemaDataObject.getNodeDelegate();
192: ActivatedNodesMediator nodesMediator = new ActivatedNodesMediator(
193: delegate);
194: nodesMediator.setExplorerManager(this );
195: CookieProxyLookup cpl = new CookieProxyLookup(new Lookup[] {
196: Lookups.fixed(new Object[] {
197: // Need ActionMap in lookup so our actions are used.
198: map,
199: // Need the data object registered in the lookup so that the
200: // projectui code will close our open editor windows when the
201: // project is closed.
202: schemaDataObject,
203: // The Show Cookie in lookup to show schema component
204: showCookie, }), nodesMediator.getLookup(),
205: // The Node delegate Lookup must be the last one in the list
206: // for the CookieProxyLookup to work properly.
207: delegate.getLookup(), }, delegate);
208: associateLookup(cpl);
209: addPropertyChangeListener("activatedNodes", nodesMediator);
210: addPropertyChangeListener("activatedNodes", cpl);
211: setLayout(new BorderLayout());
212: // initUI(true);
213: }
214:
215: public ExplorerManager getExplorerManager() {
216: return manager;
217: }
218:
219: public void propertyChange(PropertyChangeEvent evt) {
220: String property = evt.getPropertyName();
221: if (!SchemaModel.STATE_PROPERTY.equals(property)) {
222: return;
223: }
224: State newState = (State) evt.getNewValue();
225: if (newState == SchemaModel.State.VALID) {
226: errorMessage = null;
227: recreateUI();
228: return;
229: }
230:
231: //model is broken
232: if (errorMessage == null) {
233: errorMessage = NbBundle.getMessage(
234: SchemaColumnViewMultiViewElement.class,
235: "MSG_NotWellformedSchema");
236: }
237: //fix for IZ:116057
238: SwingUtilities.invokeLater(new Runnable() {
239: public void run() {
240: setActivatedNodes(new Node[] { schemaDataObject
241: .getNodeDelegate() });
242: }
243: });
244: emptyUI(errorMessage);
245: }
246:
247: public SchemaDataObject getSchemaDataObject() {
248: return schemaDataObject;
249: }
250:
251: private SchemaModel getSchemaModel() {
252: try {
253: if (schemaModel != null)
254: return schemaModel;
255:
256: SchemaEditorSupport editor = getSchemaDataObject()
257: .getSchemaEditorSupport();
258: schemaModel = editor.getModel();
259: if (schemaModel != null) {
260: PropertyChangeListener pcl = WeakListeners
261: .create(PropertyChangeListener.class, this ,
262: schemaModel);
263: schemaModel.addPropertyChangeListener(pcl);
264: }
265: } catch (IOException io) {
266: errorMessage = io.getMessage();
267: }
268:
269: return schemaModel;
270: }
271:
272: @Override
273: public int getPersistenceType() {
274: return PERSISTENCE_NEVER;
275: }
276:
277: public void setMultiViewCallback(MultiViewElementCallback callback) {
278: multiViewCallback = callback;
279: }
280:
281: public CloseOperationState canCloseElement() {
282: // if this is not the last cloned designer, closing is OK
283: if (!SchemaMultiViewSupport.isLastView(multiViewCallback
284: .getTopComponent())) {
285: return CloseOperationState.STATE_OK;
286: }
287: // return a placeholder state - to be sure our CloseHandler is called
288: return MultiViewFactory.createUnsafeCloseState(
289: "ID_SCHEMA_COLUMNVIEW_CLOSING", // dummy ID // NOI18N
290: MultiViewFactory.NOOP_CLOSE_ACTION,
291: MultiViewFactory.NOOP_CLOSE_ACTION);
292: }
293:
294: /**
295: * Indicates if the schema model is in a valid state.
296: *
297: * @return true if schema is valid, false otherwise.
298: */
299: private boolean isSchemaValid() {
300: SchemaEditorSupport editor = schemaDataObject
301: .getSchemaEditorSupport();
302: try {
303: SchemaModel sm = editor.getModel();
304: if (sm != null && sm.getState() == SchemaModel.State.VALID) {
305: return true;
306: }
307: } catch (IOException io) {
308: // Fall through and return false.
309: }
310: return false;
311: }
312:
313: /**
314: * Initializes the UI. Here it checks for the state of the underlying
315: * schema model. If valid, draws the UI, else empties the UI with proper
316: * error message.
317: */
318: private void initUI() {
319: if (isSchemaValid()) {
320: recreateUI();
321: return;
322: }
323:
324: // If it comes here, either the schema is not well-formed or invalid.
325: if (errorMessage == null) {
326: errorMessage = NbBundle.getMessage(
327: SchemaColumnViewMultiViewElement.class,
328: "MSG_NotWellformedSchema");
329: }
330:
331: // Empties the UI, with proper error message, when the underlying
332: // schema model is in INVALID/NOT-WELLFORMED state.
333: emptyUI(errorMessage);
334: }
335:
336: /**
337: * Creates the UI. Creates the abeDesigner only if it was null
338: * or the underlying documnet was found modifed externally.
339: */
340: private void recreateUI() {
341: removeAll();
342: if (categoryPane == null) {
343: categoryPane = new DefaultCategoryPane();
344: SchemaModel model = getSchemaModel();
345: Lookup lookup = getLookup();
346: Category columns = new SchemaColumnsCategory(model, lookup);
347: categoryPane.addCategory(columns);
348: Category tree = new SchemaTreeCategory(model, lookup);
349: categoryPane.addCategory(tree);
350: // Set the default view according to the persisted setting.
351: ViewMode mode = SchemaSettings.getDefault().getViewMode();
352: switch (mode) {
353: case COLUMN:
354: categoryPane.setCategory(columns);
355: break;
356: case TREE:
357: categoryPane.setCategory(tree);
358: break;
359: }
360: }
361: // Add the schema category pane as our primary interface.
362: add(categoryPane.getComponent(), BorderLayout.CENTER);
363: revalidate();
364: repaint();
365: }
366:
367: /**
368: * Empties the UI, with proper error message, when the underlying
369: * schema model is in INVALID/NOT-WELLFORMED state.
370: */
371: private void emptyUI(String errorMessage) {
372: removeAll();
373: errorLabel.setText("<" + errorMessage + ">");
374: errorLabel
375: .setHorizontalAlignment(javax.swing.SwingConstants.CENTER);
376: errorLabel
377: .setHorizontalTextPosition(javax.swing.SwingConstants.CENTER);
378: Color usualWindowBkg = UIManager.getColor("window"); //NOI18N
379: errorLabel
380: .setBackground(usualWindowBkg != null ? usualWindowBkg
381: : Color.white);
382: errorLabel.setOpaque(true);
383: add(errorLabel, BorderLayout.CENTER);
384: revalidate();
385: repaint();
386: }
387:
388: /**
389: * Adds the undo/redo manager to the schema model as an undoable
390: * edit listener, so it receives the edits onto the queue.
391: */
392: private void addUndoManager() {
393: SchemaModel model = getSchemaModel();
394: if (model != null) {
395: SchemaEditorSupport editor = schemaDataObject
396: .getSchemaEditorSupport();
397: QuietUndoManager undo = editor.getUndoManager();
398: // Ensure the listener is not added twice.
399: model.removeUndoableEditListener(undo);
400: model.addUndoableEditListener(undo);
401: // Ensure the model is sync'd when undo/redo is invoked,
402: // otherwise the edits are added to the queue and eventually
403: // cause exceptions.
404: undo.setModel(model);
405: AXIModel aModel = AXIModelFactory.getDefault().getModel(
406: model);
407: undo.removeWrapperModel(aModel);
408: }
409: }
410:
411: @Override
412: public void componentActivated() {
413: super .componentActivated();
414: addUndoManager();
415: ExplorerUtils.activateActions(manager, true);
416: }
417:
418: @Override
419: public void componentDeactivated() {
420: super .componentDeactivated();
421: ExplorerUtils.activateActions(manager, false);
422: }
423:
424: @Override
425: public void componentOpened() {
426: super .componentOpened();
427: }
428:
429: @Override
430: public void componentClosed() {
431: super .componentClosed();
432: }
433:
434: @Override
435: public void componentShowing() {
436: super .componentShowing();
437: initUI();
438: addUndoManager();
439: if (categoryPane != null) {
440: Category cat = categoryPane.getCategory();
441: if (cat != null) {
442: cat.componentShown();
443: }
444: }
445: }
446:
447: @Override
448: public void componentHidden() {
449: super .componentHidden();
450: if (categoryPane != null) {
451: Category cat = categoryPane.getCategory();
452: if (cat != null) {
453: cat.componentHidden();
454: }
455: }
456: }
457:
458: @SuppressWarnings("deprecation")
459: @Override
460: public void requestFocus() {
461: super .requestFocus();
462: // For Help to work properly, need to take focus.
463: if (categoryPane != null) {
464: categoryPane.getComponent().requestFocus();
465: }
466: }
467:
468: @SuppressWarnings("deprecation")
469: @Override
470: public boolean requestFocusInWindow() {
471: boolean retVal = super .requestFocusInWindow();
472: // For Help to work properly, need to take focus.
473: if (categoryPane != null) {
474: return categoryPane.getComponent().requestFocusInWindow();
475: }
476: return retVal;
477: }
478:
479: public JComponent getToolbarRepresentation() {
480: // This is called every time user switches between elements.
481: if (toolbar == null) {
482: try {
483: SchemaModel model = schemaDataObject
484: .getSchemaEditorSupport().getModel();
485: if (model != null
486: && model.getState() == SchemaModel.State.VALID) {
487: toolbar = new JToolBar();
488: toolbar.setFloatable(false);
489: if (categoryPane != null) {
490: toolbar.addSeparator();
491: categoryPane.populateToolbar(toolbar);
492: }
493: // vlv: search
494: SearchManager sm = SearchManager.getDefault();
495:
496: if (sm != null) {
497: toolbar.addSeparator();
498: toolbar.add(sm.getSearchAction());
499: }
500: toolbar.addSeparator();
501: JButton validateButton = toolbar
502: .add(new ValidateAction(model));
503: validateButton
504: .getAccessibleContext()
505: .setAccessibleName(
506: ""
507: + validateButton
508: .getAction()
509: .getValue(
510: ValidateAction.NAME));
511: }
512: } catch (IOException ioe) {
513: // wait until the model is valid
514: }
515: }
516: return toolbar;
517: }
518:
519: @Override
520: public UndoRedo getUndoRedo() {
521: return schemaDataObject.getSchemaEditorSupport()
522: .getUndoManager();
523: }
524:
525: public JComponent getVisualRepresentation() {
526: return this ;
527: }
528:
529: @Override
530: public HelpCtx getHelpCtx() {
531: return new HelpCtx(SchemaColumnViewMultiViewDesc.class);
532: }
533:
534: private class ColumnViewFindAction extends AbstractAction {
535: /** silence compiler warnings */
536: private static final long serialVersionUID = 1L;
537:
538: /**
539: * Creates a new instance of ColumnViewFindAction.
540: */
541: public ColumnViewFindAction() {
542: }
543:
544: public void actionPerformed(ActionEvent event) {
545: SchemaColumnViewMultiViewElement parent = SchemaColumnViewMultiViewElement.this ;
546: if (parent.categoryPane != null) {
547: CategoryPane pane = parent.categoryPane;
548: pane.getSearchComponent().showComponent();
549: }
550: }
551: }
552:
553: /**
554: * Force the model to sync with a simple document, then sync again with
555: * the original document. This is used to reconstruct the model in the
556: * event that it is in an invalid state.
557: *
558: * @param model the model to be reset.
559: */
560: private void forceResetDocument(SchemaModel model) {
561: SchemaDataObject dobj = getSchemaDataObject();
562: SchemaEditorSupport editor = dobj.getSchemaEditorSupport();
563: StyledDocument doc = editor.getDocument();
564: QuietUndoManager um = editor.getUndoManager();
565: // Must ignore the edits that are about to occur.
566: model.removeUndoableEditListener(um);
567: // Changing the document causes the editor support to end up
568: // creating another MVE, so avoid that altogether by ignoring
569: // the call to updateTitles().
570: editor.ignoreUpdateTitles(true);
571: try {
572: // Reset the model state by forcing it to sync again.
573: String saved = doc.getText(0, doc.getLength());
574: doc.remove(0, doc.getLength());
575: doc.insertString(0, EMPTY_DOC, null);
576: model.sync();
577: doc.remove(0, doc.getLength());
578: doc.insertString(0, saved, null);
579: model.sync();
580: dobj.setModified(false);
581: } catch (BadLocationException e) {
582: Logger.getLogger(
583: SchemaColumnViewMultiViewElement.class.getName())
584: .log(Level.FINE, "forceResetDocument", e);
585: } catch (IOException e) {
586: Logger.getLogger(
587: SchemaColumnViewMultiViewElement.class.getName())
588: .log(Level.FINE, "forceResetDocument", e);
589: } finally {
590: editor.ignoreUpdateTitles(false);
591: model.addUndoableEditListener(um);
592: }
593: }
594: }
|