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.