Showing posts with label menus. Show all posts
Showing posts with label menus. Show all posts

2010-02-14

Using Adapters in Handlers to control functionality

In my last blog post about the use of Dependency injection in e4, I used adapters in handlers with functional interfaces to control the enablement of functionality in an application. By request, here is an extended version of that with some additional explanations. [Incidentally, this was proposed as a talk for EclipseCon ´10, but unfortunately got rejected...]

Note that this is only for users in larger applications - if you have a command with a single handler and simple expressions, then this is not needed at all...

Why?

When you implement a larger Eclipse RCP based application, you can run into a number of problems with handlers and menus as you add more and more objects to the domain model of your application model and more and more commands for the objects in the UI.

In order to control the visibility of menu items in menus - most notable the popup menu - you have the visibleWhen construct for the commands in the menu. The used expression often is an or-ed together version of all the activeWhen expressions for the handlers for the command in question. E.g. if you have a command C with two handlers h1 and h2, each with the activeWhen expression aw1 and aw2, then you often want the command to use the visibleWhen expression (or aw1 aw2).

But... as new objects can be added by new plug-ins and the number of commands are enlarged for the existing objects over time, how do you maintain the visibleWhen expression?

Also, very often, you have a lot of common implementation for a specific command in the different handlers for the command - this is not so surprising as the command is an abstract description of some functionality - e.g. "cut", "print", "connect", etc - and some common code must be expected no matter which object that is operated on. E.g. consider the "print" command: a lot of the code for selection of printer and print options will be common no matter which object is printed.

Finally, very often, you really don't want to introduce all the different UI functionality in the domain model itself - maybe for principle reasons or maybe because you don't "own" the code of the model and cannot modify it. You might therefore want to use adapters to handle the functionality in the cases where you cannot change model and direct methods where you can change the model.

For these different reasons I often use a common design pattern in my applications that incorporate functional interfaces for the different commands, adapters for the implementations and some very simple handlers on top - and not least some very easy-to-maintain menu and handler expressions.

In the following, I have used a very simple RCP application as the example with just a single view that shows a table with a number of objects. The objects are Contacts - with name and address -, Customers - extends Contact with a segment - and Segment - with just a segment. [Yes, we could have used a set of model interfaces in this example as well, as we would if we had just used EMF for the model, but in many domain models, that is just not possible...] The table have a popup menu with just a single command "Show Segment" that should be shown for all Customers and Segments, but not for the Contacts. The command must show the segment of the selected object if relevant.

The complete test application can be found here.

How...

First, I use a functional interface for each of the domain model commands in the application. These interfaces have a number of methods used by the command handler to execute the wanted operation. Often this is just a single method, but there can be multiple methods for more complex scenarios.

In this case, the functional interface could be as follows:

public interface IDoShowSegment {
  void doShowSegment(Shell shell);
}

The doShowSegment method could also have been getSegmentName...

In my example, the Customer class implements the interface directly, whereas the Segment class does not, but have an adapter factory instead. The relevant part of the Command class looks as follows:

public class Customer extends Contact implements IDoShowSegment {
  // ...

  @Override
  public void doShowSegment(Shell shell) {
    MessageDialog.openInformation(shell, "The customer segment is...", getSegment());
  }
}

And the relevant part of the adapter factory looks as follows:

public class SegmentAdapterFactory implements IAdapterFactory {
  @Override
  public Object getAdapter(Object adaptableObject, Class adapterType) {
    if (!(adaptableObject instanceof Segment)) {
      return null;
    }
    final Segment s = (Segment) adaptableObject;
    if (adapterType == IDoShowSegment.class) {
      return new IDoShowSegment() {
        @Override
        public void doShowSegment(Shell shell) {
          MessageDialog.openInformation(shell, "The segment is...", s.getSegment());
        }
      };
    }
    return null;
  }

  // ...
}

The factory is installed using the adapters extension point

<extension point="org.eclipse.core.runtime.adapters">
  <factory
      adaptableType="com.rcpcompany.ex.adapterhandlers.model.Segment"
      class="com.rcpcompany.ex.adapterhandlers.adapters.SegmentAdapterFactory">
    <adapter type="com.rcpcompany.ex.adapterhandlers.functionality.IDoShowSegment" />
  </factory>
</extension>

[The used functional interface isn't perfect, as it forces the adapter factory to construct a new object for each call to the getAdapter method. It can be helped by changing the method to doShowSegment(Object,Segment), but... for the sake of simplicity...]

To invoke the "Show Segment" functionality for a specify object, we when have to do the following:

final IDoShowSegment showSegment = (IDoShowSegment)Platform.getAdapterManager().getAdapter(element, IDoShowSegment.class);
if (showSegment != null) {
  final Shell shell = ...;
  showSegment.doShowSegment(shell);
}

Note that we here just ignore the case where the object cannot be adapted to our functional interface.... Also note that the getAdapter method of the adapter manager automatically checks if 1) the object directly implements the interface, 2) the object implements the IAdaptable interface - and then uses this, or 3) if an adapter factory is installed for the interface and object class.

In the context of the handler, this is as follows - with the obvious declarations for the handler.

public class ShowSegmentHandler extends AbstractHandler implements IHandler {
  @Override
  public Object execute(ExecutionEvent event) throws ExecutionException {
    final ISelection selection = HandlerUtil.getCurrentSelectionChecked(event);
    if (!(selection instanceof IStructuredSelection)) {
      return null;
    }
    final IStructuredSelection ss = (IStructuredSelection) selection;
    final Object element = ss.getFirstElement();
    final IDoShowSegment showSegment = (IDoShowSegment) Platform.getAdapterManager().getAdapter(element,
        IDoShowSegment.class);
    if (showSegment == null) {
      return null;
    }
    final Shell shell = HandlerUtil.getActiveShellChecked(event);
    showSegment.doShowSegment(shell);
    return null;
  }
}

With the above code and a menus extension, the "Show Segment" command is shown as enabled in the popup menu for all items - even the Contact objects.

To make the handler only active when the selection is an object that supports the functional interface, use the following handlers declaration:

<extension point="org.eclipse.ui.handlers">
  <handler
        class="com.rcpcompany.ex.adapterhandlers.handlers.ShowSegmentHandler"
        commandId="com.rcpcompany.ex.adapterhandlers.commands.ShowSegment">
    <activeWhen>
      <iterate ifEmpty="false" operator="and">
        <adapt type="com.rcpcompany.ex.adapterhandlers.functionality.IDoShowSegment" />
      </iterate>
    </activeWhen>
  </handler>
</extension>

With this declaration the command is only enabled in the popup menu when relevant, though it is still shown for the Contact objects.

To show the command only when an active handler exists, use the following visibleWhen expression:

<extension point="org.eclipse.ui.menus">
  <menuContribution locationURI="popup:org.eclipse.ui.popup.any">
    <command commandId="com.rcpcompany.ex.adapterhandlers.commands.ShowSegment" style="push">
      <visibleWhen checkEnabled="false">
        <iterate ifEmpty="false" operator="and">
          <adapt type="com.rcpcompany.ex.adapterhandlers.functionality.IDoShowSegment" />
        </iterate>
      </visibleWhen>
    </command>
  </menuContribution>
</extension>

Note that this only use the activeWhen expression to control the visibility of the command in the menu. This is slightly different from the visibleWhen with checkEnabled=true, which is based on the enabledWhen expression.

The End Result

The end result is that if you want to implement the command for a new or existing object you must either extend the object with the functional interface or add an adapter for the object class to the functional interface - I usually prefer the later.

To find the objects that supports the command, just look for implementations of the interface - which by the way I normally name "IDo<command-id>" - here IDoShowSegment.

Useful? In larger applications, yes - in small applications, this is probably like shooting flies with canons. Is it worth the added code and the redirection in the handler? That can only be decided by you...

2007-06-19

Using activities for user management

One common requirement from almost all RCP based applications is the ability to restrict parts of the user interface based on the identity of the current user: when the user logs in as the application is started, the current capabilities of the user is typically downloaded from a sign-on server, and now we want to restrict the user interface of the application based on these capabilities.

This blog entry will show how to implement this using activities in Eclipse RCP. A demonstration plug-in is found here.

There seems to be at least a couple of possible solutions based on plug-ins: only install the plug-ins relevant for the user or only load the plug-ins that are allowed for the user. Although this is not all that difficult to implement, it is a rather coarse solution. It is not always the case that the functionality of plug-ins can be divided along the same lines as wanted from the security system. It is similar to using a canon to shoot a mosquito...

A much more elegant solution is to use activities - also known as capabilities - for the job as these can be used completely orthogonal to plug-ins: once the activities can be defined, these can be used to restrict just about any contribution of the interface in any plug-in.

Another very nice property of activities, is the fact the almost all of the implementation is confined to the various plugin.xml files. Only a very small part of the functionality must go into the Java code itself and even that can be limited to the WorkbenchAdvisor or a similar place...

To use activities you must use the following model:
  • Activity: A single activity (or capability)
  • Category: An arbitrary set of activities - these are only relevant if you expect to expose the Capabilities preference page to the users, which is very unlikely when activities are used in this use case...
  • Pattern: A regular expression for the contributions that are managed via an activity
  • Manager: The activity manager
  • Identifier: A single ID from a specific plug-in
It is possible to assign many different types of contributions to activities, so the contributions are only enabled if the activity is enabled: perspectives, views, editors, wizards, preference pages, menus, toolbars, commands, and actions.

You must go through the following steps to use the activities in your application.

Defining a new activity


To define a new activity you use the following org.eclipse.ui.activities/activity extension:

<extension
point="org.eclipse.ui.activities">
<activity
description="The Support Role"
id="com.rcpcompany.demo.activities.support"
name="Sample action"/>
</extension>

The ID is normally chosen to make it easy to map the capabilities returned by the sign-on server to the internally used IDs. In this case, we assume that the returned token is "support" so it is just a matter of concatenating two strings :-)

Assigning contributions to activities


This is the difficult part, until you find out how to map the different contributions to the ID scheme used in the activity patterns.

Basically you must add a org.eclipse.ui.activities/activityPatternBinding extension for each item you want to handle. Although it is possible to use regular expressions to match many IDs at once, this can hardly be recommended as this makes it rather hard to assign IDs to the different contributions used in an RCP application.

Given the following definition for a view:

<extension
point="org.eclipse.ui.views">
<view
class="com.rcpcompany.demo.ui.views.History"
id="com.rcpcompany.demo.ui.views.History"
name="History"/>
</extension>

the following definition is used to restrict access to the view based on whether the activity is enabled:

<extension
point="org.eclipse.ui.activities">
<activityPatternBinding
activityId="com.rcpcompany.demo.activities.support"
pattern=".*/com\.rcpcompany\.demo\.ui\.views\.History"/>
</activityPatternBinding>
</extension>

The IDs to use for different contributions is taken from the id attribute used in the definition elements.

I use one conversion to help avoid some of the work: when a command is used in a org.eclipse.ui.menus/menuContribution/command the id is the same as the command with suffix .mc.something. As in the following example:

<extension
point="org.eclipse.ui.commands">
<command
description="Shows the name of the current resource"
id="com.rcpcompany.demo.ui.commands.showName"
name="&Show Name">
</command>
</extension>

<extension
point="org.eclipse.ui.menus">
<menuContribution
locationURI="menu:file">
<command
commandId="com.rcpcompany.demo.ui.commands.showName"
id="com.rcpcompany.demo.ui.commands.showName.mc.file">
<visibleWhen>
...
</visibleWhen>
</command>
<command
commandId="org.eclipse.ui.edit.rename"
id="org.eclipse.ui.edit.rename.mc.file">
</command>
</menuContribution>
<menuContribution
locationURI="toolbar:org.eclipse.ui.main.toolbar">
<toolbar
id="com.rcpcompany.demo.ui.toolbars.main.show">
<command
commandId="com.rcpcompany.demo.ui.commands.showName"
id="com.rcpcompany.demo.ui.commands.showName.mc.toolbar.main.show">
</command>
</toolbar>
</menuContribution>
</extension>

<extension
point="org.eclipse.ui.activities">
<activityPatternBinding
activityId="com.rcpcompany.demo.activities.support"
pattern=".*/com\.rcpcompany\.demo\.ui\.commands\.showName(\.mc\..*)?"/>
</activityPatternBinding>
</extension>

Enabling activities


To enable the needed features when the user is logged in, the following code can be used in WorkbenchAdvitor:

@Override
public void initialize(IWorkbenchConfigurer configurer) {
super.initialize(configurer);

IWorkbenchActivitySupport activitySupport = configurer.getWorkbench()
.getActivitySupport();
IActivityManager activityManager = activitySupport.getActivityManager();

Set<String> enabledActivities = new HashSet<String>();
String id = "com.rcpcompany.training.demo.activities.activities." + myRole;
if (activityManager.getActivity(id).isDefined()) {
enabledActivities.add(id);
}

activitySupport.setEnabledActivityIds(enabledActivities);
}

Please notice the the code tests whether the role exists before using it. If this is not done, an undeclared activity will be enabled and no parts of the interface will be affected.

And the rest...

There are a number of other things to be done as well depending on the actual application:
  • If the roles can be updated while the application is running, you must repeat the code above in your listener. Unfortunately, you must also clean up the existing perspectives, views and editors in the workbench. This is particular difficult for editors as this can be dirty or in an inconsistent state where they cannot just be saved...
  • If you want to debug what contributions that are visible and not, then use the Capability preference page:

    <extension
    point="org.eclipse.ui.preferencePages">
    <page
    category="org.eclipse.ui.preferencePages.Workbench"
    name="Capabilities"
    id="org.eclipse.sdk.capabilities"
    class="org.eclipse.ui.activities.ActivitiesPreferencePage"/>
    </extension>
    Please note that you also need an activity category in this case...
  • There can be any number of roles enabled at the same time.
  • Roles can depend on other roles, so you can have "support manager" that depends on "support".
  • A demonstration plug-in is found here.

2007-06-17

Translating objectContributions into menuContributions

Maybe it is trivial... but never the less I have just used the better part of a day understanding just why my initial proposal didn't work :-(

Consider the following action definition used in Eclipse 3.2.
<extension
    point="org.eclipse.ui.popupMenus">
  <objectContribution
      adaptable="false"
      id="com.rcpcompany.demo.providers.ui.objectContributions.ires.general"
      objectClass="com.rcpcompany.training.demo.core.IRes">
    <action
        class="com.rcpcompany.training.demo.providers.ui.actions.ShowName"
        icon="icons/ShowName.gif"
        id="com.rcpcompany.demo.providers.ui.actions.showname"
        label="Show Name">
    </action>
    <filter
        name="folder"
        value="true">
    </filter>
  </objectContribution>
</extension>
This definition will add a new action with the text "Show Name" to all popup menus if the current selection contains at least one object of the IRes interface and this object returns true for the folder (using an IActionFilter). It is a rather tense description.

For Eclipse 3.3, this description is spread over a number of extension.

First a commands extension is used to declare the command itself:
<extension
    point="org.eclipse.ui.commands">
  <command
      description="Shows the name of the current resource"
      id="com.rcpcompany.training.demo33.providers.ui.commands.showName"
      name="&Show Name">
  </command>
</extension>

Then the image of the command must be added:
<extension
    point="org.eclipse.ui.commandImages">
  <image
      commandId="com.rcpcompany.training.demo33.providers.ui.commands.showName"
      icon="icons/ShowName.gif">
  </image>
</extension>

The next thing is to add a handler for the command:
<extension
    point="org.eclipse.ui.handlers">
  <handler
      class="com.rcpcompany.training.demo33.providers.ui.handlers.ShowName"
      commandId="com.rcpcompany.training.demo33.providers.ui.commands.showName">
    <enabledWhen>
      <with
          variable="selection">
        <iterate
            ifEmpty="false"
            operator="and">
          <and>
            <instanceof
                value="com.rcpcompany.training.demo33.core.IRes">
            </instanceof>
            <test
                property="com.rcpcompany.isFolder">
            </test>
          </and>
        </iterate>
      </with>
    </enabledWhen>
  </handler>
</extension>

This is where most of the complexity is in the new implementation: the tense declaration from 3.2 is now rather verbose. Having said that, the new notation also allows for much more complicated stuff than what was possible before. Note that the IActionFilter has been replaced with a IPropertyTester - which is very good, as all properties now need not be in a single test object, but can be distributed over any number of o objects.

Last the popup menus must be added to all popup menus:
<extension
    point="org.eclipse.ui.menus">
  <menuContribution
      locationURI="popup:org.eclipse.ui.popup.any">
    <command
 commandId="com.rcpcompany.training.demo33.providers.ui.commands.showName">
    </command>
  </menuContribution>
</extension>

Depending on the small details, there can be some changes to this - e.g. if the command should only be shown when the selection includes the right objects.

So what didn't work? I hadn't added the iterate test declaration! And since my selection actually was a StructedSelection, then the implicit iteration from 3.2 had to be handled explicitly.

Having worked with the new notation for a number of days now, I think the new notation is better than the old notation. If you just use a command in a single place, then the new notation will take longer to use, but if the same command is used in at least a few places, then the new notation is faster. With other words: the basic overhead of the new notation is larger than for the old notation...

2007-06-15

Working with the menus extension point

I'm in the process of updating my training materials for the new org.eclipse.ui.menus extension point in Eclipse 3.3. To do that I first have to get acquainted with the new functionality.

The new extension point is basically a replacement of the following extension points from Eclipse 3.2:
org.eclipse.ui.popupMenus/viewerContribution
Used to add an action to a specific popup menu (usually a context menu or a ruler) of a view or editor
org.eclipse.ui.popupMenus/objectContribution
Used to add an action to all popup menus when a specific object type is selected
org.eclipse.ui.actionSets/actionSets
Used to add an action to the top-level menu bar and top-level tool bar
org.eclipse.ui.editorActions/editorContribution
Used to add an action to a specific editor top-level menu and tool bar
org.eclipse.ui.viewActions/viewContribution
Used to add an action to a specific view local menu
The new menus extension point not only replaces these extension points, but also adds the ability to add stuff to the trim areas and to the status line. Adding to the trim areas was difficult in Eclipse 3.2, whereas adding to the status line could only easily be done in the ActionBarAdvisor - which made to difficult to use in applications with multiple plug-ins.

To add stuff to any menu or tool bar in Eclipse 3.3, you basically needs two things:
  • The ID of the menu or tool bar.
  • A command defined with the commands extension point.
The ID of the top-level menus and tool bars are:
  • menu:org.eclipse.ui.main.menu – the top-level menu
  • popup:org.eclipse.ui.any.popup – all pop-up menus
  • toolbar:org.eclipse.ui.main.toolbar – the top-level tool bar
  • toolbar:org.eclipse.ui.trim.command1 – the top left trim area
  • toolbar:org.eclipse.ui.trim.command2 – the top right trim area
  • toolbar:org.eclipse.ui.trim.vertical1 – the left vertical trim area
  • toolbar:org.eclipse.ui.trim.vertical2 – the right vertical trim area
  • toolbar:org.eclipse.ui.trim.status – the status line trim area
See MenuUtil for constants.
So... to add a new top-level menu along with a single sample action you get:
<extension
point="org.eclipse.ui.menus">
<menuContribution
  locationURI="menu:org.eclipse.ui.main.menu?after=additions">
<menu
    id="com.rcpcompany.demo.menus33.menus.sampleMenu"
    label="Sample Menu"
    mnemonic="M">
  <command
      commandId="com.rcpcompany.demo.menus33.commands.sampleCommand"
      id="com.rcpcompany.demo.menus33.menus.sampleCommand"
      mnemonic="S">
  </command>
</menu>
</menuContribution>
</extension>
Notice the locationURI above. This specification contains three parts, as described in the extension point documentation:

[Scheme]:[ID]?[ArgList]
  • Scheme - The 'type' of the UI component into which the contributions will be added. It may be either "menu", "popup" or "toolbar". While 'popup' is indeed a form of menu it is provided to allow a distinction between a view's 'chevron' menu (for which we use the "menu" scheme) and its default context menu which, by convention, should be registered using the "popup" scheme.
  • ID - This is the id of menu or toolbar into which the contributions should be added. By convention views should use their view id as the id of the root of their chevron and default popup menu. Note that there is no explicit distinction between contributions supporting editors and 'normal' contributions into the Menu Menu or Toolbar; both global contributions and editor contributions would use the "org.eclipse.ui.main.menu" id or "org.eclipse.ui.main.toolbar". A special id used with popup:, "org.eclipse.ui.any.popup", is reserved to handle contributions which are candidates to appear on any (top level) context menu. Note that these contributions are expected to implement a 'visibleWhen' expression sufficient to limit their visibility to appropriate menus
  • Query - This field allows fine-grained definition of the specific location within a given menu. It has the form "[placement]=[id]" where placement is one of "before" or "after" and the id is expected to be the id of some IContributionItem in the menu.

Nearly all the needed information about the menu item is picked up from the command definition (and optionally from the commandImages extension point) except for the tool tip which for some reason cannot be specified in the command declaration.

If you add anything to any of the top-level tool bars (those with the toolbar scheme), you must remember that these really are cool bars and you must therefore first add a tool bar before you add the command itself:
<extension
point="org.eclipse.ui.menus">
<menuContribution
  locationURI="toolbar:org.eclipse.ui.trim.status?after=BEGIN_GROUP">
<toolbar
    id="com.rcpcompany.demo.menus33.toolbars.sampleToolbar">
  <command
      commandId="com.rcpcompany.demo.menus33.commands.sampleCommand">
  </command>
</toolbar>
</menuContribution>
</extension>


One last thing: Previously it was quite clear that you had to have certain specific stuff in the ActionBarAdvisor: if you wanted to use any of the actions from the ActionFactory, then this must be hard-coded, which for practical reasons meant the menu structure would also be hard-coded.

Not so in Eclipse 3.3: here you can just register the needed actions from the ActionFactory in ActionBarAdvisor.makeActions(IWorkbenchWindow) and then declare the rest in the menus extension point using the proper command IDs. Look in the org.eclipse.ui plug-in for a list of all the defined IDs.