Sunday, June 18, 2006

Building a Common Navigator based viewer, Part V: Action Providers


Thought is the blossom; language the bud; action the fruit behind it.
-- Ralph Waldo Emerson

Overview

Action Providers provide a means to configure the retargetable actions and programmatically configure the context menu in a Common Navigator Framework viewer. In this article, we'll take a look at what an ActionProvider can do, and how to implement one for our Example View.

Action Providers are useful for cases where you have to perform some computation before you can decide what items to add to a menu or you need to adjust the retargetable actions to ensure that user keystrokes are handled properly (like Cut(Ctrl+x)/Copy(Ctrl+c)/Paste(Ctrl+p)/Delete(Delete)).

Creating our Action Provider implementation

In other posts, we started with the XML configuration for the extension. For this article, we'll start from the code.

First, we'll create our Action Provider class, which must extend org.eclipse.ui.navigator.CommonActionProvider.

The creation of the PropertyActionProvider class extending CommonActionProvider.

Our class must define a no-argument constructor so that instances may be created from our extension.

The PropertyActionProvider constructor

In our init() method, we verify that we are executing inside of a workbench part (opposed to a dialog), and then create the OpenAction, which takes the workbench page and a selection provider in its constructor. The ICommonActionExtensionSite provides access to several things that a CommonActionProvider might need. I'll refer you to the Javadoc API for more details here.

Common Navigator viewers are capable of being enclosed in dialogs. Thus, a reference that is a subclass of ICommonViewerSite is supplied by default from the ICommonActionExtensionSite. If your Action Provider is used by a viewer in a dialog, you cannot cast down to access the workbench page, since it is not available. In general, most Action Providers make assumptions that they are only used in workbench parts.

The PropertyActionProvider.init() method

The fillActionBars() method can be used to configure retargetable actions. A retargetable action is an instance of org.eclipse.ui.actions.RetargetAction with a unique identifier. The workbench registers many of these actions by default (see org.eclipse.ui.IWorkbenchActionConstants).

There is currently only one retargetable action defined by the Common Navigator Framework (CNF), org.eclipse.ui.navigator.ICommonActionConstants.OPEN. (But of course, you can register an action for any of the identifiers in IWorkbenchActionConstants). In the CNF, a RetargetAction with the ICommonActionConstants.OPEN identifier is executed each time an open event occurs on a viewer. If no action is registered when an open event occurs on a node in the viewer, the node's expanded state is toggled.

In our example, we are going to register our OpenAction if it is enabled (we'll take a look at how this is determined shortly).

Note: If this action provider is used in a dialog context, the openAction would be null because we only initialize it if we receive an ICommonViewerWorkbenchSite, so if you need your Action Provider to be robust enough for either workbench parts or dialogs, be that either all of your action fields are initialized or you check for null.

The PropertyActionProvider.fillActionBars() method

The fillContextMenu() method uses the ICommonMenuConstants to make sure that the openAction is positioned correctly in the menu.

The PropertyActionProvider.fillContextMenu() method

Our openAction is fairly simple. The contents of the run() method are unimportant for this example, but you can take a look at the example in the repository. The constructor and the isEnabled() method are shown here. The constructor remembers the references we pull from the ICommonViewerWorkbenchSite.


Adding our Action Provider to our viewer

Now let's take a look at our extension definition. Here we nest the actionProvider under our navigatorContent element in our org.eclipse.ui.navigator.navigatorContent extension. Remember that we constructed the rest of this extension in previous articles.

The actionProvider element specifies an identifier and our implementation class.

Since we have nested the actionProvider element under our navigatorContent extension,
the existing viewerContentBinding that binds our content extension to our viewer also absorbs our action provider.

Furthermore,
the menu options and retargetable action configuration will only be available if our extension is active. If the user turns off our extension, then they will not need these actions, the CNF will not invoke them.

The actionProvider element under the org.eclipse.ui.navigator.navigatorContent extension.
Alternatively, we could expose our actionProvider independently and use a viewerActionBinding to bind the Action Provider to our viewer. In this case, the action provider will be available, even if our content extension is not.

The actionProvider exposed independently and bound to a viewer.
The final example view should appear like the following. When the "Open Property" action is selected, the property file will be opened and the property name will be selected.

The final menu.

Summary

In this article, we have seen why an Action Provider does, walked through how to implement an Action Provider, and bound the Action Provider to our viewer.

In our next article, we'll take a look at sorting our properties elements.

Building a Common Navigator based viewer, Part IV: Object Contributions

Action may not always bring happiness, but there is no happiness without action.
-- Benjamin Disraeli



Overview

In the last post, we discussed how to configure the popup menus for a Common Navigator instance. As we saw, a Common Navigator can declare all of its menu insertion points through the org.eclipse.ui.navigator.viewer extension point and indicate whether contributions to the org.eclipse.ui.popupMenus extension point should be honored. The declarative menu configuration serves the dual purpose of avoiding programmatic configuration of the menu structure, and documenting the menu structure for potential extenders.

We also briefly talked about the two ways that contributors can tap into a menu. The first was through org.eclipse.ui.popupMenus, which allows you to add menus throughout the workbench, and the second was through org.eclipse.ui.navigator.navigatorContent, which is specific to the Common Navigator framework. We will cover ...popupMenus in this article, and ...navigatorContent in the next.

As always, you can find the full source for this example in the org.eclipse.ui.navigator.examples plugin from the Eclipse CVS repository. There are also some instructions on using Eclipse with CVS, which also documents the repository paths.

With that out of the way, let's dive into the example.

Adding a Delete Property action through org.eclipse.ui.popupMenus

Recall that for this series of articles, we are rendering a simple model of a properties file.

A screencap of our initial example viewer.

Now we are going to add a Delete Property action that allows a user to select a property from the listing above and remove it.



Open the Plug-in Manifest Editor by double clicking the plugin.xml file in your plugin project. If you're not quite sure what this means, check out Part I of this series.

On the Extensions tab, select Add... and choose org.eclipse.ui.popupMenus. If you don't see this option listed, uncheck "Show only extension points from the required plugins". If you haven't already added org.eclipse.ui as a dependent plugin, this will take care of that for you.

Now select the entry created in the list ("org.eclipse.ui.popupMenus"), right-click and choose "objectContribution". Then, right-click again and choose "action".

The context menu for the org.eclipse.ui.popupMenus element on the Extensions tab.

An objectContribution can declare menus (submenu extensions of the popup menu) and actions. The extension is fairly verbose to help optimize plugin loading. That is, the extension declares enough information that the actual class (and therefore the plugin) doesn't need to be loaded to render the menu. If the user never selects the action, then the plugin needn't be loaded.

In particular, the extension declares the menu label, the menu icon, and a tooltip among other things. The extension also declares the menubar path (recall we described the values for insertion points in Part III).

Use the values from the following diagram to configure the action element. You can enter the values on the Extensions page, or flip over to the plugin.xml tab and fill it in by hand.

The configuration for the objectContribution element of org.eclipse.ui.popupMenus

There's a few things we should discuss here.

First is the objectClass, which declares that we are interested in ...PropertiesTreeData. Recall from Part II that we defined a simple object model to represent the name=value pairs in a property file. To delete these items, we want our action to enable on instances of our model.

The class attribute specifies a subclass of org.eclipse.ui.actions.ActionDelegate. The implementation of DeletePropertyAction.run(IAction) has some boilerplate for robustness, but the important part is as follows:

The meat of the DeletePropertyAction.run(IAction) method.

ActionDelegate declares a selectionChanged(IAction, ISelection) method that is updated when the selection in the viewer changes. We override this method to remember the selection, so that in the event that the user selects the menu option, we know what item they clicked on.

Summary

And that's all there is to it.

Now you should feel comfortable experimenting with adding more actions to the popup menu, as well as adding submenus. Be sure to check out the menu element (accessible in the same way you found action earlier). If you use the Extensions tab, all of the available attributes will be accessible to you, and remember you can use the right-click menu to create sub elements.

In this article we covered the basics of contributing an action to our example viewer through the org.eclipse.ui.popupMenus extension point. In the next article, we'll cover how to programmatically configure the menu using an extension of org.eclipse.ui.navigator.navigatorContent.

Friday, June 16, 2006

Building a Common Navigator based viewer, Part III: Configuring Menus

Action is eloquence.
-- VOLUMNIA
Coriolanus, Act 3, Scene 2



In earlier posts, we walked through how to construct a Common Navigator Framework (CNF) viewer and a basic extension to render property files. Now we will walk through how to configure the menu for a CNF viewer and how to add actions to our viewer to manipulate our content.

Let's take a small step back though, and look at the current roadmap for upcoming posts:
  • Configuring CNF viewer popup menus (Part III). This post will describe how to configure the popup menu of a CNF viewer, how to describe the insertion points and separators for a popup menu, and how to toggle whether a CNF viewer honors the org.eclipse.ui.popupMenus extension point.

  • Adding Actions to a CNF viewer through Object Contributions (Part IV). Part IV will cover how to add actions through Object Contributions. Object Contributions allow you to add actions (and even define menu sub-structures) for specific object types. Object Contributions are not specific to CNF viewers, but they are useful for adding menu actions without alot of code.

  • Adding Actions to a CNF viewer through Action Providers (Part V). Part V will cover how to programmatically configure CNF menus and adjust the retagetable actions for the Workbench. In particularly, the example will cover how to add a custom Open action for individual property types.

Overview

There are two basic options for adding actions to a Common Navigator Framework (CNF) viewer:
  • Contribute actions using org.eclipse.ui.popupMenus as objectContributions or viewerContributions. The ...popupMenus extension point allows you to contribute individual action delegates throughout the Eclipse Workbench. CNF viewers can be configured to honor these contributions (which is the default) or to ignore them. In the case of the Project Explorer contributed by Platform/UI, object and viewer contributions are honored. (To be covered in detail in Part IV.)

  • Contribute actions using org.eclipse.ui.navigator.navigatorContent as actionProviders. Sometimes, clients require more programmatic control over exactly what actions are contributed to a given menu in a particular context, as well as what retargetable actions are configured based on the current selection. CNF Action Providers are only honored by CNF viewers. (To be covered in detail in Part V.)
Configuring Menu Structure

In Part I, we defined our example <viewer /> element, and popup menus were briefly mentioned.

Recall that there are two means to configure a popup menu. The first possibility is simply to specify a value for the popupMenuId of the <viewer /> element in org.eclipse.ui.navigator.viewer. The second possibility is to take a more active role in configuring the insertion points the menu supports.

Tip: It is an error condition if both the popupMenuId is specified as well as the popupMenu element.


If we were to set the popupMenuId, we might have something like the following:



The identifier can be used by extenders of org.eclipse.ui.popupMenus to add viewerContributions specific to the view; however, no identifier is required for objectContributions.

When only the popupMenuId attribute is specified, the default set of insertion points are automatically configured in the popup menu. These are documented in the schema reference for org.eclipse.ui.navigator.viewer, but are duplicated here for convenience:

"group.new" separator="true"
"group.goto"
"group.open" separator="true"
"group.openWith"
"group.edit" separator="true"
"group.show" separator="true"
"group.reorganize"
"group.port" separator="true"
"group.generate" separator="true"
"group.search" separator="true"
"group.build" separator="true"
"additions" separator="true"
"group.properties" separator="true"

These values can be used by the menubarPath attribute of elements in org.eclipse.ui.popupMenus.

These values can also be used when programmatically using or searching for menu items (see org.eclipse.jface.action.IContributionManager, particularly insertAfter(), insertBefore(), and appendToGroup().

All of the string constants ("group.*") are immortalized in org.eclipse.ui.navigator.ICommonMenuConstants for programmatic usage.

Alternatively, we could choose to define a <popupMenu /> element as a child of <viewer />, where we could then define our own mix of insertion points. Remember, that you can use the Extensions tab of the Plug-in Manifest Editor to drive the creation of extension elements from the menu.



For our example, we can use the same menu configuration as the Project Explorer:


Each <insertionPoint /> element indicates a GroupMarker that is accessible through the IContributionMenu API. Where ever the separator attribute is set to true, the menu will render a horizontal line. The menus in Eclipse are smart enough to only render the line if necessary, so two lines back to back only renders as one horizontal line.

Also, take note of the allowsPlatformContributions="true" attribute of <popupMenu /> element. By default, CNF viewers are enabled to honor ..popupMenus contributions. Setting this attribute to false will restrict the menu to only honoring Action Providers.

The example above doesn't do anything fancy, but in your own CNF viewer, you could define your own menu insertion point ("group.example") where ever it makes sense for your scenarios. The recommended naming convention is "group.*", as the above insertion points follow, with the exception of the special additions group.

Some comments on extending or manipulating the set of insertion points on a CNF menu. It is not possible to declaratively manipulate or extend the set of insertion points for an already configured viewer. It is possible to programmatically add new GroupMarkers or Separators through Action Providers, but this practice should be used with care. We will get into the details of how this might be done in Part V, but for now just heed the warning that this practice should be used with caution, and only when taking advantage of the dependsOn attribute of the <actionProvider /> element, where the value indicates the identifier of an Action Provider that adds extra insertion points to the menu. Insertion points should never be programmatically removed as other extensions may depend on them. If your own custom viewer has no use for a particular insertion point, then you can leave it out when you define your own <popupMenu /> element.

Summary

In this article, we have discussed how to configure a menu for a Common Navigator Framework (CNF) viewer. In the next few articles, we will discuss how we can add to this menu using org.eclipse.ui.popupMenus and org.eclipse.ui.navigator.navigatorContent/actionProvider.

Monday, June 05, 2006

What does the Common Navigator Framework (CNF) help me do?

A framework should help you do something, not make you do something. In the following article, I'll go over the scenarios that motivated the creation of a generic, viewer integration framework. I'll also discuss a little of the background of the framework and how it evolved to an open source solution for the Eclipse Platform.

The details of this article roughly follow the flow from the presentation given at EclipseCon 2006.

Use case 1: Expose various types of information in a viewer to provide a cohesive user experience with editors.

One of the greatest features of Eclipse is its inherent extensibility. However, in some cases, the way in which the extensibility of Eclipse is utilized can come at the cost of the user experience. One problem that many products have come across when extending Eclipse is viewer explosion, where many views for different tasks are created to satisfy the requirements of each individual component. Depending on the overall product architecture, the views may sometimes duplicate information (such as each view rendering the project and its resources, plus one other piece of value-added content for each project), or alternatively the views may render just their data, which means that if the user is exploiting function from multiple components within the product, s/he may have to keep multiple views open.

The pattern that many components teams follow tends to be a viewer that exposes a custom model (like a J2EE Deployment Descriptor, as in the Web Tools Platform) plus the resource or Java(tm) representation of the project.



It turns out that this pattern is really driven by integrators incorporating the editor model into their viewers. Often, the extra content in a viewer is really a representation of some model that an editor can operate on, which is exposed to the user to make it easier for them to manipulate or navigate. So the first requirement of fight club .. err the CNF is:

Requirement: Support a single viewer for all editor model integrators.


Use Case 2: Expose model elements that are not tied directly to the workspace.

Not all viewers follow the editor model pattern; some viewers follow the pattern of incorporating data from outside the workspace environment to make it easier for users to have a "one-stop" shop for all of their development needs. The database extensions in WTP are an example of this:



Now merging all of the content of these views in one place can overwhelm the user, so the CNF must allow users to choose what is relevant to them.

There are two requirements derived from this scenario:

Requirement: Allow non-resource driven model content.

Requirement: Allow users to choose what they want to see in their integrated viewer.




Use Case 3: Open Source Platform must allow integration from Commercial Products.

One of the biggest value propositions of Eclipse is for vendors to pool resources to produce the commodity layers; then vendors can differentiate themselves to provide value-added content based on their customers' needs. The Web Tools Platform is one of the first big examples where many different kinds of viewer content would be required for end-users, both from standard and proprietary models, and therefore vendors must not be restricted from extending the primary navigational viewer.

A similar problem exists on the level of commercial products, where value-added content from Independent Service Vendors (ISVs) must be able to integrate seamlessly to enhance the user experience. Often, the integration paths for ISVs are not as well documented as an open source platform, so providing a solution on the open source layer allows the ISVs to use the same consistent, public, documented API that the community has helped hone.

So the general use case here is to allow extensibility across layers of software; downstream layers must be able to integrate seamlessly to maintain a cohesive user experience.

Requirement: Allow Platforms (open source and commercial) to reuse a consistent viewer integration framework and API.




So in summary, there are four basic requirements of the CNF:

  1. Support a single viewer for all editor model integrators.
  2. Allow non-resource driven model content.
  3. Allow users to choose what they want to see in their integrated viewer.
  4. Allow Platforms (open source and commercial) to reuse a consistent viewer integration framework and API.




In addition to the requirements above, there were a few goals driven from experience with developing Eclipse viewers from release to release:
  1. Enhanced reusability. Clients should be able to re-use Platform content (Resource model, Java(tm) model, etc) with little or no code; and clients should be able to define their own viewers an "absorb" content easily.
  2. Mitigate client reaction from release to release. Eclipse continues to evolve at all levels as an effective Platform. Often enhancements are made at the lower levels (such as the enhanced working set support from JDT in 3.1). For downstream integrators to adopt this functionality, it often requires an expensive porting or coding effort, in addition to whatever value-added content is slated for that product's own release. The CNF should make it easier for clients to absorb these enhancements without coding efforts.
The CNF began as product solution in Rational(R) Application Developer (RAD) v6.0. It was originally derived from the General Purpose Navigator proposal for Eclipse 3.0. The GPN was a great start, but much of the original code has been re-written; however some of the concepts still remain.

When IBM Rational decided to contribute some of the lower integration layers of RAD v6.0 to the then newly formed Eclipse Web Tools Platform project, the CNF was included in the contribution. Product solutions often have very different focuses from Platform solutions, as the measure of value in a product solution is on the user experience, not on the ease of extendibility. Some initial API definition was done in the context of WTP, but there was uncertainty at the time about the proper home for the CNF. Like many of the frameworks in WTP, it is not specific to any particular specification, and solves a general problem required for products built on Eclipse. Therefore, it was expected that the proper home for the CNF was in the Platform. At the time, it wasn't clear that the Platform would be willing to accept the CNF or if another version or solution would present itself, so the effort invested to harden the CNF API in WTP was minimal.

In Eclipse 3.2, it was agreed that the Platform would adopt the CNF, with the proper API finalization that was required. I've had requests to provide some thoughts and feedback on what it takes to turn a product solution into a platform solution, and I hope to address that in a future post.

So in a nutshell, the CNF is designed to help you integrate document models into an navigator experience, integrate content that isn't specific to the workbench, allow you to absorb other content seamlessly (in particular resource and Java(tm) models), and mitigate your expense in time and effort to absorb incremental enhancements from release to release from layers beneath you. The CNF began as a product solution for a general problem in Rational Application Developer v6.0, and has been contributed to the open source Eclipse Platform in 3.2 to allow the community to better integrate their navigational viewers and provide a more cohesive user experience across software layers and products.