Bug 442157 improved rectilinear router
Change-Id: I69d1b6e9009669538d9a7978bf44b6399d9ad28d
Signed-off-by: mgolubev <golubev@montages.com>
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/HintedOrthogonalRouter.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/HintedOrthogonalRouter.java
new file mode 100644
index 0000000..2b09bb7
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/HintedOrthogonalRouter.java
@@ -0,0 +1,61 @@
+/*****************************************************************************
+ * Copyright (c) 2014-15 CEA LIST, Montages AG 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:
+ * Michael Golubev (Montages) - Initial API and implementation
+ *
+ *****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.draw2d.Connection;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.OrthogonalRouter;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter;
+
+/**
+ * When connection anchor and first/last bendpoint are not aligned vertically / horizontally,
+ * the {@link OrthogonalRouter} has to decide whether the first / last segment of link will
+ * go:
+ * <ul>
+ * <li>from bendpoint to some point different to anchor</li>
+ * <li>from anchor to some point different to bendpoint</li>
+ * </ul>
+ * The default {@link RectilinearRouter} always prefers the first choice.
+ * <p>
+ * This router extends the {@link OrthogonalRouter} with ability to specify the
+ * hint for this choice externally, on the per-connection basis.
+ *
+ * @since 3.3
+ */
+public interface HintedOrthogonalRouter extends OrthogonalRouter {
+
+ /**
+ * Specifies 2 possible strategy for the routing of the first / last link segment.
+ * <ul>
+ * <li> {@link EndRoutingHint#FixBendpointMoveAnchor} is default behavior.
+ * It will route the segment from bendpoint to some point different to anchor</li>
+ * <li> {@link EndRoutingHint#FixAnchorMoveBendpoint} will route from anchor
+ * to some point different to bendpoint</li>
+ * </ul>
+ */
+ public static enum EndRoutingHint {
+ FixAnchorMoveBendpoint, //
+ FixBendpointMoveAnchor;
+ }
+
+ /**
+ * Returns default strategy for this router which will be used when hint is not specified.
+ */
+ public EndRoutingHint getDefaultEndRoutingStrategy();
+
+ /**
+ * Asks router to use specified strategy for given connection.
+ * @param hint desired hint or <code>null</code> to revert connection back to default strategy.
+ */
+ public void setEndRoutingHint(Connection conn, EndRoutingHint hint);
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFConnectionLayer.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFConnectionLayer.java
new file mode 100644
index 0000000..dd699c1
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFConnectionLayer.java
@@ -0,0 +1,31 @@
+/*****************************************************************************
+ * Copyright (c) 2014-15 CEA LIST, Montages AG 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:
+ * Michael Golubev (Montages) - Initial API and implementation
+ *
+ *****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.draw2d.ConnectionRouter;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersConnectionLayer;
+
+/**
+ * Replaces rectilinear router with custom {@link LinkLFRectilinearRouter}
+ * implementation.
+ *
+ * @since 3.3
+ */
+public class LinkLFConnectionLayer extends CustomRoutersConnectionLayer {
+
+ @Override
+ protected ConnectionRouter createRectilinearRouter() {
+ return new LinkLFRectilinearRouter();
+ }
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPart.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPart.java
new file mode 100644
index 0000000..c2718f4
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPart.java
@@ -0,0 +1,37 @@
+/*****************************************************************************
+ * Copyright (c) 2014-15 CEA LIST, Montages AG 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:
+ * Michael Golubev (Montages) - Initial API and implementation
+ *
+ *****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.gmf.runtime.notation.MeasurementUnit;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersConnectionLayer;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersDiagramRootEditPart;
+
+/**
+ * Implementation of the {@link CustomRoutersDiagramRootEditPart} which installs
+ * customized {@link LinkLFRectilinearRouter} as a rectilinear router for given
+ * diagram.
+ *
+ * @since 3.3
+ */
+public class LinkLFDiagramRootEditPart extends CustomRoutersDiagramRootEditPart {
+
+ public LinkLFDiagramRootEditPart(MeasurementUnit mUnit) {
+ super(mUnit);
+ }
+
+ @Override
+ protected CustomRoutersConnectionLayer createConnectionLayer() {
+ return new LinkLFConnectionLayer();
+ }
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPartProvider.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPartProvider.java
new file mode 100644
index 0000000..d190c17
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFDiagramRootEditPartProvider.java
@@ -0,0 +1,37 @@
+/*****************************************************************************
+ * Copyright (c) 2014-15 CEA LIST, Montages AG 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:
+ * Michael Golubev (Montages) - Initial API and implementation
+ *
+ *****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.gmf.runtime.notation.Diagram;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersDiagramRootEditPart;
+import org.eclipse.gmf.tooling.runtime.providers.router.CustomRoutersDiagramRootEditPartProvider;
+
+/**
+ * LinkLF specific implementation of the
+ * {@link CustomRoutersDiagramRootEditPart}.
+ * <p/>
+ * This provider will install LinkLF-specific {@link LinkLFConnectionLayer}
+ * which in turn will use the customized {@link LinkLFRectilinearRouter} for
+ * applicable diagram.
+ *
+ * @since 3.3
+ */
+public class LinkLFDiagramRootEditPartProvider extends
+ CustomRoutersDiagramRootEditPartProvider {
+
+ @Override
+ public CustomRoutersDiagramRootEditPart createRootEditPart(Diagram diagram) {
+ return new LinkLFDiagramRootEditPart(diagram.getMeasurementUnit());
+ }
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFRectilinearRouter.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFRectilinearRouter.java
new file mode 100644
index 0000000..c8dace9
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/LinkLFRectilinearRouter.java
@@ -0,0 +1,519 @@
+package org.eclipse.gmf.tooling.runtime.linklf.router;
+
+import java.util.IdentityHashMap;
+import java.util.Map;
+
+import org.eclipse.draw2d.Connection;
+import org.eclipse.draw2d.ConnectionAnchor;
+import org.eclipse.draw2d.IFigure;
+import org.eclipse.draw2d.PositionConstants;
+import org.eclipse.draw2d.geometry.Dimension;
+import org.eclipse.draw2d.geometry.Point;
+import org.eclipse.draw2d.geometry.PointList;
+import org.eclipse.draw2d.geometry.PrecisionPoint;
+import org.eclipse.draw2d.geometry.PrecisionRectangle;
+import org.eclipse.gef.EditPartViewer;
+import org.eclipse.gef.GraphicalEditPart;
+import org.eclipse.gef.SnapToGrid;
+import org.eclipse.gmf.runtime.draw2d.ui.figures.BaseSlidableAnchor;
+import org.eclipse.gmf.runtime.draw2d.ui.figures.FigureUtilities;
+import org.eclipse.gmf.runtime.draw2d.ui.figures.OrthogonalConnectionAnchor;
+import org.eclipse.gmf.runtime.draw2d.ui.geometry.LineSeg;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.OrthogonalRouterUtilities;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter;
+import org.eclipse.gmf.tooling.runtime.linklf.DiagramGridSpec;
+import org.eclipse.gmf.tooling.runtime.linklf.SlidableSnapToGridAnchor;
+import org.eclipse.gmf.tooling.runtime.providers.router.SnapToGridRouter;
+
+/**
+ * Extends the standard GMF Runtime rectilinear router with following
+ * improvements:
+ * <ul>
+ * <li>respects Snap to grid for the implicit bendpoints introduced when
+ * switching from oblique to recti- routing</li>
+ * <li>enforces that the last / first segment is always orthogonal to the node
+ * side, avoiding the visual anchor position different to the user-set anchor
+ * position. The latter happens with default RectilinearRouter when first or
+ * last segment is parallel to (and in fact hidden by) the node side</li>
+ * <li>routes the segments from/to affixed items orthogonal to the side of the
+ * parent node to which affixed node is attached</li>
+ * </ul>
+ *
+ * @since 3.3
+ */
+public class LinkLFRectilinearRouter extends RectilinearRouter2 implements
+ HintedOrthogonalRouter, SnapToGridRouter {
+
+ private final Map<Connection, EndRoutingHint> myEndRoutingHints = new IdentityHashMap<Connection, EndRoutingHint>();
+
+ private DiagramGridSpec myGridSpec;
+
+ public LinkLFRectilinearRouter() {
+ super();
+ }
+
+ public EndRoutingHint getDefaultEndRoutingStrategy() {
+ return EndRoutingHint.FixAnchorMoveBendpoint;
+ }
+
+ @Override
+ public void setEditPartViewer(EditPartViewer viewer) {
+ if (myGridSpec != null && myGridSpec.getViewer() == viewer) {
+ return;
+ }
+ if (myGridSpec != null) {
+ myGridSpec.dispose();
+ }
+ myGridSpec = viewer == null ? null : new DiagramGridSpec(viewer);
+ }
+
+ @Override
+ public void setEndRoutingHint(Connection conn, EndRoutingHint hint) {
+ if (hint == getDefaultEndRoutingStrategy()) {
+ hint = null;
+ }
+ if (hint == null) {
+ myEndRoutingHints.remove(conn);
+ } else {
+ myEndRoutingHints.put(conn, hint);
+ }
+ }
+
+ /**
+ * While {@link RectilinearRouter} claims to ignore the endpoints, it still
+ * passes them into the
+ * {@link #removePointsInViews(Connection, PointList, Point, Point)} method.
+ *
+ * This leads to 2 problems: - the one described in the bug #451604 - even
+ * if we fix the bug above by providing more hints, this added middle point
+ * is not snapped when should be
+ * <p/>
+ *
+ * So this class ensures that the endpoints are reset to anchor positions
+ * befor routing
+ */
+ @Override
+ public void routeLine(Connection conn, int nestedRoutingDepth,
+ PointList newLine) {
+ if (nestedRoutingDepth == 0 && newLine.size() >= 2) {
+ Point sourceLoc = conn.getSourceAnchor().getReferencePoint();
+ Point targetLoc = conn.getTargetAnchor().getReferencePoint();
+
+ conn.translateToRelative(sourceLoc);
+ conn.translateToRelative(targetLoc);
+
+ newLine.setPoint(sourceLoc, 0);
+ newLine.setPoint(targetLoc, newLine.size() - 1);
+ }
+ super.routeLine(conn, nestedRoutingDepth, newLine);
+ }
+
+ /**
+ * @see org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter#resetEndPointsToEdge(org.eclipse.draw2d.Connection,
+ * org.eclipse.draw2d.geometry.PointList)
+ *
+ * @param conn
+ * @param line
+ */
+ @Override
+ protected void resetEndPointsToEdge(Connection conn, PointList line) {
+ if (isReorienting(conn)) {
+ /*
+ * If the connection doesn't have a shape as a source or target
+ * we'll let the oblique router to do the work. The connection
+ * doesn't need to be rectilinear at this point. There is no support
+ * for making a rectilinear connection for which one of the ends is
+ * not connected to anything.
+ */
+ super.resetEndPointsToEdge(conn, line);
+ return;
+ }
+ PrecisionRectangle source = sourceBoundsRelativeToConnection2(conn);
+ PrecisionRectangle target = targetBoundsRelativeToConnection2(conn);
+ int offSourceDirection = PositionConstants.NONE;
+ int offTargetDirection = PositionConstants.NONE;
+ int sourceAnchorRelativeLocation = PositionConstants.NONE;
+ int targetAnchorRelativeLocation = PositionConstants.NONE;
+
+ SnapToGrid snapper = getSnapper();
+
+ if (line.size() == 0) {
+ /*
+ * No valid bend points. We can't call super, so we will do the work
+ * from RouterHelper ourselves
+ */
+ PrecisionPoint sourceReference = new PrecisionPoint(conn
+ .getTargetAnchor().getReferencePoint());
+ PrecisionPoint sourceAnchorPoint = new PrecisionPoint(conn
+ .getSourceAnchor().getLocation(sourceReference));
+ PrecisionPoint targetAnchorPoint = new PrecisionPoint(conn
+ .getTargetAnchor().getLocation(sourceAnchorPoint));
+ conn.translateToRelative(sourceAnchorPoint);
+ conn.translateToRelative(targetAnchorPoint);
+
+ line.addPoint(sourceAnchorPoint);
+ line.addPoint(targetAnchorPoint);
+
+ sourceAnchorRelativeLocation = getAnchorOffRectangleDirection2(
+ sourceAnchorPoint, source);
+ targetAnchorRelativeLocation = getAnchorOffRectangleDirection2(
+ targetAnchorPoint, target);
+
+ insertPointsProducingNotAlignedRectilinearSegments(conn, line,
+ sourceAnchorRelativeLocation, targetAnchorRelativeLocation,
+ snapper);
+
+ offSourceDirection = getOffShapeDirection2(sourceAnchorRelativeLocation);
+ offTargetDirection = getOffShapeDirection2(targetAnchorRelativeLocation);
+ } else {
+ if (conn.getSourceAnchor() instanceof OrthogonalConnectionAnchor) {
+ addSegmentToSourceAnchor(line, conn,
+ (OrthogonalConnectionAnchor) conn.getSourceAnchor());
+ } else {
+ /*
+ * If anchor is not supporting orthogonal connections we'll use
+ * the oblique connection anchors and then convert it to
+ * rectilinear.
+ */
+ PrecisionPoint reference = new PrecisionPoint(
+ line.getFirstPoint());
+ conn.getSourceAnchor().getOwner()
+ .translateToAbsolute(reference);
+ PrecisionPoint anchorLocation = new PrecisionPoint(conn
+ .getSourceAnchor().getLocation(reference));
+ conn.translateToRelative(anchorLocation);
+ line.insertPoint(anchorLocation, 0);
+ }
+ if (conn.getTargetAnchor() instanceof OrthogonalConnectionAnchor) {
+ addSegmentToTargetAnchor(line, conn,
+ (OrthogonalConnectionAnchor) conn.getTargetAnchor());
+ } else {
+ /*
+ * If anchor is not supporting orthogonal connections we'll use
+ * the oblique connection anchors and then convert it to
+ * rectilinear.
+ */
+ PrecisionPoint reference = new PrecisionPoint(
+ line.getLastPoint());
+ conn.getSourceAnchor().getOwner()
+ .translateToAbsolute(reference);
+ PrecisionPoint anchorLocation = new PrecisionPoint(conn
+ .getTargetAnchor().getLocation(reference));
+ conn.translateToRelative(anchorLocation);
+ line.addPoint(anchorLocation);
+ }
+ sourceAnchorRelativeLocation = getAnchorOffRectangleDirection2(
+ line.getFirstPoint(), source);
+ offSourceDirection = getOffShapeDirection2(sourceAnchorRelativeLocation);
+ targetAnchorRelativeLocation = getAnchorOffRectangleDirection2(
+ line.getLastPoint(), target);
+ offTargetDirection = getOffShapeDirection2(targetAnchorRelativeLocation);
+ }
+
+ /*
+ * Convert the polyline to rectilinear. If the connection is rectilinear
+ * already then the connection will remain as it is.
+ */
+ OrthogonalRouterUtilities.transformToOrthogonalPointList(line,
+ offSourceDirection, offTargetDirection);
+ removeRedundantPoints(line);
+ }
+
+ /**
+ * We need to find two points offset from the source and target anchors
+ * outside the shapes such that when the polyline is converted to
+ * rectilinear from oblique we won't have rectilinear line segments alligned
+ * with source or target shapes edges.
+ * <p/>
+ * Copy-pasted from {@link RectilinearRouter} lines 416.
+ */
+ @Deprecated
+ public static void insertPointsProducingNotAlignedRectilinearSegments(
+ PointList line, int sourceAnchorRelativeLocation,
+ int targetAnchorRelativeLocation) {
+ Point offStart = line.getFirstPoint();
+ Point offEnd = line.getLastPoint();
+ Dimension offsetDim = offStart.getDifference(offEnd).scale(0.5);
+ offStart.translate(getTranslationValue2(sourceAnchorRelativeLocation,
+ Math.abs(offsetDim.width), Math.abs(offsetDim.height)));
+ offEnd.translate(getTranslationValue2(targetAnchorRelativeLocation,
+ Math.abs(offsetDim.width), Math.abs(offsetDim.height)));
+ line.insertPoint(offStart, 1);
+ line.insertPoint(offEnd, 2);
+ }
+
+ public static void insertPointsProducingNotAlignedRectilinearSegments(
+ Connection conn, PointList line, int sourceAnchorRelativeLocation,
+ int targetAnchorRelativeLocation, SnapToGrid snapper) {
+ insertPointsProducingNotAlignedRectilinearSegments(line,
+ sourceAnchorRelativeLocation, targetAnchorRelativeLocation);
+ if (asVerticalOrHorizontal(sourceAnchorRelativeLocation) != asVerticalOrHorizontal(targetAnchorRelativeLocation)) {
+ // for "|_"-like routing we don't need snapping, the added bendpoint
+ // is determined fully by anchors
+ return;
+ }
+ // so we are in "_|~"-like routing
+ if (snapper != null) {
+ PrecisionPoint addedForSource = new PrecisionPoint(line.getPoint(1));
+ PrecisionPoint addedForSourceAbs = makeAbsolute(conn,
+ addedForSource.getPreciseCopy());
+ PrecisionPoint snappedForSourceAbs = addedForSourceAbs
+ .getPreciseCopy();
+
+ PrecisionPoint addedForTarget = new PrecisionPoint(line.getPoint(2));
+ PrecisionPoint addedForTargetAbs = makeAbsolute(conn,
+ addedForTarget.getPreciseCopy());
+ PrecisionPoint snappedForTargetAbs = addedForTargetAbs
+ .getPreciseCopy();
+
+ snapper.snapPoint(null,
+ asVerticalOrHorizontal(sourceAnchorRelativeLocation),
+ addedForSourceAbs, snappedForSourceAbs);
+ snapper.snapPoint(null,
+ asVerticalOrHorizontal(targetAnchorRelativeLocation),
+ addedForTargetAbs, snappedForTargetAbs);
+
+ PrecisionPoint snappedForSource = makeRelative(conn,
+ snappedForSourceAbs.getPreciseCopy());
+ PrecisionPoint snappedForTarget = makeRelative(conn,
+ snappedForTargetAbs.getPreciseCopy());
+
+ if (snappedForSource.getDistance(addedForSource) <= snappedForTarget
+ .getDistance(addedForTarget)) {
+ Dimension delta = snappedForSource
+ .getDifference(addedForSource);
+ line.setPoint(snappedForSource, 1);
+ line.setPoint(addedForTarget.getTranslated(delta), 2);
+ } else {
+ Dimension delta = snappedForTarget
+ .getDifference(addedForTarget);
+ line.setPoint(addedForSource.getTranslated(delta), 1);
+ line.setPoint(snappedForTarget, 2);
+ }
+ }
+ }
+
+ public static int asVerticalOrHorizontal(int direction) {
+ return getOffShapeDirection2(direction);
+ }
+
+ protected SnapToGrid getSnapper() {
+ if (myGridSpec == null || myGridSpec.getAbsoluteGridSpec() == null) {
+ return null;
+ }
+ return new SnapToGrid((GraphicalEditPart) myGridSpec.getViewer()
+ .getContents());
+ }
+
+ protected void addSegmentToSourceAnchor(PointList line, Connection conn,
+ OrthogonalConnectionAnchor anchor) {
+ Point firstBend = line.getFirstPoint();
+ int prevSegmentOrientation = line.size() < 2 ? PositionConstants.NONE
+ : getSegmentOrientation(firstBend, line.getPoint(1));
+ Point[] prependSegment = computeFirstOrLastSegment(conn, anchor,
+ firstBend, prevSegmentOrientation);
+ for (int i = 0; i < prependSegment.length; i++) {
+ Point next = prependSegment[i];
+ if (i == prependSegment.length - 1 && next.equals(firstBend)) {
+ continue; // actually break
+ }
+ line.insertPoint(next, i);
+ }
+ }
+
+ protected void addSegmentToTargetAnchor(PointList line, Connection conn,
+ OrthogonalConnectionAnchor anchor) {
+ Point lastBend = line.getLastPoint();
+ int prevSegmentOrientation = line.size() < 2 ? PositionConstants.NONE
+ : getSegmentOrientation(lastBend,
+ line.getPoint(line.size() - 2));
+ Point[] appendSegment = computeFirstOrLastSegment(conn, anchor,
+ lastBend, prevSegmentOrientation);
+ for (int i = appendSegment.length - 1; i >= 0; i--) {
+ Point next = appendSegment[i];
+ if (i == appendSegment.length - 1 && next.equals(lastBend)) {
+ continue;
+ }
+ line.addPoint(next);
+ }
+ }
+
+ /**
+ * Returns the array of point that represents the routing from the point at
+ * figure bounds to the given first or last bendpoint. Result array is
+ * ordered from the figure bounds, so the 0-th index always represent the
+ * point at figure.
+ */
+ protected Point[] computeFirstOrLastSegment(Connection conn,
+ OrthogonalConnectionAnchor anchor, Point bendpoint,
+ int prevSegmentOrientation) {
+ LineSeg fromFigureToBendpoint = OrthogonalRouterUtilities
+ .getOrthogonalLineSegToAnchorLoc(conn, anchor, bendpoint);
+ Point figurePoint = fromFigureToBendpoint.getOrigin();
+ Point syntheticBend = bendpoint.getCopy();
+ EndRoutingHint hint = findEndRoutingHint(conn, anchor);
+ if (hint == EndRoutingHint.FixAnchorMoveBendpoint) {
+ Point bendpointInAbsCoordinates = bendpoint.getCopy();
+ conn.translateToAbsolute(bendpointInAbsCoordinates);
+ int orientation = getOrientationAgainst(anchor,
+ bendpointInAbsCoordinates);
+ if (orientation == PositionConstants.NONE) {
+ orientation = getPreferredOutgoingDirection(conn, anchor);
+ }
+ if (orientation == PositionConstants.NONE) {
+ orientation = flipOrientation(prevSegmentOrientation);
+ }
+ Point refPoint = anchor.getReferencePoint().getCopy();
+ conn.translateToRelative(refPoint);
+ switch (orientation) {
+ case PositionConstants.VERTICAL:
+ syntheticBend.setX(refPoint.x());
+ break;
+ case PositionConstants.HORIZONTAL:
+ syntheticBend.setY(refPoint.y());
+ break;
+ default:
+ // Activator.log.error(new
+ // IllegalStateException("Unexpected orientation: " +
+ // orientation + //
+ // ", bendpoint-abs: " + bendpointInAnchorCoordinates +
+ // ", bendpoint-link-rel:: [" + bendpoint + "], " + //
+ // ", anchor: " + anchor.getReferencePoint()
+ // ));
+ break;
+ }
+ fromFigureToBendpoint = OrthogonalRouterUtilities
+ .getOrthogonalLineSegToAnchorLoc(conn, anchor,
+ syntheticBend);
+ figurePoint = fromFigureToBendpoint.getOrigin();
+ }
+
+ Point[] result = new Point[] { figurePoint, syntheticBend };
+ return result;
+ }
+
+ /**
+ * [GMFRT] extract to utility method
+ *
+ * This code is partial copy of the
+ * {@link BaseSlidableAnchor#getOrthogonalLocation(Point)}.
+ *
+ * @deprecated
+ */
+ private int getOrientationAgainst(ConnectionAnchor anchor,
+ Point orthoReference) {
+ // Note that <code>ownReference</code> is not used besides call the
+ // getClosestSide()
+ // so in contrast to the original code in BaseSlidableAnchor, we have
+ // commented out useless updates
+ PrecisionPoint ownReference = new PrecisionPoint(
+ anchor.getReferencePoint());
+ PrecisionRectangle bounds = new PrecisionRectangle(
+ FigureUtilities.getAnchorableFigureBounds(anchor.getOwner()));
+ anchor.getOwner().translateToAbsolute(bounds);
+ bounds.expand(0.000001, 0.000001);
+ PrecisionPoint preciseOrthoReference = new PrecisionPoint(
+ orthoReference);
+ int orientation = PositionConstants.NONE;
+ if (bounds.contains(preciseOrthoReference)) {
+ int side = SlidableSnapToGridAnchor.getClosestSide2(ownReference,
+ bounds);
+ switch (side) {
+ case PositionConstants.LEFT:
+ case PositionConstants.RIGHT:
+ // ownReference.preciseY = preciseOrthoReference.preciseY();
+ orientation = PositionConstants.HORIZONTAL;
+ break;
+ case PositionConstants.TOP:
+ case PositionConstants.BOTTOM:
+ // ownReference.preciseX = preciseOrthoReference.preciseX();
+ orientation = PositionConstants.VERTICAL;
+ break;
+ }
+ } else if (preciseOrthoReference.preciseX >= bounds.preciseX
+ && preciseOrthoReference.preciseX <= bounds.preciseX
+ + bounds.preciseWidth) {
+ // ownReference.preciseX = preciseOrthoReference.preciseX;
+ orientation = PositionConstants.VERTICAL;
+ } else if (preciseOrthoReference.preciseY >= bounds.preciseY
+ && preciseOrthoReference.preciseY <= bounds.preciseY
+ + bounds.preciseHeight) {
+ // ownReference.preciseY = preciseOrthoReference.preciseY;
+ orientation = PositionConstants.HORIZONTAL;
+ }
+ // ownReference.updateInts();
+ return orientation;
+ }
+
+ protected EndRoutingHint findEndRoutingHint(Connection conn,
+ OrthogonalConnectionAnchor anchor) {
+ EndRoutingHint result = myEndRoutingHints.get(conn);
+ if (result == null) {
+ result = getDefaultEndRoutingStrategy();
+ }
+ return result;
+ }
+
+ protected static int flipOrientation(int verticalOrHorizontal) {
+ switch (verticalOrHorizontal) {
+ case PositionConstants.VERTICAL:
+ return PositionConstants.HORIZONTAL;
+ case PositionConstants.HORIZONTAL:
+ return PositionConstants.VERTICAL;
+ default:
+ return PositionConstants.NONE;
+ }
+ }
+
+ protected static int getSegmentOrientation(Point start, Point end) {
+ if (start.x() == end.x()) {
+ return PositionConstants.VERTICAL;
+ }
+ if (start.y() == end.y()) {
+ return PositionConstants.HORIZONTAL;
+ }
+ return PositionConstants.NONE;
+ }
+
+ /**
+ * When anchor is placed directly at the anchorable bound of its owner, this
+ * method returns the direction orthogonal to the side this anchor is
+ * attached to. Returns {@link PositionConstants#NONE} in all other cases.
+ *
+ * @return {@link PositionConstants#HORIZONTAL} or
+ * {@link PositionConstants#VERTICAL} or
+ * {@link PositionConstants#NONE}
+ */
+ protected int getPreferredOutgoingDirection(Connection conn,
+ ConnectionAnchor anchor) {
+ final double TOLERANCE = 2;
+
+ IFigure owner = anchor.getOwner();
+ if (owner == null) {
+ return PositionConstants.NONE;
+ }
+ PrecisionRectangle bounds = new PrecisionRectangle(
+ FigureUtilities.getAnchorableFigureBounds(owner));
+ owner.translateToAbsolute(bounds);
+ conn.translateToRelative(bounds);
+
+ PrecisionPoint anchorLoc = new PrecisionPoint(
+ anchor.getReferencePoint());
+ conn.translateToRelative(anchorLoc);
+
+ if (Math.abs(anchorLoc.preciseX() - bounds.preciseX()) < TOLERANCE) {
+ return PositionConstants.HORIZONTAL; // .WEST
+ }
+ if (Math.abs(anchorLoc.preciseX() - bounds.preciseRight()) < TOLERANCE) {
+ return PositionConstants.HORIZONTAL; // .EAST
+ }
+ if (Math.abs(anchorLoc.preciseY() - bounds.preciseY()) < TOLERANCE) {
+ return PositionConstants.VERTICAL; // .NORTH
+ }
+ if (Math.abs(anchorLoc.preciseY() - bounds.preciseBottom()) < TOLERANCE) {
+ return PositionConstants.VERTICAL; // .SOUTH
+ }
+ return PositionConstants.NONE;
+ }
+
+}
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/RectilinearRouter2.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/RectilinearRouter2.java
new file mode 100644
index 0000000..638f249
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/router/RectilinearRouter2.java
@@ -0,0 +1,262 @@
+/******************************************************************************
+ * Copyright (c) 2002, 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.gmf.tooling.runtime.linklf.router;
+
+import org.eclipse.draw2d.Connection;
+import org.eclipse.draw2d.IFigure;
+import org.eclipse.draw2d.PositionConstants;
+import org.eclipse.draw2d.geometry.Dimension;
+import org.eclipse.draw2d.geometry.Point;
+import org.eclipse.draw2d.geometry.PointList;
+import org.eclipse.draw2d.geometry.PrecisionRectangle;
+import org.eclipse.draw2d.geometry.Rectangle;
+import org.eclipse.draw2d.geometry.Translatable;
+import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.RectilinearRouter;
+
+/**
+ * Right now extending {@link RectilinearRouter} does not make sense, because
+ * all of the methods of interest are private there.
+ * <p/>
+ * We, however, have extracted this class to at least formally note the copy
+ * paste and distinguish it from copy-paste-with-modifications which are
+ * intended to be placed into subclasses.
+ * <p/>
+ * All of the methods in this class are just the copy-pasted versions of their
+ * private counterparts in {@link RectilinearRouter}, without any changes.
+ * <p>
+ * This class is intentionally made package local, and will be removed once the
+ * respective patch for bug 331779 is merged.
+ *
+ * @see https://bugs.eclipse.org/bugs/show_bug.cgi?id=331779
+ * @since 3.3
+ */
+class RectilinearRouter2 extends RectilinearRouter {
+
+ /**
+ * [GMFRT] make protected in {@link RectilinearRouter}
+ * <p/>
+ * Iterates through points of a polyline and does the following: if 3 points
+ * lie on the same line the middle point is removed
+ *
+ * @param line
+ * polyline's points
+ */
+ @Deprecated
+ public static boolean removeRedundantPoints2(PointList line) {
+ int initialNumberOfPoints = line.size();
+ if (line.size() > 2) {
+ PointList newLine = new PointList(line.size());
+ newLine.addPoint(line.removePoint(0));
+ while (line.size() >= 2) {
+ Point p0 = newLine.getLastPoint();
+ Point p1 = line.getPoint(0);
+ Point p2 = line.getPoint(1);
+ if (p0.x == p1.x && p0.x == p2.x) {
+ // Have two vertical segments in a row
+ // get rid of the point between
+ line.removePoint(0);
+ } else if (p0.y == p1.y && p0.y == p2.y) {
+ // Have two horizontal segments in a row
+ // get rid of the point between
+ line.removePoint(0);
+ } else {
+ newLine.addPoint(line.removePoint(0));
+ }
+ }
+ while (line.size() > 0) {
+ newLine.addPoint(line.removePoint(0));
+ }
+ line.removeAllPoints();
+ line.addAll(newLine);
+ }
+ return line.size() != initialNumberOfPoints;
+ }
+
+ /**
+ * [GMFRT] make protected in {@link RectilinearRouter}
+ * <p/>
+ * Calculates geographic position of a point located outside the given
+ * rectangle relative to the rectangle
+ *
+ * @param p
+ * point outside of rectangle
+ * @param r
+ * the rectangle
+ * @return geographic position of the point relative to the recatangle
+ */
+ @Deprecated
+ protected static int getOutisePointOffRectanglePosition2(Point p,
+ Rectangle r) {
+ int position = PositionConstants.NONE;
+ if (r.x > p.x) {
+ position |= PositionConstants.WEST;
+ } else if (r.x + r.width < p.x) {
+ position |= PositionConstants.EAST;
+ }
+ if (r.y > p.y) {
+ position |= PositionConstants.NORTH;
+ } else if (r.y + r.height < p.y) {
+ position |= PositionConstants.SOUTH;
+ }
+ return position;
+ }
+
+ /**
+ * [GMFRT] make protected in {@link RectilinearRouter}
+ * <p/>
+ * Determines whether the rectilinear line segment coming out of the shape
+ * should be horizontal or vertical based on the anchor geographic position
+ * relative to the shape
+ *
+ * @param anchorRelativeLocation
+ * @return
+ */
+ @Deprecated
+ protected static int getOffShapeDirection2(int anchorRelativeLocation) {
+ if (anchorRelativeLocation == PositionConstants.EAST
+ || anchorRelativeLocation == PositionConstants.WEST) {
+ return PositionConstants.HORIZONTAL;
+ } else if (anchorRelativeLocation == PositionConstants.NORTH
+ || anchorRelativeLocation == PositionConstants.SOUTH) {
+ return PositionConstants.VERTICAL;
+ }
+ return PositionConstants.NONE;
+ }
+
+ /**
+ * [GMFRT] make protected in {@link RectilinearRouter}
+ * <p/>
+ * Returns a translation dimension for the anchor point. Translation
+ * dimension translates the anchor point off the shape. The off shape
+ * direction is specified by the relative to the shape geographic position
+ * of the anchor
+ *
+ * @param position
+ * relative to the shape geographic position of the anchor
+ * @param xFactorValue
+ * translation value along x-axis
+ * @param yFactorValue
+ * translation value along y-axis
+ * @return
+ */
+ @Deprecated
+ protected static Dimension getTranslationValue2(int position,
+ int xFactorValue, int yFactorValue) {
+ Dimension translationDimension = new Dimension();
+ if (position == PositionConstants.EAST) {
+ translationDimension.width = xFactorValue;
+ } else if (position == PositionConstants.SOUTH) {
+ translationDimension.height = yFactorValue;
+ } else if (position == PositionConstants.WEST) {
+ translationDimension.width = -xFactorValue;
+ } else if (position == PositionConstants.NORTH) {
+ translationDimension.height = -yFactorValue;
+ }
+ return translationDimension;
+ }
+
+ @Override
+ public void routeLine(Connection conn, int nestedRoutingDepth,
+ PointList newLine) {
+ super.routeLine(conn, nestedRoutingDepth, newLine);
+ }
+
+ /**
+ * [GMFRT] make protected in {@link RectilinearRouter}
+ * <p/>
+ * Source bounding rectangle relative to connection figure coordinates
+ *
+ * @param conn
+ * connection
+ * @return <code>PrecisionRectangle</code> source bounds relative to
+ * connection's coordinate system
+ */
+ @Deprecated
+ protected PrecisionRectangle sourceBoundsRelativeToConnection2(
+ Connection conn) {
+ PrecisionRectangle source = new PrecisionRectangle(conn
+ .getSourceAnchor().getOwner().getBounds());
+ conn.getSourceAnchor().getOwner().translateToAbsolute(source);
+ conn.translateToRelative(source);
+ return source;
+ }
+
+ /**
+ * [GMFRT] make protected in {@link RectilinearRouter}
+ * <p/>
+ * Target bounding rectangle relative to connection figure coordinates
+ *
+ * @param conn
+ * connection
+ * @return <code>PrecisionRectangle</code> target bounds relative to
+ * connection's coordinate system
+ */
+ @Deprecated
+ protected PrecisionRectangle targetBoundsRelativeToConnection2(
+ Connection conn) {
+ PrecisionRectangle target = new PrecisionRectangle(conn
+ .getTargetAnchor().getOwner().getBounds());
+ conn.getTargetAnchor().getOwner().translateToAbsolute(target);
+ conn.translateToRelative(target);
+ return target;
+ }
+
+ /**
+ * [GMFRT] make protected in {@link RectilinearRouter}
+ * <p/>
+ * Determines the relative to rectangle geographic location of a point.
+ * Example: If shape is closer to the the top edge of the rectangle location
+ * would be north. Method used to determine which side of shape's bounding
+ * rectangle is closer to connection's anchor point. All geometric
+ * quantities must be in the same coordinate system.
+ *
+ * @param anchorPoint
+ * location of the anchor point
+ * @param rect
+ * bounding rectangle of the shape
+ * @return
+ */
+ @Deprecated
+ protected int getAnchorOffRectangleDirection2(Point anchorPoint,
+ Rectangle rect) {
+ int position = PositionConstants.NORTH;
+ int criteriaValue = Math.abs(anchorPoint.y - rect.y);
+ int tempCriteria = Math.abs(anchorPoint.y - rect.y - rect.height);
+ if (tempCriteria < criteriaValue) {
+ criteriaValue = tempCriteria;
+ position = PositionConstants.SOUTH;
+ }
+
+ tempCriteria = Math.abs(anchorPoint.x - rect.x);
+ if (tempCriteria < criteriaValue) {
+ criteriaValue = tempCriteria;
+ position = PositionConstants.WEST;
+ }
+
+ tempCriteria = Math.abs(anchorPoint.x - rect.x - rect.width);
+ if (tempCriteria < criteriaValue) {
+ criteriaValue = tempCriteria;
+ position = PositionConstants.EAST;
+ }
+
+ return position;
+ }
+
+ protected static <T extends Translatable> T makeRelative(IFigure figure, T t) {
+ figure.translateToRelative(t);
+ return t;
+ }
+
+ protected static <T extends Translatable> T makeAbsolute(IFigure figure, T t) {
+ figure.translateToAbsolute(t);
+ return t;
+ }
+}
\ No newline at end of file