Introduction:
In this article, I will explain about how to create tree table functionality. Here, I assume that you are familiar with creating trees and tables.
Creating tree table:
First of all, do not use TableTreeViewer and TableTree to create tree table. They are deprecated as of 3.1. Creating tree table is easy if you know how to work with trees and tables. Tree table is nothing but a tree which has columns. This is basically how you will create tree table.
Let's assume that you have a tree which displays your organization hierarchy (tree of employee names) . But now, you want to convert that into a tree table so that you can also display employee age and salary (in separate columns) in the hierarchy.
this.treeViewer = new TreeViewer(parent);
Tree tree = this.treeViewer.getTree();
final GridData gridData = new GridData(SWT.FILL, SWT.FILL, true, true);
this.treeViewer.getControl().setLayoutData(gridData);
this.treeViewer.setUseHashlookup(true);
/*** Tree table specific code starts ***/
tree.setHeaderVisible(true);
tree.setLinesVisible(true);
TreeColumn treeColumn = new TreeColumn(tree, SWT.LEFT);
treeColumn.setText("Name");
treeColumn = new TreeColumn(tree, SWT.LEFT);
treeColumn.setText("Age");
treeColumn = new TreeColumn(tree, SWT.LEFT);
treeColumn.setText("Salary");
TableLayout layout = new TableLayout();
int nColumns = 3;
int weight = 100 / nColumns;
for (int i = 0; i < nColumns; i++) {
layout.addColumnData(new ColumnWeightData(weight));
}
tree.setLayout(layout);
/*** Tree table specific code ends ***/
this.treeViewer.setContentProvider(new OrgHierachyContentProvider());
this.treeViewer.setLabelProvider(new OrgHierarchyLabelProvider());
this.treeViewer.setInput(this.employeeOrgHierarchyModel);
If you are familiar with creating tree, you would be familiar with the first few lines (creating tree) and the last few lines (setting content provider and label provider). The middle section (as marked above) deals with the tree table specific code. If you are familiar with creating tables, then you would understand this. Basically, you are saying that header and column lines should be visible in the tree and you are also creating tree columns and setting the layout for the columns.
Tweaking label provider:
Your content provider would be exactly same as you would do for any tree. This is because underlying data is still the same. In this particular case, the content provider provides employee object for every tree node. Only the representation of model data is going to be in tree table form instead of tree. So, once the above code is set up, you need to tweak the label provider so that it provides labels for individual columns in the tree node.
For tree, you would normally implement ILabelProvider and implement getImage and getText (which just had employee name in our case). For tree table, you should implement ITableLabelProvider (just like you would do for tables) and implement getColumnImage and getColumnText. This way for each column we know what employee attribute to return. Below is a snippet of how your label provider would look:
public String getColumnText(final Object element, final int columnIndex) {
if (element instanceof Employee) {
if (columnIndex == 0) {
return (Employee(element)).getName();
} else if (columnIndex == 1) {
return (Employee(element)).getAge();
} else if (columnIndex == 2) {
return (Employee(element)).getSalary();
}
}
return "";
}
Conclusion:
Basically you set up tree table as specified above and your data (content provider) provides tree data i.e bean for every node. Then your label provider takes the bean and provides values for different columns in the tree node. That's all you need to create a tree table. So, if you are familiar with trees and tables, creating tree table is very easy.
Saturday, August 29, 2009
Friday, August 28, 2009
Adding dynamic filter to trees in RCP (Eclipse 3.4 - Ganymede)
Introduction:
Dynamic filter provides a search box above the tree and filters elements on the tree as user types it. In this article, I will talk about how to add dynamic filtering capabilities to your tree. I will also explain about how to customize the default filter.
Adding dynamic filter:
Adding dynamic filter to the tree is very easy. Instead of creating treeviewer directly, you need to create FilteredTree (org.eclipse .ui.dialogs.FilteredTree.FilteredTree). The below code creates FilteredTree using PatterFilter.
PatternFilter filter = new PatternFilter();
FilteredTree tree = new FilteredTree(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, filter);
this.treeViewer = tree.getViewer();
and that's all you need to do to have dynamic filtering in your tree. Isn't that amazing! I know how hard it is to do in Swing.
When your view initially comes up, it would have a text box above your tree. By default it says "type filter text". But you can change that by calling setInitialText(String text) method on the FilteredTree.
Customizing Filter:
If you see above, the constructor for FilteredTree takes PatternFilter. By default, it does word matching and it matches the beginning of every word in your text. In the previous article, we discussed about how to create filters. You would create a similar filter, but which extends PatternFilter (implement just the select method like in any filter) and pass it to FilteredTree.
To customize the pattern in PatternFilter, you could use the void setPattern(String pattern) in PatternFilter. You can set the pattern string using which this filter should select elements in the viewer. This is a public method.
You can also override methods like boolean isElementSelectable(Object element) (Answers whether the given element is a valid selection in the filtered tree), boolean isElementVisible(Viewer viewer, Object element) (Answers whether the given element in the given viewer matches the filter pattern), boolean isParentMatch(Viewer viewer, Object element), boolean isLeafMatch(Viewer viewer, Object element) and boolean wordMatches(String text) to customize the filter.
wordMatches tells if any of the words in the given text satisfy the filter criteria. You could override this method if you want your filter to behave differently. For example, instead of matching every word, you can override this method so that it matches the whole string or just the first word in your string etc.,
Conclusion:
We saw how to create a dynamic filter and attach it to the tree. Also, we saw how to customize the pattern filter.
Dynamic filter provides a search box above the tree and filters elements on the tree as user types it. In this article, I will talk about how to add dynamic filtering capabilities to your tree. I will also explain about how to customize the default filter.
Adding dynamic filter:
Adding dynamic filter to the tree is very easy. Instead of creating treeviewer directly, you need to create FilteredTree (org.
PatternFilter filter = new PatternFilter();
FilteredTree tree = new FilteredTree(parent, SWT.MULTI | SWT.H_SCROLL | SWT.V_SCROLL, filter);
this.treeViewer = tree.getViewer();
and that's all you need to do to have dynamic filtering in your tree. Isn't that amazing! I know how hard it is to do in Swing.
When your view initially comes up, it would have a text box above your tree. By default it says "type filter text". But you can change that by calling setInitialText(String text) method on the FilteredTree.
Customizing Filter:
If you see above, the constructor for FilteredTree takes PatternFilter. By default, it does word matching and it matches the beginning of every word in your text. In the previous article, we discussed about how to create filters. You would create a similar filter, but which extends PatternFilter (implement just the select method like in any filter) and pass it to FilteredTree.
To customize the pattern in PatternFilter, you could use the void setPattern(String pattern) in PatternFilter. You can set the pattern string using which this filter should select elements in the viewer. This is a public method.
You can also override methods like boolean isElementSelectable(Object element) (Answers whether the given element is a valid selection in the filtered tree), boolean isElementVisible(Viewer viewer, Object element) (Answers whether the given element in the given viewer matches the filter pattern), boolean isParentMatch(Viewer viewer, Object element), boolean isLeafMatch(Viewer viewer, Object element) and boolean wordMatches(String text) to customize the filter.
wordMatches tells if any of the words in the given text satisfy the filter criteria. You could override this method if you want your filter to behave differently. For example, instead of matching every word, you can override this method so that it matches the whole string or just the first word in your string etc.,
Conclusion:
We saw how to create a dynamic filter and attach it to the tree. Also, we saw how to customize the pattern filter.
Wednesday, August 19, 2009
Applying filters to trees and tables in Eclipse RCP (3.4 - Ganymede)
Introduction:
This article talks about applying filters to trees and tables. Basically, this would apply to any viewer (which extends StructuredViewer) in Eclipse RCP framework.
Implementing filter:
All filters extend from ViewerFilter and need to implement select method. select method looks like this:
public boolean select(final Viewer viewer, final Object parentElement, final Object element)
Here viewer is the table or tree viewer from your view.
parentElement is the parent node (for the node to be selected) in the tree and for table it is the whole content of the table
element is the current node (to be selected) in the tree and for table it is the current row in the table.
element and parentElement are actually model objects which form the content in your content provider. This method needs to return true if the node/row needs to be selected based on the filter criteria.
Say you have a tree which displays names and we are writing a filter to only display names in the tree, which starts with "XYZ", then the filter class might look like this:
public class NameFilter extends ViewerFilter {
/*
* @see ViewerFilter#select(Viewer, Object, Object)
*/
public boolean select(final Viewer viewer, final Object parentElement, final Object element) {
if (element instanceof NameNode) { // Assume your tree node is of type NameNode
NameNode node = (NameNode) element;
String nodeName = node.getName();
return nodeName.startsWith("XYZ");
}
return false;
}
For a table, once you get the object (which represents the row) you can get the individual column values from the object, check the column value you want and return true or false depending on if the row needs to be displayed or not.
Attaching filter to the viewer:
Once you define the filter, next step is to attach the filter to the viewer. For example, in your view you could create a menu which lists filters. Each menuitem in the menu could be an Action (org.eclipse.jface.Action).
Your action can be like this. It basically updates the filter everytime when user clicks on the menuitem.
this.xyzNameFilter = new NameFilter(); // Creating filter instance
this.xyzNameAction = new Action("Names starting with XYZ") {
public void run() {
MyView.this.updateFilter(JobsExplorerView.this.xyzNameAction);
}
};
this.xyzNameAction .setChecked(false); // turned off initially
and updateFilter adds/removes the filter to the viewer.
public void updateFilter(final Action action) {
if (action == this.xyzNameAction) {
if (action.isChecked()) {
this.treeViewer.addFilter(this.xyzNameFilter);
} else {
this.treeViewer.removeFilter(this.xyzNameFilter);
}
}
}
treeViewer.addFilter and removeFilter takes care of adding/removing filters. Good thing about filters is you can have multiple filters and they are chained in the order as you add them. The filter filters on the results from the previous filters.
Conclusion:
This article explained about how to implement filters and adding them to your view. Though it talked about trees and tables, similar approach would work for any type of viewer.
This article talks about applying filters to trees and tables. Basically, this would apply to any viewer (which extends StructuredViewer) in Eclipse RCP framework.
Implementing filter:
All filters extend from ViewerFilter and need to implement select method. select method looks like this:
public boolean select(final Viewer viewer, final Object parentElement, final Object element)
Here viewer is the table or tree viewer from your view.
parentElement is the parent node (for the node to be selected) in the tree and for table it is the whole content of the table
element is the current node (to be selected) in the tree and for table it is the current row in the table.
element and parentElement are actually model objects which form the content in your content provider. This method needs to return true if the node/row needs to be selected based on the filter criteria.
Say you have a tree which displays names and we are writing a filter to only display names in the tree, which starts with "XYZ", then the filter class might look like this:
public class NameFilter extends ViewerFilter {
/*
* @see ViewerFilter#select(Viewer, Object, Object)
*/
public boolean select(final Viewer viewer, final Object parentElement, final Object element) {
if (element instanceof NameNode) { // Assume your tree node is of type NameNode
NameNode node = (NameNode) element;
String nodeName = node.getName();
return nodeName.startsWith("XYZ");
}
return false;
}
For a table, once you get the object (which represents the row) you can get the individual column values from the object, check the column value you want and return true or false depending on if the row needs to be displayed or not.
Attaching filter to the viewer:
Once you define the filter, next step is to attach the filter to the viewer. For example, in your view you could create a menu which lists filters. Each menuitem in the menu could be an Action (org.eclipse.jface.Action).
Your action can be like this. It basically updates the filter everytime when user clicks on the menuitem.
this.xyzNameFilter = new NameFilter(); // Creating filter instance
this.xyzNameAction = new Action("Names starting with XYZ") {
public void run() {
MyView.this.updateFilter(JobsExplorerView.this.xyzNameAction);
}
};
this.xyzNameAction .setChecked(false); // turned off initially
and updateFilter adds/removes the filter to the viewer.
public void updateFilter(final Action action) {
if (action == this.xyzNameAction) {
if (action.isChecked()) {
this.treeViewer.addFilter(this.xyzNameFilter);
} else {
this.treeViewer.removeFilter(this.xyzNameFilter);
}
}
}
treeViewer.addFilter and removeFilter takes care of adding/removing filters. Good thing about filters is you can have multiple filters and they are chained in the order as you add them. The filter filters on the results from the previous filters.
Conclusion:
This article explained about how to implement filters and adding them to your view. Though it talked about trees and tables, similar approach would work for any type of viewer.
Monday, August 10, 2009
Writing Maven (2.x) Plugins
Introduction:
This article talks about writing maven 2.x plugins. You can write maven plugins in Java, Groovy, Ruby and BeanShell. This article specifically talks about writing maven plugins in Java.
Maven Plugins:
Maven plugin is just a maven artifact which has plugin descriptor (xml file) and a set of mojos. Mojo is a java class and each mojo in the artifact corresponds to one goal in the plugin. So, a plugin can have any number of goals.
Creating maven plugin project:
You can create a maven plugin using the archetype plugin. Use the following command:
mvn archetype:create -DgroupId=com.mycompany -DartifactId=myfirst-maven-plugin -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-mojo
Run this in any directory and it will create a maven plugin project (myfirst-maven-plugin) under that directory.
Take a look at pom.xml in the base folder. packaging is set to maven-plugin indicating that is a maven-plugin project and there will be two default dependencies. One is the maven plugin api and the other one is JUnit (test scope).
At this point, you can do mvn eclipse:eclipse if you want to start developing the plugin in eclipse.
Understanding plugin descriptor:
Before we look at the mojo's, let's take a look at the plugin descriptor. Do mvn install to install your plugin into your local repository. If you look at the jar artifact in your maven repository, you can find plugin.xml in META-INF\maven folder. "Maven plugin" plugin takes care of generating this during packaging.
<plugin>
<description></description>
<groupId>com.mycompany</groupId>
<artifactId>first-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<goalPrefix>first</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
<mojos>
<mojo>
<goal>touch</goal>
<description>Goal which touches a timestamp file.</description>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>process-sources</phase>
<implementation>com.mycompany.MyMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<parameters>
<parameter>
<name>outputDirectory</name>
<type>java.io.File</type>
<required>true</required>
<editable>true</editable>
<description>Location of the file.</description>
</parameter>
</parameters>
<configuration>
<outputDirectory implementation="java.io.File">${project.build.directory}</outputDirectory>
</configuration>
</mojo>
</mojos>
<dependencies/>
</plugin>
The first few are maven plugin co-ordinates just like any other maven plugin. Goal prefix is the prefix for all the goals in the plugin. For example, when you do mvn surefire:test surefire is the goal prefix.
isolatedRealm - Deprecated
inheritedByDefault - defaults to true. Value of true indicates that any binding (i.e execution of goal in certain hase) in the parent pom will be inherited by the child poms.
mojos - Under mojos, you can have any number of mojo elements each corresponding to a goal in your plugin.
goal - name of your goal. In mvn surefire:test test is the goal name.
description - goal description to display in help
requiresDirectInvocation - defaults to false. Value of true means you can only execute the goal directly but
cannot bind it to any phase in the pom.
requiresProject - defaults to true. Value of true means you can execute the goal only within a maven project.
requiresReports - defaults to false. Value of true means the goal relies on the presence of reports section.
aggregator - Most likely will be deprecated in future.
requiresOnline - default is false. Value of true means the goal cannot be executed in offline (-o) mode.
phase - default phase where this goal binds to (if the user does not specify any binding to phase then this would be phase where goal would bind to). If this is not specified, then user is required to provide the binding phase.
implementation - class name of mojo
language - default is java, but can be Groovy, Ruby or BeanShell.
instantantiationStrategy - instantiation strategy to load mojo
executionStrategy - Most likely will be deprecated in future release
parameters - This list all the parameters of the mojo.
parameter name - name of the parameter
parameter type - type of the parameter (java.lang.String, java.io.File etc.,)
parameter required - if true, the parameter must be specified to run the goal
parameter editable - if false, user cannot override parameter value in the pom (the will always come from plugin descriptor)
parameter description - parameter description to display during help
parameter configuration - provides default values for all of the parameters
dependencies - maven plugin dependencies. Maven will download these dependencies before executing this goal.
Details of plugin descriptor can be daunting. But the good news is we don't have to write the plugin descriptor by hand. Maven generates them based on the annotations in the mojos. But, plugin descriptors are good to understand anyway.
Understanding Mojo:
When you open the source, you can see "MyMojo" class present there. All mojos extend from AbstractMojo which implements Mojo interface. The interface has setLog, getLog and execute() methods. Base class implements set and get log methods and the individual mojos have to implement the execute method. Simply the mojo cares only about execute and what it needs to do, when user executes this plugin.
MyMojo java class:
package com.mycompany;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/**
* Goal which touches a timestamp file.
*
* @goal touch
*
* @phase process-sources
*/
public class MyMojo extends AbstractMojo {
/**
* Location of the file.
* @parameter expression="${project.build.directory}"
* @required
*/
private File outputDirectory;
public void execute() throws MojoExecutionException {
File f = outputDirectory;
if (!f.exists()) {
f.mkdirs();
}
File touch = new File(f, "touch.txt");
FileWriter w = null;
try {
w = new FileWriter(touch);
w.write("touch.txt");
} catch (IOException e) {
throw new MojoExecutionException("Error creating file " + touch, e);
} finally {
if (w != null) {
try {
w.close();
} catch (IOException e) {
// ignore
}
}
}
}
}
As said above, we don't have to write the plugin descriptor by ourselves, but it comes from annotations from the mojo java class. If you look at the javadoc for class, it has description and then @goal goalName to indicate that this mojo class defines a goal and also @phase (which is optional) to indicate which phase the goal binds to by default.
javadoc on the variable outputDirectory has @required which means the parameter is required to run the goal and it also has @parameter to indicate that this variable is a parameter which user can use to configure the goal. The parameter also has default configuration which comes from default-value attribute. You can also specify alias for your parameter using alias attribute.
Parameter example:
use defaults.
Running the goal:
mvn com.mycompany:myfirst-maven-plugin:1.0-SNAPSHOT:touch (which is mvn groupId:artifactId:version:goal)
This article talks about writing maven 2.x plugins. You can write maven plugins in Java, Groovy, Ruby and BeanShell. This article specifically talks about writing maven plugins in Java.
Maven Plugins:
Maven plugin is just a maven artifact which has plugin descriptor (xml file) and a set of mojos. Mojo is a java class and each mojo in the artifact corresponds to one goal in the plugin. So, a plugin can have any number of goals.
Creating maven plugin project:
You can create a maven plugin using the archetype plugin. Use the following command:
mvn archetype:create -DgroupId=com.mycompany -DartifactId=myfirst-maven-plugin -DarchetypeGroupId=org.apache.maven.archetypes -DarchetypeArtifactId=maven-archetype-mojo
Run this in any directory and it will create a maven plugin project (myfirst-maven-plugin) under that directory.
Take a look at pom.xml in the base folder. packaging is set to maven-plugin indicating that is a maven-plugin project and there will be two default dependencies. One is the maven plugin api and the other one is JUnit (test scope).
At this point, you can do mvn eclipse:eclipse if you want to start developing the plugin in eclipse.
Understanding plugin descriptor:
Before we look at the mojo's, let's take a look at the plugin descriptor. Do mvn install to install your plugin into your local repository. If you look at the jar artifact in your maven repository, you can find plugin.xml in META-INF\maven folder. "Maven plugin" plugin takes care of generating this during packaging.
<plugin>
<description></description>
<groupId>com.mycompany</groupId>
<artifactId>first-maven-plugin</artifactId>
<version>1.0-SNAPSHOT</version>
<goalPrefix>first</goalPrefix>
<isolatedRealm>false</isolatedRealm>
<inheritedByDefault>true</inheritedByDefault>
<mojos>
<mojo>
<goal>touch</goal>
<description>Goal which touches a timestamp file.</description>
<requiresDirectInvocation>false</requiresDirectInvocation>
<requiresProject>true</requiresProject>
<requiresReports>false</requiresReports>
<aggregator>false</aggregator>
<requiresOnline>false</requiresOnline>
<inheritedByDefault>true</inheritedByDefault>
<phase>process-sources</phase>
<implementation>com.mycompany.MyMojo</implementation>
<language>java</language>
<instantiationStrategy>per-lookup</instantiationStrategy>
<executionStrategy>once-per-session</executionStrategy>
<parameters>
<parameter>
<name>outputDirectory</name>
<type>java.io.File</type>
<required>true</required>
<editable>true</editable>
<description>Location of the file.</description>
</parameter>
</parameters>
<configuration>
<outputDirectory implementation="java.io.File">${project.build.directory}</outputDirectory>
</configuration>
</mojo>
</mojos>
<dependencies/>
</plugin>
The first few are maven plugin co-ordinates just like any other maven plugin. Goal prefix is the prefix for all the goals in the plugin. For example, when you do mvn surefire:test surefire is the goal prefix.
isolatedRealm - Deprecated
inheritedByDefault - defaults to true. Value of true indicates that any binding (i.e execution of goal in certain hase) in the parent pom will be inherited by the child poms.
mojos - Under mojos, you can have any number of mojo elements each corresponding to a goal in your plugin.
goal - name of your goal. In mvn surefire:test test is the goal name.
description - goal description to display in help
requiresDirectInvocation - defaults to false. Value of true means you can only execute the goal directly but
cannot bind it to any phase in the pom.
requiresProject - defaults to true. Value of true means you can execute the goal only within a maven project.
requiresReports - defaults to false. Value of true means the goal relies on the presence of reports section.
aggregator - Most likely will be deprecated in future.
requiresOnline - default is false. Value of true means the goal cannot be executed in offline (-o) mode.
phase - default phase where this goal binds to (if the user does not specify any binding to phase then this would be phase where goal would bind to). If this is not specified, then user is required to provide the binding phase.
implementation - class name of mojo
language - default is java, but can be Groovy, Ruby or BeanShell.
instantantiationStrategy - instantiation strategy to load mojo
executionStrategy - Most likely will be deprecated in future release
parameters - This list all the parameters of the mojo.
parameter name - name of the parameter
parameter type - type of the parameter (java.lang.String, java.io.File etc.,)
parameter required - if true, the parameter must be specified to run the goal
parameter editable - if false, user cannot override parameter value in the pom (the will always come from plugin descriptor)
parameter description - parameter description to display during help
parameter configuration - provides default values for all of the parameters
dependencies - maven plugin dependencies. Maven will download these dependencies before executing this goal.
Details of plugin descriptor can be daunting. But the good news is we don't have to write the plugin descriptor by hand. Maven generates them based on the annotations in the mojos. But, plugin descriptors are good to understand anyway.
Understanding Mojo:
When you open the source, you can see "MyMojo" class present there. All mojos extend from AbstractMojo which implements Mojo interface. The interface has setLog, getLog and execute() methods. Base class implements set and get log methods and the individual mojos have to implement the execute method. Simply the mojo cares only about execute and what it needs to do, when user executes this plugin.
MyMojo java class:
package com.mycompany;
/*
* Copyright 2001-2005 The Apache Software Foundation.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import org.apache.maven.plugin.AbstractMojo;
import org.apache.maven.plugin.MojoExecutionException;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
/**
* Goal which touches a timestamp file.
*
* @goal touch
*
* @phase process-sources
*/
public class MyMojo extends AbstractMojo {
/**
* Location of the file.
* @parameter expression="${project.build.directory}"
* @required
*/
private File outputDirectory;
public void execute() throws MojoExecutionException {
File f = outputDirectory;
if (!f.exists()) {
f.mkdirs();
}
File touch = new File(f, "touch.txt");
FileWriter w = null;
try {
w = new FileWriter(touch);
w.write("touch.txt");
} catch (IOException e) {
throw new MojoExecutionException("Error creating file " + touch, e);
} finally {
if (w != null) {
try {
w.close();
} catch (IOException e) {
// ignore
}
}
}
}
}
As said above, we don't have to write the plugin descriptor by ourselves, but it comes from annotations from the mojo java class. If you look at the javadoc for class, it has description and then @goal goalName to indicate that this mojo class defines a goal and also @phase (which is optional) to indicate which phase the goal binds to by default.
javadoc on the variable outputDirectory has @required which means the parameter is required to run the goal and it also has @parameter to indicate that this variable is a parameter which user can use to configure the goal. The parameter also has default configuration which comes from default-value attribute. You can also specify alias for your parameter using alias attribute.
Parameter example:
@parameter [alias="someAlias"] [expression="${someExpression}"]So, when the plugin is built, it builds the plugin descriptor using these and the rest of the parameters
[default-value="value"]
You can also add @readOnly attribute, which makes the parameter read only
and not configurable by the user. To deprecate a parameter, use @deprecated
attribute.
@requiresDependencyResolution- Fla gs this goal as requiring the
dependencies in the specified scope i.e all the dependencies in this scope
would first be resolved before executing the goal.
use defaults.
Running the goal:
mvn com.mycompany:myfirst-maven-plugin:1.0-SNAPSHOT:touch (which is mvn groupId:artifactId:version:goal)
Notice that this goal runs only within any of your maven
projects. If you run it outside, then it will error out,
since @requiresProject is true by default. You can turn
it off in the mojo, if you want to run the goal in any
directory.
If you want to customize/configure the params you can do
so using the -D parameter.
Plugin Prefix:
As you can see typing the above goal is long and cumbersome.
To avoid typing this everytime, maven assigns plugin prefix.
For our plugin, prefix is "myfirst". This is because maven
automatically generates plugin prefix for your plugin if
your plugin name follows the maven syntax of
$name-maven-plugin or maven-$name-plugin. You can see in
maven-metadata-local.xml in your group id directory in M2
Repo to get an idea of how the prefixes are assigned.<plugin><name>myfirst maven PluginMaven Mojo</name><prefix>myfirst</prefix><artifactId>myfirst-maven-plugin</artifactId></plugin>Here myfirst is the prefix for myfirst maven plugin.But by default, maven searches in the
org.apache.maven.plugins (core plugins) and
org.codehaus.mojo (extra plugins) groups to see if the
plugin is assigned a prefix. It looks at
maven-metadata-central.xml to find this.If we want our
group id to be also searched for prefixes, then we can
do so by adding our plugin group in settings.xml.
Add this block to your ~\.m2\settings.xml:<pluginGroups> <pluginGroup>com.mycompany</pluginGroup></pluginGroups><build>
Now, you should be able to run the same above goal like
this: mvn myfirst:touch
If you do not like the maven generated plugin prefix, you
can also set your own by configuring maven plugin plugin in
your pom. For, example you can add the following to your
pom:
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>2.3</version>
<configuration>
<goalPrefix>mycustomprefix</goalPrefix>
</configuration>
</plugin>
</plugins>
</build>Now, you should be able to run the same above goal like
this:
mvn mycustomprefix:touchLogging and Exceptions:During execution, the mojo can get the log using getLog and
log messages. This log is very similar to log4j and has
debug, info, warn, error etc., Mojo should log error
messages if it is recoverable and can still continue. But,
if it cannot continue, it should throw exception. There are
two types of exceptions available and a mojo should decide
on which exception to throw at what time:
MojoExecutionException - Throw this for fatal exceptions
and the build cannot continue. This will cause BUILD ERROR
message to be displayed.
MojoFailureException - The goal cannot continue, but still
the build can continue.
Customizing Mojo:
You can customize the mojo using mojo parameters. You can
customize the parameter using the following ways:
1) Using -D option in command line while executing the goal
(-DparamName=paramValue)
2) Using the properties section in settings.xml
<properties>paramValue
</properties>
3) Adding the goal to the build section of the POM in your
project (by using executions and configuration). You can
even bind your goal to multiple phases and configure them
differently while executing in each phase.
settings.xml takes precendence over -D property and the one
in pom takes precedence over settings.xml.
Parameter Types:
The parameter type can be String, StringBuffer, Character,
Boolean (true or false), Integer, Double, Float, Date
(Example format: "yyyy-MM-dd HH:mm:ssa" (a sample date is
"2009-08-15 3:43:23PM"), URL (http:\\www.mycompany.com),
Files and Directories (C:\temp.txt)
We can also have multi-valued parameters.
1) For array - private String[] files;
<files>
<param>c:\test.txt</param>
<param>c:\test2.txt</param>
</files>
2) Collection (Any implementation of collection interface
like LinkedList, HashSet etc.,)
private HashSet mySetOfFiles;
<mySetOfFiles>
<param>c:\test.txt</param>
<param>c:\test2.txt</param>
</mySetOfFiles>
3) Map (Any implementation of Map interface like HashMap,
LinkedHashMap etc.,)
private Map myMap;
<myMap>
<key1>value1</key1>
<key2>value2</key2>
</myMap>
4) Properties - java.util.properties
private Properties myProps;
<myProps>
<property>
<name>propertyName1</name>
<value>propertyValue1</value>
<property>
<property>
<name>propertyName2</name>
<value>propertyValue2</value>
<property>
</myProps>
5) Any other object - private Tag tag;
(name and desc are fields of Tag)<configuration>
...
<tag>
<name>tagname</name>
<desc>tagdesc</desc>
</tag>
</configuration>...
The rules for mapping complex objects are as follows:
- There must be a private field that corresponds to name of the element being mapped. So in our case the tag element must map to a tag field in the mojo. If the private variable has _ in it (for example: name_) and if you want name to be the parameter then add a setter (setName) so that this can work.
- The object instantiated must be in the same package as the Mojo itself. So if your mojo is in com.mycompany.mojo.query then the mapping mechanism will look in that package for an object named Tag.
- If you wish to have the object to be instantiated live in a different package or have a more complicated name then you must specify this using an implementation attribute like this:
... <configuration implementation="com.mycompany.xyz.Tag">
<tag>
<name>tagname</name>
<desc>tagdesc</desc>
</tag>
</configuration>...
Handling pre-requisites:
You can use @executes goal to achieve this:
This annotation will cause Maven to spawn a parallel build
and execute a goal or a lifecycle in a parallel instance of
Maven that isn't going to affect the current build.
Few examples:
@execute phase="package" - Executes a parallel lifecycle
ending in the specified phase. Results of this execution is
available via maven property${executedProperty}.
@execute goal="eclipse:eclipse" - Executes this goal in
parallel. Results of this does not affect the current build.
Conclusion:
We saw how to create a maven plugin, went through plugin
descriptor file (plugin.xml), and mojo's. We then saw how to
install the maven plugin, how plugin prefix mechanism works,
how to customize mojo parameters and different types of
parameters including custom objects. Finally, we also briefly
touched upon how to execute pre-conditions for the goal. Hope
this helps in understanding how to write maven plugins and
gives you a quick start on writing your own maven plugin.
Friday, May 8, 2009
Context Sensitive Help in Eclipse (Version 3.4 - Ganymede)
Introduction:
This article will explain and walk you through on how to add context sensitive help to your Eclipse application. Also, it touches upon how to implement programmatic help in Eclipse.
Adding context extension:
Open plugin.xml of your help plugin (or your source plugin, if you have help files in your source plugin itself) . Go to extensions and add org.eclipse.help.contexts extension point. Create contexts element inside it which has two attributes - file and plugin. The file attribute points to your contexts.xml (we will discuss the details of contexts.xml below) and the plugin attribute points to the plugin for which context sensitive help is needed. The plugin attribute is very useful, and allows any plugin to contribute to the context help of other plugins. This provides extensibility. Below is an example of this extension point:
<extension
point="org.eclipse.help.contexts">
<contexts
file="contexts.xml"
plugin="com.mycompany.xyz">
</contexts>
</extension>
Contexts.xml:
This file lists the various context ids. The root element is contexts and it can have many context elements. Each context has id and title attributes and it has description and topic elements. A context can have multiple topics each pointing to a html location. PDE has support for editing contexts xml file. You should be able to right click on contexts and add context, edit attributes of context etc., using the PDE editor. Below is an example of a sample contexts xml file:
<contexts>
<context id="package_explorer_view" title="Package Explorer View">
<description>Short description of package explorer view
<topic href="html/views/packageExplorer.html" label="Package Explorer View"/>
</context>
<context id="java_editor" title="Java Editor">
<description>Short description of java editor
<topic href="html/editors/javaEditor.htmll" label="Java Editor"/>
</context>
</contexts>
Setting context ids in the code:
Now that we have defined the contexts file and the context ids, it is time to associate the views and editors in the application to those context ids. You can set the context ids for your control using this:
IWorkbenchHelpSystem helpSystem = PlatformUI.getWorkbench().getHelpSystem();
helpSystem.setHelp(control, "com.mycompany.xyz.package_explorer_view");
Note that context id has the plugin name prefixed to it. The contex id in itself (i.e without the prefix) should not have whitespaces or periods.
Also, context id's are inherited by the child controls. You can override by setting the context id for each of your child control. If not, they would use the parent context id.
Invoking Help:
Now, whenever you press F1, it will display context sensitive help. It can either appear in infopopup or the Help view. You can control that using the preference in
Window->Preferences->Help. It has option to show context sensitive help in either the help view or in the infopopup. The bottom of the infopopup also has option to show the context in help view.
Adding Dynamic Help menu item:
Dynamic Help view is the same help view which appears when F1 is pressed. If you wish, you can add it to your Help menu (like in Eclipse IDE) by adding org.eclipse.ui.help.dynamicHelp command under your menu. Also, don't forget to register this action in your ApplicationActionBarAdvisor class.
action = ActionFactory.DYNAMIC_HELP.create(window);
this.register(action);
The dynamic help view changes content based on the active selection. For example, if you are in package explorer, it will show help for that view and then when you move selection from there to java editor, it will display the contents related to the java editor.
Programmatic Help:
Eclipse also provides API to access the help system programmatically. For example, if you want to open the global help or the context help for a particular editor upon pressing a button, you can do so by using the API. IWorkbenchHelpSystem has various methods to access help. Some of the common useful ones are:
displayHelp();
displayHelp(String contextId);
displayHelpResource(String htmlUrl);
setHelp(Control control, String contextId);
Below is a code snippet for displaying the context help when help button is pressed:
Button help = new Button(buttonsComposite, SWT.PUSH);
help.setText("Help");
GridData gridData = new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false);
help.setLayoutData(gridData);
help.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent evt) {
IWorkbenchHelpSystem helpSystem = PlatformUI.getWorkbench().getHelpSystem();
helpSystem.displayHelp("com.mycompany.xyz.java_editor");
}
});
Conclusion:
We saw how to add the required contexts extension, details of contexts xml file and how to associate context ids in the code. We also discussed about adding dynamic help, how to add it to the menu and how to access the help system programmatically. Hope this article helps you in implementing context sensitive help in your eclipse application.
This article will explain and walk you through on how to add context sensitive help to your Eclipse application. Also, it touches upon how to implement programmatic help in Eclipse.
Adding context extension:
Open plugin.xml of your help plugin (or your source plugin, if you have help files in your source plugin itself) . Go to extensions and add org.eclipse.help.contexts extension point. Create contexts element inside it which has two attributes - file and plugin. The file attribute points to your contexts.xml (we will discuss the details of contexts.xml below) and the plugin attribute points to the plugin for which context sensitive help is needed. The plugin attribute is very useful, and allows any plugin to contribute to the context help of other plugins. This provides extensibility. Below is an example of this extension point:
<extension
point="org.eclipse.help.contexts">
<contexts
file="contexts.xml"
plugin="com.mycompany.xyz">
</contexts>
</extension>
Contexts.xml:
This file lists the various context ids. The root element is contexts and it can have many context elements. Each context has id and title attributes and it has description and topic elements. A context can have multiple topics each pointing to a html location. PDE has support for editing contexts xml file. You should be able to right click on contexts and add context, edit attributes of context etc., using the PDE editor. Below is an example of a sample contexts xml file:
<contexts>
<context id="package_explorer_view" title="Package Explorer View">
<description>Short description of package explorer view
<topic href="html/views/packageExplorer.html" label="Package Explorer View"/>
</context>
<context id="java_editor" title="Java Editor">
<description>Short description of java editor
<topic href="html/editors/javaEditor.htmll" label="Java Editor"/>
</context>
</contexts>
Setting context ids in the code:
Now that we have defined the contexts file and the context ids, it is time to associate the views and editors in the application to those context ids. You can set the context ids for your control using this:
IWorkbenchHelpSystem helpSystem = PlatformUI.getWorkbench().getHelpSystem();
helpSystem.setHelp(control, "com.mycompany.xyz.package_explorer_view");
Note that context id has the plugin name prefixed to it. The contex id in itself (i.e without the prefix) should not have whitespaces or periods.
Also, context id's are inherited by the child controls. You can override by setting the context id for each of your child control. If not, they would use the parent context id.
Invoking Help:
Now, whenever you press F1, it will display context sensitive help. It can either appear in infopopup or the Help view. You can control that using the preference in
Window->Preferences->Help. It has option to show context sensitive help in either the help view or in the infopopup. The bottom of the infopopup also has option to show the context in help view.
Adding Dynamic Help menu item:
Dynamic Help view is the same help view which appears when F1 is pressed. If you wish, you can add it to your Help menu (like in Eclipse IDE) by adding org.eclipse.ui.help.dynamicHelp command under your menu. Also, don't forget to register this action in your ApplicationActionBarAdvisor class.
action = ActionFactory.DYNAMIC_HELP.create(window);
this.register(action);
The dynamic help view changes content based on the active selection. For example, if you are in package explorer, it will show help for that view and then when you move selection from there to java editor, it will display the contents related to the java editor.
Programmatic Help:
Eclipse also provides API to access the help system programmatically. For example, if you want to open the global help or the context help for a particular editor upon pressing a button, you can do so by using the API. IWorkbenchHelpSystem has various methods to access help. Some of the common useful ones are:
displayHelp();
displayHelp(String contextId);
displayHelpResource(String htmlUrl);
setHelp(Control control, String contextId);
Below is a code snippet for displaying the context help when help button is pressed:
Button help = new Button(buttonsComposite, SWT.PUSH);
help.setText("Help");
GridData gridData = new GridData(SWT.BEGINNING, SWT.BEGINNING, false, false);
help.setLayoutData(gridData);
help.addSelectionListener(new SelectionAdapter() {
public void widgetSelected(SelectionEvent evt) {
IWorkbenchHelpSystem helpSystem = PlatformUI.getWorkbench().getHelpSystem();
helpSystem.displayHelp("com.mycompany.xyz.java_editor");
}
});
Conclusion:
We saw how to add the required contexts extension, details of contexts xml file and how to associate context ids in the code. We also discussed about adding dynamic help, how to add it to the menu and how to access the help system programmatically. Hope this article helps you in implementing context sensitive help in your eclipse application.
Tuesday, May 5, 2009
Working with Eclipse Help System (Version 3.4 - Ganymede)
Introduction:
Almost any application we work with needs a help system. The help system can be set up for normal workbench mode, remote infocenter mode (help via web) and standalone mode (for users who do not use eclipse). This article will guide you through how to add help menu, how to set up the help system using the workbench mode.
Adding help menu items:
Under your help menu, create a new command with commandId org.eclipse.ui.help.helpContents to add Help->HelpContents menu item. To add Search menu item, create a new command with commandId org.eclipse.ui.help.helpSearch. To add index menu item, create a new command with commandId org.eclipse.help.ui.indexcommand. You also need to make sure that the actions are registered. Add this block of code to makeActions(window) method in your ApplicationActionBarAdvisor to register them:
IWorkbenchAction action = ActionFactory.HELP_CONTENTS.create(window);
this.register(action);
action = ActionFactory.HELP_SEARCH.create(window);
this.register(action);
Generating sample help content:
The help content can either go with your source plugin or in a separate documentation plugin. I personally prefer to have it in a separate documentation plugin. Help is contributed by documentation plugins by using org.eclipse.help.toc extension point. The easiest way to add some help content is using the help content template which comes with Eclipse. Open plugin.xml and go to Extensions tab. Click on "Add" and type in org.eclipse.help.toc in extension point filter and in the available templates select Help Content and click Next. Make sure to select Primary checkbox, if you wish to contribute a main book to the help content. This will generate a bunch of toc xml files and sample html pages under html directory. Now, if you do Help->Help Contents you should be able to see the sample content.
Understanding help content:
If you look at plugin.xml, you will see that all of the toc* points to xml file. The primary toc.xml has several topics inside it and each topic can either specify link or anchor. In this case, you can see that all of the them are specifying anchors and not an actual link. These anchors can actually be used by any plugin to contribute help under that section. The primary toc would look like this:
<toc label="Sample Table of Contents" topic="html/toc.html">
<topic label="Getting Started">
<anchor id="gettingstarted"/>
</topic>
<topic label="Concepts">
<anchor id="concepts"/>
</topic>
<topic label="Tasks">
<anchor id="tasks"/>
</topic>
<topic label="Reference">
<anchor id="reference"/>
</topic>
<topic label="Samples">
<anchor id="samples"/>
</topic>
</toc>
If you see tocconcepts.xml, it uses anchor to contribute help i.e all topics inside this toc would go under the main topic which has this anchor. The link_to attribute takes care of this.
<toc label="Concepts" link_to="toc.xml#concepts">
<topic label="Main Topic" href="html/concepts/maintopic.html">
<topic label="Sub Topic" href="html/concepts/subtopic.html" />
</topic>
<topic label="Main Topic 2">
<topic label="Sub Topic 2" href="html/concepts/subtopic2.html" />
</topic>
</toc>
This is bottom-up composition. For top-down nesting, instead of defining anchors, the primary toc can instead create the links directly. Below is an example of top-down nesting, where links are directly used:
<toc label="Sample Table of Contents" topic="html/toc.html">
<topic label="Getting Started">
<link toc="toc_gettingStarted.xml"/>
</topic>
<topic label="Concepts">
<link toc="toc_concepts.xml"/>
</topic>
<topic label="Tasks">
<link toc="toc_tasks.xml"/>
</topic>
<topic label="Reference">
<link toc="toc_reference.xml"/>
</topic>
<topic label="Samples">
<link toc="toc_samples.xml"/>
</topic>
</toc>
Now, the secondary toc files (i.e tocconcepts.xml or toctasks.xml) do not need link_to attribute anymore.
Top down nesting is easier to understand but bottom up composition is preferred since it is very flexible and other plugins can contribute content to that section dynamically.
The actual help content is actually html pages. So, it does not matter how they are generated i.e using docbook or DITA or FrameMaker. As long as the html files are valid and they are structured properly, help will show up fine. You can also use XHTML to generate dynamic content.
Localization of help content:
By default, the toc files are in the base directory and the html pages reside under the html directory. For localizing the content, the content needs to be under nl/<languageCode> or nl/<languageCode>/<countryCode>. For example, for help content in german, the contents need to be in nl/de/DE directory. The content is first searched in nl/<languageCode>/<countryCode>. If it is not found, then it is searched in nl/<languageCode>. If it is not found there also, then it looks in the default base directory of the plugin.
The amount of documentation can quickly grow as your application gets bigger. Eclipse allows the documentation to be bundled in doc.zip file so that you can compress your documentation files. But, this works only when the plugin is deployed in unpacked form. Also, if you have localized help content, you cannot have one big doc.zip which has content for all languages. Instead, you need to have doc.zip for each of the languages under it's own nl directory.
Help Index:
To set up the help index, you need to use org.eclipse.help.index extension point. This extension point takes a index xml file and it looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<index version="1.0">
<entry keyword="Eclipse Concepts">
<entry keyword="Views">
<topic href="html/concepts/views.html" />
</entry>
<entry keyword="Editors">
<topic href="html/concepts/editors.html" />
</entry>
</entry>
</index>
This shows up in the help index page. Basically, this is similar to book keyword index. Here Eclipse concepts is shown as a folder under which you have two keywords - Views and Editors.
Don't confuse this with search index. Talking about search index, the index is built by eclipse, whenever user first does search on the help. If you have large number of help files and do not want your user to wait for a long time, then you also have the option of pre building index files and ship it along with your plugin. Basically, this is a tradeoff between time and space.
Customization:
The help contents window is very rich and offers lot of functionalities including searching, printing, bookmarking, moving between search results to table of contents etc., We can also customize some of the help contents window UI using the preferences defined in org.eclipse.help plugin. Some of the preferences are:
banner - To specify new banner at the top of the help
banner_height - To specify the height of the banner
help_home - To specify the custom html help home page, when help opens up
page_not_found - The page to show when the help topic html page is not found
windowTitlePrefix - To specify whether the help window show have "Help - " string prefixed
always_external_browser - Set this to true, if you want help to always open in external browser
bookmarksView - Set this to true or false to control the visibility of bookmarks view
Conclusion:
We saw how to add help to your menu, how to generate and understand help content, how to create index pages, how to localize and customize help functionality. Eclipse also supports context sensitive help, programmatic help and active help. I purposefully left them out for simplicity purpose. I will cover them in a separate article. Hope this article helps you in understanding and getting started with the Eclipse help system.
Almost any application we work with needs a help system. The help system can be set up for normal workbench mode, remote infocenter mode (help via web) and standalone mode (for users who do not use eclipse). This article will guide you through how to add help menu, how to set up the help system using the workbench mode.
Adding help menu items:
Under your help menu, create a new command with commandId org.eclipse.ui.help.helpContents to add Help->HelpContents menu item. To add Search menu item, create a new command with commandId org.eclipse.ui.help.helpSearch. To add index menu item, create a new command with commandId org.eclipse.help.ui.indexcommand. You also need to make sure that the actions are registered. Add this block of code to makeActions(window) method in your ApplicationActionBarAdvisor to register them:
IWorkbenchAction action = ActionFactory.HELP_CONTENTS.create(window);
this.register(action);
action = ActionFactory.HELP_SEARCH.create(window);
this.register(action);
Generating sample help content:
The help content can either go with your source plugin or in a separate documentation plugin. I personally prefer to have it in a separate documentation plugin. Help is contributed by documentation plugins by using org.eclipse.help.toc extension point. The easiest way to add some help content is using the help content template which comes with Eclipse. Open plugin.xml and go to Extensions tab. Click on "Add" and type in org.eclipse.help.toc in extension point filter and in the available templates select Help Content and click Next. Make sure to select Primary checkbox, if you wish to contribute a main book to the help content. This will generate a bunch of toc xml files and sample html pages under html directory. Now, if you do Help->Help Contents you should be able to see the sample content.
Understanding help content:
If you look at plugin.xml, you will see that all of the toc* points to xml file. The primary toc.xml has several topics inside it and each topic can either specify link or anchor. In this case, you can see that all of the them are specifying anchors and not an actual link. These anchors can actually be used by any plugin to contribute help under that section. The primary toc would look like this:
<toc label="Sample Table of Contents" topic="html/toc.html">
<topic label="Getting Started">
<anchor id="gettingstarted"/>
</topic>
<topic label="Concepts">
<anchor id="concepts"/>
</topic>
<topic label="Tasks">
<anchor id="tasks"/>
</topic>
<topic label="Reference">
<anchor id="reference"/>
</topic>
<topic label="Samples">
<anchor id="samples"/>
</topic>
</toc>
If you see tocconcepts.xml, it uses anchor to contribute help i.e all topics inside this toc would go under the main topic which has this anchor. The link_to attribute takes care of this.
<toc label="Concepts" link_to="toc.xml#concepts">
<topic label="Main Topic" href="html/concepts/maintopic.html">
<topic label="Sub Topic" href="html/concepts/subtopic.html" />
</topic>
<topic label="Main Topic 2">
<topic label="Sub Topic 2" href="html/concepts/subtopic2.html" />
</topic>
</toc>
This is bottom-up composition. For top-down nesting, instead of defining anchors, the primary toc can instead create the links directly. Below is an example of top-down nesting, where links are directly used:
<toc label="Sample Table of Contents" topic="html/toc.html">
<topic label="Getting Started">
<link toc="toc_gettingStarted.xml"/>
</topic>
<topic label="Concepts">
<link toc="toc_concepts.xml"/>
</topic>
<topic label="Tasks">
<link toc="toc_tasks.xml"/>
</topic>
<topic label="Reference">
<link toc="toc_reference.xml"/>
</topic>
<topic label="Samples">
<link toc="toc_samples.xml"/>
</topic>
</toc>
Now, the secondary toc files (i.e tocconcepts.xml or toctasks.xml) do not need link_to attribute anymore.
Top down nesting is easier to understand but bottom up composition is preferred since it is very flexible and other plugins can contribute content to that section dynamically.
The actual help content is actually html pages. So, it does not matter how they are generated i.e using docbook or DITA or FrameMaker. As long as the html files are valid and they are structured properly, help will show up fine. You can also use XHTML to generate dynamic content.
Localization of help content:
By default, the toc files are in the base directory and the html pages reside under the html directory. For localizing the content, the content needs to be under nl/
The amount of documentation can quickly grow as your application gets bigger. Eclipse allows the documentation to be bundled in doc.zip file so that you can compress your documentation files. But, this works only when the plugin is deployed in unpacked form. Also, if you have localized help content, you cannot have one big doc.zip which has content for all languages. Instead, you need to have doc.zip for each of the languages under it's own nl directory.
Help Index:
To set up the help index, you need to use org.eclipse.help.index extension point. This extension point takes a index xml file and it looks like this:
<?xml version="1.0" encoding="UTF-8"?>
<index version="1.0">
<entry keyword="Eclipse Concepts">
<entry keyword="Views">
<topic href="html/concepts/views.html" />
</entry>
<entry keyword="Editors">
<topic href="html/concepts/editors.html" />
</entry>
</entry>
</index>
This shows up in the help index page. Basically, this is similar to book keyword index. Here Eclipse concepts is shown as a folder under which you have two keywords - Views and Editors.
Don't confuse this with search index. Talking about search index, the index is built by eclipse, whenever user first does search on the help. If you have large number of help files and do not want your user to wait for a long time, then you also have the option of pre building index files and ship it along with your plugin. Basically, this is a tradeoff between time and space.
Customization:
The help contents window is very rich and offers lot of functionalities including searching, printing, bookmarking, moving between search results to table of contents etc., We can also customize some of the help contents window UI using the preferences defined in org.eclipse.help plugin. Some of the preferences are:
banner - To specify new banner at the top of the help
banner_height - To specify the height of the banner
help_home - To specify the custom html help home page, when help opens up
page_not_found - The page to show when the help topic html page is not found
windowTitlePrefix - To specify whether the help window show have "Help - " string prefixed
always_external_browser - Set this to true, if you want help to always open in external browser
bookmarksView - Set this to true or false to control the visibility of bookmarks view
Conclusion:
We saw how to add help to your menu, how to generate and understand help content, how to create index pages, how to localize and customize help functionality. Eclipse also supports context sensitive help, programmatic help and active help. I purposefully left them out for simplicity purpose. I will cover them in a separate article. Hope this article helps you in understanding and getting started with the Eclipse help system.
Wednesday, April 22, 2009
List of plugins needed for Eclipse RCP Help (Eclipse Version 3.4 - Ganymede)
It took me a LOT of time to compile the right set of plugins needed for Eclipse RCP Help. I could find few related articles but they were outdated for Ganymede. First of all there is no need for any compile time dependency to be added to your plugin.xml. If you have a basic RCP application, which has no help in it, you need to add these plugins to enable Help on top of the plugins which you already have. They only need to be in the target platform. Below are the plugins which you need to have in your target platform. Make sure to add them to your product as well.
javax.servlet
javax.servlet.jsp
org.apache.commons.el
org.apache.commons.logging
org.apache.jasper
org.apache.lucene
org.apache.lucene.analysis
org.eclipse.equinox.http.jetty
org.eclipse.equinox.http.registry
org.eclipse.equinox.http.servlet
org.eclipse.equinox.jsp.jasper
org.eclipse.equinox.jsp.jasper.registry
org.eclipse.help
org.eclipse.help.ui
org.eclipse.help.webapp
org.eclipse.help.base
org.eclipse.osgi.services
org.mortbay.jetty
Note: You no longer need org.eclipse.help.appserver and org.eclipse.tomcat plugins in your target platform in 3.4 (Ganymede).
javax.servlet
javax.servlet.jsp
org.apache.commons.el
org.apache.commons.logging
org.apache.jasper
org.apache.lucene
org.apache.lucene.analysis
org.eclipse.equinox.http.jetty
org.eclipse.equinox.http.registry
org.eclipse.equinox.http.servlet
org.eclipse.equinox.jsp.jasper
org.eclipse.equinox.jsp.jasper.registry
org.eclipse.help
org.eclipse.help.ui
org.eclipse.help.webapp
org.eclipse.help.base
org.eclipse.osgi.services
org.mortbay.jetty
Note: You no longer need org.eclipse.help.appserver and org.eclipse.tomcat plugins in your target platform in 3.4 (Ganymede).
Subscribe to:
Posts (Atom)