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 */