Eclipse RCP Dynamic popup menu

I do not have a lot of experience with the Eclipse Rich Client Platform (RCP), yet I was asked to create a dynamic submenu for an element in a JFace TreeView. The submenu should be filled in depending on the selected object plus a fixed menu item which should open a new dialog. The menu should be hidden if the selected object has a value set for the property ‘myProperty’.
I’ve struggled for days to get this working, especially since most solutions on the internet referred to the MenuManager which didn’t work at all in my case.

Fortunately, after some trial and error I managed to set up the submenu using the standard plugin extensions.

The fixed command

First thing to define is the fixed command which will open a new dialog.

Under the org.eclipse.ui.commands extention point, add the following command definition

      <command defaultHandler="be.x.y.z.AddToHandler" id="be.x.y.z.addTo" name="Add to ..">
      </command>

The corresponding handler class is fairly simple, it’ll just open a new dialog while passing the selected item to this dialog.

import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.core.commands.IHandler;
import org.eclipse.core.commands.IHandlerListener;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.ui.handlers.HandlerUtil;



public class AddToHandler implements IHandler {

  @Override
  public void addHandlerListener(final IHandlerListener handlerListener) {
  }

  @Override
  public void dispose() {
  }

  @Override
  public Object execute(final ExecutionEvent event) throws ExecutionException {
    ISelection selection = HandlerUtil.getActiveWorkbenchWindow(event).getActivePage().getSelection();
    MyObject myObject = SelectionEval.singleton(selection, MyObject.class);
    MySearchDialog gds = new MySearchDialog(HandlerUtil.getActiveWorkbenchWindow(event).getShell(), myObject);
    gds.open();
    return null;
  }

  @Override
  public boolean isEnabled() {
    return true; // set to true to make it always visible
  }

  @Override
  public boolean isHandled() {
    return true; // set to true to make it always visible
  }

  @Override
  public void removeHandlerListener(final IHandlerListener handlerListener) {
  }

}

Testing the selected object

Next step is to define a property tester which will check if the selected object satisfies all conditions to show the menu.

The following property tester should be defined in the org.eclipse.core.expressions.propertyTesters extension point:

      <propertyTester class="be.x.y.z.addToPropertyTester" id="be.x.y.z.addToPropertyTester" namespace="be.x.y.z.addToPropertyTester" properties="myProperty" type="java.lang.Object">
      

The property tester itself is fairly simple and does nothing but a null-check


import org.eclipse.core.expressions.PropertyTester;
import be.x.y.z.MyObject;

public class AddToPropertyTester extends PropertyTester {

  @Override
  public boolean test(final Object receiver, final String property, final Object[] args, final Object expectedValue) {
    if (property.equals("myProperty") && receiver instanceof MyObject) {
      MyObject myObject = (MyObject) receiver;
      return myObject.getMyProperty() == null;
    }
    return false;
  }

}

Adding menu items dynamically

Adding items using a dynamic menu requires an instance of a ContributionItem.

import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.TreeMap;
import org.eclipse.jface.action.ContributionItem;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.SelectionAdapter;
import org.eclipse.swt.events.SelectionEvent;
import org.eclipse.swt.widgets.Menu;
import org.eclipse.swt.widgets.MenuItem;
import org.eclipse.ui.PlatformUI;


public class AddToContributionItem extends ContributionItem {

  @Override
  public void fill(final Menu menu, final int index) {
    super.fill(menu, index);
    ISelection is = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage().getSelection();
    MyObject singleton = SelectionEval.singleton(is, MyObject.class);
    if (singleton == null) {
      return;
    }

    Map<string, map<string,="" object="">> data = new TreeMap<>();
    // Some logic to fill in data

    int count = 0;
    for (Entry<string, map<string,="" object="">> entry : data.entrySet()) {
      if (++count > 15) {	// Limit number of items to 15
        return;
      }
      MenuItem menuItem = new MenuItem(menu, SWT.NONE, index);
      menuItem.setText(entry.getKey());
      menuItem.setData("OBJECT_DATA", entry.getValue().get("OBJECT_DATA"));	// you can store extra data here, which you can use in the selection listner
      menuItem.addSelectionListener(new SelectionAdapter() {

        @Override
        public void widgetSelected(final SelectionEvent e) {
          MyData dO = (MyData) e.widget.getData("OBJECT_DATA");
          // Whatever you want to do
        }
      });
    }

  }
}

Glueing it all together

Last step is to glue everything together in an org.eclipse.ui.menus extension point:

      <menuContribution allPopups="false" locationURI="popup:org.eclipse.ui.popup.any"> <!-- This is the menu contribution for the "add to" menu --></pre>
<menu id="be.x.y.z.addtomenu" label="Add to"> <!-- This is the menu item which <span class="hiddenSuggestion" pre="which " data-mce-bogus="1">contains</span> a sub menu -->

            	<!-- This is the dynamic part of the menu -->
            <command id="be.x.y.z.addToMenuContribution" label="..."></command>	<!-- This command will open the search dialog -->
                <!-- Use the isHandled() and isEnabled() method to show or hide this fixed menu item) -->

            	<!-- This is the fixed menu item which opens a new search dialog -->
            	<!-- Only show the menu if *selection*.count == *1* AND *selection*[O] instanceof MyObject AND *selection*[O] has no myProperty -->

                         <!-- Important, this is the property tester namespace + property -->

                  	<!-- Make sure we have selected only 1 single object -->

         </menu>
<pre>

             
   

Disclaimer: I’m not an Eclipse RCP expert and even though this might work, it’s probably not the most efficient way to do this, especially because we’re binding it to popup:org.eclipse.ui.popup.any, however, it was to only way I could get this working in our somewhat bloated project.

Advertisements

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s