/*******************************************************************************
* Copyright (c) 2010 protos software gmbh (http://www.protos.de).
* 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:
* Thomas Schuetz and Henrik Rentz-Reichert (initial contribution)
*
*******************************************************************************/
package org.eclipse.etrice.ui.behavior.support;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.etrice.core.naming.RoomNameProvider;
import org.eclipse.etrice.core.room.BaseState;
import org.eclipse.etrice.core.room.ChoicePoint;
import org.eclipse.etrice.core.room.ChoicepointTerminal;
import org.eclipse.etrice.core.room.ContinuationTransition;
import org.eclipse.etrice.core.room.EntryPoint;
import org.eclipse.etrice.core.room.InitialTransition;
import org.eclipse.etrice.core.room.NonInitialTransition;
import org.eclipse.etrice.core.room.RefinedState;
import org.eclipse.etrice.core.room.RoomFactory;
import org.eclipse.etrice.core.room.State;
import org.eclipse.etrice.core.room.StateGraph;
import org.eclipse.etrice.core.room.StateTerminal;
import org.eclipse.etrice.core.room.SubStateTrPointTerminal;
import org.eclipse.etrice.core.room.TrPoint;
import org.eclipse.etrice.core.room.TrPointTerminal;
import org.eclipse.etrice.core.room.Transition;
import org.eclipse.etrice.core.room.TransitionTerminal;
import org.eclipse.etrice.core.room.TriggeredTransition;
import org.eclipse.etrice.core.validation.ValidationUtil;
import org.eclipse.etrice.ui.behavior.ImageProvider;
import org.eclipse.etrice.ui.behavior.dialogs.TransitionPropertyDialog;
import org.eclipse.graphiti.datatypes.ILocation;
import org.eclipse.graphiti.dt.IDiagramTypeProvider;
import org.eclipse.graphiti.features.IAddFeature;
import org.eclipse.graphiti.features.ICreateConnectionFeature;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.IReason;
import org.eclipse.graphiti.features.IRemoveFeature;
import org.eclipse.graphiti.features.IUpdateFeature;
import org.eclipse.graphiti.features.context.IAddConnectionContext;
import org.eclipse.graphiti.features.context.IAddContext;
import org.eclipse.graphiti.features.context.ICreateConnectionContext;
import org.eclipse.graphiti.features.context.ICustomContext;
import org.eclipse.graphiti.features.context.IDoubleClickContext;
import org.eclipse.graphiti.features.context.IRemoveContext;
import org.eclipse.graphiti.features.context.IUpdateContext;
import org.eclipse.graphiti.features.context.impl.AddConnectionContext;
import org.eclipse.graphiti.features.context.impl.RemoveContext;
import org.eclipse.graphiti.features.custom.AbstractCustomFeature;
import org.eclipse.graphiti.features.custom.ICustomFeature;
import org.eclipse.graphiti.features.impl.AbstractAddFeature;
import org.eclipse.graphiti.features.impl.AbstractCreateConnectionFeature;
import org.eclipse.graphiti.features.impl.AbstractUpdateFeature;
import org.eclipse.graphiti.features.impl.Reason;
import org.eclipse.graphiti.mm.GraphicsAlgorithmContainer;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.mm.algorithms.Polygon;
import org.eclipse.graphiti.mm.algorithms.Polyline;
import org.eclipse.graphiti.mm.algorithms.Text;
import org.eclipse.graphiti.mm.algorithms.styles.Point;
import org.eclipse.graphiti.mm.pictograms.Anchor;
import org.eclipse.graphiti.mm.pictograms.Connection;
import org.eclipse.graphiti.mm.pictograms.ConnectionDecorator;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.FreeFormConnection;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.services.Graphiti;
import org.eclipse.graphiti.services.IGaService;
import org.eclipse.graphiti.services.IPeCreateService;
import org.eclipse.graphiti.tb.DefaultToolBehaviorProvider;
import org.eclipse.graphiti.tb.IToolBehaviorProvider;
import org.eclipse.graphiti.ui.features.DefaultFeatureProvider;
import org.eclipse.graphiti.util.ColorConstant;
import org.eclipse.graphiti.util.IColorConstant;
import org.eclipse.jface.window.Window;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.PlatformUI;
public class TransitionSupport {
private static final IColorConstant LINE_COLOR = new ColorConstant(0, 0, 0);
private static final int LINE_WIDTH = 1;
private static final int MAX_LABEL_LENGTH = 20;
static class FeatureProvider extends DefaultFeatureProvider {
private class CreateFeature extends AbstractCreateConnectionFeature {
public CreateFeature(IFeatureProvider fp) {
super(fp, "Transition", "create Transition");
}
@Override
public String getCreateImageId() {
return ImageProvider.IMG_TRANSITION;
}
@Override
public boolean canCreate(ICreateConnectionContext context) {
TransitionTerminal src = getTransitionTerminal(context.getSourceAnchor());
TransitionTerminal tgt = getTransitionTerminal(context.getTargetAnchor());
if (src==null && !isInitialPoint(context.getSourceAnchor()))
return false;
if (tgt==null)
return false;
StateGraph sg = getStateGraph(context);
if (sg==null)
return false;
return ValidationUtil.isConnectable(src, tgt, sg).isOk();
}
public boolean canStartConnection(ICreateConnectionContext context) {
TransitionTerminal src = getTransitionTerminal(context.getSourceAnchor());
if (src==null && !isInitialPoint(context.getSourceAnchor()))
return false;
StateGraph sg = getStateGraph(context);
if (sg==null)
return false;
return ValidationUtil.isConnectable(src, sg).isOk();
}
@Override
public Connection create(ICreateConnectionContext context) {
Connection newConnection = null;
TransitionTerminal src = getTransitionTerminal(context.getSourceAnchor());
TransitionTerminal dst = getTransitionTerminal(context.getTargetAnchor());
StateGraph sg = getStateGraph(context);
if (dst!=null && sg!=null) {
// TODOHRR-B transition dialog
// allow switch between default and non-default CP branch? This would change the transition type
Transition trans = null;
if (src==null) {
InitialTransition t = RoomFactory.eINSTANCE.createInitialTransition();
t.setTo(dst);
trans = t;
}
else if (src instanceof SubStateTrPointTerminal
|| (src instanceof TrPointTerminal && ((TrPointTerminal)src).getTrPoint() instanceof EntryPoint)) {
ContinuationTransition t = RoomFactory.eINSTANCE.createContinuationTransition();
t.setFrom(src);
t.setTo(dst);
trans = t;
}
else if (src instanceof ChoicepointTerminal) {
boolean dfltBranch = true;
for (Transition tr : sg.getTransitions()) {
if (tr instanceof ContinuationTransition) {
TransitionTerminal from = ((ContinuationTransition) tr).getFrom();
if (from instanceof ChoicepointTerminal) {
if (((ChoicepointTerminal) from).getCp()==((ChoicepointTerminal)src).getCp())
dfltBranch = false;
}
}
}
NonInitialTransition t = dfltBranch? RoomFactory.eINSTANCE.createContinuationTransition()
: RoomFactory.eINSTANCE.createCPBranchTransition();
t.setFrom(src);
t.setTo(dst);
trans = t;
}
else {
TriggeredTransition t = RoomFactory.eINSTANCE.createTriggeredTransition();
t.setFrom(src);
t.setTo(dst);
trans = t;
}
if (trans instanceof InitialTransition) {
trans.setName("init");
}
else {
String name = RoomNameProvider.getUniqueTransitionName(sg);
trans.setName(name);
}
Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
TransitionPropertyDialog dlg = new TransitionPropertyDialog(shell, sg, trans);
if (dlg.open()!=Window.OK)
// find a method to abort creation
//throw new RuntimeException();
return null;
sg.getTransitions().add(trans);
AddConnectionContext addContext = new AddConnectionContext(context.getSourceAnchor(), context.getTargetAnchor());
addContext.setNewObject(trans);
newConnection = (Connection) getFeatureProvider().addIfPossible(addContext);
}
return newConnection;
}
private boolean isInitialPoint(Anchor anchor) {
if (anchor!=null) {
Object obj = getBusinessObjectForPictogramElement(anchor.getParent());
if (obj instanceof StateGraph) {
Object parent = getBusinessObjectForPictogramElement((ContainerShape) anchor.getParent().eContainer());
if (parent instanceof StateGraph)
return true;
}
}
return false;
}
private TransitionTerminal getTransitionTerminal(Anchor anchor) {
if (anchor != null) {
Object obj = getBusinessObjectForPictogramElement(anchor.getParent());
if (obj instanceof TrPoint) {
Object parent = getBusinessObjectForPictogramElement((ContainerShape) anchor.getParent().eContainer());
if (parent instanceof State) {
BaseState state = (parent instanceof RefinedState)? ((RefinedState)parent).getBase() : (BaseState)parent;
SubStateTrPointTerminal sstpt = RoomFactory.eINSTANCE.createSubStateTrPointTerminal();
sstpt.setState(state);
sstpt.setTrPoint((TrPoint) obj);
return sstpt;
}
else {
TrPointTerminal tpt = RoomFactory.eINSTANCE.createTrPointTerminal();
tpt.setTrPoint((TrPoint) obj);
return tpt;
}
}
else if (obj instanceof State) {
BaseState state = (obj instanceof RefinedState)? ((RefinedState)obj).getBase() : (BaseState)obj;
StateTerminal st = RoomFactory.eINSTANCE.createStateTerminal();
st.setState(state);
return st;
}
else if (obj instanceof ChoicePoint) {
ChoicepointTerminal ct = RoomFactory.eINSTANCE.createChoicepointTerminal();
ct.setCp((ChoicePoint) obj);
return ct;
}
}
return null;
}
public StateGraph getStateGraph(ICreateConnectionContext context) {
ContainerShape shape = (ContainerShape) context.getSourcePictogramElement().eContainer();
Object bo = getBusinessObjectForPictogramElement(shape);
if (bo instanceof StateGraph)
return (StateGraph) bo;
shape = (ContainerShape) shape.eContainer();
bo = getBusinessObjectForPictogramElement(shape);
if (bo instanceof StateGraph)
return (StateGraph) bo;
return null;
}
}
private class AddFeature extends AbstractAddFeature {
public AddFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canAdd(IAddContext context) {
if (context instanceof IAddConnectionContext && context.getNewObject() instanceof Transition) {
return true;
}
return false;
}
@Override
public PictogramElement add(IAddContext context) {
IAddConnectionContext addConContext = (IAddConnectionContext) context;
Transition trans = (Transition) context.getNewObject();
IPeCreateService peCreateService = Graphiti.getPeCreateService();
FreeFormConnection connection = peCreateService.createFreeFormConnection(getDiagram());
connection.setStart(addConContext.getSourceAnchor());
connection.setEnd(addConContext.getTargetAnchor());
if (addConContext.getSourceAnchor()==addConContext.getTargetAnchor()) {
Point pt = createSelfTransitionBendPoint(connection);
connection.getBendpoints().add(pt);
}
Graphiti.getPeService().setPropertyValue(connection, Constants.TYPE_KEY, Constants.TRANS_TYPE);
IGaService gaService = Graphiti.getGaService();
Polyline polyline = gaService.createPolyline(connection);
polyline.setForeground(manageColor(LINE_COLOR));
polyline.setLineWidth(LINE_WIDTH);
ConnectionDecorator cd = peCreateService
.createConnectionDecorator(connection, false, 1.0, true);
createArrow(cd);
ConnectionDecorator textDecorator =
peCreateService.createConnectionDecorator(connection, true,
0.5, true);
Text text = gaService.createDefaultText(textDecorator);
text.setForeground(manageColor(IColorConstant.BLACK));
gaService.setLocation(text, 10, 0);
text.setValue(getLabel(trans));
// create link and wire it
link(connection, trans);
return connection;
}
private Point createSelfTransitionBendPoint(FreeFormConnection connection) {
ILocation begin = Graphiti.getPeService().getLocationRelativeToDiagram(connection.getStart());
// TODOHRR: algorithm to determine self transition bend point position
int deltaX = 0;
int deltaY = StateGraphSupport.MARGIN*3;
return Graphiti.getGaService().createPoint(begin.getX()+deltaX, begin.getY()+deltaY);
}
private Polyline createArrow(GraphicsAlgorithmContainer gaContainer) {
IGaService gaService = Graphiti.getGaService();
Polygon polygon =
gaService.createPolygon(gaContainer, new int[] { -15, 5, 0, 0, -15, -5 });
polygon.setForeground(manageColor(LINE_COLOR));
polygon.setBackground(manageColor(LINE_COLOR));
polygon.setLineWidth(LINE_WIDTH);
return polygon;
}
}
private class UpdateFeature extends AbstractUpdateFeature {
public UpdateFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canUpdate(IUpdateContext context) {
Object bo = getBusinessObjectForPictogramElement(context.getPictogramElement());
if (bo instanceof EObject && ((EObject)bo).eIsProxy())
return true;
if (bo instanceof Transition)
return true;
return false;
}
@Override
public IReason updateNeeded(IUpdateContext context) {
Object bo = getBusinessObjectForPictogramElement(context.getPictogramElement());
if (bo instanceof EObject && ((EObject)bo).eIsProxy()) {
return Reason.createTrueReason("Transition deleted from model");
}
if (bo instanceof Transition) {
Transition t = (Transition) bo;
Connection conn = (Connection)context.getPictogramElement();
if (conn.getConnectionDecorators().size()>=2) {
ConnectionDecorator cd = conn.getConnectionDecorators().get(1);
if (cd.getGraphicsAlgorithm() instanceof Text) {
Text label = (Text) cd.getGraphicsAlgorithm();
if (!label.getValue().equals(getLabel(t)))
return Reason.createTrueReason("Label needs update");
}
}
}
return Reason.createFalseReason();
}
@Override
public boolean update(IUpdateContext context) {
Connection containerShape = (Connection)context.getPictogramElement();
Object bo = getBusinessObjectForPictogramElement(containerShape);
if (bo instanceof EObject && ((EObject)bo).eIsProxy()) {
IRemoveContext rc = new RemoveContext(containerShape);
IFeatureProvider featureProvider = getFeatureProvider();
IRemoveFeature removeFeature = featureProvider.getRemoveFeature(rc);
if (removeFeature != null) {
removeFeature.remove(rc);
}
EcoreUtil.delete((EObject) bo);
return true;
}
boolean updated = false;
if (bo instanceof Transition) {
Transition t = (Transition) bo;
Connection conn = (Connection)context.getPictogramElement();
if (conn.getConnectionDecorators().size()>=2) {
ConnectionDecorator cd = conn.getConnectionDecorators().get(1);
if (cd.getGraphicsAlgorithm() instanceof Text) {
Text label = (Text) cd.getGraphicsAlgorithm();
String transitionLabelName = getLabel(t);
if (!label.getValue().equals(transitionLabelName)) {
label.setValue(transitionLabelName);
updated = true;
}
}
}
}
return updated;
}
}
private static class PropertyFeature extends AbstractCustomFeature {
private String name;
private String description;
public PropertyFeature(IFeatureProvider fp) {
super(fp);
this.name = "Edit Transition";
this.description = "Edit Transition";
}
@Override
public String getName() {
return name;
}
@Override
public String getDescription() {
return description;
}
@Override
public boolean canExecute(ICustomContext context) {
PictogramElement[] pes = context.getPictogramElements();
if (pes != null && pes.length == 1) {
PictogramElement pe = pes[0];
if (pe instanceof ConnectionDecorator)
pe = (PictogramElement) pe.eContainer();
if (!(pe instanceof Connection))
return false;
Object bo = getBusinessObjectForPictogramElement(pe);
if (bo instanceof Transition) {
return true;
}
}
return false;
}
@Override
public void execute(ICustomContext context) {
PictogramElement pe = context.getPictogramElements()[0];
if (pe instanceof ConnectionDecorator)
pe = (PictogramElement) pe.eContainer();
Transition trans = (Transition) getBusinessObjectForPictogramElement(pe);
StateGraph sg = (StateGraph)trans.eContainer();
Shell shell = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell();
TransitionPropertyDialog dlg = new TransitionPropertyDialog(shell, sg, trans);
if (dlg.open()!=Window.OK)
// TODOHRR: introduce a method to revert changes, does hasDoneChanges=false roll back changes?
//throw new RuntimeException();
return;
updateLabel(trans, (Connection) pe);
}
}
private IFeatureProvider fp;
public FeatureProvider(IDiagramTypeProvider dtp, IFeatureProvider fp) {
super(dtp);
this.fp = fp;
}
@Override
public ICreateConnectionFeature[] getCreateConnectionFeatures() {
return new ICreateConnectionFeature[] { new CreateFeature(fp) };
}
@Override
public IAddFeature getAddFeature(IAddContext context) {
return new AddFeature(fp);
}
@Override
public IUpdateFeature getUpdateFeature(IUpdateContext context) {
return new UpdateFeature(fp);
}
@Override
public ICustomFeature[] getCustomFeatures(ICustomContext context) {
return new ICustomFeature[] { new PropertyFeature(fp) };
}
protected static void updateLabel(Transition trans, Connection conn) {
if (conn.getConnectionDecorators().size()<2)
return;
ConnectionDecorator cd = conn.getConnectionDecorators().get(1);
if (cd.getGraphicsAlgorithm() instanceof Text) {
Text label = (Text) cd.getGraphicsAlgorithm();
label.setValue(getLabel(trans));
}
}
protected static String getLabel(Transition trans) {
String label = RoomNameProvider.getTransitionLabelName(trans);
if (label.length()>MAX_LABEL_LENGTH)
label = label.substring(0, MAX_LABEL_LENGTH)+"...";
return label;
}
}
class BehaviorProvider extends DefaultToolBehaviorProvider {
public BehaviorProvider(IDiagramTypeProvider dtp) {
super(dtp);
}
@Override
public ICustomFeature getDoubleClickFeature(IDoubleClickContext context) {
return new FeatureProvider.PropertyFeature(getDiagramTypeProvider().getFeatureProvider());
}
@Override
public String getToolTip(GraphicsAlgorithm ga) {
// if this is called we know there is a business object!=null
PictogramElement pe = ga.getPictogramElement();
if (pe instanceof ConnectionDecorator)
pe = (PictogramElement) pe.eContainer();
EObject bo = Graphiti.getLinkService().getBusinessObjectForLinkedPictogramElement(pe);
if (bo instanceof Transition)
return RoomNameProvider.getTransitionLabelName((Transition) bo);
return super.getToolTip(ga);
}
}
private FeatureProvider pfp;
private BehaviorProvider tbp;
public TransitionSupport(IDiagramTypeProvider dtp, IFeatureProvider fp) {
pfp = new FeatureProvider(dtp,fp);
tbp = new BehaviorProvider(dtp);
}
public IFeatureProvider getFeatureProvider() {
return pfp;
}
public IToolBehaviorProvider getToolBehaviorProvider() {
return tbp;
}
}