/*
* @(#)JTreeTable.java 1.2 98/10/27
*
* Copyright 1997, 1998 by Sun Microsystems, Inc.,
* 901 San Antonio Road, Palo Alto, California, 94303, U.S.A.
* All rights reserved.
*
* This software is the confidential and proprietary information
* of Sun Microsystems, Inc. ("Confidential Information"). You
* shall not disclose such Confidential Information and shall use
* it only in accordance with the terms of the license agreement
* you entered into with Sun.
*
* The Swing Connection has given permission for the following code
* to be freely used, modified and redistributed. However, since it
* is example code, Sun does not support it and warns that it has not
* been as rigorously tested as Sun's product code.
*
* For further details, see The Swing Connection's Copyright Notice and
* Disclaimer at http://java.sun.com/products/jfc/tsc/page_2/page_2.html
*
* The articles upon which the TreeTable idea is based are located at
* http://java.sun.com/products/jfc/tsc/tech_topics/tech_topics.html
*/
package arlut.csd.ganymede.client;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.event.InputEvent;
import java.awt.event.MouseEvent;
import java.util.EventObject;
import javax.swing.JTable;
import javax.swing.JTree;
import javax.swing.ListSelectionModel;
import javax.swing.LookAndFeel;
import javax.swing.UIManager;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;
import javax.swing.table.TableCellEditor;
import javax.swing.table.TableCellRenderer;
import javax.swing.tree.DefaultTreeCellRenderer;
import javax.swing.tree.DefaultTreeSelectionModel;
import javax.swing.tree.TreeCellRenderer;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
/**
* This example shows how to create a simple JTreeTable component,
* by using a JTree as a renderer (and editor) for the cells in a
* particular column in the JTable.
*
* @version $Id$
*
* @author Philip Milne
* @author Scott Violet
*/
public class JTreeTable extends JTable {
/** A subclass of JTree. */
protected TreeTableCellRenderer tree;
public JTreeTable(TreeTableModel treeTableModel)
{
super();
/* Keep the JTable from scrolling inappropriately */
setAutoscrolls(false);
// Create the tree. It will be used as a renderer and editor.
tree = new TreeTableCellRenderer(treeTableModel);
// Install a adapter wrapper around our treeTableModel, to
// represent the visible rows in the tree.
super.setModel(new TreeTableModelAdapter(treeTableModel, tree));
// Install the tree editor renderer and editor.
setDefaultRenderer(TreeTableModel.class, tree);
setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor());
// No grid.
setShowGrid(false);
// No intercell spacing
setIntercellSpacing(new Dimension(0, 0));
// Increase the height of the rows to match the gclient tree.
setRowHeight(getRowHeight()+5);
// Make the tree and table row heights the same.
tree.setRowHeight(getRowHeight());
tree.setRootVisible(false);
tree.setShowsRootHandles(true);
tree.putClientProperty("JTree.lineStyle", "Angled");
}
/**
* Overridden to message super and forward the method to the tree.
* Since the tree is not actually in the component hieachy it will
* never receive this unless we forward it in this manner.
*/
public void updateUI()
{
super.updateUI();
if (tree != null)
{
tree.updateUI();
}
// Use the tree's default foreground and background colors in the
// table.
LookAndFeel.installColorsAndFont(this, "Tree.background",
"Tree.foreground", "Tree.font");
}
/**
* Determines if the specified column contains hierarchical nodes.
*
* @param column zero-based index of the column
* @return true if the class of objects in the specified column implement
* the {@link javax.swing.tree.TreeNode} interface; false otherwise.
*/
public boolean isHierarchical(int column)
{
return TreeTableModel.class.isAssignableFrom(getColumnClass(column));
}
/**
* Overriden to invoke repaint for the particular location if
* the column contains the tree. This is done as the tree editor does
* not fill the bounds of the cell, we need the renderer to paint
* the tree in the background, and then draw the editor over it.
* You should not need to call this method directly.
*
* {@inheritDoc}
*/
public boolean editCellAt(int row, int column, EventObject e)
{
expandOrCollapseNode(e); // RG: Fix Issue 49!
boolean canEdit = super.editCellAt(row, column, e);
// we assume that column zero is the tree column
if (canEdit && column == 0)
{
repaint(getCellRect(row, column, false));
}
return canEdit;
}
private void expandOrCollapseNode(EventObject e)
{
if (e instanceof MouseEvent)
{
MouseEvent me = (MouseEvent) e;
// If the modifiers are not 0 (or the left mouse button),
// tree may try and toggle the selection, and table
// will then try and toggle, resulting in the
// selection remaining the same. To avoid this, we
// only dispatch when the modifiers are 0 (or the left mouse
// button).
if (me.getModifiers() == 0 || me.getModifiers() == InputEvent.BUTTON1_MASK)
{
final int count = getColumnCount();
for (int i = count - 1; i >= 0; i--)
{
if (isHierarchical(i))
{
int savedHeight = tree.getRowHeight();
tree.setRowHeight(getRowHeight());
MouseEvent pressed = new MouseEvent
(tree,
me.getID(),
me.getWhen(),
me.getModifiers(),
me.getX() - getCellRect(0, i, false).x,
me.getY(),
me.getClickCount(),
me.isPopupTrigger());
tree.dispatchEvent(pressed);
// For Mac OS X, we need to dispatch a MOUSE_RELEASED as well
MouseEvent released = new MouseEvent
(tree,
java.awt.event.MouseEvent.MOUSE_RELEASED,
pressed.getWhen(),
pressed.getModifiers(),
pressed.getX(),
pressed.getY(),
pressed.getClickCount(),
pressed.isPopupTrigger());
tree.dispatchEvent(released);
tree.setRowHeight(savedHeight);
break;
}
}
}
}
}
/* Workaround for BasicTableUI anomaly. Make sure the UI never tries to
* paint the editor. The UI currently uses different techniques to
* paint the renderers and editors and overriding setBounds() below
* is not the right thing to do for an editor. Returning -1 for the
* editing row in this case, ensures the editor is never painted.
*/
public int getEditingRow()
{
return (getColumnClass(editingColumn) == TreeTableModel.class) ? -1 :
editingRow;
}
/**
* Overridden to pass the new rowHeight to the tree.
*/
public void setRowHeight(int rowHeight)
{
super.setRowHeight(rowHeight);
if (tree != null && tree.getRowHeight() != rowHeight)
{
tree.setRowHeight(getRowHeight());
}
}
/**
* Returns the tree that is being shared between the model.
*/
public JTree getTree()
{
return tree;
}
/**
* A TreeCellRenderer that displays a JTree.
*/
public class TreeTableCellRenderer extends JTree implements TableCellRenderer {
/** Last table/tree row asked to renderer. */
protected int visibleRow;
public TreeTableCellRenderer(TreeModel model)
{
super(model);
}
/**
* updateUI is overridden to set the colors of the Tree's renderer
* to match that of the table.
*/
public void updateUI()
{
super.updateUI();
// Make the tree's cell renderer use the table's cell selection
// colors.
TreeCellRenderer tcr = getCellRenderer();
if (tcr instanceof DefaultTreeCellRenderer)
{
DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer)tcr);
// For 1.1 uncomment this, 1.2 has a bug that will cause an
// exception to be thrown if the border selection color is
// null.
// dtcr.setBorderSelectionColor(null);
dtcr.setTextSelectionColor(UIManager.getColor("Table.selectionForeground"));
dtcr.setBackgroundSelectionColor(UIManager.getColor("Table.selectionBackground"));
dtcr.setBackgroundNonSelectionColor(Color.white);
}
}
/**
* Sets the row height of the tree, and forwards the row height to
* the table.
*/
public void setRowHeight(int rowHeight)
{
if (rowHeight > 0)
{
super.setRowHeight(rowHeight);
if (JTreeTable.this != null &&
JTreeTable.this.getRowHeight() != rowHeight)
{
JTreeTable.this.setRowHeight(getRowHeight());
}
}
}
/**
* This is overridden to set the height to match that of the JTable.
*/
public void setBounds(int x, int y, int w, int h)
{
super.setBounds(x, 0, w, JTreeTable.this.getHeight());
}
/**
* Sublcassed to translate the graphics such that the last visible
* row will be drawn at 0,0.
*/
public void paint(Graphics g)
{
g.translate(0, -visibleRow * getRowHeight());
super.paint(g);
}
/**
* TreeCellRenderer method. Overridden to update the visible row.
*/
public Component getTableCellRendererComponent(JTable table,
Object value,
boolean isSelected,
boolean hasFocus,
int row, int column)
{
visibleRow = row;
return this;
}
}
/**
* TreeTableCellEditor implementation. Component returned is the
* JTree.
*/
public class TreeTableCellEditor extends AbstractCellEditor implements TableCellEditor {
public Component getTableCellEditorComponent(JTable table,
Object value,
boolean isSelected,
int r, int c)
{
return tree;
}
/**
* Overridden to return false, and if the event is a mouse event
* it is forwarded to the tree.
*
* The behavior for this is debatable, and should really be offered
* as a property. By returning false, all keyboard actions are
* implemented in terms of the table. By returning true, the
* tree would get a chance to do something with the keyboard
* events. For the most part this is ok. But for certain keys,
* such as left/right, the tree will expand/collapse where as
* the table focus should really move to a different column. Page
* up/down should also be implemented in terms of the table.
* By returning false this also has the added benefit that clicking
* outside of the bounds of the tree node, but still in the tree
* column will select the row, whereas if this returned true
* that wouldn't be the case.
*
* By returning false we are also enforcing the policy that
* the tree will never be editable (at least by a key sequence).
*/
public boolean isCellEditable(EventObject e)
{
return true;
}
}
}