package net.ms.designer.editors.componentdetail.figures;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.draw2d.AbstractRouter;
import org.eclipse.draw2d.Bendpoint;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.ConnectionRouter;
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.LayoutManager;
import org.eclipse.draw2d.geometry.Dimension;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.PointList;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.graph.Path;
import org.eclipse.draw2d.graph.ShortestPathRouter;
public final class ShortestPathConnectionRouter extends AbstractRouter
{
public static class LayoutWrapper implements LayoutManager
{
private final LayoutManager delegate;
private final ShortestPathConnectionRouter router;
public LayoutWrapper(LayoutManager delegate,
ShortestPathConnectionRouter router)
{
this.delegate = delegate;
this.router = router;
}
public Object getConstraint(IFigure child)
{
return delegate.getConstraint(child);
}
public Dimension getMinimumSize(IFigure container, int wHint, int hHint)
{
return delegate.getMinimumSize(container, wHint, hHint);
}
public Dimension getPreferredSize(IFigure container, int wHint,
int hHint)
{
return delegate.getPreferredSize(container, wHint, hHint);
}
public void invalidate()
{
delegate.invalidate();
}
public void layout(IFigure container)
{
delegate.layout(container);
router.processLayout();
}
public void remove(IFigure child)
{
router.removeChild(child);
delegate.remove(child);
}
public void setConstraint(IFigure child, Object constraint)
{
delegate.setConstraint(child, constraint);
router.addChild(child);
}
}
private Map constraintMap = new HashMap();
private Map figuresToBounds;
private Map connectionToPaths;
private boolean isDirty;
private ShortestPathRouter algorithm = new ShortestPathRouter();
private IFigure container;
private Set staleConnections = new HashSet();
private FigureListener figureListener = new FigureListener()
{
public void figureMoved(IFigure source)
{
Rectangle newBounds = source.getBounds().getCopy();
if (algorithm.updateObstacle((Rectangle) figuresToBounds
.get(source), newBounds)) {
queueSomeRouting();
isDirty = true;
}
figuresToBounds.put(source, newBounds);
}
};
private boolean ignoreInvalidate;
/**
* Creates a new shortest path router with the given container. The
* container should contain all the obstacles the connetions will wrap
* around.
*
* @param container
* the container
*/
public ShortestPathConnectionRouter(IFigure container)
{
isDirty = false;
algorithm = new ShortestPathRouter();
this.container = container;
}
void queueSomeRouting()
{
if (connectionToPaths == null || connectionToPaths.isEmpty())
return;
try {
ignoreInvalidate = true;
((Connection) connectionToPaths.keySet().iterator().next())
.revalidate();
} finally {
ignoreInvalidate = false;
}
}
void addChild(IFigure child)
{
if (connectionToPaths == null)
return;
if (figuresToBounds.containsKey(child))
return;
Rectangle bounds = child.getBounds().getCopy();
algorithm.addObstacle(bounds);
figuresToBounds.put(child, bounds);
child.addFigureListener(figureListener);
isDirty = true;
}
void removeChild(IFigure child)
{
if (connectionToPaths == null)
return;
Rectangle bounds = child.getBounds().getCopy();
boolean change = algorithm.removeObstacle(bounds);
figuresToBounds.remove(child);
child.removeFigureListener(figureListener);
if (change) {
isDirty = true;
queueSomeRouting();
}
}
private void hookAll()
{
figuresToBounds = new HashMap();
for (int i = 0; i < container.getChildren().size(); i++)
addChild((IFigure) container.getChildren().get(i));
}
private void unhookAll()
{
if (figuresToBounds != null)
{
Iterator figureItr = figuresToBounds.keySet().iterator();
while (figureItr.hasNext())
{
//Must use iterator's remove to avoid concurrent modification
IFigure child = (IFigure) figureItr.next();
figureItr.remove();
removeChild(child);
}
figuresToBounds = null;
}
}
/**
* Gets the constraint for the given {@link Connection}. The constraint is
* the paths list of bend points for this connection.
*
* @param connection
* The connection whose constraint we are retrieving
* @return The constraint
*/
public Object getConstraint(Connection connection)
{
return constraintMap.get(connection);
}
/**
* @see org.eclipse.draw2d.ConnectionRouter#invalidate(org.eclipse.draw2d.Connection)
*/
public void invalidate(Connection connection)
{
if (ignoreInvalidate)
return;
staleConnections.add(connection);
isDirty = true;
}
/**
* @see org.eclipse.draw2d.ConnectionRouter#remove(org.eclipse.draw2d.Connection)
*/
public void remove(Connection connection)
{
Path path = (Path) connectionToPaths.remove(connection);
staleConnections.remove(connection);
constraintMap.remove(connection);
algorithm.removePath(path);
isDirty = true;
if (connectionToPaths.isEmpty())
{
unhookAll();
connectionToPaths = null;
} else {
//Make sure one of the remaining is revalidated so that we can
// re-route again.
queueSomeRouting();
}
}
private void processLayout()
{
if (staleConnections.isEmpty())
return;
((Connection) staleConnections.iterator().next()).revalidate();
}
/**
* @see ConnectionRouter#route(Connection)
*/
public void route(Connection conn)
{
if (isDirty)
{
ignoreInvalidate = true;
processStaleConnections();
isDirty = false;
List updated = algorithm.solve();
Connection current;
for (int i = 0; i < updated.size(); i++)
{
Path path = (Path) updated.get(i);
current = (Connection) path.data;
current.revalidate();
PointList points = path.getPoints().getCopy();
Point ref1, ref2, start, end;
ref1 = new PrecisionPoint(points.getPoint(1));
ref2 = new PrecisionPoint(points.getPoint(points.size() - 2));
current.translateToAbsolute(ref1);
current.translateToAbsolute(ref2);
start = current.getSourceAnchor().getLocation(ref1).getCopy();
end = current.getTargetAnchor().getLocation(ref2).getCopy();
current.translateToRelative(start);
current.translateToRelative(end);
points.setPoint(start, 0);
points.setPoint(end, points.size() - 1);
current.setPoints(points);
}
ignoreInvalidate = false;
}
}
/**
* @since 3.0
*/
private void processStaleConnections()
{
Iterator iter = staleConnections.iterator();
if (iter.hasNext() && connectionToPaths == null)
{
connectionToPaths = new HashMap();
hookAll();
}
while (iter.hasNext())
{
Connection conn = (Connection) iter.next();
Path path = (Path) connectionToPaths.get(conn);
if (path == null)
{
path = new Path(conn);
connectionToPaths.put(conn, path);
algorithm.addPath(path);
}
List constraint = (List) getConstraint(conn);
if (constraint == null)
constraint = Collections.EMPTY_LIST;
Point start = conn.getSourceAnchor().getReferencePoint().getCopy();
Point end = conn.getTargetAnchor().getReferencePoint().getCopy();
container.translateToRelative(start);
container.translateToRelative(end);
path.setStartPoint(start);
path.setEndPoint(end);
if (!constraint.isEmpty())
{
PointList bends = new PointList(constraint.size());
for (int i = 0; i < constraint.size(); i++)
{
Bendpoint bp = (Bendpoint) constraint.get(i);
bends.addPoint(bp.getLocation());
}
path.setBendPoints(bends);
} else
path.setBendPoints(null);
isDirty |= path.isDirty;
}
staleConnections.clear();
}
public void setConstraint(Connection connection, Object constraint)
{
//Connection.setConstraint() already calls revalidate, so we know that
// a
// route() call will follow.
staleConnections.add(connection);
constraintMap.put(connection, constraint);
isDirty = true;
}
}