/*******************************************************************************
* 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.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.draw2d.Animation;
import org.eclipse.draw2d.LayoutAnimator;
import org.eclipse.draw2d.geometry.PrecisionPoint;
import org.eclipse.draw2d.geometry.Rectangle;
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.action.IMenuCreator;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.ui.IWorkbenchPart;
import org.eclipse.zest.layouts.InvalidLayoutConfiguration;
import org.eclipse.zest.layouts.LayoutAlgorithm;
import org.eclipse.zest.layouts.LayoutEntity;
import org.eclipse.zest.layouts.LayoutRelationship;
import org.eclipse.zest.layouts.LayoutStyles;
import org.eclipse.zest.layouts.algorithms.DirectedGraphLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.GridLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.HorizontalLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.HorizontalTreeLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.RadialLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.SpringLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.TreeLayoutAlgorithm;
import org.eclipse.zest.layouts.algorithms.VerticalLayoutAlgorithm;
import org.eclipse.zest.layouts.exampleStructures.SimpleGraph;
import de.tub.tfs.muvitor.ui.MuvitorActivator;
import de.tub.tfs.muvitor.ui.MuvitorConstants;
/**
* This action applies the ZEST graph layouts to the EditPartViewer containing
* some selected GraphicalEditPart. The user may select one of the ZEST layout
* algorithms.
*
* @author "Tony Modica"
*/
public class GenericGraphLayoutActionZEST extends SelectionAction {
public static final String ID = "GenericGraphLayoutActionZEST";
private static final String DESC = "Redistribute the nodes in a graphical viewer with a ZEST layout algortihm";
private static final String LABEL = "Redistribute nodes with a ZEST layout algortihm";
static final ArrayList<String> algorithmNames = new ArrayList<String>();
/**
* the algorithms ZEST provide
*/
static final ArrayList<LayoutAlgorithm> algorithms = new ArrayList<LayoutAlgorithm>();
static {
algorithms.add(new SpringLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
algorithms.add(new TreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
algorithms.add(new HorizontalTreeLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
algorithms.add(new RadialLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
algorithms.add(new GridLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
algorithms.add(new HorizontalLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
algorithms.add(new VerticalLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
algorithms.add(new DirectedGraphLayoutAlgorithm(LayoutStyles.NO_LAYOUT_NODE_RESIZING));
}
static {
algorithmNames.add("Spring");
algorithmNames.add("Tree (vertical)");
algorithmNames.add("Tree (horizontal)");
algorithmNames.add("Radial");
algorithmNames.add("Grid");
algorithmNames.add("Horizontal");
algorithmNames.add("Vertical");
algorithmNames.add("Directed Graph (draw2D)");
}
/**
* the viewer containing the currently selected GraphicalEditPart
*/
private EditPartViewer viewer;
/**
* the currently selected layout algorithm
*/
LayoutAlgorithm currentAlgorithm;
/**
* The constructor prepares the menu to selected the layout algorithm from
*
* @param part
* the workbench part
*/
public GenericGraphLayoutActionZEST(final IWorkbenchPart part) {
super(part);
setId(ID);
setText(LABEL);
setDescription(DESC);
setToolTipText(DESC);
setImageDescriptor(MuvitorActivator
.getImageDescriptor(MuvitorConstants.ICON_GRAPHLAYOUT_16));
currentAlgorithm = algorithms.get(0);
setMenuCreator(new IMenuCreator() {
Menu menu;
@Override
public void dispose() {
if (menu != null) {
menu.dispose();
menu = null;
}
}
@Override
public Menu getMenu(final Control parent) {
if (menu == null) {
menu = new Menu(parent);
fillMenu();
}
return menu;
}
@Override
public Menu getMenu(final Menu parent) {
// not intended as submenu
return null;
}
private void fillMenu() {
for (final String algorithmName : algorithmNames) {
final MenuItem item = new MenuItem(menu, SWT.RADIO);
item.setText(algorithmName);
item.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(final SelectionEvent e) {
final int index = menu.indexOf((MenuItem) e.widget);
currentAlgorithm = algorithms.get(index);
}
});
}
// select first item by default
menu.getItem(0).setSelection(true);
}
});
}
/**
* 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;
}
final SimpleGraph graph = new SimpleGraph();
final List<NodeEditPart> nodesToMove = new ArrayList<NodeEditPart>();
final Set<ConnectionEditPart> connections = new HashSet<ConnectionEditPart>();
// add nodes to the graph
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 LayoutEntity node = graph.addObjectNode(nodeEditPart);
node.setLocationInLayout(bounds.x, bounds.y);
node.setSizeInLayout(bounds.width, bounds.height);
nodesToMove.add(nodeEditPart);
// store connections adjacent to this node edit part
// these must be converted to graph edges later
connections.addAll(nodeEditPart.getSourceConnections());
connections.addAll(nodeEditPart.getTargetConnections());
}
}
// add connections to the graph
for (final ConnectionEditPart connection : connections) {
final NodeEditPart source = (NodeEditPart) connection.getSource();
final NodeEditPart target = (NodeEditPart) connection.getTarget();
// Graphs must not contain unresolvable cycles
if (source != target && nodesToMove.contains(source) && nodesToMove.contains(target)) {
graph.addObjectRelationship(source, target, false, 1);
}
}
// extract graph entities and relationships
final List<?> entitiesList = graph.getEntities();
final LayoutEntity[] entities = new LayoutEntity[entitiesList.size()];
entitiesList.toArray(entities);
final List<?> relationshipList = graph.getRelationships();
final LayoutRelationship[] relationships = new LayoutRelationship[relationshipList.size()];
relationshipList.toArray(relationships);
// perform layout
try {
currentAlgorithm.applyLayout(entities, relationships, 15.0, 15.0, 800, 600, false,
false);
} catch (final InvalidLayoutConfiguration e) {
e.printStackTrace();
}
// combine commands that will apply the new node location values
final CompoundCommand compCommand = new CompoundCommand();
for (final NodeEditPart editPart : nodesToMove) {
final LayoutEntity node = graph.addObjectNode(editPart);
final Rectangle bounds = editPart.getFigure().getBounds();
final ChangeBoundsRequest request = new ChangeBoundsRequest(RequestConstants.REQ_MOVE);
request.setMoveDelta(new PrecisionPoint(node.getXInLayout() - bounds.x, node.getYInLayout()
- bounds.y));
final Command command = editPart.getCommand(request);
// Some editparts may return unexecutable commands
if (command != null && command.canExecute()) {
compCommand.add(editPart.getCommand(request));
}
}
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);
}
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 algorithm for layout
* manually.
*
* @param name
* the name of the algorithm to use. Must be one of
* {@value #algorithms}.
* @see #setViewer(EditPartViewer)
*/
public void setAlgorithm(final String name) {
currentAlgorithm = algorithms.get(algorithmNames.indexOf(name));
}
/**
* 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
* @see #setAlgorithm(String)
*/
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 trimmed.
*/
@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;
}
}