站内搜索: 请输入搜索关键词
当前页面: 图书首页 > Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Section 17.2.  Declarative Actions in Hyperbola - Eclipse Rich Client Platform: Designing, Coding, and Packaging Java Applications

Previous Page
Next Page

17.2. Declarative Actions in Hyperbola

Imagine a scenario where Hyperbola is extended by the Debug and MUC (multi-user chat) plug-ins. Ideally, the actions defined by these plug-ins are placed side-by-side in some top-level menu and the fact that they originate from different sources is hidden. This promotes the added behavior as an integral part of Hyperbola rather than as something tacked on the side.

Here we focus on this scenario and talk about:

  • The different kinds of declarative actions.

  • Extending the ActionBarAdvisor to support action contributions into the top-level menu and toolbar.

  • Using an action set to add two declarative actions, "Export Contacts" and "Import Contacts," to Hyperbola's top-level menu and toolbar. The actions are added in several locations to demonstrate placement control.

  • Adding a context menu with Export and Import actions to the Contacts view.

17.2.1. Declarative Actions

One of the Workbench's main roles is as an integration point. Plug-ins contribute actions, views, and the like to the Workbench and the Workbench structures, places, and manages them. Diverse sets of plug-ins are thus integrated and the user experience is improved. To participate in the integration, however, plug-ins must supply their contributions declaratively rather than programmatically.

The Workbench supports the following extension points into which plug-ins make action contributions, describing the action's implementation, placement, icon, and label. Armed with a set of extensions to these extension points, the Workbench inserts the actions into existing menus and toolbars.

org.eclipse.ui.actionSets This extension describes a set of menus and actions that is added to the top-level menu and toolbar. Action sets are enabled and disabled as a group.

org.eclipse.ui.popupMenus This extension point is used to contribute actions to context menus that have been registered with the Workbench.

org.eclipse.ui.editorActions This extension point is used to add actions to the top-level menu and toolbar when a particular editor is enabled.

org.eclipse.ui.viewActions This extension point is used to add actions to the local menu and toolbar for views.

Within the context of a small application, such as Hyperbola, there is usually no need to use declarative actions. However, there are benefits of using declarative actions that should be considered:

  • Declarative actions are shown in the UI without loading their associated plug-in. In large applications with many plug-ins, this is crucial to scalability.

  • Dynamic reconfiguration of top-level menus and toolbars based on the active perspective is enabled by associating action sets with perspectives.

  • Users can configure top-level menus and toolbars via the perspective customization dialog (see the ActionFactory.EDIT_ACTION_SETS action).

The downside of declarative actions is that they cannot build on one another and their ordering within a menu is controlled by the Workbench. In other words, you cannot deterministically order two actions sets within the same menu. The other issue is that toolbar and menu paths are error-prone. It's hard to find the right paths, and when you get it wrong, menus just do not show up.

17.2.2. Allowing Contributions

To take advantage of these features, RCP applications should define a top-level menu and toolbar skeleton in the ActionBarAdvisor and use declarative actions for everything else.

Even if you choose not to use declarative actions for your part of the application, you should design your application so that other plug-ins can extend its menus and toolbars. This way, third parties can add functions and you can ship additional functions after the main product ships.

The first step is to add placeholders into the top-level menus and toolbars. This is where the declarative actions will plug in. Placeholders are added to IContributionManager instances, such as ToolbarManager or MenuManager. They are named entities that are referenced from declarative action definitions.

Hyperbola already has a named top-level menu. In the ActionBarAdvisor method fillMenu(), the Hyperbola menu is created as a MenuManager using the snippet below. The first parameter to the constructor is the menu name, as shown in the UI, and the second, "hyperbola", is the id of the menu.

org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
MenuManager hyperbolaMenu = new MenuManager("&Hyperbola", "hyperbola");

You can define placeholders using the "hyperbola" menu as part of the path (e.g., hyperbola/placeholder). Once the placeholders are defined, the Workbench takes care of the restit decides which contributed actions are applicable and automatically inserts them when the toolbar or menu is shown.

The Workbench supplies a standard placeholder id that is used to mark the location in a contribution manager where contributions are added. The constant IWorkbenchActionConstants.MB_ADDITIONS is used in the following code snippets to identify placeholders. It is then used in plugin.xml files of plug-ins contributing action sets to link actions into menus and toolbars.

org.eclipse.ui/IWorkbenchActionConstants
/**
 * Name of group for adding new top-level menus (value "additions").
 */
public static final String MB_ADDITIONS = "additions";

Let's change Hyperbola's ActionBarAdvisor to allow contributed actions in the following areas:

  • on the menu bar, between the Hyperbola and Help menus

  • in the Hyperbola menu, between the first two groups

  • at the end of the toolbar

  • in the Contacts view's context menu

We cover the first three actions next and the context menu action in Section 17.2.4, "Context Menus."

There's no magic when adding placeholders; you simply add them the same way that actions are added. The following code snippet from Hyperbola's ActionBarAdvisor adds a placeholder to the Hyperbola menu as well as to the menu bar and the end of the toolbar:

org.eclipsercp.hyperbola/ApplicationActionBarAdvisor
protected void fillMenuBar(IMenuManager menuBar) {
  // Top-level menu called Hyperbola with id 'hyperbola'.
  MenuManager hyperbolaMenu = new MenuManager("&Hyperbola", "hyperbola");
  hyperbolaMenu.add(addContactAction);
  hyperbolaMenu.add(removeContactAction);
  hyperbolaMenu.add(chatAction);
  // Placeholder within the 'hyperbola' menu called 'additions'. This
  // can be referenced as 'hyperbola/additions'.
  hyperbolaMenu.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
  hyperbolaMenu.add(new Separator());
  hyperbolaMenu.add(preferencesAction);
  hyperbolaMenu.add(new Separator());
  hyperbolaMenu.add(exitAction);
  ...
  menuBar.add(hyperbolaMenu);
  // Top-level menu placeholder with id 'additions'.
  menuBar.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
  menuBar.add(helpMenu);
}

protected void fillCoolBar(ICoolBarManager coolBar) {
  IToolBarManager toolbar = new ToolBarManager(coolBar.getStyle());
  coolBar.add(toolbar);
  toolbar.add(addContactAction);
  toolbar.add(removeContactAction);
  toolbar.add(chatAction);
  // Top-level toolbar placeholder with id 'additions'.
  coolBar.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
}

Placeholders are added as either Separators or GroupMarkers. Separators surround all contributions by the appropriate separators, whereas contributions to a GroupMarker are added as-is, without any additional separators.

Tip

To support action contributions to menus anywhere in your application, not just at the top level of menus, you must document the menu and group ids defined by your application.

The Workbench provides a list of commonly used menu ids in IWorkbenchActionConstants. The IDE product uses these, but you are free to use your own identifiers instead. These ids effectively become API and so should be documented and maintained.


17.2.3. Declaring Actions

Now that the placeholders are in place, let's declaratively add some action sets to Hyperbola. In this example, the two actions called "Export Contacts" and "Import Contacts" are added to the three placeholders we just defined. In practice, placing an action in one or two spots is enough, but here we want to demonstrate all the cases.

The action set below adds a menu called "Tools" to the top-level menu bar and adds the import and export actions to that menu.

org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.ui.actionSets">
  <actionSet
      id="org.eclipsercp.hyperbola.actionSet1"
      label="Hyperbola Tools"
      visible="true">
    <menu
        id="org.eclipsercp.hyperbola.tools"
        label="&amp;Tools"
        path="additions">
      <groupMarker name="group1"/>
    </menu>
    <action
        class="org.eclipsercp.hyperbola.actions.ExportContactsAction"
        icon="icons/export.gif"
        id="org.eclipsercp.hyperbola.exportContacts"
        label="&amp;Export Contacts"
        menubarPath="org.eclipsercp.hyperbola.tools/group1"
        style="push"/>
    <action
        class="org.eclipsercp.hyperbola.actions.ImportContactsAction"
        icon="icons/import.gif"
        id="org.eclipsercp.hyperbola.importContacts"
        label="&amp;Import Contacts"
        menubarPath="org.eclipsercp.hyperbola.tools/group1"
        style="push"/>
  </actionSet>
</extension>

The first thing in an action set is its id. An id must be globally unique relative to all other action set ids. The easiest way to ensure uniqueness is to prefix the id by the id of the contributing plug-in.

Action sets are not normally shown to the user. The actionSet element's label attribute is used to represent the action set in the perspective configuration dialog (see ActionFactory.EDIT_ACTION_SETS). The actionSet element's visible attribute determines if the action set should be visible in all perspectives. If this is set to false, then perspectives determine which action sets to show using IPageLayout.addActionSet(String). When true, the action set can only be hidden by the user if the perspective customization dialog is shown, or by calling the IWorkbenchPage method hideActionSet(String).

The menu element defines a top-level menu into which actions can be placed. The menu element's path attribute causes the menu to be placed in the menu bar in the "additions" placeholder that we defined in the code earlier. The menu element's label attribute defines the text that is shown in the UI to represent the menu. This menu element also defines a groupMarker element that defines a placeholder for actions. The menu element's menu id is important because it is used to place the actions defined in the containing actionSet element. Again, the menu element's id should be unique among menus.

The action elements carry enough information to support displaying an action without running any code. That information includes the label, icon, and action style (e.g., push button, toggle, radio). The location of the action is determined by the action element's menubarPath attribute. The path consists of multiple menu ids with a terminating group id. In this example, the menu and group happen to be defined by the action set's menu and the menu itself is placed in the menu bar.

The implementation of an action is defined by its class attribute. The supplied class must implement an interface that is particular to the extension point to which the action is being added. For example, here the class must implement IWorkbenchWindowActionDelegate. This gives the action a Workbench window as context for its operation. If the action was added to a view or editor, it would have to implement a different interface. All these interfaces extend IActionDelegate. Let's take a look at the ImportContactsAction:

org.eclipsercp.hyperbola/ImportContactsAction
public class ImportContactsAction implements IWorkbenchWindowActionDelegate
{
  private IWorkbenchWindow window;

  public void dispose() {
  }

  public void init(IWorkbenchWindow window) {
    this.window = window;
  }

  public void run(IAction action) {
    HyperbolaUtils.import();
  }

  public void selectionChanged(IAction action, ISelection selection) {
  }
}

Action classes for declarative actions are very similar to a regular IAction, but they have additional methods such as init() and selectionChanged(). An IAction is not used directly because the Workbench is lazy. It places proxy IAction instances into menus and toolbars instead of instantiating declared actions. The proxy actions keep a reference to the action delegate for which they are serving as a proxy.

Notice that run(IAction) and selectionChange(IAction) take an action argumentthis is the proxy. Under the covers, the proxy is the action that is part of the menu. As such, changing how the action appears (e.g., its enablement or label) requires changing the proxy action. The proxy action uses the information from the declarative action's description to display, perform basic enablement, and then ultimately run the action.

Adding these actions to the Hyperbola menu and the toolbar follows the same pattern. The only difference is that the menubarPath attribute has a different value. For example, adding the following action to the action set places the Import and Export actions, as shown in Figure 17-2.

org.eclipsercp.hyperbola/plugin.xml
<action
    class="org.eclipsercp.hyperbola.actions.ExportContactsAction"
    icon="icons/export.gif"
    id="org.eclipsercp.hyperbola.action2"
    label="&amp;Export Contacts"
    menubarPath="hyperbola/additions"
    style="push"
    toolbarPath="additions"/>

Figure 17-2. Import and Export declarative actions in the menu and toolbar


Here, the hyperbola/additions menu path and addition toolbar path refer directly to the names of the menus and placeholders we added to the Hyperbola ActionBarAdvisor earlier.

Note

To enable key bindings in declarative actions, use the same steps as defined in Chapter 12, "Adding Key Bindings." Instead of registering the action in the ActionBarAdvisor, set the definitionId attribute in the action definition to that of the command id.


17.2.4. Context Menus

It is often convenient for users to act directly on a UI element using a context menu or by double-clicking, for example, initiating a chat in Hyperbola by double-clicking on a contact in the Contacts view. So far, Hyperbola does not have any context actions let alone any extensible structure for adding them. In this section, we add a context menu and then add some declarative actions to it.

The snippet below shows the ContactView method makeActions(). This method is called from the ContactView method createControlPart() and does the following:

  • adds a context menu

  • adds the chat action to the menu

  • adds the MB_ADDITIONS placeholder to the menu

  • registers the menu with the Workbench

  • initializes double-click behavior on the tree viewer

org.eclipsercp.hyperbola/ContactsView
private void makeActions() {
  chatAction = new ChatAction(getSite().getWorkbenchWindow());

  // Initiate a chat on double click.
  treeViewer.addDoubleClickListener(new IDoubleClickListener() {
    public void doubleClick(DoubleClickEvent event) {
      chatAction.run();
    }
  });

  // Create the context menu and register it with the Workbench.
  MenuManager menuMgr = new MenuManager("contactsPopup");
  manager.add(chatAction);
  manager.add(new Separator(IWorkbenchActionConstants.MB_ADDITIONS));
  Menu menu = menuMgr.createContextMenu(viewer.getControl());
  treeViewer.getControl().setMenu(menu);
  getSite().registerContextMenu(menuMgr, treeViewer);
}

The ChatAction used here is the same one that was added to Hyperbola's top-level menu in Chapter 7, "Adding a Chat Editor." The context menu is defined using the MenuManager in the same way as the top-level menus defined previously.

The registerContextMenu() method is used to hook the menu into the Workbench and its declarative action mechanisms. When a registered menu is shown, the Workbench adds any related declarative contributions. In the case of a context menu, contributions to the org.eclipse.ui.popupMenus extension point that apply to the current selection are added.

The last step is to define the declarative actions for Export and Import that place them in the context menu. The following snippet adds an org.eclipse.ui.popupMenus extension that applies to any instance of RosterEntry. Notice that here, the action is not placed in a particular menu. Rather, it is scoped such that when a RosterEntry (e.g., a contact) is selected, the action is available and is added to any open context menu.

org.eclipsercp.hyperbola/plugin.xml
<extension point="org.eclipse.ui.popupMenus">
  <objectContribution
      adaptable="false"
      id="org.eclipsercp.hyperbola.objectContribution1"
      objectClass="org.jivesoftware.smack.RosterEntry">
    <action
        class="org.eclipsercp.hyperbola.actions.ExportContactsAction"
        icon="icons/export.gif"
        id="org.eclipsercp.hyperbola.action1"
        label="&amp;Export Contacts"/>
  </objectContribution>
</extension>

Apart from the objectContribution element, the actions in this example are described in the same manner as action sets. The existing ExportContactsAction implementation is even reused after one small changethe action class must implement IObjectActionDelegate in addition to IWorkbenchWindowActionDelegate. The new interface is needed so the action can track its context. In the case of a context menu action, the context is the UI part that is showing the context menu.


Previous Page
Next Page