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.
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 objectState
s can be found here.
action class
to be a class which extends
org.eclipse.gmf.runtime.emf.ui.action.AbstractModelActionDelegate
and implements
org.eclipse.ui.IViewActionDelegate
.action menuPath
to be com.ibm.xtools.modeler.ui.actions.AddDiagramMenu/additions
.
This will add the menu items to the "Add Diagram" sub context menu in the "additions"
grouping (which is at the bottom of the submenu).<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); } } }
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
.
specializes
id to be org.eclipse.gmf.runtime.notation.diagram
.edithelperadvice
to be a class that extends
org.eclipse.gmf.runtime.emf.type.core.edithelper.AbstractEditHelperAdvice
.
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.
edithelperadvice
to be a class that extends
org.eclipse.gmf.runtime.emf.type.core.edithelper.AbstractEditHelperAdvice
.typeId
to be com.ibm.xtools.uml.namespace
.inheritance
to all.<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; } }; } }
The AddDiagramCommand
class has two responsibilities:
CreateDiagramCommand
The AddDiagramCommand#doExecuteWithResult
method creates and executes a new
CreateDiagramCommand
. The
CreateDiagramCommand
constructor requires 5 parameters:
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.
Extend the
org.eclipse.gmf.runtime.diagram.core.viewProviders
extension-point.
viewProvider class
to be a class which extends
org.eclipse.gmf.runtime.diagram.core.providers.AbstractViewProvider
.context semanticHints
to be the diagram type.context viewClass
to be org.eclipse.gmf.runtime.notation.Diagram
.<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; } }
Extend the
org.eclipse.gmf.runtime.diagram.ui.editpartProviders
extension-point.
editpartProvider class
to be a class which extends
org.eclipse.gmf.runtime.diagram.ui.services.editpart.AbstractEditPartProvider
.object
enablement criteria to be a org.eclipse.gmf.runtime.notation.Diagram
class whose type is the expected diagram type.<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.
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.
IconProvider class
to be a class which extends
org.eclipse.gmf.runtime.common.core.service.AbstractProvider
and implements
org.eclipse.gmf.runtime.common.ui.services.icon.IIconProvider
.<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.