/*******************************************************************************
* 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.henshin.tggeditor.actions;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.draw2d.graph.CompoundDirectedGraph;
import org.eclipse.draw2d.graph.CompoundDirectedGraphLayout;
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.draw2d.graph.Subgraph;
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.requests.ChangeBoundsRequest;
import org.eclipse.gef.ui.actions.SelectionAction;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.ui.IWorkbenchPart;
import de.tub.tfs.henshin.tgg.TNode;
import de.tub.tfs.henshin.tgg.TripleGraph;
import de.tub.tfs.henshin.tgg.interpreter.util.NodeUtil;
import de.tub.tfs.henshin.tggeditor.commands.move.MoveDividerCommand;
import de.tub.tfs.henshin.tggeditor.editparts.graphical.Divider;
import de.tub.tfs.henshin.tggeditor.editparts.graphical.GraphEditPart;
import de.tub.tfs.henshin.tggeditor.util.GraphicalNodeUtil;
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 GenericTGGGraphLayoutAction extends SelectionAction {
public static final String ID = "GenericTGGTreeGraphLayoutAction";
/** Default padding for nodes in the graph (fixed spacing around nodes). */
private static final int DEFAULT_PADDING = 25;
private static final String DESC = "Automatic TGG Tree layout";
private static final String LABEL = "Redistribute nodes (Containment Trees)";
/**
* The viewer containing the currently selected GraphicalEditPart
*/
private EditPartViewer viewer;
/**
* The graphEditpart containing the currently selected GraphicalEditPart
*/
private GraphEditPart graphEditPart = null;
/**
* @param part
* the workbench part
*/
public GenericTGGGraphLayoutAction(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;
}
if(viewer.getContents() instanceof GraphEditPart)
graphEditPart=(GraphEditPart) viewer.getContents();
// compute layout graph
CompoundDirectedGraph srcGraph = new CompoundDirectedGraph();
DirectedGraph corGraph = new DirectedGraph();
DirectedGraph tarGraph = new DirectedGraph();
srcGraph.setDefaultPadding(new Insets(DEFAULT_PADDING));
corGraph.setDefaultPadding(new Insets(DEFAULT_PADDING));
tarGraph.setDefaultPadding(new Insets(DEFAULT_PADDING));
// list to store the connection edit parts found between the nodes
final Set<ConnectionEditPart> srcConn = new HashSet<ConnectionEditPart>();
final Set<ConnectionEditPart> corConn = new HashSet<ConnectionEditPart>();
final Set<ConnectionEditPart> tarConn = new HashSet<ConnectionEditPart>();
final Set<ConnectionEditPart> otherConn = 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 ) {
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 = 0;
node.y = bounds.y;
node.height = bounds.height;
node.width = bounds.width;
nodeEditPartToNodeMap.put(nodeEditPart, node);
if (NodeUtil.isSourceNode((TNode) nodeEditPart.getModel())){
srcGraph.nodes.add(node);
for (Object obj : nodeEditPart.getSourceConnections()) {
ConnectionEditPart conn = (ConnectionEditPart) obj;
if (NodeUtil.isSourceNode((TNode) conn.getTarget().getModel())){
srcConn.add(conn);
} else {
otherConn.add(conn);
}
}
for (Object obj : nodeEditPart.getTargetConnections()) {
ConnectionEditPart conn = (ConnectionEditPart) obj;
if (NodeUtil.isSourceNode((TNode) conn.getSource().getModel())){
srcConn.add(conn);
} else {
otherConn.add(conn);
}
}
} else if (NodeUtil.isCorrespondenceNode((TNode) nodeEditPart.getModel())){
corGraph.nodes.add(node);
for (Object obj : nodeEditPart.getSourceConnections()) {
ConnectionEditPart conn = (ConnectionEditPart) obj;
if (NodeUtil.isCorrespondenceNode((TNode) conn.getTarget().getModel())){
corConn.add(conn);
} else {
otherConn.add(conn);
}
}
for (Object obj : nodeEditPart.getTargetConnections()) {
ConnectionEditPart conn = (ConnectionEditPart) obj;
if (NodeUtil.isCorrespondenceNode((TNode) conn.getSource().getModel())){
corConn.add(conn);
} else {
otherConn.add(conn);
}
}
} else if (NodeUtil.isTargetNode((TNode) nodeEditPart.getModel())){
tarGraph.nodes.add(node);
for (Object obj : nodeEditPart.getSourceConnections()) {
ConnectionEditPart conn = (ConnectionEditPart) obj;
if (NodeUtil.isTargetNode((TNode) conn.getTarget().getModel())){
tarConn.add(conn);
} else {
otherConn.add(conn);
}
}
for (Object obj : nodeEditPart.getTargetConnections()) {
ConnectionEditPart conn = (ConnectionEditPart) obj;
if (NodeUtil.isTargetNode((TNode) conn.getSource().getModel())){
tarConn.add(conn);
} else {
otherConn.add(conn);
}
}
}
}
}
/*for (ConnectionEditPart connectionEditPart : otherConn) {
final Node snode = new Node(connectionEditPart.getSource());
snode.x = 0;
final Rectangle sbounds = ((GraphicalEditPart) connectionEditPart.getSource()).getFigure().getBounds();
// ignore figures without bounds
if (sbounds == null) {
continue;
}
snode.y = sbounds.y;
snode.height = sbounds.height;
snode.width = sbounds.width;
final Node tnode = new Node(connectionEditPart.getTarget());
tnode.x = 0;
final Rectangle tbounds = ((GraphicalEditPart) connectionEditPart.getTarget()).getFigure().getBounds();
// ignore figures without bounds
if (tbounds == null) {
continue;
}
tnode.y = tbounds.y;
tnode.height = tbounds.height;
tnode.width = tbounds.width;
nodeEditPartToNodeMap.put((NodeEditPart) connectionEditPart.getSource(),snode);
nodeEditPartToNodeMap.put((NodeEditPart) connectionEditPart.getTarget(),tnode);
}*/
// Convert connections to (Draw2d) Edges
for (final ConnectionEditPart connection : srcConn) {
Node sourceNode=nodeEditPartToNodeMap.get(connection.getSource());
Node targetNode=nodeEditPartToNodeMap.get(connection.getTarget());
if( // store only containment edges for layouting the tree structure
(((org.eclipse.emf.henshin.model.Edge)connection.getModel()).getType().isContainment())
&&
// source and target of edge are in selection
sourceNode != null && targetNode != null
&&
// Graphs must not contain unresolvable cycles
(connection.getSource() != connection.getTarget()) ) {
srcGraph.edges.add(new Edge(connection, sourceNode, targetNode));
}
}
// Convert connections to (Draw2d) Edges
for (final ConnectionEditPart connection : tarConn) {
if( // store only containment edges for layouting the tree structure
(((org.eclipse.emf.henshin.model.Edge)connection.getModel()).getType().isContainment())
&&
// Graphs must not contain unresolvable cycles
(connection.getSource() != connection.getTarget()) ) {
tarGraph.edges.add(new Edge(connection, nodeEditPartToNodeMap.get(connection
.getSource()), nodeEditPartToNodeMap.get(connection.getTarget())));
}
}
// Convert connections to (Draw2d) Edges
for (final ConnectionEditPart connection : corConn) {
if( // store only containment edges for layouting the tree structure
(((org.eclipse.emf.henshin.model.Edge)connection.getModel()).getType().isContainment())
&&
// Graphs must not contain unresolvable cycles
(connection.getSource() != connection.getTarget()) ) {
corGraph.edges.add(new Edge(connection, nodeEditPartToNodeMap.get(connection
.getSource()), nodeEditPartToNodeMap.get(connection.getTarget())));
}
}
// split the graph into its unconnected subgraphs
addComponentSubgraphs(srcGraph);
// perform layout
new CompoundDirectedGraphLayout().visit(srcGraph);
// int sourceWidth=srcGraph.getLayoutSize().width();
// workaround, as srcGraph.getLayoutSize().width() delivers 0
int sourceWidth=computeWidth(srcGraph);
new DirectedGraphLayout().visit(tarGraph);
new DirectedGraphLayout().visit(corGraph);
// combine commands that will apply the new node location values
final CompoundCommand compCommand = new CompoundCommand();
// move dividers to maximum of source and correspondence width
if(graphEditPart!=null){
final TripleGraph graph = graphEditPart.getCastedModel();
final int newXDivSC = Math.max(sourceWidth,computeSrcWidth(graphEditPart.getCastedModel()));
// correspondence component has at least a width of 40
final int corWidth = Math.max(corGraph.getLayoutSize().width(), computeCorWidth(graphEditPart.getCastedModel()));
final int newXDivCT = newXDivSC + corWidth;
final Divider divSc = graphEditPart.getDividerSCpart().getCastedModel();
final Divider divCt = graphEditPart.getDividerCTpart().getCastedModel();
// move first the divider between C and T to provide space for the move of divider between S and C
boolean dividerCTwasMoved = false;
MoveDividerCommand cmdCT = null;
if (graph.getDividerCT_X() != newXDivCT) {
cmdCT = new MoveDividerCommand(divCt,
newXDivCT, graph.getDividerMaxY());
if (cmdCT != null && cmdCT.canExecute()) {
cmdCT.execute();
dividerCTwasMoved = true;
}
}
if (graph.getDividerSC_X() != newXDivSC) {
MoveDividerCommand cmdSC = new MoveDividerCommand(divSc,
newXDivSC, graph.getDividerMaxY());
if (cmdSC != null && cmdSC.canExecute()) {
cmdSC.execute();
}
}
// if move divider CT command was delayed by move divider SC command (moving to left), then execute the command
if (cmdCT != null && !dividerCTwasMoved && cmdCT.canExecute()) {
cmdCT.execute();
dividerCTwasMoved = true;
}
}
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);
int sourceY = 0;
int targetY = 0;
int deltaY = node.y;
int deltaX = 0;
if (NodeUtil.isCorrespondenceNode((TNode) editPart.getModel())){
deltaX = ((TripleGraph)((TNode) editPart.getModel()).getGraph()).getDividerSC_X() + 10;
int sAmt = 0;
int tAmt = 0;
for (Object obj : editPart.getSourceConnections()) {
ConnectionEditPart e = (ConnectionEditPart) obj;
if (NodeUtil.isTargetNode((TNode) e.getTarget().getModel())){
tAmt++;
if (nodeEditPartToNodeMap.get(e.getTarget()) != null)
targetY = (int) (nodeEditPartToNodeMap.get(e.getTarget()).y );
} else if (NodeUtil.isSourceNode((TNode) e.getTarget().getModel())){
sAmt++;
if (nodeEditPartToNodeMap.get(e.getTarget()) != null)
sourceY = (int) (nodeEditPartToNodeMap.get(e.getTarget()).y );
}
}
for (Object obj : editPart.getTargetConnections()) {
ConnectionEditPart e = (ConnectionEditPart) obj;
if (NodeUtil.isTargetNode((TNode) e.getSource().getModel())){
tAmt++;
if (nodeEditPartToNodeMap.get(e.getSource()) != null)
targetY = (int) (nodeEditPartToNodeMap.get(e.getSource()).y);
} else if (NodeUtil.isSourceNode((TNode) e.getSource().getModel())){
sAmt++;
if (nodeEditPartToNodeMap.get(e.getSource()) != null)
sourceY = (int) (nodeEditPartToNodeMap.get(e.getSource()).y);
}
}
if (sAmt + tAmt != 0)
deltaY = (int) (Math.abs(sourceY + targetY) / (sAmt + tAmt));
else
deltaY = Math.abs(sourceY + targetY) ;
} else if (NodeUtil.isTargetNode((TNode) editPart.getModel())){
deltaX = ((TripleGraph)((TNode) editPart.getModel()).getGraph()).getDividerCT_X() + 10;
}
request.setMoveDelta(new Point(node.x + deltaX - bounds.x, deltaY - 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));
}
}
// this allows to use this action independently from an editor
if (getWorkbenchPart() == null || getCommandStack() == null) {
compCommand.execute();
} else {
execute(compCommand);
}
}
private int computeSrcWidth(TripleGraph castedModel) {
if (castedModel == null)
return 200;
return castedModel.getDividerSC_X();
}
private int computeCorWidth(TripleGraph castedModel) {
if (castedModel == null)
return 40;
return castedModel.getDividerCT_X() - castedModel.getDividerSC_X();
}
private int computeTarWidth(TripleGraph castedModel) {
// TODO Auto-generated method stub
return castedModel.getDividerCT_X();
}
private int computeWidth(CompoundDirectedGraph srcGraph) {
Node n =null;
int maxX=0;
for (Object o:srcGraph.nodes){
if (o instanceof Node){
n = (Node) o;
maxX = Math.max(n.width+n.x,maxX);
}
}
return maxX;
}
private HashSet<Node> visitedNodes = new HashSet<Node>();
private void addComponentSubgraphs(CompoundDirectedGraph srcGraph) {
Subgraph currentComponentNode=null;
Subgraph previousComponentNode=null;
int amountPlainNodes = srcGraph.nodes.size();
//Subgraph previousComponentNode = null;
// iterate over all nodes and create a new component for each connected component
for(int currentNodePosition = 0; currentNodePosition < amountPlainNodes;currentNodePosition++){
Node currentNode=srcGraph.nodes.getNode(currentNodePosition);
if(!visitedNodes.contains(currentNode)){
previousComponentNode = currentComponentNode;
currentComponentNode = new Subgraph("component");
srcGraph.nodes.add(currentComponentNode);
if(previousComponentNode!=null)
srcGraph.edges.add(new Edge("componentEdge",previousComponentNode,currentComponentNode));
visitAndAddToComponent(currentNode,currentComponentNode);
}
}
}
private void visitAndAddToComponent(Node currentNode, Subgraph componentNode) {
if(!visitedNodes.contains(currentNode)){
// process current node, if not processed before
// node is put into the current subgraph
componentNode.addMember(currentNode);
currentNode.setParent(componentNode);
visitedNodes.add(currentNode);
// process all nodes from incoming edges
Iterator<?> edgesIterator = currentNode.incoming.iterator();
while (edgesIterator.hasNext()){
Object o = edgesIterator.next();
if (o instanceof Edge){
Edge e = (Edge) o;
visitAndAddToComponent(e.source,componentNode);
}
};
// process all nodes from outgoing edges
edgesIterator = currentNode.outgoing.iterator();
while (edgesIterator.hasNext()){
Object o = edgesIterator.next();
if (o instanceof Edge){
Edge e = (Edge) o;
visitAndAddToComponent(e.target,componentNode);
}
}
}
}
/**
* 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;
}
}