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