/*******************************************************************************
* Copyright (c) 2011 Red Hat, Inc.
* All rights reserved.
* This program is 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:
* Red Hat, Inc. - initial API and implementation
******************************************************************************/
package org.eclipse.bpmn2.modeler.core.utils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.bpmn2.di.BPMNEdge;
import org.eclipse.bpmn2.di.BPMNShape;
import org.eclipse.bpmn2.modeler.core.Activator;
import org.eclipse.bpmn2.modeler.core.ModelHandler;
import org.eclipse.dd.di.DiagramElement;
import org.eclipse.emf.common.util.EList;
import org.eclipse.graphiti.datatypes.IDimension;
import org.eclipse.graphiti.datatypes.ILocation;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.mm.algorithms.styles.Point;
import org.eclipse.graphiti.mm.pictograms.Anchor;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.FixPointAnchor;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.mm.pictograms.impl.FreeFormConnectionImpl;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.services.IGaService;
import org.eclipse.graphiti.services.IPeService;
public class AnchorUtil {
public static final String BOUNDARY_FIXPOINT_ANCHOR = "boundary.fixpoint.anchor";
private static final IPeService peService = Graphiti.getPeService();
private static final IGaService gaService = Graphiti.getGaService();
public enum AnchorLocation {
TOP("anchor.top"), BOTTOM("anchor.bottom"), LEFT("anchor.left"), RIGHT("anchor.right");
private final String key;
private AnchorLocation(String key) {
this.key = key;
}
public String getKey() {
return key;
}
public static AnchorLocation getLocation(String key) {
for (AnchorLocation l : values()) {
if (l.getKey().equals(key)) {
return l;
}
}
return null;
}
}
public static class AnchorTuple {
public FixPointAnchor sourceAnchor;
public FixPointAnchor targetAnchor;
}
public static class BoundaryAnchor {
public FixPointAnchor anchor;
public AnchorLocation locationType;
public ILocation location;
}
public static FixPointAnchor createAnchor(Shape s, AnchorLocation loc, int x, int y) {
IGaService gaService = Graphiti.getGaService();
IPeService peService = Graphiti.getPeService();
FixPointAnchor anchor = peService.createFixPointAnchor(s);
peService.setPropertyValue(anchor, BOUNDARY_FIXPOINT_ANCHOR, loc.getKey());
anchor.setLocation(gaService.createPoint(x, y));
gaService.createInvisibleRectangle(anchor);
return anchor;
}
public static Map<AnchorLocation, BoundaryAnchor> getBoundaryAnchors(Shape s) {
Map<AnchorLocation, BoundaryAnchor> map = new HashMap<AnchorLocation, AnchorUtil.BoundaryAnchor>(4);
Iterator<Anchor> iterator = s.getAnchors().iterator();
while (iterator.hasNext()) {
Anchor anchor = iterator.next();
String property = Graphiti.getPeService().getPropertyValue(anchor, BOUNDARY_FIXPOINT_ANCHOR);
if (property != null && anchor instanceof FixPointAnchor) {
BoundaryAnchor a = new BoundaryAnchor();
a.anchor = (FixPointAnchor) anchor;
a.locationType = AnchorLocation.getLocation(property);
a.location = peService.getLocationRelativeToDiagram(anchor);
map.put(a.locationType, a);
}
}
return map;
}
public static Point getCenterPoint(Shape s) {
GraphicsAlgorithm ga = s.getGraphicsAlgorithm();
ILocation loc = peService.getLocationRelativeToDiagram(s);
return gaService.createPoint(loc.getX() + (ga.getWidth() / 2), loc.getY() + (ga.getHeight() / 2));
}
@SuppressWarnings("restriction")
public static Tuple<FixPointAnchor, FixPointAnchor> getSourceAndTargetBoundaryAnchors(Shape source, Shape target,
Connection connection) {
Map<AnchorLocation, BoundaryAnchor> sourceBoundaryAnchors = getBoundaryAnchors(source);
Map<AnchorLocation, BoundaryAnchor> targetBoundaryAnchors = getBoundaryAnchors(target);
if (connection instanceof FreeFormConnectionImpl) {
EList<Point> bendpoints = ((FreeFormConnectionImpl) connection).getBendpoints();
if (bendpoints.size() > 0) {
FixPointAnchor sourceAnchor = getCorrectAnchor(sourceBoundaryAnchors, bendpoints.get(0));
FixPointAnchor targetAnchor = getCorrectAnchor(targetBoundaryAnchors,
bendpoints.get(bendpoints.size() - 1));
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceAnchor, targetAnchor);
}
}
BoundaryAnchor sourceTop = sourceBoundaryAnchors.get(AnchorLocation.TOP);
BoundaryAnchor sourceBottom = sourceBoundaryAnchors.get(AnchorLocation.BOTTOM);
BoundaryAnchor sourceLeft = sourceBoundaryAnchors.get(AnchorLocation.LEFT);
BoundaryAnchor sourceRight = sourceBoundaryAnchors.get(AnchorLocation.RIGHT);
BoundaryAnchor targetBottom = targetBoundaryAnchors.get(AnchorLocation.BOTTOM);
BoundaryAnchor targetTop = targetBoundaryAnchors.get(AnchorLocation.TOP);
BoundaryAnchor targetRight = targetBoundaryAnchors.get(AnchorLocation.RIGHT);
BoundaryAnchor targetLeft = targetBoundaryAnchors.get(AnchorLocation.LEFT);
boolean sLower = sourceTop.location.getY() > targetBottom.location.getY();
boolean sHigher = sourceBottom.location.getY() < targetTop.location.getY();
boolean sRight = sourceLeft.location.getX() > targetRight.location.getX();
boolean sLeft = sourceRight.location.getX() < targetLeft.location.getX();
if (sLower) {
if (!sLeft && !sRight) {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceTop.anchor, targetBottom.anchor);
} else if (sLeft) {
FixPointAnchor fromTopAnchor = getCorrectAnchor(targetBoundaryAnchors,
peService.getLocationRelativeToDiagram(sourceTop.anchor));
FixPointAnchor fromRightAnchor = getCorrectAnchor(targetBoundaryAnchors,
peService.getLocationRelativeToDiagram(sourceRight.anchor));
double topLength = getLength(peService.getLocationRelativeToDiagram(fromTopAnchor),
peService.getLocationRelativeToDiagram(sourceTop.anchor));
double rightLength = getLength(peService.getLocationRelativeToDiagram(fromRightAnchor),
peService.getLocationRelativeToDiagram(sourceRight.anchor));
if (topLength < rightLength) {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceTop.anchor, fromTopAnchor);
} else {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceRight.anchor, fromRightAnchor);
}
} else {
FixPointAnchor fromTopAnchor = getCorrectAnchor(targetBoundaryAnchors,
peService.getLocationRelativeToDiagram(sourceTop.anchor));
FixPointAnchor fromLeftAnchor = getCorrectAnchor(targetBoundaryAnchors,
peService.getLocationRelativeToDiagram(sourceLeft.anchor));
double topLength = getLength(peService.getLocationRelativeToDiagram(fromTopAnchor),
peService.getLocationRelativeToDiagram(sourceTop.anchor));
double leftLength = getLength(peService.getLocationRelativeToDiagram(fromLeftAnchor),
peService.getLocationRelativeToDiagram(sourceLeft.anchor));
if (topLength < leftLength) {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceTop.anchor, fromTopAnchor);
} else {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceLeft.anchor, fromLeftAnchor);
}
}
}
if (sHigher) {
if (!sLeft && !sRight) {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceBottom.anchor, targetTop.anchor);
} else if (sLeft) {
FixPointAnchor fromBottomAnchor = getCorrectAnchor(targetBoundaryAnchors,
peService.getLocationRelativeToDiagram(sourceBottom.anchor));
FixPointAnchor fromRightAnchor = getCorrectAnchor(targetBoundaryAnchors,
peService.getLocationRelativeToDiagram(sourceRight.anchor));
double bottomLength = getLength(peService.getLocationRelativeToDiagram(fromBottomAnchor),
peService.getLocationRelativeToDiagram(sourceBottom.anchor));
double rightLength = getLength(peService.getLocationRelativeToDiagram(fromRightAnchor),
peService.getLocationRelativeToDiagram(sourceRight.anchor));
if (bottomLength < rightLength) {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceBottom.anchor, fromBottomAnchor);
} else {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceRight.anchor, fromRightAnchor);
}
} else {
FixPointAnchor fromBottomAnchor = getCorrectAnchor(targetBoundaryAnchors,
peService.getLocationRelativeToDiagram(sourceBottom.anchor));
FixPointAnchor fromLeftAnchor = getCorrectAnchor(targetBoundaryAnchors,
peService.getLocationRelativeToDiagram(sourceLeft.anchor));
double bottomLength = getLength(peService.getLocationRelativeToDiagram(fromBottomAnchor),
peService.getLocationRelativeToDiagram(sourceBottom.anchor));
double leftLength = getLength(peService.getLocationRelativeToDiagram(fromLeftAnchor),
peService.getLocationRelativeToDiagram(sourceLeft.anchor));
if (bottomLength < leftLength) {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceBottom.anchor, fromBottomAnchor);
} else {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceLeft.anchor, fromLeftAnchor);
}
}
}
// if source left is further than target right then use source left and target right
if (sRight) {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceLeft.anchor, targetRight.anchor);
}
// if source right is smaller than target left then use source right and target left
if (sLeft) {
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceRight.anchor, targetLeft.anchor);
}
return new Tuple<FixPointAnchor, FixPointAnchor>(sourceTop.anchor, targetTop.anchor);
}
private static FixPointAnchor getCorrectAnchor(Map<AnchorLocation, BoundaryAnchor> targetBoundaryAnchors,
ILocation loc) {
return getCorrectAnchor(targetBoundaryAnchors, gaService.createPoint(loc.getX(), loc.getY()));
}
private static double getLength(ILocation start, ILocation end) {
return Math.sqrt(Math.pow(start.getX() - end.getX(), 2) + Math.pow(start.getY() - end.getY(), 2));
}
private static FixPointAnchor getCorrectAnchor(Map<AnchorLocation, BoundaryAnchor> boundaryAnchors, Point point) {
BoundaryAnchor bottom = boundaryAnchors.get(AnchorLocation.BOTTOM);
BoundaryAnchor top = boundaryAnchors.get(AnchorLocation.TOP);
BoundaryAnchor right = boundaryAnchors.get(AnchorLocation.RIGHT);
BoundaryAnchor left = boundaryAnchors.get(AnchorLocation.LEFT);
boolean pointLower = point.getY() > bottom.location.getY();
boolean pointHigher = point.getY() < top.location.getY();
boolean pointRight = point.getX() > right.location.getX();
boolean pointLeft = point.getX() < left.location.getX();
// Find the best connector.
if (pointLower) {
if (!pointLeft && !pointRight) {
// bendpoint is straight below the shape
return bottom.anchor;
} else if (pointLeft) {
int deltaX = left.location.getX() - point.getX();
int deltaY = point.getY() - bottom.location.getY();
if (deltaX > deltaY) {
return left.anchor;
} else {
return bottom.anchor;
}
} else {
int deltaX = point.getX() - right.location.getX();
int deltaY = point.getY() - bottom.location.getY();
if (deltaX > deltaY) {
return right.anchor;
} else {
return bottom.anchor;
}
}
}
if (pointHigher) {
if (!pointLeft && !pointRight) {
// bendpoint is straight above the shape
return top.anchor;
} else if (pointLeft) {
int deltaX = left.location.getX() - point.getX();
int deltaY = top.location.getY() - point.getY();
if (deltaX > deltaY) {
return left.anchor;
} else {
return top.anchor;
}
} else {
int deltaX = point.getX() - right.location.getX();
int deltaY = top.location.getY() - point.getY();
if (deltaX > deltaY) {
return right.anchor;
} else {
return top.anchor;
}
}
}
// if we reach here, then the point is neither above or below the shape and we only need to determine if we need
// to connect to the left or right part of the shape
if (pointRight) {
return right.anchor;
}
if (pointLeft) {
return left.anchor;
}
return top.anchor;
}
public static void reConnect(BPMNShape shape, Diagram diagram) {
try {
ModelHandler handler = ModelHandler.getInstance(diagram);
for (BPMNEdge bpmnEdge : handler.getAll(BPMNEdge.class)) {
DiagramElement sourceElement = bpmnEdge.getSourceElement();
DiagramElement targetElement = bpmnEdge.getTargetElement();
if (sourceElement != null && targetElement != null) {
boolean sourceMatches = sourceElement.getId().equals(shape.getId());
boolean targetMatches = targetElement.getId().equals(shape.getId());
if (sourceMatches || targetMatches) {
updateEdge(bpmnEdge, diagram);
}
}
}
} catch (Exception e) {
Activator.logError(e);
}
}
private static void updateEdge(BPMNEdge edge, Diagram diagram) {
ContainerShape source = (ContainerShape) Graphiti.getLinkService()
.getPictogramElements(diagram, edge.getSourceElement()).get(0);
ContainerShape target = (ContainerShape) Graphiti.getLinkService()
.getPictogramElements(diagram, edge.getTargetElement()).get(0);
Connection connection = (Connection) Graphiti.getLinkService().getPictogramElements(diagram, edge).get(0);
Tuple<FixPointAnchor, FixPointAnchor> anchors = getSourceAndTargetBoundaryAnchors(source, target, connection);
ILocation loc = peService.getLocationRelativeToDiagram(anchors.getFirst());
org.eclipse.dd.dc.Point p = edge.getWaypoint().get(0);
p.setX(loc.getX());
p.setY(loc.getY());
loc = peService.getLocationRelativeToDiagram(anchors.getSecond());
p = edge.getWaypoint().get(edge.getWaypoint().size() - 1);
p.setX(loc.getX());
p.setY(loc.getY());
relocateConnection(source.getAnchors(), anchors, target);
deleteEmptyAdHocAnchors(source);
deleteEmptyAdHocAnchors(target);
}
private static void relocateConnection(EList<Anchor> anchors, Tuple<FixPointAnchor, FixPointAnchor> newAnchors,
ContainerShape target) {
List<Connection> connectionsToBeUpdated = new ArrayList<Connection>();
for (Anchor anchor : anchors) {
if (!(anchor instanceof FixPointAnchor)) {
continue;
}
for (Connection connection : anchor.getOutgoingConnections()) {
if (connection.getEnd().eContainer().equals(target)) {
connectionsToBeUpdated.add(connection);
}
}
}
for (Connection c : connectionsToBeUpdated) {
c.setStart(newAnchors.getFirst());
c.setEnd(newAnchors.getSecond());
}
}
private static void deleteEmptyAdHocAnchors(Shape s) {
List<Integer> indexes = new ArrayList<Integer>();
for (int i = s.getAnchors().size()-1; i>=0; --i) {
Anchor a = s.getAnchors().get(i);
if (!(a instanceof FixPointAnchor)) {
continue;
}
if (peService.getProperty(a, BOUNDARY_FIXPOINT_ANCHOR) == null && a.getIncomingConnections().isEmpty()
&& a.getOutgoingConnections().isEmpty()) {
indexes.add(i);
}
}
for (int i : indexes) {
peService.deletePictogramElement(s.getAnchors().get(i));
}
}
public static void addFixedPointAnchors(Shape shape, GraphicsAlgorithm ga) {
IDimension size = gaService.calculateSize(ga);
int w = size.getWidth();
int h = size.getHeight();
AnchorUtil.createAnchor(shape, AnchorLocation.TOP, w / 2, 0);
AnchorUtil.createAnchor(shape, AnchorLocation.RIGHT, w, h / 2);
AnchorUtil.createAnchor(shape, AnchorLocation.BOTTOM, w / 2, h);
AnchorUtil.createAnchor(shape, AnchorLocation.LEFT, 0, h / 2);
}
public static void relocateFixPointAnchors(Shape shape, int w, int h) {
Map<AnchorLocation, BoundaryAnchor> anchors = AnchorUtil.getBoundaryAnchors(shape);
FixPointAnchor anchor = anchors.get(AnchorLocation.TOP).anchor;
anchor.setLocation(gaService.createPoint(w / 2, 0));
anchor = anchors.get(AnchorLocation.RIGHT).anchor;
anchor.setLocation(gaService.createPoint(w, h / 2));
anchor = anchors.get(AnchorLocation.BOTTOM).anchor;
anchor.setLocation(gaService.createPoint(w / 2, h));
anchor = anchors.get(AnchorLocation.LEFT).anchor;
anchor.setLocation(gaService.createPoint(0, h / 2));
}
}