| /********************************************************************* |
| * Copyright (c) 2000, 2019 IBM Corporation and others. |
| * |
| * This program and the accompanying materials are made |
| * available under the terms of the Eclipse Public License 2.0 |
| * which is available at https://www.eclipse.org/legal/epl-2.0/ |
| * |
| * Contributors: |
| * IBM Corporation - initial API and implementation |
| * SAP AG - Changed for initial API, implementation and documentation of Graphiti: |
| * added getBounds |
| * added getSourceDecorations |
| * added getTargetDecorations |
| * added setSourceDecorations |
| * added setTargetDecorations |
| * removed getMaxValue |
| * added override tags |
| * mwenz - Bug 352440 - Fixed deprecation warnings - contributed by Felix Velasco |
| * mgorning - Bug 378301 - Small gap between connections and right side anchor |
| * |
| * SPDX-License-Identifier: EPL-2.0 |
| **********************************************************************/ |
| package org.eclipse.graphiti.ui.internal.figures; |
| |
| import java.util.ArrayList; |
| import java.util.List; |
| |
| import org.eclipse.draw2d.AnchorListener; |
| import org.eclipse.draw2d.Connection; |
| import org.eclipse.draw2d.ConnectionAnchor; |
| import org.eclipse.draw2d.ConnectionRouter; |
| import org.eclipse.draw2d.DelegatingLayout; |
| import org.eclipse.draw2d.Figure; |
| import org.eclipse.draw2d.IFigure; |
| import org.eclipse.draw2d.RoutingListener; |
| import org.eclipse.draw2d.geometry.Point; |
| import org.eclipse.draw2d.geometry.PointList; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm; |
| import org.eclipse.graphiti.ui.internal.parts.IPictogramElementDelegate; |
| |
| /** |
| * A Graphiti Polyline Connection. The "polyline functionality" is inherited |
| * from the super-class, the "connection functionality" is added in this class. |
| * <p> |
| * Important difference: The super-class denotes a polyline-shape. There the |
| * points are translated towards the center depending on the line-width, so that |
| * the bounds of the polyline-shape are only dependent on the points of the |
| * polyline but not on the current line-width. Otherwise it would be difficult |
| * to align its positions/sizes with the neighbor-shapes or parent-shapes. This |
| * class on the other hand denotes a polyline-connection. Here the points are |
| * not translated towards the center, because the polyline must always go |
| * directly through the given points. |
| * |
| * @noinstantiate This class is not intended to be instantiated by clients. |
| * @noextend This class is not intended to be subclassed by clients. |
| */ |
| public class GFPolylineConnection extends GFPolyline implements Connection, AnchorListener { |
| |
| /** |
| * Creates a new GFPolylineConnection. |
| * |
| * @param pictogramElementDelegate |
| * The PictogramElementDelegate which provides the |
| * GraphicsAlgorithm. |
| * @param graphicsAlgorithm |
| * The GraphicsAlgorithm which provides the values to paint this |
| * Shape. |
| */ |
| public GFPolylineConnection(IPictogramElementDelegate pictogramElementDelegate, GraphicsAlgorithm graphicsAlgorithm) { |
| super(pictogramElementDelegate, graphicsAlgorithm); |
| |
| // by default the connections are drawn as rounded bezier-curves |
| setGeneralBezierDistance(15); |
| } |
| |
| /** |
| * Returns a new instance of the input point-list, which is adjusted |
| * regarding the given zoom-factor and line-width. |
| * <p> |
| * The implementation only zooms the points regarding the zoom-factor. It |
| * does not translate the points towards the center depending on the |
| * line-width. See explanation in the javadoc of this class. |
| * |
| * @param points |
| * The point-list which to adjust. |
| * @param zoom |
| * The zoom-factor to regard. |
| * @param lw |
| * The line-width to regard. |
| */ |
| @Override |
| protected PointList getAdjustedPointList(PointList points, double zoom, double lw) { |
| // only zoom, but do not translate regarding the line-width as the |
| // super-class |
| // copy the point list |
| PointList pointsCopy = new PointList(); |
| pointsCopy.addAll(points); |
| // adjust both end points if necessary; see Bug 378301 |
| // it seems there is a problem if we draw a line to an end point from |
| // left to right or up to bottom. The last point will not be drawn and |
| // hence there is a gap between the connection and the shape. Especially |
| // if zoom is greater then 100%. |
| if (pointsCopy.size() >= 2) { |
| // check end points and adjust them if necessary |
| int lastPointIndex = pointsCopy.size() - 1; |
| adjustEndPoint(pointsCopy, 0, 1); |
| adjustEndPoint(pointsCopy, lastPointIndex, lastPointIndex - 1); |
| } |
| // handle zoom |
| PointList ret = new PointList(); |
| for (int i = 0; i < pointsCopy.size(); i++) { |
| Point point = pointsCopy.getPoint(i); |
| point.scale(zoom); |
| ret.addPoint(point); |
| } |
| return ret; |
| } |
| |
| /** |
| * Returns the bounds which holds all the points in this polyline |
| * connection. Note, that the childrens bounds are also added to the |
| * super-class bounds. |
| * <p> |
| * The implementation adds the half line-width to the bounds of the |
| * super-class, because the points are not translated towards the center |
| * (see {@link #getAdjustedPointList(PointList, double, double)}), so that |
| * bounds must expand depending on the line-width. |
| * |
| * @return The bounds which holds all the points in this polyline |
| * connection. |
| */ |
| @Override |
| public Rectangle getBounds() { |
| if (bounds == null) { |
| super.getBounds(); |
| int halfLineWidth = getLineWidth() / 2; |
| bounds.expand(halfLineWidth, halfLineWidth); |
| |
| for (int i = 0; i < getChildren().size(); i++) { |
| IFigure child = (IFigure) getChildren().get(i); |
| bounds.union(child.getBounds()); |
| } |
| } |
| return bounds; |
| } |
| |
| @Override |
| protected List<BezierPoint> getBezierPoints(PointList points, double zoom) { |
| List<BezierPoint> ret = new ArrayList<BezierPoint>(points.size()); |
| if (getSpecificBezierDistances() != null && (points.size() == getSpecificBezierDistances().length / 2)) { |
| for (int i = 0; i < points.size(); i++) { |
| int bezierDistanceBefore = (int) (getSpecificBezierDistances()[2 * i] * zoom); |
| int bezierDistanceAfter = (int) (getSpecificBezierDistances()[2 * i + 1] * zoom); |
| Point point = points.getPoint(i); |
| ret.add(new BezierPoint(point.x, point.y, bezierDistanceBefore, bezierDistanceAfter)); |
| } |
| } else { |
| // special case if we create or remove a bendpoint |
| for (int i = 0; i < points.size(); i++) { |
| int bezierDistance = (int) (getGeneralBezierDistance() * zoom); |
| Point point = points.getPoint(i); |
| ret.add(new BezierPoint(point.x, point.y, bezierDistance, bezierDistance)); |
| } |
| } |
| return ret; |
| } |
| |
| @Override |
| public void setSpecificBezierDistances(int[] bezierDistances) { |
| super.setSpecificBezierDistances(bezierDistances); |
| setGeneralBezierDistance(getMaxValue(bezierDistances)); |
| } |
| |
| // ///////////////////////////////////////////////////////////////////////// |
| // ///////////////////////////////////////////////////////////////////////// |
| // the following code is copied from PolylineConnection // |
| // (changed: added @Override tags) // |
| // (changed: made methods getXXXDecoration() public) // |
| // ///////////////////////////////////////////////////////////////////////// |
| // ///////////////////////////////////////////////////////////////////////// |
| |
| private ConnectionAnchor startAnchor, endAnchor; |
| |
| private ConnectionRouter connectionRouter = ConnectionRouter.NULL; |
| |
| /** |
| * A list of all decorations, which are currently added to this connection. |
| */ |
| private List<IFigure> decorations = new ArrayList<IFigure>(); |
| |
| { |
| setLayoutManager(new DelegatingLayout()); |
| addPoint(new Point(0, 0)); |
| addPoint(new Point(100, 100)); |
| } |
| |
| /** |
| * Hooks the source and target anchors. |
| * |
| * @see Figure#addNotify() |
| */ |
| @Override |
| 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 <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 anchor at the end of this polyline connection (may be null) |
| */ |
| public ConnectionAnchor getTargetAnchor() { |
| return endAnchor; |
| } |
| |
| 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. |
| */ |
| @Override |
| 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 |
| */ |
| @Override |
| 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() |
| */ |
| @Override |
| 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(); |
| } |
| |
| /** |
| * Adds a decoration to this connection. The decoration is located depending |
| * on the distance values. |
| * |
| * @param decoration |
| * The decoration to add. |
| * @param distanceToStart |
| * If the distance values refer to the start-point or the |
| * end-point. See {@link FlexibleRotatableLocator}. |
| * @param relativeDistance |
| * The relative distance. See {@link FlexibleRotatableLocator}. |
| * @param absoluteDistance |
| * The absolute distance. See {@link FlexibleRotatableLocator}. |
| * @param rotateDegrees |
| * The rotation in degrees. See {@link FlexibleRotatableLocator}. |
| */ |
| public void addDecoration(IFigure decoration, boolean distanceToStart, double relativeDistance, |
| int absoluteDistance, double rotateDegrees) { |
| if (decoration != null) { |
| // remove if decoration was added before |
| boolean removed = decorations.remove(decoration); |
| if (removed) { // was added before |
| remove(decoration); |
| } |
| |
| // add decoration |
| decorations.add(decoration); |
| add(decoration, new FlexibleRotatableLocator(this, distanceToStart, relativeDistance, absoluteDistance, |
| rotateDegrees)); |
| } |
| } |
| |
| /** |
| * Removes all currently added decorations from the connection. |
| * |
| * @see #getAllDecorations() |
| */ |
| public void removeAllDecorations() { |
| for (IFigure decoration : decorations) { |
| remove(decoration); |
| } |
| decorations.clear(); |
| } |
| |
| /** |
| * Returns a list of all decorations, which are currently added to this |
| * connection. Can be empty but not null. |
| * |
| * @return A list of all decorations, which are currently added to this |
| * connection. |
| * @see #addDecoration(IFigure, boolean, double, int) |
| */ |
| public List<IFigure> getAllDecorations() { |
| return decorations; |
| } |
| |
| /** |
| * 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(); |
| } |
| |
| 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<RoutingListener> listeners = new ArrayList<RoutingListener>(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++) |
| 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 |= listeners.get(i).route(connection); |
| |
| if (!consumed) |
| realRouter.route(connection); |
| |
| for (int i = 0; i < listeners.size(); i++) |
| listeners.get(i).postRoute(connection); |
| } |
| |
| public void remove(Connection connection) { |
| for (int i = 0; i < listeners.size(); i++) |
| listeners.get(i).remove(connection); |
| realRouter.remove(connection); |
| } |
| |
| public void setConstraint(Connection connection, Object constraint) { |
| for (int i = 0; i < listeners.size(); i++) |
| listeners.get(i).setConstraint(connection, constraint); |
| realRouter.setConstraint(connection, constraint); |
| } |
| |
| } |
| |
| private int getMaxValue(int[] numbers) { |
| int maxValue = numbers[0]; |
| for (int i = 1; i < numbers.length; i++) { |
| if (numbers[i] > maxValue) { |
| maxValue = numbers[i]; |
| } |
| } |
| return maxValue; |
| } |
| |
| private void adjustEndPoint(PointList pointsCopy, int endPointIndex, int previousPointIndex) { |
| Point endPoint = pointsCopy.getPoint(endPointIndex); |
| Point previousPoint = pointsCopy.getPoint(previousPointIndex); |
| if (endPoint.x > previousPoint.x) { |
| endPoint.x += 1; |
| pointsCopy.setPoint(endPoint, endPointIndex); |
| } |
| if (endPoint.y > previousPoint.y) { |
| endPoint.y += 1; |
| pointsCopy.setPoint(endPoint, endPointIndex); |
| } |
| } |
| } |