Advanced Help
Volume Number: 6
Issue Number: 1
Column Tag: C Workshop
Advanced Help Facility
By Joe Pillera, Ann Arbor, MI
Note: Source code files accompanying article are located on MacTech CD-ROM orsource code disks.
An Advanced Help Facility Using C
One of the nicest features of a well written program is its help facility. Some
programmers implement a scrolling text-based help system (as in Prototyper™),
while still others (as in the case of FullWrite™) implement nice graphical help
screens. Some of these help systems are modal dialogs, while others are modeless
windows that you can drag with the mouse. I’m greedy - I wanted the best features
from all of these facilities. This is the story of MacTutor Help: an advanced, portable
help facility that takes these concepts and pushes them to the limit, creating an
application-independent help facility that
• is a modeless window that can dragged with the mouse, and supports Multi finder
context switching.
• is capable of dynamically switching between text and graphics mode, in order to
support both text and picture screens.
• is user customizable: your users can add their own help information to MacTutor
Help - without having to write one line of code. This is achieved via my custom
ResEdit resource, HTBL (“help table”).
• is language independent, and can be used in any development system that provides
full access to the Macintosh toolbox. This is because MacTutor Help is a code
resource, and can be added to any program by just pasting my HELP resource
with ResEdit.
For notational convenience, I’ll abbreviate MacTutor Help to MTH.
A PICT is Worth a Thousand Words
The idea of explaining difficult concepts with pictures is a powerful one. To
implement this notion, my help screen consists of two button controls, “next” and
“previous”, to scroll multiple pictures within a help topic back and forth. When a
pictorial help topic has only one picture, these controls are made invisible, so they
don’t confuse the user. Note that I could have put a vertical scroll bar up to scroll
multiple pictures (as I do with text), but this proved to be prohibitive. Even though it
would have been consistent to use a scroll bar, using button controls gives the user
much more control over the picture scrolling. For example, have you ever scrolled
PICT items inside ResEdit? It can be very annoying, because the pictures will ‘fly’ by
so fast that you have to drag the thumb-wheel manually to see all of them. Therefore, I
bend the ‘consistency rule’ of user-interface design to allow the user greater control
over the environment. Furthermore, a message indicator is provided to show the user
where they are in that pictorial subtopic. Refer to Figure 1 for an example of MTH in
graphics mode. For a peek at the PICT resource fork of the “Help Data” file, refer to
Figure 2. See Figure 3 for an illustration of MTH handling a picture topic with three
(3) screens.
Figure 1: MacTutor Help entering the graphics (PICT) mode.
Figure 2: Creating a PICT help screen with ResEdit
Figure 3: When a graphical (PICT) help topic has more than one screen, MacTutor
Help will automatically display additional controls to scroll through them
Saying it in TEXT
The ability of MTH that sets it apart from all other help facilities I’ve seen is the
dynamic switching between PICT and TEXT mode. Creating text-based help screens is
very easy: using ResEdit, just create a TEXT resource in the “Help Data” file, then tell
MTH its resource id. That’s all there is to it. When it’s time to go from graphics to
text mode, MTH will call Quick Draw to erase the picture, call the Text Edit Manager to
make a clipping region in the display area, and finally call the Control Manager to put
up a vertical scroll bar. Refer to Figure 4 for an example of MTH in text mode. For an
example of creating your TEXT resource fork on the “Help Data” file, refer to Figure
5.
Figure 4: MacTutor Help dynamically entering the TEXT mode
Figure 5: Creating a TEXT help screen with ResEdit
Internals
Refer to Figure 6 - MTH makes extensive use of user items within its DLOG
template. This allows easy customization of the size, shape and location of the MTH
window regions, without having the modify one line of code.
Also, because it is written in the portable C language, it shouldn’t be too difficult
to port the source code to other C development systems.
Figure 6: The dialog item list of the MacTutor Help system.
Externals
All of the help information is stored in an external data file, in the form of PICT
and TEXT resources. To further enhance optimization, the PICT resources are made
purgeable, so that if the Finder needs to perform any memory garbage collection, these
resources will be zapped when no longer needed. But because TEXT resources tend to be
much easier on memory, it set these to preload, so they appear quickly on the screen
when invoked.
One of the advantages to this external file approach is that the physical program
size is kept to a minimum (for example, the MTH object code itself weighs in at 5K,
where as the accompanying file Help Data for the Demo program is well over 86K in
size). How are external resource files accessed? Simple, just issue a command to the
Resource Manager:
/* 1 */
refnum = OpenResFile(“\pHelp Data”);
MTH assumes that this data file is in the same volume (i.e. same folder) as your
application; if it isn’t, it issues a warning rather than continuing.
Figure 7: MacTutor Help comes complete with its own ResEdit template, HTBL, to let
you customize the topics table without having to recompile the program
Once the file “Help Data” is opened, its resources become accessible, just as
though they were on the resource fork of your program itself. For instance, displaying
a picture is then just a matter of getting a Handle to it, and resetting the rectangle
within the dialog resource:
/* 2 */
newPict = GetPicture();
GetDItem(helpPtr,,&iType,&iHandle,&iBox); EraseRect(&iBox);
SetDItem(helpPtr,,iType,newPict,&iBox); The scrolling list of text was just a little more difficult. What became the most
interesting development aspect was allowing your programs to communicate to MTH in
a seamless way; this could only mean one thing: altering ResEdit itself.
Customizing ResEdit
I wanted a help system so generic, that users of your software - not just the
developers - could customize the help system to suit their needs, without having to
recompile a single line of code. In order to implement this, I had to create my own
ResEdit template, HTBL (“help table”), which allows you to specify the help screens
in a very concise and simple way. Refer to Figure 7: specifying the contents and order
of your help screens couldn’t be easier. Let’s say your engineering department wants
to add their own textual help screen to your program: all they have to do is edit the
HTBL resource, and then create another TEXT resource with their information. The
next time MTH is executed, their information will automatically appear in the menu.
To add this template to ResEdit, just copy my TMPL=47 (from this month’s
source code disk) to a backup copy of ResEdit. Just make sure that my ID=47 doesn’t
conflict with any templates in your version of ResEdit. That’s all there is to it.
For those of you who are ResEdit hackers, below I describe how I designed my
custom TMPL resource, using my own pseudo-code syntax:
/* 3 */
resource (TMPL=47) {
First Menu = DWRD
Last Menu = DWRD
numTopics = ZCNT
***** = LSTC
Screen Type = TNAM
Resource Start = DWRD
Resource End = DWRD
Topic Title = PSTR
***** = LSTE
}
Where did the “47” come from? In my copy of ResEdit, the last TMPL template
was ID=46, so I just added my template to the end of that list. Please refer to Figure 8
for an example of adding my TMPL resource to ResEdit.
Figure 8: Adding the MacTutor Help template to ResEdit
Byte Your Way Into Resources
For those of you who are assembly language programmers, take a close look at
ParseInt() and ParseOSType(): these routines read in an integer and longint from my
custom HTBL resource, respectively. Notice how they are implemented: they read in a
byte (i.e. an unsigned char) at a time, then bit shift their intermediate results. My
first inclination was to read in the entire int or long - but this caused address
exceptions (ID=2 System Errors) on all small screen Macs. What happened was
simple: whenever the previous topics title (a pascal string) had an odd number of
bytes, the next integer was aligned on an odd address - a no-no on the MC68000
architecture. On an MC680x0 however, reading in a data structure of two or more
bytes at an odd address is ok.
Design Tradeoffs
My primary goal in designing MTH was “cut-and-paste” portability of the help
system - across all languages and development systems. This presented an anomaly
from the start, because it is a modeless dialog, and a pure modeless dialog is intimately
sewn into the application’s event loop. In order to maintain high portability, MTH will
disable your menu bar while it is active, and re-enable it when done. This isn’t that
bad, if you consider that Multifinder context switching and window dragging are
automatically implemented. Update events are also handled correctly, but keep in mind
that if your application has its own windows active when MTH is invoked, you’ll have
to add a couple of lines to Help_Event_Loop() - otherwise these windows won’t be
updated when you drag the MTH window over them. For example, I’m developing a
large application using CAPPS’ (the editor construction kit from THINK Technologies),
so I had to modify the update processing as follows:
/* 4 */
void Help_Event_Loop()
{
...
case updateEvt:
if ((WindowPtr)evtInfo. event.message == helpPtr)
/* Normal case for MacTutor Help */
Handle_Update();
else {
/* Special case: tell CAPPS’ to update */
/* one of its text windows. */
wPtr = (WindInfoPtr) evtInfo. event.message;
BeginUpdate (wPtr);
SetPort (wPtr);
EraseRect (&wPtr-> window.port.portRect);
DrawGrowIcon (wPtr);
DrawControls (wPtr);
PEUpdate (wPtr->hPE);
EndUpdate (wPtr);
}
break;
...
}
Aside from special purpose handling of update events, you will find integrating
MTH within your own programs almost trivial.
Using MacTutor Help Within Your Own Code
This code has been designed from the start to be as generic and portable as
possible. At the same time, I wanted to provide MacTutor readers with maximum
programming flexibility - so MTH comes in two flavors:
• source code form (THINK C files), for those developers using the THINK C
development system. This is advantageous over the code resource, because it
allows you the ability to customize MacTutor Help to suit your own tastes.
• code resource form, for programmers using any development system other than
THINK C.
Pass The Source Code
If you use the THINK C development system, just include the “help.c” file into
your project window. Communication to MTH is then performed via two functions:
Init_Help(), to read in the HTBL resource fork, and Do_Help(), which seamlessly
enters the help system when the user selects “help” from one of your menus. Note
that I could have imbedded Init_Help() into the help system itself, but reading in the
HTBL resource fork more than once would have been redundant.
For completeness, I’ve included a complete example of the source code version of
MTH in action within a demo THINK C program.
Using The HELP Resource
HELP is the code resource form of MTH. Take this approach if you are using a
development system other than THINK C. As in the source code version, you tell MTH
where your resources are via HTBL. However, there are three important differences
between the “code resource” and “source code” versions of MTH. In the code resource
version:
1. Init_Help() is done for you automatically.
2. Do_Help() is replaced with a special main() function,
which has code from the file. Thus,
instead of activating MTH via Do_Help(), you must
get a handle to the HELP resource, and execute that
handle by dereferencing it twice.
3. Because a code resource doesn’t have access to Quick
Draw globals, I perform InitGraf(&thePort) inside
the code resource itself.
For example, to execute the MTH code resource from C, we have:
/* 5 */
void Call_MTH()
{
Handle h;
ProcPtr pp;
h = GetResource(‘HELP’,128);
if ((ResErr == noErr) && (h != NIL)) {