1 /*
   2  * Copyright 2001-2008 Sun Microsystems, Inc.  All Rights Reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.
   8  *
   9  * This code is distributed in the hope that it will be useful, but WITHOUT
  10  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  11  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  12  * version 2 for more details (a copy is included in the LICENSE file that
  13  * accompanied this code).
  14  *
  15  * You should have received a copy of the GNU General Public License version
  16  * 2 along with this work; if not, write to the Free Software Foundation,
  17  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  18  *
  19  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
  20  * CA 95054 USA or visit www.sun.com if you need additional information or
  21  * have any questions.
  22  *
  23  */
  24 
  25 package sun.jvm.hotspot.ui.treetable;
  26 
  27 import java.awt.*;
  28 
  29 import javax.swing.*;
  30 import javax.swing.border.*;
  31 import javax.swing.event.*;
  32 import javax.swing.tree.*;
  33 import javax.swing.table.*;
  34 
  35 import java.awt.event.*;
  36 
  37 import java.util.EventObject;
  38 
  39 /**
  40  * This example shows how to create a simple JTreeTable component,
  41  * by using a JTree as a renderer (and editor) for the cells in a
  42  * particular column in the JTable.
  43  *
  44  * @version 1.2 10/27/98
  45  *
  46  * @author Philip Milne
  47  * @author Scott Violet
  48  */
  49 public class JTreeTable extends JTable {
  50     /** A subclass of JTree. */
  51     protected TreeTableCellRenderer tree;
  52 
  53     //////////////////////////
  54     // Convenience routines //
  55     //////////////////////////
  56 
  57     private boolean treeEditable = true;
  58     private boolean showsIcons   = true;
  59 
  60     public boolean getTreeEditable() {
  61         return treeEditable;
  62     }
  63 
  64     public void setTreeEditable(boolean editable) {
  65         treeEditable = editable;
  66     }
  67 
  68     public boolean getShowsIcons() {
  69         return showsIcons;
  70     }
  71 
  72     public void setShowsIcons(boolean show) {
  73         showsIcons = show;
  74     }
  75 
  76     public void setRootVisible(boolean visible) {
  77         tree.setRootVisible(visible);
  78     }
  79 
  80     public boolean getShowsRootHandles() {
  81         return tree.getShowsRootHandles();
  82     }
  83 
  84     public void setShowsRootHandles(boolean newValue) {
  85         tree.setShowsRootHandles(newValue);
  86     }
  87 
  88     public JTreeTable(TreeTableModel treeTableModel) {
  89         super();
  90 
  91         // Create the tree. It will be used as a renderer and editor.
  92         tree = new TreeTableCellRenderer(treeTableModel);
  93 
  94         // Install a tableModel representing the visible rows in the tree.
  95         super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
  96 
  97         // Force the JTable and JTree to share their row selection models.
  98         ListToTreeSelectionModelWrapper selectionWrapper = new
  99                                 ListToTreeSelectionModelWrapper();
 100         tree.setSelectionModel(selectionWrapper);
 101         setSelectionModel(selectionWrapper.getListSelectionModel());
 102 
 103         // Install the tree editor renderer and editor.
 104         setDefaultRenderer(TreeTableModel.class, tree);
 105         setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
 106 
 107         // No grid.
 108         setShowGrid(false);
 109 
 110         // No intercell spacing
 111         setIntercellSpacing(new Dimension(0, 0));
 112 
 113         // And update the height of the trees row to match that of
 114         // the table.
 115         if (tree.getRowHeight() < 1) {
 116             // Metal looks better like this.
 117             setRowHeight(20);
 118         }
 119     }
 120 
 121     /**
 122      * Overridden to message super and forward the method to the tree.
 123      * Since the tree is not actually in the component hieachy it will
 124      * never receive this unless we forward it in this manner.
 125      */
 126     public void updateUI() {
 127         super.updateUI();
 128         if(tree != null) {
 129             tree.updateUI();
 130             // Do this so that the editor is referencing the current renderer
 131             // from the tree. The renderer can potentially change each time
 132             // laf changes.
 133             setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
 134         }
 135         // Use the tree's default foreground and background colors in the
 136         // table.
 137         LookAndFeel.installColorsAndFont(this, "Tree.background",
 138                                          "Tree.foreground", "Tree.font");
 139     }
 140 
 141     /**
 142      * Workaround for BasicTableUI anomaly. Make sure the UI never tries to
 143      * resize the editor. The UI currently uses different techniques to
 144      * paint the renderers and editors and overriding setBounds() below
 145      * is not the right thing to do for an editor. Returning -1 for the
 146      * editing row in this case, ensures the editor is never painted.
 147      */
 148     public int getEditingRow() {
 149         return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 :
 150                 editingRow;
 151     }
 152 
 153     /**
 154      * Returns the actual row that is editing as <code>getEditingRow</code>
 155      * will always return -1.
 156      */
 157     private int realEditingRow() {
 158         return editingRow;
 159     }
 160 
 161     /**
 162      * This is overriden to invoke supers implementation, and then,
 163      * if the receiver is editing a Tree column, the editors bounds is
 164      * reset. The reason we have to do this is because JTable doesn't
 165      * think the table is being edited, as <code>getEditingRow</code> returns
 166      * -1, and therefore doesn't automaticly resize the editor for us.
 167      */
 168     public void sizeColumnsToFit(int resizingColumn) {
 169         super.sizeColumnsToFit(resizingColumn);
 170         if (getEditingColumn() != -1 && getColumnClass(editingColumn) ==
 171             TreeTableModel.class) {
 172             Rectangle cellRect = getCellRect(realEditingRow(),
 173                                              getEditingColumn(), false);
 174             Component component = getEditorComponent();
 175             component.setBounds(cellRect);
 176             component.validate();
 177         }
 178     }
 179 
 180     /**
 181      * Overridden to pass the new rowHeight to the tree.
 182      */
 183     public void setRowHeight(int rowHeight) {
 184         super.setRowHeight(rowHeight);
 185         if (tree != null && tree.getRowHeight() != rowHeight) {
 186             tree.setRowHeight(getRowHeight());
 187         }
 188     }
 189 
 190     /**
 191      * Returns the tree that is being shared between the model.
 192      */
 193     public JTree getTree() {
 194         return tree;
 195     }
 196 
 197     /**
 198      * Overriden to invoke repaint for the particular location if
 199      * the column contains the tree. This is done as the tree editor does
 200      * not fill the bounds of the cell, we need the renderer to paint
 201      * the tree in the background, and then draw the editor over it.
 202      */
 203     public boolean editCellAt(int row, int column, EventObject e){
 204         boolean retValue = super.editCellAt(row, column, e);
 205         if (retValue && getColumnClass(column) == TreeTableModel.class) {
 206             repaint(getCellRect(row, column, false));
 207         }
 208         return retValue;
 209     }
 210 
 211     /** A DefaultTreeCellRenderer which can optionally skip drawing
 212         all icons. */
 213     class JTreeTableCellRenderer extends DefaultTreeCellRenderer {
 214         public Icon getClosedIcon()        { return (showsIcons ? super.getClosedIcon()        : null); }
 215         public Icon getDefaultClosedIcon() { return (showsIcons ? super.getDefaultClosedIcon() : null); }
 216         public Icon getDefaultLeafIcon()   { return (showsIcons ? super.getDefaultLeafIcon()   : null); }
 217         public Icon getDefaultOpenIcon()   { return (showsIcons ? super.getDefaultOpenIcon()   : null); }
 218         public Icon getLeafIcon()          { return (showsIcons ? super.getLeafIcon()          : null); }
 219         public Icon getOpenIcon()          { return (showsIcons ? super.getOpenIcon()          : null); }
 220     }
 221 
 222     /**
 223      * A TreeCellRenderer that displays a JTree.
 224      */
 225     public class TreeTableCellRenderer extends JTree implements
 226                  TableCellRenderer {
 227         /** Last table/tree row asked to renderer. */
 228         protected int visibleRow;
 229         /** Border to draw around the tree, if this is non-null, it will
 230          * be painted. */
 231         protected Border highlightBorder;
 232 
 233         public TreeTableCellRenderer(TreeModel model) {
 234             super(model);
 235             setCellRenderer(new JTreeTableCellRenderer());
 236         }
 237 
 238         /**
 239          * updateUI is overridden to set the colors of the Tree's renderer
 240          * to match that of the table.
 241          */
 242         public void updateUI() {
 243             super.updateUI();
 244             // Make the tree's cell renderer use the table's cell selection
 245             // colors.
 246             TreeCellRenderer tcr = getCellRenderer();
 247             if (tcr instanceof DefaultTreeCellRenderer) {
 248                 DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr);
 249                 // For 1.1 uncomment this, 1.2 has a bug that will cause an
 250                 // exception to be thrown if the border selection color is
 251                 // null.
 252                 // dtcr.setBorderSelectionColor(null);
 253                 dtcr.setTextSelectionColor(UIManager.getColor
 254                                            ("Table.selectionForeground"));
 255                 dtcr.setBackgroundSelectionColor(UIManager.getColor
 256                                                 ("Table.selectionBackground"));
 257             }
 258         }
 259 
 260         /**
 261          * Sets the row height of the tree, and forwards the row height to
 262          * the table.
 263          */
 264         public void setRowHeight(int rowHeight) {
 265             if (rowHeight > 0) {
 266                 super.setRowHeight(rowHeight);
 267                 if (JTreeTable.this != null &&
 268                     JTreeTable.this.getRowHeight() != rowHeight) {
 269                     JTreeTable.this.setRowHeight(getRowHeight());
 270                 }
 271             }
 272         }
 273 
 274         /**
 275          * This is overridden to set the height to match that of the JTable.
 276          */
 277         public void setBounds(int x, int y, int w, int h) {
 278             super.setBounds(x, 0, w, JTreeTable.this.getHeight());
 279         }
 280 
 281         /**
 282          * Sublcassed to translate the graphics such that the last visible
 283          * row will be drawn at 0,0.
 284          */
 285         public void paint(Graphics g) {
 286             g.translate(0, -visibleRow * getRowHeight());
 287             super.paint(g);
 288             // Draw the Table border if we have focus.
 289             if (highlightBorder != null) {
 290                 highlightBorder.paintBorder(this, g, 0, visibleRow *
 291                                             getRowHeight(), getWidth(),
 292                                             getRowHeight());
 293             }
 294         }
 295 
 296         /**
 297          * TreeCellRenderer method. Overridden to update the visible row.
 298          */
 299         public Component getTableCellRendererComponent(JTable table,
 300                                                        Object value,
 301                                                        boolean isSelected,
 302                                                        boolean hasFocus,
 303                                                        int row, int column) {
 304             Color background;
 305             Color foreground;
 306 
 307             if(isSelected) {
 308                 background = table.getSelectionBackground();
 309                 foreground = table.getSelectionForeground();
 310             }
 311             else {
 312                 background = table.getBackground();
 313                 foreground = table.getForeground();
 314             }
 315             highlightBorder = null;
 316             if (realEditingRow() == row && getEditingColumn() == column) {
 317                 background = UIManager.getColor("Table.focusCellBackground");
 318                 foreground = UIManager.getColor("Table.focusCellForeground");
 319             }
 320             else if (hasFocus) {
 321                 highlightBorder = UIManager.getBorder
 322                                   ("Table.focusCellHighlightBorder");
 323                 if (isCellEditable(row, column)) {
 324                     background = UIManager.getColor
 325                                  ("Table.focusCellBackground");
 326                     foreground = UIManager.getColor
 327                                  ("Table.focusCellForeground");
 328                 }
 329             }
 330 
 331             visibleRow = row;
 332             setBackground(background);
 333 
 334             TreeCellRenderer tcr = getCellRenderer();
 335             if (tcr instanceof DefaultTreeCellRenderer) {
 336                 DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr);
 337                 if (isSelected) {
 338                     dtcr.setTextSelectionColor(foreground);
 339                     dtcr.setBackgroundSelectionColor(background);
 340                 }
 341                 else {
 342                     dtcr.setTextNonSelectionColor(foreground);
 343                     dtcr.setBackgroundNonSelectionColor(background);
 344                 }
 345             }
 346             return this;
 347         }
 348     }
 349 
 350 
 351     /**
 352      * An editor that can be used to edit the tree column. This extends
 353      * DefaultCellEditor and uses a JTextField (actually, TreeTableTextField)
 354      * to perform the actual editing.
 355      * <p>To support editing of the tree column we can not make the tree
 356      * editable. The reason this doesn't work is that you can not use
 357      * the same component for editing and renderering. The table may have
 358      * the need to paint cells, while a cell is being edited. If the same
 359      * component were used for the rendering and editing the component would
 360      * be moved around, and the contents would change. When editing, this
 361      * is undesirable, the contents of the text field must stay the same,
 362      * including the caret blinking, and selections persisting. For this
 363      * reason the editing is done via a TableCellEditor.
 364      * <p>Another interesting thing to be aware of is how tree positions
 365      * its render and editor. The render/editor is responsible for drawing the
 366      * icon indicating the type of node (leaf, branch...). The tree is
 367      * responsible for drawing any other indicators, perhaps an additional
 368      * +/- sign, or lines connecting the various nodes. So, the renderer
 369      * is positioned based on depth. On the other hand, table always makes
 370      * its editor fill the contents of the cell. To get the allusion
 371      * that the table cell editor is part of the tree, we don't want the
 372      * table cell editor to fill the cell bounds. We want it to be placed
 373      * in the same manner as tree places it editor, and have table message
 374      * the tree to paint any decorations the tree wants. Then, we would
 375      * only have to worry about the editing part. The approach taken
 376      * here is to determine where tree would place the editor, and to override
 377      * the <code>reshape</code> method in the JTextField component to
 378      * nudge the textfield to the location tree would place it. Since
 379      * JTreeTable will paint the tree behind the editor everything should
 380      * just work. So, that is what we are doing here. Determining of
 381      * the icon position will only work if the TreeCellRenderer is
 382      * an instance of DefaultTreeCellRenderer. If you need custom
 383      * TreeCellRenderers, that don't descend from DefaultTreeCellRenderer,
 384      * and you want to support editing in JTreeTable, you will have
 385      * to do something similiar.
 386      */
 387     public class TreeTableCellEditor extends DefaultCellEditor {
 388         public TreeTableCellEditor() {
 389             super(new TreeTableTextField());
 390         }
 391 
 392         /**
 393          * Overriden to determine an offset that tree would place the
 394          * editor at. The offset is determined from the
 395          * <code>getRowBounds</code> JTree method, and additionaly
 396          * from the icon DefaultTreeCellRenderer will use.
 397          * <p>The offset is then set on the TreeTableTextField component
 398          * created in the constructor, and returned.
 399          */
 400         public Component getTableCellEditorComponent(JTable table,
 401                                                      Object value,
 402                                                      boolean isSelected,
 403                                                      int r, int c) {
 404             Component component = super.getTableCellEditorComponent
 405                 (table, value, isSelected, r, c);
 406             JTree t = getTree();
 407             boolean rv = t.isRootVisible();
 408             int offsetRow = rv ? r : r - 1;
 409             Rectangle bounds = t.getRowBounds(offsetRow);
 410             int offset = bounds.x;
 411             TreeCellRenderer tcr = t.getCellRenderer();
 412             if (tcr instanceof DefaultTreeCellRenderer) {
 413                 Object node = t.getPathForRow(offsetRow).
 414                                 getLastPathComponent();
 415                 Icon icon;
 416                 if (t.getModel().isLeaf(node))
 417                     icon = ((DefaultTreeCellRenderer)tcr).getLeafIcon();
 418                 else if (tree.isExpanded(offsetRow))
 419                     icon = ((DefaultTreeCellRenderer)tcr).getOpenIcon();
 420                 else
 421                     icon = ((DefaultTreeCellRenderer)tcr).getClosedIcon();
 422                 if (icon != null) {
 423                     offset += ((DefaultTreeCellRenderer)tcr).getIconTextGap() +
 424                               icon.getIconWidth();
 425                 }
 426             }
 427             ((TreeTableTextField)getComponent()).offset = offset;
 428             return component;
 429         }
 430 
 431         /**
 432          * This is overriden to forward the event to the tree. This will
 433          * return true if the click count >= 3, or the event is null.
 434          */
 435         public boolean isCellEditable(EventObject e) {
 436             if (e instanceof MouseEvent) {
 437                 MouseEvent me = (MouseEvent)e;
 438                 // If the modifiers are not 0 (or the left mouse button),
 439                 // tree may try and toggle the selection, and table
 440                 // will then try and toggle, resulting in the
 441                 // selection remaining the same. To avoid this, we
 442                 // only dispatch when the modifiers are 0 (or the left mouse
 443                 // button).
 444                 if (me.getModifiers() == 0 ||
 445                     me.getModifiers() == InputEvent.BUTTON1_MASK) {
 446                     for (int counter = getColumnCount() - 1; counter >= 0;
 447                          counter--) {
 448                         if (getColumnClass(counter) == TreeTableModel.class) {
 449                             MouseEvent newME = new MouseEvent
 450                                   (JTreeTable.this.tree, me.getID(),
 451                                    me.getWhen(), me.getModifiers(),
 452                                    me.getX() - getCellRect(0, counter, true).x,
 453                                    me.getY(), me.getClickCount(),
 454                                    me.isPopupTrigger());
 455                             JTreeTable.this.tree.dispatchEvent(newME);
 456                             break;
 457                         }
 458                     }
 459                 }
 460                 if (me.getClickCount() >= 3) {
 461                     return treeEditable;
 462                 }
 463                 return false;
 464             }
 465             if (e == null) {
 466                 return treeEditable;
 467             }
 468             return false;
 469         }
 470     }
 471 
 472 
 473     /**
 474      * Component used by TreeTableCellEditor. The only thing this does
 475      * is to override the <code>reshape</code> method, and to ALWAYS
 476      * make the x location be <code>offset</code>.
 477      */
 478     static class TreeTableTextField extends JTextField {
 479         public int offset;
 480 
 481         public void setBounds(int x, int y, int w, int h) {
 482             int newX = Math.max(x, offset);
 483             super.setBounds(newX, y, w - (newX - x), h);
 484         }
 485     }
 486 
 487 
 488     /**
 489      * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
 490      * to listen for changes in the ListSelectionModel it maintains. Once
 491      * a change in the ListSelectionModel happens, the paths are updated
 492      * in the DefaultTreeSelectionModel.
 493      */
 494     class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
 495         /** Set to true when we are updating the ListSelectionModel. */
 496         protected boolean         updatingListSelectionModel;
 497 
 498         public ListToTreeSelectionModelWrapper() {
 499             super();
 500             getListSelectionModel().addListSelectionListener
 501                                     (createListSelectionListener());
 502         }
 503 
 504         /**
 505          * Returns the list selection model. ListToTreeSelectionModelWrapper
 506          * listens for changes to this model and updates the selected paths
 507          * accordingly.
 508          */
 509         ListSelectionModel getListSelectionModel() {
 510             return listSelectionModel;
 511         }
 512 
 513         /**
 514          * This is overridden to set <code>updatingListSelectionModel</code>
 515          * and message super. This is the only place DefaultTreeSelectionModel
 516          * alters the ListSelectionModel.
 517          */
 518         public void resetRowSelection() {
 519             if(!updatingListSelectionModel) {
 520                 updatingListSelectionModel = true;
 521                 try {
 522                     super.resetRowSelection();
 523                 }
 524                 finally {
 525                     updatingListSelectionModel = false;
 526                 }
 527             }
 528             // Notice how we don't message super if
 529             // updatingListSelectionModel is true. If
 530             // updatingListSelectionModel is true, it implies the
 531             // ListSelectionModel has already been updated and the
 532             // paths are the only thing that needs to be updated.
 533         }
 534 
 535         /**
 536          * Creates and returns an instance of ListSelectionHandler.
 537          */
 538         protected ListSelectionListener createListSelectionListener() {
 539             return new ListSelectionHandler();
 540         }
 541 
 542         /**
 543          * If <code>updatingListSelectionModel</code> is false, this will
 544          * reset the selected paths from the selected rows in the list
 545          * selection model.
 546          */
 547         protected void updateSelectedPathsFromSelectedRows() {
 548             if(!updatingListSelectionModel) {
 549                 updatingListSelectionModel = true;
 550                 try {
 551                     // This is way expensive, ListSelectionModel needs an
 552                     // enumerator for iterating.
 553                     int        min = listSelectionModel.getMinSelectionIndex();
 554                     int        max = listSelectionModel.getMaxSelectionIndex();
 555 
 556                     clearSelection();
 557                     if(min != -1 && max != -1) {
 558                         for(int counter = min; counter <= max; counter++) {
 559                             if(listSelectionModel.isSelectedIndex(counter)) {
 560                                 TreePath     selPath = tree.getPathForRow
 561                                                             (counter);
 562 
 563                                 if(selPath != null) {
 564                                     addSelectionPath(selPath);
 565                                 }
 566                             }
 567                         }
 568                     }
 569                 }
 570                 finally {
 571                     updatingListSelectionModel = false;
 572                 }
 573             }
 574         }
 575 
 576         /**
 577          * Class responsible for calling updateSelectedPathsFromSelectedRows
 578          * when the selection of the list changse.
 579          */
 580         class ListSelectionHandler implements ListSelectionListener {
 581             public void valueChanged(ListSelectionEvent e) {
 582                 updateSelectedPathsFromSelectedRows();
 583             }
 584         }
 585     }
 586 }