Following a reader's comment on my earlier post, I found the new API (Extension point description of org.eclipse.ui.menus) which unifies all command contributions to Eclipse menus, views, objects, toolbars etc.
With this example it requires a lot less code to add an action (well, the new API uses commands) which is visible or enabled dynamically based on an object property. However, at the same time, we have to do quite a bit more extension configurations: Instead of a single declaration of an org.eclipse.ui.popupMenus extension, we need all this:
- A command (which only declares the abstract concept of the semantic behavior):
<extension point="org.eclipse.ui.commands"> <command description="Do something with a model element" id="my.command.id" name="Do Something"> </command> </extension>
ActionDelegate
):<extension point="org.eclipse.ui.handlers"> <handler class="my.contribution.MyCommandHandler" commandId="my.command.id"> </handler> </extension>
<extension point="org.eclipse.ui.menus"> <menuContribution locationURI="popup:org.eclipse.ui.popup.any?after=additions"> <command commandId="my.command.id" style="push"> <visibleWhen> <with variable="activeMenuSelection"> <iterate> <instanceof value="my.model.MyObject"> </instanceof> </iterate> </with> </visibleWhen> </command> </menuContribution> </extension>
Note that the locationURI offers a lot of options - the case above simulates the objectContribution behavior, as the command is added to any popup menu and the visibility is determined by the selected objects' types. Instead of behaving like an objectContribution, we can simply change the locationURI to menu:<view-or-menu-id>
or to toolbar:<view-or-toolbar-id>
in order to contribute the command to a view's menu or toolbar, respectively. Naturally, in these cases, we don't need the visibleWhen constraint. The command handler implemtation is a lot simpler than the action delegate handler, as the abstract class org.eclipse.core.commands.AbstractHandler
does all the extra work (listener implementation etc.) for us. The only method, we have to implement ourselves is execute()
:
package my.contribution; import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.jface.viewers.IStructuredSelection; import org.eclipse.ui.handlers.HandlerUtil; public class MyCommandHandler extends AbstractHandler { @Override public Object execute(ExecutionEvent event) throws ExecutionException { IStructuredSelection selection = (IStructuredSelection) HandlerUtil.getCurrentSelectionChecked(event); MyObject object = (MyObject) selection.getFirstElement(); // do something with MyObject } }
Again, until here, we only implemented a command which is enabled always. Now, if we want to enable the command only, if the selected model element fulfills a certain constraint, in the old API we needed an adapter. In the new API, the adapter is made implicit using the concept of a PropertyTester. A PropertyTester can be used to add custom testable properties to arbitrary classes. For our example, the declaration and looks like this:
<extension point="org.eclipse.core.expressions.propertyTesters"> <propertyTester class="my.package.MyObjectPropertyTester" id="my.proptester.id" namespace="my.property.namespace" properties="type" type="my.model.MyObject"> </propertyTester> </extension>
and the implementation is as follows:
package de.feuh.st.requipse.project; import org.eclipse.core.expressions.PropertyTester; public class ArtifactPropertyTester extends PropertyTester { @Override public boolean test(Object receiver, String property, Object[] args, Object expectedValue) { MyObject obj = (MyObject)receiver; if(property.equals("type")) { return expectedValue.equals(obj.getType()); } else { return false; } } }
This way, we have defined a property called type in the namespace my.property.namespace which applies to objects which are instance of my.model.MyObject. The implementation then compares the value of the object's getType()
method to a given expected value and returns the result. As a final step, we have to use the PropertyTester in our handler declaration. We enhance the handler as follows:
<extension point="org.eclipse.ui.handlers"> <handler class="my.contribution.MyCommandHandler" commandId="my.command.id"> <enabledWhen> <with variable="selection"> <iterate ifEmpty="false" operator="and"> <test property="my.property.namespace.type" value="FOO"> </test> </iterate> </with> </enabledWhen> </handler> </extension>
This enables the command only if all of the selected objects return "FOO" for getType()
.
Further Reading: