Skip to content

Creating an OPM GEF Editor – Part 15: Adding Structural Links

Last updated on 2014-09-07

Previous Tutorial: Creating an OPM GEF Editor – Part 14: Refactoring, Refactoring and More Refactoring.

In the last tutorial we learned how to refactor classes using the build-in operations in eclipse. While automatic refactoring is very useful, there are times when you just have to do the changes manually. For example, the OPMThingEditPart creates a new OPMNodeAdapter in its constructor. But this should be done in the constructor of the OPMNodeEditPart. Sadly this change can only be done manually.
Furthermore, we now have to divide the createEditPolicies function in two: one part is generic for the OPMNodeEditPart and the other part is more specific, only for OPMThingEditPart (direct editing). So to start this tutorial I did some broad changes in the code base so that the generic functionality of the OPMNodeEditPart could also be used for structural links. So before you start this tutorial, please download the latest project files can be found here: GEF.Editor.Tutorial.15.start.zip. This tutorial is going to be specially long so grab a bag of doritos and some salsa just in case you get hungry 🙂

Structural links in OPM are displayed using a symbol that aggregates all of the links that start at a OPMThing, so if there is more than one link that starts at this OPMThing, the links are displayed as one link that starts at the OPMThing and goes to an aggregator, and from this aggregator there is one link to every target OPMThing as can be seen here. The way I decided to implement this is for the user to use a create connection tool to add a new structural link, but when the tool is applied (that is, after source and destination nodes have been selected) instead of creating a link, the activated policy does extra work: if there is no matching structural link that starts at the source thing, it creates a new aggregator and connects it between the source and the target. But if there is already an aggregator that starts at the source thing, it only adds a new link from the aggregator to the target link. Some other things must also be handled, like that removal of nodes now requires also removal of structural links that start at the deleted node. Unlike previous tutorials, the amount of code in this tutorial is pretty large, therefore only highlights will be shown here, but as usual the full code is available at the end of the tutorial.

  1. We’ll start with the simple things. To create a structural link we add new factories and tools to the editor.
    The factory creates a new OPMStructuralLinkAggregator instances and sets its kind:

    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.OPMStructuralLinkAggregator;
    import com.vainolo.phd.opm.model.OPMStructuralLinkAggregatorKind;
    
    /**
     * Factory used by palette tools to create {@link OPMStructuralLinkAggregator} of
     * {@link OPMStructuralLinkAggregatorKind#AGGREGATION} kind. 
     * @author vainolo
     *
     */
    public class OPMAggregationStructuralLinkAggregatorFactory implements CreationFactory {
    
        @Override public Object getNewObject() {
            OPMStructuralLinkAggregator aggregator = OPMFactory.eINSTANCE.createOPMStructuralLinkAggregator();
            aggregator.setKind(OPMStructuralLinkAggregatorKind.AGGREGATION);
            return aggregator;
        }
    
        @Override public Object getObjectType() {
            return OPMStructuralLinkAggregator.class;
        }
    }
    

    And we add ConnectionCreationToolEntry entries to the OPMGraphicalEditorPalette:

        /**
         * Add tools to create structural links in the diagram.
         */
        private void addOPMStructuralLinkTools() {
            ConnectionCreationToolEntry entry;
            entry = new ConnectionCreationToolEntry("Aggregation", "Create a new Aggregation link", new OPMAggregationStructuralLinkAggregatorFactory(), null, null);
            group.add(entry);
            entry = new ConnectionCreationToolEntry("Exhibition", "Create a new Exhibition link", new OPMExhibitionStructuralLinkAggregatorFactory(), null, null);
            group.add(entry);
            entry = new ConnectionCreationToolEntry("Generalization", "Create a new Generalization link", new OPMGeneralizationStructuralLinkAggregatorFactory(), null, null);
            group.add(entry);
        }
    
  2. The connection creation tools used to add the structural link activate the OPMNodeGraphicalNodeEditPolicy that is installed in the source and target OPMNode. A connection between two diagram nodes is created in two steps, first when the user clicks on the source node (calling the getConnectionCreateCommand) and second when the user clicks on the target node (calling the getConnectionCompleteCommand). These functions are called by the framework before the user actually clicks on the source node to check if the node can be set as a source of the link (changing the icon that is shown on the screen), therefore in the policies we also limit the creation of links by returning null when the current location is not a valid source/target for the link.
    All of the functionality for creating the structural link is done in the getConnectionCompleteCommand as follows:

    • We check the outgoing structural links of the OPMNode (I implemented a new function to the model to provide this functionality) for a link that ends at an OPMStructuralLinkAggregator of the kind that was requested by the tool.
    • If we find such a link, our only job is to add a new link from the aggregator to the target OPMNode.
    • If we didn’t find such a link, we must add a new aggregator to the diagram and connect it to the source and target nodes. The aggregator’s location is set between the source and target nodes.

    Note that because of the refactoring we did before this tutorial, we can use the existing OPMLinkCreateCommand and OPMNodeCreateCommand and aggregate them using CompoundCommand instances.

    package com.vainolo.phd.opm.gef.editor.policy;
    
    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 org.eclipse.gef.commands.CompoundCommand;
    import org.eclipse.gef.commands.UnexecutableCommand;
    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.gef.editor.command.OPMNodeCreateCommand;
    import com.vainolo.phd.opm.gef.editor.part.OPMStructuralLinkAggregatorEditPart;
    import com.vainolo.phd.opm.model.OPMFactory;
    import com.vainolo.phd.opm.model.OPMLink;
    import com.vainolo.phd.opm.model.OPMNode;
    import com.vainolo.phd.opm.model.OPMObjectProcessDiagram;
    import com.vainolo.phd.opm.model.OPMStructuralLinkAggregator;
    import com.vainolo.phd.opm.model.OPMThing;
    
    /**
     * Policy used to connect two nodes in the diagram. Currently connections can
     * only be created between two {@link OPMThing} instances.
     * 
     * @author vainolo
     */
    public class OPMNodeGraphicalNodeEditPolicy extends GraphicalNodeEditPolicy {
    
        private static final Dimension DEFAULT_AGGREGATOR_DIMENSION = new Dimension(30, 30);
    
        /**
         * Create a command used to begin connecting to nodes.
         * {@link OPMStructuralLinkAggregatorEditPart} nodes cannot be source nodes,
         * therefore in this case a {@link UnexecutableCommand} is returned.
         * 
         * @return a {@link Command} that contains the initial information neede to
         *         create a connection between two nodes.
         */
        @Override
        protected Command getConnectionCreateCommand(CreateConnectionRequest request) {
            // We must return null and not the usual UnexecutableCommand because is we return a
            // non-null value the framework thinks that the link can be created from this host,
            // something that we don't want to happen.
            if(request.getSourceEditPart() instanceof OPMStructuralLinkAggregatorEditPart) {
                return null;
            }
    
            if(request.getNewObject() instanceof OPMStructuralLinkAggregator) {
                request.setStartCommand(new Command() {});
                return request.getStartCommand();
            }
    
            OPMLinkCreateCommand result = new OPMLinkCreateCommand();
            result.setSource((OPMNode) getHost().getModel());
            result.setLink((OPMLink) request.getNewObject());
            result.setOPD(((OPMNode) getHost().getModel()).getOpd());
            request.setStartCommand(result);
            return result;
        }
    
        /**
         * Retrieves the command created by
         * {@link OPMNodeGraphicalNodeEditPolicy#getConnectionCreateCommand(CreateConnectionRequest)
         * getConnectionCreateCommand}, and adds it information so that the command
         * can be executed. {@link OPMStructuralLinkAggregatorEditPart} nodes cannot
         * be source nodes, therefore in this case a {@link UnexecutableCommand} is
         * returned.
         * 
         * @return a {@link Command} that can be executed to create a connection
         *         between two nodes.
         */
        @Override
        protected Command getConnectionCompleteCommand(CreateConnectionRequest request) {
            // A null command must be returned (and not the usual UnexecutableCommand),
            // otherwise GEF shows the used a symbol that the connection can be completed but
            // when the used clicks it is not created.
            if(request.getStartCommand() == null || 
               request.getTargetEditPart() instanceof OPMStructuralLinkAggregatorEditPart) {
                return null;
            }
    
            Command command = null;
    
            if(request.getNewObject() instanceof OPMStructuralLinkAggregator) {
                // create a structural link based on the request.
                command = handleOPMStructuralLinkRequest(request);
            } else {
                // Simple link creation command.
                OPMLinkCreateCommand linkCreateCommand = (OPMLinkCreateCommand) request.getStartCommand();
                linkCreateCommand.setTarget((OPMNode) getHost().getModel());
                command = linkCreateCommand;
            }
    
            return command;
        }
    
        /**
         * When the user requests the creation of a structural link, the following is done:
         * <ol>
         *   <li>If this is the first structural link of its kind between the source
         *       and target nodes, we create a new aggregator and connect it to the source
         *       and target.</li>
         *   <li>If there already is an aggregator of its kind between the nodes, we
         *       only add a new link from the aggregator to the new target.</li>
         * </ol>
         * 
         * @param request
         *            the user request to create a new strucutral link between the
         *            nodes.
         * @return a command that creates the links as stated above.
         */
        private Command handleOPMStructuralLinkRequest(CreateConnectionRequest request) {
            Command command = null;
    
            OPMNode sourceNode = (OPMNode) request.getSourceEditPart().getModel();
            OPMNode targetNode = (OPMNode) request.getTargetEditPart().getModel();
            OPMStructuralLinkAggregator aggregatorNode = (OPMStructuralLinkAggregator) request.getNewObject();
    
            boolean aggregatorFound = false;
            for(OPMLink structuralLink : sourceNode.getOutgoingStructuralLinks()) {
                OPMStructuralLinkAggregator existingAggregator = (OPMStructuralLinkAggregator) structuralLink.getTarget();
                if (existingAggregator.getKind() == aggregatorNode.getKind()) {
                    aggregatorFound = true;
                    aggregatorNode = existingAggregator;
                }
            }
    
            if (aggregatorFound) {
                // Just create a link from the aggregator to the target.
                OPMLinkCreateCommand linkCreateCommand = new OPMLinkCreateCommand();
                linkCreateCommand.setSource(aggregatorNode);
                linkCreateCommand.setTarget(targetNode);
                linkCreateCommand.setOPD(aggregatorNode.getOpd());
                linkCreateCommand.setLink(OPMFactory.eINSTANCE.createOPMLink());
    
                command = linkCreateCommand;
            } else {
                // Create a compound command consisting of three commands.
                CompoundCommand compoundCommand = new CompoundCommand();
                // create aggregator
                OPMNodeCreateCommand createAggregatorCommand = createCreateAggregatorNodeCommand(sourceNode, targetNode, aggregatorNode);
                compoundCommand.add(createAggregatorCommand);
                // connect aggregator to source.
                OPMLinkCreateCommand sourceLinkCreateCommand = createCreateOPMLlinkCreateCommand(sourceNode, aggregatorNode, sourceNode.getOpd());
                compoundCommand.add(sourceLinkCreateCommand);
                // connect aggregator to target.
                OPMLinkCreateCommand targetLinkCreateCommand = createCreateOPMLlinkCreateCommand(aggregatorNode, targetNode, sourceNode.getOpd());
                compoundCommand.add(targetLinkCreateCommand);
    
                command = compoundCommand;
            }
    
            return command;
        }
    
        /**
         * Helper function to create a command that connects two nodes with a
         * factory generated link.
         * 
         * @param source
         *            the source of the link.
         * @param target
         *            the target of the link.
         * @return
         */
        private OPMLinkCreateCommand createCreateOPMLlinkCreateCommand(OPMNode source, OPMNode target, OPMObjectProcessDiagram opd) {
            OPMLinkCreateCommand command = new OPMLinkCreateCommand();
            command.setSource(source);
            command.setTarget(target);
            command.setOPD(opd);
            command.setLink(OPMFactory.eINSTANCE.createOPMLink());
            return command;
        }
    
        /**
         * Create a command that adds the provided
         * {@link OPMStructuralLinkAggregator} to the diagram located between the
         * source and the target {@link OPMNode}.
         * 
         * @param source
         *            the source for the structural link.
         * @param target
         *            the target of the structural link.
         * @param aggregator
         *            the aggregator that should be added to the diagram.
         * @return A {@link OPMNodeCreateCommand} whose execution add the aggregator
         *         to the diagram.
         */
        public OPMNodeCreateCommand createCreateAggregatorNodeCommand(OPMNode source, OPMNode target, OPMNode aggregator) {
            OPMNodeCreateCommand command = new OPMNodeCreateCommand();
            command.setNode(aggregator);
            command.setParent(source.getOpd());
    
            // Calculate location of aggregator, between the source and targetnodes.
            Rectangle sourceConstraints = source.getConstraints();
            Rectangle targetConstraints = target.getConstraints();
            Point sourceCenter = new Point(sourceConstraints.x + sourceConstraints.width/2, sourceConstraints.y + sourceConstraints.height/2);
            Point targetCenter = new Point(targetConstraints.x + targetConstraints.width/2, targetConstraints.y + targetConstraints.height/2);
            Point aggregatorLeftTopCorner = new Point();
            aggregatorLeftTopCorner.x = sourceCenter.x + (targetCenter.x - sourceCenter.x)/2 - DEFAULT_AGGREGATOR_DIMENSION.width/2;
            aggregatorLeftTopCorner.y = sourceCenter.y + (targetCenter.y - sourceCenter.y)/2 - DEFAULT_AGGREGATOR_DIMENSION.height/2;
            if (aggregatorLeftTopCorner.x < 0) {
                aggregatorLeftTopCorner.x = 0;
            }
            if (aggregatorLeftTopCorner.y < 0) {
                aggregatorLeftTopCorner.y = 0;
            }
            command.setConstraints(new Rectangle(aggregatorLeftTopCorner, DEFAULT_AGGREGATOR_DIMENSION));
    
            return command;
        }
    
        @Override
        protected Command getReconnectTargetCommand(ReconnectRequest request) {
            return null;
        }
    
        @Override
        protected Command getReconnectSourceCommand(ReconnectRequest request) {
            return null;
        }
    }
    
  3. Since we are already working with policies, we might as well work on the OPMNodeComponentEditPolicy which is activated when the user asks to delete a OPMNode. What we do here is extend the removal functionality to handle cases when the node is the source of a structural aggregator, which means that the aggregator must be deleted; and when the node is the target of an aggregator who’s only target is the deleted node, again needing removal of the aggregator. Note that since aggregators are OPMNode instances, we can also delete it like regular nodes, deleting all their source and target connections.
    package com.vainolo.phd.opm.gef.editor.policy;
    
    import org.eclipse.gef.EditPolicy;
    import org.eclipse.gef.commands.Command;
    import org.eclipse.gef.commands.CompoundCommand;
    import org.eclipse.gef.editpolicies.ComponentEditPolicy;
    import org.eclipse.gef.requests.GroupRequest;
    
    import com.vainolo.phd.opm.gef.editor.command.OPMNodeDeleteCommand;
    import com.vainolo.phd.opm.model.OPMLink;
    import com.vainolo.phd.opm.model.OPMNode;
    import com.vainolo.phd.opm.model.OPMStructuralLinkAggregator;
    import com.vainolo.phd.opm.model.OPMThing;
    
    /**
     * {@link EditPolicy} used for delete requests.
     * 
     * @author vainolo
     * 
     */
    public class OPMNodeComponentEditPolicy extends ComponentEditPolicy {
    
        /**
         * Create a command to delete a node. When a node is deleted all incoming
         * and outgoing links are also deleted (functionality provided by the
         * command). When a {@link OPMThing} node is deleted, there is special
         * treatment for structural links that start and end at this node. If this
         * node is source for a structural link, the
         * {@link OPMStructuralLinkAggregator} of this link must be deleted. Also if
         * this node is the target of the only outgoing link of a
         * {@link OPMStructuralLinkAggregator}, the aggregator must be deleted.
         * 
         * @return a command that deletes a node and all other required diagram
         *         entities.
         */
        @Override
        protected Command createDeleteCommand(GroupRequest deleteRequest) {
            OPMNode nodeToDelete = (OPMNode) getHost().getModel();
            CompoundCommand compoundCommand = new CompoundCommand();
    
            // For every outgoing structural link, create a command to delete the aggregator
            // node at the end of the link.
            for(OPMLink outgoingStructuralLink : nodeToDelete.getOutgoingStructuralLinks()) {
                OPMNode aggregatorNode = outgoingStructuralLink.getTarget();
                OPMNodeDeleteCommand aggregatorNodeDeleteCommand = new OPMNodeDeleteCommand();
                aggregatorNodeDeleteCommand.setNode(aggregatorNode);
                compoundCommand.add(aggregatorNodeDeleteCommand);
            }
            // For every incoming structural link whose aggregator has only one outgoing
            // link, create a command to delete the aggregator.
            for(OPMLink incomingStructuralLink : nodeToDelete.getIncomingStructuralLinks()) {
                OPMNode aggregatorNode = incomingStructuralLink.getSource();
                if(aggregatorNode.getOutgoingLinks().size() == 1) {
                    OPMNodeDeleteCommand aggregatorNodeDeleteCommand = new OPMNodeDeleteCommand();
                    aggregatorNodeDeleteCommand.setNode(aggregatorNode);
                    compoundCommand.add(aggregatorNodeDeleteCommand);
                }
            }
    
            // Create a command to delete the node.
            OPMNodeDeleteCommand nodeDeleteCommand = new OPMNodeDeleteCommand();
            nodeDeleteCommand.setNode((OPMNode) getHost().getModel());
            compoundCommand.add(nodeDeleteCommand);
    
            return compoundCommand;
        }
    }
    
  4. Now we have to implement all of the regular GEF functionality for the new EditPart. The OPMStructuralLinkAggregator is represented in the diagram as a triangle figure that I had to implement similarly to the existing triangle implementation provided by org.eclipse.draw2d.Triangle.
    
    package com.vainolo.phd.opm.gef.editor.figure;
    
    import org.eclipse.draw2d.Graphics;
    import org.eclipse.draw2d.Orientable;
    import org.eclipse.draw2d.PositionConstants;
    import org.eclipse.draw2d.Shape;
    import org.eclipse.draw2d.geometry.Point;
    import org.eclipse.draw2d.geometry.PointList;
    import org.eclipse.draw2d.geometry.Rectangle;
    
    /**
     * A triangle that uses all of its bounds to draw an isosceles triangle in the
     * figure's bounds, like this:
     * 
     * <pre>
     *      ______
     *     |  /\  |
     *     | /  \ | (bounds shown as surrounding rectangle). 
     *     |/____\|
     * </pre>
     * 
     * The implementation is based on the {@link org.eclipse.draw2d.Triangle}
     * implementation.
     * 
     * @author vainolo
     * 
     */
    public final class IsoscelesTriangle extends Shape implements Orientable {
        /** The points of the triangle. */
        protected PointList triangle = new PointList(3);
        /**
         * The direction this triangle will face. Possible values are
         * {@link PositionConstants#NORTH}, {@link PositionConstants#SOUTH},
         * {@link PositionConstants#EAST} and {@link PositionConstants#WEST}.
         */
        protected int direction = NORTH;
    
        /**
         * {@inheritDoc}
         */
        public void primTranslate(int dx, int dy) {
            super.primTranslate(dx, dy);
            triangle.translate(dx, dy);
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        protected void outlineShape(Graphics graphics) {
            graphics.drawPolygon(triangle);
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        protected void fillShape(Graphics graphics) {
            graphics.fillPolygon(triangle);
        }
    
        /**
         * Validates the figure, drawing a vertical isosceles triangle filling the
         * figure's bounds.
         */
        @Override
        public void validate() {
            super.validate();
            Rectangle r = getBounds().getCopy();
            r.shrink(getInsets());
            r.resize(-1, -1);
            Point p1 = null, p2 = null, p3 = null;
            switch(direction) {
            case NORTH:
                p1 = new Point(r.x + r.width / 2, r.y);
                p2 = new Point(r.x, r.y + r.height);
                p3 = new Point(r.x + r.width, r.y + r.height);
                break;
            case SOUTH:
                p1 = new Point(r.x + r.width / 2, r.y + r.height);
                p2 = new Point(r.x, r.y);
                p3 = new Point(r.x + r.width, r.y);
                break;
            case EAST:
                p1 = new Point(r.x, r.y);
                p2 = new Point(r.x + r.width, r.y + r.height / 2);
                p3 = new Point(r.x, r.y + r.height);
                break;
            case WEST:
                p1 = new Point(r.x + r.width, r.y);
                p2 = new Point(r.x + r.width, r.y + r.height);
                p3 = new Point(r.x, r.y + r.height / 2);
            }
            triangle.removeAllPoints();
            triangle.addPoint(p1);
            triangle.addPoint(p2);
            triangle.addPoint(p3);
        }
    
        /**
         * This functions is ignored. Use
         * {@link IsoscelesTriangle#setDirection(int)} instead.
         */
        @Override
        @Deprecated
        public void setOrientation(final int orientation) {
        }
    
        /**
         * {@inheritDoc}
         */
        @Override
        public void setDirection(final int direction) {
            if(direction != NORTH && direction != SOUTH && direction != EAST
                    && direction != WEST) {
                throw new IllegalArgumentException("Invalid direction " + direction);
            }
            this.direction = direction;
            revalidate();
            repaint();
        }
    }
    

    Having the figure, we have to create an EditPart that will be displayed by the GEF framework when it finds a OPMStructuralLinkAggregator instance. The code is pretty staightforward:

    
    package com.vainolo.phd.opm.gef.editor.part;
    
    import org.eclipse.draw2d.IFigure;
    import org.eclipse.gef.GraphicalEditPart;
    
    import com.vainolo.phd.opm.gef.editor.figure.OPMStructuralLinkAggregatorFigure;
    import com.vainolo.phd.opm.model.OPMStructuralLinkAggregator;
    
    public class OPMStructuralLinkAggregatorEditPart extends OPMNodeEditPart {
    
        private IFigure figure;
    
        @Override
        protected IFigure createFigure() {
            OPMStructuralLinkAggregator model = (OPMStructuralLinkAggregator) getModel();
            figure = new OPMStructuralLinkAggregatorFigure(model.getKind());
            return figure;
        }
    
        @Override
        protected void refreshVisuals() {
            OPMStructuralLinkAggregator model = (OPMStructuralLinkAggregator) getModel();
            ((GraphicalEditPart) getParent()).setLayoutConstraint(this, figure,model.getConstraints());
        }
    }
    

    And of course, we have to add this to the OPMEditPartFactory:

    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.OPMProceduralLink;
    import com.vainolo.phd.opm.model.OPMProcess;
    import com.vainolo.phd.opm.model.OPMStructuralLinkAggregator;
    
    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 OPMProceduralLink) {
    			// It is important for OPMProceduralLink to be before OPMLink because
    			// they have an is-a relation and we would get the wrong EditPart.
    			part = new OPMProceduralLinkEditPart();
    		} else if (model instanceof OPMLink) {
    			part = new OPMLinkEditPart();
    		} else if (model instanceof OPMStructuralLinkAggregator) {
    		    part = new OPMStructuralLinkAggregatorEditPart();
    		} else {
    			throw new IllegalArgumentException("Model class "+model.getClass()+" not supported yet.");
    		}
    
    		if (part != null) {
    			part.setModel(model);
    		}
    
    		return part;
    	}
    }
    
  5. After all of this work, we can fire up our editor and add/delete some structural links between things in the diagram. Note that because of the refactoring we did in the model, the editor will not be able to open the file that exists in your workspace, so remove it and create a new one. These are the results in my editor:

We still have some work left, like changing the router of the links that start and end at the aggregator that must be a ManhattanConnectionRouter and not a BendpointConnectionRouter as currently implemented, but this will have to wait.

You can find the final project files here.

Hope you enjoyed the tutorial. If you have any comments, please send them. Feedback is always good, and can always improve my work.

Next Tutorial: Creating an OPM GEF Editor – Part 16: Displaying EMF Properties in a GEF Editor.

Published inProgramming

2 Comments

  1. Slava Slava

    A very useful training. Learned a lot. Thank you for that! But why do you not like to point of change, that “behind the scenes”? Very hard to pump every time your project and compare with their own.

Leave a Reply

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

Discover more from Musings of a Strange Loop

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

Continue reading