blob: 5db32c425b8a04306ce1fd9f24e85fd85e18e563 [file] [log] [blame]
/*******************************************************************************
* 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 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* CONTRIBUTORS:
* Thomas Schuetz and Henrik Rentz-Reichert (initial contribution)
*
*******************************************************************************/
package org.eclipse.etrice.ui.behavior.fsm.support;
import java.util.ArrayList;
import java.util.Set;
import org.eclipse.emf.common.util.Diagnostic;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.etrice.core.fsm.fSM.ChoicePoint;
import org.eclipse.etrice.core.fsm.fSM.RefinedState;
import org.eclipse.etrice.core.fsm.fSM.State;
import org.eclipse.etrice.core.fsm.fSM.StateGraph;
import org.eclipse.etrice.core.fsm.fSM.TrPoint;
import org.eclipse.etrice.core.fsm.fSM.Transition;
import org.eclipse.etrice.core.fsm.util.FSMHelpers;
import org.eclipse.etrice.ui.behavior.fsm.editor.AbstractFSMEditor;
import org.eclipse.etrice.ui.behavior.fsm.editor.DecoratorUtil;
import org.eclipse.etrice.ui.behavior.fsm.support.util.DiagramEditingUtil;
import org.eclipse.etrice.ui.behavior.fsm.support.util.FSMSupportUtil;
import org.eclipse.etrice.ui.common.base.support.DeleteWithoutConfirmFeature;
import org.eclipse.graphiti.dt.IDiagramTypeProvider;
import org.eclipse.graphiti.features.IAddFeature;
import org.eclipse.graphiti.features.IDeleteFeature;
import org.eclipse.graphiti.features.IFeatureProvider;
import org.eclipse.graphiti.features.ILayoutFeature;
import org.eclipse.graphiti.features.IReason;
import org.eclipse.graphiti.features.IRemoveFeature;
import org.eclipse.graphiti.features.IResizeShapeFeature;
import org.eclipse.graphiti.features.IUpdateFeature;
import org.eclipse.graphiti.features.context.IAddContext;
import org.eclipse.graphiti.features.context.ICustomContext;
import org.eclipse.graphiti.features.context.IDeleteContext;
import org.eclipse.graphiti.features.context.IDoubleClickContext;
import org.eclipse.graphiti.features.context.ILayoutContext;
import org.eclipse.graphiti.features.context.IRemoveContext;
import org.eclipse.graphiti.features.context.IResizeShapeContext;
import org.eclipse.graphiti.features.context.IUpdateContext;
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.AbstractLayoutFeature;
import org.eclipse.graphiti.features.impl.AbstractUpdateFeature;
import org.eclipse.graphiti.features.impl.DefaultRemoveFeature;
import org.eclipse.graphiti.features.impl.DefaultResizeShapeFeature;
import org.eclipse.graphiti.features.impl.Reason;
import org.eclipse.graphiti.mm.algorithms.GraphicsAlgorithm;
import org.eclipse.graphiti.mm.algorithms.Rectangle;
import org.eclipse.graphiti.mm.algorithms.RoundedRectangle;
import org.eclipse.graphiti.mm.algorithms.Text;
import org.eclipse.graphiti.mm.algorithms.styles.Font;
import org.eclipse.graphiti.mm.algorithms.styles.Orientation;
import org.eclipse.graphiti.mm.pictograms.ContainerShape;
import org.eclipse.graphiti.mm.pictograms.Diagram;
import org.eclipse.graphiti.mm.pictograms.PictogramElement;
import org.eclipse.graphiti.mm.pictograms.Shape;
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.IDecorator;
import org.eclipse.graphiti.tb.IToolBehaviorProvider;
import org.eclipse.graphiti.tb.ImageDecorator;
import org.eclipse.graphiti.ui.features.DefaultFeatureProvider;
import org.eclipse.graphiti.util.ColorConstant;
import org.eclipse.graphiti.util.IColorConstant;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.swt.widgets.Display;
import com.google.common.collect.Sets;
public class StateGraphSupport {
public static final int MARGIN = 40;
private static final int CORNER_SIZE = 20;
private static final int LINE_WIDTH = 4;
public static final int DEFAULT_SIZE_X = 800;
public static final int DEFAULT_SIZE_Y = 500;
private static final IColorConstant LINE_COLOR = new ColorConstant(0, 0, 0);
private static final IColorConstant BACKGROUND = new ColorConstant(255, 255, 255);
private static class FeatureProvider extends DefaultFeatureProvider {
private class AddFeature extends AbstractAddFeature {
public AddFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canAdd(IAddContext context) {
if (context.getNewObject() instanceof StateGraph) {
if (context.getTargetContainer() instanceof Diagram) {
return true;
}
}
return false;
}
@Override
public PictogramElement add(IAddContext context) {
StateGraph sg = (StateGraph) context.getNewObject();
ContainerShape targetContainer = (ContainerShape) context.getTargetContainer();
// CONTAINER SHAPE WITH RECTANGLE
IPeCreateService peCreateService = Graphiti.getPeCreateService();
ContainerShape containerShape =
peCreateService.createContainerShape(targetContainer, true);
Graphiti.getPeService().setPropertyValue(containerShape, Constants.TYPE_KEY, Constants.SG_TYPE);
// check whether the context has a size (e.g. from a create feature)
// otherwise define a default size for the shape
int width = context.getWidth() <= 0 ? DEFAULT_SIZE_X : context.getWidth();
int height = context.getHeight() <= 0 ? DEFAULT_SIZE_Y : context.getHeight();
IGaService gaService = Graphiti.getGaService();
{
// create invisible outer rectangle expanded by
// the width needed for the ports
Rectangle invisibleRectangle =
gaService.createInvisibleRectangle(containerShape);
gaService.setLocationAndSize(invisibleRectangle,
context.getX(), context.getY(), width + 2*MARGIN, height + 2*MARGIN);
// create and set visible rectangle inside invisible rectangle
// transparent first
RoundedRectangle rect = gaService.createRoundedRectangle(invisibleRectangle, CORNER_SIZE, CORNER_SIZE);
rect.setForeground(manageColor(LINE_COLOR));
rect.setBackground(manageColor(BACKGROUND));
rect.setTransparency(0.5);
rect.setLineWidth(LINE_WIDTH);
gaService.setLocationAndSize(rect, MARGIN, MARGIN, width, height);
// then unfilled opaque
rect = gaService.createRoundedRectangle(invisibleRectangle, CORNER_SIZE, CORNER_SIZE);
rect.setForeground(manageColor(LINE_COLOR));
rect.setFilled(false);
rect.setLineWidth(LINE_WIDTH);
gaService.setLocationAndSize(rect, MARGIN, MARGIN, width, height);
// create link and wire it
link(containerShape, sg);
}
{
Shape labelShape = peCreateService.createShape(containerShape, false);
Text label = gaService.createDefaultText(getDiagram(), labelShape,
FSMSupportUtil.getInstance().getFSMNameProvider().getStateGraphLabel(sg));
label.setForeground(manageColor(LINE_COLOR));
label.setBackground(manageColor(LINE_COLOR));
label.setHorizontalAlignment(Orientation.ALIGNMENT_RIGHT);
label.setVerticalAlignment(Orientation.ALIGNMENT_TOP);
Font font = Graphiti.getGaService().manageFont(
getDiagram(),
label.getFont().getName(),
(int)(label.getFont().getSize()*1.2),
label.getFont().isItalic(),
true);
label.setFont(font);
gaService.setLocationAndSize(label, 0, MARGIN, width, 2*MARGIN);
}
// call the layout feature
layoutPictogramElement(containerShape);
return containerShape;
}
}
private class LayoutFeature extends AbstractLayoutFeature {
private static final int MIN_HEIGHT = 100;
private static final int MIN_WIDTH = 250;
public LayoutFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canLayout(ILayoutContext context) {
// return true, if pictogram element is linked to an ActorClass
PictogramElement pe = context.getPictogramElement();
if (!(pe instanceof ContainerShape))
return false;
EList<EObject> businessObjects = pe.getLink().getBusinessObjects();
return businessObjects.size() == 1
&& businessObjects.get(0) instanceof StateGraph;
}
@Override
public boolean layout(ILayoutContext context) {
boolean anythingChanged = false;
ContainerShape containerShape = (ContainerShape) context
.getPictogramElement();
GraphicsAlgorithm containerGa = containerShape.getGraphicsAlgorithm();
// height
if (containerGa.getHeight() < MIN_HEIGHT) {
containerGa.setHeight(MIN_HEIGHT);
anythingChanged = true;
}
// width
if (containerGa.getWidth() < MIN_WIDTH) {
containerGa.setWidth(MIN_WIDTH);
anythingChanged = true;
}
int w = containerGa.getWidth();
int h = containerGa.getHeight();
if (containerGa.getGraphicsAlgorithmChildren().size()>=1) {
GraphicsAlgorithm ga = containerGa.getGraphicsAlgorithmChildren().get(0);
ga.setWidth(w-2*MARGIN);
ga.setHeight(h-2*MARGIN);
ga = containerGa.getGraphicsAlgorithmChildren().get(1);
ga.setWidth(w-2*MARGIN);
ga.setHeight(h-2*MARGIN);
anythingChanged = true;
}
if (containerShape.getChildren().size()>=1) {
GraphicsAlgorithm ga = containerShape.getChildren().get(0).getGraphicsAlgorithm();
ga.setWidth(w-2*MARGIN);
}
return anythingChanged;
}
}
private static class GoUpFeature extends AbstractCustomFeature implements
ICustomFeature {
public GoUpFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public String getName() {
return "Switch to SuperGraph";
}
@Override
public String getDescription() {
return "Switch Context to SuperGraph";
}
@Override
public boolean canExecute(ICustomContext context) {
ContainerShape container = (ContainerShape)context.getPictogramElements()[0];
Object bo = getBusinessObjectForPictogramElement(container);
if (bo instanceof StateGraph) {
if (((StateGraph) bo).eContainer() instanceof State)
return true;
}
return false;
}
@Override
public void execute(ICustomContext context) {
ContainerShape container = (ContainerShape)context.getPictogramElements()[0];
Object bo = getBusinessObjectForPictogramElement(container);
if (bo instanceof StateGraph) {
StateGraph sg = (StateGraph) bo;
if (sg.eContainer() instanceof RefinedState) {
RefinedState rs = (RefinedState) sg.eContainer();
FSMHelpers fsmHelpers = FSMSupportUtil.getInstance().getFSMHelpers();
if (fsmHelpers.isEmpty(sg)) {
// check action codes
boolean entryEmpty = fsmHelpers.getDetailCode(rs.getEntryCode()).trim().isEmpty();
boolean exitEmpty = fsmHelpers.getDetailCode(rs.getExitCode()).trim().isEmpty();
boolean doEmpty = fsmHelpers.getDetailCode(rs.getDoCode()).trim().isEmpty();
if (entryEmpty && exitEmpty && doEmpty) {
MessageDialog.openInformation(Display.getCurrent().getActiveShell(),
"Check of Refined State",
"A Refined State with empty action codes must have a non-empty sub state graph.");
return;
}
}
}
getDiagramBehavior().getDiagramContainer().selectPictogramElements(new PictogramElement[] {});
ContextSwitcher.goUp(getDiagram(), sg);
}
}
@Override
public boolean hasDoneChanges() {
return false;
}
}
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;
return bo instanceof StateGraph;
}
@Override
public IReason updateNeeded(IUpdateContext context) {
Object bo = getBusinessObjectForPictogramElement(context.getPictogramElement());
if (bo instanceof EObject && ((EObject)bo).eIsProxy()) {
return Reason.createTrueReason("State Graph deleted from model");
}
StateGraph sg = (StateGraph) bo;
ContainerShape shape = (ContainerShape) context.getPictogramElement();
String reason = "";
int missing = 0;
int obsolete = 0;
if (context instanceof StateGraphUpdateContext) {
IStateGraphContext ctx = ((StateGraphUpdateContext)context).getContext();
// check for states added in model not present in diagram (including inherited)
{
Set<State> expected = Sets.newHashSet(ctx.getStates());
Set<State> present = Sets.newHashSet(DiagramEditingUtil.getInstance().getStates(shape, fp));
if((missing = Sets.difference(expected, present).size()) > 0)
reason += missing+" missing states\n";
if((obsolete = Sets.difference(present, expected).size()) > 0)
reason += obsolete+" obsolete states\n";
}
// check for transition points added in model not present in diagram (including inherited)
{
missing = obsolete = 0;
Set<TrPoint> expected = Sets.newHashSet(ctx.getTrPoints());
Set<TrPoint> present = Sets.newHashSet(DiagramEditingUtil.getInstance().getTrPoints(sg, shape, fp));
if((missing = Sets.difference(expected, present).size()) > 0)
reason += missing+" missing transition points\n";
if((obsolete = Sets.difference(present, expected).size()) > 0)
reason += obsolete+" obsolete transition points\n";
}
// check for choice points added in model not present in diagram (including inherited)
{
missing = obsolete = 0;
Set<ChoicePoint> expected = Sets.newHashSet(ctx.getChPoints());
Set<ChoicePoint> present = Sets.newHashSet(DiagramEditingUtil.getInstance().getChoicePoints(shape, fp));
if((missing = Sets.difference(expected, present).size()) > 0)
reason += missing+" missing choice points\n";
if((obsolete = Sets.difference(present, expected).size()) > 0)
reason += obsolete+" obsolete choice points\n";
}
// check for transitions added in model not present in diagram (including inherited)
{
missing = obsolete = 0;
Set<Transition> expected = Sets.newHashSet(ctx.getTransitions());
Set<Transition> present = Sets.newHashSet(FSMSupportUtil.getInstance().getTransitions(getDiagram(), fp));
if((missing = Sets.difference(expected, present).size()) > 0)
reason += missing+" missing transitions\n";
if((obsolete = Sets.difference(present, expected).size()) > 0)
reason += obsolete+" obsolete transitions\n";
}
}
// check state path
if (!shape.getChildren().isEmpty()) {
Shape labelShape = shape.getChildren().get(0);
GraphicsAlgorithm ga = labelShape.getGraphicsAlgorithm();
if (ga instanceof Text)
if (!FSMSupportUtil.getInstance().getFSMNameProvider().getStateGraphLabel(sg).equals(((Text)ga).getValue()))
reason += "state graph label changed\n";
}
if (!reason.isEmpty())
return Reason.createTrueReason(reason.substring(0, reason.length()-1));
return Reason.createFalseReason();
}
@Override
public boolean update(IUpdateContext context) {
ContainerShape sgShape = (ContainerShape) context.getPictogramElement();
Object bo = getBusinessObjectForPictogramElement(sgShape);
/*
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;
}
*/
StateGraph sg = (StateGraph) bo;
if (context instanceof StateGraphUpdateContext) {
IStateGraphContext ctx = ((StateGraphUpdateContext)context).getContext();
DiagramEditingUtil.getInstance().updateStateGraph(sg, ctx, sgShape, fp);
}
if (!sgShape.getChildren().isEmpty()) {
Shape labelShape = sgShape.getChildren().get(0);
GraphicsAlgorithm ga = labelShape.getGraphicsAlgorithm();
if (ga instanceof Text)
((Text)ga).setValue(FSMSupportUtil.getInstance().getFSMNameProvider().getStateGraphLabel(sg));
}
return true;
}
}
private class ResizeFeature extends DefaultResizeShapeFeature {
public ResizeFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canResizeShape(IResizeShapeContext context) {
if (!super.canResizeShape(context))
return false;
ContainerShape containerShape = (ContainerShape)context.getShape();
Object bo = getBusinessObjectForPictogramElement(containerShape);
if (bo instanceof EObject && ((EObject)bo).eIsProxy())
return false;
int width = context.getWidth()-MARGIN;
int height = context.getHeight()-MARGIN;
int xmax = 0;
int ymax = 0;
StateGraph sg = (StateGraph) bo;
for (Shape childShape : containerShape.getChildren()) {
if (isOnInterface(sg, getBusinessObjectForPictogramElement(childShape)))
continue;
bo = getBusinessObjectForPictogramElement(childShape);
int margin = -1;
if (bo instanceof State)
margin = StateSupport.MARGIN;
else if (bo instanceof TrPoint)
margin = StateSupport.MARGIN;
else if (bo instanceof ChoicePoint)
margin = StateSupport.MARGIN;
if (margin>=0) {
GraphicsAlgorithm ga = childShape.getGraphicsAlgorithm();
int x = ga.getX()+ga.getWidth()-margin;
int y = ga.getY()+ga.getHeight()-margin;
if (x>xmax)
xmax = x;
if (y>ymax)
ymax = y;
}
}
if (width>0 && width<xmax)
return false;
if (height>0 && height<ymax)
return false;
return true;
}
@Override
public void resizeShape(IResizeShapeContext context) {
ContainerShape containerShape = (ContainerShape) context.getShape();
StateGraph sg = (StateGraph) getBusinessObjectForPictogramElement(containerShape);
if (containerShape.getGraphicsAlgorithm()!=null) {
GraphicsAlgorithm containerGa = containerShape.getGraphicsAlgorithm();
if (containerGa.getGraphicsAlgorithmChildren().size()==2) {
// scale interface item coordinates
// we refer to the visible rectangle which defines the border of our structure class
// since the margin is not scaled
GraphicsAlgorithm ga = containerGa.getGraphicsAlgorithmChildren().get(0);
double sx = (context.getWidth()-2*MARGIN)/(double)ga.getWidth();
double sy = (context.getHeight()-2*MARGIN)/(double)ga.getHeight();
for (Shape childShape : containerShape.getChildren()) {
if (isOnInterface(sg, getBusinessObjectForPictogramElement(childShape))) {
ga = childShape.getGraphicsAlgorithm();
ga.setX((int) (ga.getX()*sx));
ga.setY((int) (ga.getY()*sy));
}
}
}
}
super.resizeShape(context);
}
private boolean isOnInterface(StateGraph sg, Object childBo) {
return (childBo instanceof TrPoint);
}
}
private class RemoveFeature extends DefaultRemoveFeature {
public RemoveFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canRemove(IRemoveContext context) {
return false;
}
}
private class DeleteFeature extends DeleteWithoutConfirmFeature {
public DeleteFeature(IFeatureProvider fp) {
super(fp);
}
@Override
public boolean canDelete(IDeleteContext context) {
return false;
}
}
private IFeatureProvider fp;
public FeatureProvider(IDiagramTypeProvider dtp, IFeatureProvider fp) {
super(dtp);
this.fp = fp;
}
@Override
public IAddFeature getAddFeature(IAddContext context) {
return new AddFeature(fp);
}
@Override
public ILayoutFeature getLayoutFeature(ILayoutContext context) {
return new LayoutFeature(fp);
}
@Override
public IUpdateFeature getUpdateFeature(IUpdateContext context) {
return new UpdateFeature(fp);
}
@Override
public IResizeShapeFeature getResizeShapeFeature(
IResizeShapeContext context) {
return new ResizeFeature(fp);
}
@Override
public IRemoveFeature getRemoveFeature(IRemoveContext context) {
return new RemoveFeature(fp);
}
@Override
public IDeleteFeature getDeleteFeature(IDeleteContext context) {
return new DeleteFeature(fp);
}
@Override
public ICustomFeature[] getCustomFeatures(ICustomContext context) {
PictogramElement pe = context.getPictogramElements()[0];
Object bo = getBusinessObjectForPictogramElement(pe);
ArrayList<ICustomFeature> result = new ArrayList<ICustomFeature>();
result.add(new GoUpFeature(fp));
// Provide quick fix feature only for those edit parts which have
// errors, warnings or infos.
ArrayList<Diagnostic> diagnostics = ((AbstractFSMEditor) getDiagramTypeProvider()
.getDiagramBehavior().getDiagramContainer())
.getDiagnosingModelObserver().getElementDiagonsticMap()
.get(bo);
if (diagnostics != null) {
result.add(new QuickFixFeature(fp));
}
ICustomFeature features[] = new ICustomFeature[result.size()];
return result.toArray(features);
}
}
private class BehaviorProvider extends DefaultToolBehaviorProvider {
public BehaviorProvider(IDiagramTypeProvider dtp) {
super(dtp);
}
@Override
public GraphicsAlgorithm[] getClickArea(PictogramElement pe) {
GraphicsAlgorithm invisible = pe.getGraphicsAlgorithm();
GraphicsAlgorithm rectangle =
invisible.getGraphicsAlgorithmChildren().get(0);
return new GraphicsAlgorithm[] { rectangle };
}
@Override
public GraphicsAlgorithm getSelectionBorder(PictogramElement pe) {
GraphicsAlgorithm invisible = pe.getGraphicsAlgorithm();
GraphicsAlgorithm rectangle =
invisible.getGraphicsAlgorithmChildren().get(0);
return rectangle;
}
@Override
public ICustomFeature getDoubleClickFeature(IDoubleClickContext context) {
return new FeatureProvider.GoUpFeature(getDiagramTypeProvider().getFeatureProvider());
}
/**
* @author jayant
*/
@Override
public IDecorator[] getDecorators(PictogramElement pe) {
// Constants for positioning decorators
GraphicsAlgorithm invisible = pe.getGraphicsAlgorithm();
GraphicsAlgorithm rectangle = invisible
.getGraphicsAlgorithmChildren().get(0);
int xOrigin = rectangle.getX();
int yOrigin = rectangle.getY();
int xGap = 10, yGap = 0;
// Get the linked Business Object
EObject bo = Graphiti.getLinkService()
.getBusinessObjectForLinkedPictogramElement(pe);
// Get Diagnostics associated with the business object
ArrayList<Diagnostic> diagnostics = ((AbstractFSMEditor) getDiagramTypeProvider()
.getDiagramBehavior().getDiagramContainer())
.getDiagnosingModelObserver().getElementDiagonsticMap()
.get(bo);
// Form Decorators based on Diagnostics
ArrayList<IDecorator> decorators = DecoratorUtil
.getMarkersFromDiagnostics(diagnostics);
if (decorators.isEmpty())
return super.getDecorators(pe);
else {
int i = 0;
for (IDecorator decorator : decorators) {
((ImageDecorator) decorator).setX(xOrigin + xGap * i);
((ImageDecorator) decorator).setY(yOrigin + yGap * i);
i++;
}
return (IDecorator[]) decorators
.toArray(new IDecorator[decorators.size()]);
}
}
}
private FeatureProvider afp;
private BehaviorProvider tbp;
public StateGraphSupport(IDiagramTypeProvider dtp, IFeatureProvider fp) {
afp = new FeatureProvider(dtp, fp);
tbp = new BehaviorProvider(dtp);
}
public IFeatureProvider getFeatureProvider() {
return afp;
}
public IToolBehaviorProvider getToolBehaviorProvider() {
return tbp;
}
}