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.viewmodel;
043:
044: import java.awt.BorderLayout;
045: import java.awt.event.KeyEvent;
046: import java.beans.PropertyChangeEvent;
047: import java.beans.PropertyChangeListener;
048: import java.beans.PropertyEditor;
049: import java.util.*;
050: import javax.swing.ActionMap;
051: import javax.swing.ComponentInputMap;
052: import javax.swing.InputMap;
053: import javax.swing.JComponent;
054: import javax.swing.JPanel;
055: import javax.swing.JScrollPane;
056: import javax.swing.JTable;
057: import javax.swing.JTree;
058: import javax.swing.KeyStroke;
059: import javax.swing.SwingUtilities;
060: import javax.swing.event.TableModelEvent;
061: import javax.swing.event.TreeExpansionListener;
062: import javax.swing.event.TreeExpansionEvent;
063: import javax.swing.table.TableColumn;
064: import javax.swing.table.TableColumnModel;
065: import javax.swing.text.DefaultEditorKit;
066: import javax.swing.tree.TreeNode;
067:
068: import javax.swing.tree.TreePath;
069: import javax.swing.tree.TreeModel;
070:
071: import org.netbeans.spi.viewmodel.Models;
072: import org.netbeans.spi.viewmodel.ColumnModel;
073: import org.netbeans.spi.viewmodel.UnknownTypeException;
074: import org.openide.ErrorManager;
075: import org.openide.explorer.ExplorerUtils;
076:
077: import org.openide.explorer.ExplorerManager;
078: import org.openide.explorer.view.BeanTreeView;
079:
080: import org.openide.explorer.view.NodeTableModel;
081: import org.openide.explorer.view.TreeTableView;
082: import org.openide.explorer.view.TreeView;
083: import org.openide.explorer.view.Visualizer;
084:
085: import org.openide.nodes.AbstractNode;
086: import org.openide.nodes.Children;
087: import org.openide.nodes.Node;
088: import org.openide.nodes.NodeNotFoundException;
089:
090: import org.openide.nodes.NodeOp;
091:
092: import org.openide.nodes.PropertySupport;
093: import org.openide.windows.TopComponent;
094:
095: /**
096: * Implements root node of hierarchy created for given TreeModel.
097: *
098: * @author Jan Jancura
099: */
100: public class TreeTable extends JPanel implements
101: ExplorerManager.Provider, PropertyChangeListener,
102: TreeExpansionListener {
103:
104: private ExplorerManager explorerManager;
105: private MyTreeTable treeTable;
106: Node.Property[] columns; // Accessed from tests
107: private IndexedColumn[] icolumns;
108: private boolean isDefaultColumnAdded;
109: //private List expandedPaths = new ArrayList ();
110: private TreeModelRoot currentTreeModelRoot;
111: private Models.CompoundModel model;
112:
113: public TreeTable() {
114: setLayout(new BorderLayout());
115: treeTable = new MyTreeTable();
116: treeTable.setRootVisible(false);
117: treeTable
118: .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
119: treeTable
120: .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_AS_NEEDED);
121: add(treeTable, "Center"); //NOI18N
122: treeTable.getTree().addTreeExpansionListener(this );
123: ActionMap map = getActionMap();
124: ExplorerManager manager = getExplorerManager();
125: map.put(DefaultEditorKit.copyAction, ExplorerUtils
126: .actionCopy(manager));
127: map.put(DefaultEditorKit.cutAction, ExplorerUtils
128: .actionCut(manager));
129: map.put(DefaultEditorKit.pasteAction, ExplorerUtils
130: .actionPaste(manager));
131: map.put("delete", ExplorerUtils.actionDelete(manager, false));
132: }
133:
134: public void setModel(Models.CompoundModel model) {
135: // 2) save current settings (like columns, expanded paths)
136: //List ep = treeTable.getExpandedPaths ();
137: saveWidths();
138:
139: this .model = model;
140:
141: // 1) destroy old model
142: if (currentTreeModelRoot != null)
143: currentTreeModelRoot.destroy();
144:
145: // 3) no model => set empty root node & return
146: if (model == null) {
147: getExplorerManager().setRootContext(
148: new AbstractNode(Children.LEAF));
149: return;
150: }
151:
152: // 4) set columns for given model
153: columns = createColumns(model);
154: currentTreeModelRoot = new TreeModelRoot(model, this );
155: TreeModelNode rootNode = currentTreeModelRoot.getRootNode();
156: getExplorerManager().setRootContext(rootNode);
157: // The root node must be ready when setting the columns
158: treeTable.setProperties(columns);
159:
160: // 5) set root node for given model
161: // Moved to 4), because the new root node must be ready when setting columns
162:
163: // 6) update column widths & expanded nodes
164: updateColumnWidths();
165: //treeTable.expandNodes (expandedPaths);
166: // TODO: this is a workaround, we should find a better way later
167: /* We must not call children here - it can take a long time...
168: * the expansion is performed in TreeModelNode.TreeModelChildren.applyChildren()
169: final List backupPath = new ArrayList (expandedPaths);
170: if (backupPath.size () == 0)
171: TreeModelNode.getRequestProcessor ().post (new Runnable () {
172: public void run () {
173: try {
174: final Object[] ch = TreeTable.this.model.getChildren
175: (TreeTable.this.model.getRoot (), 0, 0);
176: SwingUtilities.invokeLater (new Runnable () {
177: public void run () {
178: expandDefault (ch);
179: }
180: });
181: } catch (UnknownTypeException ex) {}
182: }
183: });
184: else
185: SwingUtilities.invokeLater (new Runnable () {
186: public void run () {
187: treeTable.expandNodes (backupPath);
188: }
189: });
190: */
191: //if (ep.size () > 0) expandedPaths = ep;
192: }
193:
194: public ExplorerManager getExplorerManager() {
195: if (explorerManager == null) {
196: explorerManager = new ExplorerManager();
197: }
198: return explorerManager;
199: }
200:
201: public void propertyChange(PropertyChangeEvent evt) {
202: String propertyName = evt.getPropertyName();
203: TopComponent tc = (TopComponent) SwingUtilities
204: .getAncestorOfClass(TopComponent.class, this );
205: if (tc == null)
206: return;
207: if (propertyName
208: .equals(TopComponent.Registry.PROP_CURRENT_NODES)) {
209: ExplorerUtils.activateActions(getExplorerManager(),
210: equalNodes());
211: } else if (propertyName
212: .equals(ExplorerManager.PROP_SELECTED_NODES)) {
213: tc.setActivatedNodes((Node[]) evt.getNewValue());
214: }
215: }
216:
217: /**
218: * Called whenever an item in the tree has been expanded.
219: */
220: public void treeExpanded(TreeExpansionEvent event) {
221: Object obj = Visualizer.findNode(
222: event.getPath().getLastPathComponent()).getLookup()
223: .lookup(Object.class);
224: model.nodeExpanded(obj);
225: }
226:
227: /**
228: * Called whenever an item in the tree has been collapsed.
229: */
230: public void treeCollapsed(TreeExpansionEvent event) {
231: Object obj = Visualizer.findNode(
232: event.getPath().getLastPathComponent()).getLookup()
233: .lookup(Object.class);
234: model.nodeCollapsed(obj);
235: }
236:
237: private boolean equalNodes() {
238: Node[] ns1 = TopComponent.getRegistry().getCurrentNodes();
239: Node[] ns2 = getExplorerManager().getSelectedNodes();
240: if (ns1 == ns2)
241: return true;
242: if ((ns1 == null) || (ns2 == null))
243: return false;
244: if (ns1.length != ns2.length)
245: return false;
246: int i, k = ns1.length;
247: for (i = 0; i < k; i++)
248: if (!ns1[i].equals(ns2[i]))
249: return false;
250: return true;
251: }
252:
253: private Node.Property[] createColumns(Models.CompoundModel model) {
254: ColumnModel[] cs = model.getColumns();
255: int i, k = cs.length;
256: Node.Property[] columns = new Column[k];
257: boolean addDefaultColumn = true;
258: for (i = 0; i < k; i++) {
259: columns[i] = new Column(cs[i], this );
260: if (cs[i].getType() == null)
261: addDefaultColumn = false;
262: }
263: icolumns = new IndexedColumn[columns.length];
264: for (i = 0; i < icolumns.length; i++) {
265: icolumns[i] = new IndexedColumn(columns[i], i, cs[i]
266: .getCurrentOrderNumber());
267: }
268: if (!addDefaultColumn) {
269: return columns;
270: }
271: PropertySupport.ReadWrite[] columns2 = new PropertySupport.ReadWrite[columns.length + 1];
272: System.arraycopy(columns, 0, columns2, 1, columns.length);
273: columns2[0] = new DefaultColumn();
274: isDefaultColumnAdded = true;
275: return columns2;
276: }
277:
278: /** @return visible index >= globalIndex */
279: int getColumnVisibleIndex(Column c, int globalIndex) {
280: int a = (isDefaultColumnAdded) ? 1 : 0;
281: int visibleIndex = 0;
282: ColumnModel[] cs = model.getColumns();
283: if (cs.length == 0)
284: return -1;
285: for (int i = 0; i < columns.length - a; i++) {
286: if (!Boolean.TRUE.equals(columns[i]
287: .getValue("InvisibleInTreeTableView"))) {
288: int gi = cs[i].getCurrentOrderNumber();
289: //System.err.println(" CurrentOrderNumber("+cs[i].getDisplayName()+") = "+gi);
290: if (gi >= 0 && gi < globalIndex) {
291: visibleIndex++;
292: }
293: }
294: }
295: //System.err.println("getColumnVisibleIndex("+c.getDisplayName()+", "+globalIndex+") = "+visibleIndex);
296: return visibleIndex;
297: }
298:
299: /** @return global index >= visibleIndex */
300: int getColumnGlobalIndex(Column c, int visibleIndex) {
301: ColumnModel[] models = model.getColumns();
302: if (models.length == 0)
303: return -1;
304: for (int i = 0; i < icolumns.length; i++) {
305: icolumns[i].order = models[icolumns[i].index]
306: .getCurrentOrderNumber();
307: }
308: Arrays.sort(icolumns, new IndexedColumn.Cmp());
309: //System.err.print("getColumnGlobalIndex("+c.getDisplayName()+", "+visibleIndex+") = ");
310: for (int i = 0; i < icolumns.length; i++) {
311: if (!Boolean.TRUE.equals(icolumns[i].getColumn().getValue(
312: "InvisibleInTreeTableView"))) {
313: if (visibleIndex <= 0) {
314: //System.err.println(i);
315: return i;
316: }
317: visibleIndex--;
318: }
319: }
320: //System.err.println(icolumns.length+" (END)");
321: return icolumns.length;
322: }
323:
324: void updateColumnWidths() {
325: int i, k = columns.length;
326: int d = (isDefaultColumnAdded) ? 1 : 0;
327: TableColumnModel tcm = treeTable.getTable().getColumnModel();
328: for (i = 0; i < k; i++) {
329: if (Boolean.TRUE.equals(columns[i]
330: .getValue("InvisibleInTreeTableView")))
331: continue;
332: if (columns[i] instanceof Column) {
333: Column column = (Column) columns[i];
334: if (column.isDefault()) {
335: int width = column.getColumnWidth();
336: treeTable.setTreePreferredWidth(width);
337: } else {
338: int order = column.getModelOrderNumber();
339: if (order < 0)
340: order = i;
341: order = getColumnVisibleIndex(column, order);
342: TableColumn tc;
343: try {
344: tc = tcm.getColumn(order + d);
345: } catch (ArrayIndexOutOfBoundsException aioobex) {
346: ErrorManager
347: .getDefault()
348: .notify(
349: ErrorManager
350: .getDefault()
351: .annotate(
352: aioobex,
353: "Column("
354: + i
355: + ") "
356: + column
357: .getName()
358: + " model order = "
359: + column
360: .getModelOrderNumber()
361: + " visible index = "
362: + order
363: + " => column index = "
364: + (order + d)));
365: continue;
366: }
367: tc.setPreferredWidth(column.getColumnWidth());
368: }
369: }
370: }
371: }
372:
373: private void saveWidths() {
374: if (columns == null)
375: return;
376: int i, k = columns.length;
377: if (k == 0)
378: return;
379: TableColumnModel tcm = treeTable.getTable().getColumnModel();
380: Enumeration<TableColumn> etc = tcm.getColumns();
381: boolean defaultState = true;
382: while (etc.hasMoreElements()) {
383: if (etc.nextElement().getWidth() != 75) {
384: defaultState = false;
385: break;
386: }
387: }
388: if (defaultState) {
389: // All columns have the default width 75.
390: // It's very likely that the table was not fully initialized => do not save anything.
391: return;
392: }
393: int d = (isDefaultColumnAdded) ? 1 : 0;
394: for (i = 0; i < k; i++) {
395: if (Boolean.TRUE.equals(columns[i]
396: .getValue("InvisibleInTreeTableView")))
397: continue;
398: if (!(columns[i] instanceof Column))
399: continue;
400: Column column = (Column) columns[i];
401: if (column.isDefault()) {
402: TableColumn tc = tcm.getColumn(0);
403: int width = tc.getWidth();
404: column.setColumnWidth(width);
405: } else {
406: int order = column.getModelOrderNumber();
407: if (order < 0)
408: order = i;
409: order = getColumnVisibleIndex(column, order);
410: //System.err.println(" Column("+i+") "+column.getName()+" visible index = "+order+" => column index = "+(order + d));
411: TableColumn tc;
412: try {
413: tc = tcm.getColumn(order + d);
414: } catch (ArrayIndexOutOfBoundsException aioobex) {
415: ErrorManager
416: .getDefault()
417: .notify(
418: ErrorManager
419: .getDefault()
420: .annotate(
421: aioobex,
422: "Column("
423: + i
424: + ") "
425: + column
426: .getName()
427: + " model order = "
428: + column
429: .getModelOrderNumber()
430: + " visible index = "
431: + order
432: + " => column index = "
433: + (order + d)));
434: continue;
435: }
436: int width = tc.getWidth();
437: column.setColumnWidth(width);
438: }
439: }
440: }
441:
442: private void expandDefault(Object[] nodes) {
443: int i, k = nodes.length;
444: for (i = 0; i < k; i++)
445: try {
446: if (model.isExpanded(nodes[i]))
447: expandNode(nodes[i]);
448: } catch (UnknownTypeException ex) {
449: }
450: }
451:
452: /** Requests focus for the tree component. Overrides superclass method. */
453: public boolean requestFocusInWindow() {
454: super .requestFocusInWindow();
455: return treeTable.requestFocusInWindow();
456: }
457:
458: public void addNotify() {
459: super .addNotify();
460: TopComponent.getRegistry().addPropertyChangeListener(this );
461: ExplorerUtils.activateActions(getExplorerManager(), true);
462: getExplorerManager().addPropertyChangeListener(this );
463: }
464:
465: public void removeNotify() {
466: TopComponent.getRegistry().removePropertyChangeListener(this );
467: ExplorerUtils.activateActions(getExplorerManager(), false);
468: getExplorerManager().removePropertyChangeListener(this );
469: super .removeNotify();
470: }
471:
472: public boolean isExpanded(Object node) {
473: Node n = currentTreeModelRoot.findNode(node);
474: if (n == null)
475: return false; // Something what does not exist is not expanded ;-)
476: return treeTable.isExpanded(n);
477: }
478:
479: public void expandNode(Object node) {
480: Node n = currentTreeModelRoot.findNode(node);
481: if (treeTable != null && n != null)
482: treeTable.expandNode(n);
483: }
484:
485: public void collapseNode(Object node) {
486: Node n = currentTreeModelRoot.findNode(node);
487: treeTable.collapseNode(n);
488: }
489:
490: private static class MyTreeTable extends TreeTableView {
491: MyTreeTable() {
492: super ();
493: treeTable.setShowHorizontalLines(true);
494: treeTable.setShowVerticalLines(false);
495: filterInputMap(treeTable, JComponent.WHEN_FOCUSED);
496: filterInputMap(treeTable, JComponent.WHEN_IN_FOCUSED_WINDOW);
497: filterInputMap(treeTable,
498: JComponent.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT);
499: }
500:
501: private void filterInputMap(JComponent component, int condition) {
502: InputMap imap = component.getInputMap(condition);
503: if (imap instanceof ComponentInputMap) {
504: imap = new F8FilterComponentInputMap(component, imap);
505: } else {
506: imap = new F8FilterInputMap(imap);
507: }
508: component.setInputMap(condition, imap);
509: }
510:
511: JTable getTable() {
512: return treeTable;
513: }
514:
515: JTree getTree() {
516: return tree;
517: }
518:
519: /*
520: public List getExpandedPaths () {
521: List result = new ArrayList ();
522: ExplorerManager em = ExplorerManager.find (this);
523: TreeNode rtn = Visualizer.findVisualizer (
524: em.getRootContext ()
525: );
526: TreePath tp = new TreePath (rtn); // Get the root
527:
528: Enumeration exPaths = tree.getExpandedDescendants (tp);
529: if (exPaths == null) return result;
530: for (;exPaths.hasMoreElements ();) {
531: TreePath ep = (TreePath) exPaths.nextElement ();
532: Node en = Visualizer.findNode (ep.getLastPathComponent ());
533: String[] path = NodeOp.createPath (en, em.getRootContext ());
534: result.add (path);
535: }
536: return result;
537: }
538: */
539:
540: /** Expands all the paths, when exists
541: */
542: public void expandNodes(List exPaths) {
543: for (Iterator it = exPaths.iterator(); it.hasNext();) {
544: String[] sp = (String[]) it.next();
545: TreePath tp = stringPath2TreePath(sp);
546: if (tp != null)
547: showPath(tp);
548: }
549: }
550:
551: /** Converts path of strings to TreePath if exists null otherwise
552: */
553: private TreePath stringPath2TreePath(String[] sp) {
554: ExplorerManager em = ExplorerManager.find(this );
555: try {
556: Node n = NodeOp.findPath(em.getRootContext(), sp);
557:
558: // Create the tree path
559: TreeNode tns[] = new TreeNode[sp.length + 1];
560:
561: for (int i = sp.length; i >= 0; i--) {
562: tns[i] = Visualizer.findVisualizer(n);
563: n = n.getParentNode();
564: }
565: return new TreePath(tns);
566: } catch (NodeNotFoundException e) {
567: return null;
568: }
569: }
570: }
571:
572: private static final class F8FilterComponentInputMap extends
573: ComponentInputMap {
574:
575: private KeyStroke f8 = KeyStroke
576: .getKeyStroke(KeyEvent.VK_F8, 0);
577:
578: public F8FilterComponentInputMap(JComponent component,
579: InputMap imap) {
580: super (component);
581: setParent(imap);
582: }
583:
584: @Override
585: public Object get(KeyStroke keyStroke) {
586: if (f8.equals(keyStroke)) {
587: return null;
588: } else {
589: return super .get(keyStroke);
590: }
591: }
592: }
593:
594: private static final class F8FilterInputMap extends InputMap {
595:
596: private KeyStroke f8 = KeyStroke
597: .getKeyStroke(KeyEvent.VK_F8, 0);
598:
599: public F8FilterInputMap(InputMap imap) {
600: setParent(imap);
601: }
602:
603: @Override
604: public Object get(KeyStroke keyStroke) {
605: if (f8.equals(keyStroke)) {
606: return null;
607: } else {
608: return super .get(keyStroke);
609: }
610: }
611: }
612:
613: private static class IndexedColumn extends Object {
614: private Node.Property column;
615: public int index;
616: public int order;
617:
618: public IndexedColumn(Node.Property column, int index, int order) {
619: this .column = column;
620: this .index = index;
621: this .order = order;
622: }
623:
624: public Node.Property getColumn() {
625: return column;
626: }
627:
628: public static class Cmp implements Comparator<IndexedColumn> {
629:
630: public int compare(IndexedColumn ic1, IndexedColumn ic2) {
631: if (ic1.order == -1 && ic2.order >= 0) {
632: return +1;
633: }
634: if (ic2.order == -1 && ic1.order >= 0) {
635: return -1;
636: }
637: return ic1.order - ic2.order;
638: }
639:
640: }
641: }
642: }
|