[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();
+ }
+
+}