Creating an OPM GEF Editor – Part 17: How to Define Container Edit Parts

Previous Tutorial: Creating an OPM GEF Editor – Part 16: Displaying EMF Properties in a GEF Editor.

The Object Process Methodology‘s modeling language allows (and even encourages) the definition of processes by “in-zooming” (enlarging) the process to define and inserting into it internal process that constitute the process that is being defined, something like this:

In the diagram, process P1 is composed of three sub-processes, P1.1, P1.2 and P1.3. Since there is no information flow between the processes, they are executed in parallel when process P1 is executed (A full semantic specification of OPM model execution is currently under work). Another diagram entity that we have yet to add to our diagram is the OPM state, which is modeled as a “rountangle” (rounded rectangle) and is used internally in an object.
So we have three diagram entities that are containers: the Object Process Diagram, Objects and Processes (which are both OPM Things). This is one of the cases where multiple inheritance would be useful, since on one side we have nodes (Objects, Processes and States) which can be linked, and on the other hand we have containers (Object Process Diagrams, Objects, Processes) which can contain nodes. But for good reasons Java does not allow multiple inheritance (check this article for an explanation why), we settled for a less than correct model where all nodes are containers. Hope no regrets will come from this in the future.
I forgot to create an initial project zip file. Sorry for this. But the changes are not big so you can probably do this on your own very easily.

  1. As stated above, we had to do some changes to out model to enable containers. The new model can be found here.
  2. We have already implemented containers in the editor, since the Object Process Diagram (which is the “canvas” of our diagram) already allow addition of entities, therefore we just have to refactor the code to use this same functionality in all the other containers.
  3. The first thing we do is modify our OPMObjectProcessDiagramXYLayoutPolicy.java and make it a OPMContainerXYLayoutPolicy.java which can be used by all containers. In this policy we also limit the creation of new elements to Objects and Processes (this will be changed in the future to allow for states). The implementation is as follows:
    package com.vainolo.phd.opm.gef.editor.policy;
    
    import com.vainolo.phd.opm.model.OPMContainer;
    import com.vainolo.phd.opm.model.OPMNode;
    import com.vainolo.phd.opm.model.OPMObject;
    import com.vainolo.phd.opm.model.OPMProcess;
    
    import org.eclipse.draw2d.geometry.Dimension;
    import org.eclipse.draw2d.geometry.Point;
    import org.eclipse.draw2d.geometry.Rectangle;
    import org.eclipse.gef.EditPart;
    import org.eclipse.gef.GraphicalEditPart;
    import org.eclipse.gef.Request;
    import org.eclipse.gef.commands.Command;
    import org.eclipse.gef.commands.UnexecutableCommand;
    import org.eclipse.gef.editpolicies.XYLayoutEditPolicy;
    import org.eclipse.gef.requests.ChangeBoundsRequest;
    import org.eclipse.gef.requests.CreateRequest;
    
    import com.vainolo.phd.opm.gef.editor.command.OPMNodeCreateCommand;
    import com.vainolo.phd.opm.gef.editor.command.OPMNodeChangeConstraintCommand;
    import com.vainolo.phd.opm.gef.editor.part.OPMStructuralLinkAggregatorEditPart;
    
    /**
     * This class describes the commands that can be used to change the layout 
     * and create new nodes inside the {@link OPMContainer}.
     * 
     * @author vainolo
     *
     */
    public class OPMContainerXYLayoutPolicy extends XYLayoutEditPolicy {
        
        private static final Dimension DEFAULT_THING_DIMENSION = new Dimension(50, 50);
    
    	/**
    	 * Command created top change the constraints of a {@link OPMNode} instance.
    	 */
    	@Override protected Command createChangeConstraintCommand(EditPart child, Object constraint) {
    		OPMNodeChangeConstraintCommand command = new OPMNodeChangeConstraintCommand();
    		command.setNode((OPMNode) child.getModel());
    		command.setNewConstraint((Rectangle) constraint);
    		return command;
    	}
    
    	/**
    	 * Command created to add new nodes to the OPD.
    	 */
    	@Override protected Command getCreateCommand(CreateRequest request) {
    		Command retVal = null;
    		if(request.getNewObjectType().equals(OPMObject.class) || request.getNewObjectType().equals(OPMProcess.class)) {
    			OPMNodeCreateCommand command = new OPMNodeCreateCommand();
    			Rectangle constraints = (Rectangle) getConstraintFor(request);
                		command.setConstraints(new Rectangle(constraints.getLocation(), DEFAULT_THING_DIMENSION));
    			command.setContainer((OPMContainer) getHost().getModel());
    			command.setNode((OPMNode)(request.getNewObject()));
    			retVal = command;
    		} 
    		return retVal;
    	}
    	
    	/**
    	 * The superclass implementation calls 
    	 * {@link OPMContainerXYLayoutPolicy#getResizeChildrenCommand(ChangeBoundsRequest) getResizeChildrenCommand()}
    	 *  by default. This is not good in our case since we want to disallow resizing of 
    	 *  {@link OPMStructuralLinkAggregatorEditPart} while allowing to move them. Therefore
    	 *  we had to override the implementation.
    	 *  It creates a {@link Command} that can be used to move a children of the policy's owner.
    	 *  @return a {@link Command} used to move children of the host {@link EditPart}. 
    	 */
    	@Override
    	protected Command getMoveChildrenCommand(Request request) {
    	    return getChangeConstraintCommand((ChangeBoundsRequest) request);
    	}
    	
    	/**
    	 * Creates a {@link Command} to resize children of the host {@link EditPart}.
    	 * The functions checks that the children don't contain any {@link OPMStructuralLinkAggregatorEditPart}
    	 * instances which cannot be resized.
    	 * @return a {@link Commnad} to resize children of the {@link EditPart}.
    	 */
    	@Override
    	protected Command getResizeChildrenCommand(ChangeBoundsRequest request) {
    	    for(Object editPart : request.getEditParts()) {
    	        if(editPart instanceof OPMStructuralLinkAggregatorEditPart) {
    	            return UnexecutableCommand.INSTANCE;
    	        }
    	    }
    	    return getChangeConstraintCommand(request);
    	}
    }
    
  4. Next we add the policy to all container entities, but since there is no container edit part in our model, we add this policy to the OPMNodeEditPart in the createEditPolicies method and we also modify the getModelChildren so that it returns the model children:
        /**
         * Install edit policies that can be applied to {@link OPMNodeEditPart} instances.
         */
        @Override
        protected void createEditPolicies() {
            installEditPolicy(EditPolicy.COMPONENT_ROLE, new OPMNodeComponentEditPolicy());
            installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new OPMNodeGraphicalNodeEditPolicy());
            installEditPolicy(EditPolicy.LAYOUT_ROLE, new OPMContainerXYLayoutPolicy());
        }
        
        @Override protected List getModelChildren() {
            OPMNode model = (OPMNode) getModel();
            return Collections.unmodifiableList(model.getNodes());
        }
    
  5. Now comes the interesting part (or at least where I spent most of the time during this task). The Object and Process figures are created by extending a generic figure (which has no visual representation) and adding to it a figure and a label. After doing all code changes shown previously and firing up the editor, you could add internal figures, but they didn’t show on the screen! Debug prints showed that their paint methods were called, so I had no idea what was wrong… I read and re-read the GEF logic example code provided eclipse and saw that they used something called a contentPane where they added their figures, but I still didn’t understand how this helped. After some long hours of framework reading and debugging the problem showed itself: the method refreshChildren of the AbstractEditPart contains the code that handles addition and removal of the child edit parts based on the model children that the edit part returns (this method is not overriden in AbstractGraphicalEditPart). This method fetches the list of child model entities and adds them as children using the same order (using an index counter) using the addChild(childEditPart, index) method which in turns calls an abstract addChildVisual(childEditPart, index) to add the visual representation of the child. This method is implemented in the AbstractGraphicalEditPart class, which simply adds the child’s figure to the parent figure using the specified index. Can you spot the problem?
    The add method of the Figure adds the figures in the specified index, and pushes the existing figures to the end of the list – which means that they are pained above the newly added figure. And because my Object and Process figures added figures (Rectangle and Ellipse respectively) before, these figures were pushed lower in the drawing order and were drawn last, which meant they overlapped all the other figures.
    The solution? modify the figures so first we add the label, then we add the figure (rectangle or ellipse) without filling it (so that the label below is shown) and finally we use the last figure as the container of the child figures (which means that we also have to override the getContentPane method in the OPMThingEditPart which by default returns the EditPart figure. So this is how the new OPMObjectFigure is implemented:

    package com.vainolo.phd.opm.gef.editor.figure;
    
    import org.eclipse.draw2d.ChopboxAnchor;
    import org.eclipse.draw2d.ConnectionAnchor;
    import org.eclipse.draw2d.Figure;
    import org.eclipse.draw2d.Graphics;
    import org.eclipse.draw2d.IFigure;
    import org.eclipse.draw2d.Label;
    import org.eclipse.draw2d.RectangleFigure;
    import org.eclipse.draw2d.XYLayout;
    import org.eclipse.draw2d.geometry.Rectangle;
    
    public class OPMObjectFigure extends Figure implements OPMThingFigure {
    	private Label nameLabel;
    	private RectangleFigure rectangle;
    	private ConnectionAnchor connectionAnchor;
    	
    	public OPMObjectFigure() {
            setLayoutManager(new XYLayout());
            nameLabel = new Label();
            add(nameLabel);
            rectangle = new RectangleFigure();
            rectangle.setFill(false);
            rectangle.setLayoutManager(new XYLayout());
            add(rectangle);
    	}
    	
    	@Override
    	public IFigure getContentPane() {
    	    return rectangle;
    	}
    	
    	@Override protected void paintFigure(Graphics graphics) {
    		Rectangle r = getBounds().getCopy();
    		setConstraint(rectangle, new Rectangle(0, 0, r.width, r.height));
    		setConstraint(nameLabel, new Rectangle(0, 0, r.width, r.height));
    		rectangle.invalidate();
            nameLabel.invalidate();     
    	}
    	
    	public Label getNameLabel() {
    		return nameLabel;
    	}
    	
    	public ConnectionAnchor getConnectionAnchor() {
    		if (connectionAnchor == null) {
    			connectionAnchor = new ChopboxAnchor(this);
    		}
    		return connectionAnchor;
    	}
    
        @Override
        public ConnectionAnchor getSourceConnectionAnchor() {
            return getConnectionAnchor();
        }
    
        @Override
        public ConnectionAnchor getTargetConnectionAnchor() {
            return getConnectionAnchor();
        }
        
        @Override
        protected boolean useLocalCoordinates() {
            return true;
        }
    }
    

    Ah, we also override the useLocalCoordinates method which lets us manage internal figures as if they have their own coordinate system.

  6. After implementing similar changes in the OPMProcessFigure we now fire our which allows us to add child entities!

You can find the final project files here, including some extra goodies that will be shown in the next tutorial. Some of the files may not be up to date with the latest version of the tutorial, so please check the comments before you come complaining.
See you soon!

Next Tutorial: Creating an OPM GEF Editor – Part 18: Snapping to Grid and to Geometry.

16 thoughts on “Creating an OPM GEF Editor – Part 17: How to Define Container Edit Parts

  1. Vainolo,

    First, this is the last part I am going to follow for now. I got what I needed. So I want to thank you for an excellent job. You really saved me a lot of time getting started with GEF. If I was able to find the donation link you mentioned, I would have donated something for your laptop.

    Second, I’d like to log three defects in the containment feature:

    1- in my implementation, when I add a contained object, it does not show when created. I followed your explanation about the z-order of the figures, but it was not enough to solve the issue. I think that the difference between our implementations is the ‘direct edit on creation’ feature that you have (I did not do it) which probably causes the object to refresh itself when created. I found one disturbing issue which is – when adding an eCore element to an eCore container by adding it to an element list, the container doesn’t send any notification about the change (as opposed to when setting an attribute in an eCore element for example). I bypassed this by calling <>EditPart.Refresh at the end of <>CreateCommand.Execute

    Did you notice that adding an element to a list of elements in the model does not invoke any notification? Do you think it is a limitation of emf? or am I doing something wrong?

    By the way, it might help if you also show the code for the overridden method OPMThingEditPart.getContentPane. You say in the text that you added it, but you only show the OPMObjectFigure code. This confused me for awhile until I read the text enough times.

    2- When adding an object within an object within a third object, it is misplaced on creation. I am going to look into it myself, but if you found any solution for this please share.

    3- In my implementation I support mouse dragging when creating an object. If I drag the mouse to a certain dimension, the created object will have these dimensions. The issue is that when dragging the mouse inside a container, the temporary grayed rectangle that gef draws is drawn not where the mouse is, but in the same coordinates, only relative to the diagram instead of the containing object. This might be just a minor visual issue, but I suspect that it might be connected to the previous issue.

    Third, is there a way to contact you directly by e-mail to ask something about opm?

    Again, many thanks for your sharing in these tutorials.

    • I think the issue with item 2 above is the use of translateToParent in OPMContainerXYLayoutPolicy.getCreateCommand.
      First, from what I understand from the documentation, the appropriate method in this place is translateToRelative, instead of translateToParent. But strangely this does not solve the issue. Digging into it I got to the following code in org.eclipse.draw2d.Figure.translateToRelative:

      public final void translateToRelative(Translatable t) {
      if (getParent() != null) {
      getParent().translateToRelative(t);
      getParent().translateFromParent(t);
      }
      }

      This looks like a gef defect to me (though strangly I did not see any documentation for this, so maybe I am missing something). In my opinion this code should be:

      public final void translateToRelative(Translatable t) {
      if (getParent() != null) {
      getParent().translateToRelative(t);
      translateFromParent(t);
      }
      }

      But I don’t know how to fix it, as this method is tagged final so it can not be overridden. I ended up implementing my own fixedTranslateToRelative in OPMThingFigure which calculates the correct coordinates without recursion.

      • Hi Ouri. There are some old threads on the web on this same subject (see here for example), so the function is probably correct, only it’s meaning does not match its actual functionality.
        I checked the Logic example that is given with eclipse and they way they implement figure addition is as follows:

            @Override protected Command getCreateCommand(CreateRequest request) {
                Command retVal = null;
                if(getHost() instanceof OPMStateEditPart) {
                    return UnexecutableCommand.INSTANCE;
                }
                if(request.getNewObjectType().equals(OPMObject.class) || request.getNewObjectType().equals(OPMProcess.class) || request.getNewObjectType().equals(OPMState.class)) {
                    if(request.getNewObjectType().equals(OPMState.class) && (!(getHost() instanceof OPMObjectEditPart))) {
                        return UnexecutableCommand.INSTANCE;
                    }
                    OPMNodeCreateCommand command = new OPMNodeCreateCommand();
                    Rectangle constraints = (Rectangle) getConstraintFor(request);
                    if(constraints.getSize().isEmpty()) {
                        constraints.setSize(DEFAULT_THING_DIMENSION);
                    }
                    command.setConstraints(constraints);
                    command.setContainer((OPMContainer) getHost().getModel());
                    command.setNode((OPMNode)(request.getNewObject()));
                    retVal = command;
                } 
                return retVal;
            }
        

        Note the use of the internal policy methods like getConstraintFor(request) which also does point translation, in a way which I don’t understand but works.
        An I now also understand how to create diagram entities dragging the mouse 🙂

    • Finally got to check-out the project and check your comments.
      1) The direct edit on creation feature does not affect the refresh of the object. I removed it from the code and the inner object still works. You probably have some problems in you notification mechanisms, which are defined in the .genmodel file. In the list from which you want to receive connections verify that “Notify=true”. Maybe this is your problem.
      Furthermore, all the project code can be found in the zip file attached at the end of the tutorial.
      2) You are right… there is a bug here somewhere.
      3) How did you implement this? I would love to learn how.

      Glad to be of service 🙂

  2. Hi, small bug.
    If you drag a container that contains bendpoints, the bendpoints stay put. i.e. it is as if they are relative to the main diagram rather than to the container.
    Phil
    P.s. very useful tutorial by the way

    • This is because the bendpoints are defined in absolute coordinates, not relative coordinates. More code would be needed to handle this. Not a bug, just not implemented yet. But thanks for the comment.

  3. Hello Vainolo : )

    In my case, usually it works well.
    But model was created and a new child figure wasn’t. Because editpart of the child wasn’t created.
    but especially if the same type object of parent and child, it doesn’t work(the child doesn’t exist visually).
    orz …

    always thank you.
    Heizel.

  4. Hello vainolo,

    thanks for this tutorial. I hope you can help me with the following question:

    When i build a container in front of some States, i can’t move or link my old States behind the container.
    How can i bring them to the front, without push these in the container or move itself.

    I hope you can help me.

    Thank you

    • Hi. I was unable to understand the problem you are experiencing. Can you explain some more or add a link to image or code?

      • Hello,
        i add the first object in my diagram, then i will add a second one in front of the first object and drag them large (it will be a container). Is it possible that i get the first object in front, so that i can work again with it?

        I think i can catch this object in refreshVisuals and add this after the second object again, but so far it doesn’t work.

        Thanks

  5. A second question concerns me:

    I’m creating a container, included in this another container with two objects. Now I want to add a link to the two objects, then there is a continuous loop until Eclipse has to be exited, or stopped.

    Can you determine this error also, hat someone a solution? Thank you

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.