Tag Archives: editor

GEF Internals Part 2 – Mouse Interaction and the Creation Tool

After investigating the complex interactions that occur with the a title=”GEF Internals Part 1 – Mouse Interaction and the Selection Tool” href=”http://www.vainolo.com/2012/01/01/gef-internals-part-1-mouse-interaction-and-the-selection-tool/”selection tool in my previous post/a, we will now focus on a simpler case, the creation tool.

When the mouse moves over the canvas, the codeLightweightSystem/code system catches the mouse move event and forwards it to the codeDomainEventDispatched/code. If the event was not previously captured by the domain codeDomainEventDispatcher/code executes direct codedraw2d/code interaction. I am not completely sure what “captured by the domain” means, but for example when there is a mouse click, which is translated into a codemouseDown/code event, and a codemouseUp/code event, if the codemouseDown/code event was handled by the domain, then then codemouseUp/code event is “captured by the domain” and no direct codedraw2d/code interaction is allowed. Anyway, after codedraw2d/code interaction is executed (or is not executed), the codeDomainEventDispatcher/code can forward the event to the domain as either a codemouseMove/code or as a codemouseDrag/code event (depending of the states of the buttons). This can be seen in the following sequence diagram:

a href=”http://www.vainolo.com/wp-content/uploads/2012/01/dispatchMouseMoved.png”img class=”aligncenter size-full wp-image-959″ title=”dispatchMouseMoved” src=”http://www.vainolo.com/wp-content/uploads/2012/01/dispatchMouseMoved.png” alt=”" width=”422″ height=”497″ //a

When the domains receives the codemouseMove/code, it searches for the currently active tool and forwards it the codemouseMove/code request. This is the place where finally some work is done. The domain calls codeTool.mouseMove/code which is implemented in the codeAbstractTool/code class. This method does some internal work that I could not understand but seems harmless, and after this it calls codehandleMove/code, an internal method that tools should override to implement their functionality. The codeCreationTool.handleMove/code method works as follows: First it creates of updates a codeCreateRequest/code instance that contains the information used by the codeCreationTool/code, which includes the mouse location, factory that creates the new domain instances, and other things. Since the mouse can be moved a lot over the canvas, it is not logical to create a new request every time a mouse move is captured, so the codeCreationTool/code has a cached request which it updates every time a codemouseMove/code event is accepted.

After the codeCreateRequest/code is created or updated, the codeCreationTool/code updates the target of the request… This happens in two steps: First the tool finds the topmost codeEditPart/code below the current mouse location that can handle a codeCreateRequest/code. Second, it requests from the codeEditPart/code for the emtarget/em of the request… what? didn’t we just fetch the target of the request from the diagram? Well, it seems that GEF allows the codeEditPart/code under the mouse to give us another codeEditPart/code as the real target of the request. Why? I can think of a number of reasons, for example we can have an editor where you have squares and a button on the inner top left corner of the square and every time you click this button a new circle is added inside the square. So the codeEditPart/code that receives the codehandleMove/code is the button, but the codeEditPart/code that handles the request is the enclosing square, probably the parent of the button.

Now that GEF has the real codeEditPart/code that will handle the request, and the codeCreateRequest/code itself, it asks the target codeEditPart/code to create the codeCommand/code that will be executed if the tool is applied (mouse click). This command is created in the codeEditPart/code by running over all the codeEditPolicy/code instances that have been installed in the codeEditPart/code and asking them to create a codeCommand/code for the provided request. If more than one codeEditPart/code returns a codecommand/code, they are are chained inside a codeCompoundCommand/code. After the command is created, the codeTool/code refreshes the cursor that is displayed on the editor – and this is where we get the functionality that changes the cursor to an X when the current codeTool/code cannot be applied to the target under the mouse, because the command that was return by the codeEditPart/code cannot be executed.

Finally, the tool requests feedback from the target codeEditPart/code. Note that here the target may not be exactly the codeEditPart/code below the cursor. This feedback is requested in the same way as the codeCommand/code, by delegating it to the codeEditPolicy/code instances that are installed in the codeEditPart/code.

I have captured this interaction in the following sequence diagram:
a href=”http://www.vainolo.com/wp-content/uploads/2012/01/mouseMoveHandling.png”img src=”http://www.vainolo.com/wp-content/uploads/2012/01/mouseMoveHandling.png” alt=”" title=”mouseMoveHandling” width=”879″ height=”723″ class=”aligncenter size-full wp-image-961″ //a

Wasn’t that interesting? Now, you can see that having complex edit policies can be very hazardous for your editor, since they may be called on every mouse move! Furthermore, all the classes perform a LOT of functionality, and definitely do not conform to the a href=”http://en.wikipedia.org/wiki/SOLID_(object-oriented_design)”SOLID/a principles, specially a href=”http://en.wikipedia.org/wiki/Single_responsibility_principle”Single responsibility/a, which makes the code harder to understand. But I have to say that the architecture is beautiful. I’m still looking into GEF all the time, and will post my findings as I go, but if you have a special topic that you’d like to have answered, leave me a commend and I’ll see what I can do.

GEF Internals Part 1 – Mouse Interaction and the Selection Tool

There are some explanations on the net on how GEF works (see here ) but I have not found a good description on how GEF really works, so I will try to unravel the secret and post it here for the world to know. My investigation began when I found that although GEF EditPolicy instances are installed in the EditPart using a key, this key was never used (well, everything works even if I changed all the keys to “chukumuku”). I started reading more and mode code and was fascinated about how the framework works. So here it goes, what I have found in some nights of code reading and debugging.

We will start by explaining what happens when we select a tool and move the mouse over the editor. The editor itself is implemented on top of SWT (The Standard Widget Toolkit), which is like java’s AWT (Abstract Window Toolkit). My knowledge of SWT is pretty low so it won’t be explained here. The only important thing to know is that a GEF editor’s graphics are drawn on an SWT Canvas on which GEF draws the RootFigure which is used as the top level figure on which the editor is drawn. The Canvas is par of a LightweightSystem class who forwards user interaction from SWT to GEF. There is also all the drawing functionality of draw2d that is transferred in the other direction but this will not be discussed here.

Looking at this from the other side, when a GEF editor is started, the platform calls createPartControl on the GraphicalEditorClass. This function in turn calls createGraphicalViewer which instantiates a new LightweigthSystem and a Canvas on which to draw the EditPart instances. The process is better described in the following sequence diagram:

So knowing this we now go into what happens when the user interacts with the editor, using the SelectionTool for our example. As explained above, the LightweightSystem bridges the requests coming from the SWT Canvas, so when the user clicks on the editor canvas, the request that is fired by it is received by the LightweightSystem‘s EventHandler. The EventHandler is an adapter to a EventDispatcher which does the real event handling. In our case this is a DomainEventDispatcher which has a handle to the EditDomain containing our editor’s information.
Event handling is done in two parts: direct interaction and indirect interaction (my naming).

  1. Direct Interaction: while we are using GEF for editor interaction, draw2d also allows us to add mouse listeners that can interact with the figures in the diagram. In this part, the dispatcher searched for draw2d listeners in the figures below the mouse click, and if it finds one it calls the listener with the mouse event. If there is more than one listener in the Figure hierarchy, the topmost listener (z-axis) is called.
  2. Indirect Interaction: the editor directly handles the mouse event by forwarding the event to the currently active tool, in this case the SelectionTool. The selection tool is actually a complex example since it does not work directly but delegates to a DragEditPartsTracker which does the selection. This is done because we can drag the selection tool to do multiple selection. In any case, the selection tool forwards the request to the DragEditPartsTracker which finds what EditPart instances are now selected in the editor, updates the domain’s internal data structures and fires a selection changed event, which updates the graphics on the screen to match the requested selection.

I tried to catch this interaction in the following two sequence diagrams: First the generic part before the SelectionTool is invoked,

And second, the selection tool is invoked with the mouse event.

I hope that this explanation has shed a bit of light on how GEF works. For me it has been a very hard task understanding what goes on under the hood, and there are still many things that need to be cleared out. I think that in the next post I’ll show how the creation tool works, which I think is simpler and in retrospect I should have analyzed first. So see you next time.

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