Tag Archives: graph

Creating a GEF Editor – Part 10: Deleting Connections and Fixing of Thing Delete Command

Previous Tutorial: Creating a GEF Editor – Part 9: Connections

This tutorial continues the development of our OPM editor, adding a rarely useful functionality: deleting links (only used by dumb people like me who never get things right the first time :-) ). Furthermore, we must fix the Command used to delete things, since it does not deal with links at all (try deleting a thing that has a connecting link). Let’s get to work.

  1. The code to delete an OPMLink is fairly similar to the code used to delete an OPMThing only that a ConnectionEditPart uses a EditPolicy.CONNECTION_ROLE and a ConnectionEditPolicy to handle delete requests, instead of the EditPolicy.COMPONENT_ROLE and ComponentEditPolicy used by the OPMThingEditPart (Reading the code again just now, the javadoc of COMPONENT_ROLE states that this role can be used for any component of the graph, so maybe this would also work for a link. Added a TODO mark in the code to check this later). As usual, we’ll first code the Command, then the EditPolicy and finally change the EditPart so that the code always compiles cleanly
  2. The command to delete a link is pretty simple, just store all the information needed to undo the Command and detach the link from source, target and owner OPD:
    package com.vainolo.phd.opm.gef.editor.command;
    
    import org.eclipse.gef.commands.Command;
    
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMLink;
    import com.vainolo.phd.opm.model.OPMThing;
    
    /**
     * Command used to delete a link.
     * @author vainolo
     */
    public class OPMLinkDeleteCommand extends Command {
        /** Link to be deleted. */
        private OPMLink link;
        /** OPD that owns the link. */
        private OPMObjectProcessDiagram opd;
        /** Source of the link. */
        private OPMThing source;
        /** Target of the link. */
        private OPMThing target;
    
        /**
         * {@inheritDoc}
         */
        @Override public boolean canExecute() {
            return link != null;
        }
    
        /**
         * Disconnect link from source and target things and remove
         * from owner OPD.
         */
        @Override public void execute() {
            opd = link.getOpd();
            source = link.getSource();
            target = link.getTarget();
    
            link.setSource(null);
            link.setTarget(null);
            link.setOpd(null);
        }
    
        /**
         * Reconnect the link to the source and target and add
         * it to the owner OPD.
         */
        @Override public void undo() {
            link.setSource(source);
            link.setTarget(target);
            link.setOpd(opd);
        }
    
        /**
         * Set the link that will be delete from the diagram.
         * @param linkParam the link to delete from the diagram.
         */
        public void setLink(final OPMLink linkParam) {
            link = linkParam;
        }
    }
    
  3. The ConnectionEditPolicy is also very straightforward, creating and initializing the Command:
    package com.vainolo.phd.opm.gef.editor.policy;
    
    import org.eclipse.gef.editpolicies.ConnectionEditPolicy;
    import org.eclipse.gef.requests.GroupRequest;
    
    import com.vainolo.phd.opm.gef.editor.command.OPMLinkDeleteCommand;
    import com.vainolo.phd.opm.model.OPMLink;
    
    /**
     * Edit policy used by the OPMLink class to server delete requests.
     * @author vainolo
     *
     */
    public class OPMLinkConnectionEditPolicy extends ConnectionEditPolicy {
    
    	/**
    	 * Create a {@link OPMLinkDeleteCommand} and fill its details.
    	 * @param request the request that requires treatment.
    	 * @return a {@link OPMLinkDeleteCommand} that deletes a link from the model.
    	 */
    	@Override protected OPMLinkDeleteCommand getDeleteCommand(GroupRequest request) {
    		OPMLinkDeleteCommand command = new OPMLinkDeleteCommand();
    		command.setLink((OPMLink) getHost().getModel());
    		return command;
    	}
    }
    
  4. And to close things up, we install the new EditPolicy in the OPMLinkEditPart:
    package com.vainolo.phd.opm.gef.editor.part;
    
    import org.eclipse.draw2d.IFigure;
    import org.eclipse.draw2d.PolylineConnection;
    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.OPMLinkConnectionEditPolicy;
    
    /**
     * {@link EditPart} for the {@link OPMLink} model element.
     * @author vainolo
     */
    public class OPMLinkEditPart extends AbstractConnectionEditPart {
    
    	/**
    	 * Create and initialize a new {@link OPMLinkEditPart}.
    	 */
    	public OPMLinkEditPart() {
    		super();
    	}
    	
    	/**
    	 * 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>
    	 */
    	@Override protected void createEditPolicies() {
    		installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE, new ConnectionEndpointEditPolicy());
    		installEditPolicy(EditPolicy.CONNECTION_ROLE, new OPMLinkConnectionEditPolicy());		
    	}
    
    	@Override protected IFigure createFigure() {
    		PolylineConnection conn = new PolylineConnection();
    		return conn; 
    	}
    }
    
  5. That was all that we needed to delete the link from the diagram. Go ahead and check your results, there is nothing more pleasurable than this.
  6. Now we have to fix the Command that we used to delete OPMThing instances, so that it also removes the incoming and outgoing links from the graph. This would be very very simple if we were not interested in an undoable command, but we are interested, therefore it gets a bit more complicated. What we have to do is store all of the links that we deleted in the Command, also storing their source and target OPMThing. The undo method of the Command restores these connections to their original state (Note also that since the OPMLink is contained in the OPMObjectProcessDiagram we must also detach it on execute and reattach it on undo).
    package com.vainolo.phd.opm.gef.editor.command;
    
    import java.util.ArrayList;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    import org.eclipse.gef.commands.Command;
    
    import com.vainolo.phd.opm.model.OPMLink;
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMThing;
    
    /**
     * Command used to delete a thing.
     * The functionality of this class fairly closed so it is declared final. 
     * @author vainolo
     *
     */
    public final class OPMThingDeleteCommand extends Command {
    	
        /** Thing to be deleted. */
    	private OPMThing thing;
    	/** OPD that owns the thing. */
    	private OPMObjectProcessDiagram opd;
    	/** Incoming and outgoing links. */
    	private List<OPMLink> links;
    	/** Sources for the links that start or end at this thing. */
    	private Map<OPMLink, OPMThing> linkSources;
    	/** Targets for the links that start or end at this thing. */ 
    	private Map<OPMLink, OPMThing> linkTargets;
    
    	@Override
    	public void execute() {
    		detachLinks();
    		thing.setOpd(null);
    	}
    
    	@Override
    	public void undo() {
    		reattachLinks();
    		thing.setOpd(opd);
    	}
    
    	/**
    	 * Detach all links from the thing and from the other
    	 * connecting thing, storing the connection information in local
    	 * data structures.
    	 */
    	private void detachLinks() {
    		links = new ArrayList<OPMLink>();
    		linkSources = new HashMap<OPMLink, OPMThing>();
    		linkTargets = new HashMap<OPMLink, OPMThing>();
    		links.addAll(thing.getIncomingLinks());
    		links.addAll(thing.getOutgoingLinks());
    		for (OPMLink link : links) {
    			linkSources.put(link, link.getSource());
    			linkTargets.put(link, link.getTarget());
    			link.setSource(null);
    			link.setTarget(null);
    			link.setOpd(null);
    		}
    	}
    
    	/**
    	 * Reattach all links to their source and target things.
    	 */
    	private void reattachLinks() {
    		for (OPMLink link : links) {
    			link.setSource(linkSources.get(link));
    			link.setTarget(linkTargets.get(link));
    			link.setOpd(opd);
    		}
    	}
    	
    	/**
    	 * Set the thing to delete from the diagram.
    	 * @param thing the Thing to delete from the diagram.
    	 */
    	public void setThing(OPMThing thing) {
    		this.thing = thing;
    		this.opd = thing.getOpd();
    	}
    }
    
  7. That should close all things. You now have a fully operational GEF editor!

You can find the final project files here.

GEF rocks!!!

Next Tutorial: Creating a GEF Editor – Part 11: Creating Link Bendpoints

Creating a GEF Editor – Part 9: Connections

Previous Tutorial: Creating a GEF Editor – Part 8: Delete, Undo and Redo

Having a functional editor on which we can add figures is already pretty neat, but we are still missing one very important element in our editor: connections. Connections are generally used to describe some kind of relation between different entities in the diagram, and are shown as lines that connect two different figures. In this tutorial you will learn how to add basic GEF connections, and if it doesn’t get too long, we’ll also add to them some extra functionality. This is going to be fun! Enjoy.

  1. Knowing that we wanted to create connections in our editor, our model already contains an EClass that represents these connections, called OPMLink. An OPMLink simply points to a source and target OPMThing in the model (it also contains a list of bendpoints that we will use to draw our connection, but more on this later). So we don’t have to do anything to implement connections in our model. Whoppi!
  2. Now we’ll add to the editor a new EditPart called OPMLinkEditPart. An EditPart that is used as a connection implements the ConnectionEditPartinterface (either directly or by subclassing the AbstractConnectionEditPart class as we do), which requires the class to provide functionality to get/set source and target EditPart instances. Our first connection implementation is going to be very simple, having only a line that connects between to two diagram entities:
    package com.vainolo.phd.opm.gef.editor.part;
    
    import org.eclipse.draw2d.IFigure;
    import org.eclipse.draw2d.PolylineConnection;
    import org.eclipse.gef.EditPolicy;
    import org.eclipse.gef.editparts.AbstractConnectionEditPart;
    import org.eclipse.gef.editpolicies.ConnectionEndpointEditPolicy;
    
    public class OPMLinkEditPart extends AbstractConnectionEditPart {
    
    	public OPMLinkEditPart() {
    		super();
    	}
    	
    	@Override protected void createEditPolicies() {
    		installEditPolicy(EditPolicy.CONNECTION_ENDPOINTS_ROLE, new ConnectionEndpointEditPolicy());
    	}
    
    	@Override protected IFigure createFigure() {
    		PolylineConnection conn = new PolylineConnection();
    		return conn; 
    	}
    
    }
    
  3. Now we start all of the setup required for the connections. First we are going to add connection anchors to our OPMThing figures. Connection anchors define where the connection is connected to its endpoint (source or target EditPart). We use both ChopBoxAnchor and EllipseAnchor, which simply sets the anchor in the intersection point between the connection and the edge of the figure (this is better explained in pictures):
    • ChopBoxAnchor: will be used for OPMObject.
    • EllipseAnchor: will be used for OPMProcess.

    To add the anchors, we modify the OPMThingFigure interface adding it a getConnectionAnchor method:

    package com.vainolo.phd.opm.gef.editor.figure;
    
    import org.eclipse.draw2d.ConnectionAnchor;
    import org.eclipse.draw2d.IFigure;
    import org.eclipse.draw2d.Label;
    
    public interface OPMThingFigure extends IFigure {
    	public Label getNameLabel();
    	public ConnectionAnchor getConnectionAnchor();	
    }
    

    And we implement in both in the OPMObjectFigure and OPMProcessFigure:

    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.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());
    		rectangle = new RectangleFigure();
    		add(rectangle);
    		nameLabel = new Label();
    		add(nameLabel);
    	}
    
    	@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));
    		nameLabel.invalidate();		
    		rectangle.invalidate();
    	}
    	
    	public Label getNameLabel() {
    		return nameLabel;
    	}
    	
    	public ConnectionAnchor getConnectionAnchor() {
    		if (connectionAnchor == null) {
    			connectionAnchor = new ChopboxAnchor(this);
    		}
    		return connectionAnchor;
    	}
    }
    
    package com.vainolo.phd.opm.gef.editor.figure;
    
    import org.eclipse.draw2d.ConnectionAnchor;
    import org.eclipse.draw2d.Ellipse;
    import org.eclipse.draw2d.EllipseAnchor;
    import org.eclipse.draw2d.Figure;
    import org.eclipse.draw2d.Graphics;
    import org.eclipse.draw2d.Label;
    import org.eclipse.draw2d.XYLayout;
    import org.eclipse.draw2d.geometry.Rectangle;
    
    public class OPMProcessFigure extends Figure implements OPMThingFigure {
    	private Label nameLabel;
    	private Ellipse ellipse;
    	private ConnectionAnchor connectionAnchor;	
    	
    	public OPMProcessFigure() {
    		setLayoutManager(new XYLayout());
    		ellipse = new Ellipse();
    		add(ellipse);
    		nameLabel = new Label();
    		add(nameLabel);
    	}
    
    	@Override protected void paintFigure(Graphics graphics) {
    		Rectangle r = getBounds().getCopy();
    		setConstraint(ellipse, new Rectangle(0, 0, r.width, r.height));
    		setConstraint(nameLabel, new Rectangle(0, 0, r.width, r.height));
    		ellipse.invalidate();
    		nameLabel.invalidate();
    	}
    	
    	public Label getNameLabel() {
    		return nameLabel;
    	}
    	
    	public ConnectionAnchor getConnectionAnchor() {
    		if (connectionAnchor == null) {
    			connectionAnchor = new EllipseAnchor(this);
    		}
    		return connectionAnchor;
    	}		
    }
    
  4. The connection anchors we just created are used by the OPMThingEditPart when adding new connections to the diagram. I should show you this code now, but the thing is that I like to display full files (no “…” in the middle) and also to do things so that the code compiles at all times. So please bear with me for a moment. Since we will be adding new connections in the diagram, and diagram operations are done using a Command and an EditPolicy, we have to implement one of each. Implementing the Command is fairly simple:
    package com.vainolo.phd.opm.gef.editor.command;
    
    import org.eclipse.gef.commands.Command;
    
    import com.vainolo.phd.opm.model.OPMLink;
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMThing;
    
    public class OPMLinkCreateCommand extends Command {
    	
    	private OPMThing source;
    	private OPMThing target;
    	private OPMLink link;
    	private OPMObjectProcessDiagram opd;
    
    	@Override 
    	public boolean canExecute() {
    		return source != null && target != null && link != null;
    	}
    	
    	@Override public void execute() {
    		link.setSource(source);
    		link.setTarget(target);
    		link.setOpd(opd);
    	}
    
    	@Override public void undo() {
    		link.getSource().getOutgoingLinks().remove(link);
    		link.setSource(null);
    		link.getTarget().getIncomingLinks().remove(link);
    		link.setTarget(null);
    		link.setOpd(null);
    	}
    
    	public void setTarget(OPMThing target) {
    		this.target = target;
    	}
    	
    	public void setSource(OPMThing source) {
    		this.source = source;
    	}
    	
    	public void setLink(OPMLink link) {
    		this.link = link;
    	}
    	
    	public void setOPD(OPMObjectProcessDiagram opd) {
    		this.opd = opd;
    	}
    }
    

    The EditPolicy that creates this command is a subclass of GraphicalNodeEditPolicy, a policy used for creating and reconnecting connections. The Command is created when the user selects the source of the connection (by clicking on it after selecting a connection creation tool – which we will implement in a few more steps) and updated when the user select the target of the connection. In the middle of the process the Command cannot be executed because of the code we inserted in the canExecute method. I’m not sure if the framework tries to execute it right after the source is selected, but it seems that this can happen. It also seems possible to execute two different commands to create a link, one when the source is select and one when the target is selected, and I will investigate this in the future. But for now, this is the GraphicalNodeEditPolicy that we’ll use:

    package com.vainolo.phd.opm.gef.editor.policy;
    
    import org.eclipse.gef.commands.Command;
    import org.eclipse.gef.editpolicies.GraphicalNodeEditPolicy;
    import org.eclipse.gef.requests.CreateConnectionRequest;
    import org.eclipse.gef.requests.ReconnectRequest;
    
    import com.vainolo.phd.opm.gef.editor.command.OPMLinkCreateCommand;
    import com.vainolo.phd.opm.model.OPMLink;
    import com.vainolo.phd.opm.model.OPMThing;
    
    public class OPMThingGraphicalNodeEditPolicy extends GraphicalNodeEditPolicy {
    
    	@Override protected Command getConnectionCompleteCommand(CreateConnectionRequest request) {
    		OPMLinkCreateCommand result = (OPMLinkCreateCommand) request.getStartCommand();
    		result.setTarget((OPMThing)getHost().getModel());
    		return result;
    	}
    
    	@Override protected Command getConnectionCreateCommand(CreateConnectionRequest request) {
    		OPMLinkCreateCommand result = new OPMLinkCreateCommand();
    		result.setSource((OPMThing)getHost().getModel());
    		result.setLink((OPMLink) request.getNewObject());
    		result.setOPD(((OPMThing)getHost().getModel()).getOpd());
    		request.setStartCommand(result);
    		return result;
    	}
    
    	@Override protected Command getReconnectTargetCommand(ReconnectRequest request) {
    		return null;
    	}
    
    	@Override protected Command getReconnectSourceCommand(ReconnectRequest request) {
    		return null;
    	}
    }
    
  5. OK, we can now make all the changes to the OPMThingEditPart. First, we make it implement the NodeEditPart interface, which is used by other GEF classes (like AbstractConnectionEditPart) to better manage the connections in the diagram. This interface requires that the class provide ConnectionAnchor instances for the ConnectionEditPart instances which are connected to it, and also for connections that are currently being created (using Request instances). Second, we add the newly created OPMThingGraphicalNodeEditPolicy to the OPMThingEditPart. And last, we provide the GEF framework with a way to know which connections there are in the diagram by implementing the getModelSourceConnections and getModelTargetConnections methods, just like when we implemented the getModelChildren method in the OPMObjectProcessDiagramEditPart to make it know which model elements existed in our diagram. The GEF framework calls these methods and for each unique OPMLink instance that is found it creates a new OPMLinkEditPart and adds it to the diagram:
    package com.vainolo.phd.opm.gef.editor.part;
    
    import java.util.List;
    
    import org.eclipse.draw2d.ConnectionAnchor;
    import org.eclipse.draw2d.Label;
    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.ConnectionEditPart;
    import org.eclipse.gef.EditPolicy;
    import org.eclipse.gef.NodeEditPart;
    import org.eclipse.gef.Request;
    import org.eclipse.gef.RequestConstants;
    import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
    import org.eclipse.jface.viewers.TextCellEditor;
    
    import com.vainolo.phd.opm.gef.editor.figure.OPMThingFigure;
    import com.vainolo.phd.opm.gef.editor.policy.OPMThingComponentEditPolicy;
    import com.vainolo.phd.opm.gef.editor.policy.OPMThingDirectEditPolicy;
    import com.vainolo.phd.opm.gef.editor.policy.OPMThingGraphicalNodeEditPolicy;
    import com.vainolo.phd.opm.model.OPMLink;
    import com.vainolo.phd.opm.model.OPMThing;
    
    public abstract class OPMThingEditPart extends AbstractGraphicalEditPart implements NodeEditPart {
    
    	private OPMThingAdapter adapter;
    	
    	public OPMThingEditPart() {
    		super();
    		adapter = new OPMThingAdapter();
    	}
    	
    	@Override protected void createEditPolicies() {
    		installEditPolicy(EditPolicy.DIRECT_EDIT_ROLE, new OPMThingDirectEditPolicy());
    		installEditPolicy(EditPolicy.GRAPHICAL_NODE_ROLE, new OPMThingGraphicalNodeEditPolicy());
    		installEditPolicy(EditPolicy.COMPONENT_ROLE, new OPMThingComponentEditPolicy());
    	}
    
    	@Override protected List<OPMLink> getModelSourceConnections() {
    		OPMThing model = (OPMThing)getModel();
    		return model.getOutgoingLinks();
    	}
    
    	@Override protected List<OPMLink> getModelTargetConnections() {
    		OPMThing model = (OPMThing)getModel();
    		return model.getIncomingLinks();
    	}
    
    	@Override protected void refreshVisuals() {
    		OPMThingFigure figure = (OPMThingFigure)getFigure();
    		OPMThing model = (OPMThing)getModel();
    		OPMObjectProcessDiagramEditPart parent = (OPMObjectProcessDiagramEditPart) getParent();
    		
    		figure.getNameLabel().setText(model.getName());
    		parent.setLayoutConstraint(this, figure, model.getConstraints());
    	}
    
    	public ConnectionAnchor getSourceConnectionAnchor(ConnectionEditPart connection) {
    		return ((OPMThingFigure)getFigure()).getConnectionAnchor();
    	}
    
    	public ConnectionAnchor getTargetConnectionAnchor(ConnectionEditPart connection) {
    		return ((OPMThingFigure)getFigure()).getConnectionAnchor();
    	}
    
    	@Override public ConnectionAnchor getSourceConnectionAnchor(Request request) {
    		return ((OPMThingFigure)getFigure()).getConnectionAnchor();
    	}
    
    	@Override public ConnectionAnchor getTargetConnectionAnchor(Request request) {
    		return ((OPMThingFigure)getFigure()).getConnectionAnchor();
    	}	
    	
    	@Override public void activate() {
    		if(!isActive()) {
    			((OPMThing)getModel()).eAdapters().add(adapter);
    		}
    		super.activate();
    	}
    
    	@Override public void deactivate() {
    		if(isActive()) {
    			((OPMThing)getModel()).eAdapters().remove(adapter);
    		}
    
    		super.deactivate();
    	}
    	
    	@Override public void performRequest(Request req) {
    		if(req.getType() == RequestConstants.REQ_DIRECT_EDIT) {
    			performDirectEditing();
    		}
    	}
    	
    	private void performDirectEditing() {
    		Label label = ((OPMThingFigure)getFigure()).getNameLabel();
    		OPMThingDirectEditManager manager = new OPMThingDirectEditManager(this, TextCellEditor.class, new OPMThingCellEditorLocator(label), label);
    		manager.show();
    	}	
    	
    	public class OPMThingAdapter implements Adapter {
    
    		// Adapter interface
    		@Override public void notifyChanged(Notification notification) {
    			refreshVisuals();
    			refreshSourceConnections();
    			refreshTargetConnections();
    		}
    
    		@Override public Notifier getTarget() {
    			return (OPMThing)getModel();
    		}
    
    		@Override public void setTarget(Notifier newTarget) {
    			// Do nothing.
    		}
    
    		@Override public boolean isAdapterForType(Object type) {
    			return type.equals(OPMThing.class);
    		}
    	}
    }
    
  6. To finish the job, we must implement a factory that creates OPMLink instances and add a tool to the OPMGraphicalEditorPalette to handle the creation of new connections. First comes the factory:
    package com.vainolo.phd.opm.gef.editor.factory;
    
    import org.eclipse.gef.requests.CreationFactory;
    
    import com.vainolo.phd.opm.model.OPMFactory;
    import com.vainolo.phd.opm.model.OPMLink;
    
    public class OPMLinkFactory implements CreationFactory {
    
    	@Override public Object getNewObject() {
    		return OPMFactory.eINSTANCE.createOPMLink();
    	}
    
    	@Override public Object getObjectType() {
    		return OPMLink.class;
    	}
    
    }
    

    And second comes the tool:

    package com.vainolo.phd.opm.gef.editor;
    
    import org.eclipse.gef.palette.ConnectionCreationToolEntry;
    import org.eclipse.gef.palette.CreationToolEntry;
    import org.eclipse.gef.palette.PaletteGroup;
    import org.eclipse.gef.palette.PaletteRoot;
    import org.eclipse.gef.palette.SelectionToolEntry;
    
    import com.vainolo.phd.opm.gef.editor.factory.OPMLinkFactory;
    import com.vainolo.phd.opm.gef.editor.factory.OPMObjectFactory;
    import com.vainolo.phd.opm.gef.editor.factory.OPMProcessFactory;
    import com.vainolo.phd.opm.gef.editor.tool.CreationAndDirectEditTool;
    
    public class OPMGraphicalEditorPalette extends PaletteRoot {
    
    	PaletteGroup group;
    	
    	public OPMGraphicalEditorPalette() {
    		addGroup();
    		addSelectionTool();
    		addOPMObjectTool();
    		addOPMProcessTool();
    		addOPMLinkTool();
    	}
    	
    	private void addSelectionTool() {
    		SelectionToolEntry entry = new SelectionToolEntry();
    		group.add(entry);
    		setDefaultEntry(entry);
    	}
    	
    	private void addGroup() {
    		group = new PaletteGroup("OPM Controls");
    		add(group);
    	}
    	
    	private void addOPMObjectTool() {
    		CreationToolEntry entry = new CreationToolEntry("OPMObject", "Create a new Object", new OPMObjectFactory(), null, null);
    		entry.setToolClass(CreationAndDirectEditTool.class);		
    		group.add(entry);
    	}
    	
    	private void addOPMProcessTool() {
    		CreationToolEntry entry = new CreationToolEntry("OPMProcess", "Create a new Process", new OPMProcessFactory(), null, null);
    		entry.setToolClass(CreationAndDirectEditTool.class);		
    		group.add(entry);
    	}
    	
    	
    	private void addOPMLinkTool() {
    		ConnectionCreationToolEntry entry = new ConnectionCreationToolEntry("Link", "Creates a new link", new OPMLinkFactory(), null, null);
    		group.add(entry);
    	}	
    }
    
  7. One last thing (and thanks to Ouri for the comment), we have to add the OPMLink model to the OPMEditPartFactory so the edit parts are created by the framework when needed
    package com.vainolo.phd.opm.gef.editor.part;
    	
    import com.vainolo.phd.opm.model.OPMLink;
    import com.vainolo.phd.opm.model.OPMObject;
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMProcess;
    import org.eclipse.gef.EditPart;
    import org.eclipse.gef.EditPartFactory;
    	
    public class OPMEditPartFactory implements EditPartFactory {
    
        @Override public EditPart createEditPart(EditPart context, Object model) {
            EditPart part = null;
    			
            if(model instanceof OPMObjectProcessDiagram) {
                part = new OPMObjectProcessDiagramEditPart();
            } else if(model instanceof OPMObject) {
                part = new OPMObjectEditPart();
            } else if(model instanceof OPMProcess) {
                part = new OPMProcessEditPart();
            } else if(model instanceof OPMLink) {
                part = new OPMLinkEditPart();
            }
    			
            if(part != null) {
                part.setModel(model);
            }
    
            return part;
        }
    }
    
  8. Fire up your editor and add one or two links to it. Looks good, doesn’t it? But don’t get too exited because we still haven’t implemented connection removal so every link you add will stay until we do this… hopefully soon. We must also fix the command that removes things because it must also remove the connections… but it’s late and I think we’ve had enough for today.

You can find the final project files here.

I really hope you enjoyed this tutorial. So long, and thanks for all the fish :-) .

Next Tutorial: Creating a GEF Editor – Part 10: Deleting Connections and Fixing of Thing Delete Command

Creating a GEF Editor – Part 8: Delete, Undo and Redo

Previous Tutorial: Creating a GEF Editor – Part 7: Moving Elements and Direct Editing

In this tutorial we are going to add functionality to delete diagram entities and also to undo and redo editing operations. This is going to be a short tutorial because thankfully most of the functionality that we want to add only has to be configured in the framework.

  1. Delete functionality is implemented like all other functionality: we have to add a new Command to change the model, create a new EditPolicy (or extend an existing one) which creates the Command and install the command in the desired EditPart.
  2. First we implement the command, which is fairly simple. In EMF, a model instance that is disconnected from all of the model’s resource is not saved and therefore will get deleted (much like java garbage collection):
    package com.vainolo.phd.opm.gef.editor.command;
    
    import org.eclipse.gef.commands.Command;
    
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMThing;
    
    public class OPMThingDeleteCommand extends Command {
    
    	private OPMThing thing;
    	private OPMObjectProcessDiagram opd;
    
    	@Override
    	public void execute() {
    		thing.setOpd(null);
    	}
    
    	@Override
    	public void undo() {
    		thing.setOpd(opd);
    	}
    
    	public void setThing(OPMThing thing) {
    		this.thing = thing;
    		this.opd = thing.getOpd();
    	}
    }
    
  3. Request to delete diagram entities in GEF is handled by a ComponentEditPolicy:
    package com.vainolo.phd.opm.gef.editor.policy;
    
    import org.eclipse.gef.commands.Command;
    import org.eclipse.gef.editpolicies.ComponentEditPolicy;
    import org.eclipse.gef.requests.GroupRequest;
    
    import com.vainolo.phd.opm.gef.editor.command.OPMThingDeleteCommand;
    import com.vainolo.phd.opm.model.OPMThing;
    
    public class OPMThingComponentEditPolicy extends ComponentEditPolicy {
    
    	@Override protected Command createDeleteCommand(GroupRequest deleteRequest) {
    		OPMThingDeleteCommand thingDeleteCommand = new OPMThingDeleteCommand();
    		thingDeleteCommand.setThing((OPMThing) getHost().getModel());
    		return thingDeleteCommand;
    	}
    }
    
  4. And to finish, we install the new EditPolicy in the OPMThingEditPart:

    	@Override protected void createEditPolicies() {
    		installEditPolicy(EditPolicy.DIRECT_EDIT_ROLE, new OPMThingDirectEditPolicy());
    		installEditPolicy(EditPolicy.COMPONENT_ROLE, new OPMThingComponentEditPolicy());		
    	}
    
  5. You could try now to execute your editor and delete a diagram entity… But no matter how hard you press your Del button, nothing will happen. You see, delete actions are not regular actions that are applied to the model, but must be activated from the outside (for example from a menu, toolbar or key press). So we’ll be adding the delete action to the menu and toolbar of the eclipse workbench, and while we are at it we’ll also add undo and redo functionality
  6. Eclipse editors that want to provide actions to the menus and toolbars must provide a class which implements the IEditorActionBarContributor interface. GEF provides us with a basic implementation of this interface for use with our GEF editor, called ActionBarContributor (the comment in this class says that it is subject to change… but so is everything else in life. It would be nice to know why they think this class is going to change in the future). Since the three operations we are implementing are pretty standard editor operations, the framework also provides us with RetargetAction classes that can be used in the menus and toolbars.
    package com.vainolo.phd.opm.gef.editor;
    
    import org.eclipse.gef.ui.actions.ActionBarContributor;
    import org.eclipse.gef.ui.actions.DeleteRetargetAction;
    import org.eclipse.gef.ui.actions.RedoRetargetAction;
    import org.eclipse.gef.ui.actions.UndoRetargetAction;
    import org.eclipse.jface.action.IToolBarManager;
    import org.eclipse.ui.actions.ActionFactory;
    
    public class OPMGraphicalEditorActionBarContributor extends ActionBarContributor {
    
    	@Override
    	protected void buildActions() {
    		addRetargetAction(new UndoRetargetAction());
    		addRetargetAction(new RedoRetargetAction());
    		addRetargetAction(new DeleteRetargetAction());
    	}
    	
    	@Override
    	public void contributeToToolBar(IToolBarManager toolBarManager) {
    		super.contributeToToolBar(toolBarManager);
    		toolBarManager.add(getAction(ActionFactory.UNDO.getId()));
    		toolBarManager.add(getAction(ActionFactory.REDO.getId()));
    		toolBarManager.add(getAction(ActionFactory.DELETE.getId()));
    	}
    
    	@Override
    	protected void declareGlobalActionKeys() {
    		// Do nothing. This is an abstract function in the parent class.
    	}
    }
    

    The buildActions initializes the actions that are provided by our editor (and also makes them available in the menus). After they are initialized, we can also add them to the toolbar using the contributeToToolBar method.

  7. Last thing we need to do is connect action contributor class we just created to the editor. This is done in the plugin definition file, in the same place we defined the model editor, setting our class as the OPM GEF Editor’s contributorClass:
  8. That’s it. Execute your eclipse project and mode some things around. The Undo action should be available right after you do this. Undo something and now the Redo action should be available. Select a thing and delete it. Works? great!

You can find the final project files here.

As usual, hope you enjoyed this tutorial. Next in line: implementing connections between things in the model. Don’t miss it :-) .

Next Tutorial: Creating a GEF Editor – Part 9: Connections

Creating a GEF Editor – Part 6: Model Refactoring and Editing Diagram Entities

Previous Tutorial: Creating a GEF Editor – Part 5: Loading the Model from an EMF File

We finished the last tutorial with a working “editor” on which not much editing could be done… So in this tutorial we’ll be adding some editing capabilities to the GEF editor.
But before this, while preparing this tutorial I saw that doing some refactoring to the model we could reduce duplicate code, which is always good. So I did a full refactoring of the model, from which we will start this tutorial (I also renamed the ObjectProcessDiagram class to OPMObjectProcessDiagram for consistency). For your convenience, the new .ecore file can be downloaded from here. Replace your current .ecore file with this file and do “Generate All” on your .genmodel file. This will probably generate some compile errors on your project, but they can be fixed pretty easily. Furthermore, delete the (generated) ObjectProcessDiagram.java file in the modeling project. This will cause more compiler problems that can also be easily fixed, and after this your code should be clean and ready to go (as you see, model refactoring is not yet at the level of code refactoring and has a long way to go… which is pretty bad because it makes developer think too much about the model because model changes are expensive. But NEVER be afraid of refactoring, just do it with caution and with a backup/version control system at hand). You can also download the code (all eclipse projects including generated code) from which the tutorial starts from here.
BTW, I also upgraded my eclipse to the latest eclipse Indigo release, modeling tools package and impressively, things worked just fine. So lets get working.

  1. First thing we are going to do is add the OPMProcess model entity to our editor. Looking at the model we see that the OPMObject and OPMProcess classes share most (actually all) of their attributes… so why duplicate? basically this is because the real OPMObject and OPMProcess contain many more attributes that do differentiate them. But this sharing of information makes us think that also the classes used by GEF should have some hierarchy so that code is not duplicated. So we are going to create an interface OPMThingFigure (for the OPMObjectFigure and OPMProcessFigure classes) and an abstract class OPMThingEditPart (for the OPMObjectEditPart and OPMProcessEditPart). In the first case we had to use an interface instead of an abstract class because we had some problems with draw2d painting order and instancing order between the super-class and the sub-class (or something like that… I should have written that down :-( ), which made the code look pretty ugly.
    package com.vainolo.phd.opm.gef.editor.figure;
    
    import org.eclipse.draw2d.IFigure;
    import org.eclipse.draw2d.Label;
    
    public interface OPMThingFigure extends IFigure {
    	public Label getNameLabel();
    }
    
    package com.vainolo.phd.opm.gef.editor.part;
    
    import org.eclipse.gef.editparts.AbstractGraphicalEditPart;
    
    import com.vainolo.phd.opm.gef.editor.figure.OPMThingFigure;
    import com.vainolo.phd.opm.model.OPMThing;
    
    public abstract class OPMThingEditPart extends AbstractGraphicalEditPart {
    	@Override protected void refreshVisuals() {
    		OPMThingFigure figure = (OPMThingFigure)getFigure();
    		OPMThing model = (OPMThing)getModel();
    		OPMObjectProcessDiagramEditPart parent = (OPMObjectProcessDiagramEditPart) getParent();
    
    		figure.getNameLabel().setText(model.getName());
    		parent.setLayoutConstraint(this, figure, model.getConstraints());
    	}
    }
    

    Now remove the refreshVisuals method from the OPMObjectEditPart class and make the class extend OPMThingEditPart. Also make OPMObjectFigure implement OPMThingFigure and rename the getLabel method to getNameLabel to make them compatible with the interface.

    package com.vainolo.phd.opm.gef.editor.figure;
    
    import org.eclipse.draw2d.Figure;
    import org.eclipse.draw2d.Graphics;
    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 label;
    	private RectangleFigure rectangle;
    
    	public OPMObjectFigure() {
    		setLayoutManager(new XYLayout());
    		rectangle = new RectangleFigure();
    		add(rectangle);
    		label = new Label();
    		add(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));
    	}
    
    	public Label getNameLabel() {
    		return label;
    	}
    }
    
    package com.vainolo.phd.opm.gef.editor.part;
    
    import org.eclipse.draw2d.IFigure;
    
    import com.vainolo.phd.opm.gef.editor.figure.OPMObjectFigure;
    
    public class OPMObjectEditPart extends OPMThingEditPart {
    
    	@Override
    	protected IFigure createFigure() {
    		return new OPMObjectFigure();
    	}
    
    	@Override
    	protected void createEditPolicies() {
    		// TODO Auto-generated method stub
    
    	}
    }
    

    The editor should still be working as it did before this refactoring (try it just in case…)

  2. We’ll add now the OPMProcess to our editor. This is done much like we did for OPMObject: create a figure which represent the model object and create an edit part to manage that figure:
    package com.vainolo.phd.opm.gef.editor.figure;
    
    import org.eclipse.draw2d.ConnectionAnchor;
    import org.eclipse.draw2d.Ellipse;
    import org.eclipse.draw2d.EllipseAnchor;
    import org.eclipse.draw2d.Figure;
    import org.eclipse.draw2d.Graphics;
    import org.eclipse.draw2d.Label;
    import org.eclipse.draw2d.XYLayout;
    import org.eclipse.draw2d.geometry.Rectangle;
    
    public class OPMProcessFigure extends Figure implements OPMThingFigure {
    	private Label nameLabel;
    	private Ellipse ellipse;
    	private ConnectionAnchor connectionAnchor;
    
    	public OPMProcessFigure() {
    		setLayoutManager(new XYLayout());
    		ellipse = new Ellipse();
    		add(ellipse);
    		nameLabel = new Label();
    		add(nameLabel);
    	}
    
    	@Override protected void paintFigure(Graphics graphics) {
    		Rectangle r = getBounds().getCopy();
    		setConstraint(ellipse, new Rectangle(0, 0, r.width, r.height));
    		setConstraint(nameLabel, new Rectangle(0, 0, r.width, r.height));
    		ellipse.invalidate();
    		nameLabel.invalidate();
    	}
    
    	public Label getNameLabel() {
    		return nameLabel;
    	}
    
    	public ConnectionAnchor getConnectionAnchor() {
    		if (connectionAnchor == null) {
    			connectionAnchor = new EllipseAnchor(this);
    		}
    		return connectionAnchor;
    	}
    }
    
    package com.vainolo.phd.opm.gef.editor.part;
    
    import org.eclipse.draw2d.IFigure;
    
    import com.vainolo.phd.opm.gef.editor.figure.OPMProcessFigure;
    
    public class OPMProcessEditPart extends OPMThingEditPart {
    
    	@Override protected IFigure createFigure() {
    		return new OPMProcessFigure();
    	}
    }
    
  3. Now for some editing functionality. The first thing we are going to do is let the user add new entities to the diagram. This is a bit more complicated than other editing capabilities but it gives you (well, at least it gave me) much satisfaction. Before we delve into the code, it is good to have at least a superficial understanding on what we will be doing.
    The way to add new entities to a GEF diagram is by using palette tools, where the user select the tool and then clicks on the diagram to add the figure. In GEF, creation of new entities is done using a CreationToolEntry that when activated (I’m not sure if this is when the tool is selected or when the user clicks on the diagram), creates a new model entity (using a provided factory) and fires a CreateRequest on the EditPart that is represented by the figure currently below the mouse. CreateRequests are handled by a LayoutEditPolicy instance that is installed in the EditPart on which the new entities are added, in our case we use an XYLayoutEditPolicy because the OPMObjectProcessDiagram uses an FreeformLayout which is a subclass of XYLayout. The XYLayoutPolicy creates a Command that is executed and causes the model to change.
    But this is only one part of the trip. After the model is edited, we (probably) need to repaint the diagram in some way. In our case, we need to inform the OPMObjectProcessModelEditPart that there has been a change to its children and that it must repaint them. Here is one of the cases where EMF saves us times with ready-built notifiers that can be used in this case. We create an Adapter class inside the OPMObjectProcessDiagramEditPart that listens to changes to the model and refreshes the view when changes are detected (an Adapter is regularly called an Observer in java, but for some reason I still do not understand the EMF developers decided to name it differently, creating some confusion since they are implementing an Observer design pattern and not an Adapter design pattern. I really hope they have good reasons).
    There are a lot of code changes, so we’ll do this slowly. As I always like, changes are coded so that at all times our code compiles.
  4. First thing we do is create a new Command to be executed by the framework to add a new entity to the diagram:
    package com.vainolo.phd.opm.gef.editor.command;
    
    import org.eclipse.draw2d.geometry.Dimension;
    import org.eclipse.draw2d.geometry.Point;
    import org.eclipse.draw2d.geometry.Rectangle;
    import org.eclipse.gef.commands.Command;
    
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMThing;
    
    public class OPMThingCreateCommand extends Command {
    
    	private static final Dimension defaultDimension = new Dimension(50, 50);
    	private static final String defaultName = "<...>";
    
    	private OPMThing newThing;
    	private Rectangle constraints;
    	private OPMObjectProcessDiagram opd;
    
    	@Override public void execute() {
    		newThing.setName(defaultName);
    		if(constraints != null) {
    			newThing.setConstraints(constraints);
    		}
    		newThing.setOpd(opd);
    	}
    
    	@Override public void undo() {
    		newThing.setOpd(null);
    	}
    
    	public void setLocation(Point location) {
    		constraints = new Rectangle(location, defaultDimension);
    	}
    
    	public void setParent(OPMObjectProcessDiagram opd) {
    		this.opd = opd;
    	}
    
    	public void setThing(OPMThing newThing) {
    		this.newThing = newThing;
    	}
    }
    
  5. Now we create a the XYLayoutPolicy that will be installed in the OPMObjectProcessDiagramEditPart to handle the creation of new entities:
    package com.vainolo.phd.opm.gef.editor.policy;
    
    import org.eclipse.gef.commands.Command;
    import org.eclipse.gef.editpolicies.XYLayoutEditPolicy;
    import org.eclipse.gef.requests.CreateRequest;
    
    import com.vainolo.phd.opm.gef.editor.command.OPMThingCreateCommand;
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMThing;
    
    public class OPMObjectProcessDiagramXYLayoutPolicy extends XYLayoutEditPolicy {
    
    	@Override protected Command getCreateCommand(CreateRequest request) {
    		Command retVal = null;
    		if(request.getNewObject() instanceof OPMThing) {
    			OPMThingCreateCommand command = new OPMThingCreateCommand();
    			command.setLocation(request.getLocation());
    			command.setParent((OPMObjectProcessDiagram)(getHost().getModel()));
    			command.setThing((OPMThing)(request.getNewObject()));
    			retVal = command;
    		}
    		return retVal;
    	}
    }
    
  6. Now we do two changes in the OPMObjectProcessDiagramEditPart: we install the new EditPolicy and we add a model listener so the class is notified of model changes and can reflect them in the editor if desired. We must also add creation of a OPMProcessEditPart in our OPMEditPartFactory for the sake of completeness:
    package com.vainolo.phd.opm.gef.editor.part;
    
    import java.util.ArrayList;
    import java.util.List;
    import org.eclipse.draw2d.FreeformLayer;
    import org.eclipse.draw2d.FreeformLayout;
    import org.eclipse.draw2d.IFigure;
    import org.eclipse.draw2d.LineBorder;
    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.AbstractGraphicalEditPart;
    
    import com.vainolo.phd.opm.gef.editor.policy.OPMObjectProcessDiagramXYLayoutPolicy;
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMThing;
    
    public class OPMObjectProcessDiagramEditPart extends AbstractGraphicalEditPart {
    
    	private OPMObjectProcessDiagramAdapter adapter;
    
    	public OPMObjectProcessDiagramEditPart() {
    		super();
    		adapter = new OPMObjectProcessDiagramAdapter();
    	}
    
    	@Override
    	protected IFigure createFigure() {
    		FreeformLayer layer = new FreeformLayer();
    		layer.setLayoutManager(new FreeformLayout());
    		layer.setBorder(new LineBorder(1));
    		return layer;
    	}
    
    	@Override
    	protected void createEditPolicies() {
    		installEditPolicy(EditPolicy.LAYOUT_ROLE, new OPMObjectProcessDiagramXYLayoutPolicy());
    	}
    
    	@Override protected List<OPMThing> getModelChildren() {
    		List<OPMThing> retVal = new ArrayList<OPMThing>();
    		OPMObjectProcessDiagram opd = (OPMObjectProcessDiagram) getModel();
    		retVal.addAll(opd.getThings());
    		return retVal;
    	}
    
    	@Override public void activate() {
    		if(!isActive()) {
    			((OPMObjectProcessDiagram)getModel()).eAdapters().add(adapter);
    		}
    		super.activate();
    	}
    
    	@Override public void deactivate() {
    		if(isActive()) {
    			((OPMObjectProcessDiagram)getModel()).eAdapters().remove(adapter);
    		}
    		super.deactivate();
    	}
    
    	public class OPMObjectProcessDiagramAdapter implements Adapter {
    
    		@Override public void notifyChanged(Notification notification) {
    			refreshChildren();
    		}
    
    		@Override public Notifier getTarget() {
    			return (OPMObjectProcessDiagram)getModel();
    		}
    
    		@Override public void setTarget(Notifier newTarget) {
    			// Do nothing.
    		}
    
    		@Override public boolean isAdapterForType(Object type) {
    			return type.equals(OPMObjectProcessDiagram.class);
    		}
    	}
    
    }
    

    We created an inner class to handle model updates since it is the cleanest way we found that we could do it. Implementing the Adapter interface directly caused name collisions with functions used by the NodeEditPart interface which will be used later when we add connections between the model entities, and doing this in an external class required exposing the refreshChildren which is protected.
    Now the modifications to the OPMEditPartFactory:

    package com.vainolo.phd.opm.gef.editor.part;
    
    import com.vainolo.phd.opm.model.OPMObject;
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMProcess;
    
    import org.eclipse.gef.EditPart;
    import org.eclipse.gef.EditPartFactory;
    
    public class OPMEditPartFactory implements EditPartFactory {
    
    	@Override public EditPart createEditPart(EditPart context, Object model) {
    		EditPart part = null;
    
    		if(model instanceof OPMObjectProcessDiagram) {
    			part = new OPMObjectProcessDiagramEditPart();
    		} else if(model instanceof OPMObject) {
    			part = new OPMObjectEditPart();
    		} else if(model instanceof OPMProcess) {
    			part = new OPMProcessEditPart();
    		}
    
    		if(part != null) {
    			part.setModel(model);
    		}
    
    		return part;
    	}
    }
    
  7. We are almost done. The creation tools that we define in GEF work on model entities and for this they must be able to create new model classes instances on demand, therefore they require factory classes to provide them with this functionality. For this purpose we create two new classes: an OPMObjectFactory and a OPMProcessFactory.
    package com.vainolo.phd.opm.gef.editor.factory;
    
    import org.eclipse.gef.requests.CreationFactory;
    
    import com.vainolo.phd.opm.model.OPMFactory;
    import com.vainolo.phd.opm.model.OPMObject;
    
    public class OPMObjectFactory implements CreationFactory {
    
    	@Override public Object getNewObject() {
    		return OPMFactory.eINSTANCE.createOPMObject();
    	}
    
    	@Override public Object getObjectType() {
    		return OPMObject.class;
    	}
    
    }
    
    package com.vainolo.phd.opm.gef.editor.factory;
    
    import org.eclipse.gef.requests.CreationFactory;
    
    import com.vainolo.phd.opm.model.OPMFactory;
    import com.vainolo.phd.opm.model.OPMProcess;
    
    public class OPMProcessFactory implements CreationFactory {
    
    	@Override public Object getNewObject() {
    		return OPMFactory.eINSTANCE.createOPMProcess();
    	}
    
    	@Override public Object getObjectType() {
    		return OPMProcess.class;
    	}
    
    }
    
  8. To wrap things up, we must provide the GEF editor with a palette that contains the tools we want to add:
    package com.vainolo.phd.opm.gef.editor;
    
    import org.eclipse.gef.palette.CreationToolEntry;
    import org.eclipse.gef.palette.PaletteGroup;
    import org.eclipse.gef.palette.PaletteRoot;
    import org.eclipse.gef.palette.SelectionToolEntry;
    
    import com.vainolo.phd.opm.gef.editor.factory.OPMObjectFactory;
    import com.vainolo.phd.opm.gef.editor.factory.OPMProcessFactory;
    
    public class OPMGraphicalEditorPalette extends PaletteRoot {
    
    	PaletteGroup group;
    
    	public OPMGraphicalEditorPalette() {
    		addGroup();
    		addSelectionTool();
    		addOPMObjectTool();
    		addOPMProcessTool();
    	}
    
    	private void addSelectionTool() {
    		SelectionToolEntry entry = new SelectionToolEntry();
    		group.add(entry);
    		setDefaultEntry(entry);
    	}
    
    	private void addGroup() {
    		group = new PaletteGroup("OPM Controls");
    		add(group);
    	}
    
    	private void addOPMObjectTool() {
    		CreationToolEntry entry = new CreationToolEntry("OPMObject", "Create a new Object", new OPMObjectFactory(), null, null);
    		group.add(entry);
    	}
    
    	private void addOPMProcessTool() {
    		CreationToolEntry entry = new CreationToolEntry("OPMProcess", "Create a new Process", new OPMProcessFactory(), null, null);
    		group.add(entry);
    	}
    }
    

    and we tell the editor that we want to install our palette as the editor’s palette:

    package com.vainolo.phd.opm.gef.editor;
    
    import java.io.IOException;
    
    import org.eclipse.core.resources.IFile;
    import org.eclipse.core.runtime.IProgressMonitor;
    import org.eclipse.emf.common.util.URI;
    import org.eclipse.emf.ecore.resource.Resource;
    import org.eclipse.emf.ecore.resource.ResourceSet;
    import org.eclipse.emf.ecore.resource.impl.ResourceSetImpl;
    import org.eclipse.gef.DefaultEditDomain;
    import org.eclipse.gef.palette.PaletteRoot;
    import org.eclipse.gef.ui.parts.GraphicalEditorWithFlyoutPalette;
    import org.eclipse.ui.IEditorInput;
    import org.eclipse.ui.IEditorSite;
    import org.eclipse.ui.IFileEditorInput;
    import org.eclipse.ui.PartInitException;
    
    import com.vainolo.phd.opm.gef.editor.part.OPMEditPartFactory;
    import com.vainolo.phd.opm.model.OPMPackage;
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    
    public class OPMGraphicalEditor extends GraphicalEditorWithFlyoutPalette {
    
    	private Resource opdResource;
    	private OPMObjectProcessDiagram opd;
    
    	public OPMGraphicalEditor() {
    		setEditDomain(new DefaultEditDomain(this));
    	}
    
    	@Override protected void initializeGraphicalViewer() {
    		super.initializeGraphicalViewer();
    		getGraphicalViewer().setContents(opd);
    	}
    
    	@Override protected void configureGraphicalViewer() {
    		super.configureGraphicalViewer();
    		getGraphicalViewer().setEditPartFactory(new OPMEditPartFactory());
    	}
    
    	@Override protected PaletteRoot getPaletteRoot() {
    		return new OPMGraphicalEditorPalette();
    	}
    
    	@Override public void doSave(IProgressMonitor monitor) {
    		if(opdResource == null) {
    			return;
    		}
    
    		try {
    			opdResource.save(null);
    		} catch(IOException e) {
    			// TODO do something smarter.
    			e.printStackTrace();
    			opdResource = null;
    		}
    	}
    
    	@Override public void init(IEditorSite site, IEditorInput input) throws PartInitException {
    		super.init(site, input);
    
    		loadInput(input);
    	}
    
    	private void loadInput(IEditorInput input) {
    		OPMPackage.eINSTANCE.eClass(); // This initializes the OPMPackage singleton implementation.
    		ResourceSet resourceSet = new ResourceSetImpl();
    		if(input instanceof IFileEditorInput) {
    			IFileEditorInput fileInput = (IFileEditorInput) input;
    			IFile file = fileInput.getFile();
    			opdResource = resourceSet.createResource(URI.createURI(file.getLocationURI().toString()));
    			try {
    				opdResource.load(null);
    				opd = (OPMObjectProcessDiagram) opdResource.getContents().get(0);
    			} catch(IOException e) {
    				// TODO do something smarter.
    				e.printStackTrace();
    				opdResource = null;
    			}
    		}
    	}
    }
    

    BTW, if you didn’t notice, I also added save capabilities to the editor. While you cannot (yet) save manually, when you close the editor it will ask you if you want to save the file before closing (even if you didn’t do any changes).

  9. That’s it. Execute your editor, select a tool and add new entities to the diagram! This is how this looks in my diagram:

There is lots of work left, like moving the diagram entities, changing their names, and linking them. I promise to add these capabilities in the next few days.

The final eclipse project files can be downloaded here.

Bye…

Next Tutorial: Creating a GEF Editor – Part 7: Moving Elements and Direct Editing

JGraph Styles

I build a reference program to test some of the styles that JGraphX provides. This is the result of the program:

The full source code for this program follows:

package com.vainolo.jgraph;

import javax.swing.JFrame;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import java.awt.BorderLayout;
import java.awt.Color;
import com.mxgraph.swing.mxGraphComponent;
import com.mxgraph.util.mxConstants;
import com.mxgraph.util.mxUtils;
import com.mxgraph.view.mxGraph;

public class JGraphLearning5 extends JFrame {
	private static final long serialVersionUID = -2398843485837905351L;
	private static final int X_SOURCE = 50, Y_SOURCE = 50 ,PAGE_WIDTH = 1300, 
		HORIZONTAL_STEP = 150, VERTICAL_STEP = 100, SHAPE_WIDTH = 50,
		SHAPE_HEIGHT = 60;

	public JGraphLearning5() {
		mxGraph graph = new mxGraph();
		mxGraphComponent graphComponent = new mxGraphComponent(graph);
		graphComponent.getViewport().setOpaque(true);
		graphComponent.getViewport().setBackground(Color.WHITE);
		
		int x = X_SOURCE, y = Y_SOURCE;
		for (JGraphShape shape : JGraphShape.values()) {
			graph.insertVertex(null, null, shape.mxShapeConstantValue, x, y, SHAPE_WIDTH, SHAPE_HEIGHT, mxConstants.STYLE_SHAPE+"="+shape.mxShapeConstantValue);
			x += HORIZONTAL_STEP;
			if(x > PAGE_WIDTH) { x = X_SOURCE; y += VERTICAL_STEP; }
		}
		// Some options for the shapes:
		x = X_SOURCE; y += VERTICAL_STEP;
		String styleShape = mxConstants.STYLE_SHAPE+"="+mxConstants.SHAPE_RECTANGLE+";"; 
		for(JGraphStyle style: JGraphStyle.values()) {
			graph.insertVertex(null, null, style.name(), x, y, SHAPE_WIDTH, SHAPE_HEIGHT, styleShape+style.mxStyle);
			x += HORIZONTAL_STEP;
			if(x > PAGE_WIDTH) { x = X_SOURCE; y += VERTICAL_STEP; }
		}
		
		styleShape = mxConstants.STYLE_SHAPE+"="+mxConstants.SHAPE_ELLIPSE+";"; 
		for(JGraphStyle style: JGraphStyle.values()) {
			graph.insertVertex(null, null, style.name(), x, y, SHAPE_WIDTH, SHAPE_HEIGHT, styleShape+style.mxStyle);
			x += HORIZONTAL_STEP;
			if(x > PAGE_WIDTH) { x = X_SOURCE; y += VERTICAL_STEP; }
		}

		getContentPane().setLayout(new BorderLayout(0,0));
		getContentPane().add(graphComponent, BorderLayout.CENTER);
	}

	public enum JGraphShape {
		RECTANGLE(mxConstants.SHAPE_RECTANGLE),
		ELLIPSE(mxConstants.SHAPE_ELLIPSE),
		DOUBLE_ELLIPSE(mxConstants.SHAPE_DOUBLE_ELLIPSE),
		RHOMBUS(mxConstants.SHAPE_RHOMBUS),
		LINE(mxConstants.SHAPE_LINE),
		IMAGE(mxConstants.SHAPE_IMAGE),
// doesn't work		ARROW(mxConstants.SHAPE_ARROW),
		CURVE(mxConstants.SHAPE_CURVE),
		LABEL(mxConstants.SHAPE_LABEL),
		CILINDER(mxConstants.SHAPE_CYLINDER),
		SWIMLANE(mxConstants.SHAPE_SWIMLANE),
		CONNECTOR(mxConstants.SHAPE_CONNECTOR),
		ACTOR(mxConstants.SHAPE_ACTOR),
		CLOUD(mxConstants.SHAPE_CLOUD),
		TRIANGLE(mxConstants.SHAPE_TRIANGLE),
		HEXAGON(mxConstants.SHAPE_HEXAGON);
		
		public String mxShapeConstantValue;

		JGraphShape(String mxShapeConstantValue) {
			this.mxShapeConstantValue = mxShapeConstantValue;
		}
	}
	
	public enum JGraphStyle {
		OPACITY(mxConstants.STYLE_OPACITY, 50.0),
		TEXT_OPACITY(mxConstants.STYLE_TEXT_OPACITY, 50.0),
		OVERFLOW_1(mxConstants.STYLE_OVERFLOW, "visible"),
		OVERFLOW_2(mxConstants.STYLE_OVERFLOW, "hidden"),
		OVERFLOW_3(mxConstants.STYLE_OVERFLOW, "fill"),
		ROTATION(mxConstants.STYLE_ROTATION, 45),
		FILLCOLOR(mxConstants.STYLE_FILLCOLOR, mxUtils.getHexColorString(Color.RED)),
		GRADIENTCOLOR(mxConstants.STYLE_GRADIENTCOLOR, mxUtils.getHexColorString(Color.BLUE)),
		GRADIENT_DIRECTION(mxConstants.STYLE_GRADIENT_DIRECTION, mxConstants.DIRECTION_EAST, mxConstants.STYLE_GRADIENTCOLOR, mxUtils.getHexColorString(Color.YELLOW)),
		STROKECOLOR(mxConstants.STYLE_STROKECOLOR, mxUtils.getHexColorString(Color.GREEN)),
		STROKEWIDTH(mxConstants.STYLE_STROKEWIDTH, 5),
		ALIGN(mxConstants.STYLE_ALIGN, mxConstants.ALIGN_LEFT),
		VERTICAL_ALIGN(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_BOTTOM),
		LABEL_POSITION(mxConstants.STYLE_LABEL_POSITION, mxConstants.ALIGN_LEFT),
		VERTICAL_LABEL_POSITION(mxConstants.STYLE_VERTICAL_LABEL_POSITION, mxConstants.ALIGN_BOTTOM),
		GLASS(mxConstants.STYLE_GLASS, 1),
		NOLABEL(mxConstants.STYLE_NOLABEL, 1),
		LABEL_BACKGROUNDCOLOR(mxConstants.STYLE_LABEL_BACKGROUNDCOLOR, mxUtils.getHexColorString(Color.CYAN)),
		LABEL_BORDERCOLOR(mxConstants.STYLE_LABEL_BORDERCOLOR, mxUtils.getHexColorString(Color.PINK)),
		SHADOW(mxConstants.STYLE_SHADOW, true),
		DASHED(mxConstants.STYLE_DASHED, true),
		ROUNDED(mxConstants.STYLE_ROUNDED, true),
		HORIZONTAL(mxConstants.STYLE_HORIZONTAL, false),
		FONTCOLOR(mxConstants.STYLE_FONTCOLOR, mxUtils.getHexColorString(Color.ORANGE)),
		FONTFAMILY(mxConstants.STYLE_FONTFAMILY, "Times New Roman"),
		FONTSIZE(mxConstants.STYLE_FONTSIZE, 15),
		FONTSTYLE(mxConstants.STYLE_FONTSTYLE, mxConstants.FONT_BOLD),				
		;

		public String mxStyle;
		
		JGraphStyle(Object... values) {
			mxStyle = ""; 
			for (int i = 0; i < values.length; i++) {
				if(i%2==0) {
					mxStyle += values[i] + "=";
				} else {
					mxStyle += values[i] + ";";
				}
			}
		}
	}
	
	public static void main(String args[]) throws Exception {
		UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
		SwingUtilities.invokeLater(new Runnable() {
			@Override public void run() {
				JGraphLearning5 jgl = new JGraphLearning5();
				jgl.setExtendedState(JFrame.MAXIMIZED_BOTH);
				jgl.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
				jgl.setVisible(true);
			}
		});
	}
}