package org.j3de.ui.impl;
import java.util.ConcurrentModificationException;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Vector;
import javax.swing.event.ChangeListener;
import javax.media.j3d.Transform3D;
import javax.media.j3d.Bounds;
import javax.media.j3d.BoundingBox;
import javax.media.j3d.BranchGroup;
import javax.media.j3d.Group;
import javax.media.j3d.Node;
import javax.vecmath.Point3d;
import javax.vecmath.Vector3d;
import org.j3de.ui.LayoutConstraint;
import org.j3de.ui.LayoutEntry;
import org.j3de.ui.LayoutInfo;
import org.j3de.ui.LayoutManager3D;
import org.j3de.ui.UIElement;
import org.j3de.ui.UIElementMapper;
import org.j3de.ui.UILocalElement;
public class GraphLayout extends LayoutManager3D {
private static double MINCHANGE = 0.1;
private static double ANNEALING = 0.9;
protected UIElementMapper elementMapper;
/** Contains mapping of local UIElement (NOT Stubs) to their GraphLayoutElements) */
private Map gleMap;
private List elements;
private List constraints;
private LayoutInfo layoutInfo;
private double annealing;
private boolean layoutThreadRunning;
private boolean layoutModified;
private BoundsChangeHelper boundsHelper;
public GraphLayout() {
this.setCapability(Group.ALLOW_CHILDREN_READ);
this.setCapability(Group.ALLOW_CHILDREN_WRITE);
this.setCapability(Group.ALLOW_CHILDREN_EXTEND);
this.setBoundsAutoCompute(false);
this.setCapability(BranchGroup.ALLOW_BOUNDS_WRITE);
this.setCapability(BranchGroup.ALLOW_BOUNDS_READ);
this.boundsHelper = new BoundsChangeHelper(this);
this.gleMap = new HashMap();
this.elements = new Vector();
this.constraints = new Vector();
this.layoutInfo = new GraphLayoutInfo();
this.layoutThreadRunning = false;
this.layoutModified = true;
this.annealing = 1.0;
}
private void updateBounds() {
if (elements.size() == 0) {
setBounds(new BoundingBox());
return;
}
BoundingBox bb = new BoundingBox(((GraphLayoutElement)elements.get(0)).getBounds());
for (int i=0; i bb.combine(((GraphLayoutElement)elements.get(i)).getBounds());
setBounds(bb);
boundsHelper.updateBounds();
}
/** Add a ChangeListener, that is notified, when the Bounds of the UIElement change. */
public void addBoundsChangeListener(ChangeListener listener) {
boundsHelper.addBoundsChangeListener(listener);
}
/** Remove a ChangeListener, that is notified, when the Bounds of the UIElement change. */
public void removeBoundsChangeListener(ChangeListener listener) {
boundsHelper.removeBoundsChangeListener(listener);
}
public void setElementMapper(UIElementMapper elementMapper) {
this.elementMapper = elementMapper;
}
protected void doLayout() {
// Create a thread every time, because a constantly running thread
// would prevent this object from being gc'ed
layoutModified = true;
if (!layoutThreadRunning)
new Thread(new LayoutThread()).start();
}
public void normalize(Transform3D transform) {
}
public synchronized void add(UIElement element) {
// Get a local reference to element
element = elementMapper.getLocal(element).getRemoteElement();
GraphLayoutElement gle = new GraphLayoutElement(element, elementMapper, GraphLayout.this);
Iterator iter = constraints.iterator();
while(iter.hasNext()) {
LayoutConstraint constraint = (LayoutConstraint)iter.next();
if (constraint.appliesTo(element))
gle.addConstraint(constraint);
}
this.addChild(gle);
// We need a pointer to the local element, not a reference to the stub in gleMap
gleMap.put(element, gle);
elements.add(gle);
doLayout();
}
public synchronized void remove(UIElement element) {
// Get a local reference to element
element = elementMapper.getLocal(element).getRemoteElement();
GraphLayoutElement gle = (GraphLayoutElement)gleMap.get(element);
gle.detach();
gleMap.remove(element);
elements.remove(element);
// TODO : Remove Constraints that are not needed anymore !
doLayout();
}
protected synchronized void addLayoutConstraint(LayoutConstraint constraint) {
constraints.add(constraint);
constraint.setLayoutInfo(layoutInfo);
Iterator iter = elements.iterator();
while(iter.hasNext()) {
GraphLayoutElement gle = (GraphLayoutElement)iter.next();
if (constraint.appliesTo(gle.getUIElement()))
gle.addConstraint(constraint);
}
doLayout();
}
protected synchronized void removeLayoutConstraint(LayoutConstraint constraint) {
}
protected class GraphLayoutInfo implements LayoutInfo {
public void getPosition(UIElement element, Vector3d position) {
GraphLayoutElement gle = (GraphLayoutElement)gleMap.get(element);
gle.getPosition(position);
}
public javax.media.j3d.Bounds getBounds(UIElement element) {
GraphLayoutElement gle = (GraphLayoutElement)gleMap.get(element);
return gle.getElementBounds();
}
public Enumeration getLayoutEntries(UIElement element) {
return new GraphEntryEnumeration();
}
}
protected class GraphEntryEnumeration implements Enumeration {
private Iterator iter;
public GraphEntryEnumeration() {
iter = elements.iterator();
}
public boolean hasMoreElements() {
return iter.hasNext();
}
public Object nextElement() {
GraphLayoutElement gle = (GraphLayoutElement)iter.next();
Vector3d pos = new Vector3d();
gle.getPosition(pos);
return new LayoutEntry(pos, gle.getBounds());
}
}
protected class LayoutThread implements Runnable {
public void run() {
Vector3d oldPosition = new Vector3d();
Vector3d position = new Vector3d();
Vector3d vector = new Vector3d();
GraphLayoutElement gle;
Iterator iter;
while (layoutModified) {
layoutModified = false;
double maxChange = 0.0;
double change;
// Calculate next Position of each GraphLayoutElement
try {
iter = elements.iterator();
while (iter.hasNext()) {
gle = (GraphLayoutElement)iter.next();
gle.getPosition(oldPosition);
gle.move(annealing);
gle.getPosition(position);
position.sub(oldPosition);
vector.set(position);
change = vector.length();
if (change > maxChange)
maxChange = change;
}
} catch (ConcurrentModificationException e) {
// elements has been modified ...
e.printStackTrace();
}
// next time, we do everything a little bit slower :
annealing *= ANNEALING;
// if no change has been made and maxChange is smaller than minChange, the thread terminates
synchronized (GraphLayout.this) {
if (!layoutModified && (maxChange < MINCHANGE))
layoutThreadRunning = false;
}
}
updateBounds();
}
}
}