001/* 002 The Broad Institute 003 SOFTWARE COPYRIGHT NOTICE AGREEMENT 004 This software and its documentation are copyright (2003-2008) by the 005 Broad Institute/Massachusetts Institute of Technology. All rights are 006 reserved. 007 008 This software is supplied without any warranty or guaranteed support 009 whatsoever. Neither the Broad Institute nor MIT can be responsible for its 010 use, misuse, or functionality. 011*/ 012 013 014/* 015 @(#)JTreeTable.java 1.2 98/10/27 016 Copyright 1997, 1998 by Sun Microsystems, Inc., 017 901 San Antonio Road, Palo Alto, California, 94303, U.S.A. 018 All rights reserved. 019 This software is the confidential and proprietary information 020 of Sun Microsystems, Inc. ("Confidential Information"). You 021 shall not disclose such Confidential Information and shall use 022 it only in accordance with the terms of the license agreement 023 you entered into with Sun. 024 */ 025 026package ca.bc.webarts.widgets.treetable; 027//package org.genomespace.gsui.ui.treetable; 028 029import java.awt.Color; 030import java.awt.Component; 031import java.awt.Dimension; 032import java.awt.Graphics; 033import java.awt.Rectangle; 034import java.awt.event.*; 035import java.util.EventObject; 036import javax.swing.*; 037import javax.swing.event.*; 038import javax.swing.table.*; 039import javax.swing.tree.*; 040//import org.jdesktop.swing.treetable.TreeTableModel; 041 042import org.jdesktop.swingx.treetable.TreeTableModel; 043 044/** 045 * This example shows how to create a simple JTreeTable component, by using a 046 * JTree as a tree (and editor) for the cells in a particular column in the 047 * JTable. 048 * 049 * @author Philip Milne 050 * @author Scott Violet 051 * @version 1.2 10/27/98 052 */ 053public class JTreeTable extends JTable { 054 /** A subclass of JTree. */ 055 protected TreeTableCellRenderer tree; 056 TreeTableModelAdapter tableModel; 057 058 public JTreeTable(TreeTableModel treeTableModel) { 059 super(); 060 061 // Create the tree. It will be used as a tree and editor. 062 tree = new TreeTableCellRenderer(treeTableModel); 063 tree.setRootVisible(false); 064 065 // Install a tableModel representing the visible rows in the tree. 066 tableModel = new TreeTableModelAdapter(treeTableModel, tree); 067 super.setModel(tableModel); 068 069 // Force the JTable and JTree to share their row selection models. 070 ListToTreeSelectionModelWrapper selectionWrapper = new 071 ListToTreeSelectionModelWrapper(); 072 tree.setSelectionModel(selectionWrapper); 073 setSelectionModel(selectionWrapper.getListSelectionModel()); 074 // Install the tree editor tree and editor. 075 setDefaultRenderer(TreeTableModel.class, tree); 076 setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor()); 077 078 // No grid. 079 setShowGrid(false); 080 081 // No intercell spacing 082 setIntercellSpacing(new Dimension(0, 0)); 083 084 // And update the height of the trees row to match that of 085 // the table. 086 if(tree.getRowHeight() < 1) { 087 // Metal looks better like this. 088 setRowHeight(18); 089 } 090 NoHighlightRenderer r = new NoHighlightRenderer(); 091 defaultRenderersByColumnClass.put(String.class, r); 092 093 // addMouseListener(new MouseAdapter() { 094 // public void mouseClicked(MouseEvent e) { 095 // expandOrCollapseNode(e); 096 //} 097 //}); 098 } 099 100 public static class NoHighlightRenderer extends DefaultTableCellRenderer { 101 public Component getTableCellRendererComponent(JTable table, 102 Object value, 103 boolean isSelected, boolean hasFocus, 104 int r, int c) { 105 return super.getTableCellRendererComponent(table, 106 value, 107 isSelected, false, 108 r, c); 109 } 110 } 111 112 113 public void addTreeSelectionListener(javax.swing.event.TreeSelectionListener l) { 114 tree.addTreeSelectionListener(l); 115 } 116 117 public TreePath getSelectionPath() { 118 return tree.getSelectionPath(); 119 } 120 121 public boolean editCellAt(int row, int column, EventObject e) { 122 expandOrCollapseNode(e);// RG: Fix Issue 49! 123 boolean canEdit = super.editCellAt(row, column, e); 124 if(canEdit && isHierarchical(column)) { 125 repaint(getCellRect(row, column, false)); 126 } 127 return canEdit; 128 } 129 130 131 /** 132 * Overridden to message super and forward the method to the tree. Since the 133 * tree is not actually in the component hieachy it will never receive this 134 * unless we forward it in this manner. 135 */ 136 public void updateUI() { 137 super.updateUI(); 138 if(tree != null) { 139 tree.updateUI(); 140 } 141 // Use the tree's default foreground and background colors in the 142 // table. 143 LookAndFeel.installColorsAndFont(this, "Tree.background", 144 "Tree.foreground", "Tree.font"); 145 } 146 147 private void expandOrCollapseNode(EventObject e) { 148 if(e instanceof MouseEvent) { 149 MouseEvent me = (MouseEvent) e; 150 // If the modifiers are not 0 (or the left mouse button), 151 // tree may try and toggle the selection, and table 152 // will then try and toggle, resulting in the 153 // selection remaining the same. To avoid this, we 154 // only dispatch when the modifiers are 0 (or the left mouse 155 // button). 156 if(me.getModifiers() == 0 || 157 me.getModifiers() == java.awt.event.InputEvent.BUTTON1_MASK) { 158 int count = getColumnCount(); 159 160 for(int i = 0; i < count; i++) { 161 if(isHierarchical(i)) { 162 int savedHeight = tree.getRowHeight(); 163 tree.setRowHeight(getRowHeight()); 164 MouseEvent pressed = new MouseEvent 165 (tree, 166 me.getID(), 167 me.getWhen(), 168 me.getModifiers(), 169 me.getX() - getCellRect(0, i, false).x, 170 me.getY(), 171 me.getClickCount(), 172 me.isPopupTrigger()); 173 tree.dispatchEvent(pressed); 174 // For Mac OS X, we need to dispatch a MOUSE_RELEASED as well 175 MouseEvent released = new MouseEvent 176 (tree, 177 java.awt.event.MouseEvent.MOUSE_RELEASED, 178 pressed.getWhen(), 179 pressed.getModifiers(), 180 pressed.getX(), 181 pressed.getY(), 182 pressed.getClickCount(), 183 pressed.isPopupTrigger()); 184 tree.dispatchEvent(released); 185 tree.setRowHeight(savedHeight); 186 break; 187 } 188 } 189 } 190 } 191 } 192 193 194 /** 195 * Selects the node identified by the specified path. If any component of 196 * the path is hidden (under a collapsed node), and getExpandsSelectedPaths 197 * is true it is exposed (made viewable). 198 * 199 * @param path The new selectionPath value 200 */ 201 public void setSelectionPath(TreePath path) { 202 tree.setSelectionPath(path); 203 } 204 205 206 /** 207 * Overridden to pass the new rowHeight to the tree. 208 * 209 * @param rowHeight The new rowHeight value 210 */ 211 public void setRowHeight(int rowHeight) { 212 super.setRowHeight(rowHeight); 213 if(tree != null && tree.getRowHeight() != rowHeight) { 214 tree.setRowHeight(getRowHeight()); 215 } 216 } 217 218 219 /** 220 * Determines if the specified column contains hierarchical nodes. 221 * 222 * @param column zero-based index of the column 223 * @return true if the class of objects in the specified column 224 * implement the {@link javax.swing.tree.TreeNode} interface; false 225 * otherwise. 226 */ 227 public boolean isHierarchical(int column) { 228 return TreeTableModel.class.isAssignableFrom( 229 getColumnClass(column)); 230 } 231 232 233 public boolean isExpanded(TreePath path) { 234 return tree.isExpanded(path); 235 } 236 237 238 /** 239 * Returns the TreePath for a given x,y location. 240 * 241 * @param x x value 242 * @param y y value 243 * @return the <code>TreePath</code> for the givern location. 244 */ 245 public TreePath getPathForLocation(int x, int y) { 246 int row = rowAtPoint(new java.awt.Point(x, y)); 247 if(row == -1) { 248 return null; 249 } 250 return tree.getPathForRow(row); 251 } 252 253 254 /* 255 Workaround for BasicTableUI anomaly. Make sure the UI never tries to 256 paint the editor. The UI currently uses different techniques to 257 paint the trees and editors and overriding setBounds() below 258 is not the right thing to do for an editor. Returning -1 for the 259 editing row in this case, ensures the editor is never painted. 260 */ 261 public int getEditingRow() { 262 return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 : 263 editingRow; 264 } 265 266 267 /** 268 * Returns the tree that is being shared between the model. 269 * 270 * @return The tree value 271 */ 272 public JTree getTree() { 273 return tree; 274 } 275 276 277 /** 278 * A TreeCellRenderer that displays a JTree. 279 * 280 * @author Joshua Gould 281 */ 282 public class TreeTableCellRenderer extends JTree implements 283 TableCellRenderer { 284 /** Last table/tree row asked to tree. */ 285 protected int visibleRow; 286 287 288 public TreeTableCellRenderer(TreeModel model) { 289 super(model); 290 } 291 292 293 /** 294 * updateUI is overridden to set the colors of the Tree's tree to match 295 * that of the table. 296 */ 297 public void updateUI() { 298 super.updateUI(); 299 // Make the tree's cell tree use the table's cell selection 300 // colors. 301 TreeCellRenderer tcr = getCellRenderer(); 302 if(tcr instanceof DefaultTreeCellRenderer) { 303 DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr); 304 // For 1.1 uncomment this, 1.2 has a bug that will cause an 305 // exception to be thrown if the border selection color is 306 // null. 307 // dtcr.setBorderSelectionColor(null); 308 309 // dtcr.setTextSelectionColor(UIManager.getColor ("Table.selectionForeground")); 310 dtcr.setBackgroundSelectionColor(UIManager.getColor 311 ("Table.selectionBackground")); 312 313 314 dtcr.setTextSelectionColor(Color.white); // TODO: HACK: until I can figure out how to use the look and feel properly to set the colors 315 316 317 } 318 } 319 320 321 /** 322 * Sublcassed to translate the graphics such that the last visible row 323 * will be drawn at 0,0. 324 * 325 * @param g Description of the Parameter 326 */ 327 public void paint(Graphics g) { 328 g.translate(0, -visibleRow * getRowHeight()); 329 super.paint(g); 330 } 331 332 333 /** 334 * Sets the row height of the tree, and forwards the row height to the 335 * table. 336 * 337 * @param rowHeight The new rowHeight value 338 */ 339 public void setRowHeight(int rowHeight) { 340 if(rowHeight > 0) { 341 super.setRowHeight(rowHeight); 342 if(JTreeTable.this != null && 343 JTreeTable.this.getRowHeight() != rowHeight) { 344 JTreeTable.this.setRowHeight(getRowHeight()); 345 } 346 } 347 } 348 349 350 /** 351 * This is overridden to set the height to match that of the JTable. 352 * 353 * @param x The new bounds value 354 * @param y The new bounds value 355 * @param w The new bounds value 356 * @param h The new bounds value 357 */ 358 public void setBounds(int x, int y, int w, int h) { 359 super.setBounds(x, 0, w, JTreeTable.this.getHeight()); 360 } 361 362 363 /** 364 * TreeCellRenderer method. Overridden to update the visible row. 365 * 366 * @param table Description of the Parameter 367 * @param value Description of the Parameter 368 * @param isSelected Description of the Parameter 369 * @param hasFocus Description of the Parameter 370 * @param row Description of the Parameter 371 * @param column Description of the Parameter 372 * @return The tableCellRendererComponent value 373 */ 374 public Component getTableCellRendererComponent(JTable table, 375 Object value, 376 boolean isSelected, 377 boolean hasFocus, 378 int row, int column) { 379 if(isSelected) { 380 setBackground(table.getSelectionBackground()); 381 } else { 382 setBackground(table.getBackground()); 383 } 384 385 visibleRow = row; 386 return this; 387 } 388 } 389 390 391 /** 392 * TreeTableCellEditor implementation. Component returned is the JTree. 393 * 394 * @author Joshua Gould 395 */ 396 public class TreeTableCellEditor extends AbstractCellEditor implements 397 TableCellEditor { 398 public Component getTableCellEditorComponent(JTable table, 399 Object value, 400 boolean isSelected, 401 int r, int c) { 402 return tree; 403 } 404 405 406 /** 407 * Overridden to return false, and if the event is a mouse event it is 408 * forwarded to the tree.<p> 409 * 410 * The behavior for this is debatable, and should really be offered as a 411 * property. By returning false, all keyboard actions are implemented in 412 * terms of the table. By returning true, the tree would get a chance to 413 * do something with the keyboard events. For the most part this is ok. 414 * But for certain keys, such as left/right, the tree will 415 * expand/collapse where as the table focus should really move to a 416 * different column. Page up/down should also be implemented in terms of 417 * the table. By returning false this also has the added benefit that 418 * clicking outside of the bounds of the tree node, but still in the tree 419 * column will select the row, whereas if this returned true that 420 * wouldn't be the case. <p> 421 * 422 * By returning false we are also enforcing the policy that the tree will 423 * never be editable (at least by a key sequence). 424 * 425 * @param e Description of the Parameter 426 * @return The cellEditable value 427 */ 428 public boolean isCellEditable(EventObject e) { 429 if(e instanceof MouseEvent) { 430 for(int counter = getColumnCount() - 1; counter >= 0; 431 counter--) { 432 if(getColumnClass(counter) == TreeTableModel.class) { 433 MouseEvent me = (MouseEvent) e; 434 MouseEvent newME = new MouseEvent(tree, me.getID(), 435 me.getWhen(), me.getModifiers(), 436 me.getX() - getCellRect(0, counter, true).x, 437 me.getY(), me.getClickCount(), 438 me.isPopupTrigger()); 439 tree.dispatchEvent(newME); 440 break; 441 } 442 } 443 } 444 return false; 445 } 446 } 447 448 449 /** 450 * ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel to 451 * listen for changes in the ListSelectionModel it maintains. Once a change 452 * in the ListSelectionModel happens, the paths are updated in the 453 * DefaultTreeSelectionModel. 454 * 455 * @author Joshua Gould 456 */ 457 class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel { 458 /** Set to true when we are updating the ListSelectionModel. */ 459 protected boolean updatingListSelectionModel; 460 461 462 public ListToTreeSelectionModelWrapper() { 463 super(); 464 getListSelectionModel().addListSelectionListener 465 (createListSelectionListener()); 466 } 467 468 469 /** 470 * This is overridden to set <code>updatingListSelectionModel</code> and 471 * message super. This is the only place DefaultTreeSelectionModel alters 472 * the ListSelectionModel. 473 */ 474 public void resetRowSelection() { 475 if(!updatingListSelectionModel) { 476 updatingListSelectionModel = true; 477 try { 478 super.resetRowSelection(); 479 } finally { 480 updatingListSelectionModel = false; 481 } 482 } 483 // Notice how we don't message super if 484 // updatingListSelectionModel is true. If 485 // updatingListSelectionModel is true, it implies the 486 // ListSelectionModel has already been updated and the 487 // paths are the only thing that needs to be updated. 488 } 489 490 491 /** 492 * Creates and returns an instance of ListSelectionHandler. 493 * 494 * @return Description of the Return Value 495 */ 496 protected ListSelectionListener createListSelectionListener() { 497 return new ListSelectionHandler(); 498 } 499 500 501 /** 502 * If <code>updatingListSelectionModel</code> is false, this will reset 503 * the selected paths from the selected rows in the list selection model. 504 */ 505 protected void updateSelectedPathsFromSelectedRows() { 506 if(!updatingListSelectionModel) { 507 updatingListSelectionModel = true; 508 try { 509 // This is way expensive, ListSelectionModel needs an 510 // enumerator for iterating. 511 int min = listSelectionModel.getMinSelectionIndex(); 512 int max = listSelectionModel.getMaxSelectionIndex(); 513 514 clearSelection(); 515 if(min != -1 && max != -1) { 516 for(int counter = min; counter <= max; counter++) { 517 if(listSelectionModel.isSelectedIndex(counter)) { 518 TreePath selPath = tree.getPathForRow 519 (counter); 520 521 if(selPath != null) { 522 addSelectionPath(selPath); 523 } 524 } 525 } 526 } 527 } finally { 528 updatingListSelectionModel = false; 529 } 530 } 531 } 532 533 534 /** 535 * Returns the list selection model. ListToTreeSelectionModelWrapper 536 * listens for changes to this model and updates the selected paths 537 * accordingly. 538 * 539 * @return The listSelectionModel value 540 */ 541 ListSelectionModel getListSelectionModel() { 542 return listSelectionModel; 543 } 544 545 546 /** 547 * Class responsible for calling updateSelectedPathsFromSelectedRows when 548 * the selection of the list changse. 549 * 550 * @author Joshua Gould 551 */ 552 class ListSelectionHandler implements ListSelectionListener { 553 public void valueChanged(ListSelectionEvent e) { 554 updateSelectedPathsFromSelectedRows(); 555 } 556 } 557 } 558} 559