Mercurial > projects > dwt-addons
diff dwtx/draw2d/ShortestPathConnectionRouter.d @ 98:95307ad235d9
Added Draw2d code, still work in progress
author | Frank Benoit <benoit@tionex.de> |
---|---|
date | Sun, 03 Aug 2008 00:52:14 +0200 |
parents | |
children |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/dwtx/draw2d/ShortestPathConnectionRouter.d Sun Aug 03 00:52:14 2008 +0200 @@ -0,0 +1,317 @@ +/******************************************************************************* + * Copyright (c) 2000, 2005 IBM Corporation and others. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * IBM Corporation - initial API and implementation + * Port to the D programming language: + * Frank Benoit <benoit@tionex.de> + *******************************************************************************/ +module dwtx.draw2d.ShortestPathConnectionRouter; + +import dwt.dwthelper.utils; +import dwtx.dwtxhelper.Collection; + +import dwtx.draw2d.geometry.Point; +import dwtx.draw2d.geometry.PointList; +import dwtx.draw2d.geometry.PrecisionPoint; +import dwtx.draw2d.geometry.Rectangle; +import dwtx.draw2d.graph.Path; +import dwtx.draw2d.graph.ShortestPathRouter; +import dwtx.draw2d.AbstractRouter; +import dwtx.draw2d.IFigure; +import dwtx.draw2d.LayoutListener; +import dwtx.draw2d.FigureListener; +import dwtx.draw2d.Connection; +import dwtx.draw2d.Bendpoint; + +/** + * Routes multiple connections around the children of a given container figure. + * @author Whitney Sorenson + * @author Randy Hudson + * @since 3.1 + */ +public final class ShortestPathConnectionRouter + : AbstractRouter +{ + +private class LayoutTracker : LayoutListenerStub { + public void postLayout(IFigure container) { + processLayout(); + } + public void remove(IFigure child) { + removeChild(child); + } + public void setConstraint(IFigure child, Object constraint) { + addChild(child); + } +} + +private Map constraintMap; +private Map figuresToBounds; +private Map connectionToPaths; +private bool isDirty; +private ShortestPathRouter algorithm; +private IFigure container; +private Set staleConnections; +private LayoutListener listener; + +private FigureListener figureListener; +private void initFigureListener(){ + figureListener = new class() FigureListener { + public void figureMoved(IFigure source) { + Rectangle newBounds = source.getBounds().getCopy(); + if (algorithm.updateObstacle(cast(Rectangle)figuresToBounds.get(cast(Object)source), newBounds)) { + queueSomeRouting(); + isDirty = true; + } + + figuresToBounds.put(cast(Object)source, newBounds); + } + }; +} +private bool ignoreInvalidate; + +/** + * Creates a new shortest path router with the given container. The container + * contains all the figure's which will be treated as obstacles for the connections to + * avoid. Any time a child of the container moves, one or more connections will be + * revalidated to process the new obstacle locations. The connections being routed must + * not be contained within the container. + * + * @param container the container + */ +public this(IFigure container) { + initFigureListener(); + constraintMap = new HashMap(); + algorithm = new ShortestPathRouter(); + staleConnections = new HashSet(); + listener = new LayoutTracker(); + isDirty = false; + algorithm = new ShortestPathRouter(); + this.container = container; +} + +void addChild(IFigure child) { + if (connectionToPaths is null) + return; + if (figuresToBounds.containsKey(cast(Object)child)) + return; + Rectangle bounds = child.getBounds().getCopy(); + algorithm.addObstacle(bounds); + figuresToBounds.put(cast(Object)child, bounds); + child.addFigureListener(figureListener); + isDirty = true; +} + +private void hookAll() { + figuresToBounds = new HashMap(); + for (int i = 0; i < container.getChildren().size(); i++) + addChild(cast(IFigure)container.getChildren().get(i)); + container.addLayoutListener(listener); +} + +private void unhookAll() { + container.removeLayoutListener(listener); + if (figuresToBounds !is null) { + Iterator figureItr = figuresToBounds.keySet().iterator(); + while (figureItr.hasNext()) { + //Must use iterator's remove to avoid concurrent modification + IFigure child = cast(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(cast(Object)connection); +} + +/** + * Returns the default spacing maintained on either side of a connection. The default + * value is 4. + * @return the connection spacing + * @since 3.2 + */ +public int getSpacing() { + return algorithm.getSpacing(); +} + +/** + * @see ConnectionRouter#invalidate(Connection) + */ +public void invalidate(Connection connection) { + if (ignoreInvalidate) + return; + staleConnections.add(cast(Object)connection); + isDirty = true; +} + +private void processLayout() { + if (staleConnections.isEmpty()) + return; + (cast(Connection)staleConnections.iterator().next()).revalidate(); +} + +private void processStaleConnections() { + Iterator iter = staleConnections.iterator(); + if (iter.hasNext() && connectionToPaths is null) { + connectionToPaths = new HashMap(); + hookAll(); + } + + while (iter.hasNext()) { + Connection conn = cast(Connection)iter.next(); + + Path path = cast(Path)connectionToPaths.get(cast(Object)conn); + if (path is null) { + path = new Path(cast(Object)conn); + connectionToPaths.put(cast(Object)conn, path); + algorithm.addPath(path); + } + + List constraint = cast(List)getConstraint(conn); + if (constraint is 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 = cast(Bendpoint)constraint.get(i); + bends.addPoint(bp.getLocation()); + } + path.setBendPoints(bends); + } else + path.setBendPoints(null); + + isDirty |= path.isDirty; + } + staleConnections.clear(); +} + +void queueSomeRouting() { + if (connectionToPaths is null || connectionToPaths.isEmpty()) + return; + try { + ignoreInvalidate = true; + (cast(Connection)connectionToPaths.keySet().iterator().next()) + .revalidate(); + } finally { + ignoreInvalidate = false; + } +} + +/** + * @see ConnectionRouter#remove(Connection) + */ +public void remove(Connection connection) { + staleConnections.remove(cast(Object)connection); + constraintMap.remove(cast(Object)connection); + if (connectionToPaths is null) + return; + Path path = cast(Path)connectionToPaths.remove(cast(Object)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(); + } +} + +void removeChild(IFigure child) { + if (connectionToPaths is null) + return; + Rectangle bounds = child.getBounds().getCopy(); + bool change = algorithm.removeObstacle(bounds); + figuresToBounds.remove(cast(Object)child); + child.removeFigureListener(figureListener); + if (change) { + isDirty = true; + queueSomeRouting(); + } +} + +/** + * @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 = cast(Path) updated.get(i); + current = cast(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; + } +} + +/** + * @see ConnectionRouter#setConstraint(Connection, Object) + */ +public void setConstraint(Connection connection, Object constraint) { + //Connection.setConstraint() already calls revalidate, so we know that a + // route() call will follow. + staleConnections.add(cast(Object)connection); + constraintMap.put(cast(Object)connection, constraint); + isDirty = true; +} + +/** + * Sets the default space that should be maintained on either side of a connection. This + * causes the connections to be separated from each other and from the obstacles. The + * default value is 4. + * + * @param spacing the connection spacing + * @since 3.2 + */ +public void setSpacing(int spacing) { + algorithm.setSpacing(spacing); +} + +}