Last updated on 2014-09-06
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.
- Knowing that we wanted to create connections in our editor, our model already contains an
EClass
that represents these connections, calledOPMLink
. AnOPMLink
simply points to a source and targetOPMThing
in the model (it also contains a list ofbendpoints
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! - Now we’ll add to the editor a new
EditPart
calledOPMLinkEditPart
. AnEditPart
that is used as a connection implements theConnectionEditPart
interface (either directly or by subclassing theAbstractConnectionEditPart
class as we do), which requires the class to provide functionality to get/set source and targetEditPart
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; } }
- 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 targetEditPart
). We use bothChopBoxAnchor
andEllipseAnchor
, which simply sets the anchor in the intersection point between the connection and the edge of the figure (this is better explained in pictures):To add the anchors, we modify the
OPMThingFigure
interface adding it agetConnectionAnchor
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
andOPMProcessFigure
: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; } }
- 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 aCommand
and anEditPolicy
, we have to implement one of each. Implementing theCommand
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 ofGraphicalNodeEditPolicy
, a policy used for creating and reconnecting connections. TheCommand
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 theCommand
cannot be executed because of the code we inserted in thecanExecute
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 theGraphicalNodeEditPolicy
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; } }
- OK, we can now make all the changes to the
OPMThingEditPart
. First, we make it implement theNodeEditPart
interface, which is used by other GEF classes (likeAbstractConnectionEditPart
) to better manage the connections in the diagram. This interface requires that the class provideConnectionAnchor
instances for theConnectionEditPart
instances which are connected to it, and also for connections that are currently being created (usingRequest
instances). Second, we add the newly createdOPMThingGraphicalNodeEditPolicy
to theOPMThingEditPart
. And last, we provide the GEF framework with a way to know which connections there are in the diagram by implementing thegetModelSourceConnections
andgetModelTargetConnections
methods, just like when we implemented thegetModelChildren
method in theOPMObjectProcessDiagramEditPart
to make it know which model elements existed in our diagram. The GEF framework calls these methods and for each uniqueOPMLink
instance that is found it creates a newOPMLinkEditPart
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); } } }
- To finish the job, we must implement a factory that creates
OPMLink
instances and add a tool to theOPMGraphicalEditorPalette
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); } }
- One last thing (and thanks to Ouri for the comment), we have to add the
OPMLink
model to theOPMEditPartFactory
so the edit parts are created by the framework when neededpackage 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; } }
- 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
I guess that when adding the OPMLinkEditPart, it should be added to the OPMEditPartFactory as well? I did not see it mentioned here, so I am not sure about it?
Yea. Guess I missed writing this in the post (although it is implemented in the code). Thanks for the comment!
Hello Arieh,
I am trying to put the CONNECTION_LAYER behind the PRIMARY_LAYER in your editor in order for connections not to go over figures. I am trying to achieve that by overriding ScalableRootEditPart as suggested by google findings. createPrintableLayers() is the overridden method and the order of layers is switched – first connection, then primary layer.
I have set the overridden RootEditPart on OPMGraphicalEditor in configureGraphicalViewer.
Unfortunately this does not seem to work. People who suggested this didn’t seem to have problems apart from setting figures to opaque.
http://www.eclipse.org/forums/index.php?t=msg&S=c5a4cc6dbfd634c8a3ae2d6c6e944963&th=73558&goto=229956
Do you see what is wrong with what I did? Maybe some prerequisites must be fulfilled – such as the order of children on the opd.
Maybe you have some other idea how to manipulate z-order. I just want to put connections behind figures.
Regards,
Bojan
Hi Bojan.
I changed my
configureGraphicalViewer
method as follows:And now the connections are below the figures and I can see the connections below my figures. But do you need to do this?
Interestingly enough I just came to write that I succeeded to implement what I need and I saw your response.
First to answer your question. My editor layouts figures into columns. Elements within one column can be interconnected and for one particular type of connection anchors have to be positioned on the top and on the bottom side of the figure (basic rectangle). That leads to many connections crossing all the figures in the way. So the best thing to do is to go under the figure. We know which two figures are connected by connection decorations (e.g. ArrowLocator).
To my problem (the solution is obvious, but I needed to have clear mind obviously): actually switching the order of primary and connection layer did the trick but I could not witness that because all the figures had setFill(false) (how did you confirm your code had put connections behind? did you change this setting as well?).
With this change in place I could actually see connections beneath figures.
One more thing left: we need to change the order of rectangle and label figures on the OPMObjectFigure in order for label to appear on top of rectangle which is now filled.
So the GEF rendering rule holds true 🙂 – the order of the children really dictates the z-order
Thank you for your reply,
Bojan
Instead of ArrowLocator, PolylineDecoration and PolygonDecoration should stay.
I realize this post is a bit old but people (like me) are still reading it, so here’s my suggestion:
You might want to add a line about modifying the OPMThingAdapter in step 5 for people that aren’t copying the source code for the entire class. Took me quite some time to figure out why the connections I drew didn’t show up. The method calls I missed were
refreshSourceConnections();
refreshTargetConnections();
in notifyChanged(). Otherwise thanks for a great tutorial. 🙂
Thanks for the comment. I added a line telling this.
You’ve added it to the provided code, but not to the tutorial!
What did I add to the code that I didn’t add to the tutorial?
Hi Vainolo,
thanks so much for the tutorial. Still rockin in 2015!!! 🙂
I am following the tutorial from Part one and am doing the steps that you decribe incrementally adding the code changes. I missed that part and looked in the text and could not find the line telling this. But after adding the two methods mentioned to my notify method it worked.
Best Regards,
Paul
Great to see this helps.
Another great part of the tutorial, thanks Arieh!
Got stuck on this though, because the (3rd party) model I use doesn’t follow the GEF structure where connections are a feature of the node. Instead, my connections are features of the graph. This is a discrepancy with the GEF structure, which led to my connections initially not being displayed on the editor when added. Why? Because the Adapter on my NodeEditPart didn’t register any changes (cos there weren’t any), and hence didn’t refresh the source & target connections. The solution was to refresh the two NodeEditParts programatically.
I’ve written a wee blog post about this (http://wp.me/p2qdAI-1t). Just sharing in case anyone comes across the same problem.
Thanks again, your tutorial is great!
Glad you found the tutorial useful.
Hi GEF Expert, I would like to know, how to highlights rectangle in GEF?. I hope you can answer.Thanks in advance!
Highlighting a rectangle is a semantic operation, not something that is built-in in GEF. What you can do is override a
SelectionEditPolicy
, specificaly theshowSelection
andhideSelection
methods in this class, and do your highlighting here. These functions will be called when you select (and deselect) theEditPart
.[…] had implemented connection creation, following vainolo’s post about connections, but in my editor, the added connection figures never showed up, although the respective model […]
This is a damn great tutorial, still I had a problem within the OPMLinkCreateCommand’s execute() method.
Specifically, you set the source, target and opd instances for the OPMLink’s instance.
I had to add also these two lines:
source.getOutgoingLinks().add(link);
target.getIncomingLinks().add(link);
Obviously, setting the instances won’t add those to the List of incoming/outgoing edges of a given model.. maybe you overrode those methods to do so? Did I miss that part?
I never used the code you attached to each part of the tutorial (I was just reading the code and then I tried to do everything by myself) so maybe I missed something in the previous parts.
I know this is quite old now, but maybe you still remember something! 😀
Thank you!
Nevermind, I had an epiphany!
I thought I didn’t add the EOpposite property for OPMLink’s source and target attributes.. and I was that!
I updated the property by adding the references to the outgoing and incoming links, reloaded the genmodel and regenerated all the EMF classes and now it works.
Glad to be of assistance 🙂
Hi what is the simplest way to create a connection? Is it necessary to implement mvc when I have the source and target nodes already in my method.
Thanks
I want to update my connections at runtime via the arrange all option.
What arrange option?
I you are using GEF you must use MVC. The connections are created by requesting connection source and target connections (using the edit part’s
getModelSourceConnections()
andgetModelTargetConnections()
). If you want to create links directly you can usedraw2d
but this is more difficult.Hi Vainolo,
This is a great set of articles to refer in generating GMF based graphical editors. The tutorial on centered anchors helped me a lot. I have a problem with adding a custom image to my cursor in the GMF editor when connection drawing is enabled. GMF converts my cursor to a black arrow when it hovers over something that could be connected, but the cursor has an opaque background. can you help me with this?
the issue is reported at https://www.eclipse.org/forums/index.php/t/1076353/
I don’t work much with GMF, only GEF.
Hi, first thanks for this informativ Blog. Do you have experience with key bindings for short cut to the palette connection-Cursor (create-connection not Select)?
best regards.
You are welcome!
Regarding your ask: no, I’m not familiar with that type of key binging.