0001: /*
0002: * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
0003: *
0004: * Copyright 1997-2007 Sun Microsystems, Inc. All rights reserved.
0005: *
0006: * The contents of this file are subject to the terms of either the GNU
0007: * General Public License Version 2 only ("GPL") or the Common
0008: * Development and Distribution License("CDDL") (collectively, the
0009: * "License"). You may not use this file except in compliance with the
0010: * License. You can obtain a copy of the License at
0011: * http://www.netbeans.org/cddl-gplv2.html
0012: * or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
0013: * specific language governing permissions and limitations under the
0014: * License. When distributing the software, include this License Header
0015: * Notice in each file and include the License file at
0016: * nbbuild/licenses/CDDL-GPL-2-CP. Sun designates this
0017: * particular file as subject to the "Classpath" exception as provided
0018: * by Sun in the GPL Version 2 section of the License file that
0019: * accompanied this code. If applicable, add the following below the
0020: * License Header, with the fields enclosed by brackets [] replaced by
0021: * your own identifying information:
0022: * "Portions Copyrighted [year] [name of copyright owner]"
0023: *
0024: * Contributor(s):
0025: *
0026: * The Original Software is NetBeans. The Initial Developer of the Original
0027: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0028: * Microsystems, Inc. All Rights Reserved.
0029: *
0030: * If you wish your version of this file to be governed by only the CDDL
0031: * or only the GPL Version 2, indicate your decision by adding
0032: * "[Contributor] elects to include this software in this distribution
0033: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0034: * single choice of license, a recipient has the option to distribute
0035: * your version of this file under either the CDDL, the GPL Version 2 or
0036: * to extend the choice of license to its licensees as provided above.
0037: * However, if you add GPL Version 2 code and therefore, elected the GPL
0038: * Version 2 license, then the option applies only if the new code is
0039: * made subject to such option by the copyright holder.
0040: */
0041: package org.openide.explorer.view;
0042:
0043: import java.util.logging.Logger;
0044: import org.openide.awt.MouseUtils;
0045: import org.openide.nodes.Node;
0046: import org.openide.nodes.Node.Property;
0047: import org.openide.util.NbBundle;
0048: import org.openide.explorer.ExplorerManager;
0049: import org.openide.explorer.ExplorerManager.Provider;
0050: import org.openide.explorer.ExplorerUtils;
0051:
0052: import java.awt.*;
0053: import java.awt.event.*;
0054:
0055: import java.beans.PropertyChangeEvent;
0056: import java.beans.PropertyChangeListener;
0057:
0058: import java.util.ArrayList;
0059: import java.util.Comparator;
0060: import java.util.Enumeration;
0061: import java.util.HashSet;
0062: import java.util.Iterator;
0063: import java.util.logging.Level;
0064:
0065: import javax.accessibility.AccessibleContext;
0066:
0067: import javax.swing.*;
0068: import javax.swing.border.Border;
0069: import javax.swing.event.*;
0070: import javax.swing.plaf.metal.MetalScrollBarUI;
0071: import javax.swing.table.*;
0072: import javax.swing.tree.*;
0073: import org.openide.explorer.view.TreeView.PopupAdapter;
0074: import org.openide.explorer.view.TreeView.PopupSupport;
0075: import org.openide.explorer.view.TreeView.TreePropertyListener;
0076:
0077: /** Explorer view. Allows to view tree of nodes on the left
0078: * and its properties in table on the right.
0079: * <p>
0080: * The main mechanism for setting what properties are displayed is
0081: * <a href="#setProperties"><code>setProperties (Node.Property[])</code></a>.
0082: * Pass this method an
0083: * array of properties. These will act as a template, and properties of
0084: * the displayed nodes which share the same <i>name</i> will be used in
0085: * the columns of the table.
0086: *
0087: * You can customize behaviour
0088: * of property columns using <code>Property.setValue (String parameter,
0089: * Object value)</code>. For example,
0090: * assume you have following array of properties:
0091: * <br><code>org.openide.nodes.Node.Property[] properties</code><br>
0092: *
0093: * if you need second column to be initially invisible in TreeTableView, you
0094: * should set its custom parameter:
0095: * <br><code>properties[1].setValue ("InvisibleInTreeTableView", Boolean.TRUE);</code>
0096: *
0097: * <TABLE border="1" summary="custom parameter list">
0098: * <TR>
0099: * <TH> Parameter name
0100: * </TH>
0101: * <TH> Parameter type
0102: * </TH>
0103: * <TH> Description
0104: * </TH>
0105: * </TR>
0106: * <TR>
0107: * <TD> InvisibleInTreeTableView</TD>
0108: * <TD> Boolean </TD>
0109: * <TD> This property column should be initially invisible (hidden).</TD>
0110: * </TR>
0111: * <TR>
0112: * <TD> ComparableColumnTTV</TD>
0113: * <TD> Boolean </TD>
0114: * <TD> This property column should be used for sorting.</TD>
0115: * </TR>
0116: * <TR>
0117: * <TD> SortingColumnTTV</TD>
0118: * <TD> Boolean </TD>
0119: * <TD> TreeTableView should be initially sorted by this property column.</TD>
0120: * </TR>
0121: * <TR>
0122: * <TD> DescendingOrderTTV</TD>
0123: * <TD> Boolean </TD>
0124: * <TD> If this parameter and <code>SortingColumnTTV</code> is set, TreeTableView should
0125: * be initially sorted by this property columns in descending order.
0126: * </TD>
0127: * </TR>
0128: * <TR>
0129: * <TD> OrderNumberTTV</TD>
0130: * <TD> Integer </TD>
0131: * <TD> If this parameter is set to <code>N</code>, this property column will be
0132: * displayed as Nth column of table. If not set, column will be
0133: * displayed in natural order.
0134: * </TD>
0135: * </TR>
0136: * <TR>
0137: * <TD> TreeColumnTTV</TD>
0138: * <TD> Boolean </TD>
0139: * <TD> Identifies special property representing first (tree) column. To allow setting
0140: * of <code>SortingColumnTTV, DescendingOrderTTV, ComparableColumnTTV</code> parameters
0141: * also for first (tree) column, use this special parameter and add
0142: * this property to Node.Property[] array before calling
0143: * TreeTableView.setProperties (Node.Property[]).
0144: * </TD>
0145: * </TR>
0146: * <TR>
0147: * <TD> ColumnMnemonicCharTTV</TD>
0148: * <TD> String </TD>
0149: * <TD> When set, this parameter contains the mnemonic character for column's
0150: * display name (e.g. in <I>Change Visible Columns</I> dialog window).
0151: * If not set, no mnemonic will be displayed.
0152: * </TD>
0153: * </TR>
0154: * <TR>
0155: * <TD> ColumnDisplayNameWithMnemonicTTV</TD>
0156: * <TD> String </TD>
0157: * <TD> When set, this parameter contains column's display name with
0158: * '&' as the mnemonic. This parameter should be preferred over
0159: * ColumnMnemonicCharTTV.
0160: * </TD>
0161: * </TR>
0162: * </TABLE>
0163: *
0164: * <p>
0165: * This class is a <q>view</q>
0166: * to use it properly you need to add it into a component which implements
0167: * {@link Provider}. Good examples of that can be found
0168: * in {@link ExplorerUtils}. Then just use
0169: * {@link Provider#getExplorerManager} call to get the {@link ExplorerManager}
0170: * and control its state.
0171: * </p>
0172: * <p>
0173: * There can be multiple <q>views</q> under one container implementing {@link Provider}. Select from
0174: * range of predefined ones or write your own:
0175: * </p>
0176: * <ul>
0177: * <li>{@link org.openide.explorer.view.BeanTreeView} - shows a tree of nodes</li>
0178: * <li>{@link org.openide.explorer.view.ContextTreeView} - shows a tree of nodes without leaf nodes</li>
0179: * <li>{@link org.openide.explorer.view.ListView} - shows a list of nodes</li>
0180: * <li>{@link org.openide.explorer.view.IconView} - shows a rows of nodes with bigger icons</li>
0181: * <li>{@link org.openide.explorer.view.ChoiceView} - creates a combo box based on the explored nodes</li>
0182: * <li>{@link org.openide.explorer.view.TreeTableView} - shows tree of nodes together with a set of their {@link Property}</li>
0183: * <li>{@link org.openide.explorer.view.MenuView} - can create a {@link JMenu} structure based on structure of {@link Node}s</li>
0184: * </ul>
0185: * <p>
0186: * All of these views use {@link ExplorerManager#find} to walk up the AWT hierarchy and locate the
0187: * {@link ExplorerManager} to use as a controler. They attach as listeners to
0188: * it and also call its setter methods to update the shared state based on the
0189: * user action. Not all views make sence together, but for example
0190: * {@link org.openide.explorer.view.ContextTreeView} and {@link org.openide.explorer.view.ListView} were designed to complement
0191: * themselves and behaves like windows explorer. The {@link org.openide.explorer.propertysheet.PropertySheetView}
0192: * for example should be able to work with any other view.
0193: * </p>
0194: *
0195: * @author jrojcek
0196: * @since 1.7
0197: */
0198: public class TreeTableView extends BeanTreeView {
0199: // icon of column button
0200: private static final String COLUMNS_ICON = "/org/netbeans/modules/openide/explorer/columns.gif"; // NOI18N
0201:
0202: // icons of ascending/descending order in column header
0203: private static final String SORT_ASC_ICON = "org/netbeans/modules/openide/explorer/columnsSortedAsc.gif"; // NOI18N
0204: private static final String SORT_DESC_ICON = "org/netbeans/modules/openide/explorer/columnsSortedDesc.gif"; // NOI18N
0205:
0206: /** The table */
0207: protected JTable treeTable;
0208: private NodeTableModel tableModel;
0209:
0210: // Tree scroll support
0211: private JScrollBar hScrollBar;
0212: private JScrollPane scrollPane;
0213: private ScrollListener listener;
0214:
0215: // hiding columns allowed
0216: private boolean allowHideColumns = false;
0217:
0218: // sorting by column allowed
0219: private boolean allowSortingByColumn = false;
0220:
0221: // hide horizontal scrollbar
0222: private boolean hideHScrollBar = false;
0223:
0224: // button in corner of scroll pane
0225: private JButton colsButton = null;
0226:
0227: // tree model with sorting support
0228: private SortedNodeTreeModel sortedNodeTreeModel;
0229:
0230: /** Listener on keystroke to invoke default action */
0231: private ActionListener defaultTreeActionListener;
0232:
0233: // default treetable header renderer
0234: private TableCellRenderer defaultHeaderRenderer = null;
0235: private MouseUtils.PopupMouseAdapter tableMouseListener;
0236:
0237: /** Accessible context of this class (implemented by inner class AccessibleTreeTableView). */
0238: private AccessibleContext accessContext;
0239: private TreeColumnProperty treeColumnProperty = new TreeColumnProperty();
0240: private int treeColumnWidth;
0241: private Component treeTableParent = null;
0242:
0243: /** Create TreeTableView with default NodeTableModel
0244: */
0245: public TreeTableView() {
0246: this (new NodeTableModel());
0247: }
0248:
0249: /** Creates TreeTableView with provided NodeTableModel.
0250: * @param ntm node table model
0251: */
0252: public TreeTableView(NodeTableModel ntm) {
0253: tableModel = ntm;
0254:
0255: initializeTreeTable();
0256: setPopupAllowed(true);
0257: setDefaultActionAllowed(true);
0258:
0259: initializeTreeScrollSupport();
0260:
0261: // add scrollbar and scrollpane into a panel
0262: JPanel p = new CompoundScrollPane();
0263: p.setLayout(new BorderLayout());
0264: scrollPane.setViewportView(treeTable);
0265: p.add(BorderLayout.CENTER, scrollPane);
0266:
0267: ImageIcon ic = new ImageIcon(TreeTable.class
0268: .getResource(COLUMNS_ICON)); // NOI18N
0269: colsButton = new javax.swing.JButton(ic);
0270: colsButton.addActionListener(new ActionListener() {
0271: public void actionPerformed(ActionEvent evt) {
0272: selectVisibleColumns();
0273: }
0274: });
0275:
0276: JPanel sbp = new JPanel();
0277: sbp.setLayout(new FlowLayout(FlowLayout.LEFT, 0, 0));
0278: sbp.add(hScrollBar);
0279: p.add(BorderLayout.SOUTH, sbp);
0280:
0281: super
0282: .setHorizontalScrollBarPolicy(JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
0283: super
0284: .setVerticalScrollBarPolicy(JScrollPane.VERTICAL_SCROLLBAR_NEVER);
0285: setViewportView(p);
0286: setBorder(BorderFactory.createEmptyBorder()); //NOI18N
0287: setViewportBorder(BorderFactory.createEmptyBorder()); //NOI18N
0288: }
0289:
0290: public void setRowHeader(JViewport rowHeader) {
0291: rowHeader.setBorder(BorderFactory.createEmptyBorder());
0292: super .setRowHeader(rowHeader);
0293: }
0294:
0295: /* Overriden to allow hide special horizontal scrollbar
0296: */
0297: public void setHorizontalScrollBarPolicy(int policy) {
0298: hideHScrollBar = (policy == JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
0299:
0300: if (hideHScrollBar) {
0301: hScrollBar.setVisible(false);
0302: ((TreeTable) treeTable).setTreeHScrollingEnabled(false);
0303: }
0304: }
0305:
0306: /* Overriden to delegate policy of vertical scrollbar to inner scrollPane
0307: */
0308: public void setVerticalScrollBarPolicy(int policy) {
0309: if (scrollPane == null) {
0310: return;
0311: }
0312:
0313: allowHideColumns = (policy == JScrollPane.VERTICAL_SCROLLBAR_ALWAYS);
0314:
0315: if (allowHideColumns) {
0316: scrollPane.setCorner(JScrollPane.UPPER_RIGHT_CORNER,
0317: colsButton);
0318: }
0319:
0320: treeTable.getTableHeader().setReorderingAllowed(
0321: allowHideColumns);
0322:
0323: scrollPane.setVerticalScrollBarPolicy(policy);
0324: }
0325:
0326: protected NodeTreeModel createModel() {
0327: return getSortedNodeTreeModel();
0328: }
0329:
0330: /** Requests focus for the tree component. Overrides superclass method. */
0331: public void requestFocus() {
0332: if (treeTable != null) {
0333: treeTable.requestFocus();
0334: }
0335: }
0336:
0337: public boolean requestFocusInWindow() {
0338: boolean res = super .requestFocusInWindow();
0339:
0340: //#44856: pass the focus request to the treetable as well
0341: if (null != treeTable) {
0342: treeTable.requestFocus();
0343: }
0344:
0345: return res;
0346: }
0347:
0348: /* Sets sorting ability
0349: */
0350: private void setAllowSortingByColumn(boolean allow) {
0351: if (allow && (allow != allowSortingByColumn)) {
0352: addMouseListener(new MouseAdapter() {
0353: public void mouseClicked(MouseEvent evt) {
0354: // Check whether it was really a click
0355: if (evt.getClickCount() == 0)
0356: return;
0357: Component c = evt.getComponent();
0358:
0359: if (c instanceof JTableHeader) {
0360: JTableHeader h = (JTableHeader) c;
0361: int index = h.columnAtPoint(evt.getPoint());
0362:
0363: //issue 38442, column can be -1 if this is the
0364: //upper right corner - there's no column there,
0365: //so make sure it's an index >=0.
0366: if (index >= 0) {
0367: clickOnColumnAction(index - 1);
0368: }
0369: }
0370: }
0371: });
0372: }
0373:
0374: allowSortingByColumn = allow;
0375: }
0376:
0377: /* Change sorting after clicking on comparable column header.
0378: * Cycle through ascending -> descending -> no sort -> (start over)
0379: */
0380: private void clickOnColumnAction(int index) {
0381: if (index == -1) {
0382: if (treeColumnProperty.isComparable()) {
0383: if (treeColumnProperty.isSortingColumn()) {
0384: if (!treeColumnProperty.isSortOrderDescending()) {
0385: setSortingOrder(false);
0386: } else {
0387: noSorting();
0388: }
0389: } else {
0390: int realIndex = tableModel
0391: .translateVisibleColumnIndex(index);
0392: setSortingColumn(index);
0393: setSortingOrder(true);
0394: }
0395: }
0396: } else if (tableModel.isComparableColumn(index)) {
0397: if (tableModel.isSortingColumnEx(tableModel
0398: .translateVisibleColumnIndex(index))) {
0399: if (!tableModel.isSortOrderDescending()) {
0400: setSortingOrder(false);
0401: } else {
0402: noSorting();
0403: }
0404: } else {
0405: int realIndex = tableModel
0406: .translateVisibleColumnIndex(index);
0407: setSortingColumn(realIndex);
0408: setSortingOrder(true);
0409: }
0410: }
0411: }
0412:
0413: private void selectVisibleColumns() {
0414: setCurrentWidths();
0415:
0416: String viewName = null;
0417:
0418: if (getParent() != null) {
0419: viewName = getParent().getName();
0420: }
0421:
0422: if (tableModel.selectVisibleColumns(viewName, treeTable
0423: .getColumnName(0), getSortedNodeTreeModel()
0424: .getRootDescription())) {
0425: if (tableModel.getSortingColumn() == -1) {
0426: getSortedNodeTreeModel().setSortedByProperty(null);
0427: }
0428:
0429: setTreePreferredWidth(treeColumnWidth);
0430:
0431: for (int i = 0; i < tableModel.getColumnCount(); i++) {
0432: setTableColumnPreferredWidth(tableModel
0433: .getArrayIndex(i), tableModel
0434: .getVisibleColumnWidth(i));
0435: }
0436: }
0437: }
0438:
0439: private void setCurrentWidths() {
0440: treeColumnWidth = treeTable.getColumnModel().getColumn(0)
0441: .getWidth();
0442:
0443: for (int i = 0; i < tableModel.getColumnCount(); i++) {
0444: int w = treeTable.getColumnModel().getColumn(i + 1)
0445: .getWidth();
0446: tableModel.setVisibleColumnWidth(i, w);
0447: }
0448: }
0449:
0450: /** Do not initialize tree now. We will do it from our constructor.
0451: * [dafe] Used probably because this method is called *before* superclass
0452: * is fully created (constructor finished) which is horrible but I don't
0453: * have enough knowledge about this code to change it.
0454: */
0455: void initializeTree() {
0456: }
0457:
0458: /** Initialize tree and treeTable.
0459: */
0460: private void initializeTreeTable() {
0461: treeModel = createModel();
0462: treeTable = new TreeTable(treeModel, tableModel);
0463: tree = ((TreeTable) treeTable).getTree();
0464:
0465: defaultHeaderRenderer = treeTable.getTableHeader()
0466: .getDefaultRenderer();
0467: treeTable.getTableHeader().setDefaultRenderer(
0468: new SortingHeaderRenderer());
0469:
0470: // init listener & attach it to closing of
0471: managerListener = new TreePropertyListener();
0472: tree.addTreeExpansionListener(managerListener);
0473:
0474: // add listener to sort a new expanded folders
0475: tree.addTreeExpansionListener(new TreeExpansionListener() {
0476: public void treeExpanded(TreeExpansionEvent event) {
0477: TreePath path = event.getPath();
0478:
0479: if (path != null) {
0480: // bugfix $32480, store and recover currently expanded subnodes
0481: // store expanded paths
0482: Enumeration en = TreeTableView.this .tree
0483: .getExpandedDescendants(path);
0484:
0485: // sort children
0486: getSortedNodeTreeModel().sortChildren(
0487: (VisualizerNode) path
0488: .getLastPathComponent(), true);
0489:
0490: // expand again folders
0491: while (en.hasMoreElements()) {
0492: TreeTableView.this .tree
0493: .expandPath((TreePath) en.nextElement());
0494: }
0495: }
0496: }
0497:
0498: public void treeCollapsed(TreeExpansionEvent event) {
0499: // ignore it
0500: }
0501: });
0502:
0503: defaultActionListener = new PopupSupport();
0504: Action popupWrapper = new AbstractAction() {
0505: public void actionPerformed(ActionEvent evt) {
0506: SwingUtilities.invokeLater(defaultActionListener);
0507: }
0508:
0509: public boolean isEnabled() {
0510: return treeTable.isFocusOwner() || tree.isFocusOwner();
0511: }
0512: };
0513:
0514: treeTable.getInputMap(JTree.WHEN_ANCESTOR_OF_FOCUSED_COMPONENT)
0515: .put(
0516: KeyStroke.getKeyStroke(KeyEvent.VK_F10,
0517: KeyEvent.SHIFT_DOWN_MASK),
0518: "org.openide.actions.PopupAction");
0519: treeTable.getActionMap().put("org.openide.actions.PopupAction",
0520: popupWrapper);
0521: tree.addMouseListener(defaultActionListener);
0522:
0523: tableMouseListener = new MouseUtils.PopupMouseAdapter() {
0524: public void showPopup(MouseEvent mevt) {
0525: if (isPopupAllowed()) {
0526: if (mevt.getY() > treeTable.getHeight()) {
0527: // clear selection, if click under the table
0528: treeTable.clearSelection();
0529: } else {
0530: int selRow = treeTable.rowAtPoint(mevt
0531: .getPoint());
0532: boolean isAlreadySelected = false;
0533: int[] currentSelection = tree
0534: .getSelectionRows();
0535: for (int i = 0; null != currentSelection
0536: && i < currentSelection.length; i++) {
0537: if (selRow == currentSelection[i]) {
0538: isAlreadySelected = true;
0539: break;
0540: }
0541: }
0542: if (!isAlreadySelected)
0543: tree.setSelectionRow(selRow);
0544: }
0545:
0546: createPopup(mevt);
0547: }
0548: }
0549: };
0550: treeTable.addMouseListener(tableMouseListener);
0551:
0552: if (UIManager.getColor("control") != null) { // NOI18N
0553: treeTable.setGridColor(UIManager.getColor("control")); // NOI18N
0554: }
0555: }
0556:
0557: public void setSelectionMode(int mode) {
0558: super .setSelectionMode(mode);
0559:
0560: if (mode == TreeSelectionModel.SINGLE_TREE_SELECTION) {
0561: treeTable.getSelectionModel().setSelectionMode(
0562: ListSelectionModel.SINGLE_SELECTION);
0563: } else if (mode == TreeSelectionModel.CONTIGUOUS_TREE_SELECTION) {
0564: treeTable.getSelectionModel().setSelectionMode(
0565: ListSelectionModel.SINGLE_INTERVAL_SELECTION);
0566: } else if (mode == TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION) {
0567: treeTable.getSelectionModel().setSelectionMode(
0568: ListSelectionModel.MULTIPLE_INTERVAL_SELECTION);
0569: }
0570: }
0571:
0572: /** Overrides JScrollPane's getAccessibleContext() method to use internal accessible context.
0573: */
0574: public AccessibleContext getAccessibleContext() {
0575: if (accessContext == null) {
0576: accessContext = new AccessibleTreeTableView();
0577: }
0578:
0579: return accessContext;
0580: }
0581:
0582: /** Initialize full support for horizontal scrolling.
0583: */
0584: private void initializeTreeScrollSupport() {
0585: scrollPane = new JScrollPane();
0586: scrollPane.setName("TreeTableView.scrollpane"); //NOI18N
0587: scrollPane.setBorder(BorderFactory.createEmptyBorder());
0588: scrollPane.setViewportBorder(BorderFactory.createEmptyBorder());
0589:
0590: if (UIManager.getColor("Table.background") != null) { // NOI18N
0591: scrollPane.getViewport().setBackground(
0592: UIManager.getColor("Table.background")); // NOI18N
0593: }
0594:
0595: hScrollBar = new JScrollBar(JScrollBar.HORIZONTAL);
0596: hScrollBar.putClientProperty(
0597: MetalScrollBarUI.FREE_STANDING_PROP, Boolean.FALSE);
0598: hScrollBar.setVisible(false);
0599:
0600: listener = new ScrollListener();
0601:
0602: treeTable.addPropertyChangeListener(listener);
0603: scrollPane.getViewport().addComponentListener(listener);
0604: tree.addPropertyChangeListener(listener);
0605: hScrollBar.getModel().addChangeListener(listener);
0606: }
0607:
0608: /* Overriden to work well with treeTable.
0609: */
0610: public void setPopupAllowed(boolean value) {
0611: if (tree == null) {
0612: return;
0613: }
0614:
0615: if ((popupListener == null) && value) {
0616: // on
0617: popupListener = new PopupAdapter() {
0618: protected void showPopup(MouseEvent e) {
0619: int selRow = tree.getClosestRowForLocation(
0620: e.getX(), e.getY());
0621:
0622: if (!tree.isRowSelected(selRow)) {
0623: tree.setSelectionRow(selRow);
0624: }
0625: }
0626: };
0627:
0628: tree.addMouseListener(popupListener);
0629:
0630: return;
0631: }
0632:
0633: if ((popupListener != null) && !value) {
0634: // off
0635: tree.removeMouseListener(popupListener);
0636: popupListener = null;
0637:
0638: return;
0639: }
0640: }
0641:
0642: /* Overriden to work well with treeTable.
0643: */
0644: public void setDefaultActionAllowed(boolean value) {
0645: if (tree == null) {
0646: return;
0647: }
0648:
0649: defaultActionEnabled = value;
0650:
0651: if (value) {
0652: defaultTreeActionListener = new DefaultTreeAction();
0653: treeTable
0654: .registerKeyboardAction(defaultTreeActionListener,
0655: KeyStroke.getKeyStroke(KeyEvent.VK_ENTER,
0656: 0, false), JComponent.WHEN_FOCUSED);
0657: } else {
0658: // Switch off.
0659: defaultTreeActionListener = null;
0660: treeTable.unregisterKeyboardAction(KeyStroke.getKeyStroke(
0661: KeyEvent.VK_ENTER, 0, false));
0662: }
0663: }
0664:
0665: /** Set columns.
0666: * @param props each column is constructed from Node.Property
0667: */
0668: public void setProperties(Property[] props) {
0669: tableModel.setProperties(props);
0670: treeColumnProperty
0671: .setProperty(tableModel.propertyForColumn(-1));
0672:
0673: if (treeColumnProperty.isComparable()
0674: || tableModel.existsComparableColumn()) {
0675: setAllowSortingByColumn(true);
0676:
0677: if (treeColumnProperty.isSortingColumn()) {
0678: getSortedNodeTreeModel().setSortedByName(true,
0679: !treeColumnProperty.isSortOrderDescending());
0680: } else {
0681: int index = tableModel.getSortingColumn();
0682:
0683: if (index != -1) {
0684: getSortedNodeTreeModel().setSortedByProperty(
0685: tableModel.propertyForColumnEx(index),
0686: !tableModel.isSortOrderDescending());
0687: }
0688: }
0689: }
0690: }
0691:
0692: /** Sets resize mode of table.
0693: *
0694: * @param mode - One of 5 legal values: <pre>JTable.AUTO_RESIZE_OFF,
0695: * JTable.AUTO_RESIZE_NEXT_COLUMN,
0696: * JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS,
0697: * JTable.AUTO_RESIZE_LAST_COLUMN,
0698: * JTable.AUTO_RESIZE_ALL_COLUMNS</pre>
0699: */
0700: public final void setTableAutoResizeMode(int mode) {
0701: treeTable.setAutoResizeMode(mode);
0702: }
0703:
0704: /** Gets resize mode of table.
0705: *
0706: * @return mode - One of 5 legal values: <pre>JTable.AUTO_RESIZE_OFF,
0707: * JTable.AUTO_RESIZE_NEXT_COLUMN,
0708: * JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS,
0709: * JTable.AUTO_RESIZE_LAST_COLUMN,
0710: * JTable.AUTO_RESIZE_ALL_COLUMNS</pre>
0711: */
0712: public final int getTableAutoResizeMode() {
0713: return treeTable.getAutoResizeMode();
0714: }
0715:
0716: /** Sets preferred width of table column
0717: * @param index column index
0718: * @param width preferred column width
0719: */
0720: public final void setTableColumnPreferredWidth(int index, int width) {
0721: if (index == -1) {
0722: //Issue 47969 - sometimes this is called with a -1 arg
0723: return;
0724: }
0725:
0726: tableModel.setArrayColumnWidth(index, width);
0727:
0728: int j = tableModel.getVisibleIndex(index);
0729:
0730: if (j != -1) {
0731: treeTable.getColumnModel().getColumn(j + 1)
0732: .setPreferredWidth(width);
0733: }
0734: }
0735:
0736: /** Gets preferred width of table column
0737: * @param index column index
0738: * @return preferred column width
0739: */
0740: public final int getTableColumnPreferredWidth(int index) {
0741: int j = tableModel.getVisibleIndex(index);
0742:
0743: if (j != -1) {
0744: return treeTable.getColumnModel().getColumn(j + 1)
0745: .getPreferredWidth();
0746: } else {
0747: return tableModel.getArrayColumnWidth(index);
0748: }
0749: }
0750:
0751: /** Set preferred size of tree view
0752: * @param width preferred width of tree view
0753: */
0754: public final void setTreePreferredWidth(int width) {
0755: treeTable.getColumnModel().getColumn(
0756: ((TreeTable) treeTable).getTreeColumnIndex())
0757: .setPreferredWidth(width);
0758: }
0759:
0760: /** Get preferred size of tree view
0761: * @return preferred width of tree view
0762: */
0763: public final int getTreePreferredWidth() {
0764: return treeTable.getColumnModel().getColumn(
0765: ((TreeTable) treeTable).getTreeColumnIndex())
0766: .getPreferredWidth();
0767: }
0768:
0769: public void addNotify() {
0770: // to allow displaying popup also in blank area
0771: if (treeTable.getParent() != null) {
0772: treeTableParent = treeTable.getParent();
0773: treeTableParent.addMouseListener(tableMouseListener);
0774: }
0775:
0776: super .addNotify();
0777: if (tableModel.getRowCount() == 0) {
0778: //re-attach node listeners
0779: Node[] nodes = new Node[tree.getRowCount()];
0780:
0781: for (int i = 0; i < tree.getRowCount(); i++) {
0782: nodes[i] = Visualizer.findNode(tree.getPathForRow(i)
0783: .getLastPathComponent());
0784: }
0785:
0786: tableModel.setNodes(nodes);
0787: }
0788: listener.revalidateScrollBar();
0789: }
0790:
0791: public void removeNotify() {
0792: super .removeNotify();
0793:
0794: if (treeTableParent != null) { //IndexedEditorPanel
0795: treeTableParent.removeMouseListener(tableMouseListener);
0796: }
0797:
0798: treeTableParent = null;
0799:
0800: // clear node listeners
0801: tableModel.setNodes(new Node[] {});
0802: }
0803:
0804: public void addMouseListener(MouseListener l) {
0805: super .addMouseListener(l);
0806: treeTable.getTableHeader().addMouseListener(l);
0807: }
0808:
0809: public void removeMouseListener(MouseListener l) {
0810: super .removeMouseListener(l);
0811: treeTable.getTableHeader().removeMouseListener(l);
0812: }
0813:
0814: /**
0815: * Drag and drop is not supported in TreeTableView.
0816: */
0817: public void setDragSource(boolean state) {
0818: }
0819:
0820: /**
0821: * Drag and drop is not supported in TreeTableView.
0822: */
0823: public void setDropTarget(boolean state) {
0824: }
0825:
0826: /* Overriden to get position for popup invoked by keyboard
0827: */
0828: Point getPositionForPopup() {
0829: int row = treeTable.getSelectedRow();
0830:
0831: if (row < 0) {
0832: return null;
0833: }
0834:
0835: int col = treeTable.getSelectedColumn();
0836:
0837: if (col < 0) {
0838: col = 0;
0839: }
0840:
0841: Rectangle r = null;
0842:
0843: if (col == 0) {
0844: r = tree.getRowBounds(row);
0845: } else {
0846: r = treeTable.getCellRect(row, col, true);
0847: }
0848:
0849: Point p = SwingUtilities
0850: .convertPoint(treeTable, r.x, r.y, this );
0851:
0852: return p;
0853: }
0854:
0855: private void createPopup(MouseEvent e) {
0856: Point p = SwingUtilities.convertPoint(e.getComponent(), e
0857: .getX(), e.getY(), TreeTableView.this );
0858:
0859: createPopup(p.x, p.y);
0860:
0861: e.consume();
0862: }
0863:
0864: void createPopup(int xpos, int ypos) {
0865: int treeXpos = xpos - ((TreeTable) treeTable).getPositionX();
0866:
0867: if (allowHideColumns || allowSortingByColumn) {
0868: int col = treeTable.getColumnModel().getColumnIndexAtX(
0869: treeXpos);
0870: super .createExtendedPopup(xpos, ypos, getListMenu(col));
0871: } else {
0872: super .createPopup(xpos, ypos);
0873: }
0874: }
0875:
0876: /* creates List Options menu
0877: */
0878: private JMenu getListMenu(final int col) {
0879: JMenu listItem = new JMenu(NbBundle.getBundle(
0880: NodeTableModel.class).getString("LBL_ListOptions"));
0881:
0882: if (allowHideColumns && (col > 0)) {
0883: JMenu colsItem = new JMenu(NbBundle.getBundle(
0884: NodeTableModel.class).getString("LBL_ColsMenu"));
0885:
0886: boolean addColsItem = false;
0887:
0888: if (col > 1) {
0889: JMenuItem moveLItem = new JMenuItem(NbBundle.getBundle(
0890: NodeTableModel.class).getString("LBL_MoveLeft"));
0891: moveLItem.addActionListener(new ActionListener() {
0892: public void actionPerformed(
0893: java.awt.event.ActionEvent actionEvent) {
0894: treeTable.getColumnModel().moveColumn(col,
0895: col - 1);
0896: }
0897: });
0898: colsItem.add(moveLItem);
0899: addColsItem = true;
0900: }
0901:
0902: if (col < tableModel.getColumnCount()) {
0903: JMenuItem moveRItem = new JMenuItem(NbBundle.getBundle(
0904: NodeTableModel.class)
0905: .getString("LBL_MoveRight"));
0906: moveRItem.addActionListener(new ActionListener() {
0907: public void actionPerformed(
0908: java.awt.event.ActionEvent actionEvent) {
0909: treeTable.getColumnModel().moveColumn(col,
0910: col + 1);
0911: }
0912: });
0913: colsItem.add(moveRItem);
0914: addColsItem = true;
0915: }
0916:
0917: if (addColsItem) {
0918: listItem.add(colsItem);
0919: }
0920: }
0921:
0922: if (allowSortingByColumn) {
0923: JMenu sortItem = new JMenu(NbBundle.getBundle(
0924: NodeTableModel.class).getString("LBL_SortMenu"));
0925: JRadioButtonMenuItem noSortItem = new JRadioButtonMenuItem(
0926: NbBundle.getBundle(NodeTableModel.class).getString(
0927: "LBL_NoSort"), !getSortedNodeTreeModel()
0928: .isSortingActive());
0929: noSortItem.addActionListener(new ActionListener() {
0930: public void actionPerformed(
0931: java.awt.event.ActionEvent actionEvent) {
0932: noSorting();
0933: }
0934: });
0935: sortItem.add(noSortItem);
0936:
0937: int visibleComparable = 0;
0938: JRadioButtonMenuItem colItem;
0939:
0940: if (treeColumnProperty.isComparable()) {
0941: visibleComparable++;
0942: colItem = new JRadioButtonMenuItem(treeTable
0943: .getColumnName(0), treeColumnProperty
0944: .isSortingColumn());
0945: colItem.setHorizontalTextPosition(SwingConstants.LEFT);
0946: colItem.addActionListener(new ActionListener() {
0947: public void actionPerformed(
0948: java.awt.event.ActionEvent actionEvent) {
0949: setSortingColumn(-1);
0950: }
0951: });
0952: sortItem.add(colItem);
0953: }
0954:
0955: for (int i = 0; i < tableModel.getColumnCount(); i++) {
0956: if (tableModel.isComparableColumn(i)) {
0957: visibleComparable++;
0958: colItem = new JRadioButtonMenuItem(tableModel
0959: .getColumnName(i), tableModel
0960: .isSortingColumnEx(tableModel
0961: .translateVisibleColumnIndex(i)));
0962: colItem
0963: .setHorizontalTextPosition(SwingConstants.LEFT);
0964:
0965: final int index = tableModel
0966: .translateVisibleColumnIndex(i);
0967: colItem.addActionListener(new ActionListener() {
0968: public void actionPerformed(
0969: java.awt.event.ActionEvent actionEvent) {
0970: setSortingColumn(index);
0971: }
0972: });
0973: sortItem.add(colItem);
0974: }
0975: }
0976:
0977: //add invisible columns
0978: for (int i = 0; i < tableModel.getColumnCountEx(); i++) {
0979: if (tableModel.isComparableColumnEx(i)
0980: && !tableModel.isVisibleColumnEx(i)) {
0981: visibleComparable++;
0982: colItem = new JRadioButtonMenuItem(tableModel
0983: .getColumnNameEx(i), tableModel
0984: .isSortingColumnEx(i));
0985: colItem
0986: .setHorizontalTextPosition(SwingConstants.LEFT);
0987:
0988: final int index = i;
0989: colItem.addActionListener(new ActionListener() {
0990: public void actionPerformed(
0991: java.awt.event.ActionEvent actionEvent) {
0992: setSortingColumn(index);
0993: }
0994: });
0995: sortItem.add(colItem);
0996: }
0997: }
0998:
0999: if (visibleComparable > 0) {
1000: sortItem.addSeparator();
1001:
1002: boolean current_sort;
1003:
1004: if (treeColumnProperty.isSortingColumn()) {
1005: current_sort = treeColumnProperty
1006: .isSortOrderDescending();
1007: } else {
1008: current_sort = tableModel.isSortOrderDescending();
1009: }
1010:
1011: JRadioButtonMenuItem ascItem = new JRadioButtonMenuItem(
1012: NbBundle.getBundle(NodeTableModel.class)
1013: .getString("LBL_Ascending"),
1014: !current_sort);
1015: ascItem.setHorizontalTextPosition(SwingConstants.LEFT);
1016: ascItem.addActionListener(new ActionListener() {
1017: public void actionPerformed(
1018: java.awt.event.ActionEvent actionEvent) {
1019: setSortingOrder(true);
1020: }
1021: });
1022: sortItem.add(ascItem);
1023:
1024: JRadioButtonMenuItem descItem = new JRadioButtonMenuItem(
1025: NbBundle.getBundle(NodeTableModel.class)
1026: .getString("LBL_Descending"),
1027: current_sort);
1028: descItem.setHorizontalTextPosition(SwingConstants.LEFT);
1029: descItem.addActionListener(new ActionListener() {
1030: public void actionPerformed(
1031: java.awt.event.ActionEvent actionEvent) {
1032: setSortingOrder(false);
1033: }
1034: });
1035: sortItem.add(descItem);
1036:
1037: if (!getSortedNodeTreeModel().isSortingActive()) {
1038: ascItem.setEnabled(false);
1039: descItem.setEnabled(false);
1040: }
1041:
1042: listItem.add(sortItem);
1043: }
1044: }
1045:
1046: if (allowHideColumns) {
1047: JMenuItem visItem = new JMenuItem(NbBundle.getBundle(
1048: NodeTableModel.class)
1049: .getString("LBL_ChangeColumns"));
1050: visItem.addActionListener(new ActionListener() {
1051: public void actionPerformed(
1052: java.awt.event.ActionEvent actionEvent) {
1053: selectVisibleColumns();
1054: }
1055: });
1056:
1057: listItem.add(visItem);
1058: }
1059:
1060: return listItem;
1061: }
1062:
1063: /* Sets column to be currently used for sorting
1064: */
1065: private void setSortingColumn(int index) {
1066: tableModel.setSortingColumnEx(index);
1067:
1068: if (index != -1) {
1069: getSortedNodeTreeModel().setSortedByProperty(
1070: tableModel.propertyForColumnEx(index),
1071: !tableModel.isSortOrderDescending());
1072: treeColumnProperty.setSortingColumn(false);
1073: } else {
1074: getSortedNodeTreeModel().setSortedByName(true,
1075: !treeColumnProperty.isSortOrderDescending());
1076: treeColumnProperty.setSortingColumn(true);
1077: }
1078:
1079: // to change sort icon
1080: treeTable.getTableHeader().repaint();
1081: }
1082:
1083: private void noSorting() {
1084: tableModel.setSortingColumnEx(-1);
1085: getSortedNodeTreeModel().setNoSorting();
1086: treeColumnProperty.setSortingColumn(false);
1087:
1088: // to change sort icon
1089: treeTable.getTableHeader().repaint();
1090: }
1091:
1092: /* Sets sorting order for current sorting.
1093: */
1094: private void setSortingOrder(boolean ascending) {
1095: if (treeColumnProperty.isSortingColumn()) {
1096: treeColumnProperty.setSortOrderDescending(!ascending);
1097: } else {
1098: tableModel.setSortOrderDescending(!ascending);
1099: }
1100:
1101: getSortedNodeTreeModel().setSortOrder(ascending);
1102:
1103: // to change sort icon
1104: treeTable.getTableHeader().repaint();
1105: }
1106:
1107: private synchronized SortedNodeTreeModel getSortedNodeTreeModel() {
1108: if (sortedNodeTreeModel == null) {
1109: sortedNodeTreeModel = new SortedNodeTreeModel();
1110: }
1111:
1112: return sortedNodeTreeModel;
1113: }
1114:
1115: /** This is internal accessible context for TreeTableView.
1116: * It delegates setAccessibleName and setAccessibleDescription methods to set these properties
1117: * in underlying TreeTable as well.
1118: */
1119: private class AccessibleTreeTableView extends AccessibleJScrollPane {
1120: AccessibleTreeTableView() {
1121: }
1122:
1123: public void setAccessibleName(String accessibleName) {
1124: super .setAccessibleName(accessibleName);
1125:
1126: if (treeTable != null) {
1127: treeTable.getAccessibleContext().setAccessibleName(
1128: accessibleName);
1129: }
1130: }
1131:
1132: public void setAccessibleDescription(
1133: String accessibleDescription) {
1134: super .setAccessibleDescription(accessibleDescription);
1135:
1136: if (treeTable != null) {
1137: treeTable
1138: .getAccessibleContext()
1139: .setAccessibleDescription(accessibleDescription);
1140: }
1141: }
1142: }
1143:
1144: /* Horizontal scrolling support.
1145: */
1146: private final class ScrollListener extends ComponentAdapter
1147: implements PropertyChangeListener, ChangeListener {
1148: boolean movecorrection = false;
1149:
1150: ScrollListener() {
1151: }
1152:
1153: //Column width
1154: public void propertyChange(PropertyChangeEvent evt) {
1155: if (((TreeTable) treeTable).getTreeColumnIndex() == -1) {
1156: return;
1157: }
1158:
1159: if ("width".equals(evt.getPropertyName())) { // NOI18N
1160:
1161: if (!treeTable.equals(evt.getSource())) {
1162: Dimension dim = hScrollBar.getPreferredSize();
1163: dim.width = treeTable.getColumnModel().getColumn(
1164: ((TreeTable) treeTable)
1165: .getTreeColumnIndex()).getWidth();
1166: hScrollBar.setPreferredSize(dim);
1167: hScrollBar.revalidate();
1168: hScrollBar.repaint();
1169: }
1170:
1171: revalidateScrollBar();
1172: } else if ("positionX".equals(evt.getPropertyName())) { // NOI18N
1173: revalidateScrollBar();
1174: } else if ("treeColumnIndex".equals(evt.getPropertyName())) { // NOI18N
1175: treeTable.getColumnModel().getColumn(
1176: ((TreeTable) treeTable).getTreeColumnIndex())
1177: .addPropertyChangeListener(listener);
1178: } else if ("column_moved".equals(evt.getPropertyName())) { // NOI18N
1179:
1180: int from = ((Integer) evt.getOldValue()).intValue();
1181: int to = ((Integer) evt.getNewValue()).intValue();
1182:
1183: if ((from == 0) || (to == 0)) {
1184: if (movecorrection) {
1185: movecorrection = false;
1186: } else {
1187: movecorrection = true;
1188:
1189: // not allowed to move first, tree column
1190: treeTable.getColumnModel().moveColumn(to, from);
1191: }
1192:
1193: return;
1194: }
1195:
1196: // module will be revalidated in NodeTableModel
1197: treeTable.getTableHeader().getColumnModel().getColumn(
1198: from).setModelIndex(from);
1199: treeTable.getTableHeader().getColumnModel().getColumn(
1200: to).setModelIndex(to);
1201: tableModel.moveColumn(from - 1, to - 1);
1202: }
1203: }
1204:
1205: //Viewport height
1206: public void componentResized(ComponentEvent e) {
1207: revalidateScrollBar();
1208: }
1209:
1210: //ScrollBar change
1211: public void stateChanged(ChangeEvent evt) {
1212: int value = hScrollBar.getModel().getValue();
1213: ((TreeTable) treeTable).setPositionX(value);
1214: }
1215:
1216: private void revalidateScrollBar() {
1217: if (!isDisplayable()) {
1218: return;
1219: }
1220:
1221: if ((treeTable.getColumnModel().getColumnCount() > 0)
1222: && (((TreeTable) treeTable).getTreeColumnIndex() >= 0)) {
1223: int extentWidth = treeTable.getColumnModel().getColumn(
1224: ((TreeTable) treeTable).getTreeColumnIndex())
1225: .getWidth();
1226: int maxWidth = tree.getPreferredSize().width;
1227: int extentHeight = scrollPane.getViewport().getSize().height;
1228: int maxHeight = tree.getPreferredSize().height;
1229: int positionX = ((TreeTable) treeTable).getPositionX();
1230:
1231: int value = Math.max(0, Math.min(positionX, maxWidth
1232: - extentWidth));
1233:
1234: boolean hsbvisible = hScrollBar.isVisible();
1235: boolean vsbvisible = scrollPane.getVerticalScrollBar()
1236: .isVisible();
1237: int hsbheight = hsbvisible ? hScrollBar.getHeight() : 0;
1238: int vsbwidth = scrollPane.getVerticalScrollBar()
1239: .getWidth();
1240:
1241: hScrollBar.setValues(value, extentWidth, 0, maxWidth);
1242:
1243: if (hideHScrollBar
1244: || (maxWidth <= extentWidth)
1245: || (vsbvisible && ((maxHeight <= (extentHeight + hsbheight)) && (maxWidth <= (extentWidth + vsbwidth))))) {
1246: hScrollBar.setVisible(false);
1247: } else {
1248: hScrollBar.setVisible(true);
1249: }
1250: }
1251: }
1252: }
1253:
1254: /** Scrollable (better say not scrollable) pane. Used as container for
1255: * left (controlling) and rigth (controlled) scroll panes.
1256: */
1257: private static final class CompoundScrollPane extends JPanel
1258: implements Scrollable {
1259: CompoundScrollPane() {
1260: }
1261:
1262: public void setBorder(Border b) {
1263: //do nothing
1264: }
1265:
1266: public boolean getScrollableTracksViewportWidth() {
1267: return true;
1268: }
1269:
1270: public int getScrollableBlockIncrement(Rectangle visibleRect,
1271: int orientation, int direction) {
1272: return 10;
1273: }
1274:
1275: public boolean getScrollableTracksViewportHeight() {
1276: return true;
1277: }
1278:
1279: public Dimension getPreferredScrollableViewportSize() {
1280: return this .getPreferredSize();
1281: }
1282:
1283: public int getScrollableUnitIncrement(Rectangle visibleRect,
1284: int orientation, int direction) {
1285: return 10;
1286: }
1287: }
1288:
1289: /** Invokes default action.
1290: */
1291: private class DefaultTreeAction implements ActionListener {
1292: DefaultTreeAction() {
1293: }
1294:
1295: /**
1296: * Invoked when an action occurs.
1297: */
1298: public void actionPerformed(ActionEvent e) {
1299: if (treeTable.getSelectedColumn() != ((TreeTable) treeTable)
1300: .getTreeColumnIndex()) {
1301: return;
1302: }
1303:
1304: Node[] nodes = manager.getSelectedNodes();
1305:
1306: if (nodes.length == 1) {
1307: Action a = nodes[0].getPreferredAction();
1308:
1309: if (a != null) {
1310: if (a.isEnabled()) {
1311: a.actionPerformed(new ActionEvent(nodes[0],
1312: ActionEvent.ACTION_PERFORMED, "")); // NOI18N
1313: } else {
1314: Toolkit.getDefaultToolkit().beep();
1315: }
1316: }
1317: }
1318: }
1319: }
1320:
1321: /* node tree model with added sorting support
1322: */
1323: private class SortedNodeTreeModel extends NodeTreeModel {
1324: private Node.Property sortedByProperty;
1325: private boolean sortAscending = true;
1326: private Comparator<VisualizerNode> rowComparator;
1327: private boolean sortedByName = false;
1328: private SortingTask sortingTask = null;
1329:
1330: SortedNodeTreeModel() {
1331: }
1332:
1333: void setNoSorting() {
1334: setSortedByProperty(null);
1335: setSortedByName(false);
1336: sortingChanged();
1337: }
1338:
1339: boolean isSortingActive() {
1340: return ((sortedByProperty != null) || sortedByName);
1341: }
1342:
1343: void setSortedByProperty(Node.Property prop) {
1344: if (sortedByProperty == prop) {
1345: return;
1346: }
1347:
1348: sortedByProperty = prop;
1349:
1350: if (prop == null) {
1351: rowComparator = null;
1352: } else {
1353: sortedByName = false;
1354: }
1355:
1356: sortingChanged();
1357: }
1358:
1359: void setSortedByProperty(Node.Property prop, boolean ascending) {
1360: if ((sortedByProperty == prop)
1361: && (ascending == sortAscending)) {
1362: return;
1363: }
1364:
1365: sortedByProperty = prop;
1366: sortAscending = ascending;
1367:
1368: if (prop == null) {
1369: rowComparator = null;
1370: } else {
1371: sortedByName = false;
1372: }
1373:
1374: sortingChanged();
1375: }
1376:
1377: void setSortedByName(boolean sorted, boolean ascending) {
1378: if ((sortedByName == sorted)
1379: && (ascending == sortAscending)) {
1380: return;
1381: }
1382:
1383: sortedByName = sorted;
1384: sortAscending = ascending;
1385:
1386: if (sortedByName) {
1387: sortedByProperty = null;
1388: }
1389:
1390: sortingChanged();
1391: }
1392:
1393: void setSortedByName(boolean sorted) {
1394: sortedByName = sorted;
1395:
1396: if (sortedByName) {
1397: sortedByProperty = null;
1398: }
1399:
1400: sortingChanged();
1401: }
1402:
1403: void setSortOrder(boolean ascending) {
1404: if (ascending == sortAscending) {
1405: return;
1406: }
1407:
1408: sortAscending = ascending;
1409: sortingChanged();
1410: }
1411:
1412: private Node.Property getNodeProperty(Node node,
1413: Node.Property prop) {
1414: Node.PropertySet[] propsets = node.getPropertySets();
1415:
1416: for (int i = 0, n = propsets.length; i < n; i++) {
1417: Node.Property[] props = propsets[i].getProperties();
1418:
1419: for (int j = 0, m = props.length; j < m; j++) {
1420: if (props[j].equals(prop)) {
1421: return props[j];
1422: }
1423: }
1424: }
1425:
1426: return null;
1427: }
1428:
1429: synchronized Comparator<VisualizerNode> getRowComparator() {
1430: if (rowComparator == null) {
1431: rowComparator = new Comparator<VisualizerNode>() {
1432:
1433: public int compare(VisualizerNode o1,
1434: VisualizerNode o2) {
1435: if (o1 == o2) {
1436: return 0;
1437: }
1438: Node n1 = o1.node;
1439: Node n2 = o2.node;
1440:
1441: if ((n1 == null) && (n2 == null)) {
1442: return 0;
1443: }
1444: if (n1 == null) {
1445: return 1;
1446: }
1447: if (n2 == null) {
1448: return -1;
1449: }
1450: if ((n1.getParentNode() == null)
1451: || (n2.getParentNode() == null)) {
1452: // PENDING: throw Exception
1453: Logger.getAnonymousLogger().warning(
1454: "TTV.compare: Node " + n1 + " or "
1455: + n2 + " has no parent!");
1456: return 0;
1457: }
1458: if (!(n1.getParentNode().equals(n2
1459: .getParentNode()))) {
1460: // PENDING: throw Exception
1461: Logger.getAnonymousLogger().warning(
1462: "TTV.compare: Nodes " + n1
1463: + " and " + n2
1464: + " has different parent!");
1465: return 0;
1466: }
1467: int res = 0;
1468:
1469: if (sortedByName) {
1470: res = n1.getDisplayName().compareTo(
1471: n2.getDisplayName());
1472: return sortAscending ? res : (-res);
1473: }
1474: Property p1 = getNodeProperty(n1,
1475: sortedByProperty);
1476: Property p2 = getNodeProperty(n2,
1477: sortedByProperty);
1478:
1479: if ((p1 == null) && (p2 == null)) {
1480: return 0;
1481: }
1482: try {
1483: if (p1 == null) {
1484: res = -1;
1485: } else if (p2 == null) {
1486: res = 1;
1487: } else {
1488: Object v1 = p1.getValue();
1489: Object v2 = p2.getValue();
1490:
1491: if ((v1 == null) && (v2 == null)) {
1492: return 0;
1493: } else if (v1 == null) {
1494: res = -1;
1495: } else if (v2 == null) {
1496: res = 1;
1497: } else {
1498: if ((v1.getClass() != v2.getClass())
1499: || !(v1 instanceof Comparable)) {
1500: v1 = v1.toString();
1501: v2 = v2.toString();
1502: }
1503: res = ((Comparable) v1)
1504: .compareTo(v2);
1505: }
1506: }
1507: return sortAscending ? res : (-res);
1508: } catch (Exception ex) {
1509: Logger.getLogger(
1510: TreeTableView.class.getName()).log(
1511: Level.WARNING, null, ex);
1512: return 0;
1513: }
1514: }
1515: };
1516: }
1517:
1518: return rowComparator;
1519: }
1520:
1521: void sortChildren(VisualizerNode parent, boolean synchronous) {
1522: //#37802 - resorts are processed too aggressively, causing
1523: //NPEs. Except for user-invoked actions (clicking the column
1524: //header, etc.), we will defer them to run later on the EQ, so
1525: //the change in the node has a chance to be fully processed
1526: if (synchronous) {
1527: synchronized (this ) {
1528: if (sortingTask != null) {
1529: sortingTask.remove(parent);
1530:
1531: if (sortingTask.isEmpty()) {
1532: sortingTask = null;
1533: }
1534: }
1535: }
1536:
1537: doSortChildren(parent);
1538: } else {
1539: synchronized (this ) {
1540: if (sortingTask == null) {
1541: sortingTask = new SortingTask();
1542: SwingUtilities.invokeLater(sortingTask);
1543: }
1544: }
1545:
1546: sortingTask.add(parent);
1547: }
1548: }
1549:
1550: void doSortChildren(VisualizerNode parent) {
1551: if (isSortingActive()) {
1552: final Comparator<VisualizerNode> comparator = getRowComparator();
1553:
1554: if ((comparator != null) || (parent != null)) {
1555: parent.reorderChildren(comparator);
1556: }
1557: } else {
1558: parent.naturalOrder();
1559: }
1560: }
1561:
1562: void sortingChanged() {
1563: // PENDING: remember the last sorting to avoid multiple sorting
1564: // remenber expanded folders
1565: TreeNode tn = (TreeNode) (this .getRoot());
1566: java.util.List<TreePath> list = new ArrayList<TreePath>();
1567: Enumeration<TreePath> en = TreeTableView.this .tree
1568: .getExpandedDescendants(new TreePath(tn));
1569:
1570: while ((en != null) && en.hasMoreElements()) {
1571: TreePath path = en.nextElement();
1572:
1573: // bugfix #32328, don't sort whole subtree but only expanded folders
1574: sortChildren((VisualizerNode) path
1575: .getLastPathComponent(), true);
1576: list.add(path);
1577: }
1578:
1579: // expand again folders
1580: for (int i = 0; i < list.size(); i++) {
1581: TreeTableView.this .tree.expandPath(list.get(i));
1582: }
1583: }
1584:
1585: String getRootDescription() {
1586: if (getRoot() instanceof VisualizerNode) {
1587: //#37802 commenting this out - unfathomable why you would need
1588: //to sort the root's children in order to get its short
1589: //description - Tim
1590: // sortChildren ((VisualizerNode)getRoot ());
1591: return ((VisualizerNode) getRoot())
1592: .getShortDescription();
1593: }
1594:
1595: return ""; // NOI18N
1596: }
1597:
1598: // overrided mothod from DefaultTreeModel
1599: public void nodesWereInserted(TreeNode node, int[] childIndices) {
1600: super .nodesWereInserted(node, childIndices);
1601:
1602: if (node instanceof VisualizerNode && isSortingActive()) {
1603: sortChildren((VisualizerNode) node, false);
1604: }
1605: }
1606:
1607: // overrided mothod from DefaultTreeModel
1608: public void nodesChanged(TreeNode node, int[] childIndices) {
1609: super .nodesChanged(node, childIndices);
1610:
1611: if ((node != null) && (childIndices != null)
1612: && isSortingActive()) {
1613: sortChildren((VisualizerNode) node, false);
1614: }
1615: }
1616:
1617: // overrided mothod from DefaultTreeModel
1618: public void setRoot(TreeNode root) {
1619: super .setRoot(root);
1620:
1621: if (root instanceof VisualizerNode && isSortingActive()) {
1622: sortChildren((VisualizerNode) root, false);
1623: }
1624: }
1625:
1626: private class SortingTask implements Runnable {
1627: private HashSet<VisualizerNode> toSort = new HashSet<VisualizerNode>();
1628:
1629: public synchronized void add(VisualizerNode parent) {
1630: toSort.add(parent);
1631: }
1632:
1633: public synchronized void remove(VisualizerNode parent) {
1634: toSort.remove(parent);
1635: }
1636:
1637: public synchronized boolean isEmpty() {
1638: return toSort.isEmpty();
1639: }
1640:
1641: public void run() {
1642: synchronized (SortedNodeTreeModel.this ) {
1643: SortedNodeTreeModel.this .sortingTask = null;
1644: }
1645:
1646: for (Iterator<VisualizerNode> i = toSort.iterator(); i
1647: .hasNext();) {
1648: VisualizerNode curr = i.next();
1649: SortedNodeTreeModel.this .doSortChildren(curr);
1650: }
1651: }
1652: }
1653: }
1654:
1655: /* Cell renderer for sorting column header.
1656: */
1657: private class SortingHeaderRenderer extends
1658: DefaultTableCellRenderer {
1659: SortingHeaderRenderer() {
1660: }
1661:
1662: /** Overrides superclass method. */
1663: public Component getTableCellRendererComponent(JTable table,
1664: Object value, boolean isSelected, boolean hasFocus,
1665: int row, int column) {
1666: Component comp = defaultHeaderRenderer
1667: .getTableCellRendererComponent(table, value,
1668: isSelected, hasFocus, row, column);
1669:
1670: if (comp instanceof JLabel) {
1671: if ((column == 0)
1672: && treeColumnProperty.isSortingColumn()) {
1673: ((JLabel) comp)
1674: .setIcon(getProperIcon(treeColumnProperty
1675: .isSortOrderDescending()));
1676: ((JLabel) comp)
1677: .setHorizontalTextPosition(SwingConstants.LEFT);
1678:
1679: // don't use deriveFont() - see #49973 for details
1680: comp.setFont(new Font(comp.getFont().getName(),
1681: Font.BOLD, comp.getFont().getSize()));
1682: } else if ((column != 0)
1683: && ((tableModel.getVisibleSortingColumn() + 1) == column)) {
1684: ((JLabel) comp).setIcon(getProperIcon(tableModel
1685: .isSortOrderDescending()));
1686: ((JLabel) comp)
1687: .setHorizontalTextPosition(SwingConstants.LEFT);
1688:
1689: // don't use deriveFont() - see #49973 for details
1690: comp.setFont(new Font(comp.getFont().getName(),
1691: Font.BOLD, comp.getFont().getSize()));
1692: } else {
1693: ((JLabel) comp).setIcon(null);
1694: }
1695: }
1696:
1697: return comp;
1698: }
1699:
1700: private ImageIcon getProperIcon(boolean descending) {
1701: if (descending) {
1702: return new ImageIcon(org.openide.util.Utilities
1703: .loadImage(SORT_DESC_ICON));
1704: } else {
1705: return new ImageIcon(org.openide.util.Utilities
1706: .loadImage(SORT_ASC_ICON));
1707: }
1708: }
1709: }
1710:
1711: // End of inner class SortingHeaderRenderer.
1712:
1713: private static class TreeColumnProperty {
1714: private Property p = null;
1715:
1716: TreeColumnProperty() {
1717: }
1718:
1719: void setProperty(Property p) {
1720: this .p = p;
1721: }
1722:
1723: boolean isComparable() {
1724: if (p == null) {
1725: return false;
1726: }
1727:
1728: Object o = p
1729: .getValue(NodeTableModel.ATTR_COMPARABLE_COLUMN);
1730:
1731: if ((o != null) && o instanceof Boolean) {
1732: return ((Boolean) o).booleanValue();
1733: }
1734:
1735: return false;
1736: }
1737:
1738: boolean isSortingColumn() {
1739: if (p == null) {
1740: return false;
1741: }
1742:
1743: Object o = p.getValue(NodeTableModel.ATTR_SORTING_COLUMN);
1744:
1745: if ((o != null) && o instanceof Boolean) {
1746: return ((Boolean) o).booleanValue();
1747: }
1748:
1749: return false;
1750: }
1751:
1752: void setSortingColumn(boolean sorting) {
1753: if (p == null) {
1754: return;
1755: }
1756:
1757: p.setValue(NodeTableModel.ATTR_SORTING_COLUMN,
1758: sorting ? Boolean.TRUE : Boolean.FALSE);
1759: }
1760:
1761: boolean isSortOrderDescending() {
1762: if (p == null) {
1763: return false;
1764: }
1765:
1766: Object o = p.getValue(NodeTableModel.ATTR_DESCENDING_ORDER);
1767:
1768: if ((o != null) && o instanceof Boolean) {
1769: return ((Boolean) o).booleanValue();
1770: }
1771:
1772: return false;
1773: }
1774:
1775: void setSortOrderDescending(boolean descending) {
1776: if (p == null) {
1777: return;
1778: }
1779:
1780: p.setValue(NodeTableModel.ATTR_DESCENDING_ORDER,
1781: descending ? Boolean.TRUE : Boolean.FALSE);
1782: }
1783: }
1784:
1785: /* For testing - use internal execution
1786: public static void main(String[] args) {
1787: SwingUtilities.invokeLater(new Runnable() {
1788: public void run() {
1789: Node n = //new org.netbeans.core.ModuleNode();
1790: RepositoryNodeFactory.getDefault().repository(DataFilter.ALL);
1791:
1792: org.openide.explorer.ExplorerManager em = new org.openide.explorer.ExplorerManager();
1793: em.setRootContext(n);
1794:
1795: org.openide.explorer.ExplorerPanel ep = new org.openide.explorer.ExplorerPanel(em);
1796: ep.setLayout (new BorderLayout ());
1797: ep.setBorder(new EmptyBorder(20, 20, 20, 20));
1798:
1799: TreeTableView ttv = new TreeTableView();
1800: ttv.setRootVisible(false);
1801: ttv.setPopupAllowed(true);
1802: ttv.setDefaultActionAllowed(true);
1803: ttv.setVerticalScrollBarPolicy( JScrollPane.VERTICAL_SCROLLBAR_ALWAYS );
1804: ttv.setHorizontalScrollBarPolicy( JScrollPane.HORIZONTAL_SCROLLBAR_NEVER );
1805:
1806: org.openide.nodes.PropertySupport.ReadOnly prop2
1807: = new org.openide.nodes.PropertySupport.ReadOnly (
1808: "name", // NOI18N
1809: String.class,
1810: "name",
1811: "Name Tooltip"
1812: ) {
1813: public Object getValue () {
1814: return null;
1815: }
1816:
1817: };
1818: //prop2.setValue( "InvisibleInTreeTableView", Boolean.TRUE );
1819: prop2.setValue( "SortingColumnTTV", Boolean.TRUE );
1820: prop2.setValue( "DescendingOrderTTV", Boolean.TRUE );
1821: prop2.setValue( "ComparableColumnTTV", Boolean.TRUE );
1822:
1823: ttv.setProperties(
1824: // n.getChildren().getNodes()[0].getPropertySets()[0].getProperties());
1825: new Property[]{
1826: new org.openide.nodes.PropertySupport.ReadWrite (
1827: "hidden", // NOI18N
1828: Boolean.TYPE,
1829: "hidden",
1830: "Hidden tooltip"
1831: ) {
1832: public Object getValue () {
1833: return null;
1834: }
1835:
1836: public void setValue (Object o) {
1837: }
1838: },
1839: prop2,
1840: new org.openide.nodes.PropertySupport.ReadOnly (
1841: "template", // NOI18N
1842: Boolean.TYPE,
1843: "template",
1844: "Template Tooltip"
1845: ) {
1846: public Object getValue () {
1847: return null;
1848: }
1849:
1850: }
1851:
1852: }
1853: );
1854: ttv.setTreePreferredWidth(200);
1855:
1856: ttv.setTableColumnPreferredWidth(0, 60);
1857: ttv.setTableColumnPreferredWidth(1, 150);
1858: ttv.setTableColumnPreferredWidth(2, 100);
1859:
1860:
1861: ep.add("Center", ttv);
1862: ep.open();
1863: }
1864: });
1865: }
1866: */
1867: }
|