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.

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.

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

Friday, April 17, 2009

Integrating Eclipse Logging with log4j

Introduction:

As you may know, Eclipse uses it's own logging API, which is different than log4j. All the eclipse core plugins use Eclipse logging. What can you do if you have dependencies which are already using log4j? Should you use Eclipse logging or log4j in your plugin? This article deals about the options available and how to integrate both of them.

Options:

RCP logging in Eclipse and dependencies in Log4j:

Have your plugins write to Eclipse log and maintain two different log files. i.e one from log4j and one (.log) from Eclipse. One problem with writing to Eclipse log is there is no external config file with which users can control the levels of logging and the classes/packages for which they want to see the log. Also, this also means that only Eclipse logs will show up inside your application (in Error Log view).

Third party plugins to see log4j:

While the above option does not look good, the good news is there are some eclipse plugins available here and they would show the log view inside eclipse. So, if your customers ever want to see the other logs inside the application, then you can use this.

Ultimate destination is log4j:

If you want your ultimate destination to be log4j, then you can go with this option. You can attach listeners to eclipse ILOG (see Platform.addLogListener(ILogListener)) and then your listener can then convert and write to your log4j. This is one way to get all the logging in to place i.e to your log4j, since your dependencies are already writing to log4j. Once you have this, you can use third party plugins (described above) to show all the logs in one view.

Ultimate destination is Eclipse Log:

If you want your ultimate destination to be Eclipse log, this is harder to achieve. I don't know of any option to convert your dependecy project's logs to Eclipse log. But, if you plugin is writing to log4j, then you can use the plugin (or just the idea) presented in this article. This plugin uses custom appenders and hierarchy event listeners and converts the log4j log to Eclipse Log. This is a great option to use if you do not have dependency plugins which write to log4j. Also, you can precisely control the log levels at run-time using this option.

Conclusion:

We saw several options to use and combine eclipse logging and log4j. You can also have your ultimate destination to be both log4j and Eclipse log by combining the above options presented.

Wednesday, April 15, 2009

JavaRebel - JVM HotSwap plugin

I recently encountered this and find it very useful in my UI development. I use the eclipse debugger not only for debugging but also to make changes to my code without restarting my application. But, the debugger uses JVM HotSwap and is very limited since it only allows to make changes to my method i.e I can add/change contents of the method, but if I remove a method, remove a constructor etc., then it fails to reload properly. JavaRebel comes out great in these areas. It is basically a JVM plugin (javaagent) which enables you to see changes to the code immediately without restarting the application.

Using JavaRebel

They offer a free trial (which is what I'm using now). Go here to download the zip file. Just unzip the file and add the following vm arguments to your application.

-noverify -javaagent:your/path/to/javarebel.jar

It is basically an advanced version of JVM HotSwap plugin. You can see the comparison matrix here in their web-site.

When you start the application, it prints info about JavaRebel and also whenever you make changes to the code, it also prints which classes are loaded dynamically. Right now, I'm using this for eclipse plugin development, but this can be used for other purposes like creating a web-application etc.,

Tuesday, April 14, 2009

Creating RCP Cheat Sheets (Eclipse 3.4 - Ganymede)

Cheat sheets guide the user through a series of steps to achieve a specific goal in the application. This is more like a hands-on tutorial and would be very useful for new users of the application. In this article, we will explore how to create cheats for your application.

Adding cheat sheet to the help menu:
  1. Add org.eclipse.ui.cheatSheets as dependency for the plugin.
  2. Add org.eclipse.ui.actionSets extension point
  3. Under this add a new action set for cheat sheets and give it the id org.eclipse.ui.cheatsheets.actionSet
  4. Create an action under the action set and give it the id org.eclipse.ui.cheatsheets.actions.CheatSheetHelpMenuAction. Give it a menubarpath (id of the help menu\path), label and style.
Now when you run your plugin, you can see Help->Cheat sheets menu. When you click on it, you can see the "Cheat Sheet Selection" dialog, but the contents would be empty since we haven't added the cheat sheet content yet.

Creating Cheat Sheet:
  1. Add org.eclipse.ui.cheatsheets.cheatSheetContent extension point.
  2. Go to File->New->Other->User Assistance->Cheat Sheet
  3. Select the folder where you want to create the cheat sheet, type in the file name and choose whether you want to create a simple or composite cheat sheet. We will create a simple cheat sheet in this article.
  4. When the editor comes up, it would just have Title and Introduction. Replace the defaults with your own title and introduction.
  5. Click on Register Cheat Sheet in the top right to register the cheat sheet before adding contents to it.
  6. In Register cheat sheet dialog, set the category (Optional - but highly recommended so that they are grouped properly in the "Cheat Sheet Selection" dialog) and enter description. This basically adds your cheat sheet under the cheatSheetContent in plugin.xml.
  7. After this you can add how many number of steps you want and each step can have many sub-steps under it.
  8. Apart from the instructions, we can also add links to the help content by using Help section in the cheat sheet editor.
  9. One of the main advantages in cheat sheet is user can either do the step manually and click on "Click when complete" or click on "Click to perform" to automatically perform the action. Click to perform can either be achieved by adding an action or by adding a command to the step.
  10. To add the command click on Command->Browse in cheat sheet editor and select the appropriate command. If required, enter the parameter to that command.
  11. We can also add an action to a step, but that is not currently supported by the cheat sheet editor. We can go directly to the xml and add the action though. An example of an action looks like this below: <action
    required="false"
    pluginId="com.xyz.myplugin"
    class="com.xyz.myplugin.actions.MyAction>

    Here we specify the action class and the plugin id it lives in. Required = "false" attribute displays the "Click when complete" link. If required is true, then it means user has to use the click to perform and so click when complete will not be displayed.
Advanced Options through XML:

Some of the advanced options are not available via the cheat sheet editor. We can add them directly to the XML since cheat sheet is persisted in XML anyway. Click on the source tab in cheat sheet editor to open the XML.
  1. In the XML, each step is represented by an item and each sub-step is represented by a sub-item.
  2. If a particular step involves bringing up a modal dialog, you can add dialog=true attribute in the item element in XML and that will make sure to bring the cheat sheet view along with the dialog so that cheat sheet view can be operated while in a modal dialog.
  3. We already saw action above. An item can also have perform-when, onCompletion, conditional-subitem and repeated-subitem elements. You can guess what they are used for from the name. Sample XML for some of these are shown below:
<item ...>
<conditional-subitem condition="${v1}">
<subitem when="x" label="Proceed to x" />
<subitem when="y" label="Proceed to y" />
<conditional-subitem>
</item>
The above code picks the sub-step based on the value of variable v1.
<item ...>
<subitem label="Sub step">
<perform-when condition="${v1}">
<action when="a" class="com.xyz.action1"
pluginId=
"com.xyz" />
<action when="b" class="com.xyz.action2"
pluginId=
"com.xyz" />
</perform-when>
</subitem>
</item>

The above code picks the action for the sub-step based on
the value of variable v1.

Conclusion:

We saw how to add cheat sheet to the menu and contribute
cheat sheet content. We also saw how to add the content
directly through the editor or using the XML. I hope this
article helps you to create cheat sheets in your
application.

References:
  1. Building Commercial-Quality Plug-ins by Eric Clayberg and Dan Rubel.
  2. All the articles on cheat sheets in my delicious bookmarks:http://delicious.com/search?context=userposts&p=cheat%20sheet&lc=1&u=rajak27

Creating, Loading and Using Eclipse RCP Extension Points (Eclipse 3.4 - Ganymede)

As you know, eclipse platform by itself is very small and most of the functionality is provided by surrounding plugins. These plugins interact via extension points. Extension points allow the user to extend/customize the functionality of other plugins. Everything in the platform, for example views, editors, perspectives, preferences etc., use extension points.

It is easy to use the extension points defined by other plugins. It is slightly more complex to define our own extension points so that it is consumed by other plugins. This article primarily focuses on creating new points.

Creating New Extension Points:

The assumption is you have a plugin project working and you need to create a new extension point. Open plugin.xml and click on the "Extension Points" tab. Extensions tab is where we use extension points defined by other plugins. Extension Points tab is where we create our own extension points.

Click on "Add" to create a new extension point. Fill out the extension point id and name. The schema is auto-created in location schema\id. You can change it if you want. Once you click ok, the extension point schema editor will be open. You can also open it at a later time by double clicking on the schema file (.exsd) in explorer.

Overview tab:

This tab defines the basics - plugin id, point id, name and documentation. The plugin id is concatenated with the extension id by Eclipse to create a unique identifier. Documentation is very helpful for consumers using the extension points. They are used by the Extensions editor while client plugins use the extension points.

Extension Point Schema Definition:

This is where we actually specify the schema of the extension point.
  1. Create a new element by clicking on the New Element.
  2. Create new attributes for that element using New Attribute.
  3. The attributes can be of any type - boolean, string, java etc., java type is used to define the java super class or interface, which clients have to extend or implement. You can also use both. The best practice is to use both so that if the interface changes (addition of a new method etc.,) we can add a default implementation in the abstract super class without breaking the client code.
  4. Once the elements and it's attributes are defined, the next step is to add it to the extension. Add a new sequence or choice to extension (by right clicking on extension node).
  5. You can add the element(s) underneath the sequence/choice and specify the min/max occurences.

Source tab:

You can view the actual XML schema for the extension point in this tab.

Exporting the necessary classes:

Once the schema is defined, make sure to export the abstract class/interface to client plugins if your extension point defines one. You can do this by going to plugin.xml->Runtime tab and under exported packages make sure to add the package which has this class/interface defined.

The next step is to load all the extension point implementations from the plugins available in the target platform. Before that, let's see how to use this extension point from a client plugin.

Using the Extension Point:

  1. Open the client plugin's plugin.xml
  2. Go to "Dependencies" tab. Add the main/base plugin (which defines the extension point) as it's dependency.
  3. Go to "Extensions" tab and click on "Add" to add the extension point.
  4. Now, right click on the extension point to add it's children/attributes and fill in the details in "Extension details".
  5. The extensions editor gives the options based on the extension point schema. You can also view the extension point schema and description from here.
  6. If the extension point needs a class implementation, you need to create the class and put the class name in Extension Details". You can click on the class attribute name and that will also open the "New class dialog" with relevant details filled in.
Loading the extension points:

Now, let's see how load all the extension points:

try {
IConfigurationElement[] config =
Platform.getExtensionRegistry().getConfigurationElementsFor(
IGREETER_ID); // 1
for (IConfigurationElement e : config) { // 2
Object o = e.createExecutableExtension("class"); // 3
if (o instanceof IGreeter) {
((IGreeter) o).greet(); // 4
}
}
}
catch (Exception ex) {
System.out.println(ex.getMessage());
}

Eclipse platform maintains a registry of all extension points available in the target platform. Point 1 loads all the extension points named IGREETER_ID from the platform registry. Point 2 iterates through those configs. Point 3 is the main thing. It looks for "class" attribute in the config element and loads that class. In this case, it is the implementation of interface (which this extension point requires clients to implement). You can also use getChildren and getAttribute methods to query the configuration element. Once thing to note here is that, we could not just do Class.forName here. We need to use this method to instantiate the objects. Once the object is created, we can call methods in the interface to do the operation.

Lazy Initialization:

One main thing to note here is, in the above code, I've instantiated the class immediately. I've done that only to show how to do it. But, it is a BAD practice to load the implementations until it is really needed since it loads the corresponding client plugin. Imagine, if all plugins did it this way at start-up, then it would take forever to start the application. Often we need info about the extension points, but we don't want to really load the plugin for that.

For example, a typical use case is "Show views" or "Show perspectives" or in our custom case maybe "Show available implementors". If we have to show this to a user, we don't need to process all of the plugins. Instead we could just query the registry and get all the configuration elements. From the configuration elements, we could get all the information defined by the client in their plugin.xml. A good way is to use the proxy pattern. Store the config element in a proxy object and query the attributes/elements to decorate the UI. Then, when user clicks on a specific implementation and when it is time to do the action call the execute method on the proxy object which will do this:

this.configElement_.createExecutableExtension("class")

So, we actually load the client lazily only when it is needed.

Conclusion:

We saw how to create an extension point, how to use it from a client plugin's perspective, loading available extension points and how to lazily load them. I hope this article helps you in working with extension points.

References:
  1. Building Commercial-Quality Plug-ins by Eric Clayberg and Dan Rubel.
  2. All the articles on extension points in my delicious bookmarks: http://delicious.com/search?context=userposts&p=extension&lc=1&u=rajak27