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: * The Original Software is NetBeans. The Initial Developer of the Original
0026: * Software is Sun Microsystems, Inc. Portions Copyright 1997-2006 Sun
0027: * Microsystems, Inc. All Rights Reserved.
0028: *
0029: * If you wish your version of this file to be governed by only the CDDL
0030: * or only the GPL Version 2, indicate your decision by adding
0031: * "[Contributor] elects to include this software in this distribution
0032: * under the [CDDL or GPL Version 2] license." If you do not indicate a
0033: * single choice of license, a recipient has the option to distribute
0034: * your version of this file under either the CDDL, the GPL Version 2 or
0035: * to extend the choice of license to its licensees as provided above.
0036: * However, if you add GPL Version 2 code and therefore, elected the GPL
0037: * Version 2 license, then the option applies only if the new code is
0038: * made subject to such option by the copyright holder.
0039: */
0040:
0041: package org.netbeans.lib.profiler.ui.components;
0042:
0043: import org.netbeans.lib.profiler.results.CCTNode;
0044: import org.netbeans.lib.profiler.ui.UIUtils;
0045: import org.netbeans.lib.profiler.ui.components.table.*;
0046: import org.netbeans.lib.profiler.ui.components.tree.EnhancedTreeCellRenderer;
0047: import org.netbeans.lib.profiler.ui.components.tree.TreeCellRendererPersistent;
0048: import org.netbeans.lib.profiler.ui.components.treetable.*;
0049: import java.awt.*;
0050: import java.awt.event.*;
0051: import javax.swing.*;
0052: import javax.swing.event.ListSelectionEvent;
0053: import javax.swing.event.ListSelectionListener;
0054: import javax.swing.plaf.basic.BasicTreeUI;
0055: import javax.swing.table.JTableHeader;
0056: import javax.swing.table.TableCellRenderer;
0057: import javax.swing.table.TableColumnModel;
0058: import javax.swing.tree.DefaultTreeSelectionModel;
0059: import javax.swing.tree.TreeCellRenderer;
0060: import javax.swing.tree.TreeModel;
0061: import javax.swing.tree.TreePath;
0062:
0063: /**
0064: * JTreeTable component implementation
0065: *
0066: * @author Jiri Sedlacek
0067: * @author Ian Formanek
0068: */
0069: public class JTreeTable extends JTable implements CellTipAware,
0070: MouseListener, MouseMotionListener, MouseWheelListener,
0071: KeyListener {
0072: //~ Inner Classes ------------------------------------------------------------------------------------------------------------
0073:
0074: /**
0075: * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel to
0076: * listen for changes in the ListSelectionModel it maintains. Once a change
0077: * in the ListSelectionModel happens, the paths are updated in the
0078: * DefaultTreeSelectionModel.
0079: */
0080: class ListToTreeSelectionModelWrapper extends
0081: DefaultTreeSelectionModel {
0082: //~ Inner Classes --------------------------------------------------------------------------------------------------------
0083:
0084: /**
0085: * Class responsible for calling updateSelectedPathsFromSelectedRows
0086: * when the selection of the list changse.
0087: */
0088: class ListSelectionHandler implements ListSelectionListener {
0089: //~ Methods ----------------------------------------------------------------------------------------------------------
0090:
0091: public void valueChanged(ListSelectionEvent e) {
0092: updateSelectedPathsFromSelectedRows();
0093: }
0094: }
0095:
0096: //~ Instance fields ------------------------------------------------------------------------------------------------------
0097:
0098: /**
0099: * Set to true when we are updating the ListSelectionModel.
0100: */
0101: protected boolean updatingListSelectionModel;
0102:
0103: //~ Constructors ---------------------------------------------------------------------------------------------------------
0104:
0105: public ListToTreeSelectionModelWrapper() {
0106: super ();
0107: getListSelectionModel().addListSelectionListener(
0108: createListSelectionListener());
0109: }
0110:
0111: //~ Methods --------------------------------------------------------------------------------------------------------------
0112:
0113: /**
0114: * This is overridden to set <code>updatingListSelectionModel</code>
0115: * and message super. This is the only place DefaultTreeSelectionModel
0116: * alters the ListSelectionModel.
0117: */
0118: public void resetRowSelection() {
0119: if (!updatingListSelectionModel) {
0120: updatingListSelectionModel = true;
0121:
0122: try {
0123: super .resetRowSelection();
0124: } finally {
0125: updatingListSelectionModel = false;
0126: }
0127: }
0128:
0129: // Notice how we don't message super if
0130: // updatingListSelectionModel is true. If
0131: // updatingListSelectionModel is true, it implies the
0132: // ListSelectionModel has already been updated and the
0133: // paths are the only thing that needs to be updated.
0134: }
0135:
0136: /**
0137: * Creates and returns an instance of ListSelectionHandler.
0138: */
0139: protected ListSelectionListener createListSelectionListener() {
0140: return new ListSelectionHandler();
0141: }
0142:
0143: /**
0144: * If <code>updatingListSelectionModel</code> is false, this will
0145: * reset the selected paths from the selected rows in the list
0146: * selection model.
0147: */
0148: protected void updateSelectedPathsFromSelectedRows() {
0149: if (!updatingListSelectionModel) {
0150: updatingListSelectionModel = true;
0151:
0152: try {
0153: // This is way expensive, ListSelectionModel needs an
0154: // enumerator for iterating.
0155: int min = listSelectionModel.getMinSelectionIndex();
0156: int max = listSelectionModel.getMaxSelectionIndex();
0157:
0158: clearSelection();
0159:
0160: if ((min != -1) && (max != -1)) {
0161: for (int counter = min; counter <= max; counter++) {
0162: if (listSelectionModel
0163: .isSelectedIndex(counter)) {
0164: TreePath selPath = tree
0165: .getPathForRow(counter);
0166:
0167: if (selPath != null) {
0168: addSelectionPath(selPath);
0169: }
0170: }
0171: }
0172: }
0173: } finally {
0174: updatingListSelectionModel = false;
0175: }
0176: }
0177: }
0178:
0179: /**
0180: * Returns the list selection model. ListToTreeSelectionModelWrapper
0181: * listens for changes to this model and updates the selected paths
0182: * accordingly.
0183: */
0184: ListSelectionModel getListSelectionModel() {
0185: return listSelectionModel;
0186: }
0187: }
0188:
0189: //------------------------------------
0190:
0191: /**
0192: * This class is used for listening to the table header mouse events.
0193: */
0194: private class TableHeaderListener extends MouseAdapter implements
0195: MouseMotionListener {
0196: //~ Methods --------------------------------------------------------------------------------------------------------------
0197:
0198: /*
0199: * If the user clicks to the sorting column (column defining the sort criterium and order), the sorting order is reversed.
0200: * If new sorting column is selected, the appropriate sorting order for column's datatype is set.
0201: */
0202: public void mouseClicked(MouseEvent e) {
0203: if (e.getModifiers() == InputEvent.BUTTON1_MASK) {
0204: int column = tableHeader.columnAtPoint(e.getPoint());
0205: int sortingColumn = headerRenderer.getSortingColumn();
0206:
0207: if (column == sortingColumn) {
0208: headerRenderer.reverseSortingOrder();
0209: } else {
0210: headerRenderer.setSortingColumn(column);
0211:
0212: if (treeTableModel.getInitialSorting(column)) {
0213: headerRenderer.setSortingOrder(SORT_ORDER_ASC); // Default sort order for strings is Ascending
0214: } else {
0215: headerRenderer.setSortingOrder(SORT_ORDER_DESC); // Default sort order for numbers is Descending
0216: }
0217: }
0218:
0219: tableHeader.repaint();
0220:
0221: treeTableModel.sortByColumn(column, headerRenderer
0222: .getSortingOrder());
0223: updateTreeTable();
0224: }
0225: }
0226:
0227: public void mouseDragged(MouseEvent e) {
0228: }
0229:
0230: public void mouseMoved(MouseEvent e) {
0231: int focusedColumn = tableHeader.columnAtPoint(e.getPoint());
0232:
0233: if (focusedColumn != lastFocusedColumn) {
0234: if (focusedColumn != -1) {
0235: tableHeader.setToolTipText(treeTableModel
0236: .getColumnToolTipText(focusedColumn));
0237: } else {
0238: tableHeader.setToolTipText(null);
0239: }
0240:
0241: lastFocusedColumn = focusedColumn;
0242: }
0243: }
0244:
0245: /*
0246: * Here the active header button is programatically pressed
0247: */
0248: public void mousePressed(MouseEvent e) {
0249: if ((e.getModifiers() == InputEvent.BUTTON1_MASK)
0250: && (tableHeader.getResizingColumn() == null)) {
0251: headerRenderer.setPressedColumn(tableHeader
0252: .columnAtPoint(e.getPoint()));
0253: tableHeader.repaint();
0254: }
0255: }
0256:
0257: /*
0258: * Here the active header button is programatically released
0259: */
0260: public void mouseReleased(MouseEvent e) {
0261: if (e.getModifiers() == InputEvent.BUTTON1_MASK) {
0262: headerRenderer.setPressedColumn(-1);
0263: tableHeader.repaint();
0264: }
0265: }
0266: }
0267:
0268: private class TreeTableCellRenderer extends JTree implements
0269: TableCellRenderer {
0270: //~ Instance fields ------------------------------------------------------------------------------------------------------
0271:
0272: protected int currentlyPaintedRow;
0273: private Color darkerUnselectedBackground;
0274: private Color unselectedBackground;
0275: private Color unselectedForeground;
0276: private EnhancedTreeCellRenderer treeCellRenderer;
0277: private int offsetX; // x-offsed used for scrolling the TreeTable cell
0278:
0279: //~ Constructors ---------------------------------------------------------------------------------------------------------
0280:
0281: public TreeTableCellRenderer(TreeModel model) {
0282: super (model);
0283:
0284: offsetX = 0;
0285: setOpaque(false);
0286: treeCellRenderer = new EnhancedTreeCellRenderer();
0287: setCellRenderer(treeCellRenderer);
0288: unselectedBackground = UIUtils
0289: .getProfilerResultsBackground();
0290: darkerUnselectedBackground = UIUtils
0291: .getDarker(unselectedBackground);
0292: }
0293:
0294: //~ Methods --------------------------------------------------------------------------------------------------------------
0295:
0296: public void setBounds(int x, int y, int w, int h) {
0297: super .setBounds(x, 0, w, JTreeTable.this .getHeight());
0298: }
0299:
0300: public void setOffsetX(int offsetX) {
0301: this .offsetX = offsetX;
0302: }
0303:
0304: public int getOffsetX() {
0305: return offsetX;
0306: }
0307:
0308: public void setRowHeight(int rowHeight) {
0309: if (rowHeight > 0) {
0310: super .setRowHeight(rowHeight);
0311:
0312: if ((JTreeTable.this != null)
0313: && (JTreeTable.this .getRowHeight() != rowHeight)) {
0314: JTreeTable.this .setRowHeight(getRowHeight());
0315: }
0316: }
0317: }
0318:
0319: public Component getTableCellRendererComponent(JTable table,
0320: Object value, boolean isSelected, boolean hasFocus,
0321: int row, int column) {
0322: if (isSelected) {
0323: setRowForeground(table.isFocusOwner() ? table
0324: .getSelectionForeground() : UIUtils
0325: .getUnfocusedSelectionForeground());
0326: setRowBackground(table.isFocusOwner() ? table
0327: .getSelectionBackground() : UIUtils
0328: .getUnfocusedSelectionBackground());
0329: } else {
0330: if ((row & 0x1) == 0) { //even row
0331: setRowForeground((unselectedForeground != null) ? unselectedForeground
0332: : table.getForeground());
0333: setRowBackground((darkerUnselectedBackground != null) ? darkerUnselectedBackground
0334: : UIUtils.getDarker(table.getBackground()));
0335: } else {
0336: setRowForeground((unselectedForeground != null) ? unselectedForeground
0337: : table.getForeground());
0338: setRowBackground((unselectedBackground != null) ? unselectedBackground
0339: : table.getBackground());
0340: }
0341: }
0342:
0343: currentlyPaintedRow = row;
0344:
0345: return this ;
0346: }
0347:
0348: public void setTreeCellRenderer(
0349: EnhancedTreeCellRenderer renderer) {
0350: treeCellRenderer = renderer;
0351: setCellRenderer(treeCellRenderer);
0352: }
0353:
0354: public EnhancedTreeCellRenderer getTreeCellRenderer() {
0355: return treeCellRenderer;
0356: }
0357:
0358: public void customProcessKeyEvent(KeyEvent e) {
0359: processKeyEvent(e);
0360: }
0361:
0362: public void paint(Graphics g) {
0363: boolean selected;
0364: boolean focused;
0365: int xpos;
0366:
0367: selected = isRowSelected(currentlyPaintedRow);
0368: focused = JTreeTable.this .isFocusOwner();
0369:
0370: int rHeight = getRowHeight();
0371:
0372: // move tree according to offsetX
0373: g.translate(-offsetX, -currentlyPaintedRow * rHeight);
0374:
0375: if (isGTK) { // Optimized for GTK but doesn't paint selection on the left side of renderer
0376: // paint tree row, according to current Clip only one row is painted
0377: super .paint(g);
0378:
0379: // draw row background
0380: Rectangle rowBounds = getRowBounds(currentlyPaintedRow);
0381: xpos = rowBounds.x + rowBounds.width;
0382: g.setColor(getRowColor(currentlyPaintedRow, selected,
0383: focused));
0384: g.fillRect(xpos, currentlyPaintedRow * rHeight,
0385: getWidth() + offsetX - xpos, rHeight);
0386: } else {
0387: // draw row background
0388: xpos = selected ? 0
0389: : getRowBounds(currentlyPaintedRow).x;
0390: g.setColor(getRowColor(currentlyPaintedRow, selected,
0391: focused));
0392: g.fillRect(xpos, currentlyPaintedRow * rHeight,
0393: getWidth() + offsetX, rHeight);
0394:
0395: // paint tree row, according to current Clip only one row is painted
0396: super .paint(g);
0397: }
0398: }
0399:
0400: protected void setRowBackground(Color c) {
0401: //setBackground(c);
0402: treeCellRenderer.setBackground(c);
0403: treeCellRenderer.setBackgroundNonSelectionColor(c);
0404: treeCellRenderer.setBackgroundSelectionColor(c);
0405: }
0406:
0407: protected void setRowForeground(Color c) {
0408: //setForeground(c);
0409: treeCellRenderer.setForeground(c);
0410: treeCellRenderer.setTextNonSelectionColor(c);
0411: treeCellRenderer.setTextSelectionColor(c);
0412: }
0413:
0414: private Color getRowColor(int row, boolean selected,
0415: boolean focused) {
0416: if (selected) {
0417: return focused ? JTreeTable.this
0418: .getSelectionBackground() : UIUtils
0419: .getUnfocusedSelectionBackground();
0420: } else {
0421: Color backgroundColor = UIUtils
0422: .getProfilerResultsBackground();
0423: if ((row & 0x1) == 0) { //even row
0424: return UIUtils.getDarker(backgroundColor);
0425: } else {
0426: return backgroundColor;
0427: }
0428: }
0429: }
0430: }
0431:
0432: //~ Static fields/initializers -----------------------------------------------------------------------------------------------
0433:
0434: public static final boolean SORT_ORDER_DESC = false;
0435: public static final boolean SORT_ORDER_ASC = true;
0436:
0437: private static final boolean isGTK = UIUtils.isGTKLookAndFeel();
0438:
0439: //~ Instance fields ----------------------------------------------------------------------------------------------------------
0440:
0441: // --- CellTip support declarations -------
0442: protected JToolTip cellTip;
0443: protected Rectangle rendererRect;
0444:
0445: /**
0446: * A subclass of JTree.
0447: */
0448: TreeTableCellRenderer tree;
0449: protected int lastColumn = -1;
0450: protected int lastRow = -1;
0451: private AbstractTreeTableModel treeTableModel;
0452: private CustomSortableHeaderRenderer headerRenderer;
0453: private ImageIcon sortAscIcon = new ImageIcon(
0454: JTreeTable.class
0455: .getResource("/org/netbeans/lib/profiler/ui/resources/sortAsc.png")); //NOI18N
0456: private ImageIcon sortDescIcon = new ImageIcon(
0457: JTreeTable.class
0458: .getResource("/org/netbeans/lib/profiler/ui/resources/sortDesc.png")); //NOI18N
0459: private JTableHeader tableHeader;
0460: private String internalFindString;
0461:
0462: //------------------------------------
0463: // Find functionality stuff
0464: private String userFindString;
0465: private TableHeaderListener headerListener;
0466: private TreeTableModelAdapter treeTableModelAdapter;
0467: private int lastFocusedColumn = -1;
0468: private int treeSignExtent; // width/2 of the tree "+"/"-" sign
0469: private int treeSignRightMargin; // value of BasicTreeUI.getRightChildIndent()
0470: private int userFindColumn;
0471:
0472: //~ Constructors -------------------------------------------------------------------------------------------------------------
0473:
0474: public JTreeTable(AbstractTreeTableModel treeTableModel) {
0475: super ();
0476: this .treeTableModel = treeTableModel;
0477:
0478: int initialSortingColumn = treeTableModel
0479: .getInitialSortingColumn();
0480: boolean initialSortingOrder = treeTableModel
0481: .getInitialSortingOrder();
0482:
0483: if (treeTableModel.supportsSorting()) {
0484: treeTableModel.sortByColumn(initialSortingColumn,
0485: initialSortingOrder);
0486: }
0487:
0488: addKeyListener(this );
0489: addMouseListener(this );
0490: addMouseMotionListener(this );
0491:
0492: // Required for correct updating of focused/unfocused selection
0493: addFocusListener(new FocusListener() {
0494: public void focusGained(FocusEvent e) {
0495: if (getSelectedRows().length > 0) {
0496: repaint();
0497: }
0498: }
0499:
0500: public void focusLost(FocusEvent e) {
0501: if (getSelectedRows().length > 0) {
0502: repaint();
0503: }
0504: }
0505: });
0506:
0507: // Create the tree. It will be used as a renderer and editor.
0508: tree = new TreeTableCellRenderer(treeTableModel);
0509: setTreeUIVariables();
0510:
0511: // Install a tableModel representing the visible rows in the tree.
0512: treeTableModelAdapter = new TreeTableModelAdapter(
0513: treeTableModel, this );
0514: setModel(treeTableModelAdapter);
0515:
0516: if (treeTableModel.supportsSorting()) {
0517: headerListener = new TableHeaderListener();
0518:
0519: headerRenderer = new CustomSortableHeaderRenderer(
0520: sortAscIcon, sortDescIcon);
0521: headerRenderer.setSortingColumn(initialSortingColumn);
0522: headerRenderer.setSortingOrder(initialSortingOrder);
0523:
0524: updateTreeTableHeader();
0525: }
0526:
0527: getTableHeader().setReorderingAllowed(false);
0528:
0529: // Force the JTable and JTree to share their row selection models.
0530: ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper();
0531: tree.setSelectionModel(selectionWrapper);
0532: setSelectionModel(selectionWrapper.getListSelectionModel());
0533:
0534: // Install the tree editor renderer and editor.
0535: setDefaultRenderer(TreeTableModel.class, tree);
0536:
0537: // --- CellTip support ------------------
0538: cellTip = createCellTip();
0539: cellTip.setBorder(BorderFactory
0540: .createLineBorder(getGridColor()));
0541: cellTip.setLayout(new BorderLayout());
0542:
0543: CellTipManager.sharedInstance().registerComponent(this );
0544: }
0545:
0546: //~ Methods ------------------------------------------------------------------------------------------------------------------
0547:
0548: public JToolTip getCellTip() {
0549: return cellTip;
0550: }
0551:
0552: public Point getCellTipLocation() {
0553: if (rendererRect == null) {
0554: return null;
0555: }
0556:
0557: return new Point(rendererRect.getLocation().x - 1, rendererRect
0558: .getLocation().y - 1);
0559: }
0560:
0561: public int getFindColumn() {
0562: return userFindColumn;
0563: }
0564:
0565: public boolean isFindColumnValid() {
0566: return ((userFindColumn >= 0) && (userFindColumn < getColumnCount()));
0567: }
0568:
0569: public void setFindParameters(String findString, int findColumn) {
0570: userFindString = findString;
0571: userFindColumn = findColumn;
0572: internalFindString = getInternalFindString(userFindString);
0573: }
0574:
0575: public String getFindString() {
0576: return userFindString;
0577: }
0578:
0579: public boolean isFindStringDefined() {
0580: return ((userFindString != null) && (userFindString.trim()
0581: .length() > 0));
0582: }
0583:
0584: //------------------------------------
0585: // CellTip support
0586: public void setGridColor(Color gridColor) {
0587: super .setGridColor(gridColor);
0588:
0589: if ((gridColor == null) || (cellTip == null)) {
0590: return;
0591: }
0592:
0593: cellTip.setBorder(BorderFactory.createLineBorder(gridColor));
0594: }
0595:
0596: /**
0597: * Overridden to pass the new rowHeight to the tree.
0598: */
0599: public void setRowHeight(int rowHeight) {
0600: super .setRowHeight(rowHeight);
0601:
0602: if ((tree != null) && (tree.getRowHeight() != rowHeight)) {
0603: tree.setRowHeight(getRowHeight());
0604: }
0605: }
0606:
0607: public void setSortingColumn(int column) {
0608: headerRenderer.setSortingColumn(column);
0609: }
0610:
0611: public int getSortingColumn() {
0612: return headerRenderer.getSortingColumn();
0613: }
0614:
0615: public void setSortingOrder(boolean order) {
0616: headerRenderer.setSortingOrder(order);
0617: }
0618:
0619: public boolean getSortingOrder() {
0620: return headerRenderer.getSortingOrder();
0621: }
0622:
0623: /**
0624: * Returns the tree that is being shared between the model.
0625: */
0626: public JTree getTree() {
0627: return tree;
0628: }
0629:
0630: /** Sets the x-offsed used for scrolling the TreeTable cell */
0631: public void setTreeCellOffsetX(int offsetX) {
0632: if (getTreeCellOffsetX() != offsetX) {
0633: tree.setOffsetX(offsetX);
0634: repaint();
0635: }
0636: }
0637:
0638: /** Gets the x-offsed used for scrolling the TreeTable cell */
0639: public int getTreeCellOffsetX() {
0640: return tree.getOffsetX();
0641: }
0642:
0643: public void setTreeCellRenderer(EnhancedTreeCellRenderer renderer) {
0644: tree.setTreeCellRenderer(renderer);
0645: }
0646:
0647: public EnhancedTreeCellRenderer getTreeCellRenderer() {
0648: return tree.getTreeCellRenderer();
0649: }
0650:
0651: public boolean canFindBePerformed() {
0652: return (tree != null) && (treeTableModel.getRoot() != null)
0653: && isFindColumnValid() && isFindStringDefined();
0654: }
0655:
0656: public boolean findFirst() {
0657: return findFirst(true);
0658: }
0659:
0660: public boolean findNext() {
0661: if (!canFindBePerformed()) {
0662: return false;
0663: }
0664:
0665: CCTNode searchRoot = getSearchRoot();
0666:
0667: // check current search root's subtree
0668: if (doFindNext(searchRoot, 0, true)) {
0669: return true;
0670: }
0671:
0672: CCTNode searchRootParent = searchRoot.getParent();
0673:
0674: // nothing found, process next siblings
0675: while (searchRootParent != null) {
0676: if (doFindNext(searchRootParent, searchRootParent
0677: .getIndexOfChild(searchRoot) + 1, true)) {
0678: return true;
0679: }
0680:
0681: searchRoot = searchRootParent;
0682: searchRootParent = searchRoot.getParent();
0683: }
0684:
0685: return false;
0686: }
0687:
0688: public boolean findPrevious() {
0689: if (!canFindBePerformed()) {
0690: return false;
0691: }
0692:
0693: // selected/last found node
0694: CCTNode searchRoot = getSearchRoot();
0695:
0696: if (!isAnyRowSelected()) {
0697: return findFirst();
0698: }
0699:
0700: // parent of this node than could contain previous node
0701: CCTNode searchRootParent = searchRoot.getParent();
0702:
0703: while (searchRootParent != null) {
0704: // if nothing found in previous siblings
0705: if (doFindPrevious(searchRootParent, searchRootParent
0706: .getIndexOfChild(searchRoot) - 1, true)) {
0707: return true;
0708: }
0709:
0710: // swith one level up
0711: searchRoot = searchRootParent;
0712: searchRootParent = searchRoot.getParent();
0713: }
0714:
0715: return false;
0716: }
0717:
0718: //------------------------------------
0719: // Keyboard processing
0720: public void keyPressed(KeyEvent e) {
0721: if (shouldBeForwarded(e)) {
0722: dispatchKeyboardEvent(e);
0723: }
0724: }
0725:
0726: public void keyReleased(KeyEvent e) {
0727: if (shouldBeForwarded(e)) {
0728: dispatchKeyboardEvent(e);
0729: }
0730: }
0731:
0732: public void keyTyped(KeyEvent e) {
0733: if (shouldBeForwarded(e)) {
0734: dispatchKeyboardEvent(e);
0735: }
0736: }
0737:
0738: //------------------------------------
0739: // Mouse processing
0740: public void mouseClicked(MouseEvent e) {
0741: dispatchMouseEvent(e);
0742: }
0743:
0744: public void mouseDragged(MouseEvent e) {
0745: dispatchMouseEvent(e);
0746: }
0747:
0748: public void mouseEntered(MouseEvent e) {
0749: //dispatchMouseEvent(e);
0750:
0751: // --- CellTip support ------------------
0752: CellTipManager.sharedInstance().setEnabled(false);
0753: }
0754:
0755: public void mouseExited(MouseEvent e) {
0756: //dispatchMouseEvent(e);
0757:
0758: // --- CellTip support ------------------
0759: // Return if mouseExit occured because of showing heavyweight celltip
0760: if (contains(e.getPoint()) && cellTip.isShowing()) {
0761: return;
0762: }
0763:
0764: CellTipManager.sharedInstance().setEnabled(false);
0765: lastRow = -1;
0766: lastColumn = -1;
0767: }
0768:
0769: public void mouseMoved(MouseEvent e) {
0770: //dispatchMouseEvent(e);
0771:
0772: // --- CellTip support ------------------
0773:
0774: // Identify treetable row and column at cursor
0775: int row = rowAtPoint(e.getPoint());
0776: int column = columnAtPoint(e.getPoint());
0777:
0778: boolean isForTreeCell = (getColumnClass(column) == TreeTableModel.class);
0779:
0780: // Return if treetable cell is the same as in previous event
0781: if (!isForTreeCell && (row == lastRow)
0782: && (column == lastColumn)) {
0783: return;
0784: }
0785:
0786: lastRow = row;
0787: lastColumn = column;
0788:
0789: // Return if cursor isn't at any cell
0790: if ((row < 0) || (column < 0)) {
0791: CellTipManager.sharedInstance().setEnabled(false);
0792:
0793: return;
0794: }
0795:
0796: Component cellRenderer;
0797: Rectangle cellRect = getCellRect(row, column, false);
0798:
0799: if (isForTreeCell) {
0800: // Cursor at tree cell
0801: TreeCellRenderer treeCellRenderer = tree
0802: .getTreeCellRenderer();
0803: cellRenderer = ((TreeCellRendererPersistent) treeCellRenderer)
0804: .getTreeCellRendererComponentPersistent(tree,
0805: treeTableModel.getValueAt(tree
0806: .getPathForRow(row)
0807: .getLastPathComponent(), 0), false,
0808: tree.isExpanded(row), treeTableModel
0809: .isLeaf(tree.getPathForRow(row)
0810: .getLastPathComponent()),
0811: row, false);
0812:
0813: // Return if celltip is not supported for the cell
0814: if (cellRenderer == null) {
0815: CellTipManager.sharedInstance().setEnabled(false);
0816:
0817: return;
0818: }
0819:
0820: Point treeCellStart = tree.getPathBounds(
0821: tree.getPathForRow(row)).getLocation();
0822: rendererRect = new Rectangle((cellRect.x + treeCellStart.x)
0823: - tree.getOffsetX(), treeCellStart.y, cellRenderer
0824: .getPreferredSize().width, cellRenderer
0825: .getPreferredSize().height + 2);
0826: } else {
0827: // Cursor at table cell
0828: TableCellRenderer tableCellRenderer = getCellRenderer(row,
0829: column);
0830:
0831: if (!(tableCellRenderer instanceof TableCellRendererPersistent)) {
0832: return;
0833: }
0834:
0835: cellRenderer = ((TableCellRendererPersistent) tableCellRenderer)
0836: .getTableCellRendererComponentPersistent(this ,
0837: getValueAt(row, column), false, false, row,
0838: column);
0839:
0840: // Return if celltip is not supported for the cell
0841: if (cellRenderer == null) {
0842: CellTipManager.sharedInstance().setEnabled(false);
0843:
0844: return;
0845: }
0846:
0847: int horizontalAlignment = ((EnhancedTableCellRenderer) cellRenderer)
0848: .getHorizontalAlignment();
0849:
0850: if ((horizontalAlignment == SwingConstants.TRAILING)
0851: || (horizontalAlignment == SwingConstants.RIGHT)) {
0852: rendererRect = new Rectangle(
0853: (cellRect.x + cellRect.width)
0854: - cellRenderer.getPreferredSize().width,
0855: cellRect.y,
0856: cellRenderer.getPreferredSize().width,
0857: cellRenderer.getPreferredSize().height);
0858: } else {
0859: rendererRect = new Rectangle(cellRect.x, cellRect.y,
0860: cellRenderer.getPreferredSize().width,
0861: cellRenderer.getPreferredSize().height);
0862: }
0863: }
0864:
0865: if (isForTreeCell && !rendererRect.contains(e.getPoint())) {
0866: CellTipManager.sharedInstance().setEnabled(false);
0867:
0868: return;
0869: }
0870:
0871: // Return if cell contents is fully visible
0872: if ((rendererRect.x >= cellRect.x)
0873: && ((rendererRect.x + rendererRect.width) <= (cellRect.x + cellRect.width))) {
0874: CellTipManager.sharedInstance().setEnabled(false);
0875:
0876: return;
0877: }
0878:
0879: while (cellTip.getComponentCount() > 0) {
0880: cellTip.remove(0);
0881: }
0882:
0883: cellTip.add(cellRenderer, BorderLayout.CENTER);
0884: cellTip.setPreferredSize(new Dimension(rendererRect.width + 2,
0885: getRowHeight(row) + 2));
0886:
0887: CellTipManager.sharedInstance().setEnabled(true);
0888: }
0889:
0890: public void mousePressed(MouseEvent e) {
0891: dispatchMouseEvent(e);
0892: }
0893:
0894: public void mouseReleased(MouseEvent e) {
0895: dispatchMouseEvent(e);
0896: }
0897:
0898: public void mouseWheelMoved(MouseWheelEvent e) {
0899: mouseMoved(e);
0900: CellTipManager.sharedInstance().setEnabled(false);
0901: }
0902:
0903: public void processMouseEvent(MouseEvent e) {
0904: super .processMouseEvent(e);
0905: }
0906:
0907: public void resetTreeCellOffsetX() {
0908: setTreeCellOffsetX(0);
0909: }
0910:
0911: //------------------------------------
0912: public void selectNode(CCTNode node, boolean setVisible) {
0913: TreePath path = new TreePath(treeTableModel.getPathToRoot(node));
0914: getTree().setSelectionPath(path);
0915:
0916: if (setVisible) {
0917: scrollRectToVisible(getCellRect(getSelectedRow(), 0, true));
0918: }
0919: }
0920:
0921: public void selectRowByContents(String rowString, int columnIndex,
0922: boolean setVisible) {
0923: for (int i = 0; i < getRowCount(); i++) {
0924: if (getValueAt(i, columnIndex).toString().equals(rowString)) {
0925: getSelectionModel().setSelectionInterval(i, i);
0926:
0927: if (setVisible) {
0928: scrollRectToVisible(getCellRect(i, columnIndex,
0929: true));
0930: }
0931:
0932: return;
0933: }
0934: }
0935:
0936: getSelectionModel().clearSelection();
0937: }
0938:
0939: public boolean silentlyFindFirst() {
0940: return findFirst(false);
0941: }
0942:
0943: public void updateTreeTable() {
0944: treeTableModelAdapter.updateTreeTable();
0945: }
0946:
0947: public void updateTreeTableHeader() {
0948: TableColumnModel tableColumnModel = getColumnModel();
0949: int n = tableColumnModel.getColumnCount();
0950:
0951: for (int i = 0; i < n; i++) {
0952: tableColumnModel.getColumn(i).setHeaderRenderer(
0953: headerRenderer);
0954: }
0955:
0956: if (tableHeader != getTableHeader()) {
0957: if (tableHeader != null) {
0958: tableHeader.removeMouseListener(headerListener);
0959: }
0960:
0961: if (tableHeader != null) {
0962: tableHeader.removeMouseMotionListener(headerListener);
0963: }
0964:
0965: tableHeader = getTableHeader();
0966: tableHeader.addMouseListener(headerListener);
0967: tableHeader.addMouseMotionListener(headerListener);
0968: updateTreeTable();
0969: }
0970: }
0971:
0972: /**
0973: * Overridden to message super and forward the method to the tree. Since
0974: * the tree is not actually in the component hieachy it will never receive
0975: * this unless we forward it in this manner.
0976: */
0977: public void updateUI() {
0978: super .updateUI();
0979:
0980: if (tree != null) {
0981: tree.updateUI();
0982: setTreeUIVariables();
0983: }
0984: }
0985:
0986: protected JToolTip createCellTip() {
0987: return new JToolTip();
0988: }
0989:
0990: private boolean isAnyRowSelected() {
0991: TreePath treeSelectionPath = tree.getSelectionPath();
0992:
0993: return ((treeSelectionPath != null) && (treeSelectionPath
0994: .getPathCount() > 0));
0995: }
0996:
0997: private String getInternalFindString(String findString) {
0998: if (findString == null) {
0999: return null;
1000: }
1001:
1002: return findString.toLowerCase();
1003: }
1004:
1005: private CCTNode getSearchRoot() {
1006: if (!isAnyRowSelected()) {
1007: return (CCTNode) treeTableModel.getRoot();
1008: } else {
1009: return (CCTNode) tree.getSelectionPath()
1010: .getLastPathComponent();
1011: }
1012: }
1013:
1014: private void setTreeUIVariables() {
1015: if (tree.getUI() instanceof BasicTreeUI) {
1016: BasicTreeUI treeUI = (BasicTreeUI) tree.getUI();
1017: treeSignExtent = treeUI.getExpandedIcon().getIconWidth() / 2;
1018: treeSignRightMargin = treeUI.getRightChildIndent();
1019: }
1020: }
1021:
1022: private void dispatchKeyboardEvent(KeyEvent e) {
1023: JTreeTable.this .tree.customProcessKeyEvent(e);
1024:
1025: int selectedRow = getSelectedRow();
1026:
1027: if (selectedRow > -1) {
1028: scrollRectToVisible(getCellRect(selectedRow, 0, false));
1029: }
1030: }
1031:
1032: private void dispatchMouseEvent(MouseEvent e) {
1033: if (e != null) {
1034: int row = rowAtPoint(e.getPoint());
1035: int column = columnAtPoint(e.getPoint());
1036:
1037: Rectangle tableCellRect = getCellRect(row, column, true);
1038: Rectangle treeCellRect = tree.getRowBounds(row);
1039:
1040: if (treeCellRect != null) {
1041: // x-coordinate of the mouseclick must be mapped to the tree coordinate system
1042: int xClick;
1043: Class columnClass = getColumnClass(column);
1044:
1045: if (columnClass == TreeTableModel.class) {
1046: // Clicked inside tree cell
1047: xClick = e.getX() - tableCellRect.x;
1048: xClick += tree.getOffsetX();
1049:
1050: if ((xClick < (treeCellRect.x - treeSignExtent - treeSignRightMargin))
1051: || (xClick > ((treeCellRect.x + treeSignExtent)
1052: - treeSignRightMargin + 1))) {
1053: // Clicked on "+"/"-" sign
1054: xClick = (treeCellRect.x + treeCellRect.width) - 1;
1055: }
1056: } else {
1057: // Clicked outside tree cell
1058: xClick = (treeCellRect.x + treeCellRect.width) - 1;
1059: }
1060:
1061: int clickCount = 2 - (e.getClickCount() % 2);
1062:
1063: MouseEvent newEvent = new MouseEvent(
1064: JTreeTable.this .tree, e.getID(), e.getWhen(), e
1065: .getModifiers(), xClick, e.getY(),
1066: clickCount, e.isPopupTrigger());
1067: JTreeTable.this .tree.dispatchEvent(newEvent);
1068: }
1069: }
1070: }
1071:
1072: private boolean doFindNext(CCTNode rootForSearch,
1073: int childToSearchIndex, boolean requestFocus) {
1074: int nChildren = rootForSearch.getNChildren();
1075:
1076: // for all not processed children
1077: while (childToSearchIndex < nChildren) {
1078: CCTNode childToSearch = rootForSearch
1079: .getChild(childToSearchIndex);
1080:
1081: // check the child itself
1082: if (matchesFindCriterion(childToSearch)) {
1083: return selectFoundNode(childToSearch, requestFocus);
1084: }
1085: // and then its subtree
1086: else if ((childToSearch.getNChildren() > 0)
1087: && doFindNext(childToSearch, 0, requestFocus)) {
1088: return true;
1089: }
1090:
1091: childToSearchIndex++;
1092: }
1093:
1094: // nothing found
1095: return false;
1096: }
1097:
1098: private boolean doFindPrevious(CCTNode rootForSearch,
1099: int childToSearchIndex, boolean requestFocus) {
1100: // check all not processed children
1101: while (childToSearchIndex >= 0) {
1102: CCTNode childToSearch = rootForSearch
1103: .getChild(childToSearchIndex);
1104:
1105: if (doFindPrevious(childToSearch, childToSearch
1106: .getNChildren() - 1, requestFocus)) {
1107: return true;
1108: }
1109:
1110: childToSearchIndex--;
1111: }
1112:
1113: // check itself
1114: if (matchesFindCriterion(rootForSearch)) {
1115: return selectFoundNode(rootForSearch, requestFocus);
1116: }
1117:
1118: // nothing found
1119: return false;
1120: }
1121:
1122: private boolean findFirst(boolean requestFocus) {
1123: if (!canFindBePerformed()) {
1124: return false;
1125: }
1126:
1127: CCTNode searchRoot = (CCTNode) treeTableModel.getRoot();
1128:
1129: if (matchesFindCriterion(searchRoot)) {
1130: return selectFoundNode(searchRoot, requestFocus);
1131: } else {
1132: return doFindNext(searchRoot, 0, requestFocus);
1133: }
1134: }
1135:
1136: private boolean matchesFindCriterion(Object node) {
1137: // find is always performed on values of the first column
1138: // first column is always visible and has always index=0
1139: return treeTableModel.getValueAt(node, 0).toString()
1140: .toLowerCase().indexOf(internalFindString) != -1;
1141: }
1142:
1143: private boolean selectFoundNode(CCTNode nodeToSelect,
1144: boolean requestFocus) {
1145: TreePath nodeToSelectPath = new TreePath(treeTableModel
1146: .getPathToRoot(nodeToSelect));
1147: tree.expandPath(nodeToSelectPath);
1148: tree.setSelectionPath(nodeToSelectPath);
1149:
1150: if (requestFocus) {
1151: requestFocusInWindow();
1152: }
1153:
1154: Rectangle rect = tree.getPathBounds(nodeToSelectPath);
1155:
1156: if (rect != null) {
1157: scrollRectToVisible(rect);
1158:
1159: return true;
1160: } else {
1161: return false;
1162: }
1163: }
1164:
1165: // Filters-out actions handled by the JTable itself,
1166: // see http://www.netbeans.org/issues/show_bug.cgi?id=112848
1167: private boolean shouldBeForwarded(KeyEvent e) {
1168: switch (e.getKeyCode()) {
1169: case KeyEvent.VK_PAGE_UP:
1170: case KeyEvent.VK_PAGE_DOWN:
1171: return false;
1172: default:
1173: return true;
1174: }
1175: }
1176: }
|