/******************************************************************************* * Copyright (c) 2010-2015 Henshin developers. 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: * TU Berlin, University of Luxembourg, SES S.A. *******************************************************************************/ package de.tub.tfs.muvitor.actions; import java.util.Collection; import java.util.Collections; 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.eclipse.draw2d.Animation; import org.eclipse.draw2d.LayoutAnimator; import org.eclipse.draw2d.geometry.Insets; import org.eclipse.draw2d.geometry.Point; import org.eclipse.draw2d.geometry.Rectangle; import org.eclipse.draw2d.graph.DirectedGraph; import org.eclipse.draw2d.graph.DirectedGraphLayout; import org.eclipse.draw2d.graph.Edge; import org.eclipse.draw2d.graph.Node; import org.eclipse.gef.ConnectionEditPart; import org.eclipse.gef.EditPart; import org.eclipse.gef.EditPartViewer; import org.eclipse.gef.GraphicalEditPart; import org.eclipse.gef.NodeEditPart; import org.eclipse.gef.RequestConstants; import org.eclipse.gef.commands.Command; import org.eclipse.gef.commands.CompoundCommand; import org.eclipse.gef.editparts.AbstractGraphicalEditPart; import org.eclipse.gef.requests.ChangeBoundsRequest; import org.eclipse.gef.ui.actions.SelectionAction; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.IWorkbenchPart; import de.tub.tfs.muvitor.ui.MuvitorActivator; import de.tub.tfs.muvitor.ui.MuvitorConstants; /** * This action applies the Draw2d graph layouter to the EditPartViewer * containing some selected GraphicalEditPart. * * @author "Tony Modica" */ public class GenericGraphLayoutAction extends SelectionAction { public static final String ID = "GenericGraphLayoutAction"; /** Default padding for nodes in the graph (fixed spacing around nodes). */ private static final int DEFAULT_PADDING = 25; private static final String DESC = "Redistribute the nodes in a graphical viewer with a directed graph layout"; private static final String LABEL = "Redistribute nodes"; /** * The viewer containing the currently selected GraphicalEditPart */ private EditPartViewer viewer; /** * @param part * the workbench part */ public GenericGraphLayoutAction(final IWorkbenchPart part) { super(part); setId(ID); setText(LABEL); setDescription(DESC); setToolTipText(DESC); setImageDescriptor(MuvitorActivator .getImageDescriptor(MuvitorConstants.ICON_GRAPHLAYOUT_16)); } /** * Gets the Graph Layout Command and executes it. */ @SuppressWarnings("unchecked") @Override public void run() { // when invoking directly, a viewer must be set manually! if (viewer == null) { return; } // compute layout graph final DirectedGraph directedGraph = new DirectedGraph(); directedGraph.setDefaultPadding(new Insets(DEFAULT_PADDING)); // list to store the connection edit parts found between the nodes final Set<ConnectionEditPart> connections = new HashSet<ConnectionEditPart>(); // a map to get the right source and target nodes for the edges in the // final graph final Map<NodeEditPart, Node> nodeEditPartToNodeMap = new HashMap<NodeEditPart, Node>(); // list of all nodeEditParts within selection, contains all nodes if graph is selected List<EditPart> list = Collections.EMPTY_LIST; if (!getSelectedObjects().isEmpty()){ if (getSelectedObjects().size() == 1) // && getSelectedObjects().get(0) instanceof GraphEditPart) list = viewer.getContents().getChildren(); else list = getSelectedObjects(); } for (final EditPart editPart : (Collection<EditPart>)list ) { // for (final EditPart editPart : (Collection<EditPart>) viewer.getContents().getChildren()) { if (editPart instanceof NodeEditPart) { final NodeEditPart nodeEditPart = (NodeEditPart) editPart; final Rectangle bounds = ((GraphicalEditPart) editPart).getFigure().getBounds(); // ignore figures without bounds if (bounds == null) { continue; } final Node node = new Node(nodeEditPart,null); node.x = bounds.x; node.y = bounds.y; node.height = bounds.height; node.width = bounds.width; nodeEditPartToNodeMap.put(nodeEditPart, node); directedGraph.nodes.add(node); // store connections adjacent to this node edit part // these must be converted to graph edges later connections.addAll(nodeEditPart.getSourceConnections()); connections.addAll(nodeEditPart.getTargetConnections()); } } // Convert connections to (Draw2d) Edges for (final ConnectionEditPart connection : connections) { Node sourceNode=nodeEditPartToNodeMap.get(connection.getSource()); Node targetNode=nodeEditPartToNodeMap.get(connection.getTarget()); // Graphs must not contain unresolvable cycles if (connection.getSource() != connection.getTarget() && // only consider edges within the selection (sourceNode != null && targetNode != null) ) { directedGraph.edges.add(new Edge(connection, nodeEditPartToNodeMap.get(connection .getSource()), nodeEditPartToNodeMap.get(connection.getTarget()))); } } // perform layout new DirectedGraphLayout().visit(directedGraph); // combine commands that will apply the new node location values final CompoundCommand compCommand = new CompoundCommand(); for (final Entry<NodeEditPart, Node> entry : nodeEditPartToNodeMap.entrySet()) { final NodeEditPart editPart = entry.getKey(); final Rectangle bounds = editPart.getFigure().getBounds(); final Node node = entry.getValue(); final ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_MOVE); request.setMoveDelta(new Point(node.x - bounds.x, node.y - bounds.y)); final Command command = editPart.getCommand(request); // Some edit parts may return unexecutable commands if (command != null && command.canExecute()) { compCommand.add(editPart.getCommand(request)); } } // allow animation of the layout Animation.markBegin(); ((AbstractGraphicalEditPart) viewer.getContents()).getFigure().addLayoutListener( LayoutAnimator.getDefault()); // this allows to use this action independently from an editor if (getWorkbenchPart() == null || getCommandStack() == null) { compCommand.execute(); } else { execute(compCommand); } // perform the layout animation Animation.run(500); ((AbstractGraphicalEditPart) viewer.getContents()).getFigure().removeLayoutListener( LayoutAnimator.getDefault()); } /** * This setter allows universal usage of this action. Just call the * constructor with <code>null</code> and set the viewer for layout * manually. * * @param viewer */ public void setViewer(final EditPartViewer viewer) { this.viewer = viewer; } /** * This action is enabled if some graphical edit part is currently selected * from which a viewer can be determined to be layout. */ @Override protected boolean calculateEnabled() { viewer = null; if (getSelection() == null) { return false; } if (getSelection() instanceof IStructuredSelection) { final IStructuredSelection selection = (IStructuredSelection) getSelection(); for (final Object selectedObject : selection.toList()) { if (selectedObject instanceof GraphicalEditPart) { viewer = ((GraphicalEditPart) selectedObject).getViewer(); return viewer != null; } } } return false; } }