Tuesday, December 18, 2007

Dynamic Labels for Eclipse Context Menus

The following entry describes a simple way to contribute context menu commands with dynamic labels to an Eclipse view. In my first attempt, I tried to use state to modify the command label but could not get it to work. Instead, I used dynamic commands because the approach was easier to figure out and implement, and the amount of code involved was so small that even if the approach is not ideal, it seems robust and scalable.
You will need rudimentary experience with Eclipse plug-in development to find this post valuable.

Create an Eclipse Plug-in Project


For this example, we do not need anything fancy, just the most basic Eclipse plug-in (File -> New -> Project... -> Plug-in Project). I called my project my.project.
Create a new project.

Declare a Menu Extension


We will declare that we are contributing a menu item in the plugin.xml. To access the menu extension point, we need to declare a plugi-in dependency on org.eclipse.ui. The extension point we are using is org.eclipse.ui.menus.
Add the org.eclipse.ui.menus extension.

Note: we could use the "Hello, World" command contribution as a starting point, but I prefer the agglutinative to the reductive approach as a starting point.

Add a Menu Contribution


Add a menuContribution for the org.eclipse.ui.menus extension.
Set its locationURI to popup:org.eclipse.ui.popup.any?after=additions.
The scheme popup indicates that the menuContibution should appear for context menus. Other valid values are menu and toolbar.
The menu ID org.eclipse.ui.popup.any is a magic string that explains itself. If you would prefer to use resrict the popup contribution to a specific editor or view, then use its unique ID.
The query after=additions indicates that the contribution should appear after the standard IWorkbenchActionConstants.MB_ADDITIONS group. As you might guess, the placement value after might just as well be before.

Create a Dynamic Menu Contribution Node


Add a dynamic node to the menuContribution. Here we will declare the org.eclipse.ui.actions.CompoundContributionItem that will create the dynamic menu entries. For this example, I have declared the ID as my.project.myCompoundContributionItem and the class as my.project.MyCompoundContributionItem.
Add a menuContribution to the org.eclipse.ui.menus extension and a dynamic node to the menuContribution.

Create a CompoundContributionItem


Let Eclipse create my.project.MyCompoundContributionItem.java for you and set a Debug breakpoint in the getContributionItems method. We can set the ID as my.project.myCompoundContributionItem and the class as my.project.MyCompoundContributionItem.

Side-note: Debug The Menu Contribution


From the plug-in Overview page, in the Testing category, we can launch another Eclipse runtime in debug mode and trace when the getContributionItems method is called. Just set a breakpoint inside the method body, and, when the new Eclipse workbench starts, right-click anywhere in the project explorer. We have configured the dynamic menuContribution to contribute an empty array of IContributionItem to all popup menus. It's not particularly useful, but it's a start.
A trace of the getContributionItems menu being called.

Declare a Command Extension


Now that we have declared an org.eclipse.ui.menus extension and added a menuContribution with dynamic content, it should be straightforward for us to declare an org.eclipse.ui.commands extension, with two nodes: one for the category, which is the group in the context menu where our commands will appear, and one for the command declaration.
I have assigned to the category the id my.project.myCategory and name My Category and to the command the id my.project.myCommand, name Do Something and categoryId my.project.myCategory.
The org.eclipse.ui.commands with nodes for a command and for a menu group.

Declare a Command Handler


Now that we have an org.eclipse.ui.commands extension, we will add a handler for this command. We just need to declare an org.eclipse.ui.handlers extension with a new handler node and the commandId declared above, my.project.myCommand.
Three declared extensions are used for adding a context menu command with a dynamic label.
We can create a new AbstractHandler implementation, called my.project.MyHandler that looks something like this:

package my.project;

import org.eclipse.core.commands.AbstractHandler;
import org.eclipse.core.commands.ExecutionEvent;
import org.eclipse.core.commands.ExecutionException;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.ui.handlers.HandlerUtil;

public class MyHandler extends AbstractHandler {
@Override
public Object execute(ExecutionEvent event) throws ExecutionException {
MessageDialog.openInformation(
HandlerUtil.getActiveShellChecked(event), "My Handler",
"Not yet implemented");
return null;
}
}


Add a Simple CommandContributionItem


We are now going back to the MyCompoundContributionItem class to return a CommandContributionItem for the command extension above.
You may need to declare a dependency on org.eclipse.core.runtime to get a code completion for the the PlatformUI#getWorkbench method, which returns an IWorkbench. The active workbench window will suffice as our IServiceLocator. Notice in the implementation below that the label counter (the 4th Constructor parameter from the end for the CommandContributionItem) will be incremented every time a new context menu is opened. This should satisfy our simple requirement for dynamically updating the context menu label.
Similarly, we can dynamically update the icon, tooltip, etc., based on the state of the application (current selection, current perspective, etc.).

package my.project;

import java.util.Collections;

import org.eclipse.jface.action.IContributionItem;
import org.eclipse.swt.SWT;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.actions.CompoundContributionItem;
import org.eclipse.ui.menus.CommandContributionItem;


public class MyCompoundContributionItem extends CompoundContributionItem {
private static int counter = 0;
protected IContributionItem[] getContributionItems() {
return new IContributionItem[] {
new CommandContributionItem(PlatformUI.getWorkbench().getActiveWorkbenchWindow(),
"my.project.myCommandContributionItem", "my.project.myCommand",
Collections.emptyMap(), null, null, null,
"Dynamic Menu "+ counter++, null, null, SWT.NONE)
};
}
}

Now we can Launch the Eclipse Application from the Testing section of the Overview tab, right-click a few times and watch as the menu label is updated every time a new context menu pops up!
The dynamic label is updated every time we right-click to open a context menu.


Note: as of Ganymede (Eclipse 3.4), the above constructor for CommandContributionItem has been deprecated. For an example of the class MyCompoundContributionItem for use in Eclipse 3.4, look here.

6 comments:

SeuAndré said...

for eclipse 3.4 the CompoundContributionItem would have to be like this:

public class ContributionItem1 extends CompoundContributionItem {
@Override
protected IContributionItem[] getContributionItems() {

CommandContributionItemParameter ccip = new CommandContributionItemParameter(
PlatformUI.getWorkbench().getActiveWorkbenchWindow(),
"my.project.myCommandContributionItem", "sysOutSelCommand",
CommandContributionItem.STYLE_PUSH);
return new IContributionItem[] { new CommandContributionItem(ccip) };
}

}

Tim Myer said...

Hi SeuAndré,
Thanks very much for your post and your interest.
I am not clear on how your comment presents a different solution than the post linked in the last line of the blog entry (http://timezra.blogspot.com/2008/01/dynamic-labels-redux.html), aside from the CommandContributionItem.STYLE_PUSH style bit.
Could you clarify?
Thanks.
------Tim-------

SeuAndré said...

oh, u'r right, sorry i did not pay attention to that last line :)

Tim Myer said...

Hi SeuAndré,
No worries. Your comment indicates that I should make it clearer earlier in the post that the solution proposed is more appropriate for particular versions of Eclipse.
I always appreciate the feedback!
------Tim------

David Pérez said...

Very good your code and clear your explanations.
Thanks a lot!

Salvo4u said...

Hi SeuAndre ,
I loved ur code explanation but i have a different problem if u could help.
I want to add dynamic entries to submenus on a particular type of object
example:
OBJ1->right click--->MyMenu-->Item1
-->Item2
-->Item3
and so on.......