package jadex.commons; import jadex.commons.concurrent.IResultListener; import java.util.HashSet; import java.util.Set; import javax.swing.JTree; import javax.swing.SwingUtilities; import javax.swing.event.TreeExpansionEvent; import javax.swing.event.TreeExpansionListener; import javax.swing.event.TreeModelEvent; import javax.swing.event.TreeModelListener; import javax.swing.tree.TreePath; /** * The tree expansion handler assures * that tree nodes stay expanded, even when * their last child is removed, and then new * child nodes are added. * Swing doesn't do this on its own, grrr. */ public class TreeExpansionHandler implements TreeExpansionListener, TreeModelListener { //-------- attributes -------- /** The tree. */ protected JTree tree; /** A set with the tree nodes, which are expanded. */ protected Set expanded; //-------- constructors -------- /** * Create a tree expansion handler for a given tree. */ public TreeExpansionHandler(JTree tree) { this.tree = tree; this.expanded = new HashSet(); tree.addTreeExpansionListener(this); tree.getModel().addTreeModelListener(this); } //-------- TreeExpansionListener interface -------- /** * Called whenever an item in the tree has been expanded. */ public void treeExpanded(TreeExpansionEvent event) { // Mark path as expanded. // System.out.println("expand: "+event.getPath().getLastPathComponent()); expanded.add(event.getPath().getLastPathComponent()); } /** * Called whenever an item in the tree has been collapsed. */ public void treeCollapsed(TreeExpansionEvent event) { // Remove expansion mark, if any. // System.out.println("collapse: "+event.getPath().getLastPathComponent()); expanded.remove(event.getPath().getLastPathComponent()); } //-------- TreeModelListener interface -------- /** * Invoked after a node (or a set of siblings) has changed in some way. */ public void treeNodesChanged(TreeModelEvent event) { // I don't care. } /** * Invoked after nodes have been inserted into the tree. */ public void treeNodesInserted(TreeModelEvent event) { // System.out.println("nodes inserted: "+event); // When a new node has been inserted, // we may have to re-expand its parent. handlePath(event.getTreePath()); } /** * Invoked after nodes have been removed from the tree. */ public void treeNodesRemoved(TreeModelEvent event) { // Remove nodes from set to facilitate garbage collection. Object[] children = event.getChildren(); for(int i=0; i<children.length; i++) expanded.remove(children[i]); } /** * Invoked after the tree has drastically changed structure from a * given node down. */ public void treeStructureChanged(final TreeModelEvent event) { handleTreeStructureChanged(event, event.getTreePath(), Math.max(tree.getRowForPath(event.getTreePath()), 0)); } /** * Handle each node in the subtree. * Wait for node to be expanded before continuing to inlcude subnodes. */ public void handleTreeStructureChanged(final TreeModelEvent event, final TreePath root, final int i) { TreePath path = tree.getPathForRow(i); if(path!=null) { if(root.isDescendant(path)) { // System.out.println("path "+i+": "+path); handlePath(path).addResultListener(new IResultListener() { public void resultAvailable(Object source, Object result) { handleTreeStructureChanged(event, root, i+1); } public void exceptionOccurred(Object source, Exception exception) { // Shouldn't happen. exception.printStackTrace(); } }); } // else // { // System.out.println("break at: "+path); // } } } /** * Check if an action (e.g. expand) has to be performed on the path. */ protected IFuture handlePath(final TreePath path) { final Future ret = new Future(); // System.out.println("handle expand ("+expanded.contains(path.getLastPathComponent())+"): "+path.getLastPathComponent()+", "+expanded); if(expanded.contains(path.getLastPathComponent())) { // Can't expand during change event (Java bug?) SwingUtilities.invokeLater(new Runnable() { public void run() { tree.expandPath(path); // System.out.println("expanded: "+path.getLastPathComponent()); ret.setResult(null); } }); } else { ret.setResult(null); } return ret; } }