package org.eclipse.gmf.tooling.runtime.linklf.editpolicies;
import java.lang.ref.WeakReference;
import java.util.Collection;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.runtime.IAdaptable;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.draw2d.Connection;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.PositionConstants;
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.emf.transaction.TransactionalEditingDomain;
import org.eclipse.gef.EditPart;
import org.eclipse.gef.GraphicalEditPart;
import org.eclipse.gef.Request;
import org.eclipse.gef.SnapToGrid;
import org.eclipse.gef.commands.Command;
import org.eclipse.gef.requests.ChangeBoundsRequest;
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.ConnectionEditPart;
import org.eclipse.gmf.runtime.draw2d.ui.geometry.PointListUtilities;
import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.OrthogonalRouter;
import org.eclipse.gmf.runtime.draw2d.ui.internal.routers.OrthogonalRouterUtilities;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;
import org.eclipse.gmf.runtime.notation.Edge;
import org.eclipse.gmf.tooling.runtime.linklf.AbsoluteBendpointsConvention;
import org.eclipse.gmf.tooling.runtime.linklf.DiagramGridSpec;
import org.eclipse.gmf.tooling.runtime.linklf.router.SnapToGridRectilinearRouter;
public class AdjustLinksToIndirectlyMovedNodesEditPolicy extends AdjustAbsoluteBendpointsEditPolicyBase {
/**
* Default role for registering this edit policy.
* <p/>
* The value is prefixed by class FQN in order to avoid conflicts,
* but the literal should NOT be used anywhere.
*/
public static final String ROLE = AdjustLinksToIndirectlyMovedNodesEditPolicy.class.getName() + ":Role";
private WeakReference<ChangeBoundsRequest> myLastRequest;
private WeakReference<Command> myLastCommand;
@Override
protected Command getAdjustLinksCommand(ChangeBoundsRequest req) {
if (myLastRequest != null && myLastRequest.get() == req) {
if (myLastCommand == null) {
return null;
}
Command result = myLastCommand.get();
if (result != null) {
return result;
}
}
myLastCommand = null;
myLastRequest = null;
Command result = createAdjustLinksCommand(req);
myLastRequest = new WeakReference<ChangeBoundsRequest>(req);
myLastCommand = result == null ? null : new WeakReference<Command>(result);
return result;
}
@SuppressWarnings("unchecked")
protected Command createAdjustLinksCommand(ChangeBoundsRequest req) {
ICommand result = null;
CachedEditPartsSet allMoved = getMovedEditPartsSet(req);
LinkedList<GraphicalEditPart> queue = new LinkedList<GraphicalEditPart>();
queue.addAll(getHost().getChildren());
while (!queue.isEmpty()) {
GraphicalEditPart cur = queue.removeFirst();
for (EditPart link : (Collection<EditPart>) cur.getSourceConnections()) {
ICommand nextOutgoingCommand = getAdjustOneLinkCommand(link, allMoved, req, true);
result = compose(result, nextOutgoingCommand);
}
for (EditPart link : (Collection<EditPart>) cur.getTargetConnections()) {
ICommand nextIncomingCommand = getAdjustOneLinkCommand(link, allMoved, req, false);
result = compose(result, nextIncomingCommand);
}
queue.addAll((Collection<GraphicalEditPart>) cur.getChildren());
}
return result == null ? null : new ICommandProxy(result.reduce());
}
protected ICommand getAdjustOneLinkCommand(EditPart linkEP, CachedEditPartsSet allMoved, ChangeBoundsRequest req, boolean outgoing) {
if (false == linkEP instanceof ConnectionEditPart) {
return null;
}
ConnectionEditPart link = (ConnectionEditPart) linkEP;
EditPart staticEnd = outgoing ? link.getTarget() : link.getSource();
if (staticEnd == null) {
return null;
}
MovedNodeKind kind = allMoved.isMoved(staticEnd);
if (kind != MovedNodeKind.NO) {
return null;
}
Connection conn = link.getConnectionFigure();
if (conn == null) {
return null;
}
if (!acceptAdjustingConnection(link, conn)) {
return null;
}
GraphicalEditPart staticGateEP = (GraphicalEditPart) findHighestDifferentAncestor(staticEnd, getHost());
if (staticGateEP == null) {
return null;
}
PointList linkPoints = makeAbsolute(conn, conn.getPoints());
Point hostGate = findGatePosition(linkPoints, getHostFigure());
if (hostGate == null) {
return null;
}
int hostGateSegment = PointListUtilities.findNearestLineSegIndexOfPoint(linkPoints, hostGate);
Point staticGate;
int staticGateSegment;
if (staticGateEP == staticEnd) {
staticGate = null;
staticGateSegment = -1;
} else {
IFigure staticGateOwner = staticGateEP.getFigure();
staticGate = findGatePosition(linkPoints, staticGateOwner);
if (staticGate == null) {
return null;
}
staticGateSegment = PointListUtilities.findNearestLineSegIndexOfPoint(linkPoints, staticGate);
}
//System.err.println("Need to adjust link: " + link + ", outgoing: " + outgoing + ", other end: " + staticEnd);
//System.err.println("\tFound dynamic gate: abs: " + hostGate + ", rel: " + hostGateRel + ", segIndex: " + hostGateSegment);
//System.err.println("\tFound static gate: abs: " + staticGate + ", rel: " + staticGateRel + ", segIndex: " + staticGateSegment);
PreserveGatesRequest preserveReq = new PreserveGatesRequest(link, linkPoints, outgoing, req);
preserveReq.setMovingGate(hostGate, hostGateSegment);
preserveReq.setStaticGate(staticGate, staticGateSegment);
return createPreserveGatesCommand(preserveReq);
}
protected ICommand createPreserveGatesCommand(PreserveGatesRequest req) {
return new PreserveGatesCommand(getDomain(), req);
}
/**
* Hook for subclasses, allows to decide whether to adjust links on instance-by-instance basis.
* <p/>
* In this implementation the link is accepted if it <ul>
* <li>is backed by some {@link Edge}</li>
* <li>has absolute bendpoints {@link AbsoluteBendpointsConvention} which has to be adjusted</li>
* <li>has an orthogonal router</li>
* </ul>
*
* @param link link editpart to check
* @param conn its connection figure, known to be not <code>null</code>
* @return true if link gates should be preserved
*/
protected boolean acceptAdjustingConnection(ConnectionEditPart link, Connection conn) {
Edge edge = (Edge) link.getNotationView();
if (edge == null) {
return false;
}
return AbsoluteBendpointsConvention.getInstance().hasAbsoluteStoredAsRelativeBendpoints(edge) && //
conn.getConnectionRouter() instanceof OrthogonalRouter;
}
/**
* Finds the gate, that is an intersection between link points and the given gate owner.
* @param linkPointsAbs absolute link point coordinates
* @param gateOwner the figure which intersection with the link defines the gate
*
* @return absolute position of the intersection, or <code>null</code> if not found. If multiple intersections are found then only the first is returned
*/
protected Point findGatePosition(PointList linkPointsAbs, IFigure gateOwner) {
Rectangle ownerBounds = makeAbsolute(gateOwner, gateOwner.getBounds());
PointList ownerPoints = PointListUtilities.createPointsFromRect(ownerBounds);
PointList intersections = new PointList();
PointList distances = new PointList();
boolean computed = PointListUtilities.findIntersections(ownerPoints, linkPointsAbs, intersections, distances);
if (!computed || intersections.size() == 0) {
System.err.println("Can't compute intersections between:" + //
" hostBounds: " + pointList2String(ownerPoints) + //
" and link: " + pointList2String(linkPointsAbs));
return null;
}
if (intersections.size() > 1) {
System.err.println("Expected exactly one intersection between:" + //
" hostBounds: " + pointList2String(ownerPoints) + //
" and link: " + pointList2String(linkPointsAbs) + //
" actually: " + pointList2String(intersections) + //
" will use the first one: " + intersections.getFirstPoint());
}
return intersections.getFirstPoint();
}
@Override
public void deactivate() {
myLastRequest = null;
myLastCommand = null;
super.deactivate();
}
/**
* Provides the highest (closest to the root) editpart which is ancestor of the subj editPart but is still not a common ancestor of some other editpart
* @param subj the editPart to compute ancestor for
* @param other the editPart which ancestors should be avoided
* @return
*/
protected static EditPart findHighestDifferentAncestor(EditPart subj, EditPart other) {
Set<EditPart> otherChainUp = new HashSet<EditPart>();
EditPart cur = other;
while (cur != null) {
otherChainUp.add(cur);
cur = cur.getParent();
}
EditPart result = subj;
while (result != null) {
EditPart parent = result.getParent();
if (parent == null) {
//weird, there should be at least common root edit part
return null;
}
if (otherChainUp.contains(parent)) {
return result;
}
result = parent;
}
throw new IllegalStateException();
}
protected static class PreserveGatesRequest extends Request {
private final ConnectionEditPart myLink;
private final PointList myLinkPoints;
private final boolean myIsOutgoing;
private final ChangeBoundsRequest myHostRequest;
private Point myStaticGateAbs;
private Point myMovingGateAbs;
private int myStaticGateSegment;
private int myMovingGateSegment;
public PreserveGatesRequest(ConnectionEditPart link, PointList linkPointsAbs, boolean outgoing, ChangeBoundsRequest hostRequest) {
myLink = link;
myLinkPoints = linkPointsAbs;
myIsOutgoing = outgoing;
myHostRequest = hostRequest;
}
public void setMovingGate(Point movingGateAbs, int segIndex) {
myMovingGateAbs = movingGateAbs;
myMovingGateSegment = segIndex;
}
public void setStaticGate(Point staticGateAbs, int segIndex) {
myStaticGateAbs = staticGateAbs;
myStaticGateSegment = segIndex;
}
public Point getMovingGateAbs() {
return myMovingGateAbs;
}
public Point getStaticGateAbs() {
return myStaticGateAbs;
}
public int getMovingGateSegment() {
return myMovingGateSegment;
}
public int getStaticGateSegment() {
return myStaticGateSegment;
}
public ConnectionEditPart getLink() {
return myLink;
}
public Edge getEdge() {
return (Edge) myLink.getNotationView();
}
public PointList getLinkPointsBefore() {
return myLinkPoints;
}
public boolean isOutgoing() {
return myIsOutgoing;
}
public GraphicalEditPart getStaticLinkEnd() {
return (GraphicalEditPart) (isOutgoing() ? myLink.getTarget() : myLink.getSource());
}
public GraphicalEditPart getMovingLinkEnd() {
return (GraphicalEditPart) (isOutgoing() ? myLink.getSource() : myLink.getTarget());
}
public Point getMoveDelta() {
return myHostRequest.getMoveDelta();
}
public SnapToGrid getSnapToGrid() {
PrecisionRectangle gridSpec = DiagramGridSpec.getAbsoluteGridSpec(getLink().getViewer());
return gridSpec == null ? null : new SnapToGrid(getLink());
}
}
protected static class PreserveGatesCommand extends AbstractTransactionalCommand {
private static final String LABEL = "Adjusting link preserving implicit gates";
private final PreserveGatesRequest myRequest;
public PreserveGatesCommand(TransactionalEditingDomain domain, PreserveGatesRequest req) {
super(domain, LABEL, getWorkspaceFiles(req.getLink().getNotationView()));
myRequest = req;
}
@Override
protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info) throws ExecutionException {
Point moveDelta = myRequest.getMoveDelta();
if (moveDelta == null || moveDelta.x == 0 && moveDelta.y == 0) {
//may be the case due to the caching: command was created when moveDelta was here
//but at the execution time request has changed
return CommandResult.newOKCommandResult();
}
PointList oldPoints = myRequest.getLinkPointsBefore();
//System.err.println("[1]: oldPoints: " + pointList2String(oldPoints));
PointList newPointsStart = new PointList(oldPoints.size());
PointList newPointsEnd = new PointList(oldPoints.size() * 2);
if (myRequest.isOutgoing()) {
//segment indexes are 1-based, see findNearestLineSegIndexOfPoint
for (int i = 0; i < myRequest.getMovingGateSegment(); i++) {
newPointsStart.addPoint(oldPoints.getPoint(i));
}
newPointsStart.addPoint(myRequest.getMovingGateAbs());
newPointsStart.translate(moveDelta);
if (myRequest.getStaticGateAbs() != null && myRequest.getStaticGateSegment() == myRequest.getMovingGateSegment()) {
newPointsEnd.addPoint(myRequest.getStaticGateAbs());
}
for (int i = myRequest.getMovingGateSegment(); i < oldPoints.size(); i++) {
newPointsEnd.addPoint(oldPoints.getPoint(i));
}
} else {
for (int i = 0; i < myRequest.getMovingGateSegment(); i++) {
newPointsStart.addPoint(oldPoints.getPoint(i));
}
if (myRequest.getStaticGateAbs() != null && myRequest.getMovingGateSegment() == myRequest.getStaticGateSegment()) {
newPointsStart.addPoint(myRequest.getStaticGateAbs());
}
newPointsEnd.addPoint(myRequest.getMovingGateAbs());
for (int i = myRequest.getMovingGateSegment(); i < oldPoints.size(); i++) {
newPointsEnd.addPoint(oldPoints.getPoint(i));
}
newPointsEnd.translate(moveDelta);
}
Point routeStart = newPointsStart.getLastPoint();
Point routeEnd = newPointsEnd.getFirstPoint();
//System.err.println("[2]: newPointsStart: " + pointList2String(newPointsStart));
//System.err.println("[3]: newPointsEnd: " + pointList2String(newPointsEnd));
//System.err.println("[4]: routeStart: " + routeStart + ", routeEnd:" + routeEnd);
//Connection conn = myRequest.getLink().getConnectionFigure();
//newPointsStart = makeRelative(conn, newPointsStart);
//newPointsEnd = makeRelative(conn, newPointsEnd);
//routeStart = makeRelative(conn, routeStart);
//routeEnd = makeRelative(conn, routeEnd);
//System.err.println("[5.1]: newPointsStart: (rel): " + pointList2String(newPointsStart));
//System.err.println("[5.2]: newPointsEnd: (rel): " + pointList2String(newPointsEnd));
//System.err.println("[5.3]: routeStart (rel): " + routeStart + ", routeEnd (rel):" + routeEnd);
int offSourceDirection = PreserveGatesUtil.getSegmentDirection(oldPoints, myRequest.getMovingGateSegment());
int offTargetDirection = PreserveGatesUtil.getOppositeDirection(offSourceDirection);
PointList middlePart = new PointList();
middlePart.addPoint(routeStart);
middlePart.addPoint(routeEnd);
if (!OrthogonalRouterUtilities.isRectilinear(middlePart)) {
SnapToGrid snapper = myRequest.getSnapToGrid();
PreserveGatesUtil.insertPointsProducingNotAlignedRectilinearSegments(middlePart, offSourceDirection, offTargetDirection, snapper);
OrthogonalRouterUtilities.transformToOrthogonalPointList(middlePart, //
PreserveGatesUtil.asVerticalOrHorizontal(offSourceDirection), //
PreserveGatesUtil.asVerticalOrHorizontal(offTargetDirection));
}
if (middlePart.getFirstPoint().equals(newPointsStart.getLastPoint())) {
middlePart.removePoint(0);
}
if (middlePart.getLastPoint().equals(newPointsEnd.getFirstPoint())) {
middlePart.removePoint(middlePart.size() - 1);
}
PointList newPoints = new PointList(newPointsStart.size() + middlePart.size() + newPointsEnd.size());
newPoints.addAll(newPointsStart);
newPoints.addAll(middlePart);
newPoints.addAll(newPointsEnd);
PreserveGatesUtil.removeRedundantPoints(newPoints);
//System.err.println("Old link points: " + pointList2String(oldPoints));
//System.err.println("--> " + pointList2String(newPoints));
PointList relPoints = newPoints.getCopy();
myRequest.getLink().getConnectionFigure().translateToRelative(relPoints);
//System.err.println("== (rel:) " + pointList2String(relPoints));
if (newPoints != null) {
SetAbsoluteBendpointsCommand.setAbsoluteBendpoints(myRequest.getEdge(), relPoints);
}
return CommandResult.newOKCommandResult();
}
}
private static class PreserveGatesUtil extends SnapToGridRectilinearRouter {
public static int getOppositeDirection(int direction) {
int result = 0;
if ((direction & PositionConstants.EAST) != 0) {
result |= PositionConstants.WEST;
}
if ((direction & PositionConstants.WEST) != 0) {
result |= PositionConstants.EAST;
}
if ((direction & PositionConstants.NORTH) != 0) {
result |= PositionConstants.SOUTH;
}
if ((direction & PositionConstants.SOUTH) != 0) {
result |= PositionConstants.NORTH;
}
return result;
}
/**
* @param points input list of points
* @param segment 1-based segment index, valid for given points list
* @return
*/
public static int getSegmentDirection(PointList points, int segment) {
Point start = points.getPoint(segment - 1);
Point end = points.getPoint(segment);
if (start.x == end.x) {
return start.y < end.y ? PositionConstants.SOUTH : PositionConstants.NORTH;
}
if (start.y == end.y) {
return start.x < end.x ? PositionConstants.EAST : PositionConstants.WEST;
}
return getOutisePointOffRectanglePosition2(start, new Rectangle(end.x, end.y, 0, 0));
}
}
}