Dynamically calculating the position constraints for a figure in a GEF editor layout in relation to another figure

I’ve worked on the implementation of a GEF editor for an EMF-based model, using both the GEF book and vainolo‘s great GEF tutorials. The model consists of several types of nodes and connections, with different layout requirements for the display of their figures in the editor, as follows.

  • “Tokens” are nodes which have a specific order. They should be displayed as graphical nodes, ordered, on the same horizontal level, i.e., in a line, i.e., with the same y coordinate, but a specific x coordinate. These nodes should display the model element’s name (a java.lang.String) in a label. These name Strings can be of different length.
  • “Nodes” are nodes which don’t have a specific order, and have connections to Tokens or other Nodes. Graphically, the should be visualised as graphical nodes, centered above the graphical nodes of the Tokens or Nodes they’re connected with. The value of their x coordinate should be calculated as follows.
    ((x coordinate (rightmost child figure) + width (rightmost child figure)) – (x coordinate (leftmost child figure))) – (width (node figure itself) / 2)
    Vertically they should be placed one “level” (a fixed negative number of pixels, say 50) above the “level” of those of their immediate children that are furthest away from the Token line. These nodes should display the model element’s name (a java.lang.String) in a label. These name Strings can be of different length.

Figure 1 shows an example visualization (created with yEd). The alignments aren’t exact, but you get the idea…

Figure 1: Example graph.

Figure 1: Example graph.

The main issue I with the alignments is that my model – unlike those used in the GEF book and vainolo’s tutorials – does not allow for inclusion of visual data. Which is the way it should be, cf. the following quote from the GEF book.

In general, it is best that the model contain only domain information that is persisted. [...] If you were creating a commercial [...] application, depending on the larger context of the model and application, it isn’t clear that you would include presentation information such as x, y, width, and height in the domain model [...].
(Rubel et al.: 115)

My model provides an annotation mechanism for model elements, so in theory I could use an annotation to persist presentation information for model elements. However, I’m trying to implement the editor as cleanly as possible (and when I say “trying” I mean “struggling”), hence a dynamic solution is needed. Before I turn to that though, let’s have a look at how figures are generally given a position in the editor. Consider the following code snippets, representing the Figure class and the EditPart class for a given Token respectively. Both snippets are missing some methods and calls that are irrelevant for this post.

/**
* @author New Code on the Block
*
*/
public class TokenFigure extends Figure {

	private Label label;
	private RectangleFigure rectangle;

	public TokenFigure() {
		setLayoutManager(new XYLayout());
		add(new RectangleFigure());
		add(new Label());
	}

	@Override
		protected void paintFigure(Graphics graphics) {
		Rectangle r = getBounds().getCopy();
		setConstraint(rectangle, new Rectangle(0, 0, r.width, r.height));
		setConstraint(label, new Rectangle(0, 0, r.width, r.height));
	}
}

 

/**
* @author New Code on the Block
*
*/
public class TokenEditPart extends AbstractGraphicalEditPart {

	@Override
	protected IFigure createFigure() {
		return new TokenFigure();
	}

	@Override
	protected void refreshVisuals() {
		TokenFigure figure = (TokenFigure) getFigure();
		Token model = (Token) getModel();
		GraphEditPart parent = (GraphEditPart) getParent();
		figure.getLabel().setText(model.getName());
		Rectangle layout = new Rectangle([x], [y], [width], [height]);
		parent.setLayoutConstraint(this, figure, layout);
	}
}

The TokenFigure class code is given for the sake of completeness only, the interesting part is the refreshVisuals() method in TokenEditPart, which is called whenever, well, the figure visuals should be refreshed (e.g., after changes in the EditPart‘s model, or its visuals, or when resizing the viewer, etc.). On line 18, a new Rectangle is constructed which represents the actual coordinates the figure will have on the parent EditPart, the rest is fairly self-explanatory. The tricky question is: Where to get the values from? Both the example from the GEF book as well as vainolo simply read in the values persisted in the model object in question. As this isn’t an option for me, I’ll calculate them dynamically. The next section shows how it can be done, but please note that for reasons of space and readability, I’ll only include the solution for one problem (the Tokens’ alignment on one line), and I’m sure my solution is far from perfect, so please add your fixes/corrections in the comments!

Dynamically calculating the position for a GEF figure in relation to another figure

In the context of my editor (cf. description above), there will always be one node with hardcoded coordinates: the first (leftmost) Token. The coordinates of this Token can be used to dynamically calculate the position of the other Tokens (the position of which can ultimately be used to calculate the position of Nodes).

Let’s assume we want to place the graphical node for the first Token at x = 20 (i.e., 20 pixels away from the left hand border of the parent’s EditPart, which is in my case a FreeformLayer), and y = 200 (i.e., 100 pixels away from the upper border, etc.). The size of the graphical node, its width and height, will be calculated from the preferred size of the label containing the Token‘s name. Note that this is a preliminary solution, I’m sure there are better ways to achieve this. In code terms, think of this as being annotated // FIXME.

In order to calculate the coordinates for the figures for the Tokens following the first one, we simply need to find the position of the preceding TokenFigure on the parent figure, and take it from there. This is the part that has actually cost me a good while to figure out. I had already abandoned the idea of calculating positions dynamically, but stumbled upon the solution by pure chance, so there ya go.

To find the position of the preceding Token we need the following objects in our “toolbox” so to speak: the TokenEditPart‘s figure and model, and its parent’s figure and model. The parent model (in this case, Graph) should provide a getter for all model objects that need to be laid out (here, Graph provides a method getTokens()).

The steps needed to calculate the coordinates for any given token but the first are:

  1. Get a List of all Tokens in the Graph.
  2. In this List, find the index for the EditPart‘s model.
  3. Get the model object for the Token coming, in the above List, before the one we’re calculating coordinates for (lastToken).
  4. Get a List of all children of the parent EditPart, and in this list, find the one child EditPart that has lastToken as model (lastTokenEditPart).
  5. Get the constraints for lastTokenEditPart‘s figure from the parent figure’s LayoutManager.
  6. Calculate the position for the current EditPart‘s figure from these constraints.

This, and the calculation of the current EditPart‘s figure’s width and height based on the Label‘s preferred size (see above), will allow you to correctly place the current EditPart‘s figure in relation to its preceding figure. While I only give the solution to one of the problems mentioned above here, the rest are easy enough to deduce. The source code snippet is given below.

/**
* @author New Code on the Block
*
*/
public class TokenEditPart extends AbstractGraphicalEditPart {

	@Override
	protected IFigure createFigure() {
		return new TokenFigure();
	}

	@Override
	protected void refreshVisuals() {
		TokenFigure figure = (TokenFigure) getFigure();
		Token model = (Token) getModel();
		GraphEditPart parent = (GraphEditPart) getParent();
		Graph graph = (Graph) parent.getModel();

		figure.getLabel().setText(model.getName());

		Rectangle layout = calculateLayout(figure, parent, model, graph);

		parent.setLayoutConstraint(this, figure, layout);
	}

	private Rectangle calculateLayout(TokenFigure figure, GraphEditPart parent, Token model, Graph graph) {
		Rectangle calculatedLayout = null;
		List tokenList = graph.getTokens();
		int indexOfThisTokenInTokenList = tokenList.indexOf(model);
		Dimension labelPreferredSize = figure.getLabel().getPreferredSize();

		if (indexOfThisTokenInTokenList != 0) { // If model isn't the first token in the list
			Token lastToken = tokenList.get(indexOfThisTokenInTokenList - 1);
			Rectangle lastTokenFigureConstraints = null;
			IFigure parentFigure = parent.getFigure();
			for (Object ep : parent.getChildren()) {
				if (ep instanceof TokenEditPart && ((TokenEditPart) ep).getModel() == lastToken)
				lastTokenFigureConstraints = (Rectangle) parentFigure.getLayoutManager().getConstraint(((TokenEditPart) ep).getFigure());
			}
			calculatedLayout = new Rectangle(lastTokenFigureConstraints.x + lastTokenFigureConstraints.width + 5,
											lastTokenFigureConstraints.y,
											labelPreferredSize.width + 4,
											20);
		}
		else
			calculatedLayout = new Rectangle(20, 100, labelPreferredSize.width + 4, 20);

		return calculatedLayout;
	}

}

References

Dan Rubel, Jaime Wren, Eric Clayberg: The Eclipse Graphical Editing Framework (GEF). Boston (Addison-Wesley): 2012. 267 pp. In English. ISBN-13: 978-0-321-71838-9.

TwitterFacebookGoogle+EmailLinkedInPinterestDiggRedditStumbleUpon
If you found this post helpful, you may want to consider donating to help cover server costs.
Tagged with: , , , , , , , , , , , , , ,
Posted in Draw2D, GEF: Eclipse Graphical Editing Framework, Tutorial

Leave a Comment

%d bloggers like this: