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;
+       }
+}

Reply via email to