Three Steps to Apple Help, Part 3

Accessing Apple Help from C++ Code (Carbon)

The third/final step in implementing Apple Help is programmatically displaying the main contents or individual topics within your help pages.  This, of course, assumes that you have already created a set of HTML help pages for Apple Help and then indexed and integrated these files into your Xcode project.

Accessing Apple Help from C++ using Carbon is fundamentally simple.  The Apple Help Reference contains only 4 functions (plus one constant enumeration that is no longer used).  There is a little bit of necessary mucking about with bundle references and whatnot that may be unfamiliar to programmers who have not worked with Mac development before, but it is all fairly straightforward.

In order to access Apple Help from the application, you first register your help files using the AHRegisterHelpBook() function.  This only needs to be done once, ideally during program initialization, and we do it something like this:

bool RegisterHelp ( void )
{

    CFBundleRef const bundle = CFBundleGetMainBundle ( );
    if ( !bundle )
        return false;

    CFURLRef const location = CFBundleCopyBundleURL ( bundle );
    if ( !location )
        return false;

    FSRef file;
    if ( !CFURLGetFSRef ( location, &file ) )
    {
        CFRelease ( location );
        return false;
    }

    _Error = AHRegisterHelpBook ( &file );

    CFRelease ( location );

    if ( _Error )
        return false;

    return true;

}

(Additional validity checking and error reporting has been stripped from this sample code for clarity.)

In the above example, most of the code is just to obtain the FSRef (file system reference) of the application bundle from which the help is to be displayed, and it assumes that you want help for the current application.  (Obviously, you could instead pass a reference to any other bundle and access a different help system, but that would be unusual.)

Once the help files are registered, you can display the main help page using the AHGotoPage() function (with default parameters), which we accomplish with a function similar to this:

void ShowHelpContents ( void )
{

    CFBundleRef const bundle = CFBundleGetMainBundle ( );
    if ( !bundle )
        return;

    CFStringRef const key = CFSTR( "CFBundleHelpBookName" );
    CFTypeRef const book =
        CFBundleGetValueForInfoDictionaryKey ( bundle, key );
    if ( !book )
        return;

    CFStringRef const help = (CFStringRef)book;

    _Error = AHGotoPage ( help, NULL, NULL );

}

(Additional validity checking and error reporting has been stripped from this sample code for clarity.)

You can also display a specific help topic by using the AHLookupAnchor() function, for which we use something like this:

void ShowHelpTopic ( CFStringRef topic )
{

    if ( !topic )
        return;

    CFBundleRef const bundle = CFBundleGetMainBundle ( );
    if ( !bundle )
        return;

    CFStringRef const key = CFSTR( "CFBundleHelpBookName" );
    CFTypeRef const book =
        CFBundleGetValueForInfoDictionaryKey ( bundle, key );
    if ( !book )
        return;

    CFStringRef const help = (CFStringRef)book;

    _Error = AHLookupAnchor ( help, topic );

}

(Additional validity checking and error reporting has been stripped from this sample code for clarity.)

In both of these last two examples, the function reads the CFBundleHelpBookName string from the main application bundle and uses that string for the name of the help system, passed as the first parameter to the appropriate Apple Help function.  (You could, of course, hard code this string, but if you were thinking that this is a good idea, shame on you.)

Accessing Apple Help from Objective-C (Cocoa)

If you are using Objective-C and Cocoa, much of the help system handling is done behind the scenes (which is, in part, why some programmers find Objective-C to be objectionable).  If you follow the steps in the previous two posts (Part 1 and Part 2) to insert your Apple Help into a Cocoa project, your main help system will work automatically.

The main help page in a Cocoa application is accessible by the user through the ‘Help->[ApplicationName] Help’ menu option by default.  If you wish to bring up the main page from your Objective-C code, you may need to reference an anchor on that page (see below) or use the Carbon method described above.

To access a specific help topic (anchor) from Objective-C and Cocoa, this is done through the shared NSHelpManager object using the openHelpAnchor:inBook: method.  For example, to open the ‘rules’ topic, you would use the following code:

    NSString *book = [[NSBundle mainBundle]
        objectForInfoDictionaryKey:@"CFBundleHelpBookName"];
    [[NSHelpManager sharedHelpManager]
        openHelpAnchor:@"rules" inBook:book];

The documentation for NSHelpManager is quite clear that all of its functionality is simply a number of wrappers around the functions described in the first section of this post, so you could always use those functions directly, if desired.

There you have it.  At this point, you should know enough to properly implement Apple Help in your Carbon or Cocoa application for Mac OS X.  The process is a little convoluted to suss out from the documentation, so I hope this provides a decent jump start.

Three Steps to Apple Help, Part 2

Adding Help Files to an Xcode Project

The second step in implementing Apple Help is integrating the help files into an Xcode project, so they will be properly added to an application bundle, and generating an Apple Help index file.  This article describes the process continuing from the HTML files generated in the previous blog entry (Part 1).  Be sure to read the comments there, too, where Alexander Halser of Help & Manual describes an alternative (and probably quicker) method of adding the necessary ‘name’ tags.

First, transfer the entire folder containing the HTML help files to your Mac OS X development system (as/if necessary).  [A discussion of how this is done is far below the scope of this article.]  The rest of this article assumes that this folder is named “Help”; rename the folder or adjust the instructions as appropriate.

Next, you will be adding the files to the application bundle.  Open your project in Xcode.  Highlight the ‘Resources’ group, right-click (or Command-click) it, and select ‘Add->Existing Files…’ from the context menu.  Choose the ‘Help’ folder and then click the ‘Add’ button.  In the next dialog box, mark the ‘Copy items into destination group’s folder (if needed)’ check box, select the ‘Create Folder Reference for any added folders’ radio button, and then click the ‘Add’ button.  This will create a folder reference in the project, such that the entire contents of the ‘<Project>/Help’ folder will be added to the bundle as a unit.  In other words, you can add, delete, or change files in that folder from outside Xcode and those changes will be reflected in the build without explicitly adjusting the project file.

If you want to add a localized help file instead (which is not a bad idea, even if translating the help file is not in the immediate plans), the above process is slightly different.  In that case, explicitly copy (or move) the ‘Help’ folder into the appropriate project language folder, such as ‘<Project>/English.lproj’.  Follow the same procedure (‘Add->Existing Files…’), but select the ‘<Project>/English.lproj/Help’ folder, and do not (or, rather, it is unnecessary to) mark the check box, but the radio button is still important.  In this case, you will see that instead of just adding a ‘Help’ subgroup to the ‘Resources’ group, Xcode will automatically add the group as ‘Help/English’ (or whichever language).  Repeat as necessary for additional languages.

Now that the help (HTML) files will be properly copied to your application bundle(s), you need to identify these files as the Apple Help files for the project.  To do this, open the ‘Info.plist’ file for the project, a property list that contains global project settings.  Right-click (or Command-click) and select ‘Add Row’ from the context menu, choosing ‘Help Book directory path’ [or ‘CFBundleHelpBookFolder’].  Edit the value of that row to be “Help”.  Using the same procedure, add a ‘Help Book main page CONTENT attribute’ [or ‘CFBundleHelpBookName’] row, and set its value to the identical string from the “AppleTitle’ meta name (e.g., “Pretty Good Solitaire Help” [sans quotes]).  You can now close Xcode.

Creating an Apple Help Index

In order for a user to be able to search your help file, you must create an Apple Help index file.  The tool you need for this is (the enigmatically named) Help Indexer, which should already be installed on your system in the ‘/Developer/Applications/Utilities’ folder.  (I find it convenient to create an alias to this particular utility in a more accessible location.)

Launch the Help Indexer tool.  On the first execution (and thereafter as necessary), you will want to set the utility preferences (‘Help Indexer->Preferences…’), as they determine the parameters for the Apple Help index (or indices) you will create.  In the ‘Index Style’ panel, clear the ‘Generate Panther-compatible Indices’ checkbox, unless you plan to support Mac OS X 10.3 or earlier.  (At this point, we only support 10.4 [Tiger] and later [Leopard and Snow Leopard].)  I recommend selecting the ‘Errors and warnings’ radio button in the ‘Logging Options’ panel, as including the status messages for large help files simply produces too much noise that could hide any valid warning or error messages.

To actually generate the help index file, once the tool is configured, is quite simple.  Click the ‘Select…’ button, browse to select/highlight the ‘Help’ folder, and click the ‘Open’ button.  Then, click the ‘Create Index’ button, and the process begins.  When finished, review the warning/error messages (if any), and then click ‘Quit’ to close the tool.  (Note that, if you do generate a Panther-compatible index, the messages are shown in a separate tab, and you may get a ‘Warning: [filename].html — Error parsing file.  Check validity of HTML.’ message for each file.  In our experience, we were unable to eliminate these warnings, but the indexing still seemed to work properly on the older systems.)

At this point, you can open Xcode and, if you look into your ‘Help’ group (added above), you will see that a new ‘Help.helpindex’ file has been added (as well as a Panther-compatible ‘Help idx’ file, if selected).  The advantage of adding the folder as a reference group, as noted earlier, is that changes to the folder contents (such as adding the index file) are automatically reflected in the project.  The corresponding disadvantage, however, is that these changes do not necessarily trigger a bundle rebuild, so after making help file changes, you will want to either clean your target (‘Build->Clean’ or Shift-Command-K) or explicitly incorporate the changes by right-clicking (or Control-clicking) on the project group that hold the help files and selecting the ‘Preprocess’ command from the context menu.

Now you have a fully indexed Apple Help system incorporated into your project.  All that remains is to add code to programmatically access the main help content, as well as individual topics, which I will cover in the third installment, so please stay tuned.

Three Steps to Apple Help, Part 1

Building an HTML Help File

The first step in implementing Apple Help is building an HTML help file.  Apple Help is the default help system in Mac OS X, and it is fundamentally a system for displaying HTML 4.01 from an application.  In order to use some (or any) of the features of Apple Help, one must do some minor customization, which is what I intend to describe here.  (The actual writing process is left as an exercise for the reader.)

For an overview of Apple Help, see this Apple Help Programming Guide.

Any decent HTML (or text) editor can be used to produce the necessary help files, but since ours is a help system for a product available on both Mac OS X and Windows, it makes sense to start with a full-featured help editor under Windows.  In our case, we use Help & Manual, which is an excellent and capable product.  (Note that we still choose to use the older H&M 4, since version 5 added the silly ribbon toolbar interface.)

Starting with a help project appropriate for Windows HTML Help (.chm), and with a tool that either works with or exports separate HTML files, there are only a few extra steps required to make an equivalent version for Apple Help.  In H&M, you use the ‘Browser-based Help’ option to write the necessary HTML files (as described later).

The common method of accessing a topic in HTML Help (as well as in the older WinHelp format) is via help context numbers.  Apple Help, on the other hand, uses a named anchor tag for each topic that needs to be accessed directly from an application.  For example, for a topic referenced as ‘contents’, you would add the following HTML tag to the topic:

<a name="contents" />

A similar tag should be added to every desired topic page, each with a unique name.  In H&M, this can be done by using the ‘Insert|Plain HTML Code…’ menu option, or you could edit the HTML directly, of course.  I found it most useful to have the basic tag on the clipboard, paste it onto each page, and then directly edit/add the name portion.

Important Note: Do not use spaces or capital letters for the topic names. Apple Help seems to have trouble finding such tags, so context help and searching may fail.

The next step is to export the separate HTML files (if necessary).  In H&M, this is accomplished by generating ‘Browser-based Help’ (via ‘File|Compile Help File…’ or the toolbar).  First, however, you will want to click ‘Customize Browser-based Help’ and make a few changes to the default options.  On the ‘Layout’ page, select the page layout that puts everything in the same window (i.e., no frames, no scripts).  Next, on the ‘Popup Topics’ page, select ‘HTML encoded topics’, which should remove any Javascript.  Finally, on the ‘HTML Export Options’ page, change the ‘Extension for HTML topic files” to “.html” (from “.htm”).

Once your help files are together, you need to add two custom meta tags to the header of the main help page (the one you want to display when the help is opened generically).  The “AppleTitle” meta tag sets the title of the help file, and the “AppleIcon” meta tag sets the name of the help icon (described below).  For Pretty Good Solitaire Mac Edition, which has all the help files in a ‘Help’ folder, we add the following lines inside the page header (<HEAD>) of our contents page, just after the title (<TITLE></TITLE>):

<meta name="AppleTitle" content="Pretty Good Solitaire Help" />
<meta name="AppleIcon" content="Help/help.png" />

The help icon (‘help.png’, in the above example) should be a 16×16, 32-bit image (with alpha transparency) that represents the help file, usually a small version of the application icon.  Place this image in the same folder with all of the HTML files.  Note that Apple Help in recent versions of OS X do not display the help icon prominently (if at all), but help icons were important in earlier versions.

Finally, in order to appear similar to other Mac applications, you will want to change the fonts in your CSS (you did use CSS, right?) to prefer ‘Geneva’ over ‘Arial’ for the display font.  H&M generates a ‘default.css’ file, so to make this change, you need only replace each instance of “font-family: ‘Arial’;” in this file with “font-family: ‘Geneva’, ‘Arial’;” and you are done.

As a final hint, if you are using Help & Manual, you can provide a link to the keyword index (automatically generated) by manually adding a link similar to the following (for the ‘pgsme’ project):

<a href="pgsme_kwindex.html">Help Index</a>

At this point, you should have a folder containing all of the HTML files for your help system, with the necessary tags, and a help icon file.  Now, just prepare to transfer these files onto your Mac development system and wait for the second installment, in which I will explain how to generate the Apple Help index and add the help files to an Xcode project.