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.ui.basic;
043:
044: import java.awt.BorderLayout;
045: import java.beans.PropertyChangeEvent;
046: import java.beans.PropertyChangeListener;
047: import java.beans.PropertyVetoException;
048: import java.lang.reflect.Method;
049: import java.util.ArrayList;
050: import java.util.EventListener;
051: import java.util.List;
052: import javax.swing.JPanel;
053: import javax.swing.JScrollPane;
054: import org.netbeans.modules.xml.schema.model.SchemaComponent;
055: import org.netbeans.modules.xml.schema.model.SchemaModel;
056: import org.netbeans.modules.xml.schema.ui.nodes.SchemaNodeFactory;
057: import org.netbeans.modules.xml.schema.ui.nodes.StructuralSchemaNodeFactory;
058: import org.netbeans.modules.xml.schema.ui.nodes.categorized.CategorizedSchemaNodeFactory;
059: import org.netbeans.modules.xml.xam.ui.column.LinkPanel;
060: import org.netbeans.modules.xml.xam.ui.column.Column;
061: import org.netbeans.modules.xml.xam.ui.column.ColumnView;
062: import org.netbeans.modules.xml.xam.ui.column.BasicColumnView;
063: import org.openide.ErrorManager;
064: import org.openide.explorer.ExplorerManager;
065: import org.openide.nodes.Node;
066: import org.openide.util.Lookup;
067:
068: /**
069: * Displays the XML schema in a series of columns, each usually
070: * containing a tree representing a subset of the schema model.
071: *
072: * <p><em>Note: SchemaColumnsView, like all NbColumnView subclasses, has its
073: * own JScrollPane, you do not need to place it in a JScrollPane.</em></p>
074: *
075: * @author Todd Fast, todd.fast@sun.com
076: * @author Nathan Fiedler
077: * @author Jeri Lockhart
078: */
079: public class SchemaColumnsView extends JPanel implements ColumnView,
080: PropertyChangeListener {
081: /** silence compiler warnings */
082: static final long serialVersionUID = 1L;
083: /** Where the columns are shown. */
084: private transient BasicColumnView columnView;
085: /** Where the bread crumbs live. */
086: private transient LinkPanel breadCrumbs;
087: private transient SchemaModel schemaModel;
088: private transient SchemaNodeFactory factory;
089: private ViewType viewType;
090: private transient Lookup lookup;
091: private Class<? extends EventListener> columnListenerClass;
092: private transient EventListener columnListener;
093: public static final String PROP_COLUMN_ADDED = "column_added";
094:
095: public static enum ViewType {
096: /** The developer will set the node factory directly */
097: CUSTOM,
098: /** Use the categorized node factory */
099: CATEGORIZED,
100: /** Use the structural node factory */
101: STRUCTURAL;
102: }
103:
104: /**
105: * Constructs a new instance of SchemaColumnsView.
106: *
107: * @param model schema model being displayed.
108: * @param viewType type of column view.
109: * @param lookup from whence services are found.
110: */
111: public SchemaColumnsView(SchemaModel model, ViewType viewType,
112: Lookup lookup) {
113: super (new BorderLayout());
114: breadCrumbs = new LinkPanel(this );
115: add(breadCrumbs, BorderLayout.NORTH);
116: columnView = new BasicColumnView();
117: add(columnView, BorderLayout.CENTER);
118: this .schemaModel = model;
119: this .lookup = lookup;
120: _setViewType(viewType);
121: }
122:
123: /**
124: * Set the schema node factory.
125: *
126: * @param factory new schema node factory.
127: */
128: public void setNodeFactory(SchemaNodeFactory factory) {
129: _setNodeFactory(factory);
130: }
131:
132: /**
133: * Private version of <code>setNodeFactory()</code> so it can be safely
134: * called from the constructor.
135: *
136: * @param factory new schema node factory.
137: */
138: private void _setNodeFactory(SchemaNodeFactory factory) {
139: this .factory = factory;
140: clearColumns();
141: Node rootNode = factory.createRootNode();
142: SchemaColumn rootPanel = createColumnComponent(rootNode, true);
143: appendColumn(rootPanel);
144: }
145:
146: /**
147: * Construct a default column component for the given schema component.
148: *
149: * @param node root node of the schema component.
150: * @param showRoot true to show the root of the tree.
151: */
152: protected SchemaColumn createColumnComponent(Node node,
153: boolean showRoot) {
154: SchemaColumn panel = new SchemaColumn(this , node, showRoot);
155: addColumnListener(panel);
156: return panel;
157: }
158:
159: /**
160: * Add the pre-defined column listener to the given column.
161: *
162: * @param column SchemaColumn to which the listener is added.
163: */
164: public void addColumnListener(SchemaColumn column) {
165: if (!(columnListener == null && columnListenerClass == null)) {
166: try {
167: // Look for the method, e.g. addPropertyChangeListener.
168: Method addListenerMethod = SchemaColumn.class
169: .getMethod("add"
170: + columnListenerClass.getSimpleName(),
171: columnListenerClass); // NOI18N
172: addListenerMethod.invoke(column, columnListener);
173: } catch (Exception e) {
174: // This is not expected to happen, but log it if it does.
175: ErrorManager.getDefault().notify(ErrorManager.WARNING,
176: e);
177: }
178: }
179: }
180:
181: /**
182: * Set the column listener for this column view. The listener will
183: * be added to each column instance that has a corresponding "add"
184: * method (e.g. "addPropertyChangeListener()").
185: *
186: * @param listenerClass class of the listener.
187: * @param listener listener to be added.
188: */
189: public <L extends EventListener> void setColumnListener(
190: Class<L> listenerClass, L listener) {
191: columnListenerClass = listenerClass;
192: columnListener = listener;
193: }
194:
195: /**
196: * Retrieve the column listener, if one is defined.
197: *
198: * @return the column listener, or null if none.
199: */
200: public EventListener getColumnListener() {
201: return columnListener;
202: }
203:
204: /**
205: * Private version of <code>setViewType()</code> so it can be safely
206: * called from the constructor.
207: *
208: * @param type the type of schema column view.
209: */
210: private void _setViewType(ViewType type) {
211: if (viewType != type) {
212: viewType = type;
213: SchemaNodeFactory factory = createNodeFactory(type);
214: if (factory != null) {
215: _setNodeFactory(factory);
216: }
217: }
218: }
219:
220: /**
221: * Create the SchemaNodeFactory appropriate for the given view type.
222: *
223: * @param type the type of schema column view.
224: */
225: private SchemaNodeFactory createNodeFactory(ViewType type) {
226: SchemaNodeFactory factory;
227: switch (type) {
228: case CATEGORIZED:
229: factory = new CategorizedSchemaNodeFactory(schemaModel,
230: lookup);
231: break;
232: case STRUCTURAL:
233: factory = new StructuralSchemaNodeFactory(schemaModel,
234: lookup);
235: break;
236: default:
237: factory = null;
238: break;
239: }
240: return factory;
241: }
242:
243: public void propertyChange(PropertyChangeEvent event) {
244: Object src = event.getSource();
245: if (event.getPropertyName().equals(Column.PROP_TITLE)
246: && src instanceof Column) {
247: // Update the link title to reflect the change.
248: breadCrumbs.updateLink((Column) src);
249: }
250: }
251:
252: public void showComponent(SchemaComponent sc) {
253: Node rootNode = null;
254: Column currentColumn = getFirstColumn();
255: ExplorerManager currentExplorer = null;
256: if (currentColumn instanceof ExplorerManager.Provider)
257: currentExplorer = ((ExplorerManager.Provider) currentColumn)
258: .getExplorerManager();
259: if (currentExplorer != null) {
260: rootNode = currentExplorer.getRootContext();
261: } else {
262: rootNode = factory.createRootNode();
263: clearColumns();
264: currentColumn = createColumnComponent(rootNode, false);
265: appendColumn(currentColumn);
266: currentExplorer = ExplorerManager.find(currentColumn
267: .getComponent());
268: }
269: List<Node> path = UIUtilities.findPathFromRoot(rootNode, sc);
270: if (path.isEmpty())
271: return;
272: // retain existing columns if appropriate
273: int idx = 0;
274: Column tmpColumn = currentColumn;
275: ExplorerManager tmpExplorer = currentExplorer;
276: for (Node node : path.subList(0, path.size() - 1)) {
277: boolean found = false;
278: while (tmpExplorer != null
279: && tmpExplorer.getRootContext() == node) {
280: found = true;
281: currentColumn = tmpColumn;
282: currentExplorer = tmpExplorer;
283: tmpColumn = getNextColumn(tmpColumn);
284: if (!(tmpColumn instanceof ExplorerManager.Provider))
285: break;
286: tmpExplorer = ((ExplorerManager.Provider) tmpColumn)
287: .getExplorerManager();
288: }
289: if (found)
290: idx++;
291: else // remove columns if needed
292: {
293: removeColumnsAfter(currentColumn);
294: try {
295: currentExplorer.setSelectedNodes(new Node[] {});
296: } catch (PropertyVetoException ex) {
297: }
298: break;
299: }
300: }
301: // add necessary columns
302: if (idx < path.size() - 1) {
303: List<Column> columns = new ArrayList<Column>();
304: for (Node node : path.subList(idx, path.size() - 1)) {
305: currentColumn = createColumnComponent(node, true);
306: columns.add(currentColumn);
307: }
308: Column[] arr = columns.toArray(new Column[columns.size()]);
309: appendColumns(arr);
310: }
311: // select node representing component
312: if (currentColumn instanceof ExplorerManager.Provider)
313: currentExplorer = ((ExplorerManager.Provider) currentColumn)
314: .getExplorerManager();
315: try {
316: if (currentExplorer != null)
317: currentExplorer.setSelectedNodes(new Node[] { path
318: .get(path.size() - 1) });
319: } catch (PropertyVetoException ex) {
320: }
321: }
322:
323: /**
324: * Remove our listeners from the columns, starting with the given
325: * column and moving toward the right.
326: *
327: * @param col first column from which to remove listeners.
328: */
329: private void removeListeners(Column col) {
330: while (col != null) {
331: col.removePropertyChangeListener(this );
332: col = getNextColumn(col);
333: }
334: }
335:
336: @Override
337: public void requestFocus() {
338: super .requestFocus();
339: columnView.requestFocus();
340: }
341:
342: @Override
343: public boolean requestFocusInWindow() {
344: super .requestFocusInWindow();
345: return columnView.requestFocusInWindow();
346: }
347:
348: //
349: // ColumnView implementation
350: //
351:
352: public void appendColumn(Column column) {
353: column.addPropertyChangeListener(this );
354: columnView.appendColumn(column);
355: breadCrumbs.appendLink(column);
356: firePropertyChange(PROP_COLUMN_ADDED, null, column);
357: }
358:
359: public void appendColumns(Column[] columns) {
360: for (Column column : columns) {
361: column.addPropertyChangeListener(this );
362: breadCrumbs.appendLink(column);
363: firePropertyChange(PROP_COLUMN_ADDED, null, column);
364: }
365: columnView.appendColumns(columns);
366: }
367:
368: public void clearColumns() {
369: removeListeners(getFirstColumn());
370: columnView.clearColumns();
371: breadCrumbs.clearLinks();
372: }
373:
374: public int getColumnCount() {
375: return columnView.getColumnCount();
376: }
377:
378: public int getColumnIndex(Column column) {
379: return columnView.getColumnIndex(column);
380: }
381:
382: public Column getFirstColumn() {
383: return columnView.getFirstColumn();
384: }
385:
386: public Column getNextColumn(Column column) {
387: return columnView.getNextColumn(column);
388: }
389:
390: public void removeColumnsAfter(Column column) {
391: removeListeners(getNextColumn(column));
392: int index = columnView.getColumnIndex(column);
393: columnView.removeColumnsAfter(column);
394: // Remove the links after this column.
395: breadCrumbs.truncateLinks(index + 1);
396: }
397:
398: public void scrollToColumn(Column column, boolean synchronous) {
399: columnView.scrollToColumn(column, synchronous);
400: }
401: }
|