Article Index
Commands Tutorial
Selection and Enablement of Commands
Parameters for Commands
Authentication in RCP Application
Misc Items
Misc Items
Toggle & Radio Commands
All Pages

 

One of the frequently asked questions on Command Framework is 'how to dynamically update a command?' I thought I'll couple the answer with implementing ISourceProvider and explain it with a usecase: Authentication in RCP applications. Most of the RCP applications I've seen, needs authentication. Obviously it would need a Login/Logout menu items and other user related options. This can be accomplished in many ways, and I'm going to show you how to do it with Command Framework. Lets do it in step by step.

Defining the session state thru ISourceProvider:

First, we need to store the session state in a variable. When we say variable in Command Framework, its not a public final static String. It is something that you can use it in the visibleWhen, activeWhen and enabledWhen expressions. The variable has to be provided thru a ISourceProvider:

<extension point="org.eclipse.ui.services">
<sourceProvider provider="com.eclipse_tips.rcp.app.SessionSourceProvider">
<variable
name="com.eclipse-tips.rcp.app.sessionState"
priorityLevel="workbench">
</variable>
</sourceProvider>
</extension>

A ISourceProvider can provide the state of multiple variables. However, our source provider will give the value of only one variable - 'com.eclipse-tips.rcp.app.sessionState'. The values of this variable would be either 'loggedIn' or 'loggedOut'. The list of values cannot be defined thru the extension, so this is up to you to define it, publish it and use it. In the extension, you can see the priorityLevel attribute. This is used by the IHandlerService to determine which handler is active for a given command and its defined in the org.eclipse.ui.ISources interface. See my earlier tip on Command Framework for more explanation on this priority attribute. The class should either implement ISourceProvider or extend AbstractSourceProvider. The preferred way is extending, so we'll do it that way:

 

public class SessionSourceProvider extends AbstractSourceProvider {
public final static String SESSION_STATE = "com.eclipse-tips.rcp.app.sessionState";
private final static String LOGGED_IN = "loggedIn";
private final static String LOGGED_OUT = "loggedOut";
boolean loggedIn;
@Override
public String[] getProvidedSourceNames() {
return new String[] {SESSION_STATE};
}
@Override
public Map getCurrentState() {
Map currentState = new HashMap(1);
String currentState = loggedIn?LOGGED_IN:LOGGED_OUT;
currentState.put(SESSION_STATE, currentState);
return currentState;
}
@Override
public void dispose() {}
public void setLoggedIn(boolean loggedIn) {
 if(this.loggedIn == loggedIn)
 return; // no change
this.loggedIn = loggedIn;
 String currentState = loggedIn?LOGGED_IN:LOGGED_OUT;
 fireSourceChanged(ISources.WORKBENCH, SESSION_STATE, currentState);
 }
 }

The first method is simple. As I mentioned earlier, a source provider can provider the state of multiple variables. The list of the variables (source names) is returned in the call. The second method is called to get the current state. The variable:value pairs are put in a map and returned back to the caller. The third method dispose is a no-op method for us. These three methods completes the API contract, but we have added one more method, where the value is updated to the source provider itself. So whenever a Login/Logout happens we need to call this method. This method fires the sourceChanged event, so that all the listeners can update accordingly. When the sourceChanged event is fired, the status of the Command Handlers which has activeWhen or enabledWhen expressions with this variable re-evaluated with the new value of the variable. This holds good for visibleWhen expressions of the Commands as well. So if you want to show/enable a contribution item only when the user has logged in, you can use this variable like:

<extension
       point="org.eclipse.ui.menus">
    <menuContribution
          locationURI="menu:file?after=additions">
       <command
             commandId="org.eclipse.ui.window.preferences">
          <visibleWhen>
             <with
                   variable="com.eclipse-tips.rcp.app.sessionState">
                <equals value="loggedIn" />
             </with>
          </visibleWhen>
       </command>
    </menuContribution>
 </extension>

Now the File->Preferences will be visible only when the user has logged in.

Updating the state:

Now that we have the source provider and the menu items that are dynamically enabled/disabled or shown/hidden according to the sessionState, the problem is how to we update the state? Elsewhere, when a command is executed for Login/Logout, we have to make a call to SessionSourceProvider.setLoggedIn() method. But how do we get hold of the instance of the SessionSourceProvider? This is where the ISourceProviderService comes into picture. This service has a getSourceProvider() method, where if you give the variable name, it will give the corresponding source provider. The next obvious question would be where to get the implementation of this service? Not this service, for any service, you can use IServiceLocator to get the implementation and fortunately the IWorkbenchWindow implements this service. So when we execute the Login/Logout command:

// get the window (which is a IServiceLocator)
 IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
 // get the service
ISourceProviderService service = (ISourceProviderService) window.getService(ISourceProviderService.class);
 // get our source provider by querying by the variable name
SessionSourceProvider sessionSourceProvider = (SessionSourceProvider) service.getSourceProvider(SessionSourceProvider.SESSION_STATE);
 // set the value
 sessionSourceProvider.setLoggedIn(isSessionActive);

Dynamically updating the Login/Logout command:

The Login and Logout commands are mutually exclusive. When one appears the other won't. So you can have two different commands and use the visibleWhen with our sessionState variable to show/hide them. The other way is to have one command and update the text of the command according to the state. The first way is very similar to the Preference command that is explained above, so let me explain the second way here: The idea is to define a single command and two handlers:

<extension
       point="org.eclipse.ui.commands">
    <command
          id="com.eclipse-tips.rcp.app.sessionCommand"
          name="Session Command">
    </command>
 </extension>
<extension
       point="org.eclipse.ui.handlers">
    <handler
          class="com.eclipse_tips.rcp.app.handlers.LoginHandler"
          commandId="com.eclipse-tips.rcp.app.sessionCommand">
       <activeWhen>
          <with
                variable="com.eclipse-tips.rcp.app.sessionState">
             <equals
                   value="loggedOut">
             </equals>
          </with>
       </activeWhen>
    </handler>
    <handler
          class="com.eclipse_tips.rcp.app.handlers.LogoutHandler"
          commandId="com.eclipse-tips.rcp.app.sessionCommand">
       <activeWhen>
          <with
                variable="com.eclipse-tips.rcp.app.sessionState">
             <equals
                   value="loggedIn">
             </equals>
          </with>
       </activeWhen>
    </handler>
 </extension>
<extension
       point="org.eclipse.ui.menus">
    <menuContribution
          locationURI="menu:file?before=quit">
       <command
             commandId="com.eclipse-tips.rcp.app.sessionCommand"
             style="push">
       </command>
    </menuContribution>
 </extension>

So the first handler, LoginHandler will be active when the user is logged out and the LogoutHandler will be active when the user is logged in. Both the handlers after performing their respective actions will notify the SessionSourceProvider, but how do we change the menu text? This happens thru IElementUpdater. When a handler implements this interface, it can update the associated contributions. When I say update, it means the text, tooltip, image etc. So the LoginHandler would look like:

public class LoginHandler extends AbstractHandler implements IElementUpdater {
 @Override
 public Object execute(ExecutionEvent event) throws ExecutionException
{
 // perform login here ...
 IWorkbenchWindow window = HandlerUtil.getActiveWorkbenchWindow(event);
 ISourceProviderService service = (ISourceProviderService) window.getService(ISourceProviderService.class);
 SessionSourceProvider sessionSourceProvider = (SessionSourceProvider) service.getSourceProvider(SessionSourceProvider.SESSION_STATE);
 // update the source provider
 sessionSourceProvider.setLoggedIn(true);
 return null;
 }

 @Override
 public void updateElement(UIElement element, Map parameters) {
 element.setText("Login");
 }
 }

The LogoutHandler also will have a similar code. Remember its not a mandatory thing to have multiple handlers to update the command dynamically. You can do it even with single handler as well. One last piece, the Command Framework will call the updateElement() method only when the value of the variables in the *when expression is changed. So in other places where you want to update a command when no variable change has occurred, you need to use the ICommandService:

ICommandService commandService = (ICommandService.class)serviceLocator.getService(ICommandService.class); commandService.refreshElements(commandId, null);

In the previous parts of the series, we saw how Commands contribute to push style menu items. But commands allow you to contribute menu items with 'toggle' and 'radio' style as well. Let us see how to contribute the familiar "Format" menu thru Commands:

image

 

First we will look at the toggle style menu contribution. Commands can have states associated with it. A state id known by its id and it can have any value, which is stored in a subclass of org.eclipse.core.commands.State. To know whether a command is checked or not, the Command Framework looks for a state with the id 'org.eclipse.ui.commands.toggleState'. Lets specify the default checked state of Bold to true and Italic to false:

<command
       defaultHandler="com.eclipse_tips.commandstate.BoldHandler"
       id="com.eclipse-tips.commandState.boldCommand"
       name="Bold">
    <state
          class="org.eclipse.ui.handlers.RegistryToggleState:true"
          id="org.eclipse.ui.commands.toggleState">
    </state>
 </command>
 <command
       defaultHandler="com.eclipse_tips.commandstate.ItalicHandler"
       id="com.eclipse-tips.commandState.italicCommand"
       name="Italic">
    <state
          class="org.eclipse.ui.handlers.RegistryToggleState:false"
          id="org.eclipse.ui.commands.toggleState">
    </state>
 </command>

 

The Framework expects a Boolean value from the state. It doesn't care about which class that holds the state. So why should we use the RegistryToggleState instead of our own state class? Two reasons:

  1. It implements IExecutableExtension. So you can specify the default values in the plugin.xml as I've done above.
  2. It extends PersistentState, so it can remember the value between Eclipse sessions. So if the user had checked/unchecked the menu, restarts Eclipse, he will get the same state as he left before - all this, you get without even your plugin being loaded.

So how should the handler work for this command?

public Object execute(ExecutionEvent event) throws ExecutionException {
 Command command = event.getCommand();
 boolean oldValue = HandlerUtil.toggleCommandState(command);
 // use the old value and perform the operation
return null;
 }

Basically, its the Handler's responsibility to update the Command's state. The HandlerUtil has a convenient method which toggles the state and gives you the old value of the state. You can use the value to perform the operation.

The radio state is also similar, which expects the state with a predefined id. Since the same command is contributed for various option, its should be a parameterized command. The id of the parameter which denotes the radio state is also predefined:

 

<command
       defaultHandler="com.eclipse_tips.commandstate.AlignHandler"
       id="com.eclipse-tips.commandState.alignCommand"
       name="Align Command">
    <commandParameter
          id="org.eclipse.ui.commands.radioStateParameter"
          name="State"
          optional="false">
    </commandParameter>
    <state
         class="org.eclipse.ui.handlers.RadioState:left"
          id="org.eclipse.ui.commands.radioState">
    </state>
 </command>

 

Just like toggle state, the state must have the id and the class could be anything that stores the value as String. The RadioState class provides initializing from plugin.xml and also persists the value across sessions.

The menu contribution would specify the parameter value:

 

<menuContribution
       locationURI="menu:org.eclipse.ui.main.menu?after=additions">
    <menu
          label="Format">
       ... other contributions here
       <command
             commandId="com.eclipse-tips.commandState.alignCommand"
             label="Align Left"
             style="radio">
          <parameter
                name="org.eclipse.ui.commands.radioStateParameter"
                value="left">
          </parameter>
       </command>
       <command
             commandId="com.eclipse-tips.commandState.alignCommand"
             label="Align Center"
             style="radio">
          <parameter
                name="org.eclipse.ui.commands.radioStateParameter"
                value="center">
          </parameter>
       </command>
       <command
             commandId="com.eclipse-tips.commandState.alignCommand"
             label="Align Right"
             style="radio">
          <parameter
                name="org.eclipse.ui.commands.radioStateParameter"
                value="right">
          </parameter>
       </command>
    </menu>
 </menuContribution>

 

The Command Framework understands this parameter and sets the UI contributions according to this value. Again its the handler's job to set the correct state from the command's parameter during execution. You have helper methods in the HandlerUtil to perform this:

 

public Object execute(ExecutionEvent event) throws ExecutionException {
     if(HandlerUtil.matchesRadioState(event))
         return null;
 // we are already in the updated state - do nothing
     String currentState = event.getParameter(RadioState.PARAMETER_ID);
     // perform task for current state
     if(currentState.equals("left"))
     // perform left alignment
     else if(currentState.equals("center"))
     // perform center alignment
     // and so on ...
     // and finally update the current state
     HandlerUtil.updateRadioState(event.getCommand(), currentState);
 return null;
 }

 

Bonus:

Q: I want the initial value always loaded from the plugin.xml, the state should not be remembered between sessions. Should I write my own State class?

A: Not necessary. You can specify both the default value of the state and whether to persist or not in the plugin.xml itself:

 

<command             defaultHandler="com.eclipse_tips.commandstate.AlignHandler"
       id="com.eclipse-tips.commandState.alignCommand"
       name="Align Command">
    <commandParameter
          id="org.eclipse.ui.commands.radioStateParameter"
          name="State"
          optional="false">
    </commandParameter>
    <state
          id="org.eclipse.ui.commands.radioState">
       <class
             class="org.eclipse.ui.handlers.RadioState">
          <parameter
               name="default"
                value="left">
          </parameter>
          <parameter
                name="persisted"
                value="false">
          </parameter>
       </class>
    </state>
 </command>

 

This applies for toggle state as well.

Q: I want to know the state of the command elsewhere in the code. How do I get it?

A: Just use Command.getState(<state id>).getValue(). The state ids are available as constants at RegistryToggleState.STATE_ID and RadioState.STATE_ID

My patch for this feature has just been checked in and should be available from 3.5 M6 onwards. If you are curious, pick up a recent Nightly build and play with this.

 



More articles :

» Selection Dialogs in Eclipse

If you are an Eclipse Plug-in developer, you must have used the MessageDialog. There are many other Dialogs provided by Eclipse Platform are reusable and part of the API. I'll try to explain the various selection dialogs that I know of. In case I've...

» Adding a new editorAction for Orion

In case you have not heard it yet,  is the new Web IDE from Eclipse. In a blog entry , I said that this one is going to stand out from the crowd and going to rule the world. Why can't the other Web IDEs do that? Because, Orion also would be walking...

» Extending FilteredItemsSelectionDialog

In a previous tip, you have seen various . One thing which was not explained in it was FilteredItemsSelectionDialog, as it deserves a tip on its own. In this tip, I'll explain how to extend that class.

» A glimpse at the Faceted Project Framework

If you haven't noticed, the Faceted Project Framework from WTP is now proposed as a separate project. I like the framework very much, probably because my very first Eclipse Plug-in is simply a WTP Facet :-) When I wrote that plug-in, there was no...

» Creating the defacto RCP Mail application

  In many of the RCP related tips in this website and other sites, the starting point would be this one liner "Create the RCP Mail application". Instead of repeatedly this step in those tips, I've put the steps in this item. Step 1: Open the "New"...

Subscribe To

Unless stated, all the text contents of this site is available under Eclipse Public License