package prefuse.util.ui;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.event.TreeModelEvent;
import javax.swing.event.TreeModelListener;
import javax.swing.tree.TreeModel;
import javax.swing.tree.TreePath;
import prefuse.data.Graph;
import prefuse.data.Node;
import prefuse.data.Tree;
import prefuse.data.event.EventConstants;
import prefuse.data.event.GraphListener;
import prefuse.util.StringLib;
import prefuse.util.collections.CopyOnWriteArrayList;
import prefuse.visual.VisualTree;
/**
* Swing component that displays a prefuse Tree instance in a Swing
* JTree component. Graph instances can also be displayed by first
* getting a Tree instance with the
* {@link prefuse.data.Graph#getSpanningTree()} method.
*
* @author <a href="http://jheer.org">jeffrey heer</a>
* @see javax.swing.JTree
*/
public class JPrefuseTree extends JTree {
private Tree m_tree;
private String m_field;
/**
* Create a new JPrefuseTree.
* @param t the Tree to display
* @param labelField the data field used to privde labels
*/
public JPrefuseTree(Tree t, String labelField) {
super();
m_tree = t;
m_field = labelField;
PrefuseTreeModel model = new PrefuseTreeModel();
super.setModel(model);
m_tree.addGraphModelListener(model);
}
/**
* Return the backing Tree instance.
* @return the backing Tree
*/
public Tree getTree() {
return m_tree;
}
/**
* Returns a String label for Node instances by looking up the
* label data field specified in the constructor of this class.
* @see javax.swing.JTree#convertValueToText(java.lang.Object, boolean, boolean, boolean, int, boolean)
*/
public String convertValueToText(Object value, boolean selected,
boolean expanded, boolean leaf, int row, boolean hasFocus)
{
if ( value == null )
return "";
if ( value instanceof Node ) {
Object o = ((Node)value).get(m_field);
if ( o.getClass().isArray() ) {
return StringLib.getArrayString(o);
} else {
return o.toString();
}
} else {
return value.toString();
}
}
// ------------------------------------------------------------------------
/**
* TreeModel implementation that provides an adapter to a backing prefuse
* Tree instance.
*/
public class PrefuseTreeModel implements TreeModel, GraphListener {
private CopyOnWriteArrayList m_listeners = new CopyOnWriteArrayList();
/**
* @see javax.swing.tree.TreeModel#getRoot()
*/
public Object getRoot() {
return m_tree.getRoot();
}
/**
* @see javax.swing.tree.TreeModel#getChild(java.lang.Object, int)
*/
public Object getChild(Object node, int idx) {
Node c = ((Node)node).getChild(idx);
if ( c == null ) {
throw new IllegalArgumentException("Index out of range: "+idx);
}
return c;
}
/**
* @see javax.swing.tree.TreeModel#getChildCount(java.lang.Object)
*/
public int getChildCount(Object node) {
return ((Node)node).getChildCount();
}
/**
* @see javax.swing.tree.TreeModel#isLeaf(java.lang.Object)
*/
public boolean isLeaf(Object node) {
return ((Node)node).getChildCount() == 0;
}
/**
* @see javax.swing.tree.TreeModel#valueForPathChanged(javax.swing.tree.TreePath, java.lang.Object)
*/
public void valueForPathChanged(TreePath path, Object newValue) {
// do nothing?
}
/**
* @see javax.swing.tree.TreeModel#getIndexOfChild(java.lang.Object, java.lang.Object)
*/
public int getIndexOfChild(Object parent, Object child) {
return ((Node)parent).getChildIndex(((Node)child));
}
/**
* @see javax.swing.tree.TreeModel#addTreeModelListener(javax.swing.event.TreeModelListener)
*/
public void addTreeModelListener(TreeModelListener tml) {
if ( !m_listeners.contains(tml) )
m_listeners.add(tml);
}
/**
* @see javax.swing.tree.TreeModel#removeTreeModelListener(javax.swing.event.TreeModelListener)
*/
public void removeTreeModelListener(TreeModelListener tml) {
m_listeners.remove(tml);
}
/**
* @see prefuse.data.event.GraphListener#graphChanged(prefuse.data.Graph, java.lang.String, int, int, int, int)
*/
public void graphChanged(Graph g, String table, int start, int end,
int col, int type)
{
if ( m_listeners == null || m_listeners.size() == 0 )
return; // nothing to do
boolean nodeTable = table.equals(Graph.NODES);
if ( type != EventConstants.UPDATE && nodeTable )
return;
else if ( type == EventConstants.UPDATE && !nodeTable )
return;
for ( int row = start; row <= end; ++row ) {
// create the event
Node n = null;
if ( nodeTable )
n = g.getNode(row);
else
n = g.getEdge(row).getTargetNode();
Object[] path = new Object[n.getDepth()+1];
for ( int i=path.length; --i>=0; n=n.getParent() ) {
path[i] = n;
}
TreeModelEvent e = new TreeModelEvent(this, path);
// fire it
Object[] lstnrs = m_listeners.getArray();
for ( int i=0; i<lstnrs.length; ++i ) {
TreeModelListener tml = (TreeModelListener)lstnrs[i];
switch ( type ) {
case EventConstants.INSERT:
tml.treeNodesInserted(e);
break;
case EventConstants.DELETE:
tml.treeNodesRemoved(e);
break;
case EventConstants.UPDATE:
tml.treeNodesChanged(e);
}
}
}
}
} // end of inner class PrefuseTreeModel
// ------------------------------------------------------------------------
/**
* Create a new window displaying the contents of the input Tree as
* a Swing JTree.
* @param t the Tree instance to display
* @param labelField the data field to use for labeling nodes
* @return a reference to the JFrame holding the tree view
*/
public static JFrame showTreeWindow(Tree t, String labelField) {
JPrefuseTree tree = new JPrefuseTree(t, labelField);
String title = t.toString();
if ( t instanceof VisualTree ) {
title = ((VisualTree)t).getGroup() + " " + title;
}
JFrame frame = new JFrame(title);
frame.getContentPane().add(new JScrollPane(tree));
frame.pack();
frame.setVisible(true);
return frame;
}
} // end of class JPrefuseTree