| /******************************************************************************* |
| * 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); |
| } |
| |
| } |
| |
| } |