001/* ---------------------------------------------------------------------------- 002 The Kiwi Toolkit - A Java Class Library 003 Copyright (C) 1998-2004 Mark A. Lindner 004 005 This library is free software; you can redistribute it and/or 006 modify it under the terms of the GNU General Public License as 007 published by the Free Software Foundation; either version 2 of the 008 License, or (at your option) any later version. 009 010 This library is distributed in the hope that it will be useful, 011 but WITHOUT ANY WARRANTY; without even the implied warranty of 012 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 013 General Public License for more details. 014 015 You should have received a copy of the GNU General Public License 016 along with this library; if not, write to the Free Software 017 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 018 02111-1307, USA. 019 020 The author may be contacted at: mark_a_lindner@yahoo.com 021 ---------------------------------------------------------------------------- 022 $Log: KTreeTable.java,v $ 023 Revision 1.2 2004/05/31 07:52:30 markl 024 finished implementation 025 026 Revision 1.1 2004/03/23 06:49:23 markl 027 New class. 028 ---------------------------------------------------------------------------- 029*/ 030 031package kiwi.ui; 032 033import java.awt.*; 034import java.awt.event.*; 035import java.util.*; 036import javax.swing.*; 037import javax.swing.event.*; 038import javax.swing.tree.*; 039import javax.swing.table.*; 040 041import kiwi.event.*; 042import kiwi.ui.model.*; 043import kiwi.util.*; 044 045/* This class incorporates ideas from Sun's TreeTable example code, as 046 * well as from the Texas Instruments TreeTable implementation, which is 047 * also built around the Sun example. 048 */ 049 050/** A combination tree/table component. This class is an extension of 051 * <code>KTable</code> in which the first column presents information 052 * in a hierarchical form in the same manner as a <code>JTree</code>. 053 * <p> 054 * See {@link kiwi.ui.FilesystemTableView kiwi.ui.FilesystemTableView} 055 * for an example usage of this component. 056 * 057 * @author Mark Lindner 058 * @since Kiwi 2.0 059 */ 060 061public class KTreeTable extends KTable 062 { 063 protected TreeTableCellRenderer treeRenderer; 064 protected TreeTableCellEditor treeEditor; 065 private KTreeModel _model; 066 KTreeModelTreeTableAdapter tableModel; 067 private boolean rootVisible = false; 068 069 /** 070 * Construct a new <code>KTreeTable</code>. 071 */ 072 073 public KTreeTable() 074 { 075 super(); 076 077 setRowHeight(18); 078 getTableHeader().setReorderingAllowed(false); 079 } 080 081 /** Overridden to prevent callers from enabling sortable mode. */ 082 083 public final void setSortable(boolean flag) 084 { 085 // ignore 086 } 087 088 /** Overridden to prevent callers from enabling editable mode. */ 089 090 public final void setEditable(boolean editable) 091 { 092 // ignore 093 } 094 095 /** Overridden to prevent column #0 (the column that displays the tree) 096 * from having its renderer or editor modified. 097 */ 098 099 public final void configureColumn(int column, TableCellRenderer renderer, 100 TableCellEditor editor) 101 { 102 if(column != 0) 103 super.configureColumn(column, renderer, editor); 104 } 105 106 /** Set the tree model for this component. 107 * 108 * @param model The new tree model. 109 */ 110 111 public final void setTreeModel(KTreeModel model) 112 { 113 _model = model; 114 115 // _model.collapse(_model.getRoot()); 116 117 // create the renderer and editor, and set the models 118 119 treeRenderer = new TreeTableCellRenderer(model); 120 treeRenderer.setRowHeight(getRowHeight()); 121 KTreeModelTreeAdapter treeModel 122 = new KTreeModelTreeAdapter(treeRenderer); 123 treeRenderer.setActualModel(treeModel); 124 treeModel.setTreeModel(model); 125 treeRenderer.setRootVisible(rootVisible); 126 127 tableModel = new KTreeModelTreeTableAdapter(treeRenderer); 128 tableModel.setTreeModel(model); 129 super.setModel(tableModel); 130 131 // Force the JTable and JTree to share their row selection models. 132 133 TreeTableSelectionModel treeTableSelModel = new TreeTableSelectionModel(); 134 treeRenderer.setSelectionModel(treeTableSelModel); 135 setSelectionModel(treeTableSelModel.getListSelectionModel()); 136 137 // Make the tree and table row heights the same. 138 139 treeRenderer.setRowHeight(getRowHeight()); 140 141 // Install the tree editor renderer and editor. 142 143 treeEditor = new TreeTableCellEditor(); 144 145 super.configureColumn(0, treeRenderer, treeEditor); 146 147 setShowGrid(false); 148 setIntercellSpacing(new Dimension(0, 0)); 149 } 150 151 /** Get the tree path for the currently selected node, or the first selected 152 * node if more than one is selected. 153 * 154 * @return The tree path to the selected node. 155 */ 156 157 public final TreePath getSelectionPath() 158 { 159 if(treeRenderer == null) 160 return(null); 161 else 162 return(treeRenderer.getSelectionPath()); 163 } 164 165 /** Set the selection to the specified tree path. 166 * 167 * @param path The tree path to the node to select. 168 */ 169 170 public final void setSelectionPath(TreePath path) 171 { 172 if(treeRenderer != null) 173 treeRenderer.setSelectionPath(path); 174 } 175 176 /** Get the tree paths for the currently selected nodes. 177 * 178 * @return An array of tree paths to the selected nodes. 179 */ 180 181 public final TreePath[] getSelectionPaths() 182 { 183 if(treeRenderer == null) 184 return(null); 185 else 186 return(treeRenderer.getSelectionPaths()); 187 } 188 189 /** Set the selection to the specified tree paths. 190 * 191 * @param paths The tree paths to the nodes to select. 192 */ 193 194 public final void setSelectionPaths(TreePath paths[]) 195 { 196 if(treeRenderer != null) 197 treeRenderer.setSelectionPaths(paths); 198 } 199 200 /** Determine if the node at the given tree path is currently selected. 201 * 202 * @param path The path to the node. 203 * @return <code>true</code> if the node is selected, <code>false</code> 204 * otherwise. 205 */ 206 207 public final boolean isPathSelected(TreePath path) 208 { 209 if(treeRenderer == null) 210 return(false); 211 else 212 return(treeRenderer.isPathSelected(path)); 213 } 214 215 /** Overridden to ensure that tree and table model row heights are the same. 216 */ 217 218 public final void setRowHeight(int rowHeight) 219 { 220 super.setRowHeight(rowHeight); 221 if(treeRenderer != null) 222 treeRenderer.setRowHeight(rowHeight); 223 } 224 225 /* 226 */ 227 228 public final boolean isCellEditable(int row, int column) 229 { 230 return(column == 0); 231 } 232 233 /* Workaround for BasicTableUI anomaly. Make sure the UI never tries to 234 * paint the editor. The UI currently uses different techniques to 235 * paint the renderers and editors and overriding setBounds() below 236 * is not the right thing to do for an editor. Returning -1 for the 237 * editing row in this case, ensures the editor is never painted. 238 */ 239 240 /** Get the row number that is currently being edited. 241 * 242 * @return The row that is being edited, or -1 if no edit is in progress. 243 */ 244 245 public final int getEditingRow() 246 { 247 return((editingColumn == 0) ? -1 : editingRow); 248 } 249 250 /** Get the selected item. 251 * 252 * @return The item that is currently selected in the tree, or the first 253 * selected item if more than one node is selected. 254 */ 255 256 public final Object getSelectedItem() 257 { 258 int row = getSelectedRow(); 259 260 if(row < 0) 261 return(null); 262 263 return(getValueAt(row, 0)); 264 } 265 266 /** Get the selected items. 267 * 268 * @return An array of items that are currently selected in the tree. 269 */ 270 271 public final Object[] getSelectedItems() 272 { 273 int rows[] = getSelectedRows(); 274 275 Object items[] = new Object[rows.length]; 276 for(int i = 0; i < rows.length; i++) 277 items[i] = getValueAt(rows[i], 0); 278 279 return(items); 280 } 281 282 /** Specify whether the root node should be visible or not. 283 * 284 * @param flag <code>true</code> if the root node should be visible in the 285 * tree table, <code>false</code> if not. 286 */ 287 288 public final void setRootVisible(boolean flag) 289 { 290 this.rootVisible = flag; 291 292 if(treeRenderer != null) 293 treeRenderer.setRootVisible(flag); 294 } 295 296 /** Get the tree path for the node at the given row. 297 * 298 * @param row The visible row in the tree table. 299 * @return A tree path to the node at that row. 300 */ 301 302 public final TreePath getPathForRow(int row) 303 { 304 return(treeRenderer.getPathForRow(row)); 305 } 306 307 /* 308 */ 309 310 private class TreeTableCellRenderer extends JTree 311 implements TableCellRenderer 312 { 313 protected int visibleRow; 314 private KTreeModelTreeCellRenderer renderer; 315 316 TreeTableCellRenderer(KTreeModel model) 317 { 318 renderer = new KTreeModelTreeCellRenderer(); 319 renderer.setHighlightBackground(getSelectionBackground()); 320 renderer.setHighlightForeground(getSelectionForeground()); 321 renderer.setModel(model); 322 setRootVisible(false); 323 } 324 325 public void setActualModel(TreeModel model) 326 { 327 super.setModel(model); 328 setCellRenderer(renderer); 329 } 330 331 public void setBounds(int x, int y, int w, int h) 332 { 333 super.setBounds(x, 0, w, KTreeTable.this.getHeight()); 334 } 335 336 public void paint(Graphics g) 337 { 338 g.translate(0, -visibleRow * getRowHeight()); 339 super.paint(g); 340 } 341 342 public Component getTableCellRendererComponent(JTable table, 343 Object value, 344 boolean isSelected, 345 boolean hasFocus, 346 int row, int column) 347 { 348 if(isSelected) 349 setBackground(table.getSelectionBackground()); 350 else 351 setBackground(table.getBackground()); 352 353 visibleRow = row; 354 return(this); 355 } 356 } 357 358 /* 359 */ 360 361 private class TreeTableCellEditor extends AbstractCellEditor 362 implements TableCellEditor 363 { 364 public Component getTableCellEditorComponent(JTable table, Object value, 365 boolean isSelected, int r, 366 int c) 367 { 368 return(treeRenderer); 369 } 370 371 // Somewhat hackish. In order to get the tree nodes to expand/collapse 372 // in response to mouse clicks in the cells, we need to install the JTree 373 // as a cell editor. But once any node is expanded, the table goes into 374 // "editing" mode, and highlighting stops working. Therefore we have to 375 // pre-empt that by returning false here, so that editing doesn't actually 376 // start...but we still need to forward the event to the tree so that it 377 // can expand/collapse as necessary. 378 379 public boolean isCellEditable(EventObject evt) 380 { 381 if(evt instanceof MouseEvent) 382 { 383 MouseEvent mevt = (MouseEvent)evt; 384 385 int col = 0; 386 387 MouseEvent newEvt = new MouseEvent( treeRenderer, mevt.getID(), 388 mevt.getWhen(), mevt.getModifiers() 389 , 390 mevt.getX() - getCellRect(0, col, 391 true).x, 392 mevt.getY(), mevt.getClickCount(), 393 mevt.isPopupTrigger()); 394 395 treeRenderer.dispatchEvent(newEvt); 396 } 397 398 return(false); 399 } 400 401 public Object getCellEditorValue() 402 { 403 return(null); 404 } 405 } 406 407 /* 408 */ 409 410 private class TreeTableSelectionModel extends DefaultTreeSelectionModel 411 implements ListSelectionListener 412 { 413 private boolean updating = false; 414 415 TreeTableSelectionModel() 416 { 417 getListSelectionModel().addListSelectionListener(this); 418 } 419 420 ListSelectionModel getListSelectionModel() 421 { 422 return(listSelectionModel); 423 } 424 425 public void resetRowSelection() 426 { 427 if(updating) 428 return; 429 430 updating = true; 431 super.resetRowSelection(); 432 updating = false; 433 } 434 435 public void valueChanged(ListSelectionEvent evt) 436 { 437 if(updating) 438 return; 439 440 updating = true; 441 442 int minRow = evt.getFirstIndex(); 443 int maxRow = evt.getLastIndex(); 444 445 for(int i = minRow; i <= maxRow; i++) 446 { 447 TreePath treePath = treeRenderer.getPathForRow(i); 448 449 if(listSelectionModel.isSelectedIndex(i)) 450 treeRenderer.addSelectionPath(treePath); 451 else 452 treeRenderer.removeSelectionPath(treePath); 453 } 454 455 updating = false; 456 } 457 } 458 459 } 460 461/* end of source file */