| /******************************************************************************* |
| * Copyright (c) 2005, 2012 IBM Corporation and others. |
| * All rights reserved. This program and the accompanying materials |
| * are 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: |
| * IBM Corporation - initial API and implementation |
| *******************************************************************************/ |
| package org.eclipse.bpel.ui.editparts; |
| |
| import java.util.ArrayList; |
| import java.util.EventObject; |
| import java.util.HashMap; |
| import java.util.Iterator; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.eclipse.bpel.model.Activity; |
| import org.eclipse.bpel.model.Flow; |
| import org.eclipse.bpel.model.Links; |
| import org.eclipse.bpel.ui.BPELEditor; |
| import org.eclipse.bpel.ui.BPELUIPlugin; |
| import org.eclipse.bpel.ui.IBPELUIConstants; |
| import org.eclipse.bpel.ui.commands.CompoundCommand; |
| import org.eclipse.bpel.ui.commands.SetConstraintCommand; |
| import org.eclipse.bpel.ui.editparts.borders.DrawerBorder; |
| import org.eclipse.bpel.ui.editparts.borders.FlowBorder; |
| import org.eclipse.bpel.ui.editparts.figures.GradientFigure; |
| import org.eclipse.bpel.ui.editparts.policies.FlowHighlightEditPolicy; |
| import org.eclipse.bpel.ui.editparts.policies.FlowResizeEditPolicy; |
| import org.eclipse.bpel.ui.editparts.policies.FlowXYLayoutEditPolicy; |
| import org.eclipse.bpel.ui.editparts.util.BPELDecorationLayout; |
| import org.eclipse.bpel.ui.editparts.util.GraphAnimation; |
| import org.eclipse.bpel.ui.util.BatchedMultiObjectAdapter; |
| import org.eclipse.bpel.ui.util.FlowXYLayout; |
| import org.eclipse.bpel.ui.util.ModelHelper; |
| import org.eclipse.bpel.ui.util.NonclippingXYLayout; |
| import org.eclipse.bpel.ui.util.RowColumnLayout; |
| import org.eclipse.bpel.ui.util.marker.BPELEditPartMarkerDecorator; |
| import org.eclipse.draw2d.IFigure; |
| import org.eclipse.draw2d.Label; |
| import org.eclipse.draw2d.LayoutManager; |
| import org.eclipse.draw2d.PositionConstants; |
| import org.eclipse.draw2d.geometry.Dimension; |
| import org.eclipse.draw2d.geometry.Insets; |
| import org.eclipse.draw2d.geometry.Point; |
| import org.eclipse.draw2d.geometry.Rectangle; |
| import org.eclipse.draw2d.graph.DirectedGraph; |
| import org.eclipse.draw2d.graph.DirectedGraphLayout; |
| import org.eclipse.draw2d.graph.Edge; |
| import org.eclipse.draw2d.graph.Node; |
| import org.eclipse.emf.common.notify.Notification; |
| import org.eclipse.emf.common.notify.Notifier; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.util.EContentAdapter; |
| import org.eclipse.gef.EditPart; |
| import org.eclipse.gef.EditPolicy; |
| import org.eclipse.gef.GraphicalEditPart; |
| import org.eclipse.gef.commands.CommandStackListener; |
| |
| public class FlowEditPart extends CollapsableEditPart { |
| |
| private FlowBorder flowBorder; |
| |
| private EContentAdapter flowContentAdapter; |
| |
| private BatchedMultiObjectAdapter flowBatchedAdapter; |
| |
| protected boolean smoothLayout = false; |
| |
| protected FlowHighlightEditPolicy flowHighlightEditPolicy; |
| |
| @Override |
| protected void addAllAdapters() { |
| super.addAllAdapters(); |
| Links links = ((Flow) getActivity()).getLinks(); |
| if (links != null) { |
| adapter.addToObject(links); |
| } |
| } |
| |
| // TODO: this looks strange.. what is it really doing?? |
| CommandStackListener stackListener = new CommandStackListener() { |
| public void commandStackChanged(EventObject event) { |
| if (!isSmoothLayout()) |
| return; |
| |
| setSmoothLayout(false); |
| |
| if (!GraphAnimation.captureLayout(getFigure())) |
| return; |
| |
| while (GraphAnimation.step()) |
| getFigure().getUpdateManager().performUpdate(); |
| GraphAnimation.end(); |
| |
| } |
| }; |
| |
| class FlowDecorationLayout extends BPELDecorationLayout { |
| @Override |
| protected Point calculateLocation(int locationHint, IFigure container, |
| Dimension childDimension) { |
| Rectangle area = container.getClientArea(); |
| switch (locationHint) { |
| case PositionConstants.CENTER: |
| // Center |
| return new Point(area.x + area.width / 2 |
| - childDimension.width / 2, area.y + area.height |
| / 2 - childDimension.height / 2); |
| case PositionConstants.TOP: |
| // Top Center |
| return new Point(area.x + area.width / 2 |
| - childDimension.width / 2, area.y |
| + FlowBorder.LINE_WIDTH); |
| case PositionConstants.BOTTOM: |
| // Bottom Center |
| return new Point(area.x + area.width / 2 |
| - childDimension.width / 2, area.y + area.height |
| - childDimension.height - FlowBorder.LINE_WIDTH); |
| case PositionConstants.LEFT: { |
| // Center Left |
| int x = area.x + DrawerBorder.DRAWER_WIDTH; |
| int y = area.y; |
| if (isCollapsed()) { |
| y += container.getBounds().height / 2; |
| } else { |
| y += area.height / 2 - childDimension.width / 2; |
| } |
| return new Point(x, y); |
| } |
| case PositionConstants.RIGHT: { |
| // Center Right |
| int x = area.x + area.width - DrawerBorder.DRAWER_WIDTH |
| - childDimension.width; |
| int y = area.y; |
| if (isCollapsed()) { |
| y += container.getBounds().height / 2; |
| } else { |
| y += area.height / 2 - childDimension.width / 2; |
| } |
| return new Point(x, y); |
| } |
| case PositionConstants.TOP | PositionConstants.LEFT: { |
| // Top Left |
| int x = area.x + DrawerBorder.DRAWER_WIDTH; |
| int y = area.y; |
| if (isCollapsed()) { |
| y += image.getBounds().height / 2; |
| } |
| return new Point(x, y); |
| } |
| case PositionConstants.TOP | PositionConstants.RIGHT: { |
| // Top Right |
| int x = area.x + area.width - DrawerBorder.DRAWER_WIDTH |
| - childDimension.width; |
| int y = area.y; |
| if (isCollapsed()) { |
| y += image.getBounds().height / 2; |
| } |
| return new Point(x, y); |
| } |
| case PositionConstants.BOTTOM | PositionConstants.LEFT: { |
| // Bottom Left |
| int x = area.x + DrawerBorder.DRAWER_WIDTH; |
| int y = area.y + area.height |
| - (image.getBounds().height / 2); |
| return new Point(x, y); |
| } |
| case PositionConstants.BOTTOM | PositionConstants.RIGHT: { |
| // Bottom Right |
| int x = area.x + area.width - DrawerBorder.DRAWER_WIDTH |
| - childDimension.width; |
| int y = area.y + area.height |
| - (image.getBounds().height / 2); |
| return new Point(x, y); |
| } |
| default: |
| return new Point(area.x, area.y); |
| } |
| } |
| } |
| |
| public FlowEditPart() { |
| super(); |
| |
| // in order to create a batched EContentAdapter we |
| // basically delegate the notifications to a real |
| // batched adapter |
| flowContentAdapter = new EContentAdapter() { |
| @Override |
| public void notifyChanged(Notification n) { |
| switch (n.getEventType()) { |
| case Notification.ADD_MANY: |
| case Notification.REMOVE_MANY: |
| case Notification.ADD: |
| case Notification.REMOVE: |
| case Notification.SET: |
| case Notification.UNSET: |
| case Notification.MOVE: |
| flowBatchedAdapter.notifyChanged(n); |
| } |
| super.notifyChanged(n); |
| } |
| }; |
| // do not add this adapter to model objects - it is called |
| // from the flow content adapter |
| flowBatchedAdapter = new BatchedMultiObjectAdapter() { |
| protected boolean refreshLayout = false; |
| |
| @Override |
| public void finish() { |
| if (refreshLayout) { |
| if (getAutoLayout()) |
| doAutoLayout(false); |
| } |
| refreshLayout = false; |
| } |
| |
| @Override |
| public void notify(Notification n) { |
| if (isActive()) { |
| refreshLayout = true; |
| } |
| } |
| }; |
| } |
| |
| @Override |
| protected void addChildVisual(EditPart childEditPart, int index) { |
| IFigure child = ((GraphicalEditPart) childEditPart).getFigure(); |
| getContentPane().add(child, |
| getFigure().getLayoutManager().getConstraint(child), index); |
| // Bugzilla 319215 |
| // addChildVisual() is also called by reorderChild() to rearrange the order of children. |
| // This causes auto layout to reference a child object with a model that has no parent. |
| // The right place to auto arrange Flow children if no *.bpelex exists yet, is in |
| // BPELEditor.arrangeEditParts() after the model is loaded |
| // if (getShowFreeformFlow() && getAutoLayout()) |
| // doAutoLayout(false); |
| } |
| |
| protected void setFlowEditPolicies() { |
| installEditPolicy(EditPolicy.LAYOUT_ROLE, new FlowXYLayoutEditPolicy()); |
| flowHighlightEditPolicy = new FlowHighlightEditPolicy(!collapsed) { |
| @Override |
| protected int getDrawerInset() { |
| return DrawerBorder.DRAWER_WIDTH; |
| } |
| |
| @Override |
| protected int getNorthInset() { |
| if (isCollapsed()) { |
| return 2; |
| } else { |
| return 2; |
| } |
| } |
| |
| @Override |
| protected int getSouthInset() { |
| return 0; |
| } |
| |
| @Override |
| protected int getEastInset() { |
| return DrawerBorder.DRAWER_WIDTH; |
| } |
| |
| @Override |
| protected int getWestInset() { |
| return DrawerBorder.DRAWER_WIDTH + 2; |
| } |
| }; |
| installEditPolicy(EditPolicy.SELECTION_FEEDBACK_ROLE, |
| flowHighlightEditPolicy); |
| } |
| |
| @Override |
| protected void createEditPolicies() { |
| super.createEditPolicies(); |
| // installEditPolicy(EditPolicy.NODE_ROLE, null); |
| setFlowEditPolicies(); |
| installEditPolicy("childFlowResize", new FlowResizeEditPolicy()); //$NON-NLS-1$ |
| } |
| |
| @Override |
| public Label getLabelFigure() { |
| if (isCollapsed()) |
| return super.getLabelFigure(); |
| return null; |
| } |
| |
| @Override |
| public void setCollapsed(boolean collapsed) { |
| if (collapsed != this.collapsed) { |
| if (flowHighlightEditPolicy != null) { |
| flowHighlightEditPolicy.setResizable(!collapsed); |
| } |
| } |
| super.setCollapsed(collapsed); |
| } |
| |
| @Override |
| protected IFigure createFigure() { |
| createEditPolicies(); // reset the edit policies based on flow display |
| // mode |
| initializeLabels(); |
| |
| editPartMarkerDecorator = new BPELEditPartMarkerDecorator( |
| (EObject) getModel(), new FlowDecorationLayout()); |
| editPartMarkerDecorator |
| .addMarkerMotionListener(getMarkerMotionListener()); |
| |
| IFigure figure = new GradientFigure(getModel()); |
| if (collapsed) { |
| addCollapsedContents(figure); |
| } else { |
| configureExpandedFigure(figure); |
| } |
| this.contentFigure = figure; |
| |
| return editPartMarkerDecorator.createFigure(figure); |
| } |
| |
| @Override |
| protected void configureExpandedFigure(IFigure figure) { |
| LayoutManager layout; |
| |
| if (!getShowFreeformFlow()) { |
| layout = new RowColumnLayout(); |
| } else { |
| FlowXYLayout xylayout = new FlowXYLayout(this); |
| Dimension d = ModelHelper.getSize(getFlow()); |
| if (d.height != 0 && d.width != 0) |
| xylayout.setSize(d); |
| layout = xylayout; |
| } |
| |
| figure.setLayoutManager(layout); |
| |
| if (!(figure.getBorder() instanceof FlowBorder)) { |
| flowBorder = new FlowBorder(figure); |
| figure.setBorder(this.flowBorder); |
| this.flowBorder.setEditPart(this); |
| } |
| figure.addMouseMotionListener(getMouseMotionListener()); |
| this.flowBorder.setEditPart(this); |
| } |
| |
| protected Flow getFlow() { |
| return (Flow) getModel(); |
| } |
| |
| @Override |
| protected boolean isCollapsable() { |
| return true; |
| } |
| |
| @Override |
| public void deactivate() { |
| if (!isActive()) |
| return; |
| super.deactivate(); |
| ((Notifier) getModel()).eAdapters().remove(flowContentAdapter); |
| |
| getViewer().getEditDomain().getCommandStack() |
| .removeCommandStackListener(stackListener); |
| } |
| |
| private boolean getShowFreeformFlow() { |
| return BPELUIPlugin.INSTANCE.getPreferenceStore().getBoolean( |
| IBPELUIConstants.PREF_SHOW_FREEFORM_FLOW); |
| } |
| |
| private boolean getAutoLayout() { |
| return BPELUIPlugin.INSTANCE.getPreferenceStore().getBoolean( |
| IBPELUIConstants.PREF_AUTO_FLOW_LAYOUT); |
| } |
| |
| @Override |
| public IFigure getContentPane() { |
| return contentFigure; |
| } |
| |
| public boolean isShowFreeform() { |
| return (getContentPane().getLayoutManager() instanceof NonclippingXYLayout); |
| } |
| |
| @Override |
| public void regenerateVisuals() { |
| if (collapsed) { |
| addCollapsedContents(this.contentFigure); |
| } else { |
| configureExpandedFigure(this.contentFigure); |
| } |
| setFlowEditPolicies(); |
| // This is necessary because..we may have replaced the border! |
| refreshDrawerImages(); |
| |
| if (getShowFreeformFlow() && getAutoLayout()) |
| doAutoLayout(false); |
| } |
| |
| public DirectedGraph computeAutoLayoutGraph(Map partsToNodes) { |
| DirectedGraph graph = new DirectedGraph(); |
| graph.setDefaultPadding(new Insets(8, 8, 10, 8)); |
| Node top = new Node(null); |
| graph.nodes.add(top); |
| top.width = top.height = 0; |
| top.setPadding(new Insets(-8, 0, 0, 0)); |
| |
| List nodesWithoutPreds = new ArrayList(); |
| |
| for (Iterator it = getChildren().iterator(); it.hasNext();) { |
| Object object = it.next(); |
| if (object instanceof BPELEditPart) { |
| BPELEditPart editPart = (BPELEditPart) object; |
| Node n = new Node(editPart); |
| n.width = editPart.getFigure().getPreferredSize().width; |
| n.height = editPart.getFigure().getPreferredSize().height; |
| n.sortValue = getFigure().getBounds().x; |
| graph.nodes.add(n); |
| partsToNodes.put(editPart, n); |
| nodesWithoutPreds.add(n); |
| // TODO: pre-sort node list based on x-coords of existing |
| // constraints? |
| } else { |
| // System.out.println(object); |
| } |
| } |
| for (Iterator it = getChildren().iterator(); it.hasNext();) { |
| Object object = it.next(); |
| if (object instanceof BPELEditPart) { |
| Node target = (Node) partsToNodes.get(object); |
| if (target == null) |
| continue; |
| |
| for (Iterator it2 = ((BPELEditPart) object) |
| .getTargetConnections().iterator(); it2.hasNext();) { |
| Object object2 = it2.next(); |
| if (object2 instanceof LinkEditPart) { |
| LinkEditPart link = (LinkEditPart) object2; |
| Node source = (Node) partsToNodes.get(link.getSource()); |
| if (source != null) { |
| graph.edges.add(new Edge(source, target)); |
| nodesWithoutPreds.remove(target); |
| } |
| } |
| } |
| } |
| } |
| for (Iterator it = nodesWithoutPreds.iterator(); it.hasNext();) { |
| graph.edges.add(new Edge(top, (Node) it.next())); |
| } |
| |
| new DirectedGraphLayout().visit(graph); |
| |
| return graph; |
| } |
| |
| public void doImmediateAutoLayout() { |
| Map<BPELEditPart, Node> partsToNodes = new HashMap<BPELEditPart, Node>(); |
| if (BPELUIPlugin.INSTANCE.getPreferenceStore().getBoolean( |
| IBPELUIConstants.PREF_USE_ANIMATION)) { |
| setSmoothLayout(true); |
| } |
| computeAutoLayoutGraph(partsToNodes); |
| |
| for (Iterator it = getChildren().iterator(); it.hasNext();) { |
| Object object = it.next(); |
| if (object instanceof BPELEditPart) { |
| BPELEditPart editPart = (BPELEditPart) object; |
| |
| Node n = partsToNodes.get(editPart); |
| Point loc = new Point(n.x, n.y); |
| // TODO: I think this is wrong |
| // getFigure().translateToRelative(loc); |
| |
| ModelHelper.setLocation((Activity) editPart.getModel(), loc); |
| } |
| } |
| } |
| |
| public void doAutoLayout() { |
| doAutoLayout(true); |
| } |
| |
| public void doAutoLayout(boolean withCommand) { |
| Map<BPELEditPart, Node> partsToNodes = new HashMap<BPELEditPart, Node>(); |
| this.getFigure().invalidateTree(); |
| if (BPELUIPlugin.INSTANCE.getPreferenceStore().getBoolean( |
| IBPELUIConstants.PREF_USE_ANIMATION)) { |
| setSmoothLayout(true); |
| } |
| computeAutoLayoutGraph(partsToNodes); |
| |
| CompoundCommand cmd = new CompoundCommand(); |
| cmd.setLabel(IBPELUIConstants.CMD_AUTO_ARRANGE); |
| |
| BPELEditor bpelEditor = ModelHelper.getBPELEditor(getModel()); |
| |
| for (Iterator it = getChildren().iterator(); it.hasNext();) { |
| Object object = it.next(); |
| if (object instanceof BPELEditPart) { |
| BPELEditPart editPart = (BPELEditPart) object; |
| |
| Node n = partsToNodes.get(editPart); |
| Point loc = new Point(n.x, n.y); |
| // TODO: I think this is wrong |
| // getFigure().translateToRelative(loc); |
| |
| SetConstraintCommand cmd2 = new SetConstraintCommand( |
| (Activity) editPart.getModel(), loc, null); |
| if (withCommand) { |
| cmd.add(cmd2); |
| } else { |
| cmd2.execute(); |
| } |
| } |
| } |
| if (withCommand) { |
| regenerateVisuals(); |
| bpelEditor.getCommandStack().execute(cmd); |
| } |
| } |
| |
| @Override |
| protected void handleModelChanged() { |
| // The size of the flow may have changed. Rebuild the edit part. |
| |
| // move this line to top of function, must call refreshChildren() to |
| // make sure the gef is in sync with model before we regenerate visuals |
| // and do an autolayout |
| refreshChildren(); |
| |
| super.handleModelChanged(); |
| regenerateVisuals(); |
| } |
| |
| @Override |
| public void activate() { |
| super.activate(); |
| ((Notifier) getModel()).eAdapters().add(flowContentAdapter); |
| |
| getViewer().getEditDomain().getCommandStack().addCommandStackListener( |
| stackListener); |
| } |
| |
| public boolean isSmoothLayout() { |
| return smoothLayout; |
| } |
| |
| public void setSmoothLayout(boolean smoothLayout) { |
| this.smoothLayout = smoothLayout; |
| } |
| |
| /* a couple of utility classes for use in the cycle detection code */ |
| |
| /** represents an edge connection for an editpart * */ |
| |
| private class EditPartEdge { |
| private EditPartNode source, dest; |
| |
| public EditPartEdge(EditPartNode source, EditPartNode dest) { |
| this.source = source; |
| this.dest = dest; |
| } |
| |
| public EditPartNode getDest() { |
| return dest; |
| } |
| |
| public EditPartNode getSource() { |
| return source; |
| } |
| } |
| |
| /** |
| * represents an node in a graph The main feature is a visit function which |
| * marks the visited node and returns false if cycle detected |
| */ |
| |
| private class EditPartNode { |
| public static final int VISITING = 1, VISITED = 2, NOTVISITED = 0; |
| |
| private EditPart part; |
| |
| private List edges = new ArrayList(); |
| |
| public int visited = NOTVISITED; |
| |
| public EditPartNode(EditPart part) { |
| this.part = part; |
| } |
| |
| public EditPart getPart() { |
| return part; |
| } |
| |
| public int getVisited() { |
| return visited; |
| } |
| |
| public void addEdge(EditPartEdge edge) { |
| edges.add(edge); |
| } |
| |
| /** returns false if cycle detected * */ |
| public boolean visit() { |
| EditPartEdge e; |
| if (visited == VISITING) { |
| return false; |
| } |
| if (visited == VISITED) |
| return true; |
| visited = VISITING; |
| for (Iterator it = edges.iterator(); it.hasNext();) { |
| e = (EditPartEdge) it.next(); |
| // if (e.getSource() == this) |
| if (e.getDest().visit() == false) |
| return false; |
| } |
| visited = VISITED; |
| return true; |
| } |
| } |
| |
| /** |
| * checks if a new connection joining sourceNode and potentialDest will |
| * result in a cycle * |
| */ |
| |
| public boolean detectImpendingCycle(EditPart sourceNode, |
| EditPart potentialDest) { |
| List<EditPartNode> nodes = new ArrayList<EditPartNode>(); |
| |
| Map<BPELEditPart, EditPartNode> partsToNodes = new HashMap<BPELEditPart, EditPartNode>(); |
| |
| // strategy: we'll build up a separate parallel graph that we can |
| // traverse to detect cycles. |
| // We are essentially duplicating the Flow's |
| // graph structure, but this is easiest since we don't have a way of |
| // "simulating a link between |
| // for the proposed link, and we don't want to pollute the editparts |
| // extraneous methods and fields |
| // just for cycle detection logic, this will have to do for now. |
| |
| for (Iterator it = getChildren().iterator(); it.hasNext();) { |
| Object object = it.next(); |
| if (object instanceof BPELEditPart) { |
| BPELEditPart editPart = (BPELEditPart) object; |
| EditPartNode n = new EditPartNode(editPart); |
| nodes.add(n); |
| partsToNodes.put(editPart, n); |
| } |
| } |
| |
| for (Iterator it = getChildren().iterator(); it.hasNext();) { |
| Object object = it.next(); |
| if (object instanceof BPELEditPart) { |
| EditPartNode source = partsToNodes.get(object); |
| if (source == null) |
| continue; |
| |
| for (Iterator it2 = ((BPELEditPart) object) |
| .getSourceConnections().iterator(); it2.hasNext();) { |
| Object targetObject = it2.next(); |
| if (targetObject instanceof LinkEditPart) { |
| LinkEditPart linkEditPart = (LinkEditPart) targetObject; |
| EditPartNode target = partsToNodes |
| .get(linkEditPart.getTarget()); |
| if (target != null) { |
| source.addEdge(new EditPartEdge(source, target)); |
| } |
| } |
| } |
| } |
| } |
| |
| // add the proposed edge |
| EditPartNode source = partsToNodes.get(sourceNode); |
| if (source != null) { |
| EditPartNode target = partsToNodes |
| .get(potentialDest); |
| if (target != null) { |
| source.addEdge(new EditPartEdge(source, target)); |
| } |
| } |
| |
| /* visit each node checking if a cycle will result */ |
| for (Iterator<EditPartNode> it = nodes.iterator(); it.hasNext();) { |
| EditPartNode v = it.next(); |
| if (v.getVisited() == EditPartNode.NOTVISITED) |
| if (v.visit() == false) |
| return false; |
| } |
| |
| return true; |
| } |
| |
| public void switchLayout(boolean horizontal) { |
| // Actually does nothing |
| } |
| } |