/* * Sun Public License Notice * * The contents of this file are subject to the Sun Public License * Version 1.0 (the "License"). You may not use this file except in * compliance with the License. A copy of the License is available at * http://www.sun.com/ * * The Original Code is NetBeans. The Initial Developer of the Original * Code is Sun Microsystems, Inc. Portions Copyright 1997-2003 Sun * Microsystems, Inc. All Rights Reserved. */ package org.openide.explorer.view; import java.awt.*; import java.awt.event.*; import javax.swing.*; import javax.swing.table.*; import javax.swing.plaf.metal.MetalScrollBarUI; import javax.swing.border.Border; import javax.swing.event.*; import javax.swing.tree.*; import javax.accessibility.AccessibleContext; import java.beans.PropertyChangeEvent; import java.beans.PropertyChangeListener; import java.util.Comparator; import java.util.Collections; import java.util.Enumeration; import java.util.ArrayList; import java.util.Arrays; import java.util.Iterator; import org.openide.ErrorManager; import org.openide.nodes.Node; import org.openide.nodes.Node.Property; import org.openide.util.actions.SystemAction; import org.openide.util.NbBundle; import org.openide.awt.MouseUtils; /** Explorer view. Allows to view tree of nodes on the left * and its properties in table on the right. * * @author jrojcek * @since 1.7 * @see <a href="../doc-files/api.html#cust-treetableview">Customizing the <code>TreeTableView</code> (Explorer API)</a> */ public class TreeTableView extends BeanTreeView { /** The table */ protected JTable treeTable; private NodeTableModel tableModel; // Tree scroll support private JScrollBar hScrollBar; private JScrollPane scrollPane; private ScrollListener listener; // hiding columns allowed private boolean allowHideColumns = false; // sorting by column allowed private boolean allowSortingByColumn = false; // hide horizontal scrollbar private boolean hideHScrollBar = false; // button in corner of scroll pane private JButton colsButton = null; // tree model with sorting support private SortedNodeTreeModel sortedNodeTreeModel; /** Listener on keystroke to invoke default action */ private ActionListener defaultTreeActionListener; // default treetable header renderer private TableCellRenderer defaultHeaderRenderer = null; private MouseUtils.PopupMouseAdapter tableMouseListener; /** Accessible context of this class (implemented by inner class AccessibleTreeTableView). */ private AccessibleContext accessContext; // icon of column button private static final String COLUMNS_ICON = "/org/openide/resources/columns.gif"; // NOI18N // icons of ascending/descending order in column header private static final String SORT_ASC_ICON = "org/openide/resources/columnsSortedAsc.gif"; // NOI18N private static final String SORT_DESC_ICON = "org/openide/resources/columnsSortedDesc.gif"; // NOI18N private TreeColumnProperty treeColumnProperty = new TreeColumnProperty(); private int treeColumnWidth; /** Create TreeTableView with default NodeTableModel */ public TreeTableView() { this(new NodeTableModel()); } /** Creates TreeTableView with provided NodeTableModel. * @param ntm node table model */ public TreeTableView(NodeTableModel ntm) { tableModel = ntm; initializeTreeTable(); setPopupAllowed(true); setDefaultActionAllowed(true); initializeTreeScrollSupport(); // add scrollbar and scrollpane into a panel JPanel p = new CompoundScrollPane(); p.setLayout(new BorderLayout()); scrollPane.setViewportView(treeTable); p.add(BorderLayout.CENTER, scrollPane); ImageIcon ic = new ImageIcon(TreeTable.class.getResource ( COLUMNS_ICON )); // NOI18N colsButton = new javax.swing.JButton( ic ); colsButton.addActionListener(new ActionListener() { public void actionPerformed(ActionEvent evt) { selectVisibleColumns(); } }); JPanel sbp = new JPanel(); sbp.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0)); sbp.add(hScrollBar); p.add(BorderLayout.SOUTH, sbp); super.setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER); super.setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER); setViewportView(p); } /* Overriden to allow hide special horizontal scrollbar */ public void setHorizontalScrollBarPolicy(int policy) { hideHScrollBar = ( policy == JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); if ( hideHScrollBar ) { hScrollBar.setVisible( false ); ((TreeTable)treeTable).setTreeHScrollingEnabled( false ); } } /* Overriden to delegate policy of vertical scrollbar to inner scrollPane */ public void setVerticalScrollBarPolicy(int policy) { if ( scrollPane == null ) return; allowHideColumns = ( policy == JScrollPane.VERTICAL_SCROLLBAR_ALWAYS ); if ( allowHideColumns ) scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER, colsButton); treeTable.getTableHeader().setReorderingAllowed( allowHideColumns ); scrollPane.setVerticalScrollBarPolicy( policy ); } protected NodeTreeModel createModel() { return getSortedNodeTreeModel (); } /* Sets sorting ability */ private void setAllowSortingByColumn( boolean allow ) { if ( allow && (allow != allowSortingByColumn )) { addMouseListener( new MouseAdapter() { public void mouseClicked (MouseEvent evt) { Component c = evt.getComponent (); if (c instanceof JTableHeader) { JTableHeader h = (JTableHeader)c; int index = h.columnAtPoint (evt.getPoint ()); clickOnColumnAction( index - 1 ); } } }); } allowSortingByColumn = allow; } /* Change sorting after clicking on comparable column header. * Cycle through ascending -> descending -> no sort -> (start over) */ private void clickOnColumnAction(int index ) { if ( index == -1 ) { if ( treeColumnProperty.isComparable() ) if ( treeColumnProperty.isSortingColumn() ) { if (!treeColumnProperty.isSortOrderDescending()) setSortingOrder(false); else { noSorting(); } } else { setSortingColumn( index ); setSortingOrder( true ); } } else if ( tableModel.isComparableColumn( index ) ) { if ( tableModel.isSortingColumn( index ) ) { if (!tableModel.isSortOrderDescending()) setSortingOrder(false); else { noSorting(); } } else { setSortingColumn( index ); setSortingOrder( true ); } } } private void selectVisibleColumns() { setCurrentWidths(); String viewName = null; if ( getParent() != null ) viewName = getParent().getName(); if ( tableModel.selectVisibleColumns( viewName, treeTable.getColumnName(0), getSortedNodeTreeModel ().getRootDescription() ) ) { if ( tableModel.getVisibleSortingColumn() == -1 ) getSortedNodeTreeModel ().setSortedByProperty( null ); setTreePreferredWidth( treeColumnWidth ); for (int i=0; i < tableModel.getColumnCount(); i++) { setTableColumnPreferredWidth( tableModel.getArrayIndex( i ), tableModel.getVisibleColumnWidth( i ) ); } } } private void setCurrentWidths() { treeColumnWidth = treeTable.getColumnModel().getColumn( 0 ).getWidth(); for (int i=0; i < tableModel.getColumnCount(); i++) { int w = treeTable.getColumnModel().getColumn(i + 1).getWidth(); tableModel.setVisibleColumnWidth( i, w ); } } /** Do not initialize tree now. We will do it from our constructor. * [dafe] Used probably because this method is called *before* superclass * is fully created (constructor finished) which is horrible but I don't * have enough knowledge about this code to change it. */ void initializeTree () { } /** Initialize tree and treeTable. */ private void initializeTreeTable() { treeModel = createModel(); treeTable = new TreeTable(treeModel, tableModel); tree = ((TreeTable)treeTable).getTree(); defaultHeaderRenderer = treeTable.getTableHeader().getDefaultRenderer(); treeTable.getTableHeader().setDefaultRenderer( new SortingHeaderRenderer() ); // init listener & attach it to closing of managerListener = new TreePropertyListener(); tree.addTreeExpansionListener (managerListener); // add listener to sort a new expanded folders tree.addTreeExpansionListener (new TreeExpansionListener () { public void treeExpanded (TreeExpansionEvent event) { TreePath path = event.getPath (); if (path != null) { // bugfix $32480, store and recover currently expanded subnodes // store expanded paths Enumeration en = TreeTableView.this.tree.getExpandedDescendants (path); // sort children getSortedNodeTreeModel ().sortChildren ((VisualizerNode)path.getLastPathComponent ()); // expand again folders while (en.hasMoreElements ()) { TreeTableView.this.tree.expandPath ((TreePath)en.nextElement ()); } } } public void treeCollapsed (TreeExpansionEvent event) { // ignore it } }); defaultActionListener = new PopupSupport(); treeTable.addFocusListener(defaultActionListener); tree.addMouseListener(defaultActionListener); tableMouseListener = new MouseUtils.PopupMouseAdapter() { public void showPopup(MouseEvent mevt) { if (isPopupAllowed()) { if ( mevt.getY() > treeTable.getHeight() ) // clear selection, if click under the table treeTable.clearSelection(); createPopup(mevt); } } }; treeTable.addMouseListener( tableMouseListener ); treeTable.setGridColor( UIManager.getColor("control") ); } /** Overrides JScrollPane's getAccessibleContext() method to use internal accessible context. */ public AccessibleContext getAccessibleContext() { if (accessContext == null) accessContext = new AccessibleTreeTableView(); return accessContext; } /** This is internal accessible context for TreeTableView. * It delegates setAccessibleName and setAccessibleDescription methods to set these properties * in underlying TreeTable as well. */ private class AccessibleTreeTableView extends AccessibleJScrollPane { AccessibleTreeTableView() { } public void setAccessibleName(String accessibleName) { super.setAccessibleName(accessibleName); if (treeTable != null) treeTable.getAccessibleContext().setAccessibleName(accessibleName); } public void setAccessibleDescription(String accessibleDescription) { super.setAccessibleDescription(accessibleDescription); if (treeTable != null) treeTable.getAccessibleContext().setAccessibleDescription(accessibleDescription); } } /** Initialize full support for horizontal scrolling. */ private void initializeTreeScrollSupport() { scrollPane = new JScrollPane() { public void setBorder(Border b) { super.setBorder(null); } }; scrollPane.getViewport().setBackground(UIManager.getColor("Table.background")); // NOI18N hScrollBar = new JScrollBar(JScrollBar.HORIZONTAL); hScrollBar.putClientProperty(MetalScrollBarUI.FREE_STANDING_PROP, Boolean.FALSE); listener = new ScrollListener(); treeTable.addPropertyChangeListener(listener); scrollPane.getViewport().addComponentListener(listener); tree.addPropertyChangeListener(listener); hScrollBar.getModel().addChangeListener(listener); } /* Overriden to work well with treeTable. */ public void setPopupAllowed (boolean value) { if (tree == null) { return; } if (popupListener == null && value) { // on popupListener = new PopupAdapter () { protected void showPopup (MouseEvent e) { int selRow = tree.getRowForLocation(e.getX(), e.getY()); if (!tree.isRowSelected(selRow)) { tree.setSelectionRow(selRow); } } }; tree.addMouseListener (popupListener); return; } if (popupListener != null && !value) { // off tree.removeMouseListener (popupListener); popupListener = null; return; } } /* Overriden to work well with treeTable. */ public void setDefaultActionAllowed(boolean value) { if (tree == null) return; defaultActionEnabled = value; if(value) { defaultTreeActionListener = new DefaultTreeAction(); treeTable.registerKeyboardAction( defaultTreeActionListener, KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false), JComponent.WHEN_FOCUSED ); } else { // Switch off. defaultTreeActionListener = null; treeTable.unregisterKeyboardAction( KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0, false) ); } } /** Set columns. * @param props each column is constructed from Node.Property */ public void setProperties(Property[] props) { tableModel.setProperties(props); treeColumnProperty.setProperty( tableModel.propertyForColumn( -1 ) ); if ( treeColumnProperty.isComparable() || tableModel.existsComparableColumn() ) { setAllowSortingByColumn( true ); if ( treeColumnProperty.isSortingColumn() ) { getSortedNodeTreeModel ().setSortedByName( true, !treeColumnProperty.isSortOrderDescending() ); } else { int index = tableModel.getVisibleSortingColumn(); if ( index != -1 ) { getSortedNodeTreeModel ().setSortedByProperty( tableModel.propertyForColumn( index ), !tableModel.isSortOrderDescending() ); } } } } /** Sets resize mode of table. * * @param mode - One of 5 legal values: <pre>JTable.AUTO_RESIZE_OFF, * JTable.AUTO_RESIZE_NEXT_COLUMN, * JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS, * JTable.AUTO_RESIZE_LAST_COLUMN, * JTable.AUTO_RESIZE_ALL_COLUMNS</pre> */ public final void setTableAutoResizeMode(int mode) { treeTable.setAutoResizeMode(mode); } /** Gets resize mode of table. * * @return mode - One of 5 legal values: <pre>JTable.AUTO_RESIZE_OFF, * JTable.AUTO_RESIZE_NEXT_COLUMN, * JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS, * JTable.AUTO_RESIZE_LAST_COLUMN, * JTable.AUTO_RESIZE_ALL_COLUMNS</pre> */ public final int getTableAutoResizeMode() { return treeTable.getAutoResizeMode(); } /** Sets preferred width of table column * @param index column index * @param width preferred column width */ public final void setTableColumnPreferredWidth(int index, int width) { tableModel.setArrayColumnWidth( index, width ); int j = tableModel.getVisibleIndex( index ); if ( j != -1 ) treeTable.getColumnModel().getColumn(j + 1).setPreferredWidth( width ); } /** Gets preferred width of table column * @param index column index * @return preferred column width */ public final int getTableColumnPreferredWidth(int index) { return tableModel.getArrayColumnWidth( index ); } /** Set preferred size of tree view * @param width preferred width of tree view */ public final void setTreePreferredWidth(int width) { treeTable.getColumnModel().getColumn(((TreeTable)treeTable).getTreeColumnIndex()).setPreferredWidth(width); } /** Get preferred size of tree view * @return preferred width of tree view */ public final int getTreePreferredWidth() { return treeTable.getColumnModel().getColumn(((TreeTable)treeTable).getTreeColumnIndex()).getPreferredWidth(); } public void addNotify() { // to allow displaying popup also in blank area if ( treeTable.getParent() != null ) { treeTable.getParent().addMouseListener( tableMouseListener ); } super.addNotify(); } public void removeNotify() { super.removeNotify(); // clear node listeners tableModel.setNodes(new Node[] {}); } public void addMouseListener(MouseListener l) { super.addMouseListener(l); treeTable.getTableHeader().addMouseListener(l); } public void removeMouseListener(MouseListener l) { super.removeMouseListener(l); treeTable.getTableHeader().removeMouseListener(l); } /* DnD is not implemented for treeTable. */ public void setDragSource (boolean state) { } /* DnD is not implemented for treeTable. */ public void setDropTarget (boolean state) { } /* Overriden to get position for popup invoked by keyboard */ Point getPositionForPopup() { int row = treeTable.getSelectedRow(); if ( row < 0 ) return null; int col = treeTable.getSelectedColumn(); if ( col < 0 ) col = 0; Rectangle r = null; if ( col == 0 ) r = tree.getRowBounds( row ); else r = treeTable.getCellRect( row, col, true ); Point p = SwingUtilities.convertPoint( treeTable, r.x, r.y, this); return p; } private void createPopup(MouseEvent e) { int xpos=e.getX(); int ypos=e.getY(); Point p = SwingUtilities.convertPoint(e.getComponent(),xpos, ypos,TreeTableView.this); int mxpos=(int)p.getX(); int mypos=(int)p.getY(); xpos -= ((TreeTable)treeTable).getPositionX(); if ( allowHideColumns || allowSortingByColumn ) { int col = treeTable.getColumnModel().getColumnIndexAtX( xpos ); super.createExtendedPopup( mxpos, mypos, getListMenu( col ) ); } else super.createPopup(mxpos, mypos); } /* creates List Options menu */ private JMenu getListMenu(final int col) { JMenu listItem = new JMenu( NbBundle.getBundle(NodeTableModel.class).getString("LBL_ListOptions") ); if ( allowHideColumns && col > 0 ) { JMenu colsItem = new JMenu( NbBundle.getBundle(NodeTableModel.class).getString("LBL_ColsMenu") ); boolean addColsItem = false; if ( col > 1 ) { JMenuItem moveLItem = new JMenuItem( NbBundle.getBundle(NodeTableModel.class).getString("LBL_MoveLeft") ); moveLItem.addActionListener( new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent actionEvent) { treeTable.getColumnModel().moveColumn( col, col - 1 ); } }); colsItem.add( moveLItem ); addColsItem = true; } if ( col < tableModel.getColumnCount() ) { JMenuItem moveRItem = new JMenuItem( NbBundle.getBundle(NodeTableModel.class).getString("LBL_MoveRight") ); moveRItem.addActionListener( new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent actionEvent) { treeTable.getColumnModel().moveColumn( col, col + 1 ); } }); colsItem.add( moveRItem ); addColsItem = true; } if ( addColsItem ) listItem.add( colsItem ); } if ( allowSortingByColumn ) { JMenu sortItem = new JMenu( NbBundle.getBundle(NodeTableModel.class).getString("LBL_SortMenu") ); JRadioButtonMenuItem noSortItem = new JRadioButtonMenuItem( NbBundle.getBundle(NodeTableModel.class).getString("LBL_NoSort"), !getSortedNodeTreeModel ().isSortingActive() ); noSortItem.addActionListener( new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent actionEvent) { noSorting(); } }); sortItem.add( noSortItem ); int visibleComparable = 0; JRadioButtonMenuItem colItem; if ( treeColumnProperty.isComparable() ) { visibleComparable++; colItem = new JRadioButtonMenuItem( treeTable.getColumnName(0), treeColumnProperty.isSortingColumn() ); colItem.setHorizontalTextPosition( SwingConstants.LEFT ); colItem.addActionListener( new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent actionEvent) { setSortingColumn( -1 ); } }); sortItem.add( colItem ); } for (int i=0; i < tableModel.getColumnCount(); i++) { if ( tableModel.isComparableColumn( i ) ) { visibleComparable++; colItem = new JRadioButtonMenuItem( tableModel.getColumnName( i ), tableModel.isSortingColumn( i ) ); colItem.setHorizontalTextPosition( SwingConstants.LEFT ); final int index = i; colItem.addActionListener( new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent actionEvent) { setSortingColumn( index ); } }); sortItem.add( colItem ); } } if ( visibleComparable > 0 ) { sortItem.addSeparator(); boolean current_sort; if ( treeColumnProperty.isSortingColumn() ) current_sort = treeColumnProperty.isSortOrderDescending(); else current_sort = tableModel.isSortOrderDescending(); JRadioButtonMenuItem ascItem = new JRadioButtonMenuItem( NbBundle.getBundle(NodeTableModel.class).getString("LBL_Ascending"), !current_sort ); ascItem.setHorizontalTextPosition( SwingConstants.LEFT ); ascItem.addActionListener( new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent actionEvent) { setSortingOrder( true ); } }); sortItem.add( ascItem ); JRadioButtonMenuItem descItem = new JRadioButtonMenuItem( NbBundle.getBundle(NodeTableModel.class).getString("LBL_Descending"), current_sort ); descItem.setHorizontalTextPosition( SwingConstants.LEFT ); descItem.addActionListener( new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent actionEvent) { setSortingOrder( false ); } }); sortItem.add( descItem ); if ( ! getSortedNodeTreeModel ().isSortingActive() ) { ascItem.setEnabled( false ); descItem.setEnabled( false ); } listItem.add( sortItem ); } } if ( allowHideColumns ) { JMenuItem visItem = new JMenuItem( NbBundle.getBundle(NodeTableModel.class).getString("LBL_ChangeColumns") ); visItem.addActionListener( new ActionListener() { public void actionPerformed(java.awt.event.ActionEvent actionEvent) { selectVisibleColumns(); } }); listItem.add( visItem ); } return listItem; } /* Sets column to be currently used for sorting */ private void setSortingColumn(int index) { tableModel.setSortingColumn( index ); if ( index != -1 ) { getSortedNodeTreeModel ().setSortedByProperty( tableModel.propertyForColumn( index ), !tableModel.isSortOrderDescending()); treeColumnProperty.setSortingColumn( false ); } else { getSortedNodeTreeModel ().setSortedByName( true, !treeColumnProperty.isSortOrderDescending() ); treeColumnProperty.setSortingColumn( true ); } // to change sort icon treeTable.getTableHeader().repaint(); } private void noSorting() { tableModel.setSortingColumn( -1 ); getSortedNodeTreeModel ().setNoSorting(); treeColumnProperty.setSortingColumn( false ); // to change sort icon treeTable.getTableHeader().repaint(); } /* Sets sorting order for current sorting. */ private void setSortingOrder(boolean ascending) { if ( treeColumnProperty.isSortingColumn() ) treeColumnProperty.setSortOrderDescending( !ascending ); else tableModel.setSortOrderDescending( !ascending ); getSortedNodeTreeModel ().setSortOrder( ascending ); // to change sort icon treeTable.getTableHeader().repaint(); } /* Horizontal scrolling support. */ private final class ScrollListener extends ComponentAdapter implements PropertyChangeListener, ChangeListener { ScrollListener() {} boolean movecorrection = false; //Column width public void propertyChange(PropertyChangeEvent evt) { if (((TreeTable)treeTable).getTreeColumnIndex() == -1) return; if ("width".equals(evt.getPropertyName())) { // NOI18N if (!treeTable.equals(evt.getSource())) { Dimension dim = hScrollBar.getPreferredSize(); dim.width = treeTable.getColumnModel().getColumn(((TreeTable)treeTable).getTreeColumnIndex()).getWidth(); hScrollBar.setPreferredSize(dim); hScrollBar.revalidate(); hScrollBar.repaint(); } revalidateScrollBar(); } else if ("positionX".equals(evt.getPropertyName())) { // NOI18N revalidateScrollBar(); } else if ("treeColumnIndex".equals(evt.getPropertyName())) { // NOI18N treeTable.getColumnModel().getColumn(((TreeTable)treeTable).getTreeColumnIndex()).addPropertyChangeListener(listener); } else if ("column_moved".equals(evt.getPropertyName())) { // NOI18N int from = ((Integer)evt.getOldValue()).intValue(); int to = ((Integer)evt.getNewValue()).intValue(); if ( from == 0 || to == 0 ) { if ( movecorrection ) movecorrection = false; else { movecorrection = true; // not allowed to move first, tree column treeTable.getColumnModel().moveColumn( to, from ); } return; } // module will be revalidated in NodeTableModel treeTable.getTableHeader().getColumnModel().getColumn( from ).setModelIndex( from ); treeTable.getTableHeader().getColumnModel().getColumn( to ).setModelIndex( to ); tableModel.moveColumn( from - 1, to - 1); } } //Viewport height public void componentResized(ComponentEvent e) { revalidateScrollBar(); } //ScrollBar change public void stateChanged(ChangeEvent evt) { int value = hScrollBar.getModel().getValue(); ((TreeTable)treeTable).setPositionX(value); } private void revalidateScrollBar() { int extentWidth = treeTable.getColumnModel().getColumn(((TreeTable)treeTable).getTreeColumnIndex()).getWidth(); int maxWidth = tree.getPreferredSize().width; int extentHeight = scrollPane.getViewport().getSize().height; int maxHeight = tree.getPreferredSize().height; int positionX = ((TreeTable)treeTable).getPositionX(); int value = Math.max(0, Math.min(positionX, maxWidth - extentWidth)); boolean hsbvisible = hScrollBar.isVisible(); boolean vsbvisible = scrollPane.getVerticalScrollBar().isVisible(); int hsbheight = hsbvisible ? hScrollBar.getHeight() : 0; int vsbwidth = scrollPane.getVerticalScrollBar().getWidth(); hScrollBar.setValues(value, extentWidth, 0, maxWidth); if (hideHScrollBar || maxWidth <= extentWidth || (vsbvisible && (maxHeight <= extentHeight + hsbheight && maxWidth <= extentWidth + vsbwidth))) hScrollBar.setVisible(false); else hScrollBar.setVisible(true); } } /** Scrollable (better say not scrollable) pane. Used as container for * left (controlling) and rigth (controlled) scroll panes. */ private static final class CompoundScrollPane extends JPanel implements Scrollable { CompoundScrollPane() {} public boolean getScrollableTracksViewportWidth() { return true; } public int getScrollableBlockIncrement(Rectangle visibleRect, int orientation, int direction) { return 10; } public boolean getScrollableTracksViewportHeight() { return true; } public Dimension getPreferredScrollableViewportSize() { return this.getPreferredSize(); } public int getScrollableUnitIncrement(Rectangle visibleRect, int orientation, int direction) { return 10; } } /** Invokes default action. */ private class DefaultTreeAction implements ActionListener { DefaultTreeAction() {} /** * Invoked when an action occurs. */ public void actionPerformed(ActionEvent e) { if (treeTable.getSelectedColumn() != ((TreeTable)treeTable).getTreeColumnIndex()) return; Node[] nodes = manager.getSelectedNodes(); if (nodes.length == 1) { SystemAction sa = nodes[0].getDefaultAction (); if (sa != null) { TreeView.invokeAction (sa, new ActionEvent (nodes[0], ActionEvent.ACTION_PERFORMED, "")); // NOI18N } } } } private synchronized SortedNodeTreeModel getSortedNodeTreeModel () { if (sortedNodeTreeModel == null) { sortedNodeTreeModel = new SortedNodeTreeModel(); } return sortedNodeTreeModel; } /* node tree model with added sorting support */ private class SortedNodeTreeModel extends NodeTreeModel { SortedNodeTreeModel() {} private Node.Property sortedByProperty; private boolean sortAscending = true; private Comparator rowComparator; private boolean sortedByName = false; private boolean noSorting = false; void setNoSorting() { noSorting = true; setSortedByProperty( null ); setSortedByName( false ); } boolean isSortingActive () { return ( sortedByProperty != null || sortedByName ); } void setSortedByProperty(Node.Property prop) { if (sortedByProperty == prop) return; sortedByProperty = prop; if (prop == null) rowComparator = null; else sortedByName = false; sortingChanged(); } void setSortedByProperty(Node.Property prop, boolean ascending) { if (sortedByProperty == prop && ascending == sortAscending) return; sortedByProperty = prop; sortAscending = ascending; if (prop == null) rowComparator = null; else sortedByName = false; sortingChanged(); } void setSortedByName(boolean sorted, boolean ascending) { if (sortedByName == sorted && ascending == sortAscending) return; sortedByName = sorted; sortAscending = ascending; if ( sortedByName ) sortedByProperty = null; sortingChanged(); } void setSortedByName(boolean sorted) { sortedByName = sorted; if ( sortedByName ) sortedByProperty = null; sortingChanged(); } void setSortOrder(boolean ascending) { if (ascending == sortAscending) return; sortAscending = ascending; sortingChanged(); } private Node.Property getNodeProperty(Node node, Node.Property prop) { Node.PropertySet[] propsets = node.getPropertySets(); for (int i = 0, n = propsets.length; i < n; i++) { Node.Property[] props = propsets[i].getProperties(); for (int j = 0, m = props.length; j < m; j++) { if (props[j].equals(prop)) { return props[j]; } } } return null; } synchronized Comparator getRowComparator() { if (rowComparator == null) { rowComparator = new Comparator() { public int compare(Object o1, Object o2) { if (o1 == o2) return 0; Node n1 = ((VisualizerNode) o1).node; Node n2 = ((VisualizerNode) o2).node; if (n1 == null && n2 == null) return 0; if (n1 == null) return 1; if (n2 == null) return -1; if (n1.getParentNode () == null || n2.getParentNode () == null) { // PENDING: throw Exception ErrorManager.getDefault ().log ("Warning: TTV.compare: Node " + n1 + " or " + n2 + " has no parent!"); // NOI18N return 0; } if (!(n1.getParentNode ().equals (n2.getParentNode ()))) { // PENDING: throw Exception ErrorManager.getDefault ().log ("Warning: TTV.compare: Nodes " + n1 + " and " + n2 + " has different parent!"); // NOI18N return 0; } int res = 0; if ( sortedByName ) { res = n1.getDisplayName().compareTo(n2.getDisplayName()); return sortAscending ? res : -res; } Node.Property p1 = getNodeProperty(n1, sortedByProperty); Node.Property p2 = getNodeProperty(n2, sortedByProperty); if ( p1 == null && p2 == null ) { return 0; } try { if ( p1 == null ) res = -1; else if ( p2 == null ) res = 1; else { Object v1 = p1.getValue(); Object v2 = p2.getValue(); if ( v1 == null && v2 == null ) return 0; else if( v1 == null ) res = -1; else if ( v2 == null ) res = 1; else { if (v1.getClass() != v2.getClass() || !(v1 instanceof Comparable)) { v1 = v1.toString(); v2 = v2.toString(); } res = ((Comparable)v1).compareTo(v2); } } return sortAscending ? res : -res; } catch (Exception ex) { ErrorManager.getDefault ().notify (ErrorManager.INFORMATIONAL, ex); return 0; } } }; } return rowComparator; } void sortChildren (VisualizerNode parent) { final Comparator comparator = getRowComparator(); if (comparator == null || parent == null) { return; } java.util.List nodeList = parent.getChildren (); int size = nodeList.size (); java.util.List newOrder = new ArrayList (nodeList); for (int i = 0; i < size; i++) { newOrder.set (i, nodeList.get (i)); } int [] perm = new int[size]; boolean ignore = false; if (isSortingActive ()) { Collections.sort(newOrder, comparator); for (int i = 0; i < size; i++) { perm[i] = newOrder.indexOf (nodeList.get (i)); } } else { Node root = parent.node; java.util.List originalSort = Arrays.asList (root.getChildren ().getNodes ()); // check sizes if (size != originalSort.size ()) { ignore = true; } // calculate permutation for (int i = 0; !ignore && i < size; i++) { perm[i] = originalSort.indexOf (( (VisualizerNode)nodeList.get (i)).node ); ignore = perm[i] == -1; } } // ??? how to call reorder better // parent.childrenReordered (new NodeReorderEvent (parent.node, perm)); // workaround, call helper method in VisualizerNode if (!ignore) { parent.doChildrenReordered (perm); } } void sortingChanged() { // PENDING: remember the last sorting to avoid multiple sorting // remenber expanded folders TreeNode tn = (TreeNode)(this.getRoot()); java.util.List list = new ArrayList(); Enumeration en = TreeTableView.this.tree.getExpandedDescendants( new TreePath( tn ) ); while ( en != null && en.hasMoreElements() ) { TreePath path = (TreePath)en.nextElement(); // bugfix #32328, don't sort whole subtree but only expanded folders sortChildren ((VisualizerNode)path.getLastPathComponent ()); list.add( path ); } // expand again folders for (int i=0; i<list.size(); i++) { TreeTableView.this.tree.expandPath( (TreePath)list.get( i ) ); } } String getRootDescription() { if ( getRoot() instanceof VisualizerNode ) { sortChildren ((VisualizerNode)getRoot ()); return ((VisualizerNode)getRoot()).getShortDescription(); } return ""; // NOI18N } // overrided mothod from DefaultTreeModel public void nodesWereInserted (TreeNode node, int[] childIndices) { super.nodesWereInserted (node, childIndices); if (node instanceof VisualizerNode && isSortingActive ()) { sortChildren ((VisualizerNode)node); } } // overrided mothod from DefaultTreeModel public void nodesChanged(TreeNode node, int[] childIndices) { super.nodesChanged (node, childIndices); if (node != null && childIndices != null && isSortingActive ()) { sortChildren ((VisualizerNode)node); } } // overrided mothod from DefaultTreeModel public void setRoot(TreeNode root) { super.setRoot (root); if (root instanceof VisualizerNode && isSortingActive ()) { sortChildren ((VisualizerNode)root); } } } /* Cell renderer for sorting column header. */ private class SortingHeaderRenderer extends DefaultTableCellRenderer { SortingHeaderRenderer() {} /** Overrides superclass method. */ public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column) { Component comp = defaultHeaderRenderer.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column); if ( comp instanceof JLabel ) { if ( column == 0 && treeColumnProperty.isSortingColumn() ) { ((JLabel)comp).setIcon( getProperIcon( treeColumnProperty.isSortOrderDescending() ) ); ((JLabel)comp).setHorizontalTextPosition( SwingConstants.LEFT ); comp.setFont( comp.getFont().deriveFont( Font.BOLD ) ); } else if ( column != 0 && tableModel.getVisibleSortingColumn() + 1 == column ) { ((JLabel)comp).setIcon( getProperIcon( tableModel.isSortOrderDescending() ) ); ((JLabel)comp).setHorizontalTextPosition( SwingConstants.LEFT ); comp.setFont( comp.getFont().deriveFont( Font.BOLD ) ); } else ((JLabel)comp).setIcon( null ); } return comp; } private ImageIcon getProperIcon(boolean descending) { if ( descending ) return new ImageIcon ( org.openide.util.Utilities.loadImage( SORT_DESC_ICON ) ); else return new ImageIcon ( org.openide.util.Utilities.loadImage( SORT_ASC_ICON ) ); } } // End of inner class SortingHeaderRenderer. private class TreeColumnProperty { TreeColumnProperty() {} private Property p = null; void setProperty (Property p ) { this.p = p; } boolean isComparable() { if ( p == null ) return false; Object o = p.getValue( NodeTableModel.ATTR_COMPARABLE_COLUMN ); if ( o != null && o instanceof Boolean ) return ((Boolean)o).booleanValue(); return false; } boolean isSortingColumn() { if ( p == null ) return false; Object o = p.getValue( NodeTableModel.ATTR_SORTING_COLUMN ); if ( o != null && o instanceof Boolean ) return ((Boolean)o).booleanValue(); return false; } void setSortingColumn( boolean sorting ) { if ( p == null ) return; p.setValue( NodeTableModel.ATTR_SORTING_COLUMN, sorting ? Boolean.TRUE : Boolean.FALSE ); } boolean isSortOrderDescending() { if ( p == null ) return false; Object o = p.getValue( NodeTableModel.ATTR_DESCENDING_ORDER ); if ( o != null && o instanceof Boolean ) return ((Boolean)o).booleanValue(); return false; } void setSortOrderDescending(boolean descending) { if ( p == null ) return; p.setValue( NodeTableModel.ATTR_DESCENDING_ORDER, descending ? Boolean.TRUE : Boolean.FALSE ); } } /* For testing - use internal execution public static void main(String[] args) { SwingUtilities.invokeLater(new Runnable() { public void run() { Node n = //new org.netbeans.core.ModuleNode(); RepositoryNodeFactory.getDefault().repository(DataFilter.ALL); org.openide.explorer.ExplorerManager em = new org.openide.explorer.ExplorerManager(); em.setRootContext(n); org.openide.explorer.ExplorerPanel ep = new org.openide.explorer.ExplorerPanel(em); ep.setLayout (new BorderLayout ()); ep.setBorder(new EmptyBorder(20, 20, 20, 20)); TreeTableView ttv = new TreeTableView(); ttv.setRootVisible(false); ttv.setPopupAllowed(true); ttv.setDefaultActionAllowed(true); ttv.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS ); ttv.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_NEVER ); org.openide.nodes.PropertySupport.ReadOnly prop2 = new org.openide.nodes.PropertySupport.ReadOnly ( "name", // NOI18N String.class, "name", "Name Tooltip" ) { public Object getValue () { return null; } }; //prop2.setValue( "InvisibleInTreeTableView", Boolean.TRUE ); prop2.setValue( "SortingColumnTTV", Boolean.TRUE ); prop2.setValue( "DescendingOrderTTV", Boolean.TRUE ); prop2.setValue( "ComparableColumnTTV", Boolean.TRUE ); ttv.setProperties( // n.getChildren().getNodes()[0].getPropertySets()[0].getProperties()); new Property[]{ new org.openide.nodes.PropertySupport.ReadWrite ( "hidden", // NOI18N Boolean.TYPE, "hidden", "Hidden tooltip" ) { public Object getValue () { return null; } public void setValue (Object o) { } }, prop2, new org.openide.nodes.PropertySupport.ReadOnly ( "template", // NOI18N Boolean.TYPE, "template", "Template Tooltip" ) { public Object getValue () { return null; } } } ); ttv.setTreePreferredWidth(200); ttv.setTableColumnPreferredWidth(0, 60); ttv.setTableColumnPreferredWidth(1, 150); ttv.setTableColumnPreferredWidth(2, 100); ep.add("Center", ttv); ep.open(); } }); } */ }