Archive

[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 customnavigatorplugin.xml file Extensions tab.

Create the category:

Right click on org.eclipse.ui.views –> New –> Category

Enter the following:

id: customnavigator.category

name: Custom Projects

Save plugin.xml.

Notice the lack of code.

To bind the category to the custom navigator:

Select Custom Plug-in Navigator (view).

In the Category field to the right enter:

Category: customnavigator.category

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:

Select Custom Plug-in Navigator (view).

In the icon field to the right enter:

icon: icons/navigator.png

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 6786 5 steps to success (with lots of spackle in between so I’m not wrong by too often):

Subclass CommonNavigator

Create a root PlatformObject

Add Extension navigatorContent and bind it to the navigator

Implement the Custom Folder and File Types, ContentProvider and LabelProvider

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 6786 5 steps above is:

Implement the Common Navigator and its behavior upon instantiation

Subclass CommonNavigator

Create a root PlatformObject

Add Extension navigatorContent and bind it to the navigator

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

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.

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.

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

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.

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.

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.

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?).

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.

Display a List of Categories per Project

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

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.

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.

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:

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

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?

I managed to clean up the look of the custom wizard and custom perspective by adding icon images to them.

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.

(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!)