gmf_tutorial5 [GMF Samples And Tutorials]
 

Overview

In this topic we will see a method to build a cutsom figure with specific edge anchor zones.

We will build a diagram editor allowing to draw operators such as plus and minus. We'll connect them to input entries and output results.

An operator has two inputs and one output. The input may be connected to an entry (that the user may modify), or a result (which is read only). The output can only be connected to a result.

Our aim is to be able to anchor the diagram edges on the graphical representations of ou operator inputs and output. To do that, we will use specific model objects (OperatorInput and OperatorOutput) that will be contained in our operators (containment association). Their graphical representations will be “manually” positionned in a container rectangle (this rectangle will also receive the other graphical items that helps to represent the operator).

Full code is availabale through :

Full code is also available for several intermediate steps of this tutorial through :

This tutorial has been built with :

  • Eclipse 3.5.1
  • Graphical Modeling Framework SDK 2.2.1
  • Eclipse Modeling Framework 2.5.0

Known bugs and fixes :

  • This tutorial doesn't show how to avoid compartment deletion : see this page to see how to fix it
  • This tutorial doesn't show how to ensure that compartment nodes that are automatically added (ie. operator output and inputs) are persisted : see this page to see how to fix it

Model creation

We will build our model with the annotated java interfaces method.

  • Create a new GMF Project named Math
  • Create a new Java package jfb.examples.gmf.math
  • Create the following java interfaces in this package :
package jfb.examples.gmf.math;
 
/**
 * @model
 */
public interface OperatorInput {
 
	/**
	 * @model
	 */
	public Operator getOperator();
 
	/**
	 * @model
	 */
	public Number getNumber();
 
}
package jfb.examples.gmf.math;
 
/**
 * @model
 */
public interface OperatorOutput {
 
	/**
	 * @model
	 */
	public Operator getOperator();
 
	/**
	 * @model
	 */
	public Result getResult();
 
}
package jfb.examples.gmf.math;
 
import java.util.List;
 
/**
 * @model abstract="true"
 */
public interface Operator {
 
	/**
	 * @model containment="true"
	 */
	public List<OperatorInput> getInputs();
 
	/**
	 * @model containment="true"
	 */
	public OperatorOutput getOutput();
 
}
package jfb.examples.gmf.math;
 
/**
 * @model
 */
public interface MinusOperator extends Operator {
 
}
package jfb.examples.gmf.math;
 
/**
 * @model
 */
public interface PlusOperator extends Operator {
 
}
package jfb.examples.gmf.math;
 
import java.util.List;
 
/**
 * @model abstract="true"
 */
public interface Number {
 
	/**
	 * @model
	 */
	public List<OperatorInput> getOperatorInputs();
 
}
package jfb.examples.gmf.math;
 
/**
 * @model
 */
public interface Entry extends Number {
 
}
package jfb.examples.gmf.math;
 
/**
 * @model
 */
public interface Result extends Number {
 
	/**
	 * @model
	 */
	public OperatorOutput getOperatorOutput();
 
}
package jfb.examples.gmf.math;
 
import java.util.List;
 
/**
 * @model
 */
public interface MathDiagram {
 
	/**
	 * @model containment="true"
	 */
	public List<Operator> getOperators();
 
	/**
	 * @model containment="true"
	 */
	public List<Entry> getEntries();
 
	/**
	 * @model containment="true"
	 */
	public List<Result> getResults();
 
}
  • Create a new EMF Generator model named math.genmodel in the model folder (see tutorial #1 for an example).
  • Choose Annotated Java as Model Importer and jfb.example.gmf.math in the packages list.
  • Open the math.ecore file
  • Select the operatorInputs : OperatorInput (child node of Number) and set the EOpposite property to number : Number (wich belongs to the OperatorInput element).
  • If you check the number : Number (child node of OperatorInput), the EOpposite property should be set to operatorInputs : OperatorInput.
  • Repeat this operation with the result : Result child node of OperatorOutput and the operatorOutput : OperatorOutput child node of Result.
  • Repeat this operation with the inputs : OperatorInput child node of Operator and the operator : Operator child node of OperatorInput.
  • Repeat this operation with the inputs : OperatorOutput child node of Operator and the operator : Operator child node of OperatorOutput.
  • Set the Lower Bound and Upper Bound of the inputs : OperatorInput node in the property view to 2 (child node of the Operator element).
  • Save the file
  • Generate the model classes and configure the GMF dashboard view (see tutorial #1 for more details on these steps)

  • Open the math.genmodel file
  • Right click the root element of the model, and select Generate Model Code
  • Right click the root element of the model, and select Generate Edit Code
  • Right click the root element of the model, and select Generate Editor Code

Tooling Definition Model

We need 6 creation tools :

  • one for the plus operators
  • one for the minus operators
  • one for the entries
  • one for the results
  • one for the “entry/result to operator” edge
  • one for the “operator to result” edge

Procedure :

  • In the dashboard, click on the Derive label which is on the left of the Tooling Def Model rectangle
  • Select the filename math.gmftool in the folder model
  • Select the diagram element MathDiagram and click Next
  • In the Nodes column, select (and select only) : Entry, Minusoperator, PlusOperator, Result
  • In the Connections column, select (and select only) : Entry.operatorInputs, OperatorOutput.result
  • Click Finish
  • You should get this :

  • Reorder the tools (add a new tool group, rename it and drag'n'drop the items) like this :

Graphical Definition Model

Procedure :

  • In the dashboard, click on the Derive label which is on the left of the Graphical Def Model rectangle.
  • Set the filename to math.gmfgraph in the model folder
  • Select the diagram element MathDiagram and click Next
  • In the Nodes column, select (and select only) : Entry, Minusoperator, OperatorInput, OperatorOutput, PlusOperator, Result
  • In the Connections column, select (and select only) : Entry.operatorInputs, OperatorOutput.result, Result.operatorInputs
  • In the Label column, select : Entry.value, Result.value
  • Click Finish
  • Open the math.gmfgraph file
  • Under the Rectangle MinusOperatorFigure node (which is under the Figure Descriptor MinusOperatorFigure) :
    • Add a Boder Layout
    • Add a Rectangle named MinusOperatorCompartmentFigure
    • Under the last node (Rectangle MinusOperatorCompartmentFigure), add a Border Layout Data (with Alignment = CENTER)
  • Under the Figure Descriptor MinusOperatorFigure node, add a Child access and select Rectangle MinusOperatorCompartmentFigure for its figure (in the properties view)
  • Yout should get this result :

  • Repeat the same operations for the Plus operator :

  • Under the Canvas math node, add two Compartment nodes
  • For the first :
    • set its name to MinusOperatorFigureCompartment
    • select Figure Descriptor MinusOperatorFigure for its figure and Child Access getFigureMinusOperatorCompartmentFigure for its accessor
  • For the second
    • set its name to PlusOperatorFigureCompartment
    • select Figure Descriptor PlusOperatorFigure for its figure and Child Access getFigurePlusOperatorCompartmentFigure for its accessor
  • Under the Figure Descriptor ResultFigure, change the Rectangle ResultFigure into an Ellispe ResultFigure (it is possible to open the math.gmfmap with a text editor and replace the string Rectangle by Ellipse in the node <actualFigure xsi:type=“gmfgraph:Rectangle” name=“ResultFigure”>
  • Save the file.

Mapping Model

  • In the dashboard, click on the Combine label
  • Select the math.gmfmap filename in the model folder
  • Select the diagram element MathDiagram and click Next
  • Click Next on the Select Diagram Palette page
  • Click Next on the Select Diagram Canvas page
  • Move OperatorOutput (<OperatorOutputResult: output) and OperatorInput (<EntryOperatorInputs: inputs) form the links zone to the nodes zone (right to left) on the Mapping page like this :

  • Click Finish
  • Open the math.gmfmap file
  • Under the Node Mapping <Result/Result> add a Feature Label Mapping and set its properties :
    • Feature to display=Number.value:EDouble
    • Diagram Label=Diagram Label ResultValue
    • Read Only=true
  • Under the Node Mapping <Entry/Entry> add a Feature Label Mapping and set its properties :
    • Feature to display=Number.value:EDouble
    • Diagram Label=Diagram Label EntryValue
    • Read Only=false
  • Under the Node Mapping <MinusOperator/MinusOperator> node (which is under Top Node Reference <operators:MinusOperator/MinusOperator>) :
    • Add a new Compartment Mapping and select Compartment MinusOperatorFigureCompartment (MinusoperatorFigure) in the properties view
    • Add a new Child Reference and set its properties like this :
      • Compartment=Compartment Mapping <MinusOperatorFigureCompartment>
      • Containment feature=Operator.inputs:OperatorInput
      • Reference Child = (empty)
    • Drag and drop the node Node Mapping <OperatorInput/OperatorInput> under this Child Reference node
    • Delete the Top Node Reference <inputs:OperatorInput/OperatorInput> under which was the node that has just been moved
    • Add a new Child Reference and set its properties like this :
      • Compartment=Compartment Mapping <MinusOperatorFigureCompartment>
      • Containment feature=Operator.outut:OperatorOutput
      • Reference Child = (empty)
    • Drag and drop the node Node Mapping <OperatorOutput/OperatorOutput> under this Child Reference node
    • Delete the Top Node Reference <output:OperatorOutput/OperatorOutput> under which was the node that has just been moved
  • Under the Node Mapping <PlusOperator/PlusOperator> node (which is under Top Node Reference <operators:PlusOperator/PlusOperator>) :
    • Add a new Compartment Mapping and select Compartment PlusOperatorFigureCompartment (PlusoperatorFigure) in the properties view
    • Add a new Child Reference and set its properties like this :
      • Compartment=Compartment Mapping <PlusOperatorFigureCompartment>
      • Containment feature=Operator.inputs:OperatorInput
      • Reference Child = Node mapping <OperatorInput/>
    • Add a new Child Reference and set its properties like this :
      • Compartment=Compartment Mapping <PlusOperatorFigureCompartment>
      • Containment feature=Operator.outut:OperatorOutput
      • Reference Child = Node mapping <OperatorOutput/>
  • Correct the diagram nodes and tool mappings :
    • For Node Mapping <Result/Result> :
      • Diagram Node=Node Result (ResultFigure)
      • Tool=Creation Tool Result
    • For Node Mapping <Entry/Entry> :
      • Diagram Node=Node Entry (EntryFigure)
      • Tool=Creation Tool Entry
    • For Node Mapping <MinusOperator/MinusOperator> :
      • Diagram Node=Node MinusOperator (MinusOperatorFigure)
      • Tool=Creation Tool MinusOperator
    • For Node Mapping <PlusOperator/PlusOperator> :
      • Diagram Node=Node PlusOperator (PlusOperatorFigure)
      • Tool=Creation Tool PlusOperator
    • For Node Mapping <OperatorOutput/> :
      • Diagram Node=Node OperatorOutput (OperatorOutputFigure)
      • Tool= (empty !)
    • For Node Mapping <OperatorInput/> :
      • Diagram Node=Node OperatorInput (OperatorInputFigure)
      • Tool= (empty !)
  • Correct the diagram links and tool mappings :
    • For Link Mapping <{Number.operatorInputs:OperatorInput}/EntryOperatorInput> :
      • Target Feature=Number.operatorInputs:OperatorInput
      • Diagram Link=Connection EntryOperatorInput
      • Tool=Creation Tool Entry/Result to operator input
    • For Link Mapping <{Result.operatorOutput:OperatorOutput}/OperatorOutputResult> :
      • Target Feature=OperatorOutput.result:Result
      • Diagram Link=Connection OperatorOutputResult
      • Tool=Creation Tool Operator output to result

Basic diagram generation

  • Click on the Transform label of the dashboard
  • A file named math.gmfgen should be created automatically
  • Open this file, and check that teh List layout property is set to true in the properties view for the following compartment nodes :
    • Gen Compartment MinuOperatorMinusOperatorFigureCompartmentEditPart
    • Gen Compartment PluOperatorPlusOperatorFigureCompartmentEditPart
  • Click on the Generate diagram editor label of the dashboard
  • In order to have two inputs and one output in every operator instance, we will modify the generated classes
  • Open the /Math.diagram/src/jfb/examples/gmf/math/diagram/edit/commands/MinusOperatorCreateCommand.java file, edit the doExecuteWithResult(IProgressMonitor, IAdaptable) method and modify it like this (do not forget to add the 'NOT' mention after @generated) :
	/**
	 * @generated NOT
	 */
	protected CommandResult doExecuteWithResult(IProgressMonitor monitor,
			IAdaptable info) throws ExecutionException {
		MinusOperator newElement = MathFactory.eINSTANCE.createMinusOperator();
 
		// Adds both operator inputs
		newElement.getInputs().add(MathFactory.eINSTANCE.createOperatorInput());
		newElement.getInputs().add(MathFactory.eINSTANCE.createOperatorInput());
 
		// Adds the operator output
		newElement.setOutput(MathFactory.eINSTANCE.createOperatorOutput());
 
		MathDiagram owner = (MathDiagram) getElementToEdit();
		owner.getOperators().add(newElement);
 
		doConfigure(newElement, monitor, info);
 
		((CreateElementRequest) getRequest()).setNewElement(newElement);
		return CommandResult.newOKCommandResult(newElement);
	}
  • Repeat this operation with the /Math.diagram/src/jfb/examples/gmf/math/diagram/edit/commands/PlusOperatorCreateCommand.java file.
  • Edit the file /Math.diagram/src/jfb/examples/gmf/math/diagram/preferences/DiagramConnectionsPreferencePage.java and add this to set the default edge routing mode to rectilinear :
	public static void initDefaults(IPreferenceStore preferenceStore) {
		preferenceStore.setDefault(IPreferenceConstants.PREF_LINE_STYLE,
			Routing.RECTILINEAR);
	}
  • If you start eclipse at this point (see gmf_tutorial1 for more details about starting an eclipse instance with your new diagram editor) you should get a basic diagram editor like this :

A this point, it is not possible to distinguish plus operators from minus operators, and their graphical representations are not very pleasant ! We will customize our graphical representations in the next steps.

Operators graphical customization

In this part, we will customize the graphical representation of our operators to get something like this :

  • Create a new package jfb.examples.gmf.math.diagram.edit.parts.custom
  • In this package create a class named PlusRoundedRectangle which will extend org.eclipse.draw2d.RoundedRectangle
  • In this class, we will override the paintFigure method in order to represent the plus sign :
package jfb.examples.gmf.math.diagram.edit.parts.custom;
 
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.RoundedRectangle;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
 
public class PlusRoundedRectangle extends RoundedRectangle {
 
	public PlusRoundedRectangle() {
		super();
		setLineWidth(2);
	}
 
	public void paintFigure(Graphics graphics) {
		super.paintFigure(graphics);
		graphics.setForegroundColor(ColorConstants.black);
		graphics.setForegroundColor(ColorConstants.black);
		graphics.setLineStyle(Graphics.LINE_SOLID);
		graphics.setLineWidth(3);
		Rectangle r = getBounds();
		// vertical line
		graphics.drawLine(
				new Point(r.x + r.width / 2, r.y + r.height * 0.2),
				new Point(r.x + r.width / 2, r.y + r.height * 0.8));
		// horizontal line
		graphics.drawLine(
				new Point(r.x + r.width * 0.2, r.y + r.height / 2),
				new Point(r.x + r.width * 0.8, r.y + r.height / 2));
	};
 
}
  • Create a second class named MinusRoundedRectangle the same way :
package jfb.examples.gmf.math.diagram.edit.parts.custom;
 
import org.eclipse.draw2d.ColorConstants;
import org.eclipse.draw2d.Graphics;
import org.eclipse.draw2d.RoundedRectangle;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.draw2d.geometry.Rectangle;
 
public class MinusRoundedRectangle extends RoundedRectangle {
 
	public MinusRoundedRectangle() {
		super();
		setLineWidth(2);
	}
 
	public void paintFigure(Graphics graphics) {
		super.paintFigure(graphics);
		graphics.setForegroundColor(ColorConstants.black);
		graphics.setForegroundColor(ColorConstants.black);
		graphics.setLineStyle(Graphics.LINE_SOLID);
		graphics.setLineWidth(3);
		Rectangle r = getBounds();
		// horizontal line
		graphics.drawLine(
				new Point(r.x + r.width * 0.2, r.y + r.height / 2),
				new Point(r.x + r.width * 0.8, r.y + r.height / 2));
	};
 
}

* Now we add our rounded rectangles to the graphical representations of our operators. To do that, edit the /Math.diagram/src/jfb/examples/gmf/math/diagram/edit/parts/MinusOperatorMinusOperatorFigureCompartmentEditPart.java file and modify the createFigure method :

	/**
	 * @generated NOT
	 */
	public IFigure createFigure() {
		ResizableCompartmentFigure result = (ResizableCompartmentFigure) super
				.createFigure();
		result.setTitleVisibility(false);
 
		// Setup for a XYLayout
		IFigure contentPane = result.getContentPane();
		contentPane.setLayoutManager(new XYLayout());
 
		// Delete content pane insets
		Insets is = contentPane.getInsets();
		is.top = 0;
		is.bottom = 0;
		is.left = 0;
		is.right = 0;
 
		// Setup graphical elements
		MinusRoundedRectangle roundedRectangle = new MinusRoundedRectangle();
		contentPane.add(roundedRectangle);
 
		// Add the resize events listener
		result.addFigureListener(new OperatorCompartmentFigureListener(this, roundedRectangle));
 
		return result;
	}

At this point we have note created the resize event listener OperatorCompartmentFigureListener. So, you may get some compilation problems ; we will create the missing class just a few steps later.

  • Do it again for the plus operator with PlusOperatorPlusOperatorFigureCompartmentEditPart.java :
	/**
	 * @generated NOT
	 */
	public IFigure createFigure() {
		ResizableCompartmentFigure result = (ResizableCompartmentFigure) super
				.createFigure();
		result.setTitleVisibility(false);
 
		// Setup for a XYLayout
		IFigure contentPane = result.getContentPane();
		contentPane.setLayoutManager(new XYLayout());
 
		// Delete content pane insets
		Insets is = contentPane.getInsets();
		is.top = 0;
		is.bottom = 0;
		is.left = 0;
		is.right = 0;
 
		// Setup graphical elements
		PlusRoundedRectangle roundedRectangle = new PlusRoundedRectangle();
		contentPane.add(roundedRectangle);
 
		// Add the resize events listener
		result.addFigureListener(new OperatorCompartmentFigureListener(this, roundedRectangle));
 
		return result;
	}
  • Now we will create a component that will listen to figure resize events (the missing component that implies compilation problems). In the previous package, we create a class named OperatorCompartmentFigureListener. This component will resize the rounded rectangle and the operator inputs and output :
package jfb.examples.gmf.math.diagram.edit.parts.custom;
 
import java.util.List;
 
import jfb.examples.gmf.math.diagram.edit.parts.OperatorInputEditPart;
import jfb.examples.gmf.math.diagram.edit.parts.OperatorOutputEditPart;
 
import org.eclipse.draw2d.FigureListener;
import org.eclipse.draw2d.IFigure;
import org.eclipse.draw2d.RoundedRectangle;
import org.eclipse.draw2d.geometry.Insets;
import org.eclipse.draw2d.geometry.Rectangle;
import org.eclipse.gef.editparts.AbstractEditPart;
import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
import org.eclipse.gmf.runtime.diagram.ui.editparts.ListCompartmentEditPart;
import org.eclipse.gmf.runtime.diagram.ui.figures.ResizableCompartmentFigure;
 
public class OperatorCompartmentFigureListener implements FigureListener {
 
	private ListCompartmentEditPart compartmentEditPart = null;
	private RoundedRectangle roundedRectangle = null;
 
	public static final double MARGIN = 20; // The margin to apply before drawing our operator
	public static final double R = 50; // The base length
	public static final double REF_W = 2 * MARGIN + R * 6; // Reference width
	public static final double REF_H = 2 * MARGIN + R * 5; // Reference height
 
	public OperatorCompartmentFigureListener(ListCompartmentEditPart compartmentEditPart, RoundedRectangle roundedRectangle) {
		this.compartmentEditPart = compartmentEditPart;
		this.roundedRectangle = roundedRectangle;
	}
 
	@Override
	public void figureMoved(IFigure f) {
		ResizableCompartmentFigure figure = (ResizableCompartmentFigure) f;
		if (figure.getSize().width != 0) {
			IFigure contentPane = figure.getContentPane();
			Insets is = figure.getInsets();
			// Determine the scale to apply
			double xScale = ((double) figure.getSize().width - is.left - is.right) / REF_W;
			double yScale = ((double) figure.getSize().height - is.top - is.bottom) / REF_H;
 
			// Set the constraints (bounds) for the rounded rectangle
			Rectangle constraint = new Rectangle(
					(int) ((MARGIN + R) * xScale),
					(int) ((MARGIN) * yScale), 
					(int) (R * 4 * xScale),
					(int) (R * 5 * yScale));
			contentPane.setConstraint(roundedRectangle, constraint);
 
			// Set the constraints for the input and output nodes
			List<AbstractEditPart> childs = compartmentEditPart.getChildren();
			boolean firstInputProcessed = false;
			for (AbstractEditPart child : childs) {
				if (child instanceof AbstractGraphicalEditPart) {
					AbstractGraphicalEditPart gEditPart = (AbstractGraphicalEditPart) child;
					// Operator output ?
					if (gEditPart instanceof OperatorOutputEditPart) {
						constraint = new Rectangle(
								(int) ((REF_W - MARGIN - R) * xScale),
								(int) ((REF_H - R) / 2 * yScale),
								(int) (R * xScale), 
								(int) (R * yScale));
						contentPane.setConstraint(gEditPart.getFigure(), constraint);
					}
					// Operator input ?
					else if (gEditPart instanceof OperatorInputEditPart) {
						constraint = new Rectangle(
								(int) (MARGIN * xScale),
								!firstInputProcessed ? (int) ((MARGIN + R) * yScale) : (int) ((MARGIN + R * 3) * yScale),
								(int) (R * xScale), 
								(int) (R * yScale));
						contentPane.setConstraint(gEditPart.getFigure(), constraint);
						firstInputProcessed = true; // This boolean heps to know if we process the first or the seconde operator input
					}
				}
			}
		}
	}
 
}
  • Reorganize the imports in the classes in which there were compilation problems (MinusOperatorMinusOperatorFigureCompartmentEditPart and PlusOperatorPlusOperatorFigureCompartmentEditPart)
  • If you start eclipse at this point you should get a diagram editor like this :

Diagram error managment

Before we add a feature that computes automatically the operations, we have to deal with a potential problem : at this point, it is possible to create cycles in our diagram :

If we let it uncorrected, it will imply infinite loops when trying to set the operator's result value.

To deal with it, we will add a contraint to our diagram that will detect the cycles.

These are the steps to follow :

  • Open math.gmfmap
  • Under the Mapping node, add an Audit Container node, and set its Name and Id to MathAuditContainer in the properties view.
  • Under this node, add an Audit Rule node and set its properties :
    • Id = CycleDetectorRule
    • Message = A cycle has been detected
    • Name = CycleDetectorRule
    • Severity = ERROR
    • Use In Live Mode = false
  • Under this node :
    • Add a Diagram Element Target node and set its Element property to Link Mapping <{OperatorOutput.result:Result}> (we want the error icon to appear on the links between the operators and their results)
    • Add a Constraint node and set its properties to :
      • Body = CycleDetectorConstraint
      • Language = java

  • Save the file
  • Click on the Transform label of the dashboard
  • Open math.gmfgen
  • Select the Gen Diagram MathDiagrameditPart node and modify its properties in order to activate the validation decorators in the diagram :
    • Validation decorators = true
    • Validation Enabled = true
  • Save the file
  • Click on the Generate diagram editor label of the dashboard

At this point a java skeleton has been generated that must be customized with the constraint specific java code. Before we implement the constraint, we will create a utility class that will contain the cycle detection code.

  • In the Math.diagram project, create a new package named jfb.examples.gmf.math.diagram.util.
  • In this package, create a java class named CycleDetectionHelper and copy/paste this code :
package jfb.examples.gmf.math.diagram.util;
 
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
 
import jfb.examples.gmf.math.OperatorInput;
import jfb.examples.gmf.math.Result;
import org.eclipse.emf.common.util.EList;
 
public class CycleDetectionHelper {
 
	/**
	 * Navigate through the model from one result to detect if there is a cycle. 
	 * @param fromResult the result from which the navigation starts.
	 * @return a boolean indicating if a cycle has been detected.
	 */
	public static boolean cycleDetected(Result fromResult) {
		List<Result> processedResults = new ArrayList<Result>();
		processedResults.add(fromResult);
		return cycleDetected(processedResults, fromResult);
	}
 
	/**
	 * Navigate through the model from one result to detect if there is a cycle. 
	 * @param processedResults result already processed during this navigation.
	 * @param result the result to process.
	 * @return a boolean indicating if a cycle has been detected.
	 */
	private static boolean cycleDetected(List<Result> processedResults, Result result) {
		System.out.println("cycleDetected(" + processedResults + ", " + result + ")");
		EList<OperatorInput> inputs = result.getOperatorInputs();
		boolean cycleDetected = false;
		for (Iterator<OperatorInput> it = inputs.iterator(); it.hasNext()
				&& !cycleDetected;) {
			OperatorInput operatorInput = it.next();
			cycleDetected = cycleDetected(processedResults, operatorInput);
		}
		return cycleDetected;
 
	}
 
	/**
	 * Navigate through the model from one result to detect if there is a cycle. 
	 * @param processedResults result already processed during this navigation.
	 * @param input the input to examine.
	 * @return a boolean indicating if a cycle has been detected.
	 */
	private static boolean cycleDetected(List<Result> processedResults, OperatorInput input) {
		System.out.println("cycleDetected(" + processedResults + ", " + input + ")");
		boolean cycleDetected = false;
		if (input != null) {
			Result nextResult = input.getOperator().getOutput().getResult();
			if (nextResult != null) {
				// A cycle is detected if we meet once again the first result in the stack 
				cycleDetected = processedResults.get(0) == nextResult;
				// If no cycle is detected at this point, we proceed with the next result
				if (!cycleDetected) {
					// but only if we haven't already met this result (which would mean that 
					// our diagram contains a cycle but for a different result from the
					// one on which we are working for this time, ie. processedResults.get(0)).
					if (!processedResults.contains(nextResult)) {
						processedResults.add(nextResult);
						cycleDetected = cycleDetected(processedResults, nextResult);
					}
				}
			}
		}
		return cycleDetected;
	}
 
}

Now we can implement the constraint :

  • Open /Math.diagram/src/jfb/examples/gmf/math/diagram/providers/MathValidationProvider.java
  • Goto to the validate method of the Adapter1 inner class and modify it like this (do not forget to add NOT after @generated) :
/**
 * @generated NOT
 */
public IStatus validate(IValidationContext ctx) {
	Edge edge = (Edge) ctx.getTarget();
	Result result = (Result) edge.getTarget().getElement();
	boolean cycleDetected = CycleDetectionHelper.cycleDetected(result);
	return cycleDetected ? ctx.createFailureStatus(edge) : ctx.createSuccessStatus();
}

As we want validation to be performed whenever our diagram is saved, we must add the following line in the doSaveDocument(IProgressMonitor monitor, Object element, IDocument document, boolean overwrite) method of our MathDocumentProvider class (located in the jfb.examples.gmf.math.diagram.part package) :

ValidateAction.runValidation((View) document.getContent());

If you start eclipse, modify you diagram and save it, you should get this result (an error icon appears on all the operator output to result links of the cycle) :

Computation automatisation

Now that we are able to detect cycles, we can add a feature that will compute automatically the operations.

To do that, we will listen to the model change notifications, and more precisely :

  • Entry value changes
  • Result value changes
  • Number to OperatorInput connections
  • OperatorOutput to Result connections

With GMF, model changes notification are easy to handle. It is possible to simply override the EditParts handleNotificationEvent(Notification notification) methods.

Before we add these handles, we first add a utility class that will help use to compute our operations :

  • In the jfb.examples.gmf.math.diagram.edit.parts.custom package, add a class named AutomaticComputationHelper and paste this code :
package jfb.examples.gmf.math.diagram.edit.parts.custom;
 
import jfb.examples.gmf.math.Number;
import jfb.examples.gmf.math.Operator;
import jfb.examples.gmf.math.OperatorInput;
import jfb.examples.gmf.math.PlusOperator;
import jfb.examples.gmf.math.Result;
import jfb.examples.gmf.math.diagram.util.CycleDetectionHelper;
 
import org.eclipse.emf.common.util.EList;
 
public class AutomaticComputationHelper {
 
	public static void numberValueChanged(Number number) {
		EList<OperatorInput> inputs = number.getOperatorInputs();
		for (OperatorInput operatorInput : inputs) {
			Operator op = operatorInput.getOperator();
			updateOperatorResult(op);
		}
	}
 
	public static void operatorOutputToResultConnectionChanged(Result result) {
		if (result.getOperatorOutput() == null) {
			result.setValue(0);
		}
		else {
			updateOperatorResult(result.getOperatorOutput().getOperator());
		}
	}
 
	public static void updateOperatorResult(Operator operator) {
		Result result = operator.getOutput().getResult();
		if (result!=null) {
			// If there is a cycle...
			if (CycleDetectionHelper.cycleDetected(result)) {
				result.setValue(0);
			}
			else {
				Number in1 = operator.getInputs().get(0).getNumber();
				Number in2 = operator.getInputs().get(1).getNumber();
				double _in1 = in1 != null ? in1.getValue() : 0;
				double _in2 = in2 != null ? in2.getValue() : 0;
				result.setValue(operator instanceof PlusOperator ? _in1 + _in2 : _in1 - _in2);
			}
		}
	}
 
}

Now we will add code to handle the model changes. “Entry value changes” will be handled in EntryEditPart :

  • Open /Math.diagram/src/jfb/examples/gmf/math/diagram/edit/parts/EntryEditPart.java
  • Add the following overriding method :
protected void handleNotificationEvent(Notification notification) {
	super.handleNotificationEvent(notification);
	System.out.println("entry.handleNotificationEvent(" + notification + ")");
	if (notification.getNotifier() instanceof Entry) {
		if (notification.getFeature() instanceof EAttribute) {
			String attrName = ((EAttribute) notification.getFeature()).getName();
			if ("value".equals(attrName)) {
				AutomaticComputationHelper.numberValueChanged((Number) notification.getNotifier());
			}
		}
	}
}

“Number to OperatorInput connections” will be handled in OperatorInputEditPart :

  • Open /Math.diagram/src/jfb/examples/gmf/math/diagram/edit/parts/OperatorInputEditPart.java
  • Add the following overriding method :
protected void handleNotificationEvent(Notification notification) {
	super.handleNotificationEvent(notification);
	if (notification.getNotifier() instanceof OperatorInput) {
		if (notification.getFeature() instanceof EReference) {
			String refName = ((EReference) notification.getFeature()).getName();
			if ("number".equals(refName)) {
				AutomaticComputationHelper.updateOperatorResult(((OperatorInput) notification.getNotifier()).getOperator());
			}
		}
	}
}

“Result value changes” and “OperatorOutput to Result connections” will be handled in ResultEditPart :

  • Open /Math.diagram/src/jfb/examples/gmf/math/diagram/edit/parts/ResultEditPart.java
  • Add the following overriding method :
protected void handleNotificationEvent(Notification notification) {
	super.handleNotificationEvent(notification);
	if (notification.getNotifier() instanceof Result) {
		if (notification.getFeature() instanceof EAttribute) {
			String attrName = ((EAttribute) notification.getFeature()).getName();
			if ("value".equals(attrName)) {
				AutomaticComputationHelper.numberValueChanged((Number) notification.getNotifier());
			}
		}
		else if (notification.getFeature() instanceof EReference) {
			String refName = ((EReference) notification.getFeature()).getName();
			if ("operatorOutput".equals(refName)) {
				AutomaticComputationHelper.operatorOutputToResultConnectionChanged((Result) notification.getNotifier());
			}
		}
	}
}

If you start eclipse, you should now get a “full featured” operator diagram editor !

Thank you

I hope that this material will be helpful for you. If you want to support it, your help is welcome :

Discussion

dinko ivanov, 2010/02/09 15:30

Thanks for the great tutorial! It helped me a lot! :-)

Regards, Dinko

Dinko, 2010/02/12 05:52

If you want to implement ports/pins or any kind of node attached to the side of another node, you might want to try the Affixed Parent Side property. It's much easier with no coding. In this example the Operator input/output nodes should have this property.

Regards, Dinko

Jean-François Brazeau, 2010/07/07 21:19

Yes you are right ! Thank you for the tip !

Regards,

JFB

Albert, 2010/03/12 13:30

Hello,

At first I wanted to congratulate you on your various tutoriels on GMF. They allowed me to take in in hand this tool and to arrive at a minimum result…

I adapted your explanations to the fact that I wish to realize, but in the stage of modification of createFigure, I don't find the class equivalent to OperatorCompartmentFigureListener in my project. Could you direct me on packages it sensible to contain it.

Otherwise in the creation of the class PlusRoundedRectangle (also in MinusRoundedRectangle), you have the operation graphics.setForegroundColor( ColorConstants.black) repeated two times in succession, is it an copy-paste error or is there a particular reason in this redundancy?

I thank you for your attention and I ask you to excuse me for my bad English.

Albert, French developer…

Albert, 2010/03/12 14:02

Saddened for my first question, she shows typically that it is always necessary to read everything before putting quetions. I thus found my answer farther in the tutoriel. It is nevertheless written well. I return, I just jump through the window…

Kind Regards, Albert

Jean-François Brazeau, 2010/07/07 21:22

Puisque tu es Français, une fois n'est pas coutume, parlons Français !

Merci pour les encouragements. Et tu as 1000 fois raisons, la double ligne positionnant la couleur du fond est une erreur de copier/coller ! Il faudra que je corrige à l'occasion !

JFB

Iman, 2010/06/02 17:58

I am having 2 silly problems first I couldn't specify the labels in “Graphical Definition Model” step but I have passed it, I don't know if this will generate a problem later? Second I cannot specify the features math.gmfmap file, “features to display” cannot be edited (I have pressed on the browse button then “Add” but nothing happens) and “Diagram label” drop down list doesn't contain anything. So I am stuck and cannot go any further in your generous tutorial. I believe that I have missed something, but I have started from the beginning and couldn't find the problem.

One more thing, I am very interested in this tool in my work to make a modeling tool, I want to draw a special figures like yours but cannot find the way to follow to do my own notations, I mean like clear steps to change my nodes and links. I would be grateful if you can tell me how to do it as steps or guidance. As hint for my problem, I want to draw circles inside circles and rectangles that contain other shapes :( can you help me.

One last question, do you have any material to enable generation of XML file from the model we draw, i.e. the circuits you have done in this tutorial could be generated to XML data file, right?

Thank you very very much.

Jean-François Brazeau, 2010/07/07 21:31

Hello Iman,

First, if you meet some problems when you follow this tutorial, I think that it may be because :

  1. there is a mistake or someting missing in the tutorial
  2. you have missed a step before
  3. you don't use exactly the same version of eclipse / GMF / EMF that were used to build this tutorial

If there is a mistake in this tutorial I would appreciate if you could tell me what… So I think it would be a good thing for you to follow the tutorial patiently from the start : this may help you not to miss any step and to detect potentiel mistakes in the tutorial.

To answer the last question : no I have no material to generate XML from the model, because there is no need of such a material as GMF / EMF natively persist the data into XML ! Just open the diagram or model file with a text editor and you will understand what I mean !

Regards,

JFB

Iman, 2010/07/12 01:36

Hello Jean,

Thank you very much for response, but I have recently discovered the problem, the classes are missing the method “Double getValue();” in the interface “Number” or just mention that you have added it at the Ecore model itself. That's why I didn't find anything related to value later. Also thank you about XML, I have tried it and it works :)

Thanks a lot for your help, but I am still trying the changing figures thing, I may need help there :) Kind regards, Iman

Daniele Barone, 2010/07/19 14:14

Wow! These are great tutorials to help people in dealing with GMF. Thank you very much for that and congratulations for all your work!!!

Indeed, I am developing an editor with GMF and your material is very useful.

In regard to this example, I was wondering if you have some information on how to, for each “Entry” or “Result”, have the label aligned in the middle and centre of the figure. Moreover, since in my case I will have a text field instead of a number, how I can have a multi-line field instead of a single line.

Thank you in advance for any suggestions you may have. Ciao

Jean-François Brazeau, 2010/08/01 16:31

Hi,

Try to replace the FlowLayout in the gmfgraph file by a BorderLayout, add a BoderLayoutData to the label and set its Alignment property to CENTER. Does it help ?

JFB

Jean, 2011/04/16 13:03

Hi,

After a day of research, I finally found a way to do that! If you want to get your label in the middle of a shape, add a children 'Grid Layout' to your shape, and a children 'grid layout data' to you label, with both 'grab excess place' turn on true, and both alignment on 'center'.

Jean

Iman, 2010/08/14 15:12

Hi,

I want to ask and I really hope to answer me soon. I am confused about link mappings in gmfmap!! I am having 4 link notations that are used to connect 10 node types, i.e. there are repeating usage of these notations.

The problem is the following: I want to make sure that they are correct, but I cannot find anywhere what is the correct meanings of Diagram link, source feature, target feature, element, containment feature? What are mandatory and what are optional and how to use them?!!.

The problem was discovered when I tried to change notations of links in gmfgraph and nothing was changed, and I think the problem is in mapping. Hope to tell me a solution to my problem cause I need to deliver that asap :((

Thanks in advance.

Thierry Templier, 2010/08/16 15:12

Hello Jean-François,

Very nice tutorial! Thanks very much!

I wonder if it's possible to do something similar without validation. I think of adding an icon at the middle of the connection according properties values of ends…

Thanks in advance,

Thierry

Jean-François Brazeau, 2010/09/18 13:21

Hmmmm… It is probably possible to do it. But maybe not easily. If you find an easy way to it, don't hesitate to share your tips by sending it to me. A new tutorial should be added. JF

EL HAMLAOUI Mahmoud, 2010/10/12 17:14

Hi Jean-François,

Congratulations for those tutorials, great work! I wonder if it is possible to dispaly some swt elements,custom figures like checkboxes instead of rectangles and others deafaults figure Thanks Mahmoud

Jean-François Brazeau, 2010/10/13 18:58

Hello Mahmoud,

In fact I don't think that it is possible. If you want to have checkboxes, you may need to create graphical components that simulates a normal checkbox behaviour. I don't think that such a feature is so simple to build…. But if you manage to do that, let me know, we should add a tutorial entry, this may interest some other people.

Regards,

JFB

Vitaly, 2011/03/29 11:22

Many thanks for tutorials! They are great and very useful! Though your plus sign looks like a cross:)

Regards

Kieara Belle, 2011/09/10 17:05

Hello,

First of all, thank you for all the tutorials, they were very informative. I am new to Eclipse EMF and GMF and have been assigned a project related to editor development using GMF. The meta-model is based on a language that represents long running transactions , much like PI-Calculus. I managed to create the editor, based on syntax, however I am not being able to figure out what and how to write the code for incorporating the semantics of the language.

For example, if I have 2 nodes and a link between them that indicates that it is a sequence i.e Node 2 follows Node 1, how do I show it ? (code it) As in, in the worst case I could name that link 'Tom' instead of 'Sequence',it would still connect those 2 nodes without any knowledge of what it's supposed to do.Like in this example, we made the editor aware that '+' operator would ADD and '-' would subtract two numbers. How do I do this to show sequence and parallel flow of processes ?

Also, when I create a diagram in my basic editor (no semantics), I also get an option to generate a domain model, which inherently I suppose is the instance of my meta-model, however, this generated model is FLAT, i.e. it does not have a tree structure as it should have (as opposed to the time when I create a model from the meta-model after generating the EMF genmodel). Does this problem exist because my editor is not semantically sound ?

I am sorry if I sound so random or uninformed about the topic, but I really do not know how I should proceed. I have seen tutorials that aid in editor generation but none related to semantics and Modifying the generated code. Could anyone please suggest how can I solve this ? Any help will be appreciated, suggestions, code snippets, …I need to submit this on 12/09/2011 (Monday morning).. Please..

Jean-François Brazeau, 2011/12/08 20:30

Hello,

Gloups… I think I am too late… Two days to solve GMF problems may be…. quite few… The result you're expecting is not really clear. Do you want something to be evaluated or computed automatically as you add nodes and edges to your diagram ? Or do you want your model to be interpreted outside of your editor ?… The generated model that you get is probably flat because you have no containment references.

Regards,

JFB

Joseph Derrick, 2011/10/19 15:21

Hi all,

I am requesting for some help. I need to know a simple procedure on how create an hexagon shape in .gmfgraph file. Thanks a lot in advance.

Derrick

Jean-François Brazeau, 2011/12/08 20:40

The fourth tutorial shows how to create a custom figure through the gmfgraph model.

Milon, 2012/09/27 01:53

This is really a good tutorial. but if u make a video tutorial for Computation automatisation, it will help us, the beginners… Thank you.

Jan, 2014/01/30 11:34

I have tried to copy all the steps of creating a cycle detector, but it does not work: the code in CycleDetectionHelper is never called. Could you please navigate me what have I done wrong? On saving the diagram something does happen, but the process seem too complicated to trace (some BatchValidator is called etc.) and it does not call my code.

 
gmf_tutorial5.txt · Last modified: 2012/07/06 20:13 by jfbraz
 
Except where otherwise noted, content on this wiki is licensed under the following license:CC Attribution-Noncommercial-Share Alike 3.0 Unported
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki