blob: 8fa7fc0d85be43d45591f381ce5c296325272e6a [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 Ivar Meikas
******************************************************************************/
package org.eclipse.bpmn2.modeler.core.features;
import org.eclipse.bpmn2.BaseElement;
import org.eclipse.bpmn2.EndEvent;
import org.eclipse.bpmn2.Lane;
import org.eclipse.bpmn2.Participant;
import org.eclipse.bpmn2.StartEvent;
import org.eclipse.bpmn2.di.BPMNDiagram;
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.di.DIImport;
import org.eclipse.bpmn2.modeler.core.di.DIUtils;
import org.eclipse.bpmn2.modeler.core.features.flow.AbstractCreateFlowFeature;
import org.eclipse.bpmn2.modeler.core.preferences.Bpmn2Preferences;
import org.eclipse.bpmn2.modeler.core.preferences.ShapeStyle;
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.bpmn2.modeler.core.utils.ShapeDecoratorUtil;
import org.eclipse.dd.dc.Bounds;
import org.eclipse.graphiti.IExecutionInfo;
import org.eclipse.graphiti.datatypes.ILocation;
import org.eclipse.graphiti.features.IAddFeature;
import org.eclipse.graphiti.features.ICreateConnectionFeature;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.IReason;
import org.eclipse.graphiti.features.IReconnectionFeature;
import org.eclipse.graphiti.features.context.IAddConnectionContext;
import org.eclipse.graphiti.features.context.IAddContext;
import org.eclipse.graphiti.features.context.ITargetContext;
import org.eclipse.graphiti.features.context.impl.AddContext;
import org.eclipse.graphiti.features.context.impl.CreateConnectionContext;
import org.eclipse.graphiti.features.context.impl.LayoutContext;
import org.eclipse.graphiti.features.context.impl.ReconnectionContext;
import org.eclipse.graphiti.features.context.impl.UpdateContext;
import org.eclipse.graphiti.features.impl.AbstractAddPictogramElementFeature;
import org.eclipse.graphiti.mm.GraphicsAlgorithmContainer;
import org.eclipse.graphiti.mm.algorithms.RoundedRectangle;
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.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.FixPointAnchor;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.services.IGaService;
import org.eclipse.graphiti.services.ILayoutService;
import org.eclipse.graphiti.services.IPeService;
import org.eclipse.graphiti.ui.editor.DiagramEditor;
/**
* This is the Graphiti {@code AddFeature} base class for all BPMN2 model elements which
* are associated with BPMN DI elements.
* <p>
* This class adds support for managing BPMN DI elements, i.e. BPMNShape and
* BPMNEdge.
* <p>
* Note that the BPMNLabel element is not yet supported, but will be added in a
* future release. For now, graphical shapes with labels are supported
* indirectly outside the scope of BPMN DI.
*
* @param <T> the generic type, a subclass of {@link BaseElement}
*/
public abstract class AbstractBpmn2AddFeature<T extends BaseElement>
extends AbstractAddPictogramElementFeature {
/** The ga service. */
protected final static IGaService gaService = Graphiti.getGaService();
/** The pe service. */
protected final static IPeService peService = Graphiti.getPeService();
/** The preferences. */
protected Bpmn2Preferences preferences;
/**
* Instantiates a new AddFeature.
*
* @param fp the Feature Provider instance
*/
public AbstractBpmn2AddFeature(IFeatureProvider fp) {
super(fp);
preferences = Bpmn2Preferences.getInstance(getDiagram());
}
public abstract IAddFeature getAddLabelFeature(IFeatureProvider fp);
/**
* Find the BPMNShape that references the given {@code BaseElement}.
*
* @param elem the BaseElement
* @return the BPMNShape object if found or null if it has not been created yet.
*/
protected BPMNShape findDIShape(BaseElement elem) {
try {
return DIUtils.findBPMNShape(elem);
} catch (Exception e) {
Activator.logError(e);
}
return null;
}
/**
* Creates a BPMNShape if it does not already exist, and then links it to
* the given {@code BaseElement}.
*
* @param shape the Container Shape
* @param elem the BaseElement
* @param applyDefaults if true, apply User Preference defaults for certain
* BPMN DI attributes, e.g. isHorizontal, isExpanded, etc.
* @return the BPMNShape
*/
protected BPMNShape createDIShape(Shape shape, BaseElement elem, boolean applyDefaults) {
BPMNShape bpmnShape = null;
Diagram diagram = Graphiti.getPeService().getDiagramForShape(shape);
if (diagram!=null) {
BPMNDiagram bpmnDiagram = (BPMNDiagram) BusinessObjectUtil.getBusinessObjectForPictogramElement(diagram);
bpmnShape = DIUtils.findBPMNShape(bpmnDiagram, elem);
}
else
bpmnShape = findDIShape(elem);
bpmnShape = DIUtils.createDIShape(shape, elem, bpmnShape, getFeatureProvider());
if (applyDefaults && bpmnShape!=null)
preferences.applyBPMNDIDefaults(bpmnShape, null);
return bpmnShape;
}
/**
* Creates a BPMNEdge if it does not already exist, and then links it to
* the given {@code BaseElement}.
*
* @param connection the connection
* @param elem the BaseElement
* @return the BPMNEdge
*/
protected BPMNEdge createDIEdge(Connection connection, BaseElement elem) {
BPMNDiagram bpmnDiagram = DIUtils.findBPMNDiagram(connection);
BPMNEdge edge = DIUtils.findBPMNEdge(bpmnDiagram, elem);
return DIUtils.createDIEdge(connection, elem, edge, getFeatureProvider());
}
/**
* Adjust the location of a newly constructed shape so that its center is at
* the mouse cursor position.
*
* @param context the AddContext
* @param width the new shape's width
* @param height the new shape's height
*/
protected void adjustLocation(IAddContext context, int width, int height) {
if (DIImport.isImporting(context)) {
return;
}
int x = context.getX();
int y = context.getY();
((AddContext)context).setWidth(width);
((AddContext)context).setHeight(height);
y -= height/2;
x -= width / 2;
((AddContext)context).setY(y);
((AddContext)context).setX(x);
}
/**
* Split a connection. This is used when a shape is dropped onto a
* connection; the target of the original connection is attached to the new
* shape, and a new connection is created that connects the new shape to the
* old connection's target.
*
* @param context the AddContext for the new shape. This will have the
* target connection which needs to be split
* @param containerShape the new container shape that was dropped onto the
* connection
*/
protected void splitConnection(IAddContext context, ContainerShape containerShape) {
if (context.getProperty(GraphitiConstants.IMPORT_PROPERTY) != null) {
return;
}
Object newObject = getBusinessObject(context);
Connection connection = context.getTargetConnection();
if (connection!=null) {
// determine how to split the line depending on where the new object was dropped:
// the longer segment will remain the original connection, and a new connection
// will be created for the shorter segment
ILayoutService layoutService = Graphiti.getLayoutService();
Anchor a0 = connection.getStart();
Anchor a1 = connection.getEnd();
double x0 = layoutService.getLocationRelativeToDiagram(a0).getX();
double y0 = layoutService.getLocationRelativeToDiagram(a0).getY();
double x1 = layoutService.getLocationRelativeToDiagram(a1).getX();
double y1 = layoutService.getLocationRelativeToDiagram(a1).getY();
double dx = x0 - context.getX();
double dy = y0 - context.getY();
double len0 = Math.sqrt(dx*dx + dy*dy);
dx = context.getX() - x1;
dy = context.getY() - y1;
double len1 = Math.sqrt(dx*dx + dy*dy);
AnchorContainer oldSourceContainer = connection.getStart().getParent();
AnchorContainer oldTargetContainer = connection.getEnd().getParent();
BaseElement baseElement = BusinessObjectUtil.getFirstElementOfType(connection, BaseElement.class);
ILocation targetLocation = layoutService.getLocationRelativeToDiagram(containerShape);
ReconnectionContext rc;
FixPointAnchor anchor;
if (newObject instanceof StartEvent || (len0 < len1 && !(newObject instanceof EndEvent))) {
anchor = AnchorUtil.createAnchor(containerShape, GraphicsUtil.getShapeCenter(oldTargetContainer));
rc = new ReconnectionContext(connection, connection.getStart(), anchor, targetLocation);
rc.setReconnectType(ReconnectionContext.RECONNECT_SOURCE);
rc.setTargetPictogramElement(containerShape);
}
else {
anchor = AnchorUtil.createAnchor(containerShape, GraphicsUtil.getShapeCenter(containerShape));
rc = new ReconnectionContext(connection, connection.getEnd(), anchor, targetLocation);
rc.setReconnectType(ReconnectionContext.RECONNECT_TARGET);
rc.setTargetPictogramElement(containerShape);
}
IReconnectionFeature rf = getFeatureProvider().getReconnectionFeature(rc);
rf.reconnect(rc);
if (!(newObject instanceof EndEvent) && !(newObject instanceof StartEvent)) {
// connection = get create feature, create connection
CreateConnectionContext ccc = new CreateConnectionContext();
if (len0 < len1) {
ccc.setSourcePictogramElement(oldSourceContainer);
ccc.setTargetPictogramElement(containerShape);
anchor = AnchorUtil.createAnchor(oldSourceContainer, GraphicsUtil.getShapeCenter(containerShape));
ccc.setSourceAnchor(anchor);
anchor = AnchorUtil.createAnchor(containerShape, GraphicsUtil.getShapeCenter(oldTargetContainer));
ccc.setTargetAnchor(anchor);
}
else {
ccc.setSourcePictogramElement(containerShape);
ccc.setTargetPictogramElement(oldTargetContainer);
anchor = AnchorUtil.createAnchor(containerShape, GraphicsUtil.getShapeCenter(oldTargetContainer));
ccc.setSourceAnchor(anchor);
anchor = AnchorUtil.createAnchor(oldTargetContainer, GraphicsUtil.getShapeCenter(containerShape));
ccc.setTargetAnchor(anchor);
}
Connection newConnection = null;
ICreateConnectionFeature ccf = AbstractCreateFlowFeature.getCreateFeature(getFeatureProvider(), ccc, baseElement);
if (ccf!=null)
newConnection = ccf.create(ccc);
DIUtils.updateDIEdge(newConnection);
}
DIUtils.updateDIEdge(connection);
}
}
/**
* Gets the height of a new shape based on User Preferences. If the shape is a copy of
* another shape, the height of the copied shape is used.
*
* @param context the AddContext for the new shape
* @return the height
*/
protected int getHeight(IAddContext context) {
Object copiedBpmnShape = context.getProperty(GraphitiConstants.COPIED_BPMN_DI_ELEMENT);
if (copiedBpmnShape instanceof BPMNShape) {
Bounds b = ((BPMNShape)copiedBpmnShape).getBounds();
if (b!=null)
// if (isHorizontal(context))
// return (int) b.getWidth();
return (int) b.getHeight();
}
if (context.getHeight() > 0)
return context.getHeight();
if (context.getProperty(GraphitiConstants.IMPORT_PROPERTY) == null) {
if (isHorizontal(context)) {
if (context.getWidth() > 0)
return context.getWidth();
return getWidth();
}
}
return getHeight();
}
/**
* Gets the width of a new shape based on User Preferences. If the shape is a copy of
* another shape, the width of the copied shape is used.
*
* @param context the AddContext for the new shape
* @return the width
*/
protected int getWidth(IAddContext context) {
Object copiedBpmnShape = context.getProperty(GraphitiConstants.COPIED_BPMN_DI_ELEMENT);
if (copiedBpmnShape instanceof BPMNShape) {
Bounds b = ((BPMNShape)copiedBpmnShape).getBounds();
if (b!=null) {
// if (isHorizontal(context))
// return (int) b.getHeight();
return (int) b.getWidth();
}
}
if (context.getWidth() > 0)
return context.getWidth();
if (context.getProperty(GraphitiConstants.IMPORT_PROPERTY) == null) {
if (isHorizontal(context)) {
if (context.getHeight() > 0)
return context.getHeight();
return getHeight();
}
}
return getWidth();
}
/**
* Gets the height.
*
* @return the height
*/
protected int getHeight() {
ShapeStyle ss = preferences.getShapeStyle(getBusinessObjectType());
return ss.getDefaultHeight();
}
/**
* Gets the width.
*
* @return the width
*/
protected int getWidth() {
ShapeStyle ss = preferences.getShapeStyle(getBusinessObjectType());
return ss.getDefaultWidth();
}
/**
* Checks User Preferences if horizontal layout is preferred.
*
* @param context the context
* @return true, if is horizontal
*/
protected boolean isHorizontal(ITargetContext context) {
// isHorizontal only applies to Lanes and Pools
if (context instanceof IAddContext) {
Object newObject = ((IAddContext)context).getNewObject();
if (!(newObject instanceof Lane) && !(newObject instanceof Participant))
return false;
}
if (context.getProperty(GraphitiConstants.IMPORT_PROPERTY) == null) {
// not importing - set isHorizontal to be the same as copied element or parent
Object copiedBpmnShape = context.getProperty(GraphitiConstants.COPIED_BPMN_DI_ELEMENT);
if (copiedBpmnShape instanceof BPMNShape) {
return ((BPMNShape)copiedBpmnShape).isIsHorizontal();
}
if (FeatureSupport.isTargetParticipant(context)) {
Participant targetParticipant = FeatureSupport.getTargetParticipant(context);
BPMNShape participantShape = findDIShape(targetParticipant);
if (participantShape!=null)
return participantShape.isIsHorizontal();
}
else if (FeatureSupport.isTargetLane(context)) {
Lane targetLane = FeatureSupport.getTargetLane(context);
BPMNShape laneShape = findDIShape(targetLane);
if (laneShape!=null)
return laneShape.isIsHorizontal();
}
}
return FeatureSupport.isHorizontal(context);
}
public abstract Class<? extends BaseElement> getBusinessObjectType();
public T getBusinessObject(IAddContext context) {
Object businessObject = context.getProperty(GraphitiConstants.BUSINESS_OBJECT);
if (businessObject instanceof BaseElement)
return (T)businessObject;
return (T)context.getNewObject();
}
public void putBusinessObject(IAddContext context, T businessObject) {
context.putProperty(GraphitiConstants.BUSINESS_OBJECT, businessObject);
}
public void postExecute(IExecutionInfo executionInfo) {
}
/**
* Helper function to return the GraphicsAlgorithm for a ContainerShape created by
* one of the BPMN2 Modeler's Add features. This can be used by subclasses to decorate
* the figure on the diagram.
*
* @param containerShape the container shape
* @return the graphics algorithm
*/
protected static GraphicsAlgorithmContainer getGraphicsAlgorithm(ContainerShape containerShape) {
if (containerShape.getGraphicsAlgorithm() instanceof RoundedRectangle)
return containerShape.getGraphicsAlgorithm();
if (containerShape.getChildren().size()>0) {
Shape shape = containerShape.getChildren().get(0);
return shape.getGraphicsAlgorithm();
}
return null;
}
/**
* Decorate connection. This is a placeholder for the hook function invoked
* when the connection is added to the diagram. Implementations can override
* this to change the appearance of the connection.
*
* @param context the Add Context
* @param connection the connection being added
* @param businessObject the business object, a {@code BaseElement} subclass.
*/
protected void decorateConnection(IAddConnectionContext context, Connection connection, T businessObject) {
}
/**
* Decorate shape. This is a placeholder for the hook function invoked when
* the shape is added to the diagram. Implementations can override this to
* change the appearance of the shape.
*
* @param context the Add Context
* @param containerShape the container shape being added
* @param businessObject the business object, a {@code BaseElement} subclass.
*/
protected void decorateShape(IAddContext context, ContainerShape containerShape, T businessObject) {
ShapeDecoratorUtil.createValidationDecorator(containerShape);
}
/**
* Update the given PictogramElement. A Graphiti UpdateContext is constructed by copying
* the properties from the given AddContext.
*
* @param addContext the Graphiti AddContext that was used to add the PE to the Diagram
* @param pe the PictogramElement
* @return a reason code indicating whether or not an update is needed.
*/
public IReason updatePictogramElement(IAddContext addContext, PictogramElement pe) {
UpdateContext updateContext = new UpdateContext(pe);
for (Object key : addContext.getPropertyKeys()) {
Object value = addContext.getProperty(key);
updateContext.putProperty(key, value);
}
return getFeatureProvider().updateIfPossible(updateContext);
}
/**
* Layout the given PictogramElement. A Graphiti LayoutContext is constructed by copying
* the properties from the given AddContext.
*
* @param addContext the Graphiti AddContext that was used to add the PE to the Diagram
* @param pe the PictogramElement
* @return a reason code indicating whether or not a layout is needed.
*/
public IReason layoutPictogramElement(IAddContext addContext, PictogramElement pe) {
LayoutContext layoutContext = new LayoutContext(pe);
for (Object key : addContext.getPropertyKeys()) {
Object value = addContext.getProperty(key);
layoutContext.putProperty(key, value);
}
return getFeatureProvider().layoutIfPossible(layoutContext);
}
protected DiagramEditor getDiagramEditor() {
return (DiagramEditor)getFeatureProvider().getDiagramTypeProvider().getDiagramBehavior().getDiagramContainer();
}
}