/******************************************************************************* * 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.ui.features; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; import java.util.Map.Entry; import org.eclipse.bpmn2.BaseElement; import org.eclipse.bpmn2.Bpmn2Package; import org.eclipse.bpmn2.FlowElement; import org.eclipse.bpmn2.FlowElementsContainer; import org.eclipse.bpmn2.FlowNode; import org.eclipse.bpmn2.Lane; import org.eclipse.bpmn2.SequenceFlow; import org.eclipse.bpmn2.SubProcess; import org.eclipse.bpmn2.modeler.core.features.AbstractBpmn2CustomFeature; import org.eclipse.bpmn2.modeler.core.features.GraphitiConstants; import org.eclipse.bpmn2.modeler.core.features.IBpmn2CreateFeature; import org.eclipse.bpmn2.modeler.core.features.SubMenuCustomFeature; import org.eclipse.bpmn2.modeler.core.model.Bpmn2ModelerFactory; import org.eclipse.bpmn2.modeler.core.preferences.Bpmn2Preferences; import org.eclipse.bpmn2.modeler.core.preferences.ModelEnablements; 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.ui.diagram.Bpmn2ToolBehaviorProvider; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EClass; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EObject; import org.eclipse.graphiti.datatypes.IDimension; import org.eclipse.graphiti.datatypes.ILocation; import org.eclipse.graphiti.features.ICreateFeature; import org.eclipse.graphiti.features.IFeatureProvider; import org.eclipse.graphiti.features.context.IContext; import org.eclipse.graphiti.features.context.ICustomContext; import org.eclipse.graphiti.features.context.impl.AddConnectionContext; import org.eclipse.graphiti.features.context.impl.CreateConnectionContext; import org.eclipse.graphiti.features.context.impl.CreateContext; import org.eclipse.graphiti.features.context.impl.MoveShapeContext; import org.eclipse.graphiti.features.impl.DefaultMoveShapeFeature; import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm; import org.eclipse.graphiti.mm.algorithms.styles.Point; import org.eclipse.graphiti.mm.pictograms.Connection; import org.eclipse.graphiti.mm.pictograms.ContainerShape; 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.palette.IPaletteCompartmentEntry; import org.eclipse.graphiti.palette.IToolEntry; import org.eclipse.graphiti.palette.impl.ObjectCreationToolEntry; import org.eclipse.graphiti.services.Graphiti; import org.eclipse.graphiti.services.ILayoutService; import org.eclipse.graphiti.tb.ContextMenuEntry; import org.eclipse.graphiti.tb.IContextMenuEntry; import org.eclipse.graphiti.tb.IToolBehaviorProvider; import org.eclipse.graphiti.ui.editor.DiagramEditor; import org.eclipse.graphiti.ui.internal.util.ui.PopupMenu; import org.eclipse.graphiti.ui.internal.util.ui.PopupMenu.CascadingMenu; import org.eclipse.jface.viewers.ILabelProvider; import org.eclipse.jface.viewers.ILabelProviderListener; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.widgets.Display; /** * @author Bob Brodt * */ public abstract class AbstractAppendNodeFeature<T extends FlowNode> extends AbstractBpmn2CustomFeature { protected boolean changesDone = false; protected Bpmn2Preferences preferences; // label provider for the popup menu that displays allowable Activity subclasses private static ILabelProvider labelProvider = new ILabelProvider() { public void removeListener(ILabelProviderListener listener) { } public boolean isLabelProperty(Object element, String property) { return false; } public void dispose() { } public void addListener(ILabelProviderListener listener) { } public String getText(Object element) { if (element instanceof ObjectCreationToolEntry) { ObjectCreationToolEntry te = (ObjectCreationToolEntry)element; return te.getLabel(); } else if (element instanceof IPaletteCompartmentEntry) { IPaletteCompartmentEntry ce = (IPaletteCompartmentEntry)element; return ce.getLabel(); } return "?"; //$NON-NLS-1$ } public Image getImage(Object element) { return null; } }; /** * @param fp */ public AbstractAppendNodeFeature(IFeatureProvider fp) { super(fp); } @Override public boolean canExecute(ICustomContext context) { CreateContext createContext = prepareCreateContext(context); if (createContext==null) return false; List<IToolEntry> tools = getTools(); if (tools.size()==0) return false; for (IToolEntry tool : tools) { ICreateFeature feature = ((ObjectCreationToolEntry)tool).getCreateFeature(); if (!feature.canCreate(createContext)) return false; } // build submenu features String key = GraphitiConstants.CONTEXT_MENU_ENTRY + this.getName(); IContextMenuEntry contextMenuEntry = (IContextMenuEntry) context.getProperty(key); if (contextMenuEntry!=null) { if (contextMenuEntry.getChildren().length == 0) { Bpmn2ToolBehaviorProvider toolProvider = getToolProvider(); LinkedHashMap<IPaletteCompartmentEntry, ContextMenuEntry> categories = new LinkedHashMap<IPaletteCompartmentEntry, ContextMenuEntry>(); ContextMenuEntry cme = null; for (IToolEntry tool : tools) { IPaletteCompartmentEntry ce = toolProvider.getCategory(tool); if (ce!=null) { if (categories.containsKey(ce)) { cme = categories.get(ce); } else { cme = new ContextMenuEntry(this, context); cme.setText(ce.getLabel()); categories.put(ce, cme); } } } if (categories.size()>1) { List<ContextMenuEntry> entries = new ArrayList<ContextMenuEntry>(); for (IToolEntry tool : tools) { IPaletteCompartmentEntry ce = toolProvider.getCategory(tool); if (ce!=null) { ICreateFeature feature = ((ObjectCreationToolEntry)tool).getCreateFeature(); SubMenuCustomFeature submenuFeature = new SubMenuCustomFeature(this, feature); cme = categories.get(ce); ContextMenuEntry e = new ContextMenuEntry(submenuFeature, context); e.setText(tool.getLabel()); cme.add(e); if (!entries.contains(cme)) { contextMenuEntry.add(cme); entries.add(cme); } } } } else { for (IToolEntry tool : tools) { ICreateFeature feature = ((ObjectCreationToolEntry)tool).getCreateFeature(); SubMenuCustomFeature submenuFeature = new SubMenuCustomFeature(this, feature); cme = new ContextMenuEntry(submenuFeature, context); cme.setText(tool.getLabel()); contextMenuEntry.add(cme); } } } } return true; } @Override public boolean isAvailable(IContext context) { if (context instanceof ICustomContext) { ICustomContext cc = (ICustomContext) context; PictogramElement pes[] = cc.getPictogramElements(); if (pes.length!=1) { return false; } BaseElement source = BusinessObjectUtil.getBusinessObject(context, BaseElement.class); if (source instanceof SubProcess) { SubProcess subProcess = (SubProcess) source; if (subProcess.isTriggeredByEvent()) return false; } } return getTools().size()>0; } /* (non-Javadoc) * @see org.eclipse.graphiti.features.custom.ICustomFeature#execute(org.eclipse.graphiti.features.context.ICustomContext) */ @Override public void execute(ICustomContext context) { PictogramElement[] pes = context.getPictogramElements(); if (pes != null && pes.length == 1) { PictogramElement pe = pes[0]; Object bo = getBusinessObjectForPictogramElement(pe); preferences = Bpmn2Preferences.getInstance((EObject)bo); if (pe instanceof ContainerShape && bo instanceof FlowNode) { ContainerShape oldShape = (ContainerShape)pe; // Let user select the new type of object to append. The selection will // be from a list of subtypes of <code>T</code> as defined by the various // AbstractAppendNodeNodeFeature specializations; for example the class // AppendActivityFeature will construct a popup list of all Activity subclasses // e.g. Task, ScriptTask, SubProcess, etc. ICreateFeature createFeature = (ICreateFeature) context.getProperty(GraphitiConstants.CREATE_FEATURE); if (createFeature==null) createFeature = selectNewShape(); if (createFeature!=null) { CreateContext createContext = prepareCreateContext(context); if (createFeature.canCreate(createContext)) { // if user made a selection, then create the new shape... ContainerShape newShape = createNewShape(oldShape, createFeature, createContext); // ...and connect this shape to the new one with a SequenceFlow... createNewConnection(oldShape, newShape); // .. then reroute the connection FeatureSupport.updateConnections(getFeatureProvider(), newShape); getFeatureProvider(). getDiagramTypeProvider(). getDiagramBehavior(). getDiagramContainer(). setPictogramElementForSelection(newShape); changesDone = true; } } } } } protected ICreateFeature selectNewShape() { Bpmn2ToolBehaviorProvider toolProvider = getToolProvider(); List<IToolEntry> tools = getTools(); ICreateFeature feature = null; // show popup menu boolean doit = tools.size()>0; if (doit) { // figure out if we need a cascading menu: If there are more than one categories // involved for the tools that have been selected, then create a cascading popup menu LinkedHashMap<IPaletteCompartmentEntry, List<IToolEntry>> categories = new LinkedHashMap<IPaletteCompartmentEntry, List<IToolEntry>>(); List<IToolEntry> categorizedTools; List<IToolEntry> uncategorizedTools = new ArrayList<IToolEntry>(); for (IToolEntry te : tools) { IPaletteCompartmentEntry ce = toolProvider.getCategory(te); if (ce!=null) { if (categories.containsKey(ce)) { categorizedTools = categories.get(ce); } else { categorizedTools = new ArrayList<IToolEntry>(); categories.put(ce, categorizedTools); } categorizedTools.add(te); } else { uncategorizedTools.add(te); } } IToolEntry tool = tools.get(0); feature = ((ObjectCreationToolEntry)tool).getCreateFeature(); if (tools.size()>1) { PopupMenu popupMenu = null; if (categories.size()>1) { List<CascadingMenu> cascadingMenus = new ArrayList<CascadingMenu>(); for (Entry<IPaletteCompartmentEntry, List<IToolEntry>> entry : categories.entrySet()) { PopupMenu subMenu = new PopupMenu(entry.getValue(), labelProvider); CascadingMenu cascadingMenu = new CascadingMenu(entry.getKey(), subMenu); cascadingMenus.add(cascadingMenu); } popupMenu = new PopupMenu(cascadingMenus, labelProvider); } else { popupMenu = new PopupMenu(tools, labelProvider); } doit = popupMenu.show(Display.getCurrent().getActiveShell()); if (doit) { Object result = popupMenu.getResult(); if (result instanceof List) { for (Object o : (List)result) { if (o instanceof IToolEntry) { tool = (IToolEntry)o; break; } } } else if (result instanceof IToolEntry) tool = (IToolEntry)result; feature = ((ObjectCreationToolEntry)tool).getCreateFeature(); } else feature = null; } } return feature; } protected List<EClass> getAvailableTypes() { DiagramEditor editor = (DiagramEditor)getDiagramEditor(); ModelEnablements enablements = (ModelEnablements)editor.getAdapter(ModelEnablements.class); EClass newType = getBusinessObjectClass(); // build a list of possible subclasses for the popup menu List<EClass> subtypes = new ArrayList<EClass>(); for (EClassifier ec : Bpmn2Package.eINSTANCE.getEClassifiers() ) { if (ec instanceof EClass) { if ( ((EClass) ec).isAbstract()) { continue; } EList<EClass>superTypes = ((EClass)ec).getEAllSuperTypes(); if (superTypes.contains(newType) && enablements.isEnabled((EClass)ec)) { if (ec!=Bpmn2Package.eINSTANCE.getBoundaryEvent() && ec!=Bpmn2Package.eINSTANCE.getStartEvent()) { subtypes.add((EClass)ec); } } } } return subtypes; } protected ContainerShape createNewShape(ContainerShape oldShape, ICreateFeature createFeature, CreateContext createContext) { ILayoutService layoutService = Graphiti.getLayoutService(); boolean horz = preferences.isHorizontalDefault(); ILocation loc = layoutService.getLocationRelativeToDiagram(oldShape); int x = loc.getX(); int y = loc.getY(); int xOffset = 0; int yOffset = 0; GraphicsAlgorithm ga = oldShape.getGraphicsAlgorithm(); int width = ga.getWidth(); int height = ga.getHeight(); FlowElement newObject; ContainerShape newShape; createContext.setX(0); createContext.setY(0); Object[] created = createFeature.create(createContext); if (created[0] instanceof List) { // this will happen if the createFeature is a CompoundCreateFeature // for example an Event with an EventDefinition child element newObject = (FlowElement) ((List)created[0]).get(0); newShape = (ContainerShape) ((List)created[0]).get(1); } else { newObject = (FlowElement) created[0]; newShape = (ContainerShape) created[1]; } ContainerShape containerShape = oldShape.getContainer(); if (containerShape!=getDiagram()) { // we are adding a new shape to a container (e.g a SubProcess) // so we need to adjust the location to be relative to the // container instead of the diagram loc = layoutService.getLocationRelativeToDiagram(containerShape); xOffset = loc.getX(); yOffset = loc.getY(); } BaseElement oldObject = BusinessObjectUtil.getFirstElementOfType(oldShape, BaseElement.class); if (oldObject instanceof Lane) { ((Lane)oldObject).getFlowNodeRefs().add((FlowNode)newObject); } // move the new shape so that it does not collide with an existing shape MoveShapeContext moveContext = new MoveShapeContext(newShape);//new AreaContext(), newObject); DefaultMoveShapeFeature moveFeature = (DefaultMoveShapeFeature)getFeatureProvider().getMoveShapeFeature(moveContext); IDimension size = GraphicsUtil.calculateSize(newShape); int wOffset = 50; int hOffset = 50; int w = size.getWidth(); int h = size.getHeight(); if (horz) { x += width + wOffset + w/2; y += height/2 - h/2; boolean done = false; while (!done) { done = true; List<Shape> shapes = getFlowElementChildren(containerShape); for (Shape s : shapes) { if (GraphicsUtil.intersects(s, x-w/2, y-h/2, w, h)) { y += 100; done = false; break; } } } } else { x += width/2 - w/2; y += height + hOffset + h/2; boolean done = false; while (!done) { done = true; List<Shape> shapes = getFlowElementChildren(containerShape); for (Shape s : shapes) { if (GraphicsUtil.intersects(s, x-w/2, y-h/2, w, h)) { x += 100; done = false; break; } } } } moveContext.setX(x - xOffset); moveContext.setY(y - yOffset); moveContext.setSourceContainer( oldShape.getContainer() ); moveContext.setTargetContainer( oldShape.getContainer() ); if (moveFeature.canMoveShape(moveContext)) moveFeature.moveShape(moveContext); return newShape; } protected List<Shape> getFlowElementChildren(ContainerShape containerShape) { List<Shape> children = new ArrayList<Shape>(); for (Shape s : containerShape.getChildren()) { FlowElement bo = BusinessObjectUtil.getFirstElementOfType(s, FlowElement.class); if (s instanceof ContainerShape && bo!=null) { children.add(s); } } return children; } protected Connection createNewConnection(ContainerShape oldShape, ContainerShape newShape) { Point p; p = GraphicsUtil.getShapeCenter(newShape); FixPointAnchor sa = AnchorUtil.createAnchor(oldShape, p); p = GraphicsUtil.getShapeCenter(oldShape); FixPointAnchor ta = AnchorUtil.createAnchor(newShape, p); CreateConnectionContext ccc = new CreateConnectionContext(); ccc.setSourcePictogramElement(oldShape); ccc.setTargetPictogramElement(newShape); ccc.setSourceAnchor(sa); ccc.setTargetAnchor(ta); FlowNode oldObject = BusinessObjectUtil.getFirstElementOfType(oldShape, FlowNode.class); FlowNode newObject = BusinessObjectUtil.getFirstElementOfType(newShape, FlowNode.class); // create a new SequenceFlow to connect the old and new FlowNodes SequenceFlow sequenceFlow = Bpmn2ModelerFactory.createObject(oldObject.eResource(), SequenceFlow.class); FlowElementsContainer container = (FlowElementsContainer) oldObject.eContainer(); container.getFlowElements().add(sequenceFlow); sequenceFlow.setSourceRef(oldObject); sequenceFlow.setTargetRef(newObject); sequenceFlow.setName(null); AddConnectionContext acc = new AddConnectionContext(ccc.getSourceAnchor(), ccc.getTargetAnchor()); acc.setNewObject(sequenceFlow); Connection connection = (Connection)getFeatureProvider().addIfPossible(acc); return connection; } protected Bpmn2ToolBehaviorProvider getToolProvider() { IToolBehaviorProvider[] toolProviders = getFeatureProvider().getDiagramTypeProvider().getAvailableToolBehaviorProviders(); for (IToolBehaviorProvider tp : toolProviders) { if (tp instanceof Bpmn2ToolBehaviorProvider) { return (Bpmn2ToolBehaviorProvider)tp; } } return null; } protected List<IToolEntry> getTools() { List<IToolEntry> tools = new ArrayList<IToolEntry>(); Bpmn2ToolBehaviorProvider toolProvider = getToolProvider(); if (toolProvider!=null) { List<EClass> availableTypes = getAvailableTypes(); for (IToolEntry te : toolProvider.getTools()) { if (te instanceof ObjectCreationToolEntry) { ObjectCreationToolEntry cte = (ObjectCreationToolEntry)te; ICreateFeature f = cte.getCreateFeature(); if (f instanceof IBpmn2CreateFeature) { EClass type = ((IBpmn2CreateFeature)f).getBusinessObjectClass(); if (availableTypes.contains(type)) tools.add(te); } } } } return tools; } /** * @return */ public abstract EClass getBusinessObjectClass(); @Override public boolean hasDoneChanges() { return changesDone; } protected static CreateContext prepareCreateContext(ICustomContext context) { CreateContext cc = new CreateContext(); PictogramElement[] pes = context.getPictogramElements(); if (pes==null || pes.length!=1) return null; EObject container = pes[0].eContainer(); if (!(container instanceof ContainerShape)) return null; cc.setTargetContainer((ContainerShape)container); return cc; } }