Customizing Diagram Shapes

Stereotype Shape

This example will demonstrate how to create custom views for stereotyped elements.

Custom View

Extend the org.eclipse.gmf.runtime.diagram.core.viewProviders extension-point and set the viewProvider class to be a class which extends org.eclipse.gmf.runtime.diagram.core.providers.AbstractViewProvider.

The custom view needs to be provided for two types of actions:

  1. Creating a new UML Class with the expected applied stereotype via Palette Tools or Action Bars etc...
  2. Visualizing an existing UML class on the diagram with the expected applied stereotype via drag and drop from Project Explorer.

To satisfy the first scenario, create an object context for class com.ibm.xtools.uml.type.IStereotypedElementType. For this context object, getStereotypeName() should return the fully qualified name of the expected stereotype, in this example the expected qualified name is MyProfile::MyStereotype.

To satisfy the second scenario, create another object context for class org.eclipse.gmf.runtime.emf.core.util.IProxyEObject. IProxyEObject is used because it provides a method to obtain the class ID without resolving the EObject if it happens to be a proxy. For this context object, getProxyClassID() should return the UML element ID for which the stereotype may be applied. A further check for the expected applied stereotype can either be implemented inside the view provider or by using a static method reference in the xml. This example implements this additional check in the view provider code.

   <extension
         point="org.eclipse.gmf.runtime.diagram.core.viewProviders">
      <viewProvider class="com.ibm.xtools.examples.atm.profiles.providers.MyViewProvider">
         <Priority name="Medium"/>

         <object class="com.ibm.xtools.uml.type.IStereotypedElementType(com.ibm.xtools.uml.type)" id="MyNode1">
            <method
	           name="getStereotypeName()"
	           value="MyProfile::MyStereotype">
            </method>
         </object>

         <!-- check for the applied stereotype in the provider code -->
         <object class="org.eclipse.gmf.runtime.emf.core.util.IProxyEObject(org.eclipse.gmf.runtime.emf.core)" id="MyNode2">
            <method
                  name="getProxyClassID()"
                  value="uml.Class">
            </method>
         </object>
         
         <context
              elements="MyNode1, MyNode2"
              viewClass="org.eclipse.gmf.runtime.notation.Node"/>
      </viewProvider>
   </extension>

The MyViewProvider implementation should provide a view factory class for each of the above scenarios.

To satisfy the first scenario, attempt to get an adapter for IStereotypedElementType.class. If the adapted IStereotypedElementType is the MyStereotype element type, then return the corresponding view factory class.

To satisfy the second scenario, get the semantic element and check if the expected applied stereotype can be found, and if so return the corresponding view factory class.

public class MyViewProvider
    extends AbstractViewProvider {

    IElementType MY_STEREOTYPE_TYPE = ElementTypeRegistry.getInstance()
            .getType("com.ibm.examples.type.myStereotype");
    
    protected Class getNodeViewClass(IAdaptable semanticAdapter, View containerView, String semanticHint) {
        Class clazz = null;
        
        if (semanticHint != null && semanticHint.length() == 0) {
            Object elementType = semanticAdapter.getAdapter(IStereotypedElementType.class);
            if (elementType == MY_STEREOTYPE_TYPE) {
                clazz = MyViewFactory.class;
            } else {
                // search for applied stereotype
                EObject eObject = getSemanticElement(semanticAdapter);
                if (eObject instanceof Element) {
                    Element element = (Element)eObject;
                    List stereotypes = element.getAppliedStereotypes();
                    for (Iterator i = stereotypes.iterator(); i.hasNext(); ) {
                        Stereotype stereotype = (Stereotype)i.next();
                        String name = stereotype.getQualifiedName();
                        if ("MyProfile::MyStereotype".equals(name)) {
                            clazz = MyViewFactory.class;
                            break;
                        }
                    }
                }
            }
        }
        
        return clazz;
    }
}

The MyViewFactory extends org.eclipse.gmf.runtime.diagram.ui.view.factories.AbstractShapeViewFactory. Override the decorateView method to set the type for the view and to append view children. In this example the MyStereotype view will make use of the UML Name text compartment by appending a child to the stereotype view using the CommonParserHint#NAME semantic hint.

public class MyViewFactory
	extends AbstractShapeViewFactory {

    protected void decorateView(View containerView, View view, IAdaptable element, String semanticHint, int index, boolean persisted) {
        // call super.decorateView to initialize view from preferences
        super.decorateView(containerView, view, element, semanticHint, index, persisted);
        
        // set view type
        view.setType("MyProfile::MyStereotype");

        // append the UML name text compartment
        getViewService().createNode(element, view, CommonParserHint.NAME, ViewUtil.APPEND, persisted, getPreferencesHint());
    }
}

Custom Edit Part

In order to create a visual representations of the notation view provided by the view provider, extend the org.eclipse.gmf.runtime.diagram.ui.editpartProviders extension-point and set the editpartProvider class to be a class which extends org.eclipse.gmf.runtime.diagram.ui.services.editpart.AbstractEditPartProvider. Set the enablement critiera to be a org.eclipse.gmf.runtime.notation.Node class whose type is the expected MyProfile::MyStereotype type.

   <extension
         point="org.eclipse.gmf.runtime.diagram.ui.editpartProviders">
      <editpartProvider class="com.ibm.examples.providers.MyEditPartProvider">
         <Priority name="Medium"/>
         <object
               class="org.eclipse.gmf.runtime.notation.Node(org.eclipse.gmf.runtime.notation)"
               id="MyNode">
            <method name="getType()" value="MyProfile::MyStereotype"/>
         </object>
         <context views="MyNode"/>
      </editpartProvider>
   </extension>

Type MyEditPartProvider returns the MyEditPart class if the view type is the expected MyStereotype type.

public class MyEditPartProvider extends AbstractEditPartProvider {

    private Map nodeMap = new HashMap();
    {
        nodeMap.put("MyProfile::MyStereotype", MyEditPart.class);
    }
    
    protected Class getNodeEditPartClass(View view) {
        return (Class) nodeMap.get(view.getType());
    }
}

The MyEditPart class should extend org.eclipse.gmf.runtime.diagram.ui.editparts.ShapeNodeEditPart to inherit the basic functions of a shape node. Override createNodeFigure() to return a NodeFigure. Since the MyStereotype view has a text compartment child, override getPrimaryChildEditPart() to return the corresponding text compartment edit part. This will force the text compartment into direct edit mode when the edit part is created on the diagram.

public class MyEditPart extends ShapeNodeEditPart {

    public MyEditPart(View view) {
        super(view);
    }

    protected NodeFigure createNodeFigure() {
        return new CustomFigure();
    }

    public EditPart getPrimaryChildEditPart() {
        return getChildBySemanticHint(CommonParserHint.NAME);
    }
}

Custom Figure

The CustomFigure class, for the MyEditPart above, extends org.eclipse.gmf.runtime.gef.ui.figures.NodeFigure. The figure is responsible for drawing the visual aspect of the edit part via the NodeFigure#paintFigure(Graphics graphics) method. In this example, paintFigure creates a rectangle with a triangular roof to represent the MyStereotype shape. Using a non-rectangular shape for the figure surfaces a couple complications.

The first complication is that the the text compartment will show up on the roof, therefore a margin border with sufficient padding is added to push the text compartment down into the main area of the figure.

The second complication is that in order to allow connections to anchor to the slanted roof, the class must implement org.eclipse.gmf.runtime.draw2d.ui.figures.IPolygonAchorableFigure and specify a list of points representing the outline of the figure otherwise it is assumed that the figure is a rectangular shape. This would result in connection ends appearing to be unattached.

Finally, the figure needs a layout manager to handle the layout of child figures.

public class CustomFigure
    extends NodeFigure
    implements IPolygonAnchorableFigure {
    
    private static int DPtoLP_1 = MapModeUtil.getMapMode().DPtoLP(1);
    private static int DPtoLP_3 = MapModeUtil.getMapMode().DPtoLP(3);
    private static int DPtoLP_10 = MapModeUtil.getMapMode().DPtoLP(10);
    private static int DPtoLP_12 = MapModeUtil.getMapMode().DPtoLP(12);

    private static final int MARGIN_TOP_BOTTOM = DPtoLP_3;
    
    private static final int MARGIN_LEFT_RIGHT = DPtoLP_10;
    
    private static final int ROOF_HEIGHT = DPtoLP_12;

    public CustomFigure() {
        setOpaque(true);
        setLayoutManager(new ConstrainedToolbarLayout());
        setBorder(new MarginBorder(ROOF_HEIGHT,
            MARGIN_LEFT_RIGHT, MARGIN_TOP_BOTTOM, MARGIN_LEFT_RIGHT));
    }

    protected void paintFigure(Graphics g) {
        Rectangle r = getHandleBounds().getCopy().expand(DPtoLP_1,DPtoLP_1);

        RGB newForeRGB = ColorUtil.blend(g.getForegroundColor()
            .getRGB(), g.getBackgroundColor().getRGB());
        
        g.setBackgroundColor( DiagramColorRegistry.getInstance()
            .getColor(newForeRGB));
        
        // draw the roof
        Point p1 = r.getTopLeft().getTranslated(0, ROOF_HEIGHT);
        Point p2 = r.getTopLeft().getTranslated((int)(r.width * 0.5), 0);
        Point p3 = r.getTopRight().getTranslated(0, ROOF_HEIGHT);
        
        PointList pList = new PointList();
        pList.addPoint(p1);
        pList.addPoint(p2);
        pList.addPoint(p3);

        g.fillPolygon(pList);
        g.drawPolygon(pList);
        
        // draw the outline
        Rectangle outline = new Rectangle();
        outline.setLocation(r.getLocation().translate(DPtoLP_1, ROOF_HEIGHT));
        // width and height - DPtoLP_1 due to line width of 1
        outline.setSize(r.width - DPtoLP_1*2, r.height - ROOF_HEIGHT - DPtoLP_1);
        g.drawRectangle(outline);
    }

    public PointList getPolygonPoints() {
        PointList ptList = new PointList();     
        Rectangle rect = getHandleBounds().getCopy().expand(DPtoLP_1,DPtoLP_1);

        Point p1 = rect.getBottomRight();
        Point p2 = rect.getBottomLeft();
        Point p3 = rect.getTopLeft().getTranslated(0, ROOF_HEIGHT);
        Point p4 = rect.getTopLeft().getTranslated((int)(rect.width*0.5), 0);
        Point p5 = rect.getTopRight().getTranslated(0, ROOF_HEIGHT);

        ptList.addPoint(p1);
        ptList.addPoint(p2);
        ptList.addPoint(p3);
        ptList.addPoint(p4);
        ptList.addPoint(p5);
        ptList.addPoint(p1);
    
        return ptList;
    }
}

Adding Compartments

Adding compartments is done in the same way as adding any other view child. The compartments require a view and corresponding edit part. Compartments can be made to either contain other shapes or list items:

To add compartments as children to a view, override the decorateView method of the compartments parent view factory and append the compartment views as children.

public class MyParentViewFactory extends AbstractShapeViewFactory {

    protected void decorateView(View containerView, View view, IAdaptable element, String semanticHint, int index, boolean persisted) {
        super.decorateView(containerView, view, element, semanticHint, index, persisted);

        getViewService().createNode(element, view, "myListCompartment", ViewUtil.APPEND, persisted, getPreferencesHint());
        getViewService().createNode(element, view, "myShapeCompartment", ViewUtil.APPEND, persisted, getPreferencesHint());
    }
}

There needs to be a view provider which will provide a view for each of the compartments. Extend the org.eclipse.gmf.runtime.diagram.core.viewProviders extension-point and set the viewProvider class to be a class which extends org.eclipse.gmf.runtime.diagram.core.providers.AbstractViewProvider.

Add a context to the view provider extension for the myShapeCompartment and myListCompartment semanticHints.

   <extension
         point="org.eclipse.gmf.runtime.diagram.core.viewProviders">
      <viewProvider class="com.ibm.examples.providers.MyCompartmentsViewProvider">
         <Priority name="Medium"/>
         <context
              semanticHints="myShapeCompartment, myListCompartment"
              viewClass="org.eclipse.gmf.runtime.notation.Node"/>
      </viewProvider>
   </extension>

The MyCompartmentsViewProvider class should return a view factory class for the myShapeCompartment and myListCompartment semantic hints.

public class MyCompartmentsViewProvider extends AbstractViewProvider {

    private Map nodeMap = new HashMap();
    {
        nodeMap.put("myShapeCompartment", ResizableCompartmentViewFactory.class);
        nodeMap.put("myListCompartment", ListCompartmentViewFactory.class);
    }
    
    protected Class getNodeViewClass(IAdaptable semanticAdapter, View containerView, String semanticHint) {
        return (Class)nodeMap.get(semanticHint);
    }
}

In order to create a visual representations of the notation view provided by the view provider, extend the org.eclipse.gmf.runtime.diagram.ui.editpartProviders extension-point and set the editpartProvider class to be a class which extends org.eclipse.gmf.runtime.diagram.ui.services.editpart.AbstractEditPartProvider. Set the enablement critiera to be a org.eclipse.gmf.runtime.notation.Node class whose type is the expected myShapeCompartment or myListCompartment type.

   <extension
         point="org.eclipse.gmf.runtime.diagram.ui.editpartProviders">
      <editpartProvider class="com.ibm.examples.providers.MyCompartmentsEditPartProvider">
         <Priority name="Medium"/>
         <object
               class="org.eclipse.gmf.runtime.notation.Node(org.eclipse.gmf.runtime.notation)"
               id="myCompartments">
            <method name="getType()" value="myShapeCompartment, myListCompartment"/>
         </object>
         <context views="myCompartments"/>
      </editpartProvider>
   </extension>

The MyCompartmentEditPartProvider class should return an edit part class for the myShapeCompartment and myListCompartment view types.

public class MyCompartmentEditPartProvider extends AbstractEditPartProvider {

    private Map nodeMap = new HashMap();
    {
        nodeMap.put("myShapeCompartment", MyShapeCompartmentEditPart.class);
        nodeMap.put("myListCompartment", MyListCompartmentEditPart.class);
    }
    
    protected Class getNodeEditPartClass(View view) {
        return (Class) nodeMap.get(view.getType());
    }
}

The MyShapeCompartmentEditPart class can be as simple as extending ShapeCompartmentEditPart and implementing a constructor. ShapeCompartmentEditPart is a generic (sub) shape container that holds instances of ShapeNodeEditParts and manages the display of ConnectionNodeEditParts anchored to these shape edit part instances.

public class MyShapeCompartmentEditPart extends ShapeCompartmentEditPart {

    public MyShapeCompartmentEditPart(View view) {
        super(view);
    }
}

List compartments are generally created to be canonical. In order to add canonical behavior to the MyListCompartmentEditPart class install a canonical edit policy for the EditPolicyRoles#CANONICAL_ROLE. Override the hasModelChildrenChanged(Notification evt) method to determine if a given event affects the semantic model children.

public class MyListCompartmentEditPart extends ListCompartmentEditPart {

    public MyListCompartmentEditPart(EObject model) {
        super(model);
    }

    protected void createDefaultEditPolicies() {
        super.createDefaultEditPolicies();
        installEditPolicy(EditPolicyRoles.CANONICAL_ROLE, new MyCanonicalEditPolicy());
    }
    
    protected boolean hasModelChildrenChanged(Notification evt) {
        Object feature = evt.getFeature();
        return feature == UMLPackage.eINSTANCE.getClass_OwnedOperation();
    }
}

The MyCanonicalEditPolicy class should extend org.eclipse.gmf.runtime.diagram.ui.editpolicies.CanonicalEditPolicy and implement the CanonicalEditPolicy#getSemanticChildrenList() method. CanonicalEditPolicy#getSemanticChildrenList() should return a list of semantic elements to be displayed in the list compartment.

public class MyCanonicalEditPolicy extends CanonicalEditPolicy {

    protected List getSemanticChildrenList() {
        return ((Class) resolveSemanticElement()).getOwnedOperations();
    }

    protected String getDefaultFactoryHint() {
        return CommonParserHint.NAME;
    }
}

Note: An edit part needs to be provided for each semantic child. The edit part for list items generally extends the org.eclipse.gmf.runtime.diagram.ui.editparts.TextCompartmentEditPart class.

Canonical: Canonical is the terminoligy used to describe a container that keeps its view of semantic data synchronized with the semantic children. This means that the child views of canonical containers are created on demand whenever the semantic children change. When the diagram is saved, canonical views are not persisted in the diagram model.

Adding Drag and Drop Behaviour

Start by creating a class which extends org.eclipse.gmf.runtime.diagram.ui.editpolicies.DragDropEditPolicy. Override DragDropEditPolicy#getDropElementCommand(EObject element, DropObjectsRequest request) to return the drop command. In this example, a command which adds a dropped UML Package to the container UML Package is returned.

public class MyDragDropEditPolicy extends DragDropEditPolicy {

    protected Command getDropElementCommand(EObject element, DropObjectsRequest request) {
        if (element instanceof Package) {
            final Package item = (Package)element;

            return new ICommandProxy(
                new AbstractTransactionalCommand(getEditingDomain(), "Add Item", null) {
            
                protected CommandResult doExecuteWithResult(IProgressMonitor monitor, IAdaptable info) {
                    Package container = (Package)ViewUtil.resolveSemanticElement((View)getHost().getModel())
                    container.getNestedPackages().add(item);
                    return CommandResult.newOKCommandResult();
                }
                
            });
        }

        return null;
    }
}

This edit policy needs to be installed on an edit part. In the edit part class, install the MyDragDropEditPolicy edit policy for the EditPolicyRoles.DRAG_DROP_ROLE role.

public class MyShapeNodeEditPart extends ShapeNodeEditPart {
    
    ...
    
    protected void createDefaultEditPolicies() {
        super.createDefaultEditPolicies();
    
        installEditPolicy(EditPolicyRoles.DRAG_DROP_ROLE, new MyDragDropEditPolicy());
    }

    ...
    
}

To install this, or any other, edit policy onto an existing edit part, see the Installing Edit Policies on Existing Edit Parts section for more info

Adding Double-Click Behaviour

Adding double-clicking behaviour to edit parts is generally useful to open another editor using model element of the edit part as the context. However it can be used to perform any sort of command. This example will demonstrate how to link double-click behaviour to an edit part to executed which will open a message dialog.

Start by creating a class which extends org.eclipse.gmf.runtime.diagram.ui.editpolicies.OpenEditPolicy. Override OpenEditPolicy#getOpenCommand(Request request) to return the open command. In this example, a command which opens a message dialog is returned.

public class MyOpenEditPolicy extends OpenEditPolicy {

    protected Command getOpenCommand(Request request) {
        return new Command("Test double-click") {
            public void execute() {
                Shell shell = new Shell();
                MessageDialog.openInformation(
                    shell,
                    "My Title",
                    "Double-click command executed.");
            }
        };
    }
}

This edit policy needs to be installed on an edit part. In the edit part class, install the MyOpenEditPolicy edit policy for the EditPolicyRoles.OPEN_ROLE role.

public class MyShapeNodeEditPart extends ShapeNodeEditPart {
    
    ...
    
    protected void createDefaultEditPolicies() {
        super.createDefaultEditPolicies();
    
        installEditPolicy(EditPolicyRoles.OPEN_ROLE, new MyOpenEditPolicy());
    }

    ...
    
}

To install this, or any other, edit policy onto an existing edit part, see the Installing Edit Policies on Existing Edit Parts section for more info

Installing Edit Policies on Existing Edit Parts

In the above examples for Adding Drag and Drop Behaviour and Adding Double-Click Behaviour, it is possible to install these edit policies onto existing edit parts in the RMP UML Modeler. This is done by extending the org.eclipse.gmf.runtime.diagram.ui.editpolicyProviders extension-point. Defining the extension is similar to that for view and edit part providers.

   <extension
         point="org.eclipse.gmf.runtime.diagram.ui.editpolicyProviders">
      <editpolicyProvider class="com.ibm.examples.providers.MyEditPolicyProvider">
         <Priority name="Medium"/>
         <object
               class="org.eclipse.gmf.runtime.diagram.ui.editparts.IGraphicalEditPart(org.eclipse.gmf.runtime.diagram.ui)"
               id="InterfaceEP">
            <method
                  name="resolveSemanticElement().eClass().getName()"
                  value="Interface"/>
         </object>
         <context editparts="InterfaceEP"/>
      </editpolicyProvider>
   </extension>

The MyEditPolicyProvider should include the same, or stricter, provides criteria defined in the xml extension. It should also implement createEditPolicies(EditPart ep) to create and install the edit policy on the given edit part. In this example, the MyOpenEditPolicy is installed on all edit parts with semantic element of type UML Interface.

public class MyEditPolicyProvider extends AbstractProvider implements IEditPolicyProvider {

    public void createEditPolicies(EditPart ep) {
        if (ep instanceof IGraphicalEditPart) {
            EObject element = ((IGraphicalEditPart)ep).resolveSemanticElement();
            if (element instanceof Interface) {
                ep.installEditPolicy(EditPolicyRoles.OPEN_ROLE, new MyOpenEditPolicy());
            }
        }
    }

    public boolean provides(IOperation operation) {
        if (operation instanceof CreateEditPoliciesOperation) {
            EditPart ep = ((CreateEditPoliciesOperation) operation).getEditPart();
            if (ep instanceof IGraphicalEditPart && ep instanceof IPrimaryEditPart) {
                EObject element = ((IGraphicalEditPart)ep).resolveSemanticElement();
                return element instanceof Interface;
            }
        }
        return false;
    }
}

Adding Decorations

The Decoration Service gives clients the ability to decorate diagram elements with an image or figure. A provider of the decoration service will be able to add an adornment to any diagram element. The decoration is typically an image, but can be any sort of graphics object or figure. A provider of the decoration service is not restricted to any specific graphic type.


eg. Cross Model Reference Decorator

To add decorations extend the org.eclipse.gmf.runtime.diagram.ui.decoratorProviders extension-point.

   <extension point="org.eclipse.gmf.runtime.diagram.ui.decoratorProviders">
      <decoratorProvider class="com.ibm.examples.providers.MyDecoratorProvider">
         <Priority name="Lowest"/>
         <object
               class="org.eclipse.gmf.runtime.diagram.ui.editparts.IPrimaryEditPart(org.eclipse.gmf.runtime.diagram.ui)"
               id="class">
            <method
                  name="resolveSemanticElement().eClass().getName()"
                  value="Class"/>
         </object>
         <context decoratorTargets="class"/>
      </decoratorProvider>
   </extension>

The MyDecoratorProvider should provide for the same, or stricter, criteria which was used in the xml extension.

In the MyDecoratorProvider#createDecorators(IDecoratorTarget decoratorTarget) method, install the decorator on the decoratorTarget using a unique key. If another decorator is installed on the same target with the same key then it will override the previous one installed. GMF defines a few standard decorator keys in the IDecoratorKeys interface: BOOKMARK, CROSS_MODEL_REFERENCE, and UNRESOLVED_VIEW. Since this example decorator will not replace any of the decorators for the pre-defined keys, an arbitrary unique key is used.

public class MyDecoratorProvider
        extends AbstractProvider implements IDecoratorProvider {

    public void createDecorators(IDecoratorTarget decoratorTarget) {
        IGraphicalEditPart editPart = (IGraphicalEditPart)decoratorTarget.getAdapter(IGraphicalEditPart.class);
        if (editPart instanceof IPrimaryEditPart) {
            EObject eObject = editPart.resolveSemanticElement();
            if (eObject instanceof Class) {
                decoratorTarget.installDecorator("myDecoratorKey", new MyDecorator(decoratorTarget));
            }
        }
    }

    public boolean provides(IOperation operation) {
        if (!(operation instanceof CreateDecoratorsOperation)) {
            return false;
        }
        IDecoratorTarget decoratorTarget = ((CreateDecoratorsOperation) operation).getDecoratorTarget();
        IGraphicalEditPart editPart = (IGraphicalEditPart)decoratorTarget.getAdapter(IGraphicalEditPart.class);
        if (editPart instanceof IPrimaryEditPart) {
            EObject eObject = editPart.resolveSemanticElement();
            return  eObject instanceof Class;
        }
        return false;
    }
}

The installed decorator should extend org.eclipse.gmf.runtime.diagram.ui.services.decorator.AbstractDecorator. The IDecorator#refresh() method is responsible for setting / removing the decoration from the target.

In this example, a NotificationListener is added to the DiagramEventBroker for the element of interest in the MyDecorator#activate() method. The DiagramEventBroker is a model server listener that broadcasts EObject events to all registered listeners. This will allow the listener in the decorator to refresh the decoration whenever the model changes. The listener is removed from the element in the MyDecorator#deactivate() deactivate method.

The DiagramEventBroker is a model server listener that broadcasts EObject events to all registered listeners.

The MyDecorator#refresh() method has the responsibility of setting / removing the decoration from the target. In this example, shape decorations are added using the IDecoratorTarget#addShapeDecoration(..) method. Adding shape decorations requires passing a direction for the location of the decoration. The available directions are found in the IDecoratorTarget.Direction enumeration. The possible directions are: CENTER, EAST, NORTH, NORTH_EAST, NORTH_WEST, SOUTH, SOUTH_EAST, SOUTH_WEST, and WEST.

If the decoration target is a connection, use the IDecoratorTarget#addConnectionDecoration(..) method to add decorations along any point of the connection.

public class MyDecorator extends AbstractDecorator {

    private final Image ABSTRACT_IMG = 
        ImageDescriptor.createFromURL("file://abstract.gif").createImage();

    private ChangeListener changeListener;
    
    protected EObject notifier;
    
    private class ChangeListener
        implements NotificationListener {

        IDecorator decorator;

        public ChangeListener(IDecorator decorator) {
            this.decorator = decorator;
        }

        public void notifyChanged(Notification evt) {
            if (evt.getFeature() == UMLPackage.eINSTANCE.getClassifier_IsAbstract()) {
                decorator.refresh();
            }
        }
    }
    
    public MyDecorator(IDecoratorTarget decoratorTarget) {
        super(decoratorTarget);
        changeListener = new ChangeListener(this);
    }
    
    public void activate() {
        IGraphicalEditPart editPart = (IGraphicalEditPart) getDecoratorTarget().getAdapter(IGraphicalEditPart.class);
        EObject element = editPart.resolveSemanticElement();
        if (element instanceof Class) {
            notifier = element;
            DiagramEventBroker.getInstance(UMLModeler.getEditingDomain()).
                addNotificationListener(notifier, changeListener);
        }
    }
    
    public void deactivate() {
        if (notifier != null) {
            DiagramEventBroker.getInstance(UMLModeler.getEditingDomain()).
                removeNotificationListener(notifier, changeListener);
            notifier = null;
        }
        super.deactivate();
    }
    
    public void refresh() {
        IGraphicalEditPart editPart = (IGraphicalEditPart) getDecoratorTarget().getAdapter(IGraphicalEditPart.class);
        EObject element = editPart.resolveSemanticElement();
        if (element instanceof Class) {
            Class clazz = (Class)element;
            removeDecoration();
            if (clazz.isAbstract()) {
                setDecoration(getDecoratorTarget().addShapeDecoration(ABSTRACT_IMG, IDecoratorTarget.Direction.NORTH_EAST, 0, false));
            }
        }
    }
}

Legal notices