/** * Copyright (c) 2006 Borland Software Corp. * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * bblajer - initial API and implementation */ package org.eclipse.gmf.runtime.lite.services; import java.util.HashMap; import java.util.Iterator; import java.util.List; import org.eclipse.draw2d.geometry.Dimension; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.PointList; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.draw2d.graph.CompoundDirectedGraph; import org.eclipse.draw2d.graph.CompoundDirectedGraphLayout; import org.eclipse.draw2d.graph.EdgeList; import org.eclipse.draw2d.graph.NodeList; import org.eclipse.draw2d.graph.Subgraph; import org.eclipse.emf.common.command.AbstractCommand; import org.eclipse.emf.common.command.Command; import org.eclipse.gef.ConnectionEditPart; import org.eclipse.gef.EditPart; import org.eclipse.gef.GraphicalEditPart; import org.eclipse.gef.RequestConstants; import org.eclipse.gef.commands.CompoundCommand; import org.eclipse.gef.requests.ChangeBoundsRequest; import org.eclipse.gmf.runtime.lite.requests.SetAllBendpointsRequest; import org.eclipse.gmf.runtime.notation.Bounds; import org.eclipse.gmf.runtime.notation.Diagram; import org.eclipse.gmf.runtime.notation.Edge; import org.eclipse.gmf.runtime.notation.Node; import org.eclipse.gmf.runtime.notation.View; /** * Default implementation of <code>IDiagramLayouter</code> that delegates to {@link org.eclipse.draw2d.graph.CompoundDirectedGraphLayout}. */ public class DefaultDiagramLayouter implements IDiagramLayouter { public Command layout(GraphicalEditPart container) { CompoundDirectedGraph graph = new CompoundDirectedGraph(); HashMap<EditPart, org.eclipse.draw2d.graph.Node> views2Nodes = buildGraph(container, graph); CompoundDirectedGraphLayout layout = new CompoundDirectedGraphLayout(); layout.visit(graph); return createLayoutCommand(container, views2Nodes, graph); } public Command layout(GraphicalEditPart container, List<GraphicalEditPart> selectedElements) { //XXX: Currently, selectedElements argument is ignored. return layout(container); } protected HashMap<EditPart, org.eclipse.draw2d.graph.Node> buildGraph(GraphicalEditPart container, CompoundDirectedGraph graph) { HashMap<EditPart, org.eclipse.draw2d.graph.Node> views2Nodes = new HashMap<EditPart, org.eclipse.draw2d.graph.Node>(); traverseChildren(container, new SubGraphBuilder(null, graph, views2Nodes)); Diagram diagram = ((View) container.getModel()).getDiagram(); for(Iterator it = diagram.getEdges().iterator(); it.hasNext(); ) { Edge nextEdge = (Edge) it.next(); ConnectionEditPart nextEdgeEP = (ConnectionEditPart) container.getViewer().getEditPartRegistry().get(nextEdge); EditPart source = nextEdgeEP.getSource(); EditPart target = nextEdgeEP.getTarget(); org.eclipse.draw2d.graph.Node sourceNode = views2Nodes.get(source); org.eclipse.draw2d.graph.Node targetNode = views2Nodes.get(target); if (sourceNode == null || targetNode == null) { continue; } org.eclipse.draw2d.graph.Edge edge = createEdge(nextEdgeEP, sourceNode, targetNode); if (edge != null) { graph.edges.add(edge); } } return views2Nodes; } protected static interface Traverser { public void acceptChild(GraphicalEditPart childEditPart); public void childrenTraversed(GraphicalEditPart parentEditPart); } protected void traverseChildren(GraphicalEditPart container, Traverser traverser) { View view = (View) container.getModel(); for(Iterator it = view.getChildren().iterator(); it.hasNext(); ) { View nextChild = (View) it.next(); GraphicalEditPart nextChildEP = (GraphicalEditPart) container.getViewer().getEditPartRegistry().get(nextChild); if (nextChildEP == null) { continue; } traverser.acceptChild(nextChildEP); } traverser.childrenTraversed(container); } protected class SubGraphBuilder implements Traverser { private final Subgraph myParent; private final CompoundDirectedGraph myGraph; private final HashMap<EditPart, org.eclipse.draw2d.graph.Node> myViews2Nodes; public SubGraphBuilder(Subgraph parent, CompoundDirectedGraph graph, HashMap<EditPart, org.eclipse.draw2d.graph.Node> views2Nodes) { myParent = parent; myGraph = graph; myViews2Nodes = views2Nodes; } public void acceptChild(GraphicalEditPart childEditPart) { org.eclipse.draw2d.graph.Node node = createNode(myParent, childEditPart); if (node != null) { setNodePosition(node); myGraph.nodes.add(node); myViews2Nodes.put(childEditPart, node); if (node instanceof org.eclipse.draw2d.graph.Subgraph) { traverseChildren(childEditPart, new SubGraphBuilder((org.eclipse.draw2d.graph.Subgraph) node, myGraph, myViews2Nodes)); } } } public void childrenTraversed(GraphicalEditPart parentEditPart) { } } protected org.eclipse.draw2d.graph.Edge createEdge(ConnectionEditPart next, org.eclipse.draw2d.graph.Node sourceNode, org.eclipse.draw2d.graph.Node targetNode) { return new org.eclipse.draw2d.graph.Edge(next, sourceNode, targetNode); } protected org.eclipse.draw2d.graph.Node createNode(org.eclipse.draw2d.graph.Subgraph parent, GraphicalEditPart next) { boolean hasChildren = hasChildren(next); if (!hasChildren) { if (isValidElementForLayout(next)) { return new org.eclipse.draw2d.graph.Node(next, parent); } return null; } return new org.eclipse.draw2d.graph.Subgraph(next, parent); } protected boolean hasChildren(GraphicalEditPart gep) { for (Iterator it = gep.getChildren().iterator(); it.hasNext(); ) { GraphicalEditPart next = (GraphicalEditPart) it.next(); if (isValidElementForLayout(next)) { return true; } if (hasChildren(next)) { return true; } } return false; } protected boolean isValidElementForLayout(GraphicalEditPart gep) { if (false == gep.getModel() instanceof Node) { return false; } Node view = (Node) gep.getModel(); if (!view.isSetElement()) { return false; } return view.getLayoutConstraint() instanceof Bounds; } protected void setNodePosition(org.eclipse.draw2d.graph.Node node) { GraphicalEditPart editPart = (GraphicalEditPart) node.data; Node notationalNode = (Node) editPart.getModel(); if (notationalNode.getLayoutConstraint() instanceof Bounds) { Rectangle bounds = editPart.getFigure().getBounds().getCopy(); editPart.getFigure().translateToAbsolute(bounds); node.x = bounds.x; node.y = bounds.y; node.width = bounds.width; node.height = bounds.height; } } protected Rectangle getNodePosition(org.eclipse.draw2d.graph.Node node) { Rectangle rect = new Rectangle(node.x, node.y, node.width, node.height); return rect; } protected Command createLayoutCommand(final GraphicalEditPart container, final HashMap<EditPart, org.eclipse.draw2d.graph.Node> views2Nodes, final CompoundDirectedGraph graph) { return new AbstractCommand() { /** * Cache of the executed commands for undo-redo functionality. */ private CompoundCommand layoutCommand; public void redo() { layoutCommand.execute(); } public void execute() { layoutCommand = new CompoundCommand(); traverseChildren(container, new ChildrenLayouter(views2Nodes, layoutCommand)); createLayoutEdgesCommand(graph.edges, layoutCommand); } @Override protected boolean prepare() { return true; } @Override public boolean canUndo() { return layoutCommand.canUndo(); } @Override public void undo() { layoutCommand.undo(); } }; } protected class ChildrenLayouter implements Traverser { private final CompoundCommand myCommand; private final HashMap<EditPart, org.eclipse.draw2d.graph.Node> myViews2Nodes; public ChildrenLayouter(HashMap<EditPart, org.eclipse.draw2d.graph.Node> views2Nodes, CompoundCommand command) { myViews2Nodes = views2Nodes; myCommand = command; } public void acceptChild(GraphicalEditPart childEditPart) { org.eclipse.draw2d.graph.Node node = myViews2Nodes.get(childEditPart); if (node == null) { return; } ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_MOVE); Rectangle bounds = getNodePosition(node); Point ptLocation = new Point(bounds.x, bounds.y); Rectangle oldBounds = childEditPart.getFigure().getBounds(); childEditPart.getFigure().translateToAbsolute(oldBounds); Point oldLocation = oldBounds.getLocation(); Dimension delta = ptLocation.getDifference(oldLocation); request.setEditParts(childEditPart); request.setMoveDelta(new Point(delta.width, delta.height)); request.setSizeDelta(bounds.getSize().getDifference(oldBounds.getSize())); request.setLocation(ptLocation); org.eclipse.gef.commands.Command cmd = childEditPart.getCommand(request); if (cmd != null && cmd.canExecute()) { cmd.execute(); myCommand.add(cmd); } } public void childrenTraversed(GraphicalEditPart parentEditPart) { parentEditPart.getFigure().invalidateTree(); parentEditPart.getFigure().validate(); traverseChildren(parentEditPart, new Traverser() { public void acceptChild(GraphicalEditPart childEditPart) { traverseChildren(childEditPart, ChildrenLayouter.this); } public void childrenTraversed(GraphicalEditPart parentEditPart) { } }); } } protected void createLayoutEdgesCommand(EdgeList edges, CompoundCommand command) { for(Iterator it = edges.iterator(); it.hasNext(); ) { org.eclipse.draw2d.graph.Edge next = (org.eclipse.draw2d.graph.Edge) it.next(); if (next.data instanceof ConnectionEditPart == false) { continue; } ConnectionEditPart connection = (ConnectionEditPart) next.data; PointList points = getPoints(next); SetAllBendpointsRequest request = new SetAllBendpointsRequest(); request.setConnectionEditPart(connection); request.setPoints(points); org.eclipse.gef.commands.Command cmd = connection.getCommand(request); if (cmd != null && cmd.canExecute()) { cmd.execute(); command.add(cmd); } } } private PointList getPoints(org.eclipse.draw2d.graph.Edge edge) { PointList result = new PointList(); result.addPoint(((GraphicalEditPart) ((ConnectionEditPart) edge.data).getSource()).getFigure().getBounds().getCenter()); NodeList vnodes = edge.vNodes; if (vnodes != null) { for (int i = 0; i < vnodes.size(); i++) { org.eclipse.draw2d.graph.Node vn = vnodes.getNode(i); Rectangle nextPosition = getNodePosition(vn); result.addPoint(nextPosition.getCenter()); } } result.addPoint(((GraphicalEditPart) ((ConnectionEditPart) edge.data).getTarget()).getFigure().getBounds().getCenter()); straightenPoints(result); result.removePoint(0); result.removePoint(result.size() - 1); return result; } private void straightenPoints(PointList points) { double flatnessTolerance = Math.cos(Math.PI/360); //angles that are almost flat are treated as flat removeFlatAngles(points, flatnessTolerance); } private void removeFlatAngles(PointList points, double flatnessTolerance) { for(int i = 0; i < points.size() - 2 && points.size() > 3; i++) { while (i < points.size() - 2 && points.size() > 3 && cos(points.getPoint(i), points.getPoint(i+1), points.getPoint(i+2)) > flatnessTolerance) { points.removePoint(i+1); } } } /** * Returns the cosine of the angle abc. * @return */ private double cos(Point a, Point b, Point c) { double ab2 = b.getDistance2(a); double ac2 = c.getDistance2(a); double bc2 = b.getDistance2(c); return (ab2 + ac2 - bc2) / (2 * Math.sqrt(ab2 * ac2)); } }