/******************************************************************************* | |
* 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 | |
} | |
} |