| /******************************************************************************* |
| * 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(); |
| } |
| } |