/* ========================
* JSynoptic : a free Synoptic editor
* ========================
*
* Project Info: http://jsynoptic.sourceforge.net/index.html
*
* This program is free software; you can redistribute it and/or modify it under the terms
* of the GNU Lesser General Public License as published by the Free Software Foundation;
* either version 2.1 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
* See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along with this
* program; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
* Boston, MA 02111-1307, USA.
*
* (C) Copyright 2001-2004, by :
* Corporate:
* Astrium SAS
* Individual:
* Claude Cazenave
*
* $Id: SceneGraphTree.java,v 1.12 2005/09/02 11:57:30 ogor Exp $
*
* Changes
* -------
* 12-Dec-2003 : Creation date (CC);
*
*/
package syn3d.ui;
import java.util.ArrayList;
import java.util.Iterator;
import javax.swing.BorderFactory;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTree;
import javax.swing.SwingUtilities;
import javax.swing.event.TreeSelectionEvent;
import javax.swing.event.TreeSelectionListener;
import javax.swing.tree.TreePath;
import syn3d.base.ActiveNode;
import syn3d.nodes.ActiveNodeTreeChangeListener;
import syn3d.nodes.SceneNode;
/**
* Class description ...
*
* @author Claude CAZENAVE
*
*/
public class SceneGraphTree extends JTree implements ActiveNodeTreeChangeListener, TreeSelectionListener {
protected boolean autoHighlight = false;
/**
* @param newModel
*/
public SceneGraphTree(SceneGraphModel model) {
super(model);
setRootVisible(false); // set to true for debug only
setShowsRootHandles(true);
setCellRenderer(new SceneGraphTreeRenderer());
setExpandsSelectedPaths(true);
setAutoscrolls(true);
// Monitor structural changes to maintain selections and expanded states
model.getRootNode().addListener(this);
addTreeSelectionListener(this);
}
/**
* Auto-highlighting means that when a node is selected in this tree, it is
* automatically highlighted. This is false by default.<br>
* Note: The change will occur in all trees sharing the same Node model.
* @see ActiveNode.highlight(boolean)
* @return Returns the autoHighlight.
*/
public boolean isAutoHighlight() {
return autoHighlight;
}
/**
* @see isAutoHighlight()
* @param autoHighlight The autoHighlight to set.
*/
public void setAutoHighlight(boolean autoHighlight) {
this.autoHighlight = autoHighlight;
}
/**
* A helper to create a panel to contain this tree
* @param title
* @return
*/
public JPanel createPanel(String title) {
JPanel p = new JPanel();
p.setBorder(BorderFactory.createTitledBorder(null, title));
JScrollPane pp =
new JScrollPane(
this,
JScrollPane.VERTICAL_SCROLLBAR_ALWAYS,
JScrollPane.HORIZONTAL_SCROLLBAR_ALWAYS);
p.add(pp);
return p;
}
/* (non-Javadoc)
* @see java.lang.Object#toString()
*/
public String toString(){
return getName();
}
/** Utility helper to get the selected ActiveNode */
public ActiveNode getSelectedNode() {
return (ActiveNode)getSelectionPath().getLastPathComponent();
}
/** Adds all expanded path under the given tree path to the arraylist, recusrively */
protected void recursiveExpandedPathFinder(TreePath current, ArrayList expPaths) {
if (!isExpanded(current)) return;
expPaths.add(current);
ActiveNode node = (ActiveNode)current.getLastPathComponent();
for (Iterator it = node.getChildren().iterator(); it.hasNext();) {
recursiveExpandedPathFinder(current.pathByAddingChild((ActiveNode)it.next()),expPaths);
}
}
/** Possible actions for TreeWorker */
public static final int NODE_ADDED = 1;
public static final int NODE_REMOVED = 2;
public static final int NODE_CHANGED = 3;
public class TreeWorker implements Runnable {
protected ActiveNode node;
protected int action;
public TreeWorker(ActiveNode node, int action) {
this.node = node;
this.action = action;
}
public void run() {
SceneGraphModel model = (SceneGraphModel)getModel();
ActiveNode target;
if (action==NODE_CHANGED) target = node;
else target = node.getParent();
ActiveNode[] nodePath = model.getPathToRoot(target);
// Keep the expanded state of all children that are expanded
ArrayList expPaths = null;
if (action!=NODE_CHANGED) {
expPaths = new ArrayList();
recursiveExpandedPathFinder(new TreePath(nodePath), expPaths);
}
// change model to add or remove the new child to the parent node
model.fireTreeChanged(target, nodePath, null, null);
// Universes have their own listeners. nodePath[0] is root, 1 is universe
// Notify only the universe concerned by the changes, not the other universes
if ((nodePath.length>=2) && (nodePath[1] instanceof SceneNode)) {
((SceneNode)nodePath[1]).notifyListeners();
}
// restore expanded state
if (action!=NODE_CHANGED) for (Iterator it = expPaths.iterator(); it.hasNext();) {
TreePath path = (TreePath)it.next();
expandPath(path);
}
// Set selection on the new element
if (action==NODE_ADDED) setSelectionPath(new TreePath(model.getPathToRoot(node)));
else if (action==NODE_REMOVED) setSelectionPath(new TreePath(model.getPathToRoot(target)));
}
}
/* (non-Javadoc)
* @see syn3d.nodes.ActiveNodeTreeChangeListener#nodeAdded(syn3d.base.ActiveNode)
*/
public void nodeAdded(ActiveNode node) {
// This function may be called from any thread
// Do things properly
SwingUtilities.invokeLater(new TreeWorker(node, NODE_ADDED));
}
/* (non-Javadoc)
* @see syn3d.nodes.ActiveNodeTreeChangeListener#nodeRemoved(syn3d.base.ActiveNode)
*/
public void nodeRemoved(ActiveNode node) {
// This function may be called from any thread
// Do things properly
SwingUtilities.invokeLater(new TreeWorker(node, NODE_REMOVED));
}
/* (non-Javadoc)
* @see syn3d.nodes.ActiveNodeTreeChangeListener#nameChanged(syn3d.base.ActiveNode)
*/
public void nameChanged(ActiveNode node) {
repaint(); // repaints posts to the AWT thread, so OK
}
/* (non-Javadoc)
* @see syn3d.nodes.ActiveNodeTreeChangeListener#highlightChanged(syn3d.base.ActiveNode, boolean)
*/
public void highlightChanged(ActiveNode node, boolean on) {
if (!autoHighlight) return;
// Avoid infinite recursion loop with highlightChanged notification
boolean highlightSave = autoHighlight;
autoHighlight = false;
SceneGraphModel model = (SceneGraphModel)getModel();
TreePath path = new TreePath(model.getPathToRoot(node));
if (on) addSelectionPath(path);
else this.removeSelectionPath(path);
autoHighlight = highlightSave;
}
/* (non-Javadoc)
* @see syn3d.nodes.ActiveNodeTreeChangeListener#structureChanged(syn3d.base.ActiveNode)
*/
public void structureChanged(ActiveNode node) {
// This function may be called from any thread
// Do things properly
SwingUtilities.invokeLater(new TreeWorker(node, NODE_CHANGED));
}
/* (non-Javadoc)
* @see javax.swing.event.TreeSelectionListener#valueChanged(javax.swing.event.TreeSelectionEvent)
*/
public void valueChanged(TreeSelectionEvent e) {
if (!autoHighlight) return;
// Avoid infinite recursion loop with highlightChanged notification
boolean highlightSave = autoHighlight;
autoHighlight = false;
TreePath[] paths = e.getPaths();
for (int i=0; i<paths.length; ++i) {
ActiveNode node = (ActiveNode)paths[i].getLastPathComponent();
node.highlight(e.isAddedPath(i), node.get3DObject());
}
autoHighlight = highlightSave;
}
}