Activities

An activity is a logical grouping of functionality that is centered around a certain kind of task. For example, developing Java software is an activity commonly performed by users of the platform, and the JDT defines many UI contributions (views, editors, perspectives, preferences, etc.) that are only useful when performing this activity. Activities can be used to implement progressive disclosure of UI elements; when used for this purpose, they are called capabilities in the UI. The second use for activities is to filter available UI elements based on other criteria such as the current user's access permissions as defined bythe application.

In the following text, we will be using the following terms to distinguish between the two uses of activities:

Conventional Activities

Conventional activities are exposed to the user under the name capabilities, although not in a way that is apparent to a new user. When an activity is enabled in the platform, the UI contributions associated with that activity are shown. When a activity is disabled in the platform, its UI contributions are not shown. Users can enable and disable these conventional activities as needed using the Command link General > Capabilities preference page.

Certain user operations serve as trigger points for enabling an activity. For example, creating a new Java project could trigger the enabling of the Java development activity. In this way, users are exposed to new functionality as they need it, and gradually learn about the activities that are available to them and how they affect the UI. When a user first starts the platform, it is desirable for as many activities as possible to be disabled, so that the application is as simple as possible. Choices made in the welcome page can help determine what activities should be enabled.

There are certain places in the UI where the user can ask to see all contributions - even the ones filtered by conventional activities, for example in the New... wizard. UI elements that are filtered by conventional activities can also still be used programmatically using the Eclipse API.

Expression-based Activities

Expression-based activities differ from conventional activities in that:

Conventional Activities vs. Perspectives

We have seen (in Perspectives) how perspectives are used to organize different view layouts and action sets into tasks. Why do we need activities? While perspectives and activities define similar kinds of tasks, the main difference is how the UI contributions for a plug-in are associated with them. UI contributions are associated with perspectives in the extension definition of the contribution. That is, a plug-in is in charge of determining what perspectives its views and action sets belong to. Plug-ins are also free to define their own perspectives. Even when a perspective is not active, the user can access the views and actions associated with the perspective through commands such as Show View.

Activities are a higher level of organization. Individual UI contributions are not aware of activities and do not refer to the activities in their extension definitions. Rather, the activities are expected to be configured at a higher level such as platform integration/configuration or product install. Individual plug-ins typically do not define new activities, unless the plug-in is a systems-level plug-in defined by a systems integrator. In a typical scenario, a systems integrator determines how functions are grouped into activities and which ones are enabled by default. Activities are associated with UI contributions using activity pattern bindings, patterns that are matched against the id of the UI contributions made by plug-ins. An example will help demonstrate these concepts.

Defining Activities

Activities are defined using the org.eclipse.ui.activities extension point.
Let's look at first at a simplified version of how the Eclipse SDK plug-in defines two conventional activities - one for developing Java software and one for developing plug-ins:
<extension
  point="org.eclipse.ui.activities">
  <activity
    name="Java Activity"
    description="Developing Java Software"
    id="org.eclipse.javaDevelopment">
  </activity>
  
  <activity
    name="Plug-in Activity"
    description="Developing Eclipse Plug-ins"
    id="org.eclipse.plugInDevelopment">

  </activity>
  ...
Activities are assigned a name and description. This name and description can be shown to the user whenever the they are enabling and disabling conventional activities, or otherwise shown information about an activity. The id of the activity is used when defining pattern bindings or other relationships between activities. For example, for conventional activities, it is possible to declare that one activity requires another activity.
<activityRequirementBinding
  activityId="org.eclipse.plugInDevelopment"
  requiredActivityId="org.eclipse.javaDevelopment">

</activityRequirementBinding>

The requirement binding states that the plug-in development activity can only be enabled when the Java development activity is enabled. Related conventional activities can also be bound into categories, that are shown to the user when the user is working with activities.
<category
  name="Development"
  description="Software Development"
  id="org.eclipse.categories.developmentCategory">
</category>

<categoryActivityBinding
  activityId="org.eclipse.javaDevelopment"
  categoryId="org.eclipse.categories.developmentCategory">

</categoryActivityBinding>
      
<categoryActivityBinding
  activityId="org.eclipse.plugInDevelopment"
  categoryId="org.eclipse.categories.developmentCategory">
</categoryActivityBinding>

The category groups the related development conventional activities together. This category is shown to the user when the user manually configures conventional activities. Note that expression-based activities can be also added to categories, but they are ignored when the user changes states of these categories.

Binding Activities to UI Contributions

Activities can be associated with UI contributions by referring to them by id, or by pattern matching. The pattern matching used in activity pattern bindings follows the rules described in the java.util.regex package for regular expressions. The patterns used by the workbench are composed of two parts. The first part uses the identifier of the plug-in that is contributing the UI extension. The second part is the id used by plug-in itself when defining the contribution (which may or may not also include the plug-in id as part of the identifier). The following format is used:

plug-in-identifier + "/" + local-identifier
For example, the following activity pattern binding states that a UI contribution from any JDT plug-in id (org.eclipse.jdt.*) is associated with the Java development activity regardless of its local identifier (.*).
<activityPatternBinding
  activityId="org.eclipse.javaDevelopment"
  pattern="org\.eclipse\.jdt\..*/.*">
</activityPatternBinding>
The next binding is more specific. It states that the contribution named javanature defined in the JDT core (org.eclipse.jdt.core) is associated with the Java development activity.
<activityPatternBinding
  activityId="org.eclipse.javaDevelopment"
  pattern="org\.eclipse\.jdt\.core/javanature">

</activityPatternBinding>
It is also possible to refer to a single UI contribution using its id without having to use regular expression syntax if the attribute isEqualityPattern is set to true.
The following XML shows the previous example with the isEqualityPattern set to true.

<activityPatternBinding
  activityId="org.eclipse.javaDevelopment"
  pattern="org.eclipse.jdt.core/javanature"
  isEqualityPattern="true">
</activityPatternBinding>

As you can see, activity pattern bindings can be used to associate large groups of contributions with a particular activity, or to associate very specific contributions with an activity. The following contributions are affected by activities: The convention used by the workbench (plug-in id + local id) allows easy binding to plug-ins that do not necessarily follow the naming practice of prefixing their UI contribution identifiers with their plug-in's identifier. Plug-ins that directly interact with the activity API are free to use their own format for identifying contributions and for pattern-matching against those names.

Binding Activities to Help Contributions

Activities are associated with help contributions using the same pattern matching scheme used for UI contributions. The second part of the identifier (the local identifier) indicates the name of the table of contents (TOC) file. For example, the following activity pattern binding associates all TOC files contributed by JDT plug-ins (org.eclipse.jdt.*) with the Java development activity:
<activityPatternBinding
  activityId="org.eclipse.javaDevelopment"
  pattern="org\.eclipse\.jdt\..*/.*">
</activityPatternBinding>
When the Java development activity is disabled, help books contributed by JDT plug-ins, or any sub-books (TOCs linked to, or linked by JDT books), even if contributed by a different plug-in, will not show in the help UI. The topics defined in these books will also not show in the search results. In the case where JDT TOCs were not displayed as primary TOCs, but were instead linked from another TOC to appear as sub-trees in a book, disabling the JDT activity has the effect of hiding the sub-trees. The containing book will appear to define less topics in the UI. Using more specific binding, it is possible to associate activities with selected TOCs from plug-ins that contribute multiple TOCs to the help system. For example, the following activity pattern binding associates the "Examples" TOC with the Java development examples activity.
<activityPatternBinding
  activityId="org.eclipse.javaDevelopmentExamples"
  pattern="org\.eclipse\.jdt\.doc\.isv\.topics_Samples.xml">

</activityPatternBinding>
With such pattern binding, disabling the Java development examples activity will hide the "Examples" section from the "JDT Plug-in Developer Guide" book.

Using the Activities API

The workbench activity support includes an API for working with all defined activities (to some extent also expression-based activities) and changing the enabled state (only for conventional activities). Most plug-ins need not be concerned with this API, but it is useful when implementing functions that allow the user to work with activities, or for implementing the trigger points that enable a particular conventional activity. It is assumed that any plug-in that is manipulating activities through API is quite aware of the ways that activities are configured for a particular product. For example, the workbench itself uses the API to trigger the enablement of conventional activities such as Java development. We'll look at how the workbench uses the generic activity API to implement triggers. The hub of all activity in the workbench is IWorkbenchActivitySupport. The activity support works in tandem with an IActivityManager. Plug-ins can obtain the activity support instance from the workbench, and the activity manager from there.
IWorkbenchActivitySupport workbenchActivitySupport = PlatformUI.getWorkbench().getActivitySupport();
IActivityManager activityManager = workbenchActivitySupport.getActivityManager();
The following snippet enables the Java development activity (if it is not already enabled). It shows a simplified version of a trigger.
...
//the user did something Java related.  Enable the Java activity.
Set enabledActivityIds = new HashSet(activityManager.getEnabledActivityIds());
if (enabledIds.add("org.eclipse.javaDevelopment"))
  workbenchActivitySupport.setEnabledActivityIds(enabledActivityIds);

IActivityManager also defines protocol for getting all defined activity and category ids, and for getting the associated IActivity or ICategory for a particular id. These objects can be used to traverse the definition for an activity or category in API, such as getting the pattern bindings or requirement bindings. Listeners can be registered on the activity manager or on the activities and categories themselves to detect changes in the definition of a particular activity or in the activity manager itself. See the package org.eclipse.ui.activities for more information.

Note that the API methods will silently ignore attempts to enable expression-based activities, or similar requests that do not apply to expression-based activities.

Using Expression-based Activities

To filter a UI element using an expression-based activity, create an activity like the following:
<activity
 id="forbiddenViewActivityId" name="Forbidden View Activity">
  <enabledWhen>
    <with variable="rightsVariable">
      <iterate ifEmpty="false" operator="or">
        <equals value="grantShowForbidden" />
      </iterate>
    </with>
  </enabledWhen>
</activity>


Then, bind this activity to a UI element, for example a view:
<activityPatternBinding
 activityId="forbiddenViewActivityId"

 <!-- Switches the interpretation of the pattern as regular expression off -->
 isEqualityPattern="true"
 pattern="DemoRCP/demorcp.views.ForbiddenView">
</activityPatternBinding>

The following code snippets show how to control the variable "rightsVariable" that appears in the activity's "enabledWhen" expression. New variables can be added through the org.eclipse.ui.services extension point as subclasses of AbstractSourceProvider.

import java.util.HashMap;
...

import org.eclipse.ui.AbstractSourceProvider;
import org.eclipse.ui.PlatformUI;
import org.eclipse.ui.handlers.IHandlerService;
import org.eclipse.ui.services.IEvaluationService;

public class RightsSourceProvider extends AbstractSourceProvider {

  public final static String RIGHT_FORBIDDEN = "grantShowForbidden";

  public final static String RIGHTS_VARIABLE = "rightsVariable";
  private final static String[] PROVIDED_SOURCE_NAMES = new String[] { RIGHTS_VARIABLE };

  private final static Map<String, List<String>> stateMap = new HashMap<String, List<String>>();

  public Map getCurrentState() {
    /* "YourRightsHandler" is here just an example for a static class
     * which returns the list of rights as a list of strings. */

    stateMap.put(RIGHTS_VARIABLE, YourRightsHandler.getUserRights());
    return stateMap;
  }

  public String[] getProvidedSourceNames() {
    return PROVIDED_SOURCE_NAMES;
  }

  /* This triggers an update of the rights variable state, and will update also all 
   * listeners to the evaluation service. So that every menu point, which is also
   * expression controlled, gets updated too. */

  public void updateRights() {
    fireSourceChanged(0, getCurrentState());
  }

  // ...
}