Adding Diagram Types

This example will demonstrate how to contribute menu items to the "Add Diagram" popup menu group to add a custom diagram type. The menu action will create a custom diagram which is stored in the EMX model file.

Add Diagram Menu Action

To contribute the menu item to the popup menu for the the project explorer extend the org.eclipse.ui.popupMenus extension-point and add a viewerContribution for targetID org.eclipse.ui.navigator.ProjectExplorer#PopupMenu (other public IDs can be found here).

The visibility of the menu actions is set using action filters defined by the org.eclipse.gmf.runtime.common.ui.services.action.actionFilterProviders extension-point. For this example the action will be visible only when a single item is selected and the selected item is either a UML Model or Package. Some predefined objectStates can be found here.

   <extension
         point="org.eclipse.ui.popupMenus">
      <viewerContribution
            id="com.ibm.examples.AddDiagramContribution"
            targetID="org.eclipse.ui.navigator.ProjectExplorer#PopupMenu">
         <visibility>
            <and>
               <objectState name="isSingleSelection" value="visibility"/>
               <or>
                  <objectState name="umlStrictType" value="Model"/>
                  <objectState name="umlStrictType" value="Package"/>
               </or>
            </and>
         </visibility>
         <action
               label="My Diagram Label"
               icon="icons/myDiagramIcon.gif"
               class="com.ibm.examples.actions.AddDiagramActionDelegate"
               menubarPath="com.ibm.xtools.modeler.ui.actions.AddDiagramMenu/additions"
               enablesFor="1"
               id="myActionId">
         </action>
      </viewerContribution>
   </extension>

The AddDiagramActionDelegate class implements the org.eclipse.gmf.runtime.common.ui.action.AbstractActionDelegate#doRun method to get a creation command for the Diagram element and execute it. To get the command, a new CreateElementRequest is first created using the current selection as the context object and the Diagram element type. The create command can then be retrieved using the element type edit helpers. After the command has successfully executed, the diagram is opened.

Additionally, we can bring the diagram name into direct edit mode within the Project Explorer after the element has been created. To do this, in the postElementCreation method, a call to NavigatorInlineEditUtil#startInlineEdit(EObject element, ICommonViewerContentDescriber describer) is made. The parameters of this method are the diagram object and an ICommonViewerContentDescriber. Implement the ICommonViewerContentDescriber as shown below, delegating to EObjectEditStringProvider for the edit string provider and UMLNavigatorWrapperFactory for retrieving the viewer element.

public class AddDiagramActionDelegate
    extends AbstractModelActionDelegate implements IViewActionDelegate {

    // get the diagram type from the ElementTypeRegistry
    private static final IElementType DIAGRAM_TYPE = ElementTypeRegistry.getInstance()
            .getType("com.ibm.examples.type.myDiagramType");

    protected TransactionalEditingDomain getEditingDomain() {
        return UMLModeler.getEditingDomain();
    }
    
    protected void doRun(IProgressMonitor progressMonitor) {
        ICommand command = getCommand();
        
        if (command == null || !command.canExecute()) {
            return;
        }
        execute(command, progressMonitor, null);
        
        CommandResult result = command.getCommandResult();

        if (result != null && result.getReturnValue() instanceof Diagram) {
            postElementCreation((Diagram) result.getReturnValue());
        }
    }

    protected void postElementCreation(final Diagram newDiagram) {
        try {
            getEditingDomain().runExclusive(new Runnable() {

                public void run() {
                    try {
                        IDiagramEditorInput diagramInput =
                            new DiagramEditorInput(
                                newDiagram);

                        PlatformUI.getWorkbench().getActiveWorkbenchWindow()
                            .getActivePage()
                            .openEditor(diagramInput, "ModelerDiagramEditor");
                    } catch (Exception e) {
                        // TODO log error
                    }
                }
            });
        } catch (Exception e) {
            // TODO log error
        }
        
        // start inline editor for Project Explorer
        NavigatorInlineEditUtil.startInlineEdit(eObject, Describer.getInstance());
    }
    
    protected ICommand getCommand() {

        ICommand result = null;

        CreateElementRequest request = getCreateElementRequest();
        
        if (request != null) {
            IElementType contextType = ElementTypeRegistry.getInstance()
                .getElementType(request.getEditHelperContext());
    
            if (contextType != null) {
                ICommand createCommand = contextType.getEditCommand(request);
    
                if (createCommand != null && createCommand.canExecute()) {
                    result = createCommand;
                }
            }
        }
        return result;
    }

    protected CreateElementRequest getCreateElementRequest() {
        // create a new CreateElementRequest for the diagram type
        return new CreateElementRequest(getSelectedElement(), DIAGRAM_TYPE);
    }
    
    protected EObject getSelectedElement() {
        // Get the selection
        EObject selection = (EObject) ((IAdaptable) getStructuredSelection()
            .getFirstElement()).getAdapter(EObject.class);
        return selection;
    }
    
    private static class Describer implements ICommonViewerContentDescriber {

        private static Describer instance;
        
        private Describer() {
            // private
        }
        
        static Describer getInstance() {
            if (instance == null) {
                instance = new Describer();
            }
            return instance;
        }
        
        public EObject getDisplayableContainer(EObject element) {
            EObject container = element.eContainer();
            while (container != null && container instanceof EAnnotation) {
                container = container.eContainer();
            }
            return container;
        }

        public IEditStringProvider getEditStringProvider() {
            return EObjectEditStringProvider.INSTANCE;
        }

        public Object getViewerElement(EObject object) {
            return UMLNavigatorWrapperFactory.getInstance().getViewerElement(object);
        }
    }
}

Element Types and Advices

The AddDiagramActionDelegate requires an element type for the diagram. Create an extension of the org.eclipse.gmf.runtime.emf.type.core.elementTypes extension-point and add a new specializationType.

An advice binding to the UML metamodel type com.ibm.xtools.uml.namespace is also required. This advice binding will be responsible for creating the command that will create the custom diagram. The namespace metamodel type edit helper currently takes care of creating UML diagrams, therefore this advice binding will add the necessary functionality to handle creating the custom diagram.

   <extension
         point="org.eclipse.gmf.runtime.emf.type.core.elementTypes">
      <specializationType
              edithelperadvice="com.ibm.examples.advices.DiagramEditHelperAdvice"
              id="com.ibm.examples.type.myDiagramType"
              icon="icons/myDiagramIcon.gif"
              name="My Diagram Name">
          <specializes id="org.eclipse.gmf.runtime.notation.diagram"/>
      </specializationType>
      
      <metamodel nsURI="http://www.eclipse.org/uml2/3.0.0/UML">
         <adviceBinding
               class="com.ibm.examples.advices.NamespaceAdviceBinding"
               id="com.ibm.examples.type.namespace"
               inheritance="all"
               typeId="com.ibm.xtools.uml.namespace"/>
      </metamodel>
   </extension>

Lastly, bind the element type and advice binding to the UML context: com.ibm.xtools.uml.type.context.

   <extension point="org.eclipse.gmf.runtime.emf.type.core.elementTypeBindings">
      <binding context="com.ibm.xtools.uml.type.context">
         <elementType pattern="com.ibm.examples.type.*"/>
         <advice pattern="com.ibm.examples.type.*"/>
      </binding>
   </extension>

The role of the DiagramEditHelperAdvice class is to return a GetEditContextCommand from the AbstractEditHelperAdvice#getBeforeEditContextCommand() method. If the create request contains the expected diagram element type, then the edit context is set to be the container from the create request. The container will either be a UML Model or Package. The setting of the context is necessary in order to switch the context of the create command to the element which will contain the diagram.

public class DiagramEditHelperAdvice extends AbstractEditHelperAdvice {

    private static final IElementType DIAGRAM_TYPE = ElementTypeRegistry.getInstance()
            .getType("com.ibm.examples.type.myDiagramType");

    protected ICommand getBeforeEditContextCommand(GetEditContextRequest request) {

        IEditCommandRequest editRequest = request.getEditCommandRequest();

        if (editRequest instanceof CreateElementRequest) {
            CreateElementRequest createRequest = (CreateElementRequest) editRequest;
            IElementType elementType = createRequest.getElementType();

            if (elementType == DIAGRAM_TYPE) {
                EObject container = createRequest.getContainer();
                GetEditContextCommand result = new GetEditContextCommand(request);
                result.setEditContext(container);
                return result;
            }
        }

        return null;
    }
}

The role of the NamespaceAdviceBinding class is to return a create command for the new diagram. The NamespaceAdviceBinding#getBeforeCreateCommand method should check to see whether the element type in the request is the expected diagram element type before returning a command. The command that is returned should be a CreateElementCommand and the creation of the diagram can be done in the CreateElementCommand#doDefaultElementCreation() method. By constructing the command in this manner this will allow for further advising to occur on the ConfigureElementCommand which is created and executed within the CreateElementCommand.

The real work to create the diagram is done in the CreateElementCommand#doDefaultElementCreation() method by constructing and executing a new AddDiagramCommand.

public class NamespaceAdviceBinding extends AbstractEditHelperAdvice {

    private static final IElementType DIAGRAM_TYPE = ElementTypeRegistry.getInstance()
            .getType("com.ibm.examples.type.myDiagramType");

    protected ICommand getBeforeCreateCommand(CreateElementRequest req) {

        IElementType elementType = req.getElementType();

        if (elementType == DIAGRAM_TYPE) {
            req.getParameters().put(IEditCommandRequest.REPLACE_DEFAULT_COMMAND, Boolean.TRUE);
            return getDiagramCreationCommand(req);
        }

        return null;
    }

    private ICommand getDiagramCreationCommand(final CreateElementRequest req) {

        // Get the preference parameter
        PreferencesHint preferencesHint = (PreferencesHint) req
            .getParameter(EditRequestParameters.DIAGRAM_PREFERENCES_HINT);

        if (preferencesHint == null) {
            preferencesHint = PreferencesHint.USE_DEFAULTS;
        }

        final PreferencesHint preferences = preferencesHint;

        return new CreateElementCommand(req) {

            protected EObject doDefaultElementCreation() {

                ICommand addDiagramCommand = new AddDiagramCommand(req
                    .getLabel(), (Namespace) getElementToEdit(), preferences);

                if (addDiagramCommand.canExecute()) {
                    try {
                        addDiagramCommand.execute(new NullProgressMonitor(), null);
                        CommandResult commandResult = addDiagramCommand
                            .getCommandResult();
    
                        if (commandResult.getStatus().isOK()) {
                            return (EObject) commandResult.getReturnValue();
                        }
                    } catch (ExecutionException e) {
                        // TODO log error
                    }
                }
                return null;
            }

            public boolean canExecute() {
                return true;
            }
        };
    }
}

Add Diagram Command

The AddDiagramCommand class has two responsibilities:

  1. Create and execute a CreateDiagramCommand
  2. Add the newly created diagram to a container.

The AddDiagramCommand#doExecuteWithResult method creates and executes a new CreateDiagramCommand. The CreateDiagramCommand constructor requires 5 parameters:

  1. The editing domain from the element which will contain the diagram.
  2. The command label.
  3. The semantic element to be referenced by the diagram.
  4. The diagram kind.
  5. The preference hint.

The result of the command will be the newly created diagram. This diagram then needs to be added to the "uml2.diagrams" annotation on the Model. Get the annotation using UMLUtil#getEAnnotation(EModelElement eModelElement, String source, boolean createOnDemand) and pass true for the createOnDemand parameter. Then add the diagram to the contents of the annotation.

public class AddDiagramCommand extends AbstractTransactionalCommand {

    private final Namespace namespace;

    private PreferencesHint preferencesHint;

    public AddDiagramCommand(String label, Namespace namespace,
            PreferencesHint preferencesHint) {

        super(TransactionUtil.getEditingDomain(namespace), label, getWorkspaceFiles(namespace));
        
        this.namespace = namespace;
        this.preferencesHint = preferencesHint;
    }

    protected CommandResult doExecuteWithResult(
            IProgressMonitor progressMonitor, IAdaptable info)
            throws ExecutionException {
        
        CreateDiagramCommand createDiagramCommand = new CreateDiagramCommand(
            TransactionUtil.getEditingDomain(namespace), getLabel(), getDiagramElement(),
            "MyDiagram", preferencesHint);
        
        createDiagramCommand.execute(progressMonitor, info);
        
        CommandResult result = createDiagramCommand.getCommandResult();
        Diagram diagram = (Diagram) result.getReturnValue();
        
        if (diagram != null) {
            // Put the diagram in the owned diagrams annotation.
            List diagrams = getOwnedDiagrams();
            diagrams.add(diagram);
        }
        
        return result;
    }

    private EObject getDiagramElement() {
        return namespace;
    }
    
    public boolean canExecute() {
        return namespace != null && super.canExecute();
    }
    
    private static final String OWNED_DIAGRAMS_ANNOTATION = "uml2.diagrams";
    
    private List getOwnedDiagrams() {
        EAnnotation annotation = 
            UML2Util.getEAnnotation(namespace, OWNED_DIAGRAMS_ANNOTATION, true);
        return annotation.getContents();
    }
}

Now all that is left is to create the view and edit part providers for the custom diagram.

Diagram View Provider

Extend the org.eclipse.gmf.runtime.diagram.core.viewProviders extension-point.

   <extension
         point="org.eclipse.gmf.runtime.diagram.core.viewProviders">
      <viewProvider class="com.ibm.examples.providers.DiagramViewProvider">
         <Priority name="Medium"/>
         <context
              semanticHints="MyDiagram"
              viewClass="org.eclipse.gmf.runtime.notation.Diagram"/>
      </viewProvider>
   </extension>

The DiagramViewProvider implementation should return the DiagramViewFactory class if the diagramKind is the expected kind.

public class DiagramViewProvider extends AbstractViewProvider {

    protected Class getDiagramViewClass(IAdaptable semanticAdapter, String diagramKind) {
        if ("MyDiagram".equals(diagramKind)) {
            return DiagramViewFactory.class;
        }
        return null;
    }
}

Diagram Edit Part Provider

Extend the org.eclipse.gmf.runtime.diagram.ui.editpartProviders extension-point.

   <extension
         point="org.eclipse.gmf.runtime.diagram.ui.editpartProviders">
      <editpartProvider class="com.ibm.examples.providers.DiagramEditPartProvider">
         <Priority name="Medium"/>
         <object
               class="org.eclipse.gmf.runtime.notation.Diagram(org.eclipse.gmf.runtime.notation)"
               id="MyDiagram">
            <method name="getType()" value="MyDiagram"/>
         </object>
         <context views="MyDiagram"/>
      </editpartProvider>
   </extension>

Similar to the DiagramViewProvider, the DiagramEditPartProvider returns the DiagramEditPart class if the view type is the expected diagram type.

public class DiagramEditPartProvider extends AbstractEditPartProvider {

    protected Class getDiagramEditPartClass(View view) {
        if ("MyDiagram".equals(view.getType())) {
            return DiagramEditPart.class;
        }
        return null;
    }
}

By following the above steps, a custom diagram can be created within a UML Model or Package by using the new menu item in the "Add Diagram" submenu.

Diagram Icon Provider

As a final step, create an icon provider to provide an icon for the custom diagram tree element in the Project Explorer.

Extend the org.eclipse.gmf.runtime.common.ui.services.iconProviders extension-point and use the exact same object and context enablement criteria as that which was used for the editpartProviders extension.

   <extension
         point="org.eclipse.gmf.runtime.common.ui.services.iconProviders">
      <IconProvider class="com.ibm.examples.providers.DiagramIconProvider">
         <Priority name="Medium"/>
         <object
               class="org.eclipse.gmf.runtime.notation.Diagram(org.eclipse.gmf.runtime.notation)"
               id="MyDiagram">
            <method
                  name="getType()"
                  value="MyDiagram"/>
         </object>
         <context elements="MyDiagram"/>
      </IconProvider>
   </extension>

The DiagramIconProvider should provide only if the operation hint can adapt to a Diagram with the expected diagram type.

public class DiagramIconProvider extends AbstractProvider
    implements IIconProvider {

    private static final IElementType DIAGRAM_TYPE = ElementTypeRegistry.getInstance()
            .getType("com.ibm.examples.myDiagramType");

    private static Image diagramImage = ImageDescriptor.createFromURL(
        DIAGRAM_TYPE.getIconURL()).createImage();
    
    public Image getIcon(IAdaptable hint, int flags) {
        return diagramImage;
    }

    public boolean provides(IOperation operation) {
        if (operation instanceof IconOperation) {
            IconOperation iconOperation = (IconOperation)operation;
            
            IAdaptable adapter = iconOperation.getHint();
            
            if (adapter == null){
                return false;
            }
            
            Diagram diagram = (Diagram)adapter.getAdapter(Diagram.class);
            if (diagram != null &&
                    "MyDiagram".equals(diagram.getType())) {
                return true;
            }
        }
        return false;
    }
}

See Customizing Icons for more information on icon providers.

To add tooling for the new diagram see the Customizing Tools guide.


Legal notices