Project

General

Profile

Download (10.5 KB) Statistics
| Branch: | Tag: | Revision:
1
/*******************************************************************************
2
 * Copyright (c) 2000, 2010 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
 *******************************************************************************/
11
package org.eclipse.draw2d;
12

    
13
import java.util.Collections;
14
import java.util.HashMap;
15
import java.util.HashSet;
16
import java.util.Iterator;
17
import java.util.List;
18
import java.util.Map;
19
import java.util.Set;
20

    
21
import org.eclipse.draw2d.geometry.Point;
22
import org.eclipse.draw2d.geometry.PointList;
23
import org.eclipse.draw2d.geometry.PrecisionPoint;
24
import org.eclipse.draw2d.geometry.Rectangle;
25
import org.eclipse.draw2d.graph.Path;
26
import org.eclipse.draw2d.graph.ShortestPathRouter;
27

    
28
/**
29
 * Routes multiple connections around the children of a given container figure.
30
 * 
31
 * @author Whitney Sorenson
32
 * @author Randy Hudson
33
 * @since 3.1
34
 */
35
public final class ShortestPathConnectionRouter extends AbstractRouter {
36

    
37
	private class LayoutTracker extends LayoutListener.Stub {
38
		public void postLayout(IFigure container) {
39
			processLayout();
40
		}
41

    
42
		public void remove(IFigure child) {
43
			removeChild(child);
44
		}
45

    
46
		public void setConstraint(IFigure child, Object constraint) {
47
			addChild(child);
48
		}
49
	}
50

    
51
	private Map constraintMap = new HashMap();
52
	private Map figuresToBounds;
53
	private Map connectionToPaths;
54
	private boolean isDirty;
55
	private ShortestPathRouter algorithm = new ShortestPathRouter();
56
	private IFigure container;
57
	private Set staleConnections = new HashSet();
58
	private LayoutListener listener = new LayoutTracker();
59

    
60
	private FigureListener figureListener = new FigureListener() {
61
		public void figureMoved(IFigure source) {
62
			Rectangle newBounds = source.getBounds().getCopy();
63
			if (algorithm.updateObstacle(
64
					(Rectangle) figuresToBounds.get(source), newBounds)) {
65
				queueSomeRouting();
66
				isDirty = true;
67
			}
68

    
69
			figuresToBounds.put(source, newBounds);
70
		}
71
	};
72
	private boolean ignoreInvalidate;
73

    
74
	/**
75
	 * Creates a new shortest path router with the given container. The
76
	 * container contains all the figure's which will be treated as obstacles
77
	 * for the connections to avoid. Any time a child of the container moves,
78
	 * one or more connections will be revalidated to process the new obstacle
79
	 * locations. The connections being routed must not be contained within the
80
	 * container.
81
	 * 
82
	 * @param container
83
	 *            the container
84
	 */
85
	public ShortestPathConnectionRouter(IFigure container) {
86
		isDirty = false;
87
		algorithm = new ShortestPathRouter();
88
		this.container = container;
89
	}
90

    
91
	void addChild(IFigure child) {
92
		if (connectionToPaths == null)
93
			return;
94
		if (figuresToBounds.containsKey(child))
95
			return;
96
		Rectangle bounds = child.getBounds().getCopy();
97
		algorithm.addObstacle(bounds);
98
		figuresToBounds.put(child, bounds);
99
		child.addFigureListener(figureListener);
100
		isDirty = true;
101
	}
102

    
103
	private void hookAll() {
104
		figuresToBounds = new HashMap();
105
		for (int i = 0; i < container.getChildren().size(); i++)
106
			addChild((IFigure) container.getChildren().get(i));
107
		container.addLayoutListener(listener);
108
	}
109

    
110
	private void unhookAll() {
111
		container.removeLayoutListener(listener);
112
		if (figuresToBounds != null) {
113
			Iterator figureItr = figuresToBounds.keySet().iterator();
114
			while (figureItr.hasNext()) {
115
				// Must use iterator's remove to avoid concurrent modification
116
				IFigure child = (IFigure) figureItr.next();
117
				figureItr.remove();
118
				removeChild(child);
119
			}
120
			figuresToBounds = null;
121
		}
122
	}
123

    
124
	/**
125
	 * Gets the constraint for the given {@link Connection}. The constraint is
126
	 * the paths list of bend points for this connection.
127
	 * 
128
	 * @param connection
129
	 *            The connection whose constraint we are retrieving
130
	 * @return The constraint
131
	 */
132
	public Object getConstraint(Connection connection) {
133
		return constraintMap.get(connection);
134
	}
135

    
136
	/**
137
	 * Returns the default spacing maintained on either side of a connection.
138
	 * The default value is 4.
139
	 * 
140
	 * @return the connection spacing
141
	 * @since 3.2
142
	 */
143
	public int getSpacing() {
144
		return algorithm.getSpacing();
145
	}
146

    
147
	/**
148
	 * @see ConnectionRouter#invalidate(Connection)
149
	 */
150
	public void invalidate(Connection connection) {
151
		if (ignoreInvalidate)
152
			return;
153
		staleConnections.add(connection);
154
		isDirty = true;
155
	}
156

    
157
	private void processLayout() {
158
		if (staleConnections.isEmpty())
159
			return;
160
		((Connection) staleConnections.iterator().next()).revalidate();
161
	}
162

    
163
	private void processStaleConnections() {
164
		Iterator iter = staleConnections.iterator();
165
		if (iter.hasNext() && connectionToPaths == null) {
166
			connectionToPaths = new HashMap();
167
			hookAll();
168
		}
169

    
170
		while (iter.hasNext()) {
171
			Connection conn = (Connection) iter.next();
172

    
173
			Path path = (Path) connectionToPaths.get(conn);
174
			if (path == null) {
175
				path = new Path(conn);
176
				connectionToPaths.put(conn, path);
177
				algorithm.addPath(path);
178
			}
179

    
180
			List constraint = (List) getConstraint(conn);
181
			if (constraint == null)
182
				constraint = Collections.EMPTY_LIST;
183

    
184
			Point start = conn.getSourceAnchor().getReferencePoint().getCopy();
185
			Point end = conn.getTargetAnchor().getReferencePoint().getCopy();
186

    
187
			container.translateToRelative(start);
188
			container.translateToRelative(end);
189

    
190
			path.setStartPoint(start);
191
			path.setEndPoint(end);
192

    
193
			if (!constraint.isEmpty()) {
194
				PointList bends = new PointList(constraint.size());
195
				for (int i = 0; i < constraint.size(); i++) {
196
					Bendpoint bp = (Bendpoint) constraint.get(i);
197
					bends.addPoint(bp.getLocation());
198
				}
199
				path.setBendPoints(bends);
200
			} else
201
				path.setBendPoints(null);
202

    
203
			isDirty |= path.isDirty;
204
		}
205
		staleConnections.clear();
206
	}
207

    
208
	void queueSomeRouting() {
209
		if (connectionToPaths == null || connectionToPaths.isEmpty())
210
			return;
211
		try {
212
			ignoreInvalidate = true;
213
			((Connection) connectionToPaths.keySet().iterator().next())
214
					.revalidate();
215
		} finally {
216
			ignoreInvalidate = false;
217
		}
218
	}
219

    
220
	/**
221
	 * @see ConnectionRouter#remove(Connection)
222
	 */
223
	public void remove(Connection connection) {
224
		staleConnections.remove(connection);
225
		constraintMap.remove(connection);
226
		if (connectionToPaths == null)
227
			return;
228
		Path path = (Path) connectionToPaths.remove(connection);
229
		algorithm.removePath(path);
230
		isDirty = true;
231
		if (connectionToPaths.isEmpty()) {
232
			unhookAll();
233
			connectionToPaths = null;
234
		} else {
235
			// Make sure one of the remaining is revalidated so that we can
236
			// re-route again.
237
			queueSomeRouting();
238
		}
239
	}
240

    
241
	void removeChild(IFigure child) {
242
		if (connectionToPaths == null)
243
			return;
244
		Rectangle bounds = child.getBounds().getCopy();
245
		boolean change = algorithm.removeObstacle(bounds);
246
		figuresToBounds.remove(child);
247
		child.removeFigureListener(figureListener);
248
		if (change) {
249
			isDirty = true;
250
			queueSomeRouting();
251
		}
252
	}
253

    
254
	/**
255
	 * @see ConnectionRouter#route(Connection)
256
	 */
257
	public void route(Connection conn) {
258
		if (isDirty) {
259
			ignoreInvalidate = true;
260
			processStaleConnections();
261
			isDirty = false;
262
			List updated = algorithm.solve();
263
			Connection current;
264
			for (int i = 0; i < updated.size(); i++) {
265
				Path path = (Path) updated.get(i);
266
				current = (Connection) path.data;
267
				current.revalidate();
268

    
269
				PointList points = path.getPoints().getCopy();
270
				Point ref1, ref2, start, end;
271
				ref1 = new PrecisionPoint(points.getPoint(1));
272
				ref2 = new PrecisionPoint(points.getPoint(points.size() - 2));
273
				current.translateToAbsolute(ref1);
274
				current.translateToAbsolute(ref2);
275

    
276
				start = current.getSourceAnchor().getLocation(ref1).getCopy();
277
				end = current.getTargetAnchor().getLocation(ref2).getCopy();
278

    
279
				current.translateToRelative(start);
280
				current.translateToRelative(end);
281
				points.setPoint(start, 0);
282
				points.setPoint(end, points.size() - 1);
283

    
284
				current.setPoints(points);
285
			}
286
			ignoreInvalidate = false;
287
		}
288
	}
289

    
290
	/**
291
	 * @return All connection paths after routing dirty paths. Some of the paths
292
	 *         that were not dirty may change as well, as a consequence of new
293
	 *         routings.
294
	 * @since 3.5
295
	 */
296
	public List getPathsAfterRouting() {
297
		if (isDirty) {
298
			processStaleConnections();
299
			isDirty = false;
300
			List all = algorithm.solve();
301
			return all;
302

    
303
		}
304
		return null;
305
	}
306

    
307
	/**
308
	 * @see ConnectionRouter#setConstraint(Connection, Object)
309
	 */
310
	public void setConstraint(Connection connection, Object constraint) {
311
		// Connection.setConstraint() already calls revalidate, so we know that
312
		// a
313
		// route() call will follow.
314
		staleConnections.add(connection);
315
		constraintMap.put(connection, constraint);
316
		isDirty = true;
317
	}
318

    
319
	/**
320
	 * Sets the default space that should be maintained on either side of a
321
	 * connection. This causes the connections to be separated from each other
322
	 * and from the obstacles. The default value is 4.
323
	 * 
324
	 * @param spacing
325
	 *            the connection spacing
326
	 * @since 3.2
327
	 */
328
	public void setSpacing(int spacing) {
329
		algorithm.setSpacing(spacing);
330
	}
331

    
332
	/**
333
	 * @return true if there are connections routed by this router, false
334
	 *         otherwise
335
	 * @since 3.5
336
	 */
337
	public boolean hasMoreConnections() {
338
		return connectionToPaths != null && !connectionToPaths.isEmpty();
339
	}
340

    
341
	/**
342
	 * @return the container which contains connections routed by this router
343
	 * @since 3.5
344
	 */
345
	public IFigure getContainer() {
346
		return container;
347
	}
348

    
349
	/**
350
	 * Sets the value indicating if connection invalidation should be ignored.
351
	 * 
352
	 * @param b
353
	 *            true if invalidation should be skipped, false otherwise
354
	 * @since 3.5
355
	 */
356
	public void setIgnoreInvalidate(boolean b) {
357
		ignoreInvalidate = b;
358
	}
359

    
360
	/**
361
	 * Returns the value indicating if connection invalidation should be
362
	 * ignored.
363
	 * 
364
	 * @return true if invalidation should be skipped, false otherwise
365
	 * @since 3.5
366
	 */
367
	public boolean shouldIgnoreInvalidate() {
368
		return ignoreInvalidate;
369
	}
370

    
371
	/**
372
	 * Returns the value indicating if the router is dirty, i.e. if there are
373
	 * any outstanding connections that need to be routed
374
	 * 
375
	 * @return true if there are connections to be routed, false otherwise
376
	 * @since 3.5
377
	 */
378
	public boolean isDirty() {
379
		return isDirty;
380
	}
381

    
382
	/**
383
	 * Returns true if the given connection is routed by this router, false
384
	 * otherwise
385
	 * 
386
	 * @param conn
387
	 *            Connection whose router is questioned
388
	 * @return true if this is the router used for conn
389
	 * @since 3.5
390
	 */
391
	public boolean containsConnection(Connection conn) {
392
		return connectionToPaths != null && connectionToPaths.containsKey(conn);
393
	}
394

    
395
}
(148-148/171)