Archive

Posts Tagged ‘cnf’

Writing an Eclipse Plug-in (Part 23): Common Navigator: Rewriting History

August 14, 2010 3 comments


[I am now using Git for my source control using the EGit plug-in. Of course it is only partially working. One of my projects has fully committed and the others say they are in staging no matter how many times I tell EGit to Commit. Sigh.

Also, starting with this post I am also going to make the code in this convoluted journey available for download in each post as well as the Missing Zip Files page. It will always be available in the format of whatever Eclipse I happen to be using at the time (7/18/10: Eclipse 3.6 Helios Release) so don’t blame me if you are using an older version and something doesn’t work the way I describe it. If you follow me you walk the edge. Of course, in switching to EGit I have no idea where the code for Parts 21 and 22 have gone. I hate when that happens.

Don’t forget to add your favorite plug-ins: in my case that means EGit, EclEmma, Eclipse-CS, and UMLet]

[Woo hoo! Eclipse 3.6 is finally released! I can’t wait to be one of the first to download it! Hey! Where is everybody? Oh, it was released June 23? Really? I hate when I miss an opening party…by almost two months…but it was because I was busy…in Miami…meeting with Michael Westin…]

Well, long time no hear! Yes, I am trying to write these posts a little more often than I have been, but it is amazing how real life gets in the way…what with the cat coming in and out of the box and the squirrels distracting me to no end (don’t get me started on the platypus). I guess I may be stuck with only one post per month (maybe less).

I promise not to beat myself up over it.

Speaking of which: when I started this post the sun was out scorching everything, and I was doing everything I could to stay out of its path. After a failed attempt at getting back into running (you know, diet and exercise will help you live forever, unless you exercise wrong thereby screwing up your leg muscles making it almost impossible to walk), but after a successful attempt to eat better (salad and seafood, anyone?) it is time to pay attention to the things that keep us getting up in the morning and make life worth living (no, not sex, drugs, and rock and roll, though they help): Eclipse plug-ins.

I was going to write a post on genetic programming, but I suspect the cat hid my Koza book because it thought I was going to write a fitness function to force it to choose one state or another. I’ll do that on my next visit to Copenhagen.

What I will post about is, well, fixing the past. Usually, that is quite difficult, but we will make an exception and pretend that we can fix what we did, not because it was wrong, but because our needs have changed (that’s my story and I’m sticking to it).
Read more…

Writing an Eclipse Plug-in (Part 19): A Quick Display Fix

February 28, 2010 2 comments

[I am deep into solving a CNF issue, but since I haven’t solved it yet you will have to settle for a bug fix.]

Random bug: when the cursor hovers over the custom navigator title bar a tooltip opens letting us know that the navigator can’t find a label for the root node. The full error message is Error: no label provider for R/. Tells you everything you need to know. Except what the problem is. Or how to fix it. Or, for Eclipse novices, what R/ means.

Luckily this is something that we are not afflicted with here at Hidden Clause. The message did tell us everything we needed to know. The Custom Navigator label provider is ignoring the root node used by the navigator (the R/ referred to in the error) and returning an empty string. The code for LabelProvider.getText() is:

   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;
    }

(Notice how it so brilliantly ignores everything except elements of type ICustomProjectElement.)

What the message also tells us, by not telling us, is that our zero-length string appears to be causing consternation in the navigator. It is causing so much consternation that the navigator thinks no label provider is available to supply it with a default label for the root node.

That something is easily fixed in the LabelProvider. I’m not sure why Eclipse does not default to no string for the root (damn, those double negatives!), but it does not so we have to assign something to it. Since the standard behavior for other navigator views is to use the name of the view, in this case Custom Plug-in Navigator, that is what we will do.

  1. Open LabelProvider.java
  2. Change getText() to include an else if:
       public String getText(Object element) {
            String text = ""; //$NON-NLS-1$
            if (ICustomProjectElement.class.isInstance(element)) {
                text = ((ICustomProjectElement)element).getText();
            } else if (IWorkspaceRoot.class.isInstance(element)) {
                text = "Custom Plug-in Navigator";
            }
            // else ignore the element
    
            return text;
        }
    
  3. Start the runtime workbench and behold the beauty of our new string.

For Those of You Who Care

I discovered the solution to the above by putting a breakpoint in LabelProvider.getText() and walking the call tree. NavigatorContentServiceLabelProvider.findStyledText() quite explicitly changed the original empty string returned by LabelProvider.getText() into a null which caused NavigatorContentServiceLabelProvider.getStyledText() to assign the error message to the navigator view. It makes sense, it just wasn’t what I wanted.

Extra credit: Run the Externalize String Wizard on LabelProvider.java and add the new string to the message.properties file.

What Just Happened?

Fixed a bug. Contain your excitement.

The cat was not impressed and refused to come out.

Code

messages.properties

CustomProjectParent_Project_Folder=icons/project-folder.png
CustomProjectSchema_Project_Schema=icons/project-schema.png
CustomProjectSchemaFilters_Project_Schema_Filters=icons/project-schema-filters.png
CustomProjectSchemaTables_Project_Schema_Tables=icons/project-schema-tables.png
CustomProjectSchemaViews_Project_Schema_Views=icons/project-schema-views.png
CustomProjectStoredProcedures_Project_Stored_Procedures=icons/project-stored-procedures.png
LabelProvider_Custom_Plugin_Navigator=Custom Plug-in Navigator

LabelProvider.java

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

import org.eclipse.core.resources.IWorkspaceRoot;
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 if (IWorkspaceRoot.class.isInstance(element)) {
            text = Messages.LabelProvider_Custom_Plugin_Navigator;
        }
        // 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) {
        // TODO Auto-generated method stub

    }

    /* (non-Javadoc)
     * @see org.eclipse.jface.viewers.IBaseLabelProvider#dispose()
     */
    @Override
    public void dispose() {
        // 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) {
        // 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) {
        // TODO Auto-generated method stub

    }

}

Writing an Eclipse Plug-in (Part 11): Common Navigator: Displaying Custom Resources or Refresh Or Die or The Magic of navigatorContent

November 14, 2009 8 comments

Today’s tasks are:

  • Open the navigator in a perspective (doesn’t matter which one)
  • Create a Custom Project
  • Create a non-custom project. It should not appear in the custom navigator.

In breaking with tradition I am going to simply tell you what needs to be done (What to Do) and then I will rationalize the implementation for the work of art that it is (Why did we do it?).

What to Do

Let’s configure customnavigator’s plugin.xml:

  1. customnavigator –> plugin.xml –> org.eclipse.ui.views –> Custom Plug-in Navigator
  2. Change customnavigator.navigator.CustomNavigator to org.eclipse.ui.navigator.CommonNavigator
  3. Delete CustomNavigator.java (YAGNI for the foreseeable future)
  4. Change org.eclipse.ui.navigator.navigatorContent –> Custom Navigator Content –> (triggerPoints) –> (or) –> customnavigator.navigator.CustomProjectWorkbenchRoot to org.eclipse.ui.navigator.navigatorContent –> Custom Navigator Content –> (triggerPoints) –> org.eclipse.core.resources.IWorkspaceRoot

Change the ContentProvider code to reflect the following changes:

ContentProvider.getParent()

    @Override
    public Object getParent(Object element) {
        Object parent = null;

        if (IProject.class.isInstance(element)) {
            parent = ((IProject)element).getWorkspace().getRoot();
        } else if (ICustomProjectElement.class.isInstance(element)) {
            parent = ((ICustomProjectElement)element).getParent();
        } // else parent = null if IWorkspaceRoot or anything else

        return parent;
    }

ContentProvider.hasChildren()

    @Override
    public boolean hasChildren(Object element) {
        boolean hasChildren = false;

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

        return hasChildren;
    }

ContentProvider.getChildren()/createCustomProjectParents()/createCustomProjectParent()

    public Object[] getChildren(Object parentElement) {
        Object[] children = null;
        if (IWorkspaceRoot.class.isInstance(parentElement)) {
            IProject[] projects = ((IWorkspaceRoot)parentElement).getProjects();
            children = createCustomProjectParents(projects);
        } else if (ICustomProjectElement.class.isInstance(parentElement)) {
            children = ((ICustomProjectElement) parentElement).getChildren();
        } else {
            children = NO_CHILDREN;
        }

        return children;
    }

    private Object createCustomProjectParent(IProject parentElement) {

        Object result = null;
        try {
            if (parentElement.getNature(ProjectNature.NATURE_ID) != null) {
                result = new CustomProjectParent(parentElement);
            }
        } catch (CoreException e) {
            // Go to the next IProject
        }

        return result;
    }

    private Object[] createCustomProjectParents(IProject[] projects) {
        Object[] result = null;

        List<Object> list = new ArrayList<Object>();
        for (int i = 0; i < projects.length; i++) {
            Object customProjectParent = createCustomProjectParent(projects[i]);
            if (customProjectParent != null) {
                list.add(customProjectParent);
            } // else ignore the project
        }

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

        return result;
    }

ContentProvider.inputChanged()/ContentProvider constructor/dispose()

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        _viewer = viewer;
    }

    public ContentProvider() {
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
    }

    @Override
    public void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

ContentProvider.resourceChanged()

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        _viewer.refresh();
    }

Why did we do it?

So, as we saw in a previous post, we can display a custom project that has specialized child nodes based on the kind of project we create; in this case a project with a nature of customplugin.projectNature.

Today’s task (a repeat of what I listed above) was:

  • Open the navigator in a perspective (doesn’t matter which one)
  • Create a Custom Project
  • Create a non-custom project. It should not appear in the custom navigator.

The challenge is: how to configure the Custom Navigator so that it will only display custom projects. In my never ending quest to write as little code as possible I will show you what to configure in plugin.xml and what code to add to ContentProvider to make it happen.

Supremely obvious point #1: without content a common navigator doesn’t display anything.
Supremely obvious point #2: you can configure a resource-based CNF navigator to do one of three things (if you can think of more, let me know or better yet write it up):

  1. Display only certain resources,
  2. Display resources in a custom way or
  3. Some combination of 1 and 2.

We want to do #3: the navigator should only display custom projects and resources related to custom projects should be displayed in a custom way.

If we had wanted to display all of the top level resources in the navigator we would just define a viewerContentBinding of org.eclipse.ui.navigator.resourceContent. Since that content binding is already defined our work would be done. That however would be too easy and wrong for what we want to do (it is amazing how often easy and wrong go together).

We know that we can display custom projects and related resources in a custom way (we did that, remember?). Now we want to make sure that no matter how many projects we create the navigator will only display our projects our way (like fast food without the fat).

In order to do this we are going to take a few steps back and a few steps forward. We are going to:

  • Delete our existing navigator class (back one step)
  • Replace the existing navigator class with a CommonNavigator (back another step)
  • Attach a content binding to the viewer (which is actually already there, but we are changing it; back another step)
  • Configure a navigatorContent (which is also already there)
    • Define an object type to send into the content provider (called the triggerPoint): IWorkspaceRoot
    • Implement the content provider code to wrap our project and its children
    • Register a resource change listener in the content provider to update the viewer when new projects are added

First the ending:
Let’s configure customnavigator’s plugin.xml:

  1. Go to customnavigator –> plugin.xml –> org.eclipse.ui.views –> Custom Plug-in Navigator
  2. Change customnavigator.navigator.CustomNavigator to org.eclipse.ui.navigator.CommonNavigator
  3. Delete CustomNavigator.java (YAGNI)
  4. Change org.eclipse.ui.navigator.navigatorContent –> Custom Navigator Content –> (triggerPoints) –> (or) –> customnavigator.navigator.CustomProjectWorkbenchRoot to org.eclipse.ui.navigator.navigatorContent –> Custom Navigator Content –> (triggerPoints) –> org.eclipse.core.resources.IWorkspaceRoot

Why change the triggerPoint from CustomProjectWorkbenchRoot to IWorkspaceRoot? Believe it or not, the CustomProjectWorkbenchRoot is overkill for what we want. In fact, using CustomProjectWorkbenchRoot will keep the navigator from working properly so in the tradition of YAGNI, we got rid of it. We may live to regret it depending on how complex this project gets, but for now out with the trash.

And now for the ContentProvider code:
ContentProvider.getParent()

    @Override
    public Object getParent(Object element) {
        Object parent = null;

        if (IProject.class.isInstance(element)) {
            parent = ((IProject)element).getWorkspace().getRoot();
        } else if (ICustomProjectElement.class.isInstance(element)) {
            parent = ((ICustomProjectElement)element).getParent();
        } // else parent = null if IWorkspaceRoot or anything else

        return parent;
    }

What is this code doing?

  • If an IWorkspaceRoot comes in return null as it has no parent.
  • If an IProject comes in, return the Workspace root.
  • If a node of type ICustomProjectElement, and all of the custom project wrappers are this type, ask the object for its parent. The project will return the Workspace root, the child nodes will return the project or its custom parent node.
  • For all others return null (no parent)

Since the “all others” and IWorkspaceRoot return null we just need to check for the IProject and ICustomProjectElement.

ContentProvider.hasChildren()

    @Override
    public boolean hasChildren(Object element) {
        boolean hasChildren = false;

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

        return hasChildren;
    }

So who has children? The only two types I care about are IWorkspaceRoot and ICustomProjectElement, otherwise return null.

  • If an IWorkspaceRoot return true if there are any projects. I know, I know, I should check if any of the projects have the custom project nature, but that is overhead I will safely ignore for now.
  • If an ICustomProjectElement comes in ask if it has any children.

Stay on target.

ContentProvider.getChildren()/createCustomProjectParents()/createCustomProjectParent()

    public Object[] getChildren(Object parentElement) {
        Object[] children = null;
        if (IWorkspaceRoot.class.isInstance(parentElement)) {
            IProject[] projects = ((IWorkspaceRoot)parentElement).getProjects();
            children = createCustomProjectParents(projects);
        } else if (ICustomProjectElement.class.isInstance(parentElement)) {
            children = ((ICustomProjectElement) parentElement).getChildren();
        } else {
            children = NO_CHILDREN;
        }

        return children;
    }

    private Object createCustomProjectParent(IProject parentElement) {

        Object result = null;
        try {
            if (parentElement.getNature(ProjectNature.NATURE_ID) != null) {
                result = new CustomProjectParent(parentElement);
            }
        } catch (CoreException e) {
            // Go to the next IProject
        }

        return result;
    }

    private Object[] createCustomProjectParents(IProject[] projects) {
        Object[] result = null;

        List<Object> list = new ArrayList<Object>();
        for (int i = 0; i < projects.length; i++) {
            Object customProjectParent = createCustomProjectParent(projects[i]);
            if (customProjectParent != null) {
                list.add(customProjectParent);
            } // else ignore the project
        }

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

        return result;
    }

In getChildren() all I care about is:

  • Returning wrapped custom project resources
  • Returning the children of a custom project
  • Returning an empty array If I don’t recognize the incoming element .

The createCustomProjectParents()/createCustomProjectParent() methods wrap my custom project IProjects; all other projects will be ignored.

ContentProvider.inputChanged()/ContentProvider constructor/dispose()

    @Override
    public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {
        _viewer = viewer;
    }

    public ContentProvider() {
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
    }

    @Override
    public void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

The order of the methods above are out of sequence for the class, but in sequence for this explanation.

In inputChanged() someone has done something that we need to pay attention to. In this case, a new project was added (we haven’t coded for deletions yet). Just save the viewer reference; we’ll need it later. However, if the input has changed we need a way to determine what that change was. Enter resource change listeners.

[If you don’t know anything about event handling in Java, much less Eclipse, the following explanation is probably not going to make a lot of sense.]

For now, the easiest place to register a listener is in the ContentProvider constructor so I placed it there. Of course, whatever we register we should unregister so in dispose() we remove the resource change listener.

Which leads us to the resource change listener itself.

ContentProvider.resourceChanged()

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        _viewer.refresh();
    }

Almost too easy. Our simple navigator just needs to be told to refresh itself when a change occurs.

To summarize: when our input changes we save the viewer; when a resource changes we call resourceChanged() which refreshes the viewer.

Life is beautiful.

What Just Happened?

Now it is time for me to admit something: I hate beets (that is: the vegetable).

Well, more significantly, I have to admit it took a while for me to figure out how simple this truly was. I looked at examples, read posts, drank plenty of Guinness. Finally, I woke up one morning and thought Oh! Is that all it is?

What I found was that the examples did not do what I was trying to do (don’t you hate when that happens?). They always seem to use the pre-existing resourceContent binding in addition to other stuff which didn’t help me at all (well, it did actually help, but kept me confused for too many nights).

The moral of the story: use resourceContent if you need to show the existing resources in a standard way or with exceptions.

Don’t use resourceContent if you want to take full control over the navigator.

That might be too general and all encompassing, but, what the hell, what isn’t?

And of course all this may change as I add more functionality to fill the custom project with custom content. Stay tuned, boys and girls.

Next time: keeping the nodes opened or closed when we add a resource.

References and Thanks

Building a Common Navigator based viewer, Part I: Defining the Viewer

Code

plugin.xml

<?xml version="1.0" encoding="UTF-8"?>
<?eclipse version="3.4"?>
<plugin>
   <extension
         point="org.eclipse.ui.views">
      <category
            id="customnavigator.category"
            name="%category.name">
      </category>
      <view
            allowMultiple="false"
            category="customnavigator.category"
            class="org.eclipse.ui.navigator.CommonNavigator"
            icon="icons/navigator.png"
            id="customnavigator.navigator"
            name="%view.name">
      </view>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.viewer">
      <viewerActionBinding
            viewerId="customnavigator.navigator">
         <includes>
            <actionExtension
                  pattern="org.eclipse.ui.navigator.resources.*">
            </actionExtension>
         </includes>
      </viewerActionBinding>
      <viewerContentBinding
            viewerId="customnavigator.navigator">
         <includes>
            <contentExtension
                  pattern="customnavigator.navigatorContent">
            </contentExtension>
         </includes>
      </viewerContentBinding>
   </extension>
   <extension
         point="org.eclipse.ui.navigator.navigatorContent">
      <navigatorContent
            activeByDefault="true"
            contentProvider="customnavigator.navigator.ContentProvider"
            id="customnavigator.navigatorContent"
            labelProvider="customnavigator.navigator.LabelProvider"
            name="%navigatorContent.name">
         <triggerPoints>
            <instanceof
                  value="org.eclipse.core.resources.IWorkspaceRoot">
            </instanceof>
         </triggerPoints>
         <commonSorter
               class="customnavigator.sorter.SchemaCategorySorter"
               id="customnavigator.sorter.schemacategorysorter">
            <parentExpression>
               <or>
                  <instanceof
                        value="customnavigator.navigator.CustomProjectSchema">
                  </instanceof>
               </or>
            </parentExpression>
         </commonSorter>
      </navigatorContent>
   </extension>

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

import java.util.ArrayList;
import java.util.List;

import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResourceChangeEvent;
import org.eclipse.core.resources.IResourceChangeListener;
import org.eclipse.core.resources.IWorkspaceRoot;
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 and Hidden Clause
 */
public class ContentProvider implements ITreeContentProvider, IResourceChangeListener {

    private static final Object[]   NO_CHILDREN = {};
    Viewer _viewer;
    private int _count = 1;

    public ContentProvider() {
        ResourcesPlugin.getWorkspace().addResourceChangeListener(this, IResourceChangeEvent.POST_CHANGE);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.ITreeContentProvider#getChildren(java.lang.Object)
     */
    @Override
    public Object[] getChildren(Object parentElement) {
        System.out.println("ContentProvider.getChildren: " + parentElement.getClass().getName()); //$NON-NLS-1$
        Object[] children = null;
        if (IWorkspaceRoot.class.isInstance(parentElement)) {
            IProject[] projects = ((IWorkspaceRoot)parentElement).getProjects();
            children = createCustomProjectParents(projects);
        } 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 (IProject.class.isInstance(element)) {
            parent = ((IProject)element).getWorkspace().getRoot();
        } else if (ICustomProjectElement.class.isInstance(element)) {
            parent = ((ICustomProjectElement)element).getParent();
        } // else parent = null if IWorkspaceRoot or anything else

        return parent;
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.ITreeContentProvider#hasChildren(java.lang.Object)
     */
    @Override
    public boolean hasChildren(Object element) {
        System.out.println("ContentProvider.hasChildren: " + element.getClass().getName()); //$NON-NLS-1$
        boolean hasChildren = false;

        if (IWorkspaceRoot.class.isInstance(element)) {
            hasChildren = ((IWorkspaceRoot)element).getProjects().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
        System.out.println("ContentProvider.getElements: " + inputElement.getClass().getName()); //$NON-NLS-1$
        return getChildren(inputElement);
    }

    /*
     * (non-Javadoc)
     * @see org.eclipse.jface.viewers.IContentProvider#dispose()
     */
    @Override
    public void dispose() {
        ResourcesPlugin.getWorkspace().removeResourceChangeListener(this);
    }

    /*
     * (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) {
        _viewer = viewer;
    }

    @Override
    public void resourceChanged(IResourceChangeEvent event) {
        _viewer.refresh();
    }

    private Object createCustomProjectParent(IProject parentElement) {

        Object result = null;
        try {
            if (parentElement.getNature(ProjectNature.NATURE_ID) != null) {
                result = new CustomProjectParent(parentElement);
            }
        } catch (CoreException e) {
            // Go to the next IProject
        }

        return result;
    }

    private Object[] createCustomProjectParents(IProject[] projects) {
        Object[] result = null;

        List<Object> list = new ArrayList<Object>();
        for (int i = 0; i < projects.length; i++) {
            Object customProjectParent = createCustomProjectParent(projects[i]);
            if (customProjectParent != null) {
                list.add(customProjectParent);
            } // else ignore the project
        }

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

        return result;
    }

}

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

October 18, 2009 29 comments

[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

    }

}