You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@taverna.apache.org by re...@apache.org on 2015/03/26 19:52:03 UTC

[14/51] [partial] incubator-taverna-workbench git commit: all packages are moved to org.apache.taverna.*

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/a9a52bd5/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphController.java
----------------------------------------------------------------------
diff --git a/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphController.java b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphController.java
new file mode 100644
index 0000000..fd002f3
--- /dev/null
+++ b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphController.java
@@ -0,0 +1,1276 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package org.apache.taverna.workbench.models.graph;
+
+import static javax.swing.JOptionPane.PLAIN_MESSAGE;
+import static javax.swing.JOptionPane.showInputDialog;
+import static javax.swing.JOptionPane.showMessageDialog;
+
+import java.awt.Color;
+import java.awt.Component;
+import java.awt.Dimension;
+import java.awt.Point;
+import java.net.URI;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
+import java.util.Set;
+
+import org.apache.taverna.lang.observer.Observable;
+import org.apache.taverna.lang.observer.Observer;
+import org.apache.taverna.ui.menu.MenuManager;
+import org.apache.taverna.workbench.configuration.colour.ColourManager;
+import org.apache.taverna.workbench.edits.CompoundEdit;
+import org.apache.taverna.workbench.edits.Edit;
+import org.apache.taverna.workbench.edits.EditException;
+import org.apache.taverna.workbench.edits.EditManager;
+import org.apache.taverna.workbench.models.graph.Graph.Alignment;
+import org.apache.taverna.workbench.models.graph.GraphEdge.ArrowStyle;
+import org.apache.taverna.workbench.models.graph.GraphElement.LineStyle;
+import org.apache.taverna.workbench.models.graph.GraphShapeElement.Shape;
+import org.apache.taverna.workbench.selection.DataflowSelectionModel;
+import org.apache.taverna.workbench.selection.events.DataflowSelectionMessage;
+import org.apache.taverna.workflow.edits.AddDataLinkEdit;
+import org.apache.taverna.workflow.edits.RemoveDataLinkEdit;
+
+import org.apache.log4j.Logger;
+
+import org.apache.taverna.scufl2.api.activity.Activity;
+import org.apache.taverna.scufl2.api.common.NamedSet;
+import org.apache.taverna.scufl2.api.common.Scufl2Tools;
+import org.apache.taverna.scufl2.api.common.WorkflowBean;
+import org.apache.taverna.scufl2.api.core.BlockingControlLink;
+import org.apache.taverna.scufl2.api.core.ControlLink;
+import org.apache.taverna.scufl2.api.core.DataLink;
+import org.apache.taverna.scufl2.api.core.Processor;
+import org.apache.taverna.scufl2.api.core.Workflow;
+import org.apache.taverna.scufl2.api.port.InputActivityPort;
+import org.apache.taverna.scufl2.api.port.InputPort;
+import org.apache.taverna.scufl2.api.port.InputProcessorPort;
+import org.apache.taverna.scufl2.api.port.InputWorkflowPort;
+import org.apache.taverna.scufl2.api.port.OutputActivityPort;
+import org.apache.taverna.scufl2.api.port.OutputPort;
+import org.apache.taverna.scufl2.api.port.OutputProcessorPort;
+import org.apache.taverna.scufl2.api.port.OutputWorkflowPort;
+import org.apache.taverna.scufl2.api.port.Port;
+import org.apache.taverna.scufl2.api.port.ProcessorPort;
+import org.apache.taverna.scufl2.api.port.ReceiverPort;
+import org.apache.taverna.scufl2.api.port.SenderPort;
+import org.apache.taverna.scufl2.api.port.WorkflowPort;
+import org.apache.taverna.scufl2.api.profiles.ProcessorBinding;
+import org.apache.taverna.scufl2.api.profiles.Profile;
+
+/**
+ * @author David Withers
+ */
+public abstract class GraphController implements
+		Observer<DataflowSelectionMessage> {
+	public enum PortStyle {
+		ALL {
+			@Override
+			Shape inputShape() {
+				return Shape.INVHOUSE;
+			}
+
+			@Override
+			Shape outputShape() {
+				return Shape.HOUSE;
+			}
+
+			@Override
+			Shape processorShape() {
+				return Shape.RECORD;
+			}
+		},
+		BOUND {
+			@Override
+			Shape inputShape() {
+				return Shape.INVHOUSE;
+			}
+
+			@Override
+			Shape outputShape() {
+				return Shape.HOUSE;
+			}
+
+			@Override
+			Shape processorShape() {
+				return Shape.RECORD;
+			}
+		},
+		NONE {
+			@Override
+			Shape inputShape() {
+				return Shape.BOX;
+			}
+
+			@Override
+			Shape outputShape() {
+				return Shape.BOX;
+			}
+
+			@Override
+			Shape processorShape() {
+				return Shape.BOX;
+			}
+		},
+		BLOB {
+			@Override
+			Shape inputShape() {
+				return Shape.CIRCLE;
+			}
+
+			@Override
+			Shape outputShape() {
+				return Shape.CIRCLE;
+			}
+
+			@Override
+			Shape processorShape() {
+				return Shape.CIRCLE;
+			}
+		};
+
+		abstract Shape inputShape();
+
+		abstract Shape outputShape();
+
+		abstract Shape processorShape();
+
+		Shape mergeShape() {
+			return Shape.CIRCLE;
+		}
+	}
+
+	private static Logger logger = Logger.getLogger(GraphController.class);
+
+	private Map<String, GraphElement> idToElement = new HashMap<>();
+	private Map<WorkflowBean, GraphElement> workflowToGraph = new HashMap<>();
+	private Map<Port, GraphNode> ports = new HashMap<>();
+	private Map<Graph, GraphNode> inputControls = new HashMap<>();
+	private Map<Graph, GraphNode> outputControls = new HashMap<>();
+	private Map<Port, Port> nestedWorkflowPorts = new HashMap<>();
+	private Map<WorkflowPort, ProcessorPort> workflowPortToProcessorPort = new HashMap<>();
+	private Map<Port, Processor> portToProcessor = new HashMap<>();
+
+	private EditManager editManager;
+	private final Workflow workflow;
+	private final Profile profile;
+	private DataflowSelectionModel dataflowSelectionModel;
+	private GraphEventManager graphEventManager;
+	private Component componentForPopups;
+
+	// graph settings
+	private PortStyle portStyle = PortStyle.NONE;
+	private Map<Processor, PortStyle> processorPortStyle = new HashMap<>();
+	private Alignment alignment = Alignment.VERTICAL;
+	private boolean expandNestedDataflows = true;
+	private Map<Activity, Boolean> dataflowExpansion = new HashMap<>();
+	protected Map<String, GraphElement> graphElementMap = new HashMap<>();
+	protected GraphElement edgeCreationSource, edgeCreationSink;
+	protected GraphEdge edgeMoveElement;
+	protected boolean edgeCreationFromSource = false;
+	protected boolean edgeCreationFromSink = false;
+	private Graph graph;
+	private boolean interactive;
+	private final ColourManager colourManager;
+
+	private Scufl2Tools scufl2Tools = new Scufl2Tools();
+
+	public GraphController(Workflow workflow, Profile profile,
+			boolean interactive, Component componentForPopups,
+			EditManager editManager, MenuManager menuManager,
+			ColourManager colourManager) {
+		this(workflow, profile, interactive, componentForPopups,
+				Alignment.VERTICAL, PortStyle.NONE, editManager, menuManager,
+				colourManager);
+	}
+
+	public GraphController(Workflow workflow, Profile profile,
+			boolean interactive, Component componentForPopups,
+			Alignment alignment, PortStyle portStyle, EditManager editManager,
+			MenuManager menuManager, ColourManager colourManager) {
+		this.workflow = workflow;
+		this.profile = profile;
+		this.interactive = interactive;
+		this.componentForPopups = componentForPopups;
+		this.alignment = alignment;
+		this.portStyle = portStyle;
+		this.editManager = editManager;
+		this.colourManager = colourManager;
+		this.graphEventManager = new DefaultGraphEventManager(this,
+				componentForPopups, menuManager);
+		graph = generateGraph();
+	}
+
+	public abstract Graph createGraph();
+
+	public abstract GraphNode createGraphNode();
+
+	public abstract GraphEdge createGraphEdge();
+
+	public void mapElement(String id, GraphElement element) {
+		idToElement.put(id, element);
+	}
+
+	public GraphElement getElement(String id) {
+		return idToElement.get(id);
+	}
+
+	public Graph getGraph() {
+		return graph;
+	}
+
+	public abstract void redraw();
+
+	/**
+	 * Generates a graph model of a dataflow.
+	 * 
+	 * @return
+	 */
+	public Graph generateGraph() {
+		workflowToGraph.clear();
+		ports.clear();
+		inputControls.clear();
+		outputControls.clear();
+		nestedWorkflowPorts.clear();
+		workflowPortToProcessorPort.clear();
+		graphElementMap.clear();
+		portToProcessor.clear();
+		return generateGraph(workflow, "", workflow.getName(), 0);
+	}
+
+	private Graph generateGraph(Workflow dataflow, String prefix, String name,
+			int depth) {
+		Graph graph = createGraph();
+		graph.setId(prefix + name);
+		graph.setAlignment(getAlignment());
+		if (getPortStyle().equals(PortStyle.BLOB) || depth == 0)
+			graph.setLabel("");
+		else
+			graph.setLabel(name);
+		graph.setFillColor(GraphColorManager.getSubGraphFillColor(depth));
+		if (depth == 0)
+			graph.setLineStyle(LineStyle.NONE);
+		else
+			graph.setLineStyle(LineStyle.SOLID);
+		graph.setColor(Color.BLACK);
+		graph.setShape(Shape.BOX);
+
+		if (depth == 0)
+			graph.setWorkflowBean(dataflow);
+		if (interactive)
+			graph.setWorkflowBean(dataflow);
+
+		// processors
+		for (Processor processor : dataflow.getProcessors())
+			graph.addNode(generateProcessorNode(processor, graph.getId(), depth));
+
+		// dataflow outputs
+		NamedSet<OutputWorkflowPort> outputPorts = dataflow.getOutputPorts();
+		if (outputPorts.size() > 0 || depth > 0)
+			graph.addSubgraph(generateOutputsGraph(outputPorts, graph.getId(),
+					graph, depth));
+
+		// dataflow inputs
+		NamedSet<InputWorkflowPort> inputPorts = dataflow.getInputPorts();
+		if (inputPorts.size() > 0 || depth > 0)
+			graph.addSubgraph(generateInputsGraph(inputPorts, graph.getId(),
+					graph, depth));
+
+		// datalinks
+		for (DataLink datalink : dataflow.getDataLinks()) {
+			GraphEdge edge = generateDataLinkEdge(datalink, depth);
+			if (edge != null)
+				graph.addEdge(edge);
+		}
+
+		// controlLinks
+		for (ControlLink controlLink : dataflow.getControlLinks())
+			if (controlLink instanceof BlockingControlLink) {
+				GraphEdge edge = generateControlLinkEdge(
+						(BlockingControlLink) controlLink, depth);
+				if (edge != null)
+					graph.addEdge(edge);
+			}
+
+		graphElementMap.put(graph.getId(), graph);
+		return graph;
+	}
+
+	public void transformGraph(Graph oldGraph, Graph newGraph) {
+		oldGraph.setAlignment(newGraph.getAlignment());
+		transformGraphElement(oldGraph, newGraph);
+		List<GraphEdge> oldEdges = new ArrayList<>(oldGraph.getEdges());
+		List<GraphEdge> newEdges = new ArrayList<>(newGraph.getEdges());
+		for (GraphEdge oldEdge : oldEdges) {
+			int index = newEdges.indexOf(oldEdge);
+			if (index >= 0) {
+				GraphEdge newEdge = newEdges.remove(index);
+				oldEdge.setPath(newEdge.getPath());
+				workflowToGraph.put(oldEdge.getWorkflowBean(), oldEdge);
+			} else
+				oldGraph.removeEdge(oldEdge);
+		}
+		List<GraphNode> newNodes = new ArrayList<>(newGraph.getNodes());
+		List<GraphNode> oldNodes = new ArrayList<>(oldGraph.getNodes());
+		for (GraphNode oldNode : oldNodes) {
+			int index = newNodes.indexOf(oldNode);
+			if (index >= 0) {
+				GraphNode newNode = newNodes.remove(index);
+				oldNode.setExpanded(newNode.isExpanded());
+				List<GraphNode> newSourceNodes = new ArrayList<>(
+						newNode.getSourceNodes());
+				List<GraphNode> oldSourceNodes = new ArrayList<>(
+						oldNode.getSourceNodes());
+				for (GraphNode oldSourceNode : oldSourceNodes) {
+					int sourceNodeIndex = newSourceNodes.indexOf(oldSourceNode);
+					if (sourceNodeIndex >= 0) {
+						GraphNode newSourceNode = newSourceNodes
+								.remove(sourceNodeIndex);
+						transformGraphElement(oldSourceNode, newSourceNode);
+					} else
+						oldNode.removeSourceNode(oldSourceNode);
+				}
+				for (GraphNode sourceNode : newSourceNodes)
+					oldNode.addSourceNode(sourceNode);
+				List<GraphNode> newSinkNodes = new ArrayList<>(
+						newNode.getSinkNodes());
+				List<GraphNode> oldSinkNodes = new ArrayList<>(
+						oldNode.getSinkNodes());
+				for (GraphNode oldSinkNode : oldSinkNodes) {
+					int sinkNodeIndex = newSinkNodes.indexOf(oldSinkNode);
+					if (sinkNodeIndex >= 0) {
+						GraphNode newSinkNode = newSinkNodes
+								.remove(sinkNodeIndex);
+						transformGraphElement(oldSinkNode, newSinkNode);
+					} else
+						oldNode.removeSinkNode(oldSinkNode);
+				}
+				for (GraphNode sinkNode : newSinkNodes)
+					oldNode.addSinkNode(sinkNode);
+				Graph oldSubGraph = oldNode.getGraph();
+				Graph newSubGraph = newNode.getGraph();
+				if (oldSubGraph != null && newSubGraph != null)
+					transformGraph(oldSubGraph, newSubGraph);
+				transformGraphElement(oldNode, newNode);
+			} else
+				oldGraph.removeNode(oldNode);
+		}
+		List<Graph> newSubGraphs = new ArrayList<>(newGraph.getSubgraphs());
+		List<Graph> oldSubGraphs = new ArrayList<>(oldGraph.getSubgraphs());
+		for (Graph oldSubGraph : oldSubGraphs) {
+			int index = newSubGraphs.indexOf(oldSubGraph);
+			if (index >= 0) {
+				Graph newSubGraph = newSubGraphs.remove(index);
+				transformGraph(oldSubGraph, newSubGraph);
+			} else
+				oldGraph.removeSubgraph(oldSubGraph);
+		}
+		for (GraphNode node : newNodes)
+			oldGraph.addNode(node);
+		for (Graph graph : newSubGraphs)
+			oldGraph.addSubgraph(graph);
+		for (GraphEdge newEdge : newEdges)
+			oldGraph.addEdge(newEdge);
+	}
+
+	public void transformGraphElement(GraphShapeElement oldGraphElement,
+			GraphShapeElement newGraphElement) {
+		oldGraphElement.setWorkflowBean(newGraphElement.getWorkflowBean());
+		oldGraphElement.setShape(newGraphElement.getShape());
+		oldGraphElement.setSize(newGraphElement.getSize());
+		oldGraphElement.setPosition(newGraphElement.getPosition());
+		oldGraphElement.setLabel(newGraphElement.getLabel());
+		oldGraphElement.setLabelPosition(newGraphElement.getLabelPosition());
+		oldGraphElement.setLineStyle(newGraphElement.getLineStyle());
+		oldGraphElement.setOpacity(newGraphElement.getOpacity());
+		oldGraphElement.setVisible(newGraphElement.isVisible());
+		oldGraphElement.setColor(newGraphElement.getColor());
+		oldGraphElement.setFillColor(newGraphElement.getFillColor());
+		workflowToGraph.put(oldGraphElement.getWorkflowBean(), oldGraphElement);
+	}
+
+	public void filterGraph(Set<?> dataflowEntities) {
+		Set<GraphElement> graphElements = new HashSet<>();
+		for (Entry<WorkflowBean, GraphElement> entry : workflowToGraph
+				.entrySet())
+			if (!dataflowEntities.contains(entry.getKey()))
+				graphElements.add(entry.getValue());
+		filterGraph(getGraph(), graphElements);
+	}
+
+	private void filterGraph(Graph graph, Set<GraphElement> graphElements) {
+		for (GraphNode node : graph.getNodes()) {
+			node.setFiltered(graphElements.contains(node));
+			Graph subgraph = node.getGraph();
+			if (subgraph != null)
+				if (graphElements.contains(subgraph)) {
+					removeFilter(subgraph);
+					subgraph.setFiltered(true);
+				} else {
+					subgraph.setFiltered(false);
+					filterGraph(subgraph, graphElements);
+				}
+		}
+		for (GraphEdge edge : graph.getEdges())
+			edge.setFiltered(graphElements.contains(edge));
+		for (Graph subgraph : graph.getSubgraphs())
+			if (graphElements.contains(subgraph)) {
+				removeFilter(subgraph);
+				subgraph.setFiltered(true);
+			} else {
+				subgraph.setFiltered(false);
+				filterGraph(subgraph, graphElements);
+			}
+	}
+
+	public void removeFilter() {
+		for (Entry<WorkflowBean, GraphElement> entry : workflowToGraph
+				.entrySet())
+			entry.getValue().setFiltered(false);
+	}
+
+	private void removeFilter(Graph graph) {
+		for (GraphNode node : graph.getNodes()) {
+			node.setOpacity(1f);
+			Graph subgraph = node.getGraph();
+			if (subgraph != null) {
+				subgraph.setFiltered(false);
+				removeFilter(subgraph);
+			}
+		}
+		for (GraphEdge edge : graph.getEdges())
+			edge.setFiltered(false);
+		for (Graph subgraph : graph.getSubgraphs()) {
+			subgraph.setFiltered(false);
+			removeFilter(subgraph);
+		}
+	}
+
+	private GraphEdge generateControlLinkEdge(BlockingControlLink condition,
+			int depth) {
+		GraphEdge edge = null;
+		GraphElement source = workflowToGraph.get(condition.getUntilFinished());
+		GraphElement sink = workflowToGraph.get(condition.getBlock());
+		if (source != null && sink != null) {
+			edge = createGraphEdge();
+			if (source instanceof Graph)
+				edge.setSource(outputControls.get(source));
+			else if (source instanceof GraphNode)
+				edge.setSource((GraphNode) source);
+			if (sink instanceof Graph)
+				edge.setSink(inputControls.get(sink));
+			else if (sink instanceof GraphNode)
+				edge.setSink((GraphNode) sink);
+			String sourceId = edge.getSource().getId();
+			String sinkId = edge.getSink().getId();
+			edge.setId(sourceId + "->" + sinkId);
+			edge.setLineStyle(LineStyle.SOLID);
+			edge.setColor(Color.decode("#505050"));
+			edge.setFillColor(null);
+			edge.setArrowHeadStyle(ArrowStyle.DOT);
+			if (depth == 0)
+				edge.setWorkflowBean(condition);
+			if (interactive)
+				edge.setWorkflowBean(condition);
+			workflowToGraph.put(condition, edge);
+			graphElementMap.put(edge.getId(), edge);
+		}
+		return edge;
+	}
+
+	private GraphEdge generateDataLinkEdge(DataLink datalink, int depth) {
+		GraphEdge edge = null;
+		Port sourcePort = datalink.getReceivesFrom();
+		Port sinkPort = datalink.getSendsTo();
+		if (nestedWorkflowPorts.containsKey(sourcePort))
+			sourcePort = nestedWorkflowPorts.get(sourcePort);
+		if (nestedWorkflowPorts.containsKey(sinkPort))
+			sinkPort = nestedWorkflowPorts.get(sinkPort);
+		GraphNode sourceNode = ports.get(sourcePort);
+		GraphNode sinkNode = ports.get(sinkPort);
+		if (sourceNode != null && sinkNode != null) {
+			edge = createGraphEdge();
+			edge.setSource(sourceNode);
+			edge.setSink(sinkNode);
+
+			StringBuilder id = new StringBuilder();
+			if (sourceNode.getParent() instanceof GraphNode) {
+				id.append(sourceNode.getParent().getId());
+				id.append(":");
+				id.append(sourceNode.getId());
+			} else
+				id.append(sourceNode.getId());
+			id.append("->");
+			if (sinkNode.getParent() instanceof GraphNode) {
+				id.append(sinkNode.getParent().getId());
+				id.append(":");
+				id.append(sinkNode.getId());
+			} else
+				id.append(sinkNode.getId());
+			edge.setId(id.toString());
+			edge.setLineStyle(LineStyle.SOLID);
+			edge.setColor(Color.BLACK);
+			edge.setFillColor(Color.BLACK);
+			if (depth == 0)
+				edge.setWorkflowBean(datalink);
+			if (interactive)
+				edge.setWorkflowBean(datalink);
+			workflowToGraph.put(datalink, edge);
+			graphElementMap.put(edge.getId(), edge);
+		}
+		return edge;
+	}
+
+	private Graph generateInputsGraph(NamedSet<InputWorkflowPort> inputPorts,
+			String prefix, Graph graph, int depth) {
+		Graph inputs = createGraph();
+		inputs.setId(prefix + "sources");
+		inputs.setColor(Color.BLACK);
+		inputs.setFillColor(null);
+		inputs.setShape(Shape.BOX);
+		inputs.setLineStyle(LineStyle.DOTTED);
+		if (getPortStyle().equals(PortStyle.BLOB))
+			inputs.setLabel("");
+		else
+			inputs.setLabel("Workflow input ports");
+
+		GraphNode triangle = createGraphNode();
+		triangle.setId(prefix + "WORKFLOWINTERNALSOURCECONTROL");
+		triangle.setLabel("");
+		triangle.setShape(Shape.TRIANGLE);
+		triangle.setSize(new Dimension((int) (0.2f * 72), (int) ((Math.sin(Math
+				.toRadians(60)) * 0.2) * 72)));
+		triangle.setFillColor(Color.decode("#ff4040"));
+		triangle.setColor(Color.BLACK);
+		triangle.setLineStyle(LineStyle.SOLID);
+		inputs.addNode(triangle);
+		inputControls.put(graph, triangle);
+
+		for (InputWorkflowPort inputWorkflowPort : inputPorts) {
+			GraphNode inputNode = createGraphNode();
+			inputNode.setId(prefix + "WORKFLOWINTERNALSOURCE_"
+					+ inputWorkflowPort.getName());
+			if (getPortStyle().equals(PortStyle.BLOB)) {
+				inputNode.setLabel("");
+				inputNode.setSize(new Dimension((int) (0.3f * 72),
+						(int) (0.3f * 72)));
+			} else
+				inputNode.setLabel(inputWorkflowPort.getName());
+			inputNode.setShape(getPortStyle().inputShape());
+			inputNode.setColor(Color.BLACK);
+			inputNode.setLineStyle(LineStyle.SOLID);
+			inputNode.setFillColor(Color.decode("#8ed6f0"));
+			if (depth == 0)
+				inputNode.setInteractive(true);
+			if (interactive)
+				inputNode.setInteractive(true);
+			if (depth < 2) {
+				inputNode.setWorkflowBean(inputWorkflowPort);
+				if (workflowPortToProcessorPort.containsKey(inputWorkflowPort)) {
+					ProcessorPort port = workflowPortToProcessorPort
+							.get(inputWorkflowPort);
+					inputNode.setWorkflowBean(port);
+					workflowToGraph.put(port, inputNode);
+				} else {
+					inputNode.setWorkflowBean(inputWorkflowPort);
+					workflowToGraph.put(inputWorkflowPort, inputNode);
+				}
+			}
+			ports.put(inputWorkflowPort, inputNode);
+			inputs.addNode(inputNode);
+			graphElementMap.put(inputNode.getId(), inputNode);
+		}
+		return inputs;
+	}
+
+	private Graph generateOutputsGraph(
+			NamedSet<OutputWorkflowPort> outputPorts, String prefix,
+			Graph graph, int depth) {
+		Graph outputs = createGraph();
+		outputs.setId(prefix + "sinks");
+		outputs.setColor(Color.BLACK);
+		outputs.setFillColor(null);
+		outputs.setShape(Shape.BOX);
+		outputs.setLineStyle(LineStyle.DOTTED);
+		if (getPortStyle().equals(PortStyle.BLOB))
+			outputs.setLabel("");
+		else
+			outputs.setLabel("Workflow output ports");
+
+		GraphNode triangle = createGraphNode();
+		triangle.setId(prefix + "WORKFLOWINTERNALSINKCONTROL");
+		triangle.setLabel("");
+		triangle.setShape(Shape.INVTRIANGLE);
+		triangle.setSize(new Dimension((int) (0.2f * 72), (int) ((Math.sin(Math
+				.toRadians(60)) * 0.2) * 72)));
+		triangle.setFillColor(Color.decode("#66cd00"));
+		triangle.setColor(Color.BLACK);
+		triangle.setLineStyle(LineStyle.SOLID);
+		outputs.addNode(triangle);
+		outputControls.put(graph, triangle);
+
+		for (OutputWorkflowPort outputWorkflowPort : outputPorts) {
+			GraphNode outputNode = createGraphNode();
+			outputNode.setId(prefix + "WORKFLOWINTERNALSINK_"
+					+ outputWorkflowPort.getName());
+			if (getPortStyle().equals(PortStyle.BLOB)) {
+				outputNode.setLabel("");
+				outputNode.setSize(new Dimension((int) (0.3f * 72),
+						(int) (0.3f * 72)));
+			} else
+				outputNode.setLabel(outputWorkflowPort.getName());
+			outputNode.setShape(getPortStyle().outputShape());
+			outputNode.setColor(Color.BLACK);
+			outputNode.setLineStyle(LineStyle.SOLID);
+			outputNode.setFillColor(Color.decode("#8ed6f0"));
+			if (depth == 0)
+				outputNode.setInteractive(true);
+			if (interactive)
+				outputNode.setInteractive(true);
+			if (depth < 2) {
+				if (workflowPortToProcessorPort.containsKey(outputWorkflowPort)) {
+					ProcessorPort port = workflowPortToProcessorPort
+							.get(outputWorkflowPort);
+					outputNode.setWorkflowBean(port);
+					workflowToGraph.put(port, outputNode);
+				} else {
+					outputNode.setWorkflowBean(outputWorkflowPort);
+					workflowToGraph.put(outputWorkflowPort, outputNode);
+				}
+			}
+			ports.put(outputWorkflowPort, outputNode);
+			outputs.addNode(outputNode);
+			graphElementMap.put(outputNode.getId(), outputNode);
+		}
+		return outputs;
+	}
+
+	private GraphNode generateProcessorNode(Processor processor, String prefix,
+			int depth) {
+		// Blatantly ignoring any other activities for now
+		ProcessorBinding processorBinding = scufl2Tools
+				.processorBindingForProcessor(processor, profile);
+		Activity activity = processorBinding.getBoundActivity();
+		@SuppressWarnings("unused")
+		URI activityType = activity.getType();
+
+		GraphNode node = createGraphNode();
+		node.setId(prefix + processor.getName());
+		if (getPortStyle().equals(PortStyle.BLOB)) {
+			node.setLabel("");
+			node.setSize(new Dimension((int) (0.3f * 72), (int) (0.3f * 72)));
+		} else
+			node.setLabel(processor.getName());
+		node.setShape(getPortStyle(processor).processorShape());
+		node.setColor(Color.BLACK);
+		node.setLineStyle(LineStyle.SOLID);
+		// if (activityType.equals(URI.create(NonExecutableActivity.URI))) {
+		// if (activityType.equals(URI.create(DisabledActivity.URI))) {
+		// node.setFillColor(GraphColorManager
+		// .getFillColor(((DisabledActivity) activity)
+		// .getActivity(), colourManager));
+		// } else {
+		// node.setFillColor(GraphColorManager
+		// .getFillColor(activityType, colourManager));
+		// }
+		// node.setOpacity(0.3f);
+		// } else
+		node.setFillColor(GraphColorManager.getFillColor(activity,
+				colourManager));
+
+		// check whether the nested workflow processors should be clickable or
+		// not, if top level workflow then should be clickable regardless
+		if (depth == 0) {
+			node.setInteractive(true);
+			node.setWorkflowBean(processor);
+		}
+		if (interactive) {
+			node.setInteractive(true);
+			node.setWorkflowBean(processor);
+		}
+
+		if (scufl2Tools.containsNestedWorkflow(processor, profile)
+				&& expandNestedDataflow(activity)) {
+			Workflow subDataflow = scufl2Tools.nestedWorkflowForProcessor(
+					processor, profile);
+
+			NamedSet<InputWorkflowPort> inputWorkflowPorts = subDataflow
+					.getInputPorts();
+			for (InputActivityPort inputActivityPort : activity.getInputPorts()) {
+				InputWorkflowPort inputWorkflowPort = inputWorkflowPorts
+						.getByName(inputActivityPort.getName());
+				InputProcessorPort inputProcessorPort = scufl2Tools
+						.processorPortBindingForPort(inputActivityPort, profile)
+						.getBoundProcessorPort();
+				nestedWorkflowPorts.put(inputProcessorPort, inputWorkflowPort);
+				workflowPortToProcessorPort.put(inputWorkflowPort,
+						inputProcessorPort);
+				processorBinding.getInputPortBindings();
+			}
+
+			NamedSet<OutputWorkflowPort> outputWorkflowPorts = subDataflow
+					.getOutputPorts();
+			for (OutputActivityPort outputActivityPort : activity
+					.getOutputPorts()) {
+				OutputWorkflowPort outputWorkflowPort = outputWorkflowPorts
+						.getByName(outputActivityPort.getName());
+				OutputProcessorPort outputProcessorPort = scufl2Tools
+						.processorPortBindingForPort(outputActivityPort,
+								profile).getBoundProcessorPort();
+				nestedWorkflowPorts
+						.put(outputProcessorPort, outputWorkflowPort);
+				workflowPortToProcessorPort.put(outputWorkflowPort,
+						outputProcessorPort);
+			}
+
+			Graph subGraph = generateGraph(subDataflow, prefix,
+					processor.getName(), depth + 1);
+			// TODO why does this depth matter?
+			if (depth == 0)
+				subGraph.setWorkflowBean(processor);
+			if (interactive)
+				subGraph.setWorkflowBean(processor);
+			node.setGraph(subGraph);
+			node.setExpanded(true);
+
+			workflowToGraph.put(processor, subGraph);
+		} else {
+			graphElementMap.put(node.getId(), node);
+			workflowToGraph.put(processor, node);
+		}
+
+		NamedSet<InputProcessorPort> inputPorts = processor.getInputPorts();
+		if (inputPorts.size() == 0) {
+			GraphNode portNode = createGraphNode();
+			portNode.setShape(Shape.BOX);
+			portNode.setColor(Color.BLACK);
+			portNode.setFillColor(node.getFillColor());
+			portNode.setLineStyle(LineStyle.SOLID);
+			node.addSinkNode(portNode);
+		} else
+			for (InputPort inputPort : inputPorts) {
+				GraphNode portNode = createGraphNode();
+				portNode.setId("i" + inputPort.getName().replaceAll("\\.", ""));
+				portNode.setLabel(inputPort.getName());
+				portNode.setShape(Shape.BOX);
+				portNode.setColor(Color.BLACK);
+				portNode.setFillColor(node.getFillColor());
+				portNode.setLineStyle(LineStyle.SOLID);
+				if (depth == 0)
+					portNode.setWorkflowBean(inputPort);
+				if (interactive)
+					portNode.setWorkflowBean(inputPort);
+				if (!node.isExpanded())
+					workflowToGraph.put(inputPort, portNode);
+				ports.put(inputPort, portNode);
+				node.addSinkNode(portNode);
+				graphElementMap.put(portNode.getId(), portNode);
+				// portToActivity.put(inputPort, activity);
+				portToProcessor.put(inputPort, processor);
+			}
+
+		NamedSet<OutputProcessorPort> outputPorts = processor.getOutputPorts();
+		if (outputPorts.size() == 0) {
+			GraphNode portNode = createGraphNode();
+			portNode.setShape(Shape.BOX);
+			portNode.setColor(Color.BLACK);
+			portNode.setFillColor(node.getFillColor());
+			portNode.setLineStyle(LineStyle.SOLID);
+			node.addSourceNode(portNode);
+		} else
+			for (OutputPort outputPort : outputPorts) {
+				GraphNode portNode = createGraphNode();
+				portNode.setId("o" + outputPort.getName().replaceAll("\\.", ""));
+				portNode.setLabel(outputPort.getName());
+				portNode.setShape(Shape.BOX);
+				portNode.setColor(Color.BLACK);
+				portNode.setFillColor(node.getFillColor());
+				portNode.setLineStyle(LineStyle.SOLID);
+				if (depth == 0)
+					portNode.setWorkflowBean(outputPort);
+				if (interactive)
+					portNode.setWorkflowBean(outputPort);
+				if (!node.isExpanded())
+					workflowToGraph.put(outputPort, portNode);
+				ports.put(outputPort, portNode);
+				node.addSourceNode(portNode);
+				graphElementMap.put(portNode.getId(), portNode);
+				// portToActivity.put(outputPort, activity);
+				portToProcessor.put(outputPort, processor);
+			}
+
+		return node;
+	}
+
+	/**
+	 * Returns the dataflow.
+	 * 
+	 * @return the dataflow
+	 */
+	public Workflow getWorkflow() {
+		return workflow;
+	}
+
+	public Profile getProfile() {
+		return profile;
+	}
+
+	/**
+	 * Returns the dataflowSelectionModel.
+	 * 
+	 * @return the dataflowSelectionModel
+	 */
+	public DataflowSelectionModel getDataflowSelectionModel() {
+		return dataflowSelectionModel;
+	}
+
+	/**
+	 * Sets the dataflowSelectionModel.
+	 * 
+	 * @param dataflowSelectionModel
+	 *            the new dataflowSelectionModel
+	 */
+	public void setDataflowSelectionModel(
+			DataflowSelectionModel dataflowSelectionModel) {
+		if (this.dataflowSelectionModel != null)
+			this.dataflowSelectionModel.removeObserver(this);
+		this.dataflowSelectionModel = dataflowSelectionModel;
+		this.dataflowSelectionModel.addObserver(this);
+	}
+
+	/**
+	 * Sets the proportion of the node's jobs that have been completed.
+	 * 
+	 * @param nodeId
+	 *            the id of the node
+	 * @param complete
+	 *            the proportion of the nodes's jobs that have been completed, a
+	 *            value between 0.0 and 1.0
+	 */
+	public void setNodeCompleted(String nodeId, float complete) {
+		if (graphElementMap.containsKey(nodeId)) {
+			GraphElement graphElement = graphElementMap.get(nodeId);
+			graphElement.setCompleted(complete);
+		}
+	}
+
+	public void setEdgeActive(String edgeId, boolean active) {
+	}
+
+	/**
+	 * Returns the alignment.
+	 * 
+	 * @return the alignment
+	 */
+	public Alignment getAlignment() {
+		return alignment;
+	}
+
+	/**
+	 * Returns the portStyle.
+	 * 
+	 * @return the portStyle
+	 */
+	public PortStyle getPortStyle() {
+		return portStyle;
+	}
+
+	/**
+	 * Returns the portStyle for a processor.
+	 * 
+	 * @return the portStyle for a processor
+	 */
+	public PortStyle getPortStyle(Processor processor) {
+		if (processorPortStyle.containsKey(processor))
+			return processorPortStyle.get(processor);
+		return portStyle;
+	}
+
+	/**
+	 * Sets the alignment.
+	 * 
+	 * @param alignment
+	 *            the new alignment
+	 */
+	public void setAlignment(Alignment alignment) {
+		this.alignment = alignment;
+	}
+
+	/**
+	 * Sets the portStyle.
+	 * 
+	 * @param style
+	 *            the new portStyle
+	 */
+	public void setPortStyle(PortStyle portStyle) {
+		this.portStyle = portStyle;
+		processorPortStyle.clear();
+	}
+
+	/**
+	 * Sets the portStyle for a processor.
+	 * 
+	 * @param style
+	 *            the new portStyle for the processor
+	 */
+	public void setPortStyle(Processor processor, PortStyle portStyle) {
+		processorPortStyle.put(processor, portStyle);
+	}
+
+	/**
+	 * Shut down any processing and update threads related to this controller.
+	 * 
+	 */
+	public void shutdown() {
+	}
+
+	/**
+	 * Returns true if the default is to expand nested workflows.
+	 * 
+	 * @return true if the default is to expand nested workflows
+	 */
+	public boolean expandNestedDataflows() {
+		return expandNestedDataflows;
+	}
+
+	/**
+	 * Returns true if the nested dataflow should be expanded.
+	 * 
+	 * @param dataflow
+	 * @return true if the nested dataflow should be expanded
+	 */
+	public boolean expandNestedDataflow(Activity dataflow) {
+		if (dataflowExpansion.containsKey(dataflow))
+			return dataflowExpansion.get(dataflow);
+		return expandNestedDataflows;
+	}
+
+	/**
+	 * Sets the default for expanding nested workflows.
+	 * 
+	 * @param expand
+	 *            the default for expanding nested workflows
+	 */
+	public void setExpandNestedDataflows(boolean expand) {
+		dataflowExpansion.clear();
+		this.expandNestedDataflows = expand;
+	}
+
+	/**
+	 * Sets whether the nested dataflow should be expanded.
+	 * 
+	 * @param expand
+	 *            whether the nested dataflow should be expanded
+	 * @param dataflow
+	 *            the nested dataflow
+	 */
+	public void setExpandNestedDataflow(Activity dataflow, boolean expand) {
+		dataflowExpansion.put(dataflow, expand);
+	}
+
+	private boolean isSingleOutputProcessor(Object dataflowObject) {
+		boolean result = false;
+		if (dataflowObject instanceof Processor) {
+			Processor processor = (Processor) dataflowObject;
+			result = processor.getOutputPorts().size() == 1;
+		}
+		return result;
+	}
+
+	public boolean startEdgeCreation(GraphElement graphElement, Point point) {
+		if (!edgeCreationFromSource && !edgeCreationFromSink) {
+			Object dataflowObject = graphElement.getWorkflowBean();
+			if (dataflowObject instanceof ReceiverPort) {
+				edgeCreationSink = graphElement;
+				edgeCreationFromSink = true;
+			} else if (dataflowObject instanceof SenderPort
+					|| isSingleOutputProcessor(dataflowObject)) {
+				edgeCreationSource = graphElement;
+				edgeCreationFromSource = true;
+			} else if (graphElement instanceof GraphEdge) {
+				GraphEdge edge = (GraphEdge) graphElement;
+				edgeCreationSource = edge.getSource();
+				edgeCreationFromSource = true;
+				edgeMoveElement = edge;
+			}
+		}
+		return edgeCreationFromSource || edgeCreationFromSink;
+	}
+
+	public boolean moveEdgeCreationTarget(GraphElement graphElement, Point point) {
+		boolean edgeValid = false;
+		Object dataflowObject = graphElement.getWorkflowBean();
+		if (edgeCreationFromSink) {
+			if (graphElement instanceof GraphNode) {
+				Object sinkObject = edgeCreationSink.getWorkflowBean();
+				if (dataflowObject instanceof OutputPort) {
+					Processor sourceProcessor = portToProcessor
+							.get(dataflowObject);
+					if (sourceProcessor != null) {
+						Processor sinkProcessor = null;
+						if (sinkObject instanceof Processor)
+							sinkProcessor = (Processor) sinkObject;
+						else if (portToProcessor.containsKey(sinkObject))
+							sinkProcessor = portToProcessor.get(sinkObject);
+						if (sinkProcessor != null) {
+							Set<Processor> possibleSinkProcessors = scufl2Tools
+									.possibleDownStreamProcessors(workflow,
+											sourceProcessor);
+							if (possibleSinkProcessors.contains(sinkProcessor)) {
+								edgeCreationSource = graphElement;
+								edgeValid = true;
+							}
+						}
+						if (sinkObject instanceof OutputWorkflowPort) {
+							edgeCreationSource = graphElement;
+							edgeValid = true;
+						}
+					}
+				} else if (dataflowObject instanceof InputWorkflowPort) {
+					edgeCreationSource = graphElement;
+					edgeValid = true;
+				} else if (dataflowObject instanceof Processor) {
+					Processor sourceProcessor = (Processor) dataflowObject;
+					Processor sinkProcessor = null;
+					if (sinkObject instanceof Processor)
+						sinkProcessor = (Processor) sinkObject;
+					else if (portToProcessor.containsKey(sinkObject))
+						sinkProcessor = portToProcessor.get(sinkObject);
+					if (sinkProcessor != null) {
+						Set<Processor> possibleSinkProcessors = scufl2Tools
+								.possibleDownStreamProcessors(workflow,
+										sourceProcessor);
+						if (possibleSinkProcessors.contains(sinkProcessor)) {
+							edgeCreationSource = graphElement;
+							edgeValid = true;
+						}
+					}
+					if (sinkObject instanceof OutputWorkflowPort) {
+						edgeCreationSource = graphElement;
+						edgeValid = true;
+					}
+				}
+			}
+			if (!edgeValid)
+				edgeCreationSource = null;
+		} else if (edgeCreationFromSource) {
+			if (graphElement instanceof GraphNode) {
+				Object sourceObject = edgeCreationSource.getWorkflowBean();
+				if (dataflowObject instanceof InputPort) {
+					Processor sinkProcessor = portToProcessor
+							.get(dataflowObject);
+					if (sinkProcessor != null) {
+						Processor sourceProcessor = null;
+						if (sourceObject instanceof Processor)
+							sourceProcessor = (Processor) sourceObject;
+						else if (portToProcessor.containsKey(sourceObject))
+							sourceProcessor = portToProcessor.get(sourceObject);
+						if (sourceProcessor != null) {
+							Set<Processor> possibleSourceProcessors = scufl2Tools
+									.possibleUpStreamProcessors(workflow,
+											sinkProcessor);
+							if (possibleSourceProcessors
+									.contains(sourceProcessor)) {
+								edgeCreationSink = graphElement;
+								edgeValid = true;
+							}
+						}
+						if (sourceObject instanceof InputWorkflowPort) {
+							edgeCreationSink = graphElement;
+							edgeValid = true;
+						}
+					}
+				} else if (dataflowObject instanceof OutputWorkflowPort) {
+					if (sourceObject != null) {
+						edgeCreationSink = graphElement;
+						edgeValid = true;
+					}
+				} else if (dataflowObject instanceof Processor) {
+					Processor sinkProcessor = (Processor) dataflowObject;
+					Processor sourceProcessor = null;
+					if (sourceObject instanceof Processor)
+						sourceProcessor = (Processor) sourceObject;
+					else if (portToProcessor.containsKey(sourceObject))
+						sourceProcessor = portToProcessor.get(sourceObject);
+					if (sourceProcessor != null) {
+						Set<Processor> possibleSourceProcessors = scufl2Tools
+								.possibleUpStreamProcessors(workflow,
+										sinkProcessor);
+						if (possibleSourceProcessors.contains(sourceProcessor)) {
+							edgeCreationSink = graphElement;
+							edgeValid = true;
+						}
+					}
+					if (sourceObject instanceof InputWorkflowPort) {
+						edgeCreationSink = graphElement;
+						edgeValid = true;
+					}
+				}
+			}
+			if (!edgeValid)
+				edgeCreationSink = null;
+		}
+		return edgeValid;
+	}
+
+	public boolean stopEdgeCreation(GraphElement graphElement, Point point) {
+		boolean edgeCreated = false;
+		if (edgeCreationSource != null && edgeCreationSink != null) {
+			SenderPort source = null;
+			ReceiverPort sink = null;
+			Object sourceDataflowObject = edgeCreationSource.getWorkflowBean();
+			Object sinkDataflowObject = edgeCreationSink.getWorkflowBean();
+			if (sourceDataflowObject instanceof SenderPort)
+				source = (SenderPort) sourceDataflowObject;
+			else if (sourceDataflowObject instanceof Processor) {
+				Processor processor = (Processor) sourceDataflowObject;
+				source = showPortOptions(processor.getOutputPorts(), "output",
+						componentForPopups, point);
+			}
+			if (sinkDataflowObject instanceof ReceiverPort)
+				sink = (ReceiverPort) sinkDataflowObject;
+			else if (sinkDataflowObject instanceof Processor) {
+				Processor processor = (Processor) sinkDataflowObject;
+				sink = showPortOptions(processor.getInputPorts(), "input",
+						componentForPopups, point);
+			}
+			if (source != null && sink != null) {
+				Edit<?> edit = null;
+				if (edgeMoveElement == null) {
+					DataLink dataLink = new DataLink();
+					dataLink.setReceivesFrom(source);
+					dataLink.setSendsTo(sink);
+					edit = new AddDataLinkEdit(workflow, dataLink);
+				} else {
+					Object existingSink = edgeMoveElement.getSink()
+							.getWorkflowBean();
+					if (existingSink != sink) {
+						List<Edit<?>> editList = new ArrayList<Edit<?>>();
+						DataLink existingDataLink = (DataLink) edgeMoveElement
+								.getWorkflowBean();
+						DataLink newDataLink = new DataLink();
+						newDataLink.setReceivesFrom(existingDataLink
+								.getReceivesFrom());
+						newDataLink.setSendsTo(sink);
+						editList.add(new RemoveDataLinkEdit(workflow,
+								existingDataLink));
+						editList.add(new AddDataLinkEdit(workflow, newDataLink));
+						edit = new CompoundEdit(editList);
+					}
+				}
+				try {
+					if (edit != null) {
+						editManager.doDataflowEdit(workflow.getParent(), edit);
+						edgeCreated = true;
+					}
+				} catch (EditException e) {
+					logger.debug("Failed to create datalink from '"
+							+ source.getName() + "' to '" + sink.getName()
+							+ "'");
+				}
+			}
+		}
+		edgeCreationSource = null;
+		edgeCreationSink = null;
+		edgeMoveElement = null;
+		edgeCreationFromSource = false;
+		edgeCreationFromSink = false;
+
+		return edgeCreated;
+	}
+
+	private <T extends Port> T showPortOptions(NamedSet<T> ports,
+			String portType, Component component, Point point) {
+		T result = null;
+		if (ports.size() == 0) {
+			showMessageDialog(component, "Service has no " + portType
+					+ " ports to connect to");
+		} else if (ports.size() == 1)
+			result = ports.first();
+		else {
+			Object[] portNames = ports.getNames().toArray();
+			String portName = (String) showInputDialog(component, "Select an "
+					+ portType + " port", "Port Chooser", PLAIN_MESSAGE, null,
+					portNames, portNames[0]);
+			if (portName != null)
+				result = ports.getByName(portName);
+		}
+		return result;
+
+	}
+
+	public void resetSelection() {
+		if (dataflowSelectionModel != null)
+			for (Object dataflowElement : dataflowSelectionModel.getSelection()) {
+				GraphElement graphElement = workflowToGraph
+						.get(dataflowElement);
+				if (graphElement != null)
+					graphElement.setSelected(true);
+			}
+	}
+
+	public void setIteration(String nodeId, int iteration) {
+		if (graphElementMap.containsKey(nodeId)) {
+			GraphElement graphElement = graphElementMap.get(nodeId);
+			graphElement.setIteration(iteration);
+		}
+	}
+
+	public void setErrors(String nodeId, int errors) {
+		if (graphElementMap.containsKey(nodeId)) {
+			GraphElement graphElement = graphElementMap.get(nodeId);
+			graphElement.setErrors(errors);
+		}
+	}
+
+	@Override
+	public void notify(Observable<DataflowSelectionMessage> sender,
+			DataflowSelectionMessage message) throws Exception {
+		GraphElement graphElement = workflowToGraph.get(message.getElement());
+		if (graphElement != null)
+			graphElement.setSelected(message.getType().equals(
+					DataflowSelectionMessage.Type.ADDED));
+	}
+
+	/**
+	 * Returns the GraphEventManager.
+	 * 
+	 * @return the GraphEventManager
+	 */
+	public GraphEventManager getGraphEventManager() {
+		return graphEventManager;
+	}
+
+	/**
+	 * Sets the GraphEventManager.
+	 * 
+	 * @param graphEventManager
+	 *            the new GraphEventManager
+	 */
+	public void setGraphEventManager(GraphEventManager graphEventManager) {
+		this.graphEventManager = graphEventManager;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/a9a52bd5/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphEdge.java
----------------------------------------------------------------------
diff --git a/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphEdge.java b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphEdge.java
new file mode 100644
index 0000000..f781d63
--- /dev/null
+++ b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphEdge.java
@@ -0,0 +1,137 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package org.apache.taverna.workbench.models.graph;
+
+import java.awt.Point;
+import java.util.List;
+
+/**
+ * An edge connecting two nodes in a graph.
+ *
+ * @author David Withers
+ */
+public class GraphEdge extends GraphElement {
+	public enum ArrowStyle {NONE, NORMAL, DOT}
+
+	private GraphNode source;
+	private GraphNode sink;
+	private ArrowStyle arrowHeadStyle = ArrowStyle.NORMAL;
+	private ArrowStyle arrowTailStyle = ArrowStyle.NONE;
+	private List<Point> path;
+
+	/**
+	 * Constructs a new instance of Edge.
+	 *
+	 */
+	public GraphEdge(GraphController graphController) {
+		super(graphController);
+	}
+
+	/**
+	 * Returns the source.
+	 *
+	 * @return the source
+	 */
+	public GraphNode getSource() {
+		return source;
+	}
+
+	/**
+	 * Sets the source.
+	 *
+	 * @param source the new source
+	 */
+	public void setSource(GraphNode source) {
+		this.source = source;
+	}
+
+	/**
+	 * Returns the sink.
+	 *
+	 * @return the sink
+	 */
+	public GraphNode getSink() {
+		return sink;
+	}
+
+	/**
+	 * Sets the sink.
+	 *
+	 * @param sink the new sink
+	 */
+	public void setSink(GraphNode sink) {
+		this.sink = sink;
+	}
+
+	/**
+	 * Returns the arrowHeadStyle.
+	 *
+	 * @return the arrowHeadStyle
+	 */
+	public ArrowStyle getArrowHeadStyle() {
+		return arrowHeadStyle;
+	}
+
+	/**
+	 * Sets the arrowHeadStyle.
+	 *
+	 * @param arrowHeadStyle the new arrowHeadStyle
+	 */
+	public void setArrowHeadStyle(ArrowStyle arrowHeadStyle) {
+		this.arrowHeadStyle = arrowHeadStyle;
+	}
+
+	/**
+	 * Returns the arrowTailStyle.
+	 *
+	 * @return the arrowTailStyle
+	 */
+	public ArrowStyle getArrowTailStyle() {
+		return arrowTailStyle;
+	}
+
+	/**
+	 * Sets the arrowTailStyle.
+	 *
+	 * @param arrowTailStyle the new arrowTailStyle
+	 */
+	public void setArrowTailStyle(ArrowStyle arrowTailStyle) {
+		this.arrowTailStyle = arrowTailStyle;
+	}
+
+	/**
+	 * Returns the path.
+	 *
+	 * @return the path
+	 */
+	public List<Point> getPath() {
+		return path;
+	}
+
+	/**
+	 * Sets the path.
+	 *
+	 * @param path the new path
+	 */
+	public void setPath(List<Point> path) {
+		this.path = path;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/a9a52bd5/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphElement.java
----------------------------------------------------------------------
diff --git a/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphElement.java b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphElement.java
new file mode 100644
index 0000000..2c4263f
--- /dev/null
+++ b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphElement.java
@@ -0,0 +1,430 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package org.apache.taverna.workbench.models.graph;
+
+import java.awt.Color;
+import java.awt.Point;
+
+import org.apache.taverna.scufl2.api.common.WorkflowBean;
+
+/**
+ * An element of a graph.
+ * 
+ * @author David Withers
+ */
+public abstract class GraphElement {
+	public enum LineStyle {
+		NONE, SOLID, DOTTED
+	}
+
+	private String id;
+	private String label;
+	private Point labelPosition;
+	private LineStyle lineStyle = LineStyle.SOLID;
+	private Color color = Color.BLACK;
+	private Color fillColor;
+	private float opacity = 1f;
+	private GraphElement parent;
+	private boolean selected;
+	private boolean active;
+	private boolean interactive;
+	private boolean visible = true;
+	private boolean filtered;
+	private WorkflowBean workflowBean;
+	protected GraphController graphController;
+	protected float completed;
+	protected int iteration;
+	protected int errors;
+
+	protected GraphElement(GraphController graphController) {
+		this.graphController = graphController;
+	}
+
+	/**
+	 * Returns the eventManager.
+	 * 
+	 * @return the eventManager
+	 */
+	public GraphEventManager getEventManager() {
+		if (graphController != null) {
+			return graphController.getGraphEventManager();
+		}
+		return null;
+	}
+
+	/**
+	 * Returns the workflowBean.
+	 * 
+	 * @return the workflowBean
+	 */
+	public WorkflowBean getWorkflowBean() {
+		return workflowBean;
+	}
+
+	/**
+	 * Sets the workflowBean.
+	 * 
+	 * @param workflowBean
+	 *            the new workflowBean
+	 */
+	public void setWorkflowBean(WorkflowBean workflowBean) {
+		this.workflowBean = workflowBean;
+	}
+
+	/**
+	 * Returns the parent.
+	 * 
+	 * @return the parent
+	 */
+	public GraphElement getParent() {
+		return parent;
+	}
+
+	/**
+	 * Sets the parent.
+	 * 
+	 * @param parent
+	 *            the new parent
+	 */
+	protected void setParent(GraphElement parent) {
+		this.parent = parent;
+	}
+
+	/**
+	 * Returns the label.
+	 * 
+	 * @return the label
+	 */
+	public String getLabel() {
+		return label;
+	}
+
+	/**
+	 * Sets the label.
+	 * 
+	 * @param label
+	 *            the new label
+	 */
+	public void setLabel(String label) {
+		this.label = label;
+	}
+
+	/**
+	 * Returns the labelPosition.
+	 * 
+	 * @return the labelPosition
+	 */
+	public Point getLabelPosition() {
+		return labelPosition;
+	}
+
+	/**
+	 * Sets the labelPosition.
+	 * 
+	 * @param labelPosition
+	 *            the new labelPosition
+	 */
+	public void setLabelPosition(Point labelPosition) {
+		this.labelPosition = labelPosition;
+	}
+
+	/**
+	 * Returns the id.
+	 * 
+	 * @return the id
+	 */
+	public String getId() {
+		return id;
+	}
+
+	/**
+	 * Sets the id.
+	 * 
+	 * @param id
+	 *            the new id
+	 */
+	public void setId(String id) {
+		if (graphController != null) {
+			graphController.mapElement(id, this);
+		}
+		this.id = id;
+	}
+
+	/**
+	 * Returns the colour.
+	 * 
+	 * @return the colour
+	 */
+	public Color getColor() {
+		return color;
+	}
+
+	/**
+	 * Sets the colour.
+	 * 
+	 * @param color
+	 *            the new colour
+	 */
+	public void setColor(Color color) {
+		this.color = color;
+	}
+
+	/**
+	 * Returns the fillColor.
+	 * 
+	 * @return the fillColor
+	 */
+	public Color getFillColor() {
+		return fillColor;
+	}
+
+	/**
+	 * Sets the fillColor.
+	 * 
+	 * @param fillColor
+	 *            the new fillColor
+	 */
+	public void setFillColor(Color fillColor) {
+		this.fillColor = fillColor;
+	}
+
+	/**
+	 * Returns the lineStyle.
+	 * 
+	 * @return the lineStyle
+	 */
+	public LineStyle getLineStyle() {
+		return lineStyle;
+	}
+
+	/**
+	 * Sets the lineStyle.
+	 * 
+	 * @param lineStyle
+	 *            the new lineStyle
+	 */
+	public void setLineStyle(LineStyle lineStyle) {
+		this.lineStyle = lineStyle;
+	}
+
+	@Override
+	public String toString() {
+		return id + "[" + label + "]";
+	}
+
+	/**
+	 * Returns the selected.
+	 * 
+	 * @return the selected
+	 */
+	public boolean isSelected() {
+		return selected;
+	}
+
+	/**
+	 * Sets the selected.
+	 * 
+	 * @param selected
+	 *            the new selected
+	 */
+	public void setSelected(boolean selected) {
+		this.selected = selected;
+	}
+
+	/**
+	 * Returns the iteration.
+	 * 
+	 * @return the value of iteration
+	 */
+	public int getIteration() {
+		return iteration;
+	}
+
+	/**
+	 * Sets the iteration.
+	 * 
+	 * @param iteration
+	 *            the new value for iteration
+	 */
+	public void setIteration(int iteration) {
+		this.iteration = iteration;
+	}
+
+	/**
+	 * Returns the errors.
+	 * 
+	 * @return the value of errors
+	 */
+	public int getErrors() {
+		return errors;
+	}
+
+	/**
+	 * Sets the errors.
+	 * 
+	 * @param errors
+	 *            the new value for errors
+	 */
+	public void setErrors(int errors) {
+		this.errors = errors;
+	}
+
+	/**
+	 * Returns the completed.
+	 * 
+	 * @return the value of completed
+	 */
+	public float getCompleted() {
+		return completed;
+	}
+
+	/**
+	 * Sets the completed value.
+	 * 
+	 * @param completed
+	 */
+	public void setCompleted(float completed) {
+		this.completed = completed;
+	}
+
+	/**
+	 * Returns <code>true</code> if the element is active. The default value is
+	 * <code>false</code>.
+	 * 
+	 * @return <code>true</code> if the element is active
+	 */
+	public boolean isActive() {
+		return active;
+	}
+
+	/**
+	 * Sets the value of active.
+	 * 
+	 * @param active
+	 *            the new active
+	 */
+	public void setActive(boolean active) {
+		this.active = active;
+	}
+
+	/**
+	 * Returns <code>true</code> if the element is interactive. The default
+	 * value is <code>false</code>.
+	 * 
+	 * @return <code>true</code> if the element is interactive
+	 */
+	public boolean isInteractive() {
+		return interactive;
+	}
+
+	/**
+	 * Sets the value of interactive.
+	 * 
+	 * @param interactive
+	 *            the new interactive
+	 */
+	public void setInteractive(boolean interactive) {
+		this.interactive = interactive;
+	}
+
+	/**
+	 * Returns <code>true</code> if the element is visible. The default value is
+	 * <code>true</code>.
+	 * 
+	 * @return <code>true</code> if the element is visible
+	 */
+	public boolean isVisible() {
+		return visible;
+	}
+
+	/**
+	 * Sets whether the element is visible.
+	 * 
+	 * @param visible
+	 *            the new value for visible
+	 */
+	public void setVisible(boolean visible) {
+		this.visible = visible;
+	}
+
+	/**
+	 * Returns the opacity value. The default value is 1.0
+	 * 
+	 * @return the opacity value
+	 */
+	public float getOpacity() {
+		return opacity;
+	}
+
+	/**
+	 * Sets the opacity of the element. Must be a value between 0.0 and 1.0.
+	 * 
+	 * @param opacity
+	 *            the new opacity value
+	 */
+	public void setOpacity(float opacity) {
+		this.opacity = opacity;
+	}
+
+	/**
+	 * Returns <code>true</code> if the element is filtered.
+	 * 
+	 * @return <code>true</code> if the element is filtered
+	 */
+	public boolean isFiltered() {
+		return filtered;
+	}
+
+	/**
+	 * Sets the value of filtered.
+	 * 
+	 * @param filtered
+	 *            the new value for filtered
+	 */
+	public void setFiltered(boolean filtered) {
+		this.filtered = filtered;
+	}
+
+	@Override
+	public int hashCode() {
+		final int prime = 31;
+		int result = 1;
+		result = prime * result + ((id == null) ? 0 : id.hashCode());
+		return result;
+	}
+
+	@Override
+	public boolean equals(Object obj) {
+		if (this == obj)
+			return true;
+		if (obj == null)
+			return false;
+		if (getClass() != obj.getClass())
+			return false;
+
+		// Equality by id
+		GraphElement other = (GraphElement) obj;
+		if (id == null)
+			return (other.id == null);
+		return id.equals(other.id);
+	}
+
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/a9a52bd5/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphEventManager.java
----------------------------------------------------------------------
diff --git a/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphEventManager.java b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphEventManager.java
new file mode 100644
index 0000000..5a0eaa8
--- /dev/null
+++ b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphEventManager.java
@@ -0,0 +1,47 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester   
+ * 
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ * 
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *    
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *    
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package org.apache.taverna.workbench.models.graph;
+
+public interface GraphEventManager {
+	void mouseClicked(GraphElement graphElement, short button, boolean altKey,
+			boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+			int screenY);
+
+	void mouseDown(GraphElement graphElement, short button, boolean altKey,
+			boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+			int screenY);
+
+	void mouseUp(GraphElement graphElement, short button, boolean altKey,
+			boolean ctrlKey, boolean metaKey, final int x, final int y,
+			int screenX, int screenY);
+
+	void mouseMoved(GraphElement graphElement, short button, boolean altKey,
+			boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+			int screenY);
+
+	void mouseOver(GraphElement graphElement, short button, boolean altKey,
+			boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+			int screenY);
+
+	void mouseOut(GraphElement graphElement, short button, boolean altKey,
+			boolean ctrlKey, boolean metaKey, int x, int y, int screenX,
+			int screenY);
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/a9a52bd5/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphNode.java
----------------------------------------------------------------------
diff --git a/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphNode.java b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphNode.java
new file mode 100644
index 0000000..5730ef0
--- /dev/null
+++ b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphNode.java
@@ -0,0 +1,153 @@
+/*******************************************************************************
+ * Copyright (C) 2007 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package org.apache.taverna.workbench.models.graph;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * A node of a graph that can optionally contain other graphs.
+ * 
+ * @author David Withers
+ */
+public class GraphNode extends GraphShapeElement {
+	private List<GraphNode> sourceNodes = new ArrayList<>();
+	private List<GraphNode> sinkNodes = new ArrayList<>();
+	private Graph graph;
+	private boolean expanded;
+
+	/**
+	 * Constructs a new instance of Node.
+	 * 
+	 */
+	public GraphNode(GraphController graphController) {
+		super(graphController);
+	}
+
+	/**
+	 * Adds a sink node.
+	 * 
+	 * @param sinkNode
+	 *            the sink node to add
+	 */
+	public void addSinkNode(GraphNode sinkNode) {
+		sinkNode.setParent(this);
+		sinkNodes.add(sinkNode);
+	}
+
+	/**
+	 * Adds a source node.
+	 * 
+	 * @param sourceNode
+	 *            the source node to add
+	 */
+	public void addSourceNode(GraphNode sourceNode) {
+		sourceNode.setParent(this);
+		sourceNodes.add(sourceNode);
+	}
+
+	/**
+	 * Returns the graph that this node contains.
+	 * 
+	 * @return the graph that this node contains
+	 */
+	public Graph getGraph() {
+		return graph;
+	}
+
+	/**
+	 * Returns the sinkNodes.
+	 * 
+	 * @return the sinkNodes
+	 */
+	public List<GraphNode> getSinkNodes() {
+		return sinkNodes;
+	}
+
+	/**
+	 * Returns the sourceNodes.
+	 * 
+	 * @return the sourceNodes
+	 */
+	public List<GraphNode> getSourceNodes() {
+		return sourceNodes;
+	}
+
+	/**
+	 * Returns true if this node is expanded to show the contained graph.
+	 * 
+	 * @return true if this node is expanded
+	 */
+	public boolean isExpanded() {
+		return expanded;
+	}
+
+	/**
+	 * Removes a sink node.
+	 * 
+	 * @param sinkNode
+	 *            the node to remove
+	 * @return true if the node was removed, false otherwise
+	 */
+	public boolean removeSinkNode(GraphNode sinkNode) {
+		return sinkNodes.remove(sinkNode);
+	}
+
+	/**
+	 * Removes a source node.
+	 * 
+	 * @param sourceNode
+	 *            the node to remove
+	 * @return true if the node was removed, false otherwise
+	 */
+	public boolean removeSourceNode(GraphNode sourceNode) {
+		return sourceNodes.remove(sourceNode);
+	}
+
+	/**
+	 * Sets whether this node is expanded to show the contained graph.
+	 * 
+	 * @param expanded
+	 *            true if this node is expanded
+	 */
+	public void setExpanded(boolean expanded) {
+		this.expanded = expanded;
+	}
+
+	/**
+	 * Sets the graph that this node contains.
+	 * 
+	 * @param graph
+	 *            the new graph
+	 */
+	public void setGraph(Graph graph) {
+		if (graph != null)
+			graph.setParent(this);
+		this.graph = graph;
+	}
+
+	@Override
+	public void setSelected(boolean selected) {
+		super.setSelected(selected);
+		if (isExpanded())
+			getGraph().setSelected(selected);
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/a9a52bd5/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphShapeElement.java
----------------------------------------------------------------------
diff --git a/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphShapeElement.java b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphShapeElement.java
new file mode 100644
index 0000000..955ec08
--- /dev/null
+++ b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/GraphShapeElement.java
@@ -0,0 +1,119 @@
+/*******************************************************************************
+ * Copyright (C) 2008 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package org.apache.taverna.workbench.models.graph;
+
+import java.awt.Dimension;
+import java.awt.Point;
+
+/**
+ * A Graph element that has shape, size and position properties.
+ * 
+ * @author David Withers
+ */
+public class GraphShapeElement extends GraphElement {
+	public enum Shape {
+		BOX, RECORD, HOUSE, INVHOUSE, DOT, CIRCLE, TRIANGLE, INVTRIANGLE
+	}
+
+	private Shape shape;
+	private int x, y, width, height;
+
+	public GraphShapeElement(GraphController graphController) {
+		super(graphController);
+	}
+
+	/**
+	 * Returns the height.
+	 * 
+	 * @return the height
+	 */
+	public int getHeight() {
+		return height;
+	}
+
+	/**
+	 * Returns the position.
+	 * 
+	 * @return the position
+	 */
+	public Point getPosition() {
+		return new Point(x, y);
+	}
+
+	/**
+	 * Returns the shape of the element.
+	 * 
+	 * @return the shape of the element
+	 */
+	public Shape getShape() {
+		return shape;
+	}
+
+	/**
+	 * Returns the width.
+	 * 
+	 * @return the width
+	 */
+	public int getWidth() {
+		return width;
+	}
+
+	/**
+	 * Sets the position.
+	 * 
+	 * @param position
+	 *            the new position
+	 */
+	public void setPosition(Point position) {
+		x = position.x;
+		y = position.y;
+	}
+
+	/**
+	 * Sets the shape of the element.
+	 * 
+	 * @param shape
+	 *            the new shape of the element
+	 */
+	public void setShape(Shape shape) {
+		this.shape = shape;
+	}
+
+	/**
+	 * Returns the size of the element.
+	 * 
+	 * @return the size of the element
+	 */
+	public Dimension getSize() {
+		return new Dimension(width, height);
+	}
+
+	/**
+	 * Sets the size of the element.
+	 * 
+	 * @param size
+	 *            the new size of the node
+	 */
+	public void setSize(Dimension size) {
+		width = size.width;
+		height = size.height;
+	}
+}

http://git-wip-us.apache.org/repos/asf/incubator-taverna-workbench/blob/a9a52bd5/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/dot/GraphLayout.java
----------------------------------------------------------------------
diff --git a/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/dot/GraphLayout.java b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/dot/GraphLayout.java
new file mode 100644
index 0000000..4d1b4bf
--- /dev/null
+++ b/taverna-graph-model/src/main/java/org/apache/taverna/workbench/models/graph/dot/GraphLayout.java
@@ -0,0 +1,326 @@
+/*******************************************************************************
+ * Copyright (C) 2008 The University of Manchester
+ *
+ *  Modifications to the initial code base are copyright of their
+ *  respective authors, or their employers as appropriate.
+ *
+ *  This program is free software; you can redistribute it and/or
+ *  modify it under the terms of the GNU Lesser General Public License
+ *  as published by the Free Software Foundation; either version 2.1 of
+ *  the License, or (at your option) any later version.
+ *
+ *  This program is distributed in the hope that it will be useful, but
+ *  WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
+ *  Lesser General Public License for more details.
+ *
+ *  You should have received a copy of the GNU Lesser General Public
+ *  License along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
+ ******************************************************************************/
+package org.apache.taverna.workbench.models.graph.dot;
+
+import static java.lang.Float.parseFloat;
+import static org.apache.taverna.workbench.models.graph.Graph.Alignment.HORIZONTAL;
+
+import java.awt.Dimension;
+import java.awt.Point;
+import java.awt.Rectangle;
+import java.io.StringReader;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.apache.taverna.workbench.models.graph.Graph;
+import org.apache.taverna.workbench.models.graph.GraphController;
+import org.apache.taverna.workbench.models.graph.GraphEdge;
+import org.apache.taverna.workbench.models.graph.GraphElement;
+import org.apache.taverna.workbench.models.graph.GraphNode;
+
+import org.apache.log4j.Logger;
+
+/**
+ * Lays out a graph from a DOT layout.
+ * 
+ * @author David Withers
+ */
+public class GraphLayout implements DOTParserVisitor {
+	private static final Logger logger = Logger.getLogger(GraphLayout.class);
+	private static final int BORDER = 10;
+
+	private Rectangle bounds;
+	private Rectangle requiredBounds;
+	private GraphController graphController;
+	private int xOffset;
+	private int yOffset;
+
+	public Rectangle layoutGraph(GraphController graphController, Graph graph,
+			String laidOutDot, Rectangle requiredBounds) throws ParseException {
+		this.graphController = graphController;
+		this.requiredBounds = requiredBounds;
+
+		bounds = null;
+		xOffset = 0;
+		yOffset = 0;
+
+		logger.debug(laidOutDot);
+		DOTParser parser = new DOTParser(new StringReader(laidOutDot));
+		parser.parse().jjtAccept(this, graph);
+
+		// int xOffset = (bounds.width - bounds.width) / 2;
+		// int yOffset = (bounds.height - bounds.height) / 2;
+
+		return new Rectangle(xOffset, yOffset, bounds.width, bounds.height);
+	}
+
+	@Override
+	public Object visit(SimpleNode node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTParse node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTGraph node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTStatementList node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTStatement node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTAttributeStatement node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTNodeStatement node, Object data) {
+		GraphElement element = graphController.getElement(removeQuotes(node
+				.getName()));
+		if (element != null)
+			return node.childrenAccept(this, element);
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTNodeId node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTPort node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTEdgeStatement node, Object data) {
+		StringBuilder id = new StringBuilder();
+		id.append(removeQuotes(node.getName()));
+		if (node.getPort() != null) {
+			id.append(":");
+			id.append(removeQuotes(node.getPort()));
+		}
+		if (node.children != null)
+			for (Node child : node.children)
+				if (child instanceof ASTEdgeRHS) {
+					NamedNode rhsNode = (NamedNode) child.jjtAccept(this, data);
+					id.append("->");
+					id.append(removeQuotes(rhsNode.getName()));
+					if (rhsNode.getPort() != null) {
+						id.append(":");
+						id.append(removeQuotes(rhsNode.getPort()));
+					}
+				}
+		GraphElement element = graphController.getElement(id.toString());
+		if (element != null)
+			return node.childrenAccept(this, element);
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTSubgraph node, Object data) {
+		GraphElement element = graphController.getElement(removeQuotes(
+				node.getName()).substring("cluster_".length()));
+		if (element != null)
+			return node.childrenAccept(this, element);
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTEdgeRHS node, Object data) {
+		return node;
+	}
+
+	@Override
+	public Object visit(ASTAttributeList node, Object data) {
+		return node.childrenAccept(this, data);
+	}
+
+	@Override
+	public Object visit(ASTAList node, Object data) {
+		if (data instanceof Graph) {
+			Graph graph = (Graph) data;
+			if ("bb".equalsIgnoreCase(node.getName())) {
+				Rectangle rect = getRectangle(node.getValue());
+				if (rect.width == 0 && rect.height == 0) {
+					rect.width = 500;
+					rect.height = 500;
+				}
+				if (bounds == null) {
+					bounds = calculateBounds(rect);
+					rect = bounds;
+				}
+				graph.setSize(rect.getSize());
+				graph.setPosition(rect.getLocation());
+			} else if ("lp".equalsIgnoreCase(node.getName())) {
+				if (bounds != null)
+					graph.setLabelPosition(getPoint(node.getValue()));
+			}
+		} else if (data instanceof GraphNode) {
+			GraphNode graphNode = (GraphNode) data;
+			if ("width".equalsIgnoreCase(node.getName()))
+				graphNode.setSize(new Dimension(getSize(node.getValue()),
+						graphNode.getHeight()));
+			else if ("height".equalsIgnoreCase(node.getName()))
+				graphNode.setSize(new Dimension(graphNode.getWidth(),
+						getSize(node.getValue())));
+			else if ("pos".equalsIgnoreCase(node.getName())) {
+				Point position = getPoint(node.getValue());
+				position.x = position.x - (graphNode.getWidth() / 2);
+				position.y = position.y - (graphNode.getHeight() / 2);
+				graphNode.setPosition(position);
+			} else if ("rects".equalsIgnoreCase(node.getName())) {
+				List<Rectangle> rectangles = getRectangles(node.getValue());
+				List<GraphNode> sinkNodes = graphNode.getSinkNodes();
+				if (graphController.getAlignment().equals(HORIZONTAL)) {
+					Rectangle rect = rectangles.remove(0);
+					graphNode.setSize(rect.getSize());
+					graphNode.setPosition(rect.getLocation());
+				} else {
+					Rectangle rect = rectangles.remove(sinkNodes.size());
+					graphNode.setSize(rect.getSize());
+					graphNode.setPosition(rect.getLocation());
+				}
+				Point origin = graphNode.getPosition();
+				for (GraphNode sinkNode : sinkNodes) {
+					Rectangle rect = rectangles.remove(0);
+					rect.setLocation(rect.x - origin.x, rect.y - origin.y);
+					sinkNode.setSize(rect.getSize());
+					sinkNode.setPosition(rect.getLocation());
+				}
+				for (GraphNode sourceNode : graphNode.getSourceNodes()) {
+					Rectangle rect = rectangles.remove(0);
+					rect.setLocation(rect.x - origin.x, rect.y - origin.y);
+					sourceNode.setSize(rect.getSize());
+					sourceNode.setPosition(rect.getLocation());
+				}
+			}
+		} else if (data instanceof GraphEdge) {
+			GraphEdge graphEdge = (GraphEdge) data;
+			if ("pos".equalsIgnoreCase(node.getName()))
+				graphEdge.setPath(getPath(node.getValue()));
+		}
+		return node.childrenAccept(this, data);
+	}
+
+	private Rectangle calculateBounds(Rectangle bounds) {
+		bounds = new Rectangle(bounds);
+		bounds.width += BORDER;
+		bounds.height += BORDER;
+		Rectangle newBounds = new Rectangle(bounds);
+		double ratio = bounds.width / (float) bounds.height;
+		double requiredRatio = requiredBounds.width
+				/ (float) requiredBounds.height;
+		// adjust the bounds so they match the aspect ration of the required bounds
+		if (ratio > requiredRatio)
+			newBounds.height = (int) (ratio / requiredRatio * bounds.height);
+		else if (ratio < requiredRatio)
+			newBounds.width = (int) (requiredRatio / ratio * bounds.width);
+
+		xOffset = (newBounds.width - bounds.width) / 2;
+		yOffset = (newBounds.height - bounds.height) / 2;
+		// adjust the bounds and so they are not less than the required bounds
+		if (newBounds.width < requiredBounds.width) {
+			xOffset += (requiredBounds.width - newBounds.width) / 2;
+			newBounds.width = requiredBounds.width;
+		}
+		if (newBounds.height < requiredBounds.height) {
+			yOffset += (requiredBounds.height - newBounds.height) / 2;
+			newBounds.height = requiredBounds.height;
+		}
+		// adjust the offset for the border
+		xOffset += BORDER / 2;
+		yOffset += BORDER / 2;
+		return newBounds;
+	}
+
+	private List<Point> getPath(String value) {
+		List<Point> path = new ArrayList<>();
+		for (String point : removeQuotes(value).split(" ")) {
+			String[] coords = point.split(",");
+			if (coords.length == 2) {
+				int x = (int) parseFloat(coords[0]) + xOffset;
+				int y = (int) parseFloat(coords[1]) + yOffset;
+				path.add(new Point(x, flipY(y)));
+			}
+		}
+		return path;
+	}
+
+	private int flipY(int y) {
+		return bounds.height - y;
+	}
+
+	private List<Rectangle> getRectangles(String value) {
+		List<Rectangle> rectangles = new ArrayList<>();
+		String[] rects = value.split(" ");
+		for (String rectangle : rects)
+			rectangles.add(getRectangle(rectangle));
+		return rectangles;
+	}
+
+	private Rectangle getRectangle(String value) {
+		String[] coords = removeQuotes(value).split(",");
+		Rectangle rectangle = new Rectangle();
+		rectangle.x = (int) parseFloat(coords[0]);
+		rectangle.y = (int) parseFloat(coords[3]);
+		rectangle.width = (int) parseFloat(coords[2]) - rectangle.x;
+		rectangle.height = rectangle.y - (int) parseFloat(coords[1]);
+		rectangle.x += xOffset;
+		rectangle.y += yOffset;
+		if (bounds != null)
+			rectangle.y = flipY(rectangle.y);
+		else
+			rectangle.y = rectangle.height - rectangle.y;
+		return rectangle;
+	}
+
+	private Point getPoint(String value) {
+		String[] coords = removeQuotes(value).split(",");
+		return new Point(xOffset + (int) parseFloat(coords[0]), flipY(yOffset
+				+ (int) parseFloat(coords[1])));
+	}
+
+	private int getSize(String value) {
+		return (int) (parseFloat(removeQuotes(value)) * 72);
+	}
+
+	private String removeQuotes(String value) {
+		String result = value.trim();
+		if (result.startsWith("\""))
+			result = result.substring(1);
+		if (result.endsWith("\""))
+			result = result.substring(0, result.length() - 1);
+		result = result.replaceAll("\\\\", "");
+		return result;
+	}
+}