This time I’m blogging about customizing labels of GMF figures. To be more precise: this blog is dedicated to show how to display information of other figures’ domain elements in a figure’s label. Here’s a preview:

labels filled with values from other model elements

Preparation

For this example I created a project called example.gmf.labelmapping.referenced.model.
Let’s get started by creating a rudimentary domain model for this example. I chose to call it Domain model (as all the following models) and this is it’s structure:
domain model

Next, create a genmodel for it and append -gen to the model and edit generation paths as shown in the following figure:
prepare genmodel

Also I set the project’s name to be the base package of the model’s root package:
prepare genmodel base package

Once that’s done, generate the model and edit code from the genmodel. Note: you won’t need the editor and test plugins for this to work.

Create a Simple Tooling Definition Model as the next step. The suggested defaults should be fine. However, the default label and title for the connection tool are quite meaningless so I changed them:
prepare gmftool model

Let’s move on to creating a Simple Graphical Definition Model. Again, the suggested defaults are just fine.
But we’ll replace the EntitySuperEntityTargetDecoration by a TriangularTargetDecoration (Note: the points are Template Points) as shown here:
prepare gmfgraph model

Finally, we’ll do a Guide Mapping Model Creation to get our gmfmap model. The suggested defaults are fine again. In the generated gmfmap check, if the creation tools are set right for the Link and Node Mapping.

Feature Implementation

Open your gmfmap model and in that editor open the gmfgraph nodes. If the gmfgraph node is not visible you need to expand some gmfmap nodes until it gets imported.

In the gmfgraph node navigate to Rectangle EntityFigure‘s Flow Layout and force it to be single line and also vertical.
Then add a Label ReferencedEntityInfoFigure to that rectangle. If you rearrange it to be between the Flow Layout and the Label EntityNameFigure it will look nice in the diagram later, but that’s optional. The rectangle will need another Child Access for the new label, so you’ve got to add one. Now, your gmfgraph model should look like that one:
extend gmfgraph model

There’s nothing more to do in the models. Generate the gmfgen model from your gmfmap model and then the diagram plugin from the latter.

Now, it’s time to put your hands on the code. As we generally do not want to mess in generated code we’ll use another plug-in to contain all the customizations. So, create a plugin project called example.gmf.labelmapping.referenced.diagram.custom and open its manifest editor. Make sure required bundles contain the following:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
Require-Bundle: org.eclipse.core.runtime,
 org.eclipse.core.resources,
 org.eclipse.core.expressions,
 org.eclipse.jface,
 org.eclipse.ui.ide,
 org.eclipse.ui.views,
 org.eclipse.ui.navigator,
 org.eclipse.ui.navigator.resources,
 org.eclipse.emf.ecore,
 org.eclipse.emf.ecore.xmi,
 org.eclipse.emf.edit.ui,
 org.eclipse.gmf.runtime.emf.core,
 org.eclipse.gmf.runtime.emf.commands.core,
 org.eclipse.gmf.runtime.emf.ui.properties,
 org.eclipse.gmf.runtime.diagram.ui,
 org.eclipse.gmf.runtime.diagram.ui.printing,
 org.eclipse.gmf.runtime.diagram.ui.printing.render,
 org.eclipse.gmf.runtime.diagram.ui.properties,
 org.eclipse.gmf.runtime.diagram.ui.providers,
 org.eclipse.gmf.runtime.diagram.ui.providers.ide,
 org.eclipse.gmf.runtime.diagram.ui.render,
 org.eclipse.gmf.runtime.diagram.ui.resources.editor,
 org.eclipse.gmf.runtime.diagram.ui.resources.editor.ide,
 example.gmf.labelmapping.referenced.diagram

Save (!) the Manifest editor.

In order to realize the custom label feature we’ll need to change the EntityEditPart‘s behavior. To do so we’ll need to serve an extension point and override a couple of classes of the generated diagram plugin.

Hands on: Select the Extensions tab in the manifest editor and add an extension for GMF’s extension point org.eclipse.gmf.runtime.diagram.ui.editpartProviders. The generated diagram plugin already provides an EditPartProvider for this extension. You can check that by looking into its plugin.xml. There you’ll see which generated class (DomainEditPartProvider in case of this example) serves that extension point. Note that class, you’ll need it shortly.

Make sure the priority is higher than the lowest value in order to force GMF runtime to favor this class over the one from the diagram plugin:

1
2
3
4
5
6
7
8
9
<extension
        point="org.eclipse.gmf.runtime.diagram.ui.editpartProviders">
    <editpartProvider
        class="example.gmf.labelmapping.referenced.diagram.custom.edit.parts.DomainEditPartProviderOverride">
        <Priority
            name="Medium">
        </Priority>
    </editpartProvider>
</extension>

Now save the manifest editor and create the package with the provider class for the extension. The DomainEditPartProviderOverride needs to extend the diagram plugin’s generated EditPartProvider and override it’s constructor to return another overriden class. Here’s the DomainEditPartProviderOverride used in this example:

1
2
3
4
5
6
public class DomainEditPartProviderOverride extends DomainEditPartProvider {
    public DomainEditPartProviderOverride() {
        super();
        setFactory(new DomainEditPartFactoryOverride());
    }
}

As you can see we’re going to override the DomainEditPartFactory of the generated diagram plugin, too. So let’s create the overriding class and make it extend DomainEditPartFactory. The overriden factory is required to create our EntityEditPartOverride in its createEditPart() method:

1
2
3
4
5
6
7
8
9
10
11
@Override
public EditPart createEditPart(EditPart context, Object model) {
    if (model instanceof View) {
        View view = (View) model;
        switch (DomainVisualIDRegistry.getVisualID(view)) {
        case EntityEditPart.VISUAL_ID:
            return new EntityEditPartOverride(view);
        }
    }
    return super.createEditPart(context, model);
}

Create the EntityEditPartOverride class in the same package (example…diagram.custom.edit.parts) and have it extend EntityEditPart.
Remember that we added an additional label to the Entity‘s figure in gmfgraph in the beginning. Now it’s time to fill that label with any data we want and we can get to from our EditPart‘s entity domain element. But how should we do this?

A simple and static solution is given in the following example:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class EntityEditPartOverride extends EntityEditPart {
 
    public EntityEditPartOverride(View view) {
        super(view);
        initializeSuperEntityLabel();
    }
 
    private void initializeSuperEntityLabel() {
        EntityFigure entityFigure = (EntityFigure) getFigure();
        WrappingLabel label = entityFigure.getFigureReferencedEntityInfoFigure();
 
        Entity entity = (Entity) resolveSemanticElement();
        Entity superEntity = entity.getSuperEntity();
        if (superEntity != null) {
            label.setText("inherits " + superEntity.getName());
        } else {
            label.setText("is top level entity");
        }
    }
 
}

The above solution will initialize the label only once. However, more likely, you’ll have a scenario where the value of that label should change while you’re modeling. So let’s go on to a more sophisticated solution. When you’re modeling you’ll change your domain model. As EditParts are controllers they can listen to changes in your domain model. We just need to tell the EntityEditPartOverride to what events it should listen and what it should do when they occur.

In GEF and GMF listening and reacting on model changes is business of EditPolicies. Hence we’ll need an EditPolicy for that and hook it into our EditPart. As that EditPolicy needs to access the WrappingLabel we’ll have to add at least one method to our EntityEditPartOverride. In fact we’ll add two more for installing listener filters for the domain model changes. It’s a good idea to put these methods in an interface. So, that leaves us with two more files to create: An InfoLabelProvider interface our EntityEditPartOverride will implement and a SuperEntityModelChangedEditPolicy.

Let’s define the interface first:

1
2
3
4
5
6
7
8
9
10
public interface InfoLabelProvider {
    void installListenerFilter(String filterId, NotificationListener listener, EObject element,
            EStructuralFeature feature);
 
    void uninstallListenerFilter(String filterId);
 
    WrappingLabel getInfoLabel();
 
    EObject resolveSemanticElement();
}

The InfoLabelProvider requires a forth method to access the Entity domain model element of the EditPart. That method is already present in all EditParts, but it’s pulled into the interface here for the EditPolicy. The install and uninstallListenerFilter methods are just delegating methods to EditPart‘s add and removeListenerFilter methods. Unfortunately, the latter ones are protected and wouldn’t be accessible from the SuperEntityModelChangedEditPolicy. The implementation of these interface methods is quite simple:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Override
public WrappingLabel getInfoLabel() {
    EntityFigure entityFigure = getPrimaryShape();
    return entityFigure.getFigureReferencedEntityInfoFigure();
}
 
@Override
public void installListenerFilter(String filterId, NotificationListener listener, EObject element,
        EStructuralFeature feature) {
    addListenerFilter(filterId, listener, element, feature);
}
 
@Override
public void uninstallListenerFilter(String filterId) {
    removeListenerFilter(filterId);
}

There’s more work to do in the EntityEditPartOverride. The SuperEntityModelChangedEditPolicy – that we’ll still have to create – needs to be registered. Also we need to make sure to do an initial label update after that EditPolicy was created. The following excerpt shows these changes:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private boolean didInitialViewUpdate;
 
public EntityEditPartOverride(View view) {
    super(view);
    didInitialViewUpdate = false;
}
 
private void initializeSuperEntityLabel() {
    if (!didInitialViewUpdate) {
        EditPolicy editPolicy = getEditPolicy(SuperEntityModelChangedEditPolicy.MODEL_CHANGED_ROLE);
        if (editPolicy instanceof SuperEntityModelChangedEditPolicy) {
            SuperEntityModelChangedEditPolicy policy = (SuperEntityModelChangedEditPolicy) editPolicy;
            didInitialViewUpdate = policy.updateSuperEntityLabel();
        }
    }
}
 
@Override
protected void createDefaultEditPolicies() {
    super.createDefaultEditPolicies();
    String role = SuperEntityModelChangedEditPolicy.MODEL_CHANGED_ROLE;
    EditPolicy originalPolicy = getEditPolicy(role);
    installEditPolicy(role, new SuperEntityModelChangedEditPolicy(role, originalPolicy));
    // try to update view if not already done
    initializeSuperEntityLabel();
}

The EditPart‘s method createDefaultEditPolicies is responsible for registering our EditPolicy for the MODEL_CHANGED_ROLE. We will keep a reference to a previously registered EditPolicy as we do not want to interrupt any notification processes.

Finally, let’s switch to creating the SuperEntityModelChangedEditPolicy now as it’s required for the above changes. Create a new package example.gmf.labelmapping.referenced.diagram.custom.edit.policy with the SuperEntityModelChangedEditPolicy inside.

The first part of that class is made up of some fields and the constructor. The MODEL_CHANGED_ROLE is used for registering the EditPolicy in the EditPart. The MY_SEMANTIC_SUPER_ENTITY_LISTENER is used for the listener filter.

1
2
3
4
5
6
7
8
9
10
11
12
public static final String MODEL_CHANGED_ROLE = "ModelChangedRole";
 
private static final String MY_SEMANTIC_SUPER_ENTITY_LISTENER = "MySemanticSuperEntityListener";
 
private final String editPolicyRole;
private final EditPolicy defaultPolicy;
 
public SuperEntityModelChangedEditPolicy(String role, EditPolicy originalPolicy) {
    super();
    this.editPolicyRole = role;
    this.defaultPolicy = originalPolicy;
}

When the EditPolicy is activated it will activate the previously registered EditPolicy. Then it will install itself as listener filter for the superEntity EReference feature of the Entity of the host EditPart. At deactivation it’s the same procedure the other way around.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Override
public void activate() {
    super.activate();
    if (defaultPolicy != null) {
        defaultPolicy.activate();
    }
 
    EditPart hostEP = getHost();
    if (hostEP instanceof InfoLabelProvider) {
        InfoLabelProvider host = (InfoLabelProvider) hostEP;
        Entity entity = (Entity) host.resolveSemanticElement();
        host.installListenerFilter(MY_SEMANTIC_SUPER_ENTITY_LISTENER, this, entity,
                CorePackage.Literals.ENTITY__SUPER_ENTITY);
    }
}
 
@Override
public void deactivate() {
    EditPart hostEP = getHost();
    if (hostEP instanceof InfoLabelProvider) {
        InfoLabelProvider host = (InfoLabelProvider) hostEP;
        host.uninstallListenerFilter(MY_SEMANTIC_SUPER_ENTITY_LISTENER);
    }
    if (defaultPolicy != null) {
        defaultPolicy.deactivate();
    }
    super.deactivate();
}

The SuperEntityModelChangedEditPolicy will be notified whenever the value of the superEntity reference changes. In that case its notifyChanged method get’s called. If notified then the EditPolicy will call its updateSuperEntityLabel method. Again, we do not want to interrupt the notification procedure, that’s why we need to call the parent EditPolicy‘s notifyChanged method.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
private EditPolicy getParentEditPolicy() {
    EditPart parentEP = getHost().getParent();
    while (parentEP != null) {
        EditPolicy editPolicy = parentEP.getEditPolicy(editPolicyRole);
        if (editPolicy != null &amp;&amp; editPolicy instanceof NotificationListener) {
            return editPolicy;
        }
        if (parentEP instanceof TopGraphicEditPart) {
            return null;
        }
        parentEP = parentEP.getParent();
    }
    return null;
}
 
@Override
public void notifyChanged(Notification n) {
    if (n.getEventType() == Notification.SET) {
        updateSuperEntityLabel();
    }
 
    NotificationListener parentPolicy = (NotificationListener) getParentEditPolicy();
    if (parentPolicy != null) {
        parentPolicy.notifyChanged(n);
    }
}

Here comes the last part: updating the label of the figure. It’s done quite similar to the simple example shown before:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
public boolean updateSuperEntityLabel() {
    EditPart host = getHost();
    if (host instanceof InfoLabelProvider) {
        return updateSuperEntityLabel((InfoLabelProvider) host);
    }
    return false;
}
 
protected boolean updateSuperEntityLabel(InfoLabelProvider host) {
    Entity entity = (Entity) host.resolveSemanticElement();
    if (entity == null || entity.getSuperEntity() == null) {
        return false;
    }
    Entity superEntity = entity.getSuperEntity();
    StringBuilder sb = new StringBuilder();
    if (superEntity != null) {
        sb.append("");
    } else {
        sb.append("(got no super Entity)");
    }
    WrappingLabel infoLabel = host.getInfoLabel();
    if (infoLabel == null) {
        return false;
    }
    infoLabel.setText(sb.toString());
    return true;
}

If you took a closer look you might have noticed that this would even print the DomainModel.name feature if the super Entity is from another imported/referenced domain model. Check the following screenshots done with this example:

use "load resource" to load other models

use Load Resource to load other models

select other domain model

select other domain model

reference element from other domain model

reference element from other domain model

see the feature in action

see the feature in action

To close this blog, here’s a ZIP with the example project: example.gmf_.labelmapping.referenced.zip. You’ll need to generate model code, edit plug-in, and diagram plug-in to get rid of the errors and get it running.