Skip to content

Creating a GEF Editor – Part 11: Creating Link Bendpoints

Last updated on 2014-09-07

Previous Tutorial: Creating a GEF Editor – Part 10: Deleting Connections and Fixing of Thing Delete Command
When I used tools to automatically create UML Class diagrams, one of the things that bothered the most was the way the connections were spread all over the diagram, and the editor didn’t allow me to organize them as I wanted to. So I had a very nice (and very large) diagram that I could show to my managers but which I couldn’t understand. Therefore one of the basic requirements of my links is the ability to organize them (move, bend, etc) as I like. Gratefully, GEF provides a simple way to do this using Bendpoints. A link that can be bend contains a list of bendpoints that define the points were the link bends. This provides a lot of freedom for the modeler, just as I like it.
Creating (or modifying) links to support bendpoints requires a number of things. First of all, each link exhibits a ConnectionRouter that manages how the link is drawn on the diagram. GEF provides a number of ConnectionRouters like FanRouter, BendpointRouter and others. To use the bendpoint capability, we must use the BendpointRouter as the router for the link (PolylineConnection classes have a NullConnectionRouter by default). Secondly, we must implement the usual GEF classes used to interact with the digram: a EditPolicy and a Command.

  1. We’ll first implement the three commands needed to interact with the link’s bendpoints: creating, moving and deleteing bendpoints. In our model, the bendpoints are stored in the OPMLink and are used by the OPMLinkEditPart to draw the link in the diagram. Note that for all commands to work, the bendpoint list in the Connection must be ordered exactly as in the list in the model, because the bendpoints are index managed.
    1. A bendpoint is creating by adding it to the list of bendpoints of the model link.
      package com.vainolo.phd.opm.gef.editor.command;
      
      import org.eclipse.draw2d.geometry.Point;
      import org.eclipse.gef.commands.Command;
      import com.vainolo.phd.opm.model.OPMLink;
      
      /**
       * Command used to create a new bendpoint in a {@linkplain OPMLink}.
       * This class is declared final since it has a very specific functionality.
       * @author vainolo
       *
       */
      public final class OPMLinkCreateBendpointCommand extends Command {
      
          /** Index on which the new bendpoint is added. */
          private int index;
          /** Location of new bendpoint. */
          private Point location;
          /** Link to which the bendpoint is added. */
          private OPMLink link;
      
          @Override public void execute() {
              link.getBendpoints().add(index, location);
          }
      
          @Override public void undo() {
              link.getBendpoints().remove(index);
          }
      
          /**
           * Set the index on which the bendpoint is added.
           * @param index Index on which the bendpoint should be added.
           */
          public void setIndex(final int index) {
              this.index = index;
              //TODO:validation checks.
          }
      
          /**
           * Set the location where the new bendpoint is added.
           * @param location point in the diagram where the new bendpoint
           * is added.
           */
          public void setLocation(final Point location) {
              this.location = location;
          }
      
          /**
           * Set the link on which the new bendpoint is added.
           * @param link link on which the bendpoint is added.
           */
          public void setOPMLink(final OPMLink link) {
              this.link = link;
          }
      }
      
    2. A bendpoint is moved by changing the bendpoint in the bendpoint list.
      package com.vainolo.phd.opm.gef.editor.command;
      
      import org.eclipse.draw2d.geometry.Point;
      import org.eclipse.gef.commands.Command;
      import com.vainolo.phd.opm.model.OPMLink;
      
      /**
       * Move a link bendpoint.
       * This class is declared final since it has a very specific functionality.
       * @author vainolo
       */
      public final class OPMLinkMoveBendpointCommand extends Command {
      	
      	/** Old location of the moved bendpoint. */
      	private Point oldLocation;
      	/** New location of the moved bendpoint. */
      	private Point newLocation;
      	/** Index of the bendpoint in the link's bendpoint list. */
      	private int index;
      	/** Link that contains the bendpoint. */
      	private OPMLink link;
      	
      	/** Move the bendpoint to the new location. */
      	public void execute() {
      		if(oldLocation == null) {
      			oldLocation = link.getBendpoints().get(index);
      		}
      		link.getBendpoints().set(index, newLocation);
      	}
      	
      	/** Restore the old location of the bendpoint. */
      	@Override public void undo() {
      		link.getBendpoints().set(index, oldLocation);
      	}
      
      	/** 
      	 * Set the index where the bendpoint is located in the bendpoint list.
      	 * @param index the index where the bendpoint is located. 
      	 */
      	public void setIndex(final int index) {
      		this.index = index;
      	}
      	
      	/**
      	 * Set the link where the bendpoint is located. 
      	 * @param link the link where the bendpoint is located. 
      	 */
      	public void setOPMLink(final OPMLink link) {
      		this.link = link;
      	}
      	
      	/**
      	 * Set the new location of the bendpoint. 
      	 * @param newLocation the new location of the bendpoint. 
      	 */
      	public void setLocation(final Point newLocation) {
      		this.newLocation = newLocation;
      	}
      }
      
    3. Finally, a bendpoint is deleted by removing it from the bendpoint list.
      package com.vainolo.phd.opm.gef.editor.command;
      
      import org.eclipse.draw2d.geometry.Point;
      import org.eclipse.gef.commands.Command;
      import com.vainolo.phd.opm.model.OPMLink;
      
      /**
       * Command used to delete a bendpoint from a {@link OPMLink}
       * This class is declared final since it has a very specific functionality.
       * @author vainolo
       */
      public final class OPMLinkDeleteBendpointCommand extends Command {
      
      	/** Link that contains the bendpoint. */
      	private OPMLink link;
      	/** Index where the bendpoint is located in the link's bendpoin list. */
      	private int index;
      	/** Point in the diagram where the bendpoint is located. */
      	private Point location;
      	
      	/**
      	 * Only execute is link is not null and index is valid.
      	 */
      	@Override public boolean canExecute() {
      		return (link != null) && (link.getBendpoints().size() > index);
      	}
      	/** 
      	 * Remove the bendpoint from the link. 
      	 */
      	@Override public void execute() {
      		location = link.getBendpoints().get(index);
      		link.getBendpoints().remove(index);
      	}
      
      	/**
      	 * Reinsert the bendpoint in the link.
      	 */
      	@Override public void undo() {
      		link.getBendpoints().add(index, location);
      	}
      	
      	/**
      	 * Set the index of the bendpoint that should be removed.
      	 * @param index the index of the bendpoint to remove.
      	 */
      	public void setIndex(final int index) {
      		this.index = index;
      	}
      	
      	/**
      	 * Set the link from which the bendpoint is removed.
      	 * @param link the link from which the bendpoint is removed.
      	 */
      	public void setOPMLink(final OPMLink link) {
      		this.link = link;
      	}	
      }
      
  2. We now create a BendpointEditPolicy to install in the Connection.
    package com.vainolo.phd.opm.gef.editor.policy;
    
    import org.eclipse.draw2d.geometry.Point;
    import org.eclipse.gef.commands.Command;
    import org.eclipse.gef.editpolicies.BendpointEditPolicy;
    import org.eclipse.gef.requests.BendpointRequest;
    
    import com.vainolo.phd.opm.gef.editor.command.OPMLinkCreateBendpointCommand;
    import com.vainolo.phd.opm.gef.editor.command.OPMLinkDeleteBendpointCommand;
    import com.vainolo.phd.opm.gef.editor.command.OPMLinkMoveBendpointCommand;
    import com.vainolo.phd.opm.model.OPMLink;
    
    /**
     * Policy used by the {@link OPMLink} to manage link bendpoints. 
     * @author vainolo
     *
     */
    public class OPMLinkBendpointEditPolicy extends BendpointEditPolicy {
    
    	/**
    	 * {@inheritDoc}
    	 */
    	@Override protected Command getCreateBendpointCommand(final BendpointRequest request) {
    		OPMLinkCreateBendpointCommand command = new OPMLinkCreateBendpointCommand();
    		
    		Point p = request.getLocation();
    		
    		command.setOPMLink((OPMLink) request.getSource().getModel());
    		command.setLocation(p);
    		command.setIndex(request.getIndex());
    		
    		return command;
    	}
    
    	/**
    	 * {@inheritDoc}
    	 */
    	@Override protected Command getMoveBendpointCommand(final BendpointRequest request) {
    		OPMLinkMoveBendpointCommand command = new OPMLinkMoveBendpointCommand();
    		
    		Point p = request.getLocation();
    		
    		command.setOPMLink((OPMLink) request.getSource().getModel());
    		command.setLocation(p);
    		command.setIndex(request.getIndex());
    		
    		return command;
    	}
    
    	/**
    	 * {@inheritDoc}
    	 */
    	@Override protected Command getDeleteBendpointCommand(final BendpointRequest request) {
    		OPMLinkDeleteBendpointCommand command = new OPMLinkDeleteBendpointCommand();
    		
    		command.setOPMLink((OPMLink)request.getSource().getModel());
    		command.setIndex(request.getIndex());
    		return command;
    	}
    }
    
  3. Lastly, we update the OPMLinkEditPart class setting a BendpointConnectionRouter as the link figure’s router, modifying the refreshVisuals method to use the bendpoints in the model when drawing the connection, and adding model notification mechanisms so that changes in the model (cased by Command execution) are displayed on the diagram.
    package com.vainolo.phd.opm.gef.editor.part;
    
    import java.util.ArrayList;
    import java.util.List;
    
    import org.eclipse.draw2d.AbsoluteBendpoint;
    import org.eclipse.draw2d.BendpointConnectionRouter;
    import org.eclipse.draw2d.Connection;
    import org.eclipse.draw2d.IFigure;
    import org.eclipse.draw2d.PolylineConnection;
    import org.eclipse.draw2d.geometry.Point;
    import org.eclipse.emf.common.notify.Adapter;
    import org.eclipse.emf.common.notify.Notification;
    import org.eclipse.emf.common.notify.Notifier;
    import org.eclipse.gef.EditPolicy;
    import org.eclipse.gef.editparts.AbstractConnectionEditPart;
    import org.eclipse.gef.editpolicies.ConnectionEndpointEditPolicy;
    
    import com.vainolo.phd.opm.gef.editor.policy.OPMLinkBendpointEditPolicy;
    import com.vainolo.phd.opm.gef.editor.policy.OPMLinkConnectionEditPolicy;
    import com.vainolo.phd.opm.model.OPMLink;
    
    /**
     * {@link EditPart} for the {@link OPMLink} model element.
     * @author vainolo
     */
    public class OPMLinkEditPart extends AbstractConnectionEditPart {
    
    	private OPMLinkAdapter adapter;
    	
    	/**
    	 * Create and initialize a new {@link OPMLinkEditPart}.
    	 */
    	public OPMLinkEditPart() {
    		super();
    		adapter = new OPMLinkAdapter();
    	}
    	
    	/**
    	 * Installs two edit policies:
    	 * <ol>
    	 *   <li>For the {@link EditPolicy#CONNECTION_ENDPOINTS_ROLE} a {@link ConnectionEndpoinEditPolicy}.</li>
    	 *   <li>For the {@link EditPolicy#CONNECTION_ROLE} a {@link OPMLinkConnectionEditPolicy}.</li>
    	 *   <li>For the {@link EditPolicy#CONNECTION_BENDPOINTS_ROLE} a {@link OPMLinkBendpointEditPolicy}.</li>
    	 * </ol>
    	 */
    	@Override protected void createEditPolicies() {
    		installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE, new ConnectionEndpointEditPolicy());
    		installEditPolicy(EditPolicy.CONNECTION_ROLE, new OPMLinkConnectionEditPolicy());
    		installEditPolicy(EditPolicy.CONNECTION_BENDPOINTS_ROLE, new OPMLinkBendpointEditPolicy());
    	}
    
    	@Override protected IFigure createFigure() {
    		PolylineConnection conn = new PolylineConnection();
    		conn.setConnectionRouter(new BendpointConnectionRouter());
    		return conn; 
    	}
    	
    	@Override protected void refreshVisuals() {
    		Connection connection = getConnectionFigure();
    		List<Point> modelConstraint = ((OPMLink)getModel()).getBendpoints();
    		List<AbsoluteBendpoint> figureConstraint = new ArrayList<AbsoluteBendpoint>();
    		for (Point p : modelConstraint) {
    			figureConstraint.add(new AbsoluteBendpoint(p));
    		}
    		connection.setRoutingConstraint(figureConstraint);
    	}
    	
    	@Override public void activate() {
    		if(!isActive()) {
    			((OPMLink)getModel()).eAdapters().add(adapter);
    		}
    		super.activate();
    	}
    
    	@Override public void deactivate() {
    		if(isActive()) {
    			((OPMLink)getModel()).eAdapters().remove(adapter);
    		}
    		super.deactivate();
    	}	
    	
    	public class OPMLinkAdapter implements Adapter {
    
    		@Override public void notifyChanged(Notification notification) {
    			refreshVisuals();
    		}
    
    		@Override public Notifier getTarget() {			
    			return (OPMLink)getModel();
    		}
    
    		@Override public void setTarget(Notifier newTarget) {
    			// Do nothing.
    		}
    
    		@Override public boolean isAdapterForType(Object type) {
    			return type.equals(OPMLink.class);
    		}
    	}
    }
    
  4. We are finished. Look at how the bendpoints look in my diagram:

You can find the final project files here.

There is a lot more to come, so stay tuned.

Next Tutorial: Creating a GEF Editor – Part 12: Enable Save Action on the Editor

Published inProgramming

2 Comments

  1. Anna Anna

    I don’t get it… how can the OPMLink from your model have a list of org.eclipse.draw2d.geometry.Point as attribute? How could you have declared that in your model?

Leave a Reply

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

Discover more from Musings of a Strange Loop

Subscribe now to keep reading and get access to the full archive.

Continue reading