blob: 5a5ffa4951fea01d4f6d846e32a5490ec6d5b67d [file] [log] [blame]
/*******************************************************************************
* 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
}
}