[Eclipse Expressions] Control the visibility of Eclipse RCP menu contributions depending on text selection with PropertyTesters

For seasoned RCP developers, this will be yesterday’s news I guess. However, I have just spent more than the usual 10 minutes on finding a solution to my problem, so I thought it’d be good to provide a quick write-up.

DISCLAIMER: This solution was tested with an RCP application based on the Eclipse Indigo (3.7.2) platform and Java JDK 1.7.0_51. I cannot see any reason, however, why this wouldn’t work with different setups.

The problem

The case I use for presenting this short tutorial is arguably a corner case. Nevertheless the solution can easily be transferred to other setups. So, I have this FoobarView extending org.eclipse.ui.part.ViewPart. Its only control is an org.eclipse.jface.text.source.SourceViewer (but could be any TextViewer for the purposes of this post). I’ve created a command in plugin.xml which I want to use from a view-dependent popup menu item. However, I want this menu item only to be visible whenever there is any text selected in the SourceViewer. For simplicity, the command should have a handler which should simply print the selected bit of text to standard out.

I was used to simply declaring a default handler for any command in plugin.xml, but as you’ll see, in order to make the above work this isn’t the way to go. So, let’s immerse ourselves in code and XML…

The solution

The command looks like this in plugin.xml:

<extension point="org.eclipse.ui.commands">
      <command
            description="Print selected text to stdout"
            id="net.sdruskat.blog.foobar.commands.printSelectedTextToStdOut"
            name="Print selected text to stdout">
      </command>
</extension>

No magic there, all nice and simple. Next, the popup menu item.

<extension point="org.eclipse.ui.menus">
      <menuContribution
            allPopups="false"
            locationURI="popup:net.sdruskat.blog.foobar.view">
         <command
               commandId="net.sdruskat.blog.foobar.commands.printSelectedTextToStdOut"
               label="Print selected text to stdout"
               style="push">
         </command>
      </menuContribution>
</extension>

Again, simple. In the menu declaration, we have the locationURI which binds menu to view. The view is declared as usual in plugin.xml so I’ll omit it here. Of course it shold have that id…

The source for the view is simple as well (auto-generated stuff, imports, generic stuff, etc. ommitted for readability).

public class FoobarView extends ViewPart {

	@Override
	public void createPartControl(Composite parent) {
		SourceViewer sourceViewer = new SourceViewer(parent, null, null, false, SWT.WRAP | SWT.BORDER | SWT.V_SCROLL | SWT.H_SCROLL | SWT.MULTI | SWT.READ_ONLY);
		// Add a MenuManager
		MenuManager menuManager = new MenuManager ();
		Menu menu = menuManager.createContextMenu(sourceViewer.getControl());
		sourceViewer.getControl().setMenu(menu);
		getSite().registerContextMenu(menuManager, sourceViewer);
		// Make the selection available to the workbench
		getSite().setSelectionProvider(sourceViewer);
		Document document = new Document("We do need some text here, don't we!");
		sourceViewer.setDocument(document);
	}
}

The handler is as run-off-the-mill as it gets.

public class FoobarHandler extends AbstractHandler {

	@Override
	public Object execute(ExecutionEvent event) throws ExecutionException {
		System.out.println("The selected text is: " + getSelection().getText());
		return null;
	}

	private TextSelection getSelection() {
		TextSelection selection = null;
		try {               
			IWorkbenchPart part = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart();
			if (part instanceof FoobarView) {
				final FoobarView view = (FoobarView) part;
				selection = (TextSelection) view.getSite().getSelectionProvider().getSelection();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return selection;
	}
}

The interesting part is the handler, and how it is declared in plugin.xml.

<extension point="org.eclipse.ui.handlers">
      <handler
            class="net.sdruskat.blog.foobar.commands.FoobarHandler"
            commandId="net.sdruskat.blog.foobar.commands.printSelectedTextToStdOut">
         <enabledWhen>
            <test
                  property="net.sdruskat.blog.foobar.commands.hasNonEmptyTextSelection">
            </test>
         </enabledWhen>
         <activeWhen>
             <with variable="activePartId">
                     <equals
                           value="net.sdruskat.blog.foobar.view">
                     </equals>
               </with>
        </activeWhen>
      </handler>
   </extension>

Note that the <activeWhen> directive should check whether the active part is your view, as there must only ever be one active handler at any one time in Eclipse.

The highlighted bit basically says “Enable this handler when the test for the property ‘hasNonEmptyTextSelection’ doesn’t fail.” (“And when I say ‘enable it’, I mean ‘don’t grey it out and let it be clickable’.”) Properties are tested with a (drumroll…) org.eclipse.core.expressions.PropertyTester! It was this which took me a while to find out about. A class extending PropertyTester must at least override test(), which returns a boolean. If true is returned the test is passed. The PropertyTester is hooked up with our handler in plugin.xml as follows.

<extension point="org.eclipse.core.expressions.propertyTesters">
      <propertyTester
            class="net.sdruskat.blog.foobar.propertytesters.SelectionTester"
            id="net.sdruskat.blog.foobar.selectionTester"
            namespace="net.sdruskat.blog.foobar.commands"
            properties="hasNonEmptyTextSelection"
            type="java.lang.Object">
      </propertyTester>
</extension> 

Here we find our class extending PropertyTester as well as an id, and a type which can be used to define objects the property tester can be applied to. The highlighted lines define a namespace and a properties variable, which is used to hook up property tester and handler in the handler extension’s enabledWhen bit (see above).

When implementing SelectionTesters test() method, one must keep in mind that what we’ll receive in the method isn’t necessarily of the type we’re after (in this case, a TextSelection). Printing the class of the receiver object to std out reveals that whenever there is text selected, the type of the receiver will be java.util.Collections$SingletonSet. So to get to the actual selected text, we must do a number of instanceof tests first. Below is my implementation of the test() method.

@Override
public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
	if ("hasNonEmptyTextSelection".equals(property)) {
		if (receiver instanceof Collection) {
			@SuppressWarnings("unchecked")
			Collection<Object> receiverCollection = (Collection<Object>) receiver;
			TextSelection selection;
			if (receiverCollection.toArray().length != 0 && receiverCollection.toArray()[0] instanceof TextSelection)  {
				selection = (TextSelection) receiverCollection.toArray()[0];
				if (!selection.getText().equals("")) {
					return true;
				}
			}
		}
    }
    return false;
}

Different tests need different implementations obviously, and the Eclipse Expressions Framework wiki page is the go-to address for all your expressions needs. The API doc for PropertyTester also has an example.

Once all this is implemented, the menu item for our handler will only be enabled when some text is selected. And additionally, we can re-use our SelectionTester to only show the menu item at all when some text is selected. In order to do this we simply add a directive to the command extension, completely in parallel to the enabledWhen directive for the handler. Below is the command extension again, with added tags.

<extension point="org.eclipse.ui.menus">
      <menuContribution
            allPopups="false"
            locationURI="popup:net.sdruskat.blog.foobar.view">
         <command
               commandId="net.sdruskat.blog.printSelectedTextToStdOut"
               label="Print selected text to stdout"
               style="push">
                  <visibleWhen>
               	     <test
                         property="net.sdruskat.blog.foobar.commands.hasNonEmptyTextSelection">
            	     </test>
                  </visibleWhen>
         </command>
      </menuContribution>
</extension>

Hope this will help someone. And as usual, if there is anything wrong or amiss, please drop me a comment.

This example is on github.

This example is on github.

You can download the sources for this example (contained in a minimal RCP application) from github. The clone URL is: https://github.com/newcodeontheblock/blog.git.

Update (26-Mar-2014)

As it turns out, the above solution has one problem: When you press CTRL + A to select the whole text in the SourceViewer, the TextSelection in SelectionTester‘s test() method will be empty, and hence the menu item will not show. A workaround for this – if a bit unclean IMHO – is to get the selection from the View‘s SelectionProvider. The updated test() method is below.

public class SelectionTester extends PropertyTester {

	@Override
	public boolean test(Object receiver, String property, Object[] args, Object expectedValue) {
		if ("hasNonEmptyTextSelection".equals(property)) {
			try {
				IWorkbenchPart activePart = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getActivePart();
				if (activePart instanceof FoobarView) {
					FoobarView view = (FoobarView) activePart;
					ISelection viewSiteSelection = view.getViewSite().getSelectionProvider().getSelection();
					if (viewSiteSelection instanceof TextSelection) {
						TextSelection textSelection = (TextSelection) viewSiteSelection;
						if (!textSelection.getText().isEmpty()) {
							return true;
						}
					}
				}
			} catch (Exception e) {
				// Do nothing. Will throw an NPE when the application is closed as there is no longer an active part.
			}
		}
		return false;
	}

}

The update is also available on github: https://github.com/newcodeontheblock/blog.

TwitterFacebookGoogle+EmailLinkedInPinterestDiggRedditStumbleUpon
If you found this post helpful, you may want to consider donating to help cover server costs.
Tagged with: , , , , , , , , , , , , , , , ,
Posted in Eclipse RCP, Tutorial

Leave a Comment

%d bloggers like this: