comparison 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
comparison
equal deleted inserted replaced
96:b492ba44e44d 98:95307ad235d9
1 /*******************************************************************************
2 * Copyright (c) 2000, 2005 IBM Corporation and others.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the Eclipse Public License v1.0
5 * which accompanies this distribution, and is available at
6 * http://www.eclipse.org/legal/epl-v10.html
7 *
8 * Contributors:
9 * IBM Corporation - initial API and implementation
10 * Port to the D programming language:
11 * Frank Benoit <benoit@tionex.de>
12 *******************************************************************************/
13 module dwtx.draw2d.ShortestPathConnectionRouter;
14
15 import dwt.dwthelper.utils;
16 import dwtx.dwtxhelper.Collection;
17
18 import dwtx.draw2d.geometry.Point;
19 import dwtx.draw2d.geometry.PointList;
20 import dwtx.draw2d.geometry.PrecisionPoint;
21 import dwtx.draw2d.geometry.Rectangle;
22 import dwtx.draw2d.graph.Path;
23 import dwtx.draw2d.graph.ShortestPathRouter;
24 import dwtx.draw2d.AbstractRouter;
25 import dwtx.draw2d.IFigure;
26 import dwtx.draw2d.LayoutListener;
27 import dwtx.draw2d.FigureListener;
28 import dwtx.draw2d.Connection;
29 import dwtx.draw2d.Bendpoint;
30
31 /**
32 * Routes multiple connections around the children of a given container figure.
33 * @author Whitney Sorenson
34 * @author Randy Hudson
35 * @since 3.1
36 */
37 public final class ShortestPathConnectionRouter
38 : AbstractRouter
39 {
40
41 private class LayoutTracker : LayoutListenerStub {
42 public void postLayout(IFigure container) {
43 processLayout();
44 }
45 public void remove(IFigure child) {
46 removeChild(child);
47 }
48 public void setConstraint(IFigure child, Object constraint) {
49 addChild(child);
50 }
51 }
52
53 private Map constraintMap;
54 private Map figuresToBounds;
55 private Map connectionToPaths;
56 private bool isDirty;
57 private ShortestPathRouter algorithm;
58 private IFigure container;
59 private Set staleConnections;
60 private LayoutListener listener;
61
62 private FigureListener figureListener;
63 private void initFigureListener(){
64 figureListener = new class() FigureListener {
65 public void figureMoved(IFigure source) {
66 Rectangle newBounds = source.getBounds().getCopy();
67 if (algorithm.updateObstacle(cast(Rectangle)figuresToBounds.get(cast(Object)source), newBounds)) {
68 queueSomeRouting();
69 isDirty = true;
70 }
71
72 figuresToBounds.put(cast(Object)source, newBounds);
73 }
74 };
75 }
76 private bool ignoreInvalidate;
77
78 /**
79 * Creates a new shortest path router with the given container. The container
80 * contains all the figure's which will be treated as obstacles for the connections to
81 * avoid. Any time a child of the container moves, one or more connections will be
82 * revalidated to process the new obstacle locations. The connections being routed must
83 * not be contained within the container.
84 *
85 * @param container the container
86 */
87 public this(IFigure container) {
88 initFigureListener();
89 constraintMap = new HashMap();
90 algorithm = new ShortestPathRouter();
91 staleConnections = new HashSet();
92 listener = new LayoutTracker();
93 isDirty = false;
94 algorithm = new ShortestPathRouter();
95 this.container = container;
96 }
97
98 void addChild(IFigure child) {
99 if (connectionToPaths is null)
100 return;
101 if (figuresToBounds.containsKey(cast(Object)child))
102 return;
103 Rectangle bounds = child.getBounds().getCopy();
104 algorithm.addObstacle(bounds);
105 figuresToBounds.put(cast(Object)child, bounds);
106 child.addFigureListener(figureListener);
107 isDirty = true;
108 }
109
110 private void hookAll() {
111 figuresToBounds = new HashMap();
112 for (int i = 0; i < container.getChildren().size(); i++)
113 addChild(cast(IFigure)container.getChildren().get(i));
114 container.addLayoutListener(listener);
115 }
116
117 private void unhookAll() {
118 container.removeLayoutListener(listener);
119 if (figuresToBounds !is null) {
120 Iterator figureItr = figuresToBounds.keySet().iterator();
121 while (figureItr.hasNext()) {
122 //Must use iterator's remove to avoid concurrent modification
123 IFigure child = cast(IFigure)figureItr.next();
124 figureItr.remove();
125 removeChild(child);
126 }
127 figuresToBounds = null;
128 }
129 }
130
131 /**
132 * Gets the constraint for the given {@link Connection}. The constraint is the paths
133 * list of bend points for this connection.
134 *
135 * @param connection The connection whose constraint we are retrieving
136 * @return The constraint
137 */
138 public Object getConstraint(Connection connection) {
139 return constraintMap.get(cast(Object)connection);
140 }
141
142 /**
143 * Returns the default spacing maintained on either side of a connection. The default
144 * value is 4.
145 * @return the connection spacing
146 * @since 3.2
147 */
148 public int getSpacing() {
149 return algorithm.getSpacing();
150 }
151
152 /**
153 * @see ConnectionRouter#invalidate(Connection)
154 */
155 public void invalidate(Connection connection) {
156 if (ignoreInvalidate)
157 return;
158 staleConnections.add(cast(Object)connection);
159 isDirty = true;
160 }
161
162 private void processLayout() {
163 if (staleConnections.isEmpty())
164 return;
165 (cast(Connection)staleConnections.iterator().next()).revalidate();
166 }
167
168 private void processStaleConnections() {
169 Iterator iter = staleConnections.iterator();
170 if (iter.hasNext() && connectionToPaths is null) {
171 connectionToPaths = new HashMap();
172 hookAll();
173 }
174
175 while (iter.hasNext()) {
176 Connection conn = cast(Connection)iter.next();
177
178 Path path = cast(Path)connectionToPaths.get(cast(Object)conn);
179 if (path is null) {
180 path = new Path(cast(Object)conn);
181 connectionToPaths.put(cast(Object)conn, path);
182 algorithm.addPath(path);
183 }
184
185 List constraint = cast(List)getConstraint(conn);
186 if (constraint is null)
187 constraint = Collections.EMPTY_LIST;
188
189 Point start = conn.getSourceAnchor().getReferencePoint().getCopy();
190 Point end = conn.getTargetAnchor().getReferencePoint().getCopy();
191
192 container.translateToRelative(start);
193 container.translateToRelative(end);
194
195 path.setStartPoint(start);
196 path.setEndPoint(end);
197
198 if (!constraint.isEmpty()) {
199 PointList bends = new PointList(constraint.size());
200 for (int i = 0; i < constraint.size(); i++) {
201 Bendpoint bp = cast(Bendpoint)constraint.get(i);
202 bends.addPoint(bp.getLocation());
203 }
204 path.setBendPoints(bends);
205 } else
206 path.setBendPoints(null);
207
208 isDirty |= path.isDirty;
209 }
210 staleConnections.clear();
211 }
212
213 void queueSomeRouting() {
214 if (connectionToPaths is null || connectionToPaths.isEmpty())
215 return;
216 try {
217 ignoreInvalidate = true;
218 (cast(Connection)connectionToPaths.keySet().iterator().next())
219 .revalidate();
220 } finally {
221 ignoreInvalidate = false;
222 }
223 }
224
225 /**
226 * @see ConnectionRouter#remove(Connection)
227 */
228 public void remove(Connection connection) {
229 staleConnections.remove(cast(Object)connection);
230 constraintMap.remove(cast(Object)connection);
231 if (connectionToPaths is null)
232 return;
233 Path path = cast(Path)connectionToPaths.remove(cast(Object)connection);
234 algorithm.removePath(path);
235 isDirty = true;
236 if (connectionToPaths.isEmpty()) {
237 unhookAll();
238 connectionToPaths = null;
239 } else {
240 //Make sure one of the remaining is revalidated so that we can re-route again.
241 queueSomeRouting();
242 }
243 }
244
245 void removeChild(IFigure child) {
246 if (connectionToPaths is null)
247 return;
248 Rectangle bounds = child.getBounds().getCopy();
249 bool change = algorithm.removeObstacle(bounds);
250 figuresToBounds.remove(cast(Object)child);
251 child.removeFigureListener(figureListener);
252 if (change) {
253 isDirty = true;
254 queueSomeRouting();
255 }
256 }
257
258 /**
259 * @see ConnectionRouter#route(Connection)
260 */
261 public void route(Connection conn) {
262 if (isDirty) {
263 ignoreInvalidate = true;
264 processStaleConnections();
265 isDirty = false;
266 List updated = algorithm.solve();
267 Connection current;
268 for (int i = 0; i < updated.size(); i++) {
269 Path path = cast(Path) updated.get(i);
270 current = cast(Connection)path.data;
271 current.revalidate();
272
273 PointList points = path.getPoints().getCopy();
274 Point ref1, ref2, start, end;
275 ref1 = new PrecisionPoint(points.getPoint(1));
276 ref2 = new PrecisionPoint(points.getPoint(points.size() - 2));
277 current.translateToAbsolute(ref1);
278 current.translateToAbsolute(ref2);
279
280 start = current.getSourceAnchor().getLocation(ref1).getCopy();
281 end = current.getTargetAnchor().getLocation(ref2).getCopy();
282
283 current.translateToRelative(start);
284 current.translateToRelative(end);
285 points.setPoint(start, 0);
286 points.setPoint(end, points.size() - 1);
287
288 current.setPoints(points);
289 }
290 ignoreInvalidate = false;
291 }
292 }
293
294 /**
295 * @see ConnectionRouter#setConstraint(Connection, Object)
296 */
297 public void setConstraint(Connection connection, Object constraint) {
298 //Connection.setConstraint() already calls revalidate, so we know that a
299 // route() call will follow.
300 staleConnections.add(cast(Object)connection);
301 constraintMap.put(cast(Object)connection, constraint);
302 isDirty = true;
303 }
304
305 /**
306 * Sets the default space that should be maintained on either side of a connection. This
307 * causes the connections to be separated from each other and from the obstacles. The
308 * default value is 4.
309 *
310 * @param spacing the connection spacing
311 * @since 3.2
312 */
313 public void setSpacing(int spacing) {
314 algorithm.setSpacing(spacing);
315 }
316
317 }