/******************************************************************************* * Copyright (c) 2011, 2012, 2013 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.List; import org.eclipse.bpmn2.Activity; import org.eclipse.bpmn2.BaseElement; import org.eclipse.bpmn2.BoundaryEvent; import org.eclipse.bpmn2.FlowNode; import org.eclipse.bpmn2.Lane; import org.eclipse.bpmn2.Participant; import org.eclipse.bpmn2.SequenceFlow; import org.eclipse.bpmn2.di.BPMNDiagram; import org.eclipse.bpmn2.modeler.core.di.DIUtils; import org.eclipse.graphiti.datatypes.IDimension; import org.eclipse.graphiti.datatypes.ILocation; import org.eclipse.graphiti.features.IMoveShapeFeature; import org.eclipse.graphiti.features.IResizeShapeFeature; import org.eclipse.graphiti.features.context.impl.MoveShapeContext; import org.eclipse.graphiti.features.context.impl.ResizeShapeContext; 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.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.platform.IDiagramContainer; import org.eclipse.graphiti.services.Graphiti; import org.eclipse.graphiti.services.ILayoutService; /** * */ public class ShapeLayoutManager { private static final int HORZ_PADDING = 50; private static final int VERT_PADDING = 50; private static final ILayoutService layoutService = Graphiti.getLayoutService(); private IDiagramContainer diagramContainer; public ShapeLayoutManager(IDiagramContainer diagramContainer) { this.diagramContainer = diagramContainer; } public void layout(BaseElement container) { layout( getContainerShape(container) ); diagramContainer.selectPictogramElements(new PictogramElement[]{}); } public void layout(ContainerShape container) { layout(container, 0); } private void layout(ContainerShape container, int level) { if (container==null) return; // Collect all child shapes: this excludes any label shapes // (which also happen to be ContainerShape objects); we want ONLY the // graphical objects that have corresponding BPMNShape objects. List<ContainerShape> childShapes = new ArrayList<ContainerShape>(); for (int i=0; i<container.getChildren().size(); ++i) { PictogramElement pe = container.getChildren().get(i); if (isChildShape(pe)) { ContainerShape childContainer = (ContainerShape)pe; boolean hasChildren = false; for (Shape shape : childContainer.getChildren()) { if (isChildShape(shape)) { hasChildren = true; break; } } if (hasChildren) layout(childContainer, level+1); // for some unknown reason, Diagram children are inserted // in reverse order by Graphiti if (container instanceof Diagram) childShapes.add(0,childContainer); else childShapes.add(childContainer); } } // layout child shapes from right to left; // shapes are sorted into bins according to the number of incoming // and ougtoing SequenceFlow connections: // 1. shapes that have only outgoing connections are added to the startShapes bin // 2. shapes with only incoming connections are tossed into the endShapes bin // 3. shapes with both incoming and outgoing connections are in the middleShapes bin // 4. shapes with no connections are in the unconnectShapes bin List<ContainerShape> startShapes = new ArrayList<ContainerShape>(); List<ContainerShape> unconnectedShapes = new ArrayList<ContainerShape>(); List<ContainerShape> middleShapes = new ArrayList<ContainerShape>(); List<ContainerShape> endShapes = new ArrayList<ContainerShape>(); for (ContainerShape child : childShapes) { if (!child.isActive()) continue; BaseElement be = BusinessObjectUtil.getFirstBaseElement(child); if (be instanceof Participant && ModelUtil.isParticipantBand((Participant)be)) continue; if (be instanceof BoundaryEvent) continue; List<SequenceFlow> incomingFlows = getIncomingSequenceFlows(child); List<SequenceFlow> outgoingFlows = getOutgoingSequenceFlows(child); int incomingCount = 0; int outgoingCount = 0; // this may be a start or end shape depending on whether ALL of the incoming // our outgoing flows are from/to shapes that are in this container. for (SequenceFlow sf : incomingFlows) { ContainerShape shape = getContainerShape(sf.getSourceRef()); if (childShapes.contains(shape) && shape!=child) ++incomingCount; } for (SequenceFlow sf : outgoingFlows) { ContainerShape shape = getContainerShape(sf.getTargetRef()); if (childShapes.contains(shape) && shape!=child) ++outgoingCount; } if (incomingCount==0) { if (outgoingCount==0) unconnectedShapes.add(child); else startShapes.add(child); } else if (outgoingCount==0) { endShapes.add(child); } else { middleShapes.add(child); } } // now build threads of sequence flows starting with all of the startShapes List<List<ContainerShape[]>> threads = new ArrayList<List<ContainerShape[]>>(); if (startShapes.size()>0) { for (ContainerShape child : startShapes) { List<ContainerShape[]> thread = new ArrayList<ContainerShape[]>(); thread.add(new ContainerShape[] {child}); buildThread(child, childShapes, thread); threads.add(thread); } } // arrange the threads int x = HORZ_PADDING; int y = VERT_PADDING; for (List<ContainerShape[]> thread : threads) { // stack the threads on top of each other x = HORZ_PADDING; int threadHeight = 0; for (ContainerShape[] group : thread) { int groupHeight = (group.length-1) * VERT_PADDING; for (ContainerShape shape : group) { IDimension size = GraphicsUtil.calculateSize(shape); groupHeight += size.getHeight(); if (groupHeight > threadHeight) { threadHeight = groupHeight; } } threadHeight += (group.length-1) * VERT_PADDING; } for (ContainerShape[] group : thread) { int groupWidth = 0; int groupHeight = (group.length-1) * VERT_PADDING; for (ContainerShape shape : group) { IDimension size = GraphicsUtil.calculateSize(shape); groupHeight += size.getHeight(); } int sy = y + (threadHeight/2 - groupHeight/2); for (ContainerShape shape : group) { IDimension size = GraphicsUtil.calculateSize(shape); if (size.getWidth()>groupWidth) { groupWidth = size.getWidth(); } moveShape(container, shape, x, sy); sy += size.getHeight() + VERT_PADDING; } x += groupWidth + HORZ_PADDING; } y += threadHeight + VERT_PADDING; } stackShapes(container, unconnectedShapes); if (startShapes.size()==0 && endShapes.size()==0 && middleShapes.size()>0) stackShapes(container, middleShapes); // now resize the container so that all children are visible if (!(container instanceof Diagram)) { resizeContainerShape(container); } for (ContainerShape child : childShapes) { if (!child.isActive()) continue; BaseElement be = BusinessObjectUtil.getFirstBaseElement(child); if (be instanceof Participant && ModelUtil.isParticipantBand((Participant)be)) continue; for (Connection c : getIncomingConnections(child)) { AnchorContainer targetShape = child; FixPointAnchor sourceAnchor = (FixPointAnchor)c.getStart(); FixPointAnchor targetAnchor = (FixPointAnchor)c.getEnd(); AnchorContainer sourceShape = sourceAnchor.getParent(); AnchorUtil.moveAnchor(sourceAnchor, GraphicsUtil.getShapeCenter(targetShape)); AnchorUtil.moveAnchor(targetAnchor, GraphicsUtil.getShapeCenter(sourceShape)); DIUtils.updateDIEdge(c); } for (Connection c : getOutgoingConnections(child)) { AnchorContainer sourceShape = child; FixPointAnchor sourceAnchor = (FixPointAnchor)c.getStart(); FixPointAnchor targetAnchor = (FixPointAnchor)c.getEnd(); AnchorContainer targetShape = targetAnchor.getParent(); AnchorUtil.moveAnchor(sourceAnchor, GraphicsUtil.getShapeCenter(targetShape)); AnchorUtil.moveAnchor(targetAnchor, GraphicsUtil.getShapeCenter(sourceShape)); DIUtils.updateDIEdge(c); } } } private void stackShapes(ContainerShape container, List<ContainerShape> unconnectedShapes) { // stack any unconnected shapes on top of each other // first stack shapes that are NOT containers (like DataObject, DataStore, etc.) int maxWidth = 0; int maxHeight = 0; int x = HORZ_PADDING; int y = VERT_PADDING; if (unconnectedShapes.size()>0) { List<ContainerShape> children = getContainerShapeChildren(container); for (ContainerShape shape : unconnectedShapes) { BaseElement be = BusinessObjectUtil.getFirstBaseElement(shape); if (getContainerShapeChildren(shape).size()==0 && !(be instanceof Lane)) { IDimension size = GraphicsUtil.calculateSize(shape); Point p = moveShape(container, shape, x, y, children); x = p.getX(); y = p.getY(); y += size.getHeight() + VERT_PADDING; if (size.getWidth() > maxWidth) maxWidth = size.getWidth(); } } if (y>maxHeight) maxHeight = y; // now handle all containers (Lane, SubProcess, Pool, etc.) x += maxWidth + HORZ_PADDING; y = VERT_PADDING; for (ContainerShape shape : unconnectedShapes) { BaseElement be = BusinessObjectUtil.getFirstBaseElement(shape); if (getContainerShapeChildren(shape).size()!=0 || be instanceof Lane) { IDimension size = GraphicsUtil.calculateSize(shape); Point p = moveShape(container, shape, x, y, children); x = p.getX(); y = p.getY(); if (be instanceof Lane) { resizeContainerShape(shape); } y += size.getHeight() + VERT_PADDING; if (size.getWidth() > maxWidth) maxWidth = size.getWidth(); } } } if (container instanceof Diagram) { x = HORZ_PADDING; y = VERT_PADDING; for (ContainerShape shape : unconnectedShapes) { moveShape(container, shape, x, y); IDimension size = GraphicsUtil.calculateSize(shape); ILocation loc = Graphiti.getPeService().getLocationRelativeToDiagram(shape); x = loc.getX(); y = loc.getY(); y += size.getHeight() + VERT_PADDING; } } } private boolean moveShape(ContainerShape container, ContainerShape shape, int x, int y) { MoveShapeContext context = new MoveShapeContext(shape); context.setLocation(x, y); context.setSourceContainer(container); context.setTargetContainer(container); IMoveShapeFeature moveFeature = diagramContainer.getDiagramTypeProvider().getFeatureProvider().getMoveShapeFeature(context); if (moveFeature.canMoveShape(context)) { moveFeature.moveShape(context); return true; } return false; } private Point moveShape(ContainerShape container, ContainerShape child, int x, int y, List<ContainerShape> allChildren) { boolean intersects; do { intersects = false; BaseElement be = BusinessObjectUtil.getFirstBaseElement(child); if (be instanceof BoundaryEvent) { // special handling for Boundary Events Activity activity = ((BoundaryEvent)be).getAttachedToRef(); ContainerShape activityShape = null; for (ContainerShape s : allChildren) { if (s!=child) { if (activity == BusinessObjectUtil.getFirstBaseElement(s)) { activityShape = s; break; } } } if (activityShape!=null) { ILocation activityLoc = Graphiti.getPeLayoutService().getLocationRelativeToDiagram(activityShape); IDimension activitySize = GraphicsUtil.calculateSize(activityShape); IDimension eventSize = GraphicsUtil.calculateSize(child); int index = activity.getBoundaryEventRefs().indexOf(be); int count = activity.getBoundaryEventRefs().size(); int deltaX = activitySize.getWidth() / 2; if (count>1) { deltaX = index * activitySize.getWidth() / (count-1); } moveShape(activityShape, child, deltaX - eventSize.getWidth()/2, activitySize.getHeight() - eventSize.getHeight()/2); y = 0; break; } } else { if (!moveShape(container, child, x, y)) break; } for (ContainerShape c : allChildren) { if (c!=child && GraphicsUtil.intersects(child, c)) { BaseElement childBE = BusinessObjectUtil.getFirstBaseElement(child); BaseElement cBE = BusinessObjectUtil.getFirstBaseElement(c); if (cBE instanceof BoundaryEvent) { // these are allowed to overlap their attached Activities if (((BoundaryEvent)cBE).getAttachedToRef() == childBE) { intersects = false; break; } } intersects = true; y += VERT_PADDING; } } } while (intersects); return Graphiti.getCreateService().createPoint(x, y); } private boolean resizeContainerShape(ContainerShape container) { List<ContainerShape> children = getContainerShapeChildren(container); ILocation containerLocation = layoutService.getLocationRelativeToDiagram(container); int width = 0; int height = 0; for (ContainerShape child : children) { if (BusinessObjectUtil.getFirstBaseElement(child)!=null) { IDimension size = GraphicsUtil.calculateSize(child); ILocation location = layoutService.getLocationRelativeToDiagram(child); int x = location.getX() - containerLocation.getX(); int y = location.getY() - containerLocation.getY(); int w = x + size.getWidth(); int h = y + size.getHeight(); if (w>width) width = w; if (h>height) height = h; } } if ( BusinessObjectUtil.getFirstBaseElement(container) instanceof Lane) { if (width < 800) width = 800; if (height < 100) height = 100; } if (width!=0 && height!=0) return resizeShape(container, width + HORZ_PADDING, height + VERT_PADDING); return false; } private boolean resizeShape(ContainerShape container, int width, int height) { ResizeShapeContext context = new ResizeShapeContext(container); int x = container.getGraphicsAlgorithm().getX(); int y = container.getGraphicsAlgorithm().getY(); context.setLocation(x, y); context.setSize(width, height); IResizeShapeFeature resizeFeature = diagramContainer.getDiagramTypeProvider().getFeatureProvider().getResizeShapeFeature(context); if (resizeFeature.canResizeShape(context)) { resizeFeature.resizeShape(context); return true; } return false; } private boolean threadContains(List<ContainerShape[]> thread, ContainerShape shape) { for (ContainerShape[] shapes : thread) { for (ContainerShape s : shapes) { if (s==shape) return true; } } return false; } private void buildThread(ContainerShape shape, List<ContainerShape> childShapes, List<ContainerShape[]> thread) { List<ContainerShape> bin = new ArrayList<ContainerShape>(); List<SequenceFlow> flows = getOutgoingSequenceFlows(shape); for (SequenceFlow flow : flows) { FlowNode target = flow.getTargetRef(); // make sure the target shape is also a child of this container // in case a SequenceFlow crosses the container boundary ContainerShape targetShape = getContainerShape(target); if (childShapes.contains(targetShape) && !threadContains(thread, targetShape)) { bin.add(targetShape); } } if (!bin.isEmpty()) { thread.add(bin.toArray(new ContainerShape[bin.size()])); for (ContainerShape nextShape : bin) { buildThread(nextShape, childShapes, thread); } } } private List<SequenceFlow> getIncomingSequenceFlows(ContainerShape shape) { List<SequenceFlow> flows = new ArrayList<SequenceFlow>(); for (Anchor a : shape.getAnchors()) { for (Connection c : a.getIncomingConnections()) { BaseElement be = BusinessObjectUtil.getFirstBaseElement(c); if (be instanceof SequenceFlow) { flows.add((SequenceFlow)be); } } } return flows; } private List<SequenceFlow> getOutgoingSequenceFlows(ContainerShape shape) { List<SequenceFlow> flows = new ArrayList<SequenceFlow>(); for (Anchor a : shape.getAnchors()) { for (Connection c : a.getOutgoingConnections()) { BaseElement be = BusinessObjectUtil.getFirstBaseElement(c); if (be instanceof SequenceFlow) { flows.add((SequenceFlow)be); } } } return flows; } private List<Connection> getIncomingConnections(ContainerShape shape) { List<Connection> connections = new ArrayList<Connection>(); for (Anchor a : shape.getAnchors()) { connections.addAll(a.getIncomingConnections()); } return connections; } private List<Connection> getOutgoingConnections(ContainerShape shape) { List<Connection> connections = new ArrayList<Connection>(); for (Anchor a : shape.getAnchors()) { connections.addAll(a.getOutgoingConnections()); } return connections; } private ContainerShape getContainerShape(BaseElement be) { Diagram diagram = null; BPMNDiagram bpmnDiagram = DIUtils.findBPMNDiagram(be, true); if (bpmnDiagram != null) { diagram = DIUtils.findDiagram(diagramContainer.getDiagramBehavior(), bpmnDiagram); if (diagram==null) { System.out.println("Diagram is null"); //$NON-NLS-1$ } } if (diagram!=null) { List<PictogramElement> list = Graphiti.getLinkService().getPictogramElements(diagram, be); for (PictogramElement pe : list) { if (isChildShape(pe)) { if (BusinessObjectUtil.getFirstBaseElement(pe) == be) return (ContainerShape)pe; } } // maybe the BaseElement is a root element (like a Process or Choreography)? if (bpmnDiagram.getPlane().getBpmnElement() == be) return diagram; } System.out.println("Container is null!"); //$NON-NLS-1$ return null; } private List<ContainerShape> getContainerShapeChildren(ContainerShape container) { List<ContainerShape> childShapes = new ArrayList<ContainerShape>(); for (PictogramElement pe : container.getChildren()) { if (isChildShape(pe)) { childShapes.add((ContainerShape)pe); } } return childShapes; } private boolean isChildShape(PictogramElement pe) { return pe instanceof ContainerShape && !FeatureSupport.isLabelShape((Shape)pe); } }