Eclipse CNF: Navigator Content Extensions
Abstract
After
providing the basic example of how the Eclipse Common Navigator Framework (CNF) can be used to display custom content, this article focuses on the main feature of the CNF – the contribution of content to the same navigator by several independent plug-ins. First of all, I will explain some minor changes introduced to the CNF in the Gallileo Edition, then I will focus on the content itself and finally provide an overview of how the action contributions can be provided. In the end of the post, some ideas on control of dynamic content are explained.
Gallileo Changes
There are two noticeable changes in the CNF that have been added with
Eclipse Galileo. The return type of the method CommonNavigator#getInitialInput() has been changed to Object (which is important for the RCP usage of the CNF) and the call of the method org.eclipse.ui.internal.ide.model.WorkbenchAdapterBuilder#registerAdapters() has been replaced by the org.eclipse.ui.ide.IDE.registerAdapters(); which should be executed in the initialize method of the ApplicationWorkbenchAdvisor of your RCP if you are using resources with the CNF.
public class CommonNavigator ...
{
/**
* Used to provide the initial input for the {@link CommonViewer}.
* By default getSite().getPage().getInput() is used.
* Subclass this to return your desired input.
* @return The initial input for the viewer.
* Defaults to getSite().getPage().getInput()
* @since 3.4
*/
protected Object getInitialInput() {...}
}
Content contribution
The main advantage of the use of the CNF over a simple tree view is the ability to contribute content to the view from several plug-ins. Thus, the plug-in that contains the view does not depend on the contributing plug-ins. If you are not familiar with the CNF, please review the previous post, since the example provided there will be extended. The data model used previously will be extended by an additional layer: along with parents and children, the pets are introduced. Let’s assume that pets are owned by children.
/**
* Represents a pet
* @author Simon Zambrovski
*/
public class Pet
{
private String name;
private Child owner;
public Pet(String name, Child owner)
{
super();
this.name = name;
this.owner = owner;
}
...
// getter and setter
}
First of all create a new plug-in project called de.techjava.rcp.cnf.subcontent, add a plug-in dependency to the de.techjava.rcp.cnf and to the org.eclipse.ui.navigator. Then add two extensions org.eclipse.ui.navigator.navigatorContent and org.eclipse.ui.navigator.viewer. The first extension defines the new Navigator Content Extension (NCE) and the second one is used to bind it to the existing viewer.
<extension
point="org.eclipse.ui.navigator.navigatorContent">
<navigatorContent
activeByDefault="true"
contentProvider="de.techjava.rcp.cnf.subcontent.provider.CNFSubContentProvider"
id="de.techjava.rcp.cnf.subcontent.navigatorContent"
labelProvider="de.techjava.rcp.cnf.subcontent.provider.CNFSubLabelProvider"
name="Sub Content"
priority="normal"
providesSaveables="false">
<enablement>
<instanceof value="de.techjava.rcp.cnf.data.Child" />
</enablement>
</navigatorContent>
</extension>
<extension
point="org.eclipse.ui.handlers">
<handler
class="de.techjava.rcp.cnf.subcontent.handler.RenameHandler"
commandId="org.eclipse.ui.edit.rename">
<activeWhen>
<reference definitionId="de.techjava.rcp.cnf.subcontent.petSelected" />
</activeWhen>
<enabledWhen>
<reference definitionId="de.techjava.rcp.cnf.subcontent.petSelected"/>
</enabledWhen>
</handler>
</extension>
The Sub Content is defined following the same scheme as the content in the previous article of this series. Its contentProvider and labelProvider attributes point to the corresponding classes. The label provider implementation is straight forward. The content provider has to be able to return a list of children, if the instance of the Child-class is selected in the Navigator. The enablement is defined in this way respectively. An important thing to understand at this point is that the CNF will join contributions of all NCEs triggered on the element selection. This means, that even if the NCE of the first plug-in does not provide any children for the Child element, there will be children contributed by the second plug-in’s content provider. Here is the example content provider, which returns the number of pets depending on the last digit in a child’s name:
public class CNFSubContentProvider implements ITreeContentProvider
{
...
public Object[] getChildren(Object parentElement)
{
if (parentElement instanceof Child
&& !(parentElement instanceof Parent))
{
Child child = ((Child) parentElement);
char lastDigit = child.getName().charAt(child.getName().length() - 1);
if (Character.isDigit(lastDigit))
{
int petCount = Integer.parseInt(String.valueOf(lastDigit));
Pet[] pets = new Pet[petCount];
for (int i = 0; i > petCount; i++)
{
pets[i] = new Pet(child.getName() + "'s pet " + (i + 1), child);
}
return pets;
}
return EMPTY_ARRAY;
} else
{
return EMPTY_ARRAY;
}
}
...
}
After this trivial configuration and the inclusion of the new plug-in into the example product, the resulting Navigator should show pets as sub-elements of the child-elements.

Shared commands
If the Navigator displays content from several plug-ins, the contributed commands should be handled carefully. The commands (contributed to the pop-up menu shown upon right-click on the corresponding item) should appear only on items they belong to. In order to achieve this, let us first look at the standard way of the command contribution. The CNF developer is strongly recommend to use the declarative contribution techniques instead of the Action Contribution Provider. For this purpose, the extension point org.eclipse.ui.navigator.viewer allows to contribute a popupMenu element as a child element of the viewer. The pop-up menu element defines the structure (in terms of separators) of the pop-up menu. There is a standard pop-up menu structure defined, which is default if a user-specific definition is ommited. In the example, the user-specific pop-up menu is defined:
<extension
point="org.eclipse.ui.navigator.viewer">
<viewer
viewerId="de.techjava.rcp.cnf.view">
<popupMenu
allowsPlatformContributions="true"
id="de.techjava.rcp.cnf.view.popup">
<insertionPoint
name="group.new"
separator="false">
</insertionPoint>
<insertionPoint
name="group.open"
separator="false">
</insertionPoint>
<insertionPoint
name="group.edit"
separator="true">
</insertionPoint>
...
</popupMenu>
</viewer>
...
</extension>
Using the
Eclipse Command Framework, the commands can be contributed to the defined pop-up menu in the following way. Using the org.eclipse.ui.menus extension point one or several menuContribution elements can be defined. Each menu contribution defines a set of commands, controls, menus, tool bars or dynamic contributions (or any combination) which are added to a specific separator addressed in a special way (see Command Framework Documentation). In this example the set of commands is contributed:
<extension
point="org.eclipse.ui.menus">
<menuContribution
locationURI="popup:de.techjava.rcp.cnf.view.popup?after=group.edit">
<command
commandId="org.eclipse.ui.edit.delete"
id="cnf.popupmenu.delete"
label="Delete"
mnemonic="D"
style="push">
</command>
<command
commandId="org.eclipse.ui.edit.rename"
id="cnf.popupmenu.rename"
label="Rename"
mnemonic="R"
style="push">
</command>
</menuContribution>
....
</extension>
Please note that the commandId must point to an existing command, which is either user-defined, or defined by the Eclipse platform. A command is an abstraction of a user command which is referenced from the User Interface on one hand and has assigned handlers (which execute the command) on the other hand. The representation of the command is shown in the UI only if a handler is assigned to it. The idea of using shared commands is to have multiple handlers for the same command, which are activated depending on the selected element.
In order to enable and disable handlers in a declarative way, the
Eclipse Core Expressions are used. Core expressions can be written in the enabledWhen and activeWhen child elements of the handler definition. The core expressions can also be defined and referenced externally using the org.eclipse.core.expressions.definitions extension point. Here is an example definition of the handler assigned to the Rename command:
<extension
point="org.eclipse.ui.handlers">
<handler
class="de.techjava.rcp.cnf.handler.RenameHandler"
commandId="org.eclipse.ui.edit.rename">
<activeWhen>
<reference definitionId="de.techjava.rcp.cnf.elementSelected" />
</activeWhen>
<enabledWhen>
<reference definitionId="de.techjava.rcp.cnf.elementSelected" />
</enabledWhen>
</handler>
</extension>
The handler is enabled and active if the expression de.techjava.rcp.cnf.elementSelected is triggered. The definition of the expression is as follows. It defines the with-scope on the current selection, iterates over the elements and applies the instance of operator on the elements using the OR-conjunction. In doing so, the expression will trigger if among the currently selected elements is at least one Child element.
<extension
point="org.eclipse.core.expressions.definitions">
<definition id="de.techjava.rcp.cnf.elementSelected">
<with variable="selection">
<iterate ifEmpty="false" operator="or">
<instanceof value="de.techjava.rcp.cnf.data.Child" />
</iterate>
</with>
</definition>
</extension>

In order to use the same action (the Rename item of the pop-up menu) in the Pet plug-in, the handler enabled on the Pet element has to be provided. The code is analogous to the code before:
<extension
point="org.eclipse.ui.handlers">
<handler
class="de.techjava.rcp.cnf.subcontent.handler.RenameHandler"
commandId="org.eclipse.ui.edit.rename">
<activeWhen>
<reference
definitionId="de.techjava.rcp.cnf.subcontent.petSelected">
</reference>
</activeWhen>
<enabledWhen>
<reference definitionId="de.techjava.rcp.cnf.subcontent.petSelected" />
</enabledWhen>
</handler>
</extension>
<extension
point="org.eclipse.core.expressions.definitions">
<definition
id="de.techjava.rcp.cnf.subcontent.petSelected">
<with variable="selection">
<iterate ifEmpty="false" operator="or">
<instanceof value="de.techjava.rcp.cnf.subcontent.data.Pet" />
</iterate>
</with>
</definition>
</extension>
This should enable the Rename action on the Pet elements, too.

Dynamic content change
Finally, the last topic, which is covered in this article is the control of which content is shown in the Common Navigator. The developer can define the initial behaviour by setting the activeByDefault attribute of the NavigatorContent element. The user can customize the visible NCEs in the special dialog:

Of course there is a way to change the content shown in the Common Navigator at run-time. The following section describes how this can be done.
Responsible for loading the NCEs is the NavigatorContentService, which can be retrieved from the running Navigator instance. Using it is straight forward.
/**
* Sets the status of a NCE of the current viewer if any
*/
public static void setToolboxNCEActive(final String extensionId, final boolean active)
{
CommonNavigator instance = findCommonNavigator(CNFNavigator.VIEW_ID);
if (instance != null)
{
INavigatorContentService contentService = instance.getNavigatorContentService();
boolean isActive = contentService.getActivationService().isNavigatorExtensionActive(extensionId);
if (active && !isActive)
{
contentService.getActivationService().activateExtensions(new String[] { extensionId }, false);
} else if (!active && isActive)
{
contentService.getActivationService().deactivateExtensions(new String[] { extensionId }, false);
} else
{
// do nothing, just quit
return;
}
contentService.getActivationService().persistExtensionActivations();
contentService.update();
}
}
The method findCommonNavigator(String viewId) is responsible for delivering the instance of the CNF. For example it could query the view for visible viewers and compare them with the requested Id:
public static CommonNavigator findCommonNavigator(String navigatorViewId)
{
IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
if (page != null)
{
IViewPart view = page.findView(navigatorViewId);
if (view != null && view instanceof CommonNavigator)
return ((CommonNavigator) view);
}
return null;
}
After the NCE status is changed the viewer has to be updated. Please note, that retrieving the instance of the INavigatorContentService via the Factory (NavigatorContentServiceFactory) will not work for this scenario (see
Bug 284650).
Making a step back and thinking about the usage of the method above, I realized that it can be reduced to the usage of core expressions instead of the simple boolean activeByDefault attribute. Maybe this enhancement could be contributed to the CNF at some point…
The source code to this post is available for download. It includes two plug-in projects (the CNF and the child content) which are packaged into a small RCP application. Just launch the product included in the CNF plug-in.
References
- http://wiki.eclipse.org/index.php/Common_Navigator_Framework
- http://wiki.eclipse.org/Command_Core_Expressions
- http://wiki.eclipse.org/index.php/Platform_Command_Framework
The compass image used in the post is taken from the FlickR gallery of Jean Franco Castro.
8 Responses to “Eclipse CNF: Navigator Content Extensions”
By Stephan on Sep 15, 2009 | Reply
Thank you for the tutorial. This helped me a lot. Is there a way to add a type of label decorators based on the CNF.
The label decorators in 2.1 seem ot be activated by enablements and not core expressions.
By Simon Zambrovski on Sep 16, 2009 | Reply
I’m not sure, I got your question right. Could you explain it in more detail?
By Stephan on Sep 17, 2009 | Reply
Yes the question was not so clear.
I’d like to add styled label additions to elements from other contentproviders. Just like the revision number after a resource in the Eclipse resource view. Can I do that with the CNF?
By Miles Parker on Sep 24, 2009 | Reply
Really helpful article. I’m building a set of views that will allow user’s to view related information from views. For example, when a user selects a container in project explorer, I’d like to show all of the models that are contained in a certain resource type. But I’ve been having a hard time figuring out how to actually have the CNF view respond to the selection. I could do this of course using a custom view part and the selection service, but I’ve got to think there is a more correct way to do this with CNF. I tried manually wiring up the content provider as a selection listener, but that doesn’t seem quite right. Any hints / ideas?
By Simon Zambrovski on Sep 24, 2009 | Reply
I’m not sure I understand the question correctly. You want another view to display the selection made in CNF? CNF itself acts a selection provider, so the selection service approach is not very wrong.
If you want to show information selected in the project explorer, why do you need another view? Why don’t you just extend the project explorer (is a CNF too) and show your models directly there?
By Miles Parker on Sep 24, 2009 | Reply
Hi Simon,
What I want is essentially a cascading set of CNF’s. The selection in one determines the selection in another. These should be dedicated to a particular type. So — this is a dumb example — let’s say you want to select a folder(s) or project(s) in project explorer. Then you might have a CNF displaying all of the Classes in that selected container in a flat display.
So I know that CNF is a selection provider but I want to wire one up as a selection listener as well. The results of a new selection need to then drive the input to the new view. As I say, I’m sure I can do this just using a View Part, but I think that folks are making a good argument for using CNF whenever possible.
By Miles Parker on Sep 24, 2009 | Reply
Sorry, should have said “The selection in one determines the *contents* of the other”. Note that that selection and content are not necessarily the same — this is why the content provider approach sounds nice. If in the prior example I could just get the selection of IContainer(s) as the root objects, then I could build the content tree up form that.