[442157] LinkLF routing: Editpolicy to fix anchor positions on resizing
backported from Papyrus LinkLF to be used in pure GMF-T diagrams

Change-Id: I0be5b9fc67617593e2d57efcc885fe7118b4d1c9
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/META-INF/MANIFEST.MF b/plugins/org.eclipse.gmf.tooling.runtime/META-INF/MANIFEST.MF
index b78213a..3553e16 100644
--- a/plugins/org.eclipse.gmf.tooling.runtime/META-INF/MANIFEST.MF
+++ b/plugins/org.eclipse.gmf.tooling.runtime/META-INF/MANIFEST.MF
@@ -40,6 +40,7 @@
  org.eclipse.gmf.tooling.runtime.linklf.labels,
  org.eclipse.gmf.tooling.runtime.linklf.policies,
  org.eclipse.gmf.tooling.runtime.linklf.router,
+ org.eclipse.gmf.tooling.runtime.linklf.xylayout,
  org.eclipse.gmf.tooling.runtime.ocl.tracker,
  org.eclipse.gmf.tooling.runtime.parsers,
  org.eclipse.gmf.tooling.runtime.part,
diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/xylayout/FixAnchorHelper.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/xylayout/FixAnchorHelper.java
new file mode 100644
index 0000000..f806387
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/xylayout/FixAnchorHelper.java
@@ -0,0 +1,406 @@
+/*****************************************************************************

+ * Copyright (c) 2014 CEA LIST.

+ *

+ *    

+ * 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:

+ *  Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Initial API and implementation

+ *

+ *****************************************************************************/

+package org.eclipse.gmf.tooling.runtime.linklf.xylayout;

+

+import org.eclipse.core.commands.ExecutionException;

+import org.eclipse.core.runtime.Assert;

+import org.eclipse.core.runtime.IAdaptable;

+import org.eclipse.core.runtime.IProgressMonitor;

+import org.eclipse.draw2d.AbstractPointListShape;

+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.draw2d.geometry.Rectangle;

+import org.eclipse.emf.transaction.TransactionalEditingDomain;

+import org.eclipse.gef.commands.Command;

+import org.eclipse.gef.commands.CompoundCommand;

+import org.eclipse.gef.editparts.AbstractConnectionEditPart;

+import org.eclipse.gmf.runtime.common.core.command.CommandResult;

+import org.eclipse.gmf.runtime.common.core.command.ICommand;

+import org.eclipse.gmf.runtime.diagram.ui.commands.ICommandProxy;

+import org.eclipse.gmf.runtime.diagram.ui.editparts.INodeEditPart;

+import org.eclipse.gmf.runtime.draw2d.ui.figures.PolylineConnectionEx;

+import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;

+import org.eclipse.gmf.runtime.gef.ui.figures.NodeFigure;

+import org.eclipse.gmf.runtime.notation.Edge;

+import org.eclipse.gmf.runtime.notation.IdentityAnchor;

+import org.eclipse.gmf.runtime.notation.NotationFactory;

+import org.eclipse.gmf.runtime.notation.View;

+

+/**

+ * This class provides command to fix the anchors during a resize. The methods

+ * are not static to allow to overriding.

+ * <p/>

+ * 

+ * This class is originally authored by Papyrus project.

+ * 

+ * @since 3.3

+ */

+public class FixAnchorHelper {

+

+	/**

+	 * the editing domain used for the command

+	 */

+	protected final TransactionalEditingDomain domain;

+

+	/**

+	 * 

+	 * Constructor.

+	 * 

+	 * @param domain

+	 *            the editing domain to use, it must not be <code>null</code>

+	 */

+	public FixAnchorHelper(final TransactionalEditingDomain domain) {

+		Assert.isNotNull(domain);

+		this.domain = domain;

+	}

+

+	/**

+	 * 

+	 * @param node

+	 *            the resized node

+	 * @param move

+	 *            the move direction

+	 * @param sizeDelta

+	 *            the delta of the size

+	 * @param moveDirection

+	 *            the direction for the resize/move

+	 * @return the command to fix the anchor or <code>null</code> if we can't

+	 *         fix it

+	 */

+	public Command getFixIdentityAnchorCommand(final INodeEditPart node,

+			final Point move, final Dimension sizeDelta, int moveDirection) {

+		final CompoundCommand cc = new CompoundCommand(

+				"Fix All Anchors Command"); //$NON-NLS-1$

+		// 1. we calculate the new bounds;

+		final IFigure nodeFigure = node.getFigure();

+		final PrecisionRectangle oldBounds = new PrecisionRectangle(

+				nodeFigure.getBounds());

+

+		// we translate the coordinates to absolute

+		nodeFigure.translateToAbsolute(oldBounds);

+		final PrecisionRectangle newBounds = oldBounds.getPreciseCopy();

+		newBounds.translate(move.preciseX(), move.preciseY());

+		newBounds.resize(sizeDelta.preciseWidth(), sizeDelta.preciseHeight());

+

+		// 2. we iterate on the target anchor

+		for (final Object targetConnection : node.getTargetConnections()) {

+			if (targetConnection instanceof AbstractConnectionEditPart) {

+				final Command tmp = getFixAnchorCommand(node, oldBounds,

+						newBounds,

+						(AbstractConnectionEditPart) targetConnection, move,

+						sizeDelta, false);

+				if (tmp != null) {

+					cc.add(tmp);

+				}

+			}

+		}

+

+		// . we iterate on the source anchor

+		for (final Object sourceConnection : node.getSourceConnections()) {

+			if (sourceConnection instanceof AbstractConnectionEditPart) {

+				final Command tmp = getFixAnchorCommand(node, oldBounds,

+						newBounds,

+						(AbstractConnectionEditPart) sourceConnection, move,

+						sizeDelta, true);

+				if (tmp != null) {

+					cc.add(tmp);

+				}

+			}

+		}

+

+		if (cc.isEmpty()) {

+			return null;

+		}

+		return cc;

+	}

+

+	/**

+	 * 

+	 * @param edgeEP

+	 *            the edge edit part

+	 * @param sourcePoint

+	 *            if <code>true</code> we return the source point and if false

+	 *            we return the end point

+	 * @return the real point to fix

+	 */

+	protected Point getRealAnchorPoint(final AbstractConnectionEditPart edgeEP,

+			final boolean sourcePoint) {

+		final IFigure figure = edgeEP.getFigure();

+		Point point = null;

+		if (figure instanceof AbstractPointListShape) {

+			if (sourcePoint) {

+				point = ((AbstractPointListShape) figure).getStart().getCopy();

+			} else {

+				point = ((AbstractPointListShape) figure).getEnd().getCopy();

+			}

+		}

+		figure.translateToAbsolute(point);

+		return point;

+	}

+

+	/**

+	 * 

+	 * @param edgeEP

+	 *            the edge edit part

+	 * @param sourcePoint

+	 *            if <code>true</code> we return the source point and if false

+	 *            we return the end point

+	 * @param nodeEP

+	 *            the node edit part

+	 * @param p

+	 *            the real anchor point in absolute coordinate

+	 * @return the anchor representing the point to fix

+	 */

+	protected IdentityAnchor getIdentityAnchor(

+			final AbstractConnectionEditPart edgeEP, final boolean sourcePoint,

+			final INodeEditPart nodeEP, final Point p) {

+		final View view = (View) edgeEP.getAdapter(View.class);

+		IdentityAnchor anchor = null;

+		if (view instanceof Edge) {

+			final Object tmpAnchor;

+			if (sourcePoint) {

+				tmpAnchor = ((Edge) view).getSourceAnchor();

+			} else {

+				tmpAnchor = ((Edge) view).getTargetAnchor();

+			}

+			if (tmpAnchor instanceof IdentityAnchor) {

+				anchor = (IdentityAnchor) tmpAnchor;

+			}

+			if (anchor == null) {

+				ConnectionAnchor connectionAnchor = null;

+				if (nodeEP.getFigure() instanceof NodeFigure) {

+					NodeFigure nodeFigure = (NodeFigure) nodeEP.getFigure();

+					if (sourcePoint) {

+						connectionAnchor = nodeFigure

+								.getSourceConnectionAnchorAt(p);

+					} else {

+						connectionAnchor = nodeFigure

+								.getTargetConnectionAnchorAt(p);

+					}

+					if (connectionAnchor != null) {

+						final String id = nodeEP

+								.mapConnectionAnchorToTerminal(connectionAnchor);

+						anchor = NotationFactory.eINSTANCE

+								.createIdentityAnchor();

+						anchor.setId(id);

+					}

+				}

+			}

+

+		}

+		return anchor;

+

+	}

+

+	/**

+	 * 

+	 * @param nodeEditPart

+	 *            the resized edit part

+	 * @param oldNodeBounds

+	 *            the old bounds for this edit part

+	 * @param newNodeBounds

+	 *            the new bounds for this edit part

+	 * @param targetConnectionEP

+	 *            the edit part of the connection for which we want fix anchor

+	 * @param move

+	 *            the move

+	 * @param sizeDelta

+	 *            the delta of the resize

+	 * @param fixSource

+	 *            if <code>true</code> we are fixing the source anchor if

+	 *            <code>false</code> we are fixing the target anchor

+	 * @return

+	 */

+	public Command getFixAnchorCommand(final INodeEditPart nodeEditPart,

+			final PrecisionRectangle oldNodeBounds,

+			final PrecisionRectangle newNodeBounds,

+			final AbstractConnectionEditPart targetConnectionEP,

+			final Point move, final Dimension sizeDelta, final boolean fixSource) {

+		final Point realAnchorPoint = getRealAnchorPoint(targetConnectionEP,

+				fixSource);

+

+		final IFigure fig = nodeEditPart.getFigure();

+		if (fig instanceof NodeFigure) {

+			final NodeFigure nodeFigure = (NodeFigure) fig;

+			final IdentityAnchor editedAnchor = getIdentityAnchor(

+					targetConnectionEP, fixSource, nodeEditPart,

+					realAnchorPoint);

+			if (realAnchorPoint != null && editedAnchor != null) {

+

+				final View view = (View) targetConnectionEP

+						.getAdapter(View.class);

+				if (view instanceof Edge) {

+					// 1. get the real side on which start/end the manipulated

+					// anchor

+					final int anchorSide = getSideOfConnectionPoint(nodeFigure,

+							targetConnectionEP, fixSource);

+					// 2. determine the new values

+					double newX = -1;

+					double newY = -1;

+					switch (anchorSide) {

+					case PositionConstants.NORTH:

+						newY = 0;

+						break;

+					case PositionConstants.WEST:

+						newX = 0;

+						break;

+					case PositionConstants.EAST:

+						newX = 1;

+						break;

+					case PositionConstants.SOUTH:

+						newY = 1;

+						break;

+					default:

+						// other case not yet manager because they are resize

+						// dependant!

+						break;

+					}

+

+					PrecisionPoint newRealAnchorPoint = new PrecisionPoint(

+							realAnchorPoint);

+					newRealAnchorPoint.setPreciseLocation(

+							newRealAnchorPoint.x() - oldNodeBounds.preciseX(),

+							newRealAnchorPoint.y() - oldNodeBounds.preciseY());

+

+					PrecisionPoint newLocation = newRealAnchorPoint

+							.getPreciseCopy();

+					newLocation.setPreciseX(newLocation.preciseX()

+							- move.preciseX());

+					newLocation.setPreciseY(newLocation.preciseY()

+							- move.preciseY());

+

+					if (newX == -1) {

+						newX = newLocation.preciseX()

+								/ newNodeBounds.preciseWidth();

+					}

+					if (newY == -1) {

+						newY = newLocation.preciseY()

+								/ newNodeBounds.preciseHeight();

+					}

+

+					if (newX <= 1 && newX >= 0 && newY <= 1 && newY >= 0) {

+						final String newIdValue = createNewAnchorIdValue(newX, newY);

+						final ICommand cmd = new AbstractTransactionalCommand(

+								this.domain, "Fix Anchor Location", null) { //$NON-NLS-1$

+

+							@Override

+							protected CommandResult doExecuteWithResult(

+									IProgressMonitor monitor, IAdaptable info)

+									throws ExecutionException {

+								editedAnchor.setId(newIdValue);

+

+								if (editedAnchor.eContainer() == null) {

+									if (fixSource) {

+										((Edge) view)

+												.setSourceAnchor(editedAnchor);

+									} else {

+										((Edge) view)

+												.setTargetAnchor(editedAnchor);

+									}

+								}

+								return CommandResult

+										.newOKCommandResult(editedAnchor);

+							}

+						};

+						return new ICommandProxy(cmd);

+					}

+				}

+			}

+		}

+		return null;

+	}

+

+	/**

+	 * 

+	 * @param nodeFigure

+	 *            the node figure

+	 * @param edgeEP

+	 *            the connection edit part

+	 * @param fixingSource

+	 *            if <code>true</code> we are fixing the source anchor and if

+	 *            <code>false</code> we are fixing the target anchor

+	 * @return the direction of the manipulated anchor (according to

+	 *         {@link PositionConstants}

+	 */

+	protected final int getSideOfConnectionPoint(final IFigure nodeFigure,

+			final AbstractConnectionEditPart edgeEP, final boolean fixingSource) {

+		int side = -1;

+		final IFigure figure = edgeEP.getFigure();

+		if (figure instanceof PolylineConnectionEx) {

+			final PolylineConnectionEx connection = (PolylineConnectionEx) figure;

+			final Rectangle bounds = nodeFigure.getBounds().getCopy();

+			nodeFigure.translateToAbsolute(bounds);

+			final Point pt;

+			if (fixingSource) {

+				pt = connection.getStart();

+			} else {

+				pt = connection.getEnd();

+			}

+			figure.translateToAbsolute(pt);

+			Rectangle includedRect = bounds.getCopy();

+			while (includedRect.contains(pt)) {

+				includedRect.shrink(1, 1);

+			}

+			side = includedRect.getPosition(pt);

+			// if the anchor side is a corner, we determine its side using

+			// another point

+			if (side == PositionConstants.NORTH_WEST

+					|| side == PositionConstants.NORTH_EAST

+					|| side == PositionConstants.SOUTH_EAST

+					|| side == PositionConstants.SOUTH_WEST) {

+				final Point previousPoint;

+				final PointList list = connection.getPoints();

+				if (list.size() > 1) {

+

+					if (fixingSource) {

+						previousPoint = list.getPoint(1);

+					} else {

+						previousPoint = list.getPoint(list.size() - 2);

+					}

+					nodeFigure.translateToAbsolute(previousPoint.getCopy());

+					while (includedRect.contains(previousPoint)) {

+						includedRect.shrink(1, 1);

+					}

+					side = includedRect.getPosition(previousPoint);

+				}

+			}

+		}

+		return side;

+	}

+

+	/**

+	 *

+	 * @param percentageOnX

+	 *            the percentage on x

+	 * @param percentageOnY

+	 *            the percentage on y

+	 * @return the string representing the new id for an anchor

+	 */

+	public static final String createNewAnchorIdValue(

+			final double percentageOnX, final double percentageOnY) {

+		final StringBuffer buffer = new StringBuffer();

+		buffer.append('(');

+		buffer.append(Double.toString(percentageOnX));

+		buffer.append(',');

+		buffer.append(Double.toString(percentageOnY));

+		buffer.append(')');

+		return buffer.toString();

+	}

+}

diff --git a/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/xylayout/XYLayoutWithFixAnchorsEditPolicy.java b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/xylayout/XYLayoutWithFixAnchorsEditPolicy.java
new file mode 100644
index 0000000..7370010
--- /dev/null
+++ b/plugins/org.eclipse.gmf.tooling.runtime/src/org/eclipse/gmf/tooling/runtime/linklf/xylayout/XYLayoutWithFixAnchorsEditPolicy.java
@@ -0,0 +1,70 @@
+/*****************************************************************************
+ * Copyright (c) 2014 CEA LIST, Montages AG 
+ *
+ *    
+ * 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:
+ *  Vincent Lorenzo (CEA LIST) vincent.lorenzo@cea.fr - Initial API and implementation
+ *  Michael Golubev (Montages) - adaptation for GMF Tooling 
+ *****************************************************************************/
+package org.eclipse.gmf.tooling.runtime.linklf.xylayout;
+
+import java.util.List;
+
+import org.eclipse.gef.EditPart;
+import org.eclipse.gef.commands.Command;
+import org.eclipse.gef.requests.ChangeBoundsRequest;
+import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
+import org.eclipse.gmf.runtime.diagram.ui.editparts.INodeEditPart;
+import org.eclipse.gmf.runtime.diagram.ui.editpolicies.XYLayoutEditPolicy;
+
+/**
+ * This class enables use of {@link FixAnchorHelper} authored by Papyrus.
+ * The code relevant to {@link FixAnchorHelper} had been partially copy-pasted from 
+ * original Papyrus modification of {@link XYLayoutEditPolicy}.
+ * 
+ * @since 3.3
+ */
+public class XYLayoutWithFixAnchorsEditPolicy extends XYLayoutEditPolicy {
+
+	private FixAnchorHelper myFixAnchorHelper;
+
+	@Override
+	public void activate() {
+		super.activate();
+		myFixAnchorHelper = new FixAnchorHelper(getHost().getEditingDomain());
+	}
+
+	@Override
+	protected Command getChangeConstraintCommand(ChangeBoundsRequest request) {
+		Command result = super.getChangeConstraintCommand(request);
+		if (result != null && result.canExecute()) {
+			//XXX: in Papyrus it is guarded by request.isConstrainedResize();
+			boolean isConstrainedResize = true;
+			@SuppressWarnings("unchecked")
+			List<EditPart> editParts = (List<EditPart>) request.getEditParts();
+			for (EditPart child : editParts) {
+				//we add the command to fix the anchor
+				if (isConstrainedResize && child instanceof INodeEditPart) {
+					if (myFixAnchorHelper != null) {
+						Command fixAnchorCommand = myFixAnchorHelper.getFixIdentityAnchorCommand( //
+								(INodeEditPart) child, request.getMoveDelta(), request.getSizeDelta(), request.getResizeDirection());
+						if (fixAnchorCommand != null) {
+							result = result.chain(fixAnchorCommand);
+						}
+					}
+				}
+			}
+		}
+		return result;
+	}
+
+	public IGraphicalEditPart getHost() {
+		return (IGraphicalEditPart) super.getHost();
+	}
+
+}