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