/**
* Copyright (c) 2013 committers of YAKINDU and others.
* 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:
* committers of YAKINDU - initial API and implementation
*
*/
package org.yakindu.sct.refactoring.refactor.impl;
import java.util.List;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.gmf.runtime.diagram.core.preferences.PreferencesHint;
import org.eclipse.gmf.runtime.diagram.core.services.ViewService;
import org.eclipse.gmf.runtime.diagram.core.util.ViewUtil;
import org.eclipse.gmf.runtime.diagram.ui.editparts.GraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart;
import org.eclipse.gmf.runtime.emf.commands.core.command.AbstractTransactionalCommand;
import org.eclipse.gmf.runtime.notation.Bounds;
import org.eclipse.gmf.runtime.notation.Node;
import org.eclipse.gmf.runtime.notation.NotationFactory;
import org.eclipse.gmf.runtime.notation.View;
import org.yakindu.sct.model.sgraph.Region;
import org.yakindu.sct.model.sgraph.SGraphFactory;
import org.yakindu.sct.model.sgraph.State;
import org.yakindu.sct.model.sgraph.Vertex;
import org.yakindu.sct.refactoring.refactor.AbstractRefactoring;
import org.yakindu.sct.ui.editor.DiagramActivator;
import org.yakindu.sct.ui.editor.editparts.StateEditPart;
import org.yakindu.sct.ui.editor.providers.SemanticHints;
import com.google.common.collect.Lists;
/**
* Groups a set of states into one composite state. All transitions to and from this set of state are preserved, i.e.
* the new composite state itself will have no incoming or outgoing transitions.
* <br><br>
* Context:
* <ul>
* <li>A set of states</li>
* </ul>
* Preconditions:
* <ul>
* <li>All states are in the same region.</li>
* </ul>
* @author thomas kutz - Initial contribution and API
*
*/
public class GroupStatesIntoCompositeRefactoring extends
AbstractRefactoring<GraphicalEditPart> {
private View parentRegionView;
private PreferencesHint preferencesHint = DiagramActivator.DIAGRAM_PREFERENCES_HINT;
private final int PADDING = 55;
private State compositeState;
private Region innerRegion;
protected List<State> contextStates;
@Override
protected void internalExecute() {
doSemanticalRefactoring();
doGraphicalRefactoring();
}
@Override
public boolean isExecutable() {
return super.isExecutable() && allStatesHaveSameParentRegion();
}
protected void doSemanticalRefactoring() {
compositeState = createCompositeState();
innerRegion = SGraphFactory.eINSTANCE.createRegion();
innerRegion.setName("inner region"); // TODO check for uniqueness?
compositeState.getRegions().add(innerRegion);
for (State state : contextStates) {
innerRegion.getVertices().add(state);
}
}
protected void doGraphicalRefactoring() {
Node compositeStateView = createNodeForCompositeState(compositeState);
Node innerRegionNode = ViewService.createNode(
getStateFigureCompartmentView(compositeStateView), innerRegion,
SemanticHints.REGION, preferencesHint);
View regionCompartment = ViewUtil.getChildBySemanticHint(innerRegionNode, SemanticHints.REGION_COMPARTMENT);
moveSelectedStateNodesTo(regionCompartment, (Bounds)compositeStateView.getLayoutConstraint());
}
protected State createCompositeState() {
State compositeState = SGraphFactory.eINSTANCE.createState();
compositeState.setName(getNameForCompositeState());
getParentRegion().getVertices().add(compositeState);
return compositeState;
}
protected Node createNodeForCompositeState(State compositeState) {
Node compositeStateNode = ViewService.createNode(parentRegionView,
compositeState, SemanticHints.STATE, preferencesHint);
setCompositeStateLayoutConstraint(compositeStateNode);
return compositeStateNode;
}
protected void setContextStates() {
contextStates = Lists.newArrayList();
List<GraphicalEditPart> contextObjects = getContextObjects();
for (GraphicalEditPart editPart : contextObjects) {
EObject element = editPart.resolveSemanticElement();
contextStates.add((State) element);
}
}
@Override
public void setContextObjects(List<GraphicalEditPart> contextObject) {
super.setContextObjects(contextObject);
setContextStates();
}
protected void moveSelectedStateNodesTo(View containerView, Bounds compositeBounds) {
for (GraphicalEditPart editPart : getContextObjects()) {
Node stateNode = (Node)editPart.getNotationView();
ViewUtil.insertChildView(containerView, stateNode, ViewUtil.APPEND, true);
Bounds newBounds = NotationFactory.eINSTANCE.createBounds();
Bounds oldBounds = (Bounds)stateNode.getLayoutConstraint();
newBounds.setX(oldBounds.getX() - compositeBounds.getX() - 7); //FIXME use bounds of region view
newBounds.setY(oldBounds.getY() - compositeBounds.getY() - 34); //FIXME use bounds of region view
((Node)editPart.getNotationView()).setLayoutConstraint(newBounds);
}
}
/**
* Iterates through all {@link StateEditPart}s of the current selection and
* computes layout constraints for the composite node.
*
* @param compositeStateNode
* node of the composite state
*/
protected void setCompositeStateLayoutConstraint(Node compositeStateNode) {
Rectangle newbounds = null;
for (GraphicalEditPart editPart : getContextObjects()) {
Rectangle childBounds = editPart.getFigure().getBounds();
if (newbounds == null)
newbounds = childBounds.getCopy();
newbounds.union(childBounds);
}
newbounds.expand(new Insets(PADDING, PADDING, PADDING, PADDING));
Bounds bounds = NotationFactory.eINSTANCE.createBounds();
bounds.setX(newbounds.x);
bounds.setY(newbounds.y);
bounds.setWidth(newbounds.width);
bounds.setHeight(newbounds.height);
compositeStateNode.setLayoutConstraint(bounds);
}
protected View getStateFigureCompartmentView(Node compositeStateView) {
return ViewUtil.getChildBySemanticHint(compositeStateView,
SemanticHints.STATE_FIGURE_COMPARTMENT);
}
protected String getNameForCompositeState() {
StringBuilder nameBuilder = new StringBuilder("Composite");
for (State state : contextStates) {
nameBuilder.append("_");
nameBuilder.append(state.getName());
}
makeNameUnique(nameBuilder);
return nameBuilder.toString();
}
protected void makeNameUnique(StringBuilder nameBuilder) {
int index = 2;
List<String> existingStateNames = Lists.newArrayList();
EList<Vertex> vertices = getParentRegion().getVertices();
for (Vertex vertex : vertices) {
existingStateNames.add(vertex.getName());
}
while (existingStateNames.contains(nameBuilder.toString())) {
nameBuilder.append(index++);
}
}
protected Region getParentRegion() {
return contextStates.get(0).getParentRegion();
}
protected boolean allStatesHaveSameParentRegion() {
parentRegionView = null;
for (IGraphicalEditPart editPart : getContextObjects()) {
if (parentRegionView == null) {
parentRegionView = (Node) ((Node)editPart.getNotationView()).eContainer();
} else {
Node nextParentRegion = (Node) ((Node)editPart.getNotationView()).eContainer();
if (!nextParentRegion.equals(parentRegionView)) {
return false;
}
}
}
return true;
}
@Override
protected String getCommandLabel() {
return "Group states into composite state";
}
@Override
protected Resource getResource() {
return getContextObject().resolveSemanticElement().eResource();
}
@Override
protected void executeCommand(AbstractTransactionalCommand refactoringCommand) {
executeCommand(refactoringCommand, getResource(), false);
}
}