A few columns ago, I started building a user interface for XM. I chose to build the user interface over Eclipse, an integrated development environment (IDE) donated to the open source community by IBM. It's designed as a basic framework that tool developers can extend through plug-ins. My aim in this series is to write such a plug-in for XSL.
One of the most interesting aspects of Eclipse is that it is possible (and easy) for you to customize your working environment by combining the best plug-ins in their categories. For example, a clear synergy exists between XM and XML editors (see the "Black Sun" site in Resources). Furthermore, as this column demonstrates, if your tool of choice is not currently supported, it is not too difficult to add a plug-in.
User interface is an ongoing concern with the plug-in. After all, the plug-in aims to offer a more graphical interface than the original command-line interface that XM has had for a long time.
I chose to work with Eclipse based on the strength of its user interface. Eclipse has its own GUI library -- the standard widget library (SWT) -- which provides a native alternative to Swing. I have used Swing-based products extensively and always found them to be inferior to native applications. Somehow, Swing emulation of native controls just does not feel right. SWT uses the platform's controls, and the result is much more pleasant.
Nevertheless, there is surprisingly little code in the plug-in to manage user interface. The plug-in mostly registers invisible services, such as the builder discussed in my previous column, "Building a project with Eclipse and XM" (see Resources for a link to the article). This column looks at preferences and properties, which gives me a chance to explore SWT.
I encourage you to download the code for this project (see Resources) and review it as you read this article.
SWT has all the controls you would expect from a modern library, such as entry fields, lists, and buttons. But it also has trees, toolbars, tables, and more. Whenever possible, SWT uses native controls, so -- at long last -- virtually no differences exist between a Java application and a native application.
AWT, Swing, and SWT share many similarities. The libraries differ mostly in their implementations (the Java platform as opposed to native platform) and their naming conventions. For SWT, a control is a widget. Widgets are similar to components in the JDK. On screen, widgets are arranged on composites (panels, windows, and so on). SWT composites are similar to containers in the JDK.
I won't spend too much time in this column on the specifics of SWT. Suffice
it to say that the widgets are in the org.eclipse.swt.widgets
package. The class names are self-explanatory, so you should have no problem
navigating through the documentation.
Instead, I will concentrate on the
layouts, which differ from their AWT and Swing counterparts. Listing 1
is an excerpt from the XMProjectPropertiesPage class. I'll cover this class
in detail in Preferences and properties, but for now let's concentrate on the
buildGUI() method.
Listing 1. The buildGUI() method
private Control buildUI(Composite parent)
{
Composite composite = new Composite(parent,SWT.NULL);
RowLayout rowLayout = new RowLayout();
rowLayout.type = SWT.VERTICAL;
rowLayout.wrap = false;
composite.setLayout(rowLayout);
hasNature = new Button(composite,SWT.CHECK);
hasNature.setText(Resources.getString("eclipse.hasnature"));
hasNature.addSelectionListener(new SelectionListener()
{
public void widgetDefaultSelected(SelectionEvent e)
{
group.setEnabled(hasNature.getSelection());
}
public void widgetSelected(SelectionEvent e)
{
group.setEnabled(hasNature.getSelection());
}
});
group = new Group(composite,SWT.NONE);
group.setText(Resources.getString("eclipse.properties"));
GridLayout gridLayout = new GridLayout();
gridLayout.numColumns = 2;
group.setLayout(gridLayout);
Label label = new Label(group,SWT.RIGHT);
label.setText(Resources.getString("eclipse.src"));
srcText = new Text(group,SWT.SINGLE);
GridData gridData = new GridData();
gridData.widthHint = 150;
srcText.setLayoutData(gridData);
label = new Label(group,SWT.RIGHT);
label.setText(Resources.getString("eclipse.rules"));
rulesText = new Text(group,SWT.LEFT);
gridData = new GridData();
gridData.horizontalAlignment = GridData.FILL;
gridData.grabExcessHorizontalSpace = true;
rulesText.setLayoutData(gridData);
label = new Label(group,SWT.RIGHT);
label.setText(Resources.getString("eclipse.publish"));
publishText = new Text(group,SWT.LEFT);
gridData = new GridData();
publishText.setLayoutData(new GridData(GridData.FILL_HORIZONTAL
| GridData.GRAB_HORIZONTAL));
isBuild = new Button(group,SWT.CHECK);
isBuild.setText(Resources.getString("eclipse.build"));
gridData = new GridData();
gridData.horizontalSpan = 2;
isBuild.setLayoutData(gridData);
return composite;
}
|
Like AWT and Swing, SWT uses layout to control the size and position of widgets on a composite. SWT layouts are almost identical to JDK layouts, but they use different classes -- again, similar concepts with different implementations.
SWT proposes four layouts in the org.eclipse.swt.layout package:
FillLayout: Renders the widgets in a vertical or horizontal row. It is similar toBoxLayoutin the JDK.RowLayout: Renders the widgets over one or more rows. More specifically, it wraps widgets at the end of the row.RowLayoutis similar toFlowLayoutfrom the JDK.GridLayout: A more powerful layout. It is close to theGridBagLayoutfrom the JDK.GridLayoutrenders widgets in a grid and offers many options to control spacing, margin, alignment, and more. A widget can span several cells in the grid.FormLayout: Unique to SWT (at least I don't know of a JDK equivalent).FormLayoutis a special grid that groups data entry fields and their labels.
Listing 1 uses both RowLayout and GridLayout.
Figure 1 shows the form that Listing 1 creates. It contains a checkbox and a group of four
other widgets. RowLayout aligns the checkbox and the group. FillLayout
is not an option because the checkbox and the group
have different sizes (FillLayout insists that all its widgets have the
same size).
Figure 1. The form created by Listing 1

Initializing the RowLayout is as easy as
setting a few properties: type toggles between vertical and
horizontal rows, while wrap controls whether the layout can create more than
one row. You then assign the layout to a composite with the
setLayout() method.
The group is a composite that draws a
border around its widgets. In Listing 1, the group uses a GridLayout class.
To initialize GridLayout, you simply set the number of
columns. Note that GridBagLayout specifies the number of rows
indirectly through constants such as REMAINDER and RELATIVE.
To specify the properties of each cell individually, GridLayout uses
a GridData class. GridData is similar to GridBagConstraints from the JDK, but the property names are different. Note a major difference
between SWT and the JDK: With SWT you set GridData on the
widget (with the setLayoutData() method), not on the layout.
Listing 1 illustrates another difference between SWT and the JDK: Composites lack the add() method from containers. Instead, widgets take a reference to their parent in
their constructors.
Although there are differences between the two APIs, it will be easy for you to learn SWT if you already have experience with AWT or Swing.
Preferences and properties are closely related. Both control the configuration and options in the environment. Preferences are intended for configuration options that apply to the environment in every project. A typical example would be colors used for syntax highlighting with an editor.
Properties are attached to resources, in the Eclipse sense of the term (see the Resources for a link to "Creating a project" which includes a discussion of Eclipse resources). For example, properties may control how a given project is built.
The XM plug-in will need both preferences and properties. Our project already uses properties to set the source, publish, and rules directories, as well as to control whether to build or make the project. Those properties were derived from the old command-line interface. In "Creating a project," I introduced a wizard that initializes the properties, but so far there hasn't been any interface gimmick to modify them.
Since I started working with project natures (see Resources for links to the previous articles), I have been searching for an option to assign the XM nature to any project. Eventually, I came to the conclusion that the nature could be treated as a special property.
As for the preferences, the console needs a mechanism to filter messages. When working on large projects, a rebuild generates dozens of messages. It is difficult to separate errors from progress messages, so I wanted to give the user an option to control which messages are displayed.
Let's start with the project properties. The
implementation is spread over two classes: ProjectProperties and
XMProjectPropertiesPage. ProjectProperties is
a utility class. It is not required by Eclipse, but I find it convenient.
ProjectProperties takes care of saving and reading the properties
from the Eclipse-supplied IProject. By isolating this code in one
class, it is easier to make sure that properties (and in particular default
values) are treated coherently throughout the plug-in. (See Resources for information on downloading the code for this class.)
XMProjectPropertiesPage is
specific to Eclipse. It adds a page to the navigator's properties dialog,
which you can access by right-clicking on a resource in the
navigator. XMProjectPropertiesPage attaches to the org.eclipse.ui.propertyPages
extension point.
Listing 2 is an excerpt from plugin.xml,
the plug-in manifest that declares the extension. Pay special attention to the
objectClass attribute, which specifies which object the property applies to.
Any object that the navigator recognizes
is a potential candidate for this field, from the generic IResource
to more specific elements like IProject or IFolder.
To be even more specific, use the <filter> tag that was introduced with the pop-up menu.
Listing 2. An excerpt from plugin.xml
<extension point="org.eclipse.ui.propertyPages">
<page id="XMProjectProperties"
name="XM Properties"
objectClass="org.eclipse.core.resources.IProject"
class="org.ananas.xm.eclipse.XMProjectPropertiesPage">
</page>
</extension>
|
In Listing 2, objectClass is set to
an IProject object. This means that the property page is visible if
the user clicks on a project. Files and directories, however, won't show this
property page.
XMProjectPropertiesPage
inherits from PropertyPage and overrides createContents(),
shown in Listing 3. The createContents() method initializes the property
page by calling buildUI() (which I introduced earlier in SWT
layouts) and reading the properties with the help of a ProjectProperties
object.
Listing 3. The createContents() method
public Control createContents(Composite parent)
{
Control control = buildUI(parent);
try
{
IAdaptable adaptable = getElement();
if(adaptable instanceof IProject)
{
properties = new ProjectProperties((IProject)adaptable);
readProperties();
}
}
catch(CoreException x)
{
ErrorDialog.openError(
PlatformUI.getWorkbench().getActiveWorkbenchWindow().getShell(),
Resources.getString("eclipse.dialogtitle"),
Resources.getString("eclipse.propertyerror"),
PluginTools.makeStatus(x));
}
return control;
}
|
PropertyPage also defines callback methods when the
user presses the OK, Cancel, Apply, or
Default buttons. XMProjectPropertiesPage overrides some
of these methods to save the new property values.
Strictly speaking, the project nature is not a property. As I've discussed before, Eclipse uses the project nature to decide which plug-ins are in charge of a project. Among other things, the project nature controls entries in the menus, builder, and more. Yet the property page includes a checkbox for the nature.
I treat the project as a property because I want to give users a chance to add XM to any existing projects. The rationale is that Eclipse lets you work on radically different projects and languages from a familiar environment. You can write Java code in the morning, PHP code in the early afternoon, and updates to a Web site with XM in late afternoon.
In real life, a project often combines two or more such tools. For example, at Pineapplesoft, most of our Web sites combine XSL (through XM) and PHP. It seems logical to have one project with a dual nature. From a user standpoint, enabling the XM nature on a project and setting the project properties happen at the same time, so it seems more logical to offer all these options on the same screen.
When testing this feature, I realized that I had not implemented the project nature properly. It
turns out you have to declare the project nature as an extension point. The
extension point must implement the IProjectNature interface and
provide methods to configure and deconfigure the project.
The extension
point is in the XMProjectNature class, shown in Listing 4. The
framework calls the configure() method when the nature is added
to a project. It calls the deconfigure() method when the nature
is removed. The configure() method should make sure that the nature
works properly, which involves registering the appropriate builders. The
deconfigure() method does the opposite. The code in configure() originally appeared in the project wizard; I only had to move it to its new
home.
Listing 4. The XMProjectNature class
package org.ananas.xm.eclipse;
import org.eclipse.core.runtime.*;
import org.eclipse.core.resources.*;
public class XMProjectNature
implements IProjectNature, PluginConstants
{
private IProject project;
public void configure()
throws CoreException
{
IProjectDescription description = project.getDescription();
if(!hasBuildSpec(description.getBuildSpec()))
{
ICommand[] old = description.getBuildSpec(),
specs = new ICommand[old.length + 1];
System.arraycopy(old,0,specs,0,old.length);
ICommand command = description.newCommand();
command.setBuilderName(BUILDER_ID);
specs[old.length] = command;
description.setBuildSpec(specs);
project.setDescription(description,new NullProgressMonitor());
}
}
public void deconfigure()
throws CoreException
{
IProjectDescription description = project.getDescription();
int count = getBuildSpecCount(description.getBuildSpec());
if(count != 0)
{
ICommand[] old = description.getBuildSpec(),
specs = new ICommand[old.length - count];
int i = 0,
j = 0;
while(i < old.length)
{
if(!old[i].getBuilderName().equals(BUILDER_ID))
specs[i] = old[j++];
i++;
}
description.setBuildSpec(specs);
project.setDescription(description,new NullProgressMonitor());
}
}
public IProject getProject()
{
return project;
}
public void setProject(IProject project)
{
this.project = project;
}
private boolean hasBuildSpec(ICommand[] commands)
{
return getBuildSpecCount(commands) != 0;
}
private int getBuildSpecCount(ICommand[] commands)
{
int count = 0;
for(int i = 0;i < commands.length;i++)
if(commands[i].getBuilderName().equals(BUILDER_ID))
count++;
return count;
}
}
|
Listing 5 is the declaration in the plug-in manifest.
Listing 5. Another excerpt from plugin.xml
<extension point="org.eclipse.core.resources.natures"
id="xmnature"
name="XM Nature">
<runtime>
<run class="org.ananas.xm.eclipse.XMProjectNature"/>
</runtime>
</extension>
|
Preferences are similar to properties, but they are associated with a plug-in instead of a resource.
Unfortunately, Eclipse stores preferences differently than properties. For preferences, Eclipse uses a preference store. The store must be initialized with default values for every preference (and, indeed, the store only records a preference if its value is not the default).
The store is initialized in the plug-in class, a class that initializes objects global to the plug-in. Most plug-ins have a plug-in class (and if you use the Eclipse wizard to generate a new plug-in, it automatically has one), but somehow XM managed to get by without one.
You declare the plug-in class by adding a class attribute to the
<plugin> tag
in the manifest, as shown here:
<plugin class="org.ananas.xm.eclipse.XMPlugin"> |
For this project, the plug-in class is XMPlugin.
XMPlugin extends AbstractUIPlugin. The framework
creates an instance of the plug-in class when it loads the plug-in, giving
it a chance to initialize global objects.
It is essential that the
plug-in does not attempt to instantiate the plug-in class itself. Memory should
never contain more than one instance of the plug-in class. To facilitate
this, the plug-in class implements the singleton pattern: It stores a
reference to itself in a private class variable. Access to this shared instance
is provided through the getDefault() method.
The most useful method for us is initializeDefaultPreferences(), which initializes default values in the preference store, as shown here:
protected void initializeDefaultPreferences(IPreferenceStore store)
{
store.setDefault(LEVEL_PREFERENCE_NAME,LEVEL_ALL);
}
|
The preference page, where the user can change preference values,
is implemented in XMPreferencesPage, as shown in Listing 6. A preference
page is similar to a property page, but Eclipse provides the FieldEditorPreferencePage class, a special class that simplifies preference pages even more.
Listing 6. The XMPreferencesPage class
package org.ananas.xm.eclipse;
import org.ananas.xm.*;
import org.eclipse.ui.*;
import org.eclipse.jface.preference.*;
public class XMPreferencesPage
extends FieldEditorPreferencePage
implements PluginConstants, IWorkbenchPreferencePage
{
public XMPreferencesPage()
{
super(GRID);
IPreferenceStore store =
XMPlugin.getDefault().getPreferenceStore();
setPreferenceStore(store);
}
protected void createFieldEditors()
{
RadioGroupFieldEditor levelEditor =
new RadioGroupFieldEditor(
LEVEL_PREFERENCE_NAME,
Resources.getString("eclipse.level"),
1,
new String[][]
{
{ Resources.getString("eclipse.level.all"), LEVEL_ALL },
{ Resources.getString("eclipse.level.info"), LEVEL_INFO },
{ Resources.getString("eclipse.level.warning"), LEVEL_WARNING },
{ Resources.getString("eclipse.level.error"), LEVEL_ERROR },
},
getFieldEditorParent());
addField(levelEditor);
}
public void init(IWorkbench workbench)
{
}
}
|
FieldEditorPreferencePage works with field editors
instead of regular widgets. A field editor is simply a widget that's associated with a
preference. Simply instantiate the field editors in the createFieldEditors()
method and the framework takes care of loading and saving the preferences.
Finally, I updated the console to read the preferences and display the messages accordingly. As Listing 7 illustrates, the console retrieves the preference store directly from the plug-in class. The console also registers a property change listener. The property store notifies listeners when a preference changes (for instance, through the preference page).
Listing 7. The initializeDefaultPreferences() method
IPreferenceStore store = XMPlugin.getDefault().getPreferenceStore();
store.addPropertyChangeListener(new IPropertyChangeListener()
{
public void propertyChange(PropertyChangeEvent e)
{
setLevel((String)e.getNewValue());
}
});
setLevel(store.getString(LEVEL_PREFERENCE_NAME));
|
I have already raved over Eclipse in my previous columns, but this environment continues to excite me. It is a powerful and flexible framework. Furthermore, it is very accessible for tool developers. As this column has demonstrated several times, it's easy to enhance Eclipse. At Pineapplesoft, we now use Eclipse and XM for all of our Web publishing projects.
- Participate in the discussion forum.
- Download the plug-in source code from the online
repository.
- Try Eclipse, an
open-source effort to develop an IDE framework. It was initiated by IBM.
- Explore the Black Sun collection of Eclipse plug-ins, including an XML editor.
- Try XPath Explorer, another
useful plug-in for XSL developers.
- Check out the FTP and WebDAV plug-in, a required tool for webmasters that was developed by the Eclipse team. (Click on a version and scroll down to
find the file.)
- Review the "Creating a project" column's discussion of properties and project nature (developerWorks, January 2003).
- Read Benoît Marchal's earlier column, "Building a project with Eclipse and XM" for more information on builders (developerWorks, March 2003). You can find all of the previous Working XML columns at the column summary page.
- Take the developerWorks tutorial "Create multi-purpose Web content with XSLT" and learn how to set up a Java servlet to create multi-purpose Web content with XSLT (March 2003).
- Read this article to learn how Eclipse works with source code version control (developerWorks, March 2003).
- Check out Rational Application Developer for WebSphere Software, a commercial offering built on the open Eclipse framework.
- Find more XML resources on the developerWorks XML zone.
- Find out how you can become an IBM Certified Developer in XML and related technologies.

Benoît Marchal is a Belgian consultant. He is the author of XML by Example and other XML books. Benoît is available to help you with XML projects -- you can contact him at bmarchal@pineapplesoft.com.





