One of the first Hello World examples when dealing with contributions to Eclipse extension points is adding an objectContribution or a viewerContribution to a popup menu. However, if one wants to add a more flexible contribution which is hided or disabled in certain circumstances, documentation gets rare. This article describes how to implement these advanced featues by example. Let's assume we have an EMF model of objects MyObject
which implement a property called type
. EMF can generate item and label providers for tree editors. Let's assume we have implemented such an editor and we want to contribute an action which is only visible for a particular type value.
Update: This article uses the old API of contributing actions to views' and objects' popup menus. There is a new API which is described in a more recent post.
As a first step we implement the corresponding ActionDelegate
and register it as an object contribution: This
<extension point="org.eclipse.ui.popupMenus"> <objectContribution adaptable="false" id="my.contribution.id" objectClass="my.model.MyObject"> <action class="my.contribution.MyActionDelegate" enablesFor="1" id="my.contribution.action.id" label="Do something only with type FOO" menubarPath="additions"> </action> </objectContribution> </extension>
This way, however, the contribution is visible and enabled always. We want to hide (or disable) it, if selectedObject.getType()
is not equal to "FOO"
. The popupMenus extension point lets us specify visibility and enablement constraints and we can use the objectState element to query the object's state. However, this is not as simple as it sounds, because objectState can only query objects which implement the IActionFilter
interface and since MyObject
is a EMF generated class, we cannot simply implement the interface. But what we can do, is adapt. So we write an adapter and its factory:
public class MyObjectToActionFilterAdapter implements IActionFilter { private static final Object MYOBJECT_TYPE = "objectType"; private static ArtifactToActionFilterAdapter INSTANCE = new ArtifactToActionFilterAdapter(); private ArtifactToActionFilterAdapter() {} @Override public boolean testAttribute(Object target, String name, String value) { if (target instanceof MyObject) { MyObject obj = (MyObject) target; if(MYOBJECT_TYPE.equals(name)) { return value.equals(obj.getType()); } } return false; } public static ArtifactToActionFilterAdapter getInstance() { return INSTANCE; } } public class MyActionFilterAdapterFactory implements IAdapterFactory { @SuppressWarnings("unchecked") @Override public Object getAdapter(Object adaptableObject, Class adapterType) { if(adapterType == IActionFilter.class) return MyObjectToActionFilterAdapter.getInstance(); return null; } @SuppressWarnings("unchecked") @Override public Class[] getAdapterList() { return new Class[] {IActionFilter.class}; } }
Note that the adapter can be implemented as a singleton because the testAttribute
implementation is called with the object to be tested. Now we have to register our adapter factory using:
<extension point="org.eclipse.core.runtime.adapters"> <factory adaptableType="my.model.MyObject" class="my.contribution.MyActionFilterAdapterFactory"> <adapter type="org.eclipse.ui.IActionFilter"> </adapter> </factory> </extension>
and after that we are ready to declare our constraints:
<extension point="org.eclipse.ui.popupMenus"> <objectContribution adaptable="false" id="my.contribution.id" objectClass="my.model.MyObject"> <action class="my.contribution.MyActionDelegate" enablesFor="1" id="my.contribution.action.id" label="Do something only with type FOO" menubarPath="additions"> </action> <visibility> <objectState name="objectType" value="FOO"> </objectState> </visibility> </objectContribution> </extension>
This causes the action to appear only if the property type of MyObject
has the value FOO
. Instead of hiding the action, we can also disable it using the element <enablement> istead of <visibility>. In this case, however, the <enablement> constraint has to be a child of the <action> element instead of a sibling.