Home > Eclipse development, Software Development > Writing an Eclipse Plug-in (Part 7): Creating a Custom Navigator

Writing an Eclipse Plug-in (Part 7): Creating a Custom Navigator


[October 23, 2009 - I updated some of the code to reflect suggestions made by Simon Zambrovski: the CustomProjectWorkbenchRoot no longer inherits from PlatformObject and, by extension, CustomNavigator.getInitialInput() now returns Object. The downloadable code also reflects the change.]

(This is going to be a long one.)

I know I keep harping on this, but I’m going to do it again: so far we have created:

  • a plug-in project that created a new project wizard to get the name and location of a new project
  • a plug-in project that creates a navigator to display the project information
  • a testable class that created the actual project and tests to go with it

Even though we have two plug-in projects we have only 7 classes, one of which just loads strings, 2 which are Activators and the others are mostly empty.

The custom navigator will change that…slightly…well, more than slightly.

Create a Category for the Custom Navigator

First task: before creating a true custom navigator let’s create a category for it. Why? Because the last time I did it second and this time I want to do it first. In fact, I expect to be doing such forward thinking a lot in the future. Especially when I get to think about things for about a week and sketch out all the things I want to do first. Like creating a custom navigator and then give it a category. Only configure the category first.

Okay, let’s go to the customnavigator plugin.xml file Extensions tab.

Create the category:

  1. Right click on org.eclipse.ui.views –> New –> Category
  2. Enter the following:
    • id: customnavigator.category
    • name: Custom Projects
  3. Save plugin.xml.

Notice the lack of code.

To bind the category to the custom navigator:

  1. Select Custom Plug-in Navigator (view).
  2. In the Category field to the right enter:
    • Category: customnavigator.category
  3. Save plugin.xml.

Of course, I can’t let this one go by without an icon so…create another 16×16 image and store it in the customnavigator icons folder (what? You don’t have an icons folder in the Plug-in project? Create one. After that create an image and store it in the icons folder. All I did was copy the perspective.png from the customproject plug-in and rename it navigator.png).

To add the icon go to the Extensions tab:

  1. Select Custom Plug-in Navigator (view).
  2. In the icon field to the right enter:
    • icon: icons/navigator.png
  3. Save plugin.xml.

Start the runtime workbench for the customnavigator plug-in. From the runtime workbench, starting from the main menu, open Window –> Show View –> Other –> Custom Projects. Look! The Custom Navigator! And it has a pretty icon!

Okay, exit the runtime workbench.

Did I mention that we didn’t have to write any code?

Refactor plugin.xml and MANIFEST.ML

Time for some plug-in project hygiene. The plugin.xml file has some strings that need to be externalized and the MANIFEST.ML file has both a string that need to be externalized and packages that should be declared as exported. Naughty, naughty (I know: we need to do the same for customplugin. Next time).

Luckily, this is easily solved.

if you scroll down In the Overview tab, you will find a link labeled Externalize Strings Wizard. Click on the link, accept everything, and click Finish at your earliest possible opportunity.

The Externalize Strings Wizard will create a file named OSGI-INF/I10n/bundle.properties. Seems like a long name for such a few strings, but it will serve us well as we add/subtract strings over time. If you prefer you could instead create a file named plugin.properties that contains the same properties. If you do that please make sure to add the plugin.properties file to the build.properties bin.includes property or else the plug-in will not know where to find the externalized strings.

To see the effect of the externalization close plugin.xml and reopen it. Viola! The perfect soufflé!

One last thing: if you go to the MANIFEST.ML tab you will notice that there is a warning icon in the top left hand corner at the top of the file. If you click on the icon it will open a window with a single suggestion for fixing the warning: Add Missing Packages. Double click Add Missing Packages. The manifest editor will add a line at the end of the file:

...
Export-Package: customnavigator

Save the MANIFEST.ML file to see the warning go away.

Alright, enough fooling around. Let’s get down to business.

Create a Custom Navigator

Creating a navigator using the Common Navigator Framework (CNF) is not hard (once you know what the hell you are doing), but it is not trivial either (someone once told me that using the word but in sentence basically negates everything said in the phrase before it. Perhaps using but is shorthand for damning with faint praise).

Here are the 6 7 8 6 5 steps to success (with lots of spackle in between so I’m not wrong by too often):

  1. Subclass CommonNavigator
  2. Create a root PlatformObject
  3. Add Extension navigatorContent and bind it to the navigator
  4. Implement the Custom Folder and File Types, ContentProvider and LabelProvider
  5. Add a ResourceListener

However, all of the above has to be understood in context of:

  • how the navigator behaves when it is first instantiated
  • how the navigator behaves after instantiation and the workspace changes

So the overlay to the 6 7 8 6 5 steps above is:

  • Implement the Common Navigator and its behavior upon instantiation
    1. Subclass CommonNavigator
    2. Create a root PlatformObject
    3. Add Extension navigatorContent and bind it to the navigator
    4. Implement the Custom Folder and File Types, ContentProvider and LabelProvider
  • Register a listener that will be called when the workspace changes and the view needs to be updated
    1. Add a ResourceListener

Subclass CommonNavigator

In Eclipse you have many ways to create a new class. For the purposes of this example, let’s define the CustomNavigator within plugin.xml and use its infrastructure to keep everything in sync.

  • Open plugin.xml –> Extensions –> Custom Plug-in Navigator (view)
  • Change the Class entry from the default CommonNavigator class to

Class: customnavigator.navigator.CustomNavigator
Just type it in. The class doesn’t exist yet.

  • Click on Class link to open the New Java Class Wizard.
  • Click Browse for Superclass and choose type CommonNavigator. Click OK.
  • Click Finish to close the New Java Class Wizard.

When the editor opens delete the CustomNavigator constructor and its comment.

Override getInitialInput() with the following code and return an object of a type we haven’t defined yet: CustomProjectWorkbenchRoot.

public class CustomNavigator extends CommonNavigator {
    @Override
    protected Object getInitialInput() {
        return new CustomProjectWorkbenchRoot();
    }
}

Damn, that was a lot of code.

Create a Root PlatformObject

Based on advice from Simon Zambrovski the CustomProjectWorkbenchRoot should not inherit from PlatformObject any more. Who am I to argue (if you like you can read up on at Simon’s site)?

Create the class CustomProjectWorkbenchRoot any way you like. I typically move the cursor to the offending line, press Ctrl+1, and select Create Class. I also created it in the same package as the CustomNavigator.

Leave CustomProjectWorkbenchRoot empty.

Run the runtime workbench if you like; open the Custom Navigator, create a Java project, create a file to go with it and the project and class file should appear in the Custom Navigator. Makes you kinda proud doesn’t it?

Of course, if all we wanted to do was create a clone of the Resource Navigator it would be easier to just go out for a walk until the feeling passed (yawn). The real goal is to have the Custom Navigator only display projects of type CustomProject (and a few other things, but one thing at a time).

Add Extension navigatorContent

Time to add a new Extension. This extension will define what content will appear in the navigator. Its name is, you guessed it, navigatorContent.

In plugin.xml go to Extensions and click Add. When the Extensions dialog opens type nav and select org.eclipse.ui.navigator.navigatorContent. Click Finish.

Right click on org.eclipse.ui.navigator.navigatorContent and select New –> navigatorContent. Enter the following:

  • id: customnavigator.navigatorContent
  • name: Custom Navigator Content
  • contentProvider: customnavigator.navigator.ContentProvider
  • labelProvider: customnavigator.navigator.LabelProvider

If the goal is to only display projects of type CustomProject then we need content/label providers that only recognize those resource types. Time to write some more code.

Click on the contentProvider link to open the New Java Class wizard. We like what we see. Click Finish.

Do the same for labelProvider.

Display Only Custom Nature Projects

This is where we perform a lobotomy, or more accurately an extension-ectomy, to the configuration of the Custom Navigator to get it to display projects of type Custom Project.

Let’s clean up the non-essential extensions:

  • Open plugin.xml –> Extensions
  • Open org.eclipse.ui.navigator.viewer –> customnavigator.navigator (viewerContentBinding) –> (includes)
  • Delete all of the nodes under (includes) (but NOT (includes))
  • Save plugin.xml

Now add the extension that binds the navigatorContent to the navigator:

  • Go back to org.eclipse.ui.navigator.viewer –> customnavigator.navigator (viewerContentBinding) –> (includes)
  • Right click on (includes) and select New –> contentExtension
  • Enter the id of the Custom Navigator Content extension in the pattern field:
    • pattern: customnavigator.navigatorContent

To see a Custom Project in the Custom Navigator we need to add the triggerPoints that tell CNF which resources we care about (there is more to it than that, but that explanation will have to do):

  • Open org.eclipse.ui.navigator.navigatorContent –> Custom Navigator Content (navigatorContent)
  • Select Custom Navigator Content (navigatorContent) –> New –> triggerPoints
  • Select triggerPoints –> New –> or
  • Select or –> New –> instanceOf
  • In the value field click Browse and select customnavigator.navigator.CustomProjectWorkbenchRoot.

We’ve actually done quite a bit. If we were to add print statements in the ContentProvider and LabelProvider classes, start the runtime workbench, and create a Custom Project you would see output in the Console view. Very cool, but still not quite what we are looking for especially since the Custom Navigator does not know how to update itself when new resources are created.

Implement the Custom Folder and File Types, ContentProvider and LabelProvider

(Yeah, long title because a whole bunch of things have to be done first before we can turn on the switch and see the navigator returning custom content.)

Now that our ContentProvider and LabelProvider are being called what should they return? If we only want to display CustomProjects then the ContentProvider needs to return only CustomProject information and the LabelProvider has to know how to talk to our CustomProject objects to return the proper label information.

Let’s look at the ContentProvider first. We don’t want it talking to an object that is not a Custom Project (I hate double negatives) so we have to put an isInstance() check for objects of type CustomProjectWorkbenchRoot (we’ll add CustomProjectParent later). Why CustomProjectWorkbenchRoot? The call to getElements()/getChildren() will return the CustomProjectParent objects that will be queried for their text and displayable image.

Once the ContentProvider is sure that an object of the proper type is available it can do the following:
– if this is the first time in retrieve all the projects from the workbench (hence the call to initializeParent() if _customProjectParents is null)
– wrap each Custom Project in a CustomProjectParent object

From ContentProvider.java

public class ContentProvider implements ITreeContentProvider {

    private static final Object[] NO_CHILDREN = {};
    private CustomProjectParent[] _customProjectParents;

    @Override
    public Object[] getChildren(Object parentElement) {
        Object[] children = null;
        if (CustomProjectWorkbenchRoot.class.isInstance(parentElement)) {
            if (_customProjectParents == null) {
                _customProjectParents = initializeParent(parentElement);
            }

            children = _customProjectParents;
        } else {
            children = NO_CHILDREN;
        }

        return children;
    }

    ...

    private CustomProjectParent[] initializeParent(Object parentElement) {
        IProject [] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();
        CustomProjectParent[] result = new CustomProjectParent[projects.length];
        for (int i = 0; i < projects.length; i++) {
            result[i] = new CustomProjectParent(projects[i]);
        }

        return result;
    }
}

In initializeParent() we ask the workspace for its current contents and we gratuitously wrap all the IProjects in a CustomProjectParent.

We have the veritable chicken and egg problem: without knowing what the parent should do how do we implement it? Using the YAGNI rule we create it and leave it empty…until the next complaint: the constructor to take in the IProject is missing. So, add that and store the IProject reference. We’ll need it later.

public class CustomProjectParent {

    private IProject _project;

    public CustomProjectParent(IProject iProject) {
        _project = iProject;
    }

    public String getProjectName() {
        return _project.getName();
    }
}

The LabelProvider needs to:
– make sure the incoming object is a CustomProjectParent and if it is:
– get the name of the project and return it
– get the image associated with CustomProjects and return it

Both of these tasks are going to be delegated to the CustomProjectParent otherwise the LabelProvider is going to be a tangle of wires as we add more and more types to the navigator tree.

LabelProvider.java

public class LabelProvider implements ILabelProvider {
    ...

    @Override
    public String getText(Object element) {
        String text = "";
        if (CustomProjectParent.class.isInstance(element)) {
            text = ((CustomProjectParent)element).getProjectName();
        }

        return text;
    }

    ...
}

The above code is going to change, but it is always instructional to see the evolution of the code.

Run a test:
– Start the runtime workbench
– Close the Welcome window
– Create a Java project
– Create a Custom Project
– When the Open Associated Perspective dialog opens click No
– Open Window –> Show View –> Other –> Custom Projects –> Custom Plug-in Navigator
Both projects will appear in the Custom Navigator as labels but no images.

Next task: exclude all the projects that are not members of our club. We do that in he ContentProvider.

ContentProvider.java

    private CustomProjectParent[] initializeParent(Object parentElement) {
        IProject [] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();

        List<CustomProjectParent> list = new Vector<CustomProjectParent>();
        for (int i = 0; i < projects.length; i++) {
            try {
                if (projects[i].getNature(ProjectNature.NATURE_ID) != null) {
                    list.add(new CustomProjectParent(projects[i]));
                }
            } catch (CoreException e) {
                // Go to the next IProject
            }
        }

        CustomProjectParent[] result = new CustomProjectParent[list.size()];
        list.toArray(result);

        return result;
    }

Oh, oh! We are out of sync with ourselves. The navigator plug-in is referencing customplugin.natures.ProjectNature which is not on its classpath or dependency list. In the customnavigator plug-in:

  • plugin.xml –> Dependencies –> Add
  • Select A Plug-in: customplugin
  • Click OK
  • Save plugin.xml

The LabelProvider should compile cleanly.

Run the (decidely) manual test again and now the Custom Project will be the only project displayed in the Custom Navigator.

[This needs a packaged test. In my haste to present how to configure the navigator I skipped a standard-practice step. Rather than interrupt the flow, which I am doing right now, I will present the steps to run the first series of tests on the navigator in the next post.]

The LabelProvider needs one more thing: an image to display along with the label. Copy the project-folder.png from customplugin or whatever image you decided to use in customplugin. We will take advantage of Eclipse’s property handling capabilities to centralize this functionality (besides, other plug-ins do the same thing. Who am I to rock the boat?).

Add this to CustomProjectParent:

CustomProjectParent.java

    private Image _image;

    ...

    public Image getImage() {
        if (_image == null) {
            _image = Activator.getImage("icons/project-folder.png"); //$NON-NLS-1$
        }

        return _image;
    }

[Hey! I just noticed that the folder to hold the icons is called icon instead of icons. Select it in the Package Explorer, press F2 and rename icon to icons.]

No, Activator.getImage() doesn’t exist. Press Ctrl+1 to auto-create it in the Activator class. Put in the code below.

Activator.java

    public static Image getImage(String imagePath) {
        ImageDescriptor imageDescriptor = AbstractUIPlugin.imageDescriptorFromPlugin(Activator.PLUGIN_ID, imagePath);
        Image image = imageDescriptor.createImage();

        return image;
    }

Yes, AbstractUIPlugin will take care of loading the image for us. Sweet.

Back to LabelProvider:

LabelProvider.java

    public Image getImage(Object element) {
        System.out.println("LabelProvider.getImage: " + element.getClass().getName());
        Image image = null;

        if (CustomProjectParent.class.isInstance(element)) {
            image = ((CustomProjectParent)element).getImage();
        }
        // else ignore the element

        return image;
    }

Run the manual test again. The screen capture below is a little different than you are probably seeing from the comfort of your own home, but shows two projects in the Project Explorer vs one in the Custom Plug-in Navigator.

custom-navigator-with-custom-project

Display a List of Categories per Project

A CustomProject displayed in the CustomNavigator will have two categories one of which has 3 sub-categories:

  • Schema
    • tables
    • views
    • filters
  • Stored Procedures

Tables, views and filters will list individual entries and Stored Procedures will list individual stored procs.

As usual the categories are bogus, but give us something to work with rather than parent1 and parent2.

This next list is going be somewhat daunting. These items need to be implemented/modified for the Custom Navigator to be of any value:

  • ContentProvider.getChildren() – return the children of CustomProjectParents
  • ContentProvider.getParent() – return the parent of CustomProjectParents and their children
  • ContentProvider.hasChildren() – return true if the incoming element has children. CustomProjectWorkbenchRoot will return true if any CustomProjects exist, CustomProjectParents will always return true since they will have Schema and Stored Proc children, Schema will return true as it has 3 children, Stored Procedures will return true if it has any entries.
  • LabelProvider.getImage() – check the object type to retrieve the proper image
  • LabelProvider.getText() – check the object type to retrieve the proper text
  • CustomProjectParent.getChildren() – will always return children (Schema and Stored Procedures)
  • CustomProjectSchema – implement getText(), getImage(), getParent(), getChildren() and a constructor that instantiates the Tables, Views and Filters children using the contents of an IProject
  • CustomProjectStoredProcedures – implement getText(), getImage(), getParent(), getChildren() and a constructor that will create children to represent file entries
  • CustomProjectSchemaTables – Display tables from an XML file
  • CustomProjectSchemaViews – Display views from an XML file
  • CustomProjectSchemaFilters – Display filters from an XML file
  • ICustomProjectElement – an interface used by all of the parents and children (except CustomProjectWorkbenchRoot) to make sure they all define getText() and get Image(). This should make the logic in LabelProvider.get[Text|Image]() a little simpler

(When I said this post was going to be long I wasn’t kidding.)

Due to the length of this post you can download the code up to this point instead of cutting-and-pasting until your fingers start to bleed.

In any case, let’s see what it will take to do a sampling of the above.

ContentProvider.getChildren()

Modifying ContentProvider.getChildren() to support CustomProjectParent means changing:

  • ContentProvider.getChildren()
  • CustomProjectParent.getChildren()
  • LabelProvider.getText()
  • LabelProvider.getImage()

and creating:

  • CustomProjectSchema.getChildren()
  • CustomProjectSchemaTables.getChildren()
  • CustomProjectSchemaViews.getChildren()
  • CustomProjectSchemaFilters.getChildren()
  • CustomProjectStoredProcedures.getChildren()

The above entailed passing in the parent reference through the constructor, creating children if appropriate and using the parent reference to find the reference to the IProject. Having the IProject reference means we can work out the leaf nodes of the categories created above.

I created a common interface, ICustomProjectElement, to the various parent and children to make their use in ContentProvider and LabelProvider simpler.

The interface did not come to me full blown. I grew it as I wrote the code for the children and it became obvious which methods were needed.

public interface ICustomProjectElement {

    public Image getImage();

    public Object[] getChildren();

    public String getText();

    public boolean hasChildren();

    public IProject getProject();

    public Object getParent();
}

In ContentProvider I changed getChildren() to reflect the use of ICustomProjectElement:

    public Object[] getChildren(Object parentElement) {
        Object[] children = null;
        if (CustomProjectWorkbenchRoot.class.isInstance(parentElement)) {
            if (_customProjectParents == null) {
                _customProjectParents = initializeParent(parentElement);
            }

            children = _customProjectParents;
        } else if (ICustomProjectElement.class.isInstance(parentElement)) {
            children = ((ICustomProjectElement) parentElement).getChildren();
        } else {
            children = NO_CHILDREN;
        }

        return children;
    }

LabelProvider gained from the use of the interface as well:

    public String getText(Object element) {
        String text = ""; //$NON-NLS-1$
        if (ICustomProjectElement.class.isInstance(element)) {
            text = ((ICustomProjectElement)element).getText();
        }
        // else ignore the element

        return text;
    }

Since the CustomProjectParent category will always have 2 children the code is also reasonable straightforward:

public class CustomProjectParent implements ICustomProjectElement {

    ...

    public ICustomProjectElement[] getChildren() {
        if (_children == null) {
            _children = initializeChildren(_project);
        }
        // else we have already initialized them

        return _children;
    }

    ...

    private ICustomProjectElement[] initializeChildren(IProject project) {
        ICustomProjectElement[] children = {
                new CustomProjectSchema(this),
                new CustomProjectStoredProcedures(this)
        };

        return children;
    }
}

Starting the runtime workbench and creating a custom project gives us a strong beginning to giving our folder-centric project a category-based view.

custom-navigator-end-of-part-7

All of the child nodes, CustomProjectSchema, CustomProjectStoredProcedures, CustomProjectSchemaTables, etc., will have code that looks suspiciously similar. It will all be refactored in the post after the navigator is mostly done. Something I learned in my years as a consultant/instructor/mentor is that you can’t test your way to learning. We’re still learning here. The thing to bear in mind is that until you know what you don’t know, you don’t know. Worry about duplicate/sloppy code at refactoring time.

[This is where the explanation for how to take an existing project structure, automatically categorize files located in arbitrary folders, and display elements from one of the XML files as information under one of the categories. As this post has gone on longer than I had planned, and I expected it to go pretty long, I will complete those last two steps in the next post. Rest easy; the hard part is over...I think.]

Rewind

So what just happened?

  1. I managed to clean up the look of the custom wizard and custom perspective by adding icon images to them.
  2. After much twisting and shouting the Custom Navigator displays only Custom Projects and the CustomProject is displayed in a way that will eventually categorizes things within the project in a more intuitive way.

I am sure you are walking away feeling like you don’t know much more about how to program/configure the CNF than you did when you started. That’s okay. There are plenty of other sources for that sort of boring information. Depth of knowledge is overrated…until it’s not. Then you’re screwed.

Download the code up to this point. Let me know if it helps.

(BTW, I cheated like there was no tomorrow. I had print statements, books, web pages…anything I thought would give me some insight into how the CNF works. This is not an easy framework. But it is cool!)

The cat is alive, but barely.

Links

Resources

http://www.eclipse.org/articles/Article-TreeViewer/TreeViewerArticle.htm
http://www.techjava.de/topics/2009/04/eclipse-common-navigator-framework/

Icons

filter – http://www.iconspedia.com/icon/document-2454.html
binoculars – http://www.clker.com/clipart-2908.html
database – http://www.clker.com/clipart-14651.html
schema – http://www.clker.com/clipart-1780.html
tables – http://www.clker.com/clipart-3493.html

Code

CustomNavigator.java

/**
 * Coder beware: this code is not warranted to do anything.
 * Copyright Oct 16, 2009 Carlos Valcarcel
 */
package customnavigator.navigator;

import org.eclipse.ui.navigator.CommonNavigator;

/**
 * @author carlos
 */
public class CustomNavigator extends CommonNavigator {
    @Override
    protected Object getInitialInput() {
        return new CustomProjectWorkbenchRoot();
    }
}

ContentProvider.java

/**
 * Coder beware: this code is not warranted to do anything.
 * Copyright Oct 17, 2009 Carlos Valcarcel
 */
package customnavigator.navigator;

import java.util.List;
import java.util.Vector;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.ResourcesPlugin;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.Viewer;

import customplugin.natures.ProjectNature;

/**
 * @author carlos
 */
public class ContentProvider implements ITreeContentProvider {

    private static final Object[]   NO_CHILDREN = {};
    private ICustomProjectElement[] _customProjectParents;

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.
     * Object)
     */
    @Override
    public Object[] getChildren(Object parentElement) {
        Object[] children = null;
        if (CustomProjectWorkbenchRoot.class.isInstance(parentElement)) {
            if (_customProjectParents == null) {
                _customProjectParents = initializeParent(parentElement);
            }

            children = _customProjectParents;
        } else if (ICustomProjectElement.class.isInstance(parentElement)) {
            children = ((ICustomProjectElement) parentElement).getChildren();
        } else {
            children = NO_CHILDREN;
        }

        return children;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.jface.viewers.ITreeContentProvider#getParent(java.lang.Object
     * )
     */
    @Override
    public Object getParent(Object element) {
        System.out.println("ContentProvider.getParent: " + element.getClass().getName()); //$NON-NLS-1$
        Object parent = null;
        if (ICustomProjectElement.class.isInstance(element)) {
            parent = ((ICustomProjectElement)element).getParent();
        }
        return parent;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.
     * Object)
     */
    @Override
    public boolean hasChildren(Object element) {
        boolean hasChildren = false;

        if (CustomProjectWorkbenchRoot.class.isInstance(element)) {
            hasChildren = _customProjectParents.length > 0;
        } else if (ICustomProjectElement.class.isInstance(element)) {
            hasChildren = ((ICustomProjectElement)element).hasChildren();
        }
        // else it is not one of these so return false
        
        return hasChildren;
    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.jface.viewers.IStructuredContentProvider#getElements(java
     * .lang.Object)
     */
    @Override
    public Object[] getElements(Object inputElement) {
        // This is the same as getChildren() so we will call that instead
        return getChildren(inputElement);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.IContentProvider#dispose()
     */
    @Override
    public void dispose() {
        System.out.println("ContentProvider.dispose"); //$NON-NLS-1$
        // TODO Auto-generated method stub

    }

    /*
     * (non-Javadoc)
     * @see
     * org.eclipse.jface.viewers.IContentProvider#inputChanged(org.eclipse.jface
     * .viewers.Viewer, java.lang.Object, java.lang.Object)
     */
    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        System.out
                .println("ContentProvider.inputChanged: old: " + oldInput.getClass().getName() + " new: " + newInput.getClass().getName()); //$NON-NLS-1$ //$NON-NLS-2$
        // TODO Auto-generated method stub

    }

    private ICustomProjectElement[] initializeParent(Object parentElement) {
        IProject[] projects = ResourcesPlugin.getWorkspace().getRoot().getProjects();

        List<CustomProjectParent> list = new Vector<CustomProjectParent>();
        for (int i = 0; i < projects.length; i++) {
            try {
                if (projects[i].getNature(ProjectNature.NATURE_ID) != null) {
                    list.add(new CustomProjectParent(projects[i]));
                }
            } catch (CoreException e) {
                // Go to the next IProject
            }
        }

        CustomProjectParent[] result = new CustomProjectParent[list.size()];
        list.toArray(result);

        return result;
    }

}

LabelProvider.java

/**
 * Coder beware: this code is not warranted to do anything.
 *
 * Copyright Oct 17, 2009 Carlos Valcarcel
 */
package customnavigator.navigator;

import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.ILabelProviderListener;
import org.eclipse.swt.graphics.Image;

/**
 * @author carlos
 *
 */
public class LabelProvider implements ILabelProvider {

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.ILabelProvider#getImage(java.lang.Object)
     */
    @Override
    public Image getImage(Object element) {
        Image image = null;
        
        if (ICustomProjectElement.class.isInstance(element)) {
            image = ((ICustomProjectElement)element).getImage();
        }
        // else ignore the element
        
        return image;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.ILabelProvider#getText(java.lang.Object)
     */
    @Override
    public String getText(Object element) {
        String text = ""; //$NON-NLS-1$
        if (ICustomProjectElement.class.isInstance(element)) {
            text = ((ICustomProjectElement)element).getText();
        }
        // else ignore the element
        
        return text;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.IBaseLabelProvider#addListener(org.eclipse.jface.viewers.ILabelProviderListener)
     */
    @Override
    public void addListener(ILabelProviderListener listener) {
        System.out.println("LabelProvider.addListener: " + listener.getClass().getName()); //$NON-NLS-1$
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
     */
    @Override
    public void dispose() {
        System.out.println("LabelProvider.dispose"); //$NON-NLS-1$
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.IBaseLabelProvider#isLabelProperty(java.lang.Object, java.lang.String)
     */
    @Override
    public boolean isLabelProperty(Object element, String property) {
        System.out.println("LabelProvider.isLabelProperty: " + element.getClass().getName()); //$NON-NLS-1$
        // TODO Auto-generated method stub
        return false;
    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.IBaseLabelProvider#removeListener(org.eclipse.jface.viewers.ILabelProviderListener)
     */
    @Override
    public void removeListener(ILabelProviderListener listener) {
        System.out.println("LabelProvider.removeListener: " + listener.getClass().getName()); //$NON-NLS-1$
        // TODO Auto-generated method stub

    }

}

About these ads
  1. October 19, 2009 at 7:43 am

    As described in the second common navigator framework article you don’t have to extend the PlatformObject since Eclipse Galileo anymore. The signature of the CommonViewer.getInitialInput() method changed, and the return value is of type Object now.

  2. cvalcarcel
    October 19, 2009 at 7:36 pm

    Thanks for the correction, Simon! I appreciate the link to the second article. Eclipse is both a great platform and a moving target. I will update my code/explanation before writing the next post.

  3. Pedro
    November 16, 2009 at 8:28 am

    Hello Carlos, I need your help again but I don’t speak English very well. Do you speak Spanish? Well, I try to write my problem. I created a customProject without errors but when I try to creating other customProject my customNavigator not update it. Thanks. My email address is pedro.santanasan@gmail.com.

  4. cvalcarcel
    November 16, 2009 at 11:02 pm

    Adding another project to this version of the plug-in doesn’t work because it is not completely configured. At this point it only knows how to give you a different view of the project.

    This is fixed in part 11. The navigatorContent extension is properly configured and the ContentProvider does the proper thing.

    If you try to squeeze more functionality out of the navigator in its current form you will be quite disappointed.

  5. April 20, 2010 at 11:04 am

    Great tutorial Carlos!

    I think you need to add the following to the snippet for ContentProvider:

    @Override
    public Object[] getElements(Object inputElement) {
    return getChildren(inputElement);
    }

    Cheers,
    Cameron.

    • cvalcarcel
      April 20, 2010 at 8:40 pm

      Actually, it was always there. I just didn’t add the code to the post as I had a link to a zip file with the code.

      I have added the code for CustomNavigator, CustomProvider and LabelProvider.

  6. Didier Rikelynck
    May 31, 2010 at 9:39 am

    Dear,

    Excellent tutorial. Exactly what I was looking for. After struggling for several days, light is at the end of the tunnel.

    But – there is always a but – after implementing your code, I noticed that the filtering in the Custom Navigator isn’t working. If I create one Java project and one Custom Project, I see nothing in the Custom navigator. I even tried your code to be sure I wasn’t missing something but no success either.
    I’m using Eclipse 3.5.2 – the RCP package.
    Hoping to hear from you soon, I’ll continue neverthless trying to figure out what’s wrong.

    Thank you very much in advance,
    D

    • cvalcarcel
      May 31, 2010 at 5:43 pm

      Ah, damning with faint praise. I feel honored. :)

      In Part 7 the navigator code only works the way it is described: create the two projects in any view except the Custom Perspective. Once the two projects exist open the Custom Perspective and only one project will appear in the custom navigator.

      Yes, the code has shortcomings. The actual fix is in Part 11. I looked. You can peek if you like or can keep adding code one painful post at a time. It worked for me (that is, doing it one painful step at a time, not necessarily peeking).

      Don’t give up. I usually wait until Friday.

      • Alex
        October 21, 2011 at 10:05 am

        Didier Rikelynck :
        Dear,
        Excellent tutorial. Exactly what I was looking for. After struggling for several days, light is at the end of the tunnel.
        But – there is always a but – after implementing your code, I noticed that the filtering in the Custom Navigator isn’t working. If I create one Java project and one Custom Project, I see nothing in the Custom navigator. I even tried your code to be sure I wasn’t missing something but no success either.
        I’m using Eclipse 3.5.2 – the RCP package.
        Hoping to hear from you soon, I’ll continue neverthless trying to figure out what’s wrong.
        Thank you very much in advance,
        D

        I have the same issue with didier. There is no Java Project or CustomProject on Custom navigator. And “MESSAGE Required filter org.eclipse.ui.navigator.resources.filters.workingSet is not present. Working set support will not function correctly.” display on console.

    • vivek
      January 12, 2012 at 4:17 pm

      Hi Didier,

      were you able to solve your issue?

      Regards,
      vivek

  7. curts
    January 27, 2011 at 10:05 am

    Null object handling for the ContentProvider.inputChanged() debug code:

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
    System.out.print(“ContentProvider.inputChanged: old: “);
    if ( oldInput != null ) {
    System.out.print( oldInput.getClass().getName() ); //$NON-NLS-1$
    }
    System.out.print(” new: “);
    if ( newInput != null ) {
    System.out.print( newInput.getClass().getName() ); //$NON-NLS-2$
    }
    System.out.println();
    // TODO Auto-generated method stub

    }

    • cvalcarcel
      February 23, 2011 at 10:51 pm

      True. Not quite as edgy, but I suppose working code is always better than edgy code…

  8. Dawid
    July 14, 2011 at 10:16 am

    Very good job Carlos!

    I have similar problem as Didier Rikelynck mentioned. I try to do what you write to him, so:
    1. I open Resource Perspective
    2. I create Java project (and choose No when Eclipse suggest to change perspective)
    3. I create MyType Project (and choose No when Eclipse suggest to change perspective)
    4. I have two project in Project Explorer
    5. I change the perspective to MyOwnPerspective and in MyOwnNavigator there is nothing. I click right mouse bottom upon MyOwn Navigator and two project appears – Java and MyType.

    Do you have any idea where I could made a mistake?

  9. Dawid
    July 14, 2011 at 1:52 pm

    I have tested it one more time. Last time I didn’t “Delete all of the nodes under (includes) (but NOT (includes))”. Now I’ve done it and nothing appears in MyOwnNavigator even if I click right mouse button (as before). and under MyOwnNavigator there is a message: “Error: No label provider for R/”

  10. Pete Howell
    August 8, 2011 at 9:01 am

    Hi

    Really enjoying your tutorials, been a great help to me so far.

    In trying to implement your examples here I’ve found that in Eclipse 3.4.2 I’m forced to make the return type of getInitialInput() IAdaptable, and therefore have CustomProjectWorkbenchRoot implement IAdaptable. This in turn creates an overridden method getAdapter, which by default returns null.

    Is there anything I need to do with getAdapter to make it work as you intended, or is this covered in a further tutorial?

    Cheers

    Pete

  11. November 9, 2011 at 5:41 am

    Great tutorial!

    Althoug, I have a small problem with collapsing elements in custom navigator. When collapsing an element which is not selected, the project completely disappear from view.

    Do you have any suggestions?

  12. November 10, 2011 at 7:10 am

    Never mind, the next tutorial solves the problem. You can delete my last two comments.

  13. Ivelina
    December 6, 2011 at 7:17 am

    Hey,

    Thanks very much for the great tutorials! They are really helping me a lot with learning to develop on Eclipse platform.

    Thanks,
    Ivelina

  14. Burcu
    April 11, 2012 at 2:34 am

    Hi, Thanks for the tutorial but I have a problem. My custom navigator shows nature projects but when I right clicked on custom navigator. I didnt use label and content providers because I dont need them.

    Can you help me?

    • Burcu
      April 11, 2012 at 3:27 am

      @Override
      protected Object getInitialInput() {
      return super.getInitialInput();
      }

      instead of Root class, I used super class and I fixed my problem!
      thanks.

  15. Joan
    April 18, 2012 at 8:20 am

    hey… I have problems with the projects appearing in the custom navigator… they appear only in the project explorer, but when i change to the custom vaigator, it;s empty. has anyone had this problem?

    • Joan
      April 18, 2012 at 8:42 am

      and in the CustomProjectParent … children is not declared… of what type must it be declared?

    • Burcu
      May 16, 2012 at 3:27 am

      if your project has applicationWorkbenchAdvisor, you must delete configurer.setSaveAndRestore(true) statement. It works in my project.

  16. Fadhly
    June 6, 2012 at 9:36 pm

    I have downloaded your plugin jars and copy them inside eclipse 3.7/plugins. The customnavigator is not working, when creating a new custom project the navigator view is empty.

  17. mat
    July 19, 2012 at 9:59 am

    It is working great except that it doesn’t automatically refresh to show new content i.e when i create a new project it doesn’t show it immediately…
    If i close the app and run it again is there..
    Any idea?? Also there is no right click menu i added navigator actions to extensions but nothing….

  18. mat
    July 19, 2012 at 10:24 am

    Let’s all take a min and thank God that after part 7 there is part 11 that fixes everything :D

  19. hanmomhanda
    November 7, 2012 at 5:53 am

    What a great tutorial.

    If the problem is fixed in part 11, the part ‘Run a test :’ in the middle should be better removed to avoid the waste of a mount of time. ;)

  20. Mariusz Cwikla
    December 1, 2012 at 9:17 am

    I have the same issue.

  21. Mariusz Cwikla
    December 1, 2012 at 9:18 am

    Alex :

    Didier Rikelynck :
    Dear,
    Excellent tutorial. Exactly what I was looking for. After struggling for several days, light is at the end of the tunnel.
    But – there is always a but – after implementing your code, I noticed that the filtering in the Custom Navigator isn’t working. If I create one Java project and one Custom Project, I see nothing in the Custom navigator. I even tried your code to be sure I wasn’t missing something but no success either.
    I’m using Eclipse 3.5.2 – the RCP package.
    Hoping to hear from you soon, I’ll continue neverthless trying to figure out what’s wrong.
    Thank you very much in advance,
    D

    I have the same issue with didier. There is no Java Project or CustomProject on Custom navigator. And “MESSAGE Required filter org.eclipse.ui.navigator.resources.filters.workingSet is not present. Working set support will not function correctly.” display on console.

    I have te same issue…

  1. No trackbacks yet.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

Join 3,187 other followers

%d bloggers like this: