Context Help in Eclipse revisited

Earlier this week Paul (who by the way took over the PHP class generator and revived it) asked me for help with help. Basically he had a problem of integrating context sensitive help into the plug-in. He read my earlier article on this topic but could not manage. Therefore I promise to have a look.
I must say this topic is not trivial and not entirely implemented in an obvious fashion. Further more my earlier article leaves some points a bit unclear. Therefore I stumbled a couple of times doing the implementation (not having reread my article). Therefore here we go in a second attempt. In implementing this I had the advantage of a project (Code Generator) I could use for „cheating“.
The aim is to add a context help for the file name input field of the PHP DB class generator on the first wizard page.

Prerequirements

or the first step

For the context help we need the plugin org.eclipse.help which we add to the manifest in the section Required-Bundle.
Now we can add the extension point for the context help in the plugin.xml:

<extension point="org.eclipse.help.contexts">
     <contexts file="contextHelp.xml" plugin="ch.sahits.phpclassgenerator"/>
     <contextProvider class="org.eclipse.help.internal.context.ContextFileProvider"/>
</extension>

In the element contexts the attribute plugin defines the plug-in where the help contents is provided. I do not think that any other value than the plug-in ID of the current plug-in makes any sense. The attribute file defines an XML file that holds the context help information. The contextProvider is the class that is invoked to retrieve the help context.

Providing the contents

or the creative work

As mentioned in the above section we need a file named contextHelp.xml
that defines our context help. This XML file can be viewed as table of contents. It defines the different contexts under which help is available and defines URLs to HTML pages that actually contain the help contents. The contextHelp.xml file is located in the root of the project, alongside the plugin.xml:

<?xml version="1.0" encoding="UTF-8"?>
<contexts>
	<context id="filename" title="File name">
		<decription>Help for file name input field</decription>
		<topic href="html/context/filename.html" label="File name"/>
	</context>
</contexts>

The XML can define several contexts that are all defined under the contexts root element. In this example there is only one. The  title attribute comes in handy when there are several topics per context ID. Then all topics are listed as links under the given title. The id attribute is the most important one. It is the unique identification of the context per plugin. Basically this means that the id must be unique within this XML. The description element is displayed when several topics are available as a short explanation. Finally there is the topic element that defines the URL of the help contents that is to be displayed. The label is used as title if there are multiple topics defined for one context ID. If there is only one topic the contents of the HTML file is displayed directly and the title, description and label are obsolete.

I suppose that the structure of this XML is inherently linked to the context provider that is defined in the extension point of the plugin.xml.

In the HTML file you can provide anything as long as it is helpful in the context. The HTML file has to be located at the designated location relative to the projects root:

<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<title>File name input field</title>
</head>
<body>
<h1>File name</h1>
<p>What is the filename</p>
<h1>Input field</h1>
<p>Which inputfield</p>
</body>
</html>

Calling the context help

or stitching all together

The most simple way to call the context help is to provide a button that calls the help that is appropriate for the context. There are multiple ways. The most simple one is to provide a button for each context. O you can have one button and figuring out the correct context when clicking it. Furthermore there is the context help button on each wizard page that can be used. This however is another story. I will add add a button to the input field that opens the context help.
For the simplicity let’s create a helper method that creates a help button into a parent container without any functionality:

/**
 * Create a help button with the help icon on it.
 * No action is associated with the button
 * @param container parent container
 * @return created button
 */
private Button createHelpButton(Composite container) {
	Button help = new Button(container,SWT.PUSH);
	Image img = PlatformUI.getWorkbench().getSharedImages().getImage(ISharedImages.IMG_LCL_LINKTO_HELP);
	help.setImage(img);
	return help;
}

The neat thing about this method is that the created button displays Eclipse’s standard help icon. Now we can create our button and add the functionality to call the appropriate context:

final String ECLIPSE_HELP = "org.eclipse.ui.help";
Button help = createHelpButton(container);
help.addSelectionListener(new SelectionAdapter() {
	/**
	 * Open the context help for the file name input field
	 */
	@Override
	public void widgetSelected(SelectionEvent e) {
		String contextHelpID = "ch.sahits.phpclassgenerator.filename";
		getShell().setData(ECLIPSE_HELP,contextHelpID);
		PlatformUI.getWorkbench().getHelpSystem().displayHelp(contextHelpID);
	}
});

The most important part is the definition of the String contextHelpID. It is composed of the plug-in ID of the plug-in that provides the context help, (this might be a different plug-in than the one from where the button is defined) and the ID as it is defined in the contextHelp.xml. Anything other just simply will not work as I had to find out. The cause for this lies in the class org.eclipse.help.internal.context.ContextManager which retrieves the instance of the context:

	/*
	 * Returns the Context for the given id and locale.
	 */
	public IContext getContext(String contextId, String locale) {

		if (HelpPlugin.DEBUG_CONTEXT  && contextId != null) {
			System.out.println("ContextManager.getContext(\"" + contextId + "\")"); //$NON-NLS-1$ //$NON-NLS-2$
		}

		// ask the providers
		int index = contextId.lastIndexOf('.');
		if (index != -1) {
			String pluginId = contextId.substring(0, index);
			Iterator iter = getContextProviders(pluginId).iterator();
			while (iter.hasNext()) {
				AbstractContextProvider provider = (AbstractContextProvider)iter.next();
				try {
					IContext context = provider.getContext(contextId, locale);
					if (context != null) {
						if (HelpPlugin.DEBUG_CONTEXT) {
							System.out.println("ContextManager.getContext found context, description = \"" + context.getText() + '"'); //$NON-NLS-1$
						}
						return new Context(context, contextId);
					}
				}
				catch (Throwable t) {
					// log and skip
					String msg = "Error querying context provider (" + provider.getClass().getName() + ") with context Id: " + contextId; //$NON-NLS-1$ //$NON-NLS-2$
					HelpPlugin.logError(msg, t);
				}
			}
		}
                // snipped out some none relevant code
                return null;
	}

Schreibe einen Kommentar