blob: 296091bf37fdf0d9f929c82733433212522e58a1 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2010 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
*******************************************************************************/
package org.eclipse.draw2d;
import java.util.ArrayList;
import java.util.List;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
/**
* An implementation of {@link Connection} based on Polyline. PolylineConnection
* adds the following additional features:
* <UL>
* <LI>
* A {@link ConnectionRouter} may be provided which will be used to determine
* the connections points.
* <LI>
* Children may be added. The bounds calculation is extended such that the
* bounds is the smallest Rectangle which is large enough to display the
* Polyline and all of its children figures.
* <LI>
* A {@link DelegatingLayout} is set as the default layout. A delegating layout
* allows children to position themselves via {@link Locator Locators}.
* </UL>
* <P>
*/
public class PolylineConnection extends Polyline implements Connection,
AnchorListener {
private ConnectionAnchor startAnchor, endAnchor;
private ConnectionRouter connectionRouter = ConnectionRouter.NULL;
private RotatableDecoration startArrow, endArrow;
{
setLayoutManager(new DelegatingLayout());
addPoint(new Point(0, 0));
addPoint(new Point(100, 100));
}
/**
* Hooks the source and target anchors.
*
* @see Figure#addNotify()
*/
public void addNotify() {
super.addNotify();
hookSourceAnchor();
hookTargetAnchor();
}
/**
* Appends the given routing listener to the list of listeners.
*
* @param listener
* the routing listener
* @since 3.2
*/
public void addRoutingListener(RoutingListener listener) {
if (connectionRouter instanceof RoutingNotifier) {
RoutingNotifier notifier = (RoutingNotifier) connectionRouter;
notifier.listeners.add(listener);
} else
connectionRouter = new RoutingNotifier(connectionRouter, listener);
}
/**
* Called by the anchors of this connection when they have moved,
* revalidating this polyline connection.
*
* @param anchor
* the anchor that moved
*/
public void anchorMoved(ConnectionAnchor anchor) {
revalidate();
}
/**
* Returns the bounds which holds all the points in this polyline
* connection. Returns any previously existing bounds, else calculates by
* unioning all the children's dimensions.
*
* @return the bounds
*/
public Rectangle getBounds() {
if (bounds == null) {
super.getBounds();
for (int i = 0; i < getChildren().size(); i++) {
IFigure child = (IFigure) getChildren().get(i);
bounds.union(child.getBounds());
}
}
return bounds;
}
/**
* Returns the <code>ConnectionRouter</code> used to layout this connection.
* Will not return <code>null</code>.
*
* @return this connection's router
*/
public ConnectionRouter getConnectionRouter() {
if (connectionRouter instanceof RoutingNotifier)
return ((RoutingNotifier) connectionRouter).realRouter;
return connectionRouter;
}
/**
* Returns this connection's routing constraint from its connection router.
* May return <code>null</code>.
*
* @return the connection's routing constraint
*/
public Object getRoutingConstraint() {
if (getConnectionRouter() != null)
return getConnectionRouter().getConstraint(this);
else
return null;
}
/**
* @return the anchor at the start of this polyline connection (may be null)
*/
public ConnectionAnchor getSourceAnchor() {
return startAnchor;
}
/**
* @return the source decoration (may be null)
*/
protected RotatableDecoration getSourceDecoration() {
return startArrow;
}
/**
* @return the anchor at the end of this polyline connection (may be null)
*/
public ConnectionAnchor getTargetAnchor() {
return endAnchor;
}
/**
* @return the target decoration (may be null)
*
* @since 2.0
*/
protected RotatableDecoration getTargetDecoration() {
return endArrow;
}
private void hookSourceAnchor() {
if (getSourceAnchor() != null)
getSourceAnchor().addAnchorListener(this);
}
private void hookTargetAnchor() {
if (getTargetAnchor() != null)
getTargetAnchor().addAnchorListener(this);
}
/**
* Layouts this polyline. If the start and end anchors are present, the
* connection router is used to route this, after which it is laid out. It
* also fires a moved method.
*/
public void layout() {
if (getSourceAnchor() != null && getTargetAnchor() != null)
connectionRouter.route(this);
Rectangle oldBounds = bounds;
super.layout();
bounds = null;
if (!getBounds().contains(oldBounds)) {
getParent().translateToParent(oldBounds);
getUpdateManager().addDirtyRegion(getParent(), oldBounds);
}
repaint();
fireFigureMoved();
}
/**
* Called just before the receiver is being removed from its parent. Results
* in removing itself from the connection router.
*
* @since 2.0
*/
public void removeNotify() {
unhookSourceAnchor();
unhookTargetAnchor();
connectionRouter.remove(this);
super.removeNotify();
}
/**
* Removes the first occurence of the given listener.
*
* @param listener
* the listener being removed
* @since 3.2
*/
public void removeRoutingListener(RoutingListener listener) {
if (connectionRouter instanceof RoutingNotifier) {
RoutingNotifier notifier = (RoutingNotifier) connectionRouter;
notifier.listeners.remove(listener);
if (notifier.listeners.isEmpty())
connectionRouter = notifier.realRouter;
}
}
/**
* @see IFigure#revalidate()
*/
public void revalidate() {
super.revalidate();
connectionRouter.invalidate(this);
}
/**
* Sets the connection router which handles the layout of this polyline.
* Generally set by the parent handling the polyline connection.
*
* @param cr
* the connection router
*/
public void setConnectionRouter(ConnectionRouter cr) {
if (cr == null)
cr = ConnectionRouter.NULL;
ConnectionRouter oldRouter = getConnectionRouter();
if (oldRouter != cr) {
connectionRouter.remove(this);
if (connectionRouter instanceof RoutingNotifier)
((RoutingNotifier) connectionRouter).realRouter = cr;
else
connectionRouter = cr;
firePropertyChange(Connection.PROPERTY_CONNECTION_ROUTER,
oldRouter, cr);
revalidate();
}
}
/**
* Sets the routing constraint for this connection.
*
* @param cons
* the constraint
*/
public void setRoutingConstraint(Object cons) {
if (connectionRouter != null)
connectionRouter.setConstraint(this, cons);
revalidate();
}
/**
* Sets the anchor to be used at the start of this polyline connection.
*
* @param anchor
* the new source anchor
*/
public void setSourceAnchor(ConnectionAnchor anchor) {
if (anchor == startAnchor)
return;
unhookSourceAnchor();
// No longer needed, revalidate does this.
// getConnectionRouter().invalidate(this);
startAnchor = anchor;
if (getParent() != null)
hookSourceAnchor();
revalidate();
}
/**
* Sets the decoration to be used at the start of the {@link Connection}.
*
* @param dec
* the new source decoration
* @since 2.0
*/
public void setSourceDecoration(RotatableDecoration dec) {
if (startArrow == dec)
return;
if (startArrow != null)
remove(startArrow);
startArrow = dec;
if (startArrow != null)
add(startArrow, new ArrowLocator(this, ConnectionLocator.SOURCE));
}
/**
* Sets the anchor to be used at the end of the polyline connection. Removes
* this listener from the old anchor and adds it to the new anchor.
*
* @param anchor
* the new target anchor
*/
public void setTargetAnchor(ConnectionAnchor anchor) {
if (anchor == endAnchor)
return;
unhookTargetAnchor();
// No longer needed, revalidate does this.
// getConnectionRouter().invalidate(this);
endAnchor = anchor;
if (getParent() != null)
hookTargetAnchor();
revalidate();
}
/**
* Sets the decoration to be used at the end of the {@link Connection}.
*
* @param dec
* the new target decoration
*/
public void setTargetDecoration(RotatableDecoration dec) {
if (endArrow == dec)
return;
if (endArrow != null)
remove(endArrow);
endArrow = dec;
if (endArrow != null)
add(endArrow, new ArrowLocator(this, ConnectionLocator.TARGET));
}
private void unhookSourceAnchor() {
if (getSourceAnchor() != null)
getSourceAnchor().removeAnchorListener(this);
}
private void unhookTargetAnchor() {
if (getTargetAnchor() != null)
getTargetAnchor().removeAnchorListener(this);
}
final class RoutingNotifier implements ConnectionRouter {
ConnectionRouter realRouter;
List listeners = new ArrayList(1);
RoutingNotifier(ConnectionRouter router, RoutingListener listener) {
realRouter = router;
listeners.add(listener);
}
public Object getConstraint(Connection connection) {
return realRouter.getConstraint(connection);
}
public void invalidate(Connection connection) {
for (int i = 0; i < listeners.size(); i++)
((RoutingListener) listeners.get(i)).invalidate(connection);
realRouter.invalidate(connection);
}
public void route(Connection connection) {
boolean consumed = false;
for (int i = 0; i < listeners.size(); i++)
consumed |= ((RoutingListener) listeners.get(i))
.route(connection);
if (!consumed)
realRouter.route(connection);
for (int i = 0; i < listeners.size(); i++)
((RoutingListener) listeners.get(i)).postRoute(connection);
}
public void remove(Connection connection) {
for (int i = 0; i < listeners.size(); i++)
((RoutingListener) listeners.get(i)).remove(connection);
realRouter.remove(connection);
}
public void setConstraint(Connection connection, Object constraint) {
for (int i = 0; i < listeners.size(); i++)
((RoutingListener) listeners.get(i)).setConstraint(connection,
constraint);
realRouter.setConstraint(connection, constraint);
}
}
}