package com.onaro.util.jfc.treeTable; /* * @(#)treeTable.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. */ import javax.swing.*; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.plaf.basic.BasicTreeUI; import javax.swing.plaf.metal.MetalTreeUI; import javax.swing.table.TableCellEditor; import javax.swing.table.TableCellRenderer; import javax.swing.tree.*; import java.awt.*; import java.awt.event.MouseEvent; import java.util.EventObject; import java.util.ArrayList; /** * This example shows how to create a simple treeTable.JTreeTable component, * by using a JTree as a renderer (and editor) for the cells in a * particular column in the JTable. * * @author Philip Milne * @author Scott Violet * @version 1.2 10/27/98 */ public class JTreeTable extends JTable { private static final long serialVersionUID = 1L; /** * A subclass of JTree. */ protected TreeTableCellRenderer tree; /** * The adapter from tree to table model. */ private TreeTableModelAdapter modelAdapter; public JTreeTable() { } public JTreeTable(TreeTableModel treeTableModel) { setTreeTableModel(treeTableModel); } public void setTreeTableModel(TreeTableModel treeTableModel) { // Create the tree. It will be used as a renderer and editor. tree = new TreeTableCellRenderer(treeTableModel); // Install a tableModel representing the visible rows in the tree. modelAdapter = new TreeTableModelAdapter(treeTableModel, tree); super.setModel(modelAdapter); // Force the JTable and JTree to share their row selection models. ListToTreeSelectionModelWrapper selectionWrapper = new ListToTreeSelectionModelWrapper(); tree.setSelectionModel(selectionWrapper); setSelectionModel(selectionWrapper.getListSelectionModel()); // Install the tree editor renderer and editor. setDefaultRenderer(TreeTableModel.class, tree); setDefaultEditor(TreeTableModel.class, new TreeTableCellEditor()); // And update the height of the trees row to match that of // the table. setRowHeight(getRowHeight()); } /** * 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", //$NON-NLS-1$ "Tree.foreground", "Tree.font"); //$NON-NLS-1$ //$NON-NLS-2$ } /* 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()); } } // todo - In order to support variable row heigt, the tree's row height need to // be set to zero. // public void setRowHeight(int row, int rowHeight) { // super.setRowHeight(row, rowHeight); // if (tree != null && tree.getRowHeight() != rowHeight) { // tree.setVariableRowHeight(); // } // } /** * Returns the tree that is being shared between the model. */ public JTree getTree() { return tree; } /** * Tells if this row represents a leaf. * * @param row the row to check if its a leaf * @return true if the row has no children */ public boolean isLeaf(int row) { return modelAdapter.isLeaf(row); } /** * A TreeCellRenderer that displays a JTree. */ public class TreeTableCellRenderer extends JTree implements TableCellRenderer { private static final long serialVersionUID = 1L; /** * Last table/tree row asked to renderer. */ protected int visibleRow; /** * Force the UI to use {@link FixedHeightLayoutCache} or otherwise on any change to the table (resize, drag, scroll) * the tree renderer will be invoked for every single cell! * * @param model */ public TreeTableCellRenderer(TreeModel model) { super(model); setUI(new MetalTreeUI() { protected AbstractLayoutCache createLayoutCache() { return new FixedHeightLayoutCache(); } }); } /** * 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")); //$NON-NLS-1$ dtcr.setBackgroundSelectionColor(UIManager.getColor ("Table.selectionBackground")); //$NON-NLS-1$ } } /** * 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.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) { // todo - In order to support variable row height, calculating the offset must be based on summing // the height of all the rows above the one that is painted. // int offset = 0; // for (int row = 0; row < visibleRow; ++row) { // offset += JTreeTable.this.getRowHeight(row); // } // g.translate(0, -offset); 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) { if (isSelected) setBackground(table.getSelectionBackground()); else setBackground(table.getBackground()); visibleRow = row; return this; } public void setForeground(Color fg) { super.setForeground(fg); TreeCellRenderer tcr = getCellRenderer(); if (tcr instanceof DefaultTreeCellRenderer) { DefaultTreeCellRenderer dtcr = ((DefaultTreeCellRenderer) tcr); dtcr.setTextNonSelectionColor(fg); dtcr.setTextSelectionColor(fg); } } } /** * 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) {
if (e instanceof MouseEvent) {
for (int counter = getColumnCount() - 1; counter >= 0;
counter--) {
if (getColumnClass(counter) == TreeTableModel.class) {
MouseEvent me = (MouseEvent) e;
if (System.getProperty("mrj.version") != null) { //$NON-NLS-1$
int x = ((BasicTreeUI) getTree().getUI()).getRightChildIndent();
MouseEvent newME = new MouseEvent(tree, me.getID(),
me.getWhen(), me.getModifiers(),
me.getX() - getCellRect(0, counter, true).x + x,
me.getY(), me.getClickCount() + 1,
me.isPopupTrigger());
tree.dispatchEvent(newME);
break;
} else {
MouseEvent newME = new MouseEvent(tree, me.getID(),
me.getWhen(), me.getModifiers(),
me.getX() - getCellRect(0, counter, true).x,
me.getY(), me.getClickCount(),
me.isPopupTrigger());
tree.dispatchEvent(newME);
break;
}
}
}
}
return false;
}
}
/**
* ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel
* to listen for changes in the ListSelectionModel it maintains. Once
* a change in the ListSelectionModel happens, the paths are updated
* in the DefaultTreeSelectionModel.
*/
class ListToTreeSelectionModelWrapper extends DefaultTreeSelectionModel {
private static final long serialVersionUID = 1L;
/**
* Set to true when we are updating the ListSelectionModel.
*/
protected boolean updatingListSelectionModel;
public ListToTreeSelectionModelWrapper() {
super();
getListSelectionModel().addListSelectionListener
(createListSelectionListener());
}
/**
* Returns the list selection model. ListToTreeSelectionModelWrapper
* listens for changes to this model and updates the selected paths
* accordingly.
*/
ListSelectionModel getListSelectionModel() {
return listSelectionModel;
}
/**
* This is overridden to set updatingListSelectionModel
* and message super. This is the only place DefaultTreeSelectionModel
* alters the ListSelectionModel.
*/
public void resetRowSelection() {
if (!updatingListSelectionModel) {
updatingListSelectionModel = true;
try {
super.resetRowSelection();
} finally {
updatingListSelectionModel = false;
}
}
// Notice how we don't message super if
// updatingListSelectionModel is true. If
// updatingListSelectionModel is true, it implies the
// ListSelectionModel has already been updated and the
// paths are the only thing that needs to be updated.
}
/**
* Creates and returns an instance of ListSelectionHandler.
*/
protected ListSelectionListener createListSelectionListener() {
return new ListSelectionHandler();
}
/**
* If updatingListSelectionModel
is false, this will
* reset the selected paths from the selected rows in the list
* selection model.
*/
protected void updateSelectedPathsFromSelectedRows() {
if (!updatingListSelectionModel) {
updatingListSelectionModel = true;
try {
// This is way expensive, ListSelectionModel needs an
// enumerator for iterating.
int min = listSelectionModel.getMinSelectionIndex();
int max = listSelectionModel.getMaxSelectionIndex();
boolean hasNewSelection = min != -1 && max != -1;
if (hasNewSelection) {
ArrayList