Chapter 25. JavaHelp

In this chapter:

25.1 JavaHelp introduction

Most software developers do not look forward to spending time documenting and explaining their product. However, the commercial success of any modern software project greatly depends on the quality and availability of the help information provided to end users. As Java apps evolve into more extensive and sophisticated solutions, they require a robust, uniform, and effective mechanism for providing help to these users. For this reason JavaSoft has developed the JavaHelp API: a full-featured, platform-independent, extensible help system that gives developers and authors the ability to efficiently incorporate an online help system into applications (both network and stand-alone), applets, JavaBean components, HTML pages, operating systems, and devices. The 1.0 release of this API is available for download from the JavaSoft web site (see http://java.sun.com/products/javahelp/).

 

Note: You will need to download and install the JavaHelp API before running the examples in this chapter.

JavaHelp includes a standard Swing-based help viewer illustrated in figure 25.1 below. This viewer contains a toolbar in its northern region and a split pane in the center. The toolbar contains two buttons allowing navigation between help topics that have been visited previously in a forward/backward style. The left split pane component is a tabbed pane containing a table of contents, help index, and search navigation facilities. The right split pane component is an editor pane used to display the content of the currently selected help topic. Help content is expected to be in the form of HTML documents, enabling rich display capabilities and greatly simplifying the delivery of help files across the Internet.

 

Note: JavaHelp also provides a way to deliver multi-lingual help depending on the currently selected locale. This capability, along with the more comprehensive internationalization support provided by Java 2, is very powerful, currently under major development, and lie beyond the scope if this text.

To use JavaHelp in an application we need to prepare a set of files described below. With the exception of content files and search files, each file is expected to be in the XML format. (In describing each XML-based file below, we present a simple example implementation which we will use in the section 25.3 application.)

 

Reference: For more information about XML see http://www.w3.org/TR/WD-xml-lang-970331.html. It is likely that in the near future, we will have graphical tools available to facilitate quick and easy construction of these system files. However, at this point we are forced to delve into some of the details of XML coding to accomplish this.

 

25.1.1 HelpSet file

This system file uses the XML format to deliver information about other help components and files. XML tags used in this file have the following meaning:

<helpset>: top level of a HelpSet document. The <version> and <lang> attributes can specify file version and language respectively.

<title>: title to be displayed in the help viewer’s title bar.

<maps>: defines a section representing a HelpSet map (discussed below).

<homeID>: ID of the default help topic, which should be listed in the map file.

<mapref>: specifies a map file or URL with its <location> attribute.

<view>: defines a section representing a help view contained in the help viewer tabbed pane. A HelpSet file may contain an arbitrary number of views.

<name>: a name identifying the view.

<label>: used for displaying the tooltip text of the view’s corresponding tabbed pane tab (more descriptive than a name).

<type>: the view type (fully qualified name of the class to be instantiated to create this view component). Normally a subclass of NavigatorView (see below).

<data>: specifies a data file or URL used in this view. The optional <engine> attribute can be used to define the fully qualified name of the class defining a search engine.

The following is a typical example of a HelpSet file, Layout.hs:

<?xml version='1.0' encoding='ISO-8859-1' ?>

<helpset version="1.0">

<title>Basic Layout Example - Help</title>

<maps>

<homeID>top</homeID>

<mapref location="Layout.jhm"/>

</maps>

<view>

<name>TOC</name>

<label>Table Of Contents</label>

<type>javax.help.TOCView</type>

<data>LayoutTOC.xml</data>

</view>

<view>

<name>Index</name>

<label>Help Index</label>

<type>javax.help.IndexView</type>

<data>LayoutIndex.xml</data>

</view>

<view>

<name>Search</name>

<label>Search</label>

<type>javax.help.SearchView</type>

<data>JavaHelpSearch</data>

</view>

</helpset>

25.1.2 Map file

This system file uses the XML format to provide a mapping of help topic IDs (text strings) to URLs (which can be either local or remote and of the form file:, http:, ftp:, or jar:). Multiple map files can be used in a HelpSet. In such a case the mappings will be merged and we are expected to always provide unique IDs because of this. XML tags used in this file have the following meaning:

<map>: top level of the map document. The <version> and <lang> attributes can specify file version and language respectively.

<mapID>: represents a single map entry. The <target> attribute specifies the string ID of the help topic and the <url> attribute specifies the associated URL of the HTML content file constituting its documentation. The optional <lang> attribute allows specification of a language to use when displaying this topic.

The following is a typical example of a map file, Layout.jhm:

<?xml version='1.0' encoding='ISO-8859-1' ?>

<map version="1.0">

<mapID target="top" url="LayoutFrame.html" />

<mapID target="FlowLayout" url="FlowLayout.html" />

<mapID target="GridLayout" url="GridLayout.html" />

<mapID target="BorderLayout" url="BorderLayout.html" />

<mapID target="BoxLayout" url="BoxLayout.html" />

</map>

25.1.3 Table of Contents (TOC) file

This system file uses the XML format to deliver information about a help topics table of contents. This represents a view that typically contains references to help topic IDs in a logically grouped order. XML tags used in this file have the following meaning:

<toc>: top level of TOC document. The <version> and <lang> attributes can specify file version and language respectively.

<tocitem>: defines a TOC entry. The <target> attribute specifies the string ID of the target help item listed in a map file. The <text> attribute specifies the TOC view text to represent the corresponding help topic.

The following is an example of a TOC file, LayoutTOC.xml:

<?xml version='1.0' encoding='ISO-8859-1' ?>

<toc version="1.0">

<tocitem>Basic Layout Example - TOC

<tocitem text="Frame" target="top"/>

<tocitem text="FlowLayout" target="FlowLayout"/>

<tocitem text="GridLayout" target="GridLayout"/>

<tocitem text="BorderLayout" target="BorderLayout"/>

<tocitem text="BoxLayout" target="BoxLayout"/>

</tocitem>

</toc>

25.1.4 Index file

This system file uses the XML format to deliver information representing a help index view. This view is very similar to a TOC view described above, but typically organizes references to help topics in alphabetical order. XML tags used in this file have the following meaning:

<index>: top level of a help index document. The <version> and <lang> attributes can specify file version and language respectively.

<indexitem>: defines index entry. Attribute <target> contains string ID of the target help item listed in the map file. Attribute <text> contains text to be displayed in the index.

Here's an example of the index file, LayoutIndex.xml:

<?xml version='1.0' encoding='ISO-8859-1' ?>

<index version="1.0">

<indexitem text="Index">

<indexitem text="B">

<indexitem target="BorderLayout" text="BorderLayout"/>

<indexitem target="BoxLayout" text="BoxLayout"/>

</indexitem>

<indexitem text="F">

<indexitem target="FlowLayout" text="FlowLayout"/>

<indexitem target="top" text="Frame"/>

</indexitem>

<indexitem text="G">

<indexitem target="GridLayout" text="GridLayout"/>

</indexitem>

</indexitem>

</index>

25.1.5 Help Content files

JavaHelp uses the HTML format for data files making up the actual help topic content. We can place help topics in one or more HTML files using typical fonts, images, and links to create a robust and efficient help system. We can also embed JComponents using the <OBJECT> tag (see chapter 19 for more about Swing’s HTML support).

25.1.6 Search files

JavaHelp supports access to search engines, and provides a default search engine of its own: com.sun.java.help.search.DefaultSearchEngine. Search engines used to perform searches are subclasses of javax.help.search.SearchEngine (as is DefaultSearchEngine). A JavaHelp search engine uses a search database residing in the directory specified by the <data> tag of the search view section in the HelpSet file (see above). This directory contains necessary search data pre-generated based on given content. This data is searched using a specified engine pointed at by the <data> tag’s engine attribute (if none is specified the default is used).

To generate binary files constituting the necessary search data, we first create content files for our help system (usually HTML), change to the directory containing these files, and run the following command listing each of our help content files as parameters:

com.sun.java.help.search.Indexer file1 file2 ...

This generates several files and places them in the default "JavaHelpIndex" sub-directory (see the JavaHelp documentation for more options available in generating search data).

The JavaHelp search mechanism can be used to support client-side searching, server-side searching, and stand-alone seaching. As with the whole JavaHelp system, we are provided with very flexible choices in both the presentation and deployment of this information.

 

25.2 JavaHelp API overview

25.2.1 HelpSet

class javax.help.HelpSet

This class represents a collection of help information files we discussed above: HelpSet, table of contents (TOC), index, content, and map files. To create a HelpSet instance we must supply two parameters: a ClassLoader instance used to locate any classes required by the navigators in the help set, and the URL of the main HelpSet file. The following code shows a typical HelpSet instantiation, assuming the main HelpSet file, MyHelpSet.hs, is contained in the current running directory:

ClassLoader loader = this.getClass().getClassLoader();

URL url = HelpSet.findHelpSet(loader, "MyHelpSet.hs");

HelpSet hs = new HelpSet(loader, url);

 

25.2.2 The HelpBroker interface

abstract interface javax.help.HelpBroker

This interface describes a component used to manage the presentation and interaction with a HelpSet. We can use the HelpSet.createHelpBroker() method to retrieve an instance of HelpBroker. We can then use HelpBroker’s enableHelpKey() method, specifying the String ID of a desired help topic, to enable JavaHelp on a given Component. This is often used on a JRootPane (see chapter 3):

HelpBroker hb = hs.createHelpBroker();

hb.enableHelpKey(myFrame.getRootPane(), "MyTopicID", hs);

When F1 is pressed while that component, or one of its children, has the focus, the JavaHelp viewer will appear displaying the appropriate help topic. If a child component has the focus and has an associated help ID assigned, the help viewer will display the help topic specific to that component. Otherwise the default topic (specified as "MyTopicID" above) will be displayed.

We can also use the enableHelpOnButton() method to enable the display of JavaHelp when a specific button is pressed:

JButton btHelp = new JButton("Help");

hb.enableHelpOnButton(btHelp, "MyTopicID", null);

A default implementation is provided by the javax.help.DefaultHelpBroker class.

25.2.3 The Map interface

abstract interface javax.help.Map

This interface defines a String ID to URL mapping. The inner class Map.ID is used to identify a help topic. It encapsulates a HelpSet reference / String ID pair. Given an ID we can get the associated URL and vice versa. Each HelpSet has an associated Map instance.

25.2.4 JHelp

class javax.help.JHelp

This class represents the main JavaHelp viewer capable of displaying help set data using JTree or JList navigators in a JTabbedPane, and a content viewer (normally a JEditorPane for HTML display). The current implementation of JavaHelp does not provide public access to this class via HelpSet or HelpBroker instances. Later in this chapter we'll see how to gain access to this component for customization.

25.2.5 The HelpModel interface

abstract interface javax.help.HelpModel

This interface represents the model of a JHelp viewer component. It maintains IDs and URLs corresponding to the HelpSet being displayed in a JHelp instance, and fires HelpModelEvents (see below) when the current help topic is changed.

25.2.6 JHelpNavigator

class javax.help.JHelpNavigator

This class represents a navigation control (JTree or JList) for the GUI presentation of help topic data. Instances of JHelpNavigator reside in the tabbed pane on the left side of the JHelp viewer. Any number of navigators can be present in a HelpSet. JavaHelp provides three sub-classes of JHelpNavigator used in JHelp by default: JHelpIndexNavigator, JHelpSearchNavigator, and JHelpTOCNavigator. Each JHelpNavigator displays data retrieved through a specific NavigatorView instance.

25.2.7 NavigatorView

abstract class javax.help.NavigatorView

Instances of NaviagtorView define what type of data a view accepts and how it is parsed. They aso define how data will be presented visually by specifying a corresponding component used to view it. JavaHelp provides three sub-classes of NavigatorView used in JHelp by default: IndexView, SeachView, and TOCView--each of which specify the corresponding JHelpNavigator subclass discussed above for representation.

25.2.8 CSH

class javax.help.CSH

This class provides simple access to context-sensitive help by supplying several static methods. Context-sensitive help is the provision of certain help information based on the user’s current task. Implementing this type of help often involves associating help topics with each component in a GUI and tracking context-sensitive events. Particularly important is the CSH.setHelpIDString(JComponent comp, String helpID) method, which assigns a String ID to a given Swing component. When all IDs are assigned, we can create a button or menu item to trigger content sensitive help, and attach a special predefined ActionListener:

JButton btItemHelp = new JButton("Item Help");

btItemHelp.addActionListener(new CSH.DisplayHelpAfterTracking(hb));

When this button is pressed, a custom mouse cursor displaying a pointer and a question mark appears. When a component is clicked the JavaHelp viewer is displayed showing the help topic corresponding to the selected component’s assigned ID (if it has one).

25.2.9 TreeItem

class javax.help.TreeItem

Instances of this class are used as user objects in a navigational view’s JTree or JList. Subclasses of TreeItem include IndexItem, used in the Index navigator, and TOCItem, used in the TOC navigator. A SearchTOCItem class extends TOCItem and is used in the Search navigator.

25.2.9 The HelpModelListener interface

abstract interface javax.help.event.HelpModelListener

This interface describes a listener which receives javax.help.event.HelpModelEvents when the current help topic is changed (i.e. whenever a new topic is selected in a navigator). HelpModelListener implementations must implement the idChanged() method which takes a HelpModelEvent as parameter. We can add a HelpModelListeners to a HelpModel using its addHelpModelListener() method.

25.2.10 Setting up JavaHelp

To run all examples in this chapter you will need to do the following (this will vary depending on your platform):

1. Download and install JavaHelp 1.0 (or the most recent release).

2. Copy all files in your javahelp\bin directory to your jdk1.2\bin directory.

3. Copy the contents of your javahelp\lib directory to your jdk1.2\lib directory.

4. Each example includes Windows batch files set up with the assumption that you have installed Java 2, and the above files, in c:\jdk1.2. If you are not using Windows or do not have Java 2 installed in this directory, you will need to write your own scripts or modify these batch files accordingly.

 

Note: The JavaHelp API now exists as a Java extension. In future Java 2 releases it is possible that JavaHelp will become part of the core API.

 

25.3 Basic JavaHelp example

In this section we will add help capabilities to the introductory layout example from chapter 4, section 4.2, demonstrating five JInternalFrames each using a different layout manager. Each of these internal frames will receive a help topic placed in a separate HTML file. The HelpSet, map, TOC, and index files for this example were listed in section 25.1, and are used to create a HelpSet instance. We will assign String IDs to each internal frame using the CSH class and attach an InternalFrameListener to transfer focus to a the first component in a selected frame’s! content pane (because focus does not get transferred by default when a new internal frame is selected). By using a our HelpSet’s HelpBroker we will then enable help on each frame, which can be invoked through the F1 key.

Figure 25.1 JavaHelp help viewer displaying the TOC view.

<<file figure25-1.gif>>

The Code: CommonLayoutsDemo.java

see \Chapter25\1

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import java.net.*;

import javax.swing.*;
import javax.swing.border.*;
import javax.swing.event.*;
import javax.help.*;

public class CommonLayoutsDemo extends JFrame 
{
  protected HelpSet m_hs;
  protected HelpBroker m_hb;
  static final String HELPSETNAME = "Help/Layout.hs";

  public CommonLayoutsDemo() {
    super("Layout Managers [Help]");
    setSize(500, 380);

    createHelp();
    InternalFrameAdapter activator = new InternalFrameAdapter() {
      public void internalFrameActivated(InternalFrameEvent e) {
        JInternalFrame fr = (JInternalFrame)e.getSource();
        Component c = fr.getContentPane().getComponent(0);
        if (c != null)
          c.requestFocus();
      }
    };

    JDesktopPane desktop = new JDesktopPane();
    getContentPane().add(desktop);

    JInternalFrame fr1 = 
      new JInternalFrame("FlowLayout", true, true);
    // Unchanged code from section 4.2
    CSH.setHelpIDString(fr1,"FlowLayout");
    fr1.addInternalFrameListener(activator);

    JInternalFrame fr2 = 
      new JInternalFrame("GridLayout", true, true);
    // Unchanged code from section 4.2
    CSH.setHelpIDString(fr2,"GridLayout");
    fr2.addInternalFrameListener(activator);

    JInternalFrame fr3 = 
      new JInternalFrame("BorderLayout", true, true);
    // Unchanged code from section 4.2
    CSH.setHelpIDString(fr3,"BorderLayout");
    fr3.addInternalFrameListener(activator);

    JInternalFrame fr4 = 
      new JInternalFrame("BoxLayout - X", true, true);
    // Unchanged code from section 4.2
    CSH.setHelpIDString(fr4,"BoxLayout");
    fr4.addInternalFrameListener(activator);

    JInternalFrame fr5 = 
      new JInternalFrame("BoxLayout - Y", true, true);
    // Unchanged code from section 4.2
    CSH.setHelpIDString(fr5,"BoxLayout");
    fr5.addInternalFrameListener(activator);
        
    // Unchanged code from section 4.2
  }

  public static void main(String argv[]) {
    new CommonLayoutsDemo ();
  }

  protected void createHelp() {
    ClassLoader loader = this.getClass().getClassLoader();
    URL url;
    try {
      url = HelpSet.findHelpSet(loader, HELPSETNAME);
      m_hs = new HelpSet(loader, url);
      m_hb = m_hs.createHelpBroker();
      m_hb.enableHelpKey(getRootPane(), "Frame", m_hs);
    } 
    catch (Exception ex) { ex.printStackTrace(); }
  }
}

Understanding the Code

Class CommonLayoutsDemo

New instance variables:

HelpSet m_hs: used to manage the help data for this application.

HelpBroker m_hb: used to manage the help viewer for this application.

New class variable:

String HELPSETNAME: the path and name of the HelpSet file relative to the location of this class.

The CommonLayoutsDemo constructor calls our custom createHelp() method to initialize JavaHelp functionality (see below). Each JInternalFrame is associated with a proper help ID using the CSH.setHelpIDString() method.

An InternalFrameAdapter (implementation of InternalFrameListener--see chapter 16) is used to detect whenever an internal frame is acivated, and transfer focus to the first component within its content pane. This is done because even though JInternalFrame can be activated by a mouse click over it's surface, this does not necessarily transfer focus to it. The focus may still belong to the component in the previously active JInternalFrame. This causes undesirable effects with JavaHelp because in this case we need to show a help topic based on the currently selected frame. By manually transfering focus to a child of the active JInterna! lFrame the correct help topic will be displayed when help is invoked. Thus, we add an instance of this listener to each internal frame.

The createHelp() method retrieves the URL corresponding to the help set for this application, and instantiates our HelpSet and HelpBroker instance variables. The enableHelpKey() method enables help functionality using the F1 key, and sets the default topic ID to "Frame" on the application’s JRootPane.

Running the Code

Activate one of the internal frames and press F1. Figure 25.1 shows the JavaHelp help viewer displaying the help topic associated with the currently active internal frame. Explore and become familiar with the navigation functionality provided by the help viewer.

25.4 Adding dialog-style help

Dialogs in modern applications quite often contain a "Help" button to invoke content-sensitive help for a particular component in that dialog (usually using an altered question mark cursor). In this section we'll show how to add such a feature provided by JavaHelp to the FTP client example we constructed in chapter 13.

Figure 25.2 FTP Client application with help buttons for default and context-sensitive help.

<<file figure25-2.gif>>

Figure 25.3 JavaHelp viewer displaying custom help for our FTP client application.

<<file figure25-3.gif>>

HelpSet file: FTP.hs

see \Chapter25\2\help

<?xml version='1.0' encoding='ISO-8859-1' ?>

<helpset>

<title>FTP Client Example - Help</title>

<maps>

<homeID>top</homeID>

<mapref location="FTP.jhm"/>

</maps>

<view>

<name>TOC</name>

<label>Table Of Contents</label>

<type>javax.help.TOCView</type>

<data>FTPTOC.xml</data>

</view>

<view>

<name>Index</name>

<label>Help Index</label>

<type>javax.help.IndexView</type>

<data>FTPIndex.xml</data>

</view>

<view>

<name>Search</name>

<label>Search</label>

<type>javax.help.SearchView</type>

<data>JavaHelpSearch</data>

</view>

</helpset>

Map file: FTP.jhm

see \Chapter25\2\help

<?xml version='1.0' encoding='ISO-8859-1' ?>

<map version="1.0">

<mapID target="top" url="FTPClient.html" />

<mapID target="UserName" url="FTPClient.html#UserName" />

<mapID target="Password" url="FTPClient.html#Password" />

<mapID target="URL" url="FTPClient.html#URL" />

<mapID target="File" url="FTPClient.html#File" />

<mapID target="PutButton" url="FTPClient.html#PutButton" />

<mapID target="GetButton" url="FTPClient.html#GetButton" />

<mapID target="FileButton" url="FTPClient.html#FileButton" />

<mapID target="CloseButton" url="FTPClient.html#CloseButton" />

</map>

TOC file: FTPTOC.xml

see \Chapter25\2\help

<?xml version='1.0' encoding='ISO-8859-1' ?>

<toc version="1.0">

<tocitem target=top>FTP Client Example - TOC

<tocitem text="User Name" target="UserName"/>

<tocitem text="Password" target="Password"/>

<tocitem text="URL" target="URL"/>

<tocitem text="File" target="File"/>

<tocitem text="Put button" target="PutButton"/>

<tocitem text="Get button" target="GetButton"/>

<tocitem text="File button" target="FileButton"/>

<tocitem text="Close button" target="CloseButton"/>

</tocitem>

</toc>

Index file: FTPIndex.xml

see \Chapter25\2\help

<?xml version='1.0' encoding='ISO-8859-1' ?>

<index version="1.0">

<indexitem text="Index">

<indexitem text="C">

<indexitem target="CloseButton" text="Close button"/>

</indexitem>

<indexitem text="F">

<indexitem target="File" text="File"/>

<indexitem target="FileButton" text="File button"/>

</indexitem>

<indexitem text="G">

<indexitem target="GetButton" text="Get button"/>

</indexitem>

<indexitem text="P">

<indexitem target="Password" text="Password"/>

<indexitem target="PutButton" text="Put button"/>

</indexitem>

<indexitem text="U">

<indexitem target="UserName" text="User Name"/>

<indexitem target="URL" text="URL"/>

</indexitem>

</indexitem>

</index>

The Code: FTPApp.java

see \Chapter25\2

// Unchanged imports from section 13.5

import javax.help.*;

public class FTPApp extends JFrame 
{
  // Unchanged code from section 13.5

  protected HelpSet m_hs;
  protected HelpBroker m_hb;
  static final String HELPSETNAME = "Help/FTP.hs";

  public FTPApp() {
    super("FTP Client [Help]");

    createHelp();
        
    JPanel p = new JPanel();
    p.setLayout(new DialogLayout2(5, 5));
    p.setBorder(new EmptyBorder(5, 5, 5, 5));

    // Unchanged code from section 13.5

    JButton btHelp = new JButton("Help");
    btHelp.setToolTipText("Show help");
    m_hb.enableHelpOnButton(btHelp, "top", null);
    p.add(btHelp);

    btHelp = new JButton(new ImageIcon("onItem.gif"));
    btHelp.setToolTipText("Component help");
    btHelp.addActionListener(
      new CSH.DisplayHelpAfterTracking(m_hb));
    p.add(btHelp);

    CSH.setHelpIDString(m_txtUser,"UserName");
    CSH.setHelpIDString(m_txtPassword,"Password");
    CSH.setHelpIDString(m_txtURL,"URL");
    CSH.setHelpIDString(m_txtFile,"File");
    CSH.setHelpIDString(m_btPut,"PutButton");
    CSH.setHelpIDString(m_btGet,"GetButton");
    CSH.setHelpIDString(m_btFile,"FileButton");
    CSH.setHelpIDString(m_btClose,"CloseButton");
        
    setSize(520,340);
    setResizable(false);
    setVisible(true);
  }

  protected void createHelp() {
    ClassLoader loader = this.getClass().getClassLoader();
    URL url;
    try {
      url = HelpSet.findHelpSet(loader, HELPSETNAME);
      m_hs = new HelpSet(loader, url);
      m_hb = m_hs.createHelpBroker();
      m_hb.enableHelpKey(getRootPane(), "top", m_hs);
    } 
    catch (Exception ex) { ex.printStackTrace(); }
  }

  // Unchanged code from section 13.5
}

Understanding the Code

Class FTPApp

The mechanism of adding help to this application is very similar to that of the previous example. We add IDs to each button and text field component using CSH’s setHelpIDString() method, and enable help invocation on the frame’s JRootPane using an identical createHelp() method (see previous example).

Two new buttons are added to the button row. The first one is titled "Help". Method enableHelpOnButton() is called on the HelpBroker instance to invoke the display of the default help topic when this button is pressed. The second button receives a predefined ActionListener retrieved with the CSH.DisplayHelpAfterTracking() method. This listener displays a custom question mark cursor and displays the appropriate help topic corresponding to the clicked component. (Note that once a component is clicked and help is shown, the cursor reverts back to normal.)

Running the Code

Figure 25.2 shows our FTP client example with two new buttons to manage help. Press the "Help" button to display the default help topic (which calso can be shown using the F1 key). Try using the content-sensitive help button to bring up a help topic for a particular component, and note the use of a custom cursor.

25.5 Customizing the JHelp viewer

So far we've seen how to add the help viewer to an application and invoke it through pressing F1 or a button. However, we may very well want to customize help functionality by adding a component to the help viewer toolbar, or adding a custom navigational view. The current release of the JavaHelp does include a fairly powerful GUI, however, there are certainly cases where this is not enough. In this and the next section we will show how to integrate two new features into the help viewer: a "Home" toolbar button switching to the home help topic (specified by the <homeID> HelpSet file tag), and a "History" view providing quick return to a specific previously viewed topic.

The bad news is that (at least in this release) JavaHelp does not provide any direct access to the JHelp instance constituting the help viewer. So no customization can be done though provided functionality. The good news is that by taking advantage of the fact that JHelp is a java.awt.Container subclass, we can gain access to its child components indirectly and do what we need.

 

Reference: We did a similar thing with JFileChooser in chapter 14 to gain access to its embedded JList. This was necessary to allow multiple selection, which is not implemented correctly as of Java 2 FCS.

The following example builds off of the previous FTP client example, and shows how to customize the help viewer by adding a "Home" button to its toolbar.

 

Note: This solution is highly dependant on the construction of JHelp, which may very well change in future implemenatations. However, it illustrates some general techniques that can be used when components are not build as flexible as they should be.

 

Note: This example, and the example in section 25.6, requires Java2 because new functionality built into java.awt.Frame and java.io.File is used here (explained below).

Figure 25.4 JHelp viewer with a custom toolbar button for jumping to the home topic specified in a HelpSet file.

<<file figure25-4.gif>>

The Code: FTPApp.java

see \Chapter25\3

// Unchanged imports from section 25.4

public class FTPApp extends JFrame 
{
  // Unchanged code from section 25.4

  public FTPApp() {
    super("FTP Client [Customizing Help]");

    // Unchanged code from section 25.4

    WindowListener wndCloser = new WindowAdapter() {
      public void windowClosing(WindowEvent e) {
        disconnect();
        System.exit(0);
      }

      Frame m_helpFrame = null;
      public void windowDeactivated(WindowEvent e) {
        if (m_helpFrame != null)
          return;
        Frame[] frames = getFrames(); // Only available in Java 2
        for (int k = 0; k < frames.length; k++) {
          if (!(frames[k] instanceof JFrame))
            continue;
          JFrame jf = (JFrame)frames[k];
          if (jf.getContentPane().getComponentCount()==0)
            continue;
          Component c = jf.getContentPane().
            getComponent(0);
          if (c == null || !(c instanceof JHelp))
            continue;
          m_helpFrame = jf;
          JHelp jh = (JHelp)c;
          for (int s=0; s<jh.getComponentCount(); s++) {
            c = jh.getComponent(s);
            if (c == null || !(c instanceof JToolBar))
              continue;
            JToolBar jtb = (JToolBar)c;
            jtb.addSeparator();
            JButton home = new JButton(new
              ImageIcon("Home.gif"));
            home.setToolTipText("Home Topic");
            jtb.add(home);
                       
            ActionListener alst = new ActionListener() { 
              public void actionPerformed(ActionEvent e) {
                try {
                  m_hb.setCurrentID(m_hs.getHomeID());
                }
                catch (Exception ex) {}
              }
            };
            home.addActionListener(alst);
          }
        }
      }
    };
    addWindowListener(wndCloser);

    // Unchanged code from section 25.4
  }

// Unchanged code from section 25.4

Understanding the Code

Class FTPApp

The idea behind this example is that when the application's frame is deactivated, this may have occurred because the help viewer's frame has become activated. So at that moment we can check all existing frames in the current JVM, and check whether they hold an instance of JHelp as a first child in the content pane. If so, we gain access to the JHelp instance and add our custom component to it.

To accomplish this, significant modification has been made to our WindowListener implementation, which previously only implemented the windowClosing() method. The Frame m_helpFrame instance variable holds a reference to the help viewer's frame (note that this frame is created only once and is hidden/shown as needed after that). The windowDeactivated() method checks this variable, and if it hasn’t been determined yet (i.e. if it is null) it retrieves an array of all the frames created by this JVM using the static Frame.getFrames() method (another nice new feature in Java 2). All these frames are examined! in turn. If one of them is an instance of JFrame and has an instance of JHelp as its first child, we take a reference to that child, cast it to JHelp, and, in turn, examine all its children. We know that one of these children should be an instance of JToolBar (since this only occurs before JHelp first becomes visible, there is no possibility that the toolbar has been undocked and left floating). As soon as it is found, we add() a JButton which calls setCurrentID() on o! ur! HelpSet when pressed:

m_hb.setCurrentID(m_hs.getHomeID());

The Map.ID instance returned by the getHomeID() method is supplied as an argument to this call, which sets the current topic in the viewer to the home topic specifed in our HelpSet file.

Running the Code

Figure 25.4 shows the JHelp viewer with a new toolbar button. Pressing this button shows the home topic as specified by the <homeID> tag in our HelpSet file. We can use a similar technique to add any other component to this toolbar, or to another JHelp constituent.

25.6 Creating a custom help view

As we mentioned earlier, JavaHelp allows developers to add a new navigator view by simply adding a new <view> tag to the HelpSet file. Unfortunately this navigator view cannot gain an access to the viewer component that displays it (it can only have access to a data file supplied as a parameter with the <data> tag, similar to TOC or index views). We can work around this limitation by using the same basic technique used in the previous section to access the JHelp toolbar. The following example adds a custom navigational view to the JHelp viewer tabbed pane to display a selectable, dynamically changeable help topic history.!

Figure 25.5 Custom JavaHelp History view added to the JavaHelp viewer.

<<file figure25-5.gif>>

HelpSet file: FTP.hs

see \Chapter25\4\help\

// Unchanged from section 25.4

<view>

<name>History</name>

<label>History</label>

<type>HistoryView</type>

</view>

</helpset>

The Code: FTPApp.java

see \Chapter25\4

// Unchanged imports from section 25.5

public class FTPApp extends JFrame 
{
  // Unchanged code from section 25.5

  public FTPApp() {
    super("FTP Client [Help History]");

    // Unchanged code from section 25.5

    WindowListener wndCloser = new WindowAdapter() {
      // Unchanged code from section 25.5

      public void windowDeactivated(WindowEvent e) {
        // Unchanged code from section 25.5

          Enumeration en = jh.getHelpNavigators();
          while (en.hasMoreElements()) {
            JHelpNavigator jhn = (JHelpNavigator)
              en.nextElement();
            NavigatorView view = jhn.getNavigatorView();
            if (view instanceof HistoryView)
              ((HistoryView)view).setNavigator(jhn);
          }
        }
      }
    };
    addWindowListener(wndCloser);

    // Unchanged code from section 25.5
  }

// Unchanged code from section 25.5

The Code: HistoryView.java

see \Chapter25\4

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.net.*;

import javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;

import javax.help.*;
import javax.help.event.*;

public class HistoryView extends TOCView
 implements HelpModelListener
{
  protected DefaultMutableTreeNode m_topNode;
  protected JTree m_tree;
  protected DefaultTreeModel m_model;
  protected TreePath m_zeroPath;

  public HistoryView(HelpSet hs, String name, String label,
   Hashtable params) {
    super(hs, name, label, hs.getLocale(), params);
  }

  public HistoryView(HelpSet hs, String name, String label,
   Locale locale, Hashtable params) {
    super(hs, name, label, locale, params);
  }

  public Component createNavigator(HelpModel model) {
    JHelpNavigator navigator = 
      new JHelpTOCNavigator(this, model);
    navigator.getUI().setIcon(new ImageIcon("History.gif"));
    return navigator;
  }

  public static MutableTreeNode parse(URL url, HelpSet hs,
    Locale locale, TreeItemFactory factory) {
      return new DefaultMutableTreeNode("History");
  }

  public void setNavigator(JHelpNavigator jhn) {
    jhn.getModel().addHelpModelListener(this);
    try {
      Container c = jhn;
      while (!(c instanceof JTree))
        c = (Container)(c.getComponent(0));
      m_tree = (JTree)c;
      TOCItem top = new TOCItem();
      top.setName("History");
      m_topNode = new DefaultMutableTreeNode(top);
      m_model = new DefaultTreeModel(m_topNode);
      m_tree.setModel(m_model);
      m_zeroPath = new TreePath(new Object[] { m_topNode });
    }
    catch (Exception ex) {
      ex.printStackTrace();
      System.err.println(ex.toString());
    }
  }

  public void idChanged(HelpModelEvent e) {
    // To avoid conflict with java.util.Map
    javax.help.Map.ID id = e.getID();
    if (m_topNode != null) {
      TOCItem item = new TOCItem(id, null, null);
      item.setName(id.id);
      for (int k=0; k<m_topNode.getChildCount(); k++) {
        DefaultMutableTreeNode child = 
          (DefaultMutableTreeNode)m_topNode.getChildAt(k);
        TOCItem childItem = (TOCItem)child.getUserObject();
        if (childItem.getID().equals(id))
          m_topNode.remove(child);
      }
      DefaultMutableTreeNode node = 
        new DefaultMutableTreeNode(item);
      m_topNode.insert(node, 0);
      m_model.reload(m_topNode);
      m_tree.expandPath(m_zeroPath);
    }
  }
}

Understanding the Code

FTP.hs

One more <view> tag is now added to our FTP.hs XML HelpSet file to request the creation of a HistoryView with a name and label of "History." This results in a new navigator in JHelp’s navigational view tabbed pane. This navigator is connected to an instance of our custom HistoryView class.

Class FTPApp

To connect the history view to the viewer itself, some code has been added to our windowDeactivated() method in the WindowAdapter inner class. This code retrieves all help views using the getHelpNavigators() method, examines all of them in turn, and, if an instance of our custom HistoryView class is found, calls the setNavigator() method to pass a reference to JHelpNavigator to it.

 

Note: The fact that we have to add this code to our application in order to use a custom view should immediately stand out as bad design, even though it is only called once (see previous example). We are forced to do this because the parent class, NavigatorView, does not provide any means of retrieving the associated instance of JHelpNavigator used to display it. This severely limits the abilities of help customization and contradicts the flexible, JavaBeans design of Swing components. We hope to see this limitation accounted for in future JavaHelp releases, making this workaround unnecessary.

 

Class HistoryView

This class represents a custom implementation of NavigatorView to provide easy access to previously viewed help topics. HistoryView implements HelpModelListener and extends TOCView to inherit its functionality and avoid writing our own NavigatorView sub-class. Instance variables:

JTree m_tree: the tree component used to display historical help topics.

DefaultMutableTreeNode m_topNode: the root node in the tree parenting all historical help topics. This node is not visible because the rootVisible property is set to false by the parent TOCView class.

DefaultTreeModel m_model: the historical tree model.

TreePath m_zeroPath: path to the root node.

The two available constructors delegate their work the corresponding superclass constructors. The createNavigator() method creates an instance of JHelpTOCNavigator (a sub-class of JHelpNavigator) to represent our historical view graphically, and assigns it a custom icon (to be displayed in JHelp’s navigational views tabbed pane).

Since we are not intending to parse any external data files in our historical view (all data is generated by the help viewer itself at run-time), the parse() method simply creates and returns an instance of DefaultMutableTreeNode with user object "History," and no child nodes.

Our custom setNavigator() method establishes a connection between this view and the JHelpNavigator component supplied as parameter representing our history information graphically. First, this view is added as a HelpModelListener to the JHelpNavigator‘s HelpModel to receive HelpModelEvents when a change in the current help topic occurs. Second, we search for the JHelpNavigator’s tree compone! nt by iterating through its containment hierarchy. Then the m_topNode variable is assigned a DefaultMutableTreeNode instance with a default TOCItem user object assigned a name of "History." We also assign the m_model variable as a reference to this tree's model, and m_zeroPath is used to store the path from the tree's root to m_topNode (which simply consists of m_topNode itself). Both variables are needed in idChanged().

The idChanged() method will be called when the current help topic is changed. This method receives an instance of HelpModelEvent which encapsultes, among other things, the new help topic ID. It then creates a new TOCItem using that ID, and checks all child nodes of m_topNode to find out whether the corresponding topic already exists in the history view. If so, it is removed to avoid redundancy. Then the new topic is inserted at the top of the tree. Thus the previously viewed topics appear listed from most recent at the top, to least recent at the bottom. Finally we call reload() on the tree model and expandPath() on the tree, using m_zeroPath, to make sure that the tree updates its graphical representation correctly after it is reloaded.

 

Note: Since the root tree node is hidden and the path to it is collapsed by default, no nodes in the view will be visible after a reload() unless we expand m_zeroPath programmatically.

That's all we need to do to create a history view. As soon as the user clicks on the topic listed in the help view, it will be displayed in the viewer because we inherit this functionality from TOCView.

Running the Code

Figure 25.5 shows the customized help viewer for our FTP Client example with the custom history view. Select several help topics in a row using the TOC or index views and then select the history view. Note that all visited help topics are listed in the reverse order that they were visited in. Click on any of these topics and note that viewer jumps to that topic (at the same time the selected topic is displayed it moves to the top of the history view, as expected).