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 }