blob: 380421655a628e3fcf8779c1a03588f73bd62e7f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2012 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
*
* @author Bob Brodt
******************************************************************************/
package org.eclipse.bpmn2.modeler.core.features;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import org.eclipse.bpmn2.BaseElement;
import org.eclipse.bpmn2.Lane;
import org.eclipse.bpmn2.Participant;
import org.eclipse.bpmn2.di.BPMNShape;
import org.eclipse.bpmn2.modeler.core.preferences.ShapeStyle;
import org.eclipse.bpmn2.modeler.core.preferences.ShapeStyle.RoutingStyle;
import org.eclipse.bpmn2.modeler.core.utils.AnchorType;
import org.eclipse.bpmn2.modeler.core.utils.AnchorUtil;
import org.eclipse.bpmn2.modeler.core.utils.BusinessObjectUtil;
import org.eclipse.bpmn2.modeler.core.utils.FeatureSupport;
import org.eclipse.bpmn2.modeler.core.utils.GraphicsUtil;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.context.IAddConnectionContext;
import org.eclipse.graphiti.features.context.IAddContext;
import org.eclipse.graphiti.features.context.IDeleteContext;
import org.eclipse.graphiti.features.context.impl.DeleteContext;
import org.eclipse.graphiti.features.impl.AbstractAddShapeFeature;
import org.eclipse.graphiti.mm.algorithms.Polyline;
import org.eclipse.graphiti.mm.algorithms.styles.LineStyle;
import org.eclipse.graphiti.mm.algorithms.styles.Point;
import org.eclipse.graphiti.mm.pictograms.Anchor;
import org.eclipse.graphiti.mm.pictograms.AnchorContainer;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ConnectionDecorator;
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.FreeFormConnection;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.ui.features.DefaultDeleteFeature;
import org.eclipse.graphiti.util.ColorConstant;
import org.eclipse.graphiti.util.IColorConstant;
/**
* Router for straight-line connections from source to target. Currently this
* does nothing but serves as a container for common fields and methods.
*/
public class DefaultConnectionRouter extends AbstractConnectionRouter {
/** The all shapes. */
protected List<ContainerShape> allShapes;
/** The connection. */
protected Connection connection = null;
/** The source. */
protected Shape source;
/** The target. */
protected Shape target;
/** The target anchor. */
protected FixPointAnchor sourceAnchor, targetAnchor;
/**
* Instantiates a new default connection router.
*
* @param fp the fp
*/
public DefaultConnectionRouter(IFeatureProvider fp) {
super(fp);
}
public static IConnectionRouter getRouter(IFeatureProvider fp, Connection connection) {
IConnectionRouter router = null;
BaseElement be = BusinessObjectUtil.getFirstBaseElement(connection);
if (be!=null) {
// get the user preference of routing style for this connection
ShapeStyle ss = ShapeStyle.getShapeStyle(be);
if (ss!=null) {
if (ss.getRoutingStyle() == RoutingStyle.MANHATTAN)
router = new ManhattanConnectionRouter(fp);
else if (ss.getRoutingStyle() == RoutingStyle.MANUAL) {
router = new BendpointConnectionRouter(fp);
}
else if (ss.getRoutingStyle() == RoutingStyle.AUTOMATIC) {
router = new AutomaticConnectionRouter(fp);
}
}
}
if (router==null)
router = new BendpointConnectionRouter(fp);
return router;
}
/* (non-Javadoc)
* @see org.eclipse.bpmn2.modeler.core.features.IConnectionRouter#canRoute(org.eclipse.graphiti.mm.pictograms.Connection)
*/
@Override
public boolean canRoute(Connection connection) {
// don't touch Choreography Task Message Links.
AnchorContainer ac = AnchorUtil.getAnchorContainer(connection.getStart());
if (AnchorType.getType(ac) == AnchorType.MESSAGELINK)
return false;
ac = AnchorUtil.getAnchorContainer(connection.getEnd());
if (AnchorType.getType(ac) == AnchorType.MESSAGELINK)
return false;
return true;
}
/* (non-Javadoc)
* @see org.eclipse.bpmn2.modeler.core.features.IConnectionRouter#needsUpdate(org.eclipse.graphiti.mm.pictograms.Connection)
*/
@Override
public boolean routingNeeded(Connection connection) {
if (this.connection==null) {
return true;
}
return false;
}
/* (non-Javadoc)
* @see org.eclipse.bpmn2.modeler.core.features.AbstractConnectionRouter#initialize(org.eclipse.graphiti.mm.pictograms.Connection)
*/
@Override
protected void initialize(Connection connection) {
if (this.connection!=connection) {
this.connection = connection;
this.sourceAnchor = (FixPointAnchor) connection.getStart();
this.targetAnchor = (FixPointAnchor) connection.getEnd();
this.source = (Shape) AnchorUtil.getAnchorContainer(sourceAnchor);
this.target = (Shape) AnchorUtil.getAnchorContainer(targetAnchor);
}
}
/* (non-Javadoc)
* @see org.eclipse.bpmn2.modeler.core.features.AbstractConnectionRouter#route(org.eclipse.graphiti.mm.pictograms.Connection)
*/
@Override
public boolean route(Connection connection) {
return false;
}
/* (non-Javadoc)
* @see org.eclipse.bpmn2.modeler.core.features.AbstractConnectionRouter#dispose()
*/
@Override
public void dispose() {
// be sure to clean up the routing info
removeRoutingInfo(connection);
}
/**
* Check if the connection's source and target nodes are identical.
*
* @return true if connection source == target
*/
protected boolean isSelfConnection() {
if (source != target)
return false;
return true;
}
/**
* Find all shapes in the diagram which can cause a collision.
*
* @return the list
*/
protected List<ContainerShape> findAllShapes() {
if (allShapes!=null)
return allShapes;
allShapes = new ArrayList<ContainerShape>();
Diagram diagram = peService.getDiagramForPictogramElement(connection);
// first find all ancestors of source and target, and their siblings
List<ContainerShape> ancestors = new ArrayList<ContainerShape>();
List<ContainerShape> shapes = new ArrayList<ContainerShape>();
TreeIterator<EObject> iter = diagram.eAllContents();
while (iter.hasNext()) {
EObject o = iter.next();
if (o instanceof ContainerShape && !(o instanceof Diagram)) {
// this is a potential collision shape
ContainerShape shape = (ContainerShape)o;
BPMNShape bpmnShape = BusinessObjectUtil.getFirstElementOfType(shape, BPMNShape.class);
if (bpmnShape==null) {
// this shape does not have a visual,
// so no collision is possible
continue;
}
if (FeatureSupport.isGroupShape(shape) || FeatureSupport.isLabelShape(shape) ) {
// ignore Groups and Labels
continue;
}
boolean ignore = false;
EObject ancestor = source.eContainer();
while (ancestor!=null) {
if (ancestor instanceof ContainerShape && shape==ancestor) {
ancestors.add((ContainerShape)ancestor);
ignore = true;
break;
}
ancestor = ancestor.eContainer();
}
if (!ignore) {
ancestor = target.eContainer();
while (ancestor!=null) {
if (ancestor instanceof ContainerShape && shape==ancestor) {
ancestors.add((ContainerShape)ancestor);
ignore = true;
break;
}
ancestor = ancestor.eContainer();
}
if (!ignore && !shapes.contains(shape)) {
shapes.add(shape);
}
}
}
}
for (ContainerShape shape : shapes) {
// this is a potential collision shape
if (!ancestors.contains(shape)) {
boolean ignore = false;
// only Lanes can be siblings to other Lanes within a container
// which may be either another Lane or a Pool.
BaseElement be = BusinessObjectUtil.getFirstBaseElement(shape);
if (be instanceof Lane) {
EObject ancestor = shape.eContainer();
while (!(ancestor instanceof Diagram)) {
if (ancestors.contains(ancestor)) {
ignore = true;
break;
}
ancestor = ancestor.eContainer();
}
}
// Yet another hack for dealing with imported files.
// Here we have a poorly laid out diagram such that
// a Pool overlaps another Pool almost completely.
// This is an indication that the tool used to create
// the file is rendering Pools on separate pages, even
// though there is only a single BPMNDiagram.
// See https://bugs.eclipse.org/bugs/show_bug.cgi?id=463205
// for an example of this.
if (be instanceof Lane || be instanceof Participant) {
for (ContainerShape ancestor : ancestors) {
if (GraphicsUtil.intersects(shape, ancestor)) {
ignore = true;
break;
}
}
}
// check if any ancestors overlaps this Lane or Pool
// this is a special case
if (!ignore) {
allShapes.add(shape);
}
}
}
GraphicsUtil.dump("All Shapes: ", allShapes); //$NON-NLS-1$
return allShapes;
}
/**
* Gets the collision edge.
*
* @param p1 the p1
* @param p2 the p2
* @return the collision edge
*/
protected GraphicsUtil.LineSegment getCollisionEdge(Point p1, Point p2) {
ContainerShape shape = getCollision(p1, p2);
if (shape!=null) {
return GraphicsUtil.findNearestEdge(shape, p1);
}
return null;
}
/**
* Gets the collision.
*
* @param p1 the p1
* @param p2 the p2
* @return the collision
*/
protected ContainerShape getCollision(Point p1, Point p2) {
List<ContainerShape> collisions = findCollisions(p1, p2);
if (collisions.size()==0)
return null;
if (collisions.size()==1)
return collisions.get(0);
sortCollisions(collisions, p2);
return collisions.get(0);
}
/**
* Find collisions.
*
* @param p1 the p1
* @param p2 the p2
* @return the list
*/
protected List<ContainerShape> findCollisions(Point p1, Point p2) {
List<ContainerShape> collisions = new ArrayList<ContainerShape>();
if (allShapes==null)
findAllShapes();
for (ContainerShape shape : allShapes) {
if (GraphicsUtil.intersectsLine(shape, p1, p2))
collisions.add(shape);
}
return collisions;
}
/**
* Sort collisions.
*
* @param collisions the collisions
* @param p the p
*/
protected void sortCollisions(List<ContainerShape> collisions, final Point p) {
Collections.sort(collisions, new Comparator<ContainerShape>() {
@Override
public int compare(ContainerShape s1, ContainerShape s2) {
GraphicsUtil.LineSegment seg1 = GraphicsUtil.findNearestEdge(s1, p);
double d1 = seg1.getDistance(p);
GraphicsUtil.LineSegment seg2 = GraphicsUtil.findNearestEdge(s2, p);
double d2 = seg2.getDistance(p);
return (int) (d2 - d1);
}
});
}
/**
* Find Connection line crossings. This will return a list of all
* Connections on the Diagram that intersect any line segment of the given
* Connection. Connections that are attached to the given Connection are
* ignored.
*
* @param connection the Connection to test.
* @param start the starting point of a line segment on the connection.
* @param end the ending point of the line segment.
* @return a list of Connections that cross over the line segment.
*/
protected List<Connection> findCrossings(Connection connection, Point start, Point end) {
// TODO: figure out why this isn't working!
List<Connection> crossings = new ArrayList<Connection>();
Diagram diagram = peService.getDiagramForPictogramElement(connection);
List<Connection> allConnections = diagram.getConnections();
List<FixPointAnchor> connectionAnchors = AnchorUtil.getAnchors(connection);
for (Connection c : allConnections) {
if (Graphiti.getPeService().getProperty(c, RoutingNet.CONNECTION)!=null) {
continue;
}
if (connectionAnchors.contains(c.getStart()) || connectionAnchors.contains(c.getEnd()))
continue;
Point p1 = GraphicsUtil.createPoint(c.getStart());
Point p3 = GraphicsUtil.createPoint(c.getEnd());
if (c instanceof FreeFormConnection) {
FreeFormConnection ffc = (FreeFormConnection) c;
Point p2 = p1;
for (Point p : ffc.getBendpoints()) {
if (GraphicsUtil.intersects(start, end, p1, p)) {
crossings.add(c);
break;
}
p2 = p1 = p;
}
if (GraphicsUtil.intersects(start, end, p2, p3)) {
crossings.add(c);
}
}
else if (GraphicsUtil.intersects(start, end, p1, p3)) {
crossings.add(c);
}
}
return crossings;
}
/**
* Length.
*
* @param p1 the p1
* @param p2 the p2
* @return the double
*/
protected static double length(Point p1, Point p2) {
return GraphicsUtil.getLength(p1, p2);
}
/**
* Draw connection routes.
*
* @param allRoutes the all routes
*/
protected void drawConnectionRoutes(List<ConnectionRoute> allRoutes) {
if (GraphicsUtil.debug) {
DeleteRoutingConnectionFeature deleteFeature = new DeleteRoutingConnectionFeature(fp);
deleteFeature.delete();
Diagram diagram = peService.getDiagramForPictogramElement(connection);
for (int i=0; i<allRoutes.size(); ++i) {
ConnectionRoute r = allRoutes.get(i);
// Anchor sa = AnchorUtil.createFixedAnchor(source, r.get(0));
// Anchor ta = AnchorUtil.createFixedAnchor(target, r.get( r.size()-1 ));
// AddConnectionContext context = new AddConnectionContext(sa, ta);
// context.setTargetContainer(diagram);
// context.setNewObject(r);
// AddRoutingConnectionFeature feature = new AddRoutingConnectionFeature(fp);
// feature.add(context);
GraphicsUtil.dump(r.toString());
}
}
}
/**
* The Class AddRoutingConnectionFeature.
*/
protected class AddRoutingConnectionFeature extends AbstractAddShapeFeature {
/** The Constant CONNECTION. */
public static final String CONNECTION = "ROUTING_NET_CONNECTION"; //$NON-NLS-1$
/**
* Instantiates a new adds the routing connection feature.
*
* @param fp the fp
*/
public AddRoutingConnectionFeature(IFeatureProvider fp) {
super(fp);
}
/* (non-Javadoc)
* @see org.eclipse.graphiti.func.IAdd#canAdd(org.eclipse.graphiti.features.context.IAddContext)
*/
@Override
public boolean canAdd(IAddContext ac) {
return true;
}
/* (non-Javadoc)
* @see org.eclipse.graphiti.func.IAdd#add(org.eclipse.graphiti.features.context.IAddContext)
*/
@Override
public PictogramElement add(IAddContext ac) {
IAddConnectionContext context = (IAddConnectionContext) ac;
Anchor sourceAnchor = context.getSourceAnchor();
Anchor targetAnchor = context.getTargetAnchor();
ConnectionRoute route = (ConnectionRoute) context.getNewObject();
Diagram diagram = getDiagram();
FreeFormConnection connection = peService
.createFreeFormConnection(diagram);
connection.setStart(sourceAnchor);
connection.setEnd(targetAnchor);
for (int i = 1; i < route.size() - 1; ++i) {
connection.getBendpoints().add(route.get(i));
}
FeatureSupport.setPropertyValue(connection, CONNECTION, "" + route.getId()); //$NON-NLS-1$
Polyline connectionLine = Graphiti.getGaService().createPolyline(
connection);
connectionLine.setLineWidth(1);
connectionLine.setLineStyle(LineStyle.DASH);
IColorConstant foreground = new ColorConstant(255, 120, 255);
int w = 3;
int l = 15;
ConnectionDecorator decorator = peService
.createConnectionDecorator(connection, false, 1.0, true);
Polyline arrowhead = gaService.createPolygon(decorator, new int[] {
-l, w, 0, 0, -l, -w, -l, w });
arrowhead.setForeground(gaService.manageColor(diagram, foreground));
connectionLine.setForeground(gaService.manageColor(diagram,
foreground));
FeatureSupport.setToolTip(connection.getGraphicsAlgorithm(), route.toString());
return connection;
}
}
/**
* The Class DeleteRoutingConnectionFeature.
*/
protected class DeleteRoutingConnectionFeature extends DefaultDeleteFeature {
/**
* Instantiates a new delete routing connection feature.
*
* @param fp the fp
*/
public DeleteRoutingConnectionFeature(IFeatureProvider fp) {
super(fp);
}
/* (non-Javadoc)
* @see org.eclipse.graphiti.ui.features.DefaultDeleteFeature#canDelete(org.eclipse.graphiti.features.context.IDeleteContext)
*/
@Override
public boolean canDelete(IDeleteContext context) {
return true;
}
/**
* Delete.
*/
public void delete() {
delete(null);
}
/* (non-Javadoc)
* @see org.eclipse.graphiti.ui.features.DefaultDeleteFeature#delete(org.eclipse.graphiti.features.context.IDeleteContext)
*/
@Override
public void delete(IDeleteContext context) {
List<Connection> deleted = new ArrayList<Connection>();
deleted.addAll(getDiagram().getConnections());
for (Connection connection : deleted) {
if (Graphiti.getPeService().getProperty(connection, RoutingNet.CONNECTION)!=null) {
context = new DeleteContext(connection);
super.delete(context);
}
}
}
}
}