Skip navigation

Hi all, this post comes after a long time. I had decided to blog once a week, but work has kept me away from doing it so far.

Anyway, I have been hanging around the Netbeans Platform more than ever now, and am writing a couple of Project Templates. Wow, it has been an awesome ride and my respect for developer tools has increased multifold. To write cool code is one thing, but writing cool code which is extensible is a real achievement. There are several places in the Netbeans Platform where you get such "Aha" moments, beautiful extensible code. It’s a fantastic place to see how to write good Java code.

So, finally to the topic of this post.

Project Customizers

Every project will have a set of properties that the user can customize. Let’s take the example of a standard Java application project in Netbeans. You can customize the libraries that are on the compile time class path, and you can also choose which Java platform you want to use for the project. In this post, I will run through the steps to create a customizer for your own project templates. Here’s a screenshot of the Project Properties UI for a Java application project.

These GUI dialogs are called Project Customizers in the Netbeans Platform parlance.

Now, almost every project type needs a customizer. And almost all project customizers will have the same UI structure. A Tree View showing options (called categories) in the left hand side and a panel associated with each category for editing the properites associated with that category. So, it doesn’t make much sense asking the Project Template developer to do this UI styling stuff again and again.

Netbeans Project Support API module provides support for writing customizers through the org.netbeans.spi.project.ui.CustomizerProvider interface, which will do the work of UI styling for you. All you (Project Template developer) have to do is to provide a set of categories and a JPanel for each category for manipulating properties.

So, to create a customizer for my project template, I write an implementation of the CustomizerProvider as shown below.

import org.netbeans.spi.project.ui.CustomizerProvider;
import org.netbeans.spi.project.ui.support.ProjectCustomizer;

/**
* My Project Template's Customizer
*/
public class MyProjectCustomizerImpl implements CustomizerProvider {

public MyProjectCustomizerImpl() {
}

/**
* This method will be called by the platformto display the customizer
* when the user selects "Project Properties" option
*/
public void showCustomizer() {
}
}

The showCustomizer() method is where all the action happens. This method is called by the Netbeans Project System when the user chooses the "Project Properties" option.

But wait a minute! How on earth did the Project system come to know about this Customizer of mine? Well, it’s through the Project’s lookup! Here’s how I let the project system know about my Customizer:

public class MyProject implements Project {

...
...

public Lookup getLookup() {
if(lookup == null) {
lookup
= Lookups.fixed(new Object[] {
new Info(), // Project Information
new MyProjectCustomizerImpl(), // Customizer
new MyLogicalViewProvider(), // Ze Logicale View
this
});
}

return lookup;
}

Now to implement the logic for the customizer. I want two categories for my customizer.

  • Basic Files – To set the Java Platform support options
  • Important Files – To set the README.TXT file for my project

So, I will create two categories in my customizer class like this:

public class MyProjectCustomizerImpl implements CustomizerProvider {

private ProjectCustomizer.Category[] categories;
private ProjectCustomizer.CategoryComponentProvider panelProvider;

// Names of categories
private static final String IMPORTANT_FILES_CATEGORY = "ImpFilesCategory";
private static final String BASICS_FILES_CATEGORY = "BasicsFilesCategory";

private void init() {
ProjectCustomizer.Category importantFiles
= ProjectCustomizer.Category.create(
IMPORTANT_FILES_CATEGORY,
"Important Files",
null,
null);

ProjectCustomizer.Category basicsFiles
= ProjectCustomizer.Category.create(
BASICS_FILES_CATEGORY,
"Plugin Basics",
null,
null);

categories
= new ProjectCustomizer.Category[] {
importantFiles,
basicsFiles
};

Map panels
= new HashMap();
panels.put(importantFiles,
new ImportantFilesVisual());
panels.put(basicsFiles,
new BasicsVisual());

panelProvider
= new PanelProvider(panels);
}
}

As you can see in the code above, I have created two ProjectCustomizer.Category instances, one for each category. Then I create a Map to associate the properties manipulation UI (JPanels that I have created) with the categories. Here are the panels that I have:

What about the PanelProvider class?

Well, its here inside the MyProjectCustomizerImpl class:

private static class PanelProvider implements ProjectCustomizer.CategoryComponentProvider {
private Map panels;

private JPanel EMPTY_PANEL = new JPanel();

public PanelProvider(Map panels) {
this.panels = panels;
}

public JComponent create(ProjectCustomizer.Category category) {
JComponent panel
= (JComponent) panels.get(category);
return panel == null ? EMPTY_PANEL : panel;
}

}

I don’t think it needs any explaining. Quite easy, isn’t it?

Now, we are almost there. The only thing left is to fill out the necessary details in the showCustomizer() method to actually show the customizer.

public void showCustomizer() {
init();

OptionListener listener
= new OptionListener(project);
Dialog dialog
= ProjectCustomizer.createCustomizerDialog(categories, panelProvider,
null, listener, null);
dialog.addWindowListener(listener);

dialog.setTitle(ProjectUtils.getInformation(project).getDisplayName());

dialog.setVisible(
true);
}

First we initialize the categories and their UI by calling the init() method. The ProjectCustomizer.createCustomizerDialog() method gives us a nice Dialog which we just display on screen to take the customizations from the user.

Oh, and about the OptionListener, well, somebody has to listen to the OK and Cancel button events on the Dialog, right? Here’s the code for the OptionListener:

private class OptionListener extends WindowAdapter implements ActionListener {
private Project project;

OptionListener(Project project) {
this.project = project;
}

public void actionPerformed(ActionEvent e) {
// Close and dispose the dialog
}

public void windowClosed(WindowEvent e) {

}

public void windowClosing(WindowEvent e) {
// Close and dispose the dialog
}
}

  

Now the final step. We will need to register an action on the project node so that our customizer gets invoked. Add the following to the root node in your project’s logical view (assuming your root node is a sub class of AbstractNode or FilterNode):

Here’s the output:

Here are the Javadoc links for the Netbeans Platform classes used here:

Acknowledgement: The code above is mostly taken from the Netbeans J2SEProject Support Module. I am just documenting my learnings here. Thanks to Ibon Urrutia for pointing out some errors in the original post.

Advertisements

2 Comments

  1. The tutorial is cool man..

  2. @Rich: Thanks for providing the info. Looks like a neat addon.
    @Ranganath: Thanks for the nice words.


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

%d bloggers like this: