TweetFollow Us on Twitter

Advanced Help
Volume Number:6
Issue Number:1
Column Tag:C Workshop

Related Info: TextEdit Resource Manager Control Manager
Dialog Manager List Manager

Advanced Help Facility

By Joe Pillera, Ann Arbor, MI

Note: Source code files accompanying article are located on MacTech CD-ROM or source 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 Multifinder 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(<rsrc ID>);
   GetDItem(helpPtr,<PictArea>,&iType,&iHandle,&iBox);
   EraseRect(&iBox);
   SetDItem(helpPtr,<PictArea>,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 <SetUpA4.h> 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)) {
     HLock(h);
     pp = (ProcPtr) *h;
     (*pp)();
     HUnlock(h);
   }
 }

As you can see, this is a straight-forward exercise using the C language. Getting things to run from Pascal is just a bit more detailed:

/* 6 */

 PROCEDURE Call_Code( routine : Ptr );
   INLINE  $205F,  (* MOVEA.L (A7)+, (A0) *)
   INLINE  $4E90;  (* JMP     (A0)        *)

 PROCEDURE Call_MTH;
   VAR
     h : handle;
   BEGIN
     h := GetResource(‘HELP’,128);
     IF ((ResError = noErr) AND (h <> NIL)) THEN
       BEGIN
         HLock(h);
         Call_Code(h^);
         HUnlock(h);
       END
   END;

Note that even if you’re using a development system such as ZBasic, the concept of executing a code resource will still be the same.

If this all seems confusing - don’t worry. On this month’s source code disk, I’ve included two examples of executing MTH as a code resource: using both THINK C and MPW Pascal.

Figure 8: Adding the MacTutor Help template to ResEdit

Error Handling

MTH is a conscientious program, and will attempt to catch your mistakes before they cause system crashes. This is especially helpful when using the code resource version - because debugging a code resource (in a high level way) is next to impossible. When MTH finds a mistake (especially when parsing the HTBL resource), it will issue an alert rather than continuing. The error recovery system is implemented via an enumerated type, and can be easily user-extended; it is defined as follows:

/* 7 */

 typedef  enum {
 /* Missing ID=128 of the help table */
 err_no_HTBL,  /* Not enough help screens specified */
 err_min_res,    /* Intro screen must be a picture */
 err_intro_pict, /*Not a TEXT or PICT screen (typo?) */
 err_bad_type, /* PICT for this topic isn’t there */
 err_no_pict,  /* TEXT for this topic isn’t there */
 err_no_text
 }  ErrorTypes;

This extensive checking has saved me a lot of grief on more than one occasion. For an example of MTH error recovery in action, refer to Figure 9.

General Assumptions

The underlying assumption to my data structures is that there is some symmetry to your ‘PICT’ resource naming conventions. For example: let’s say topic one has three paint screens of information, the first PICT being id=300, and last being id=302. MTH now knows not only where your PICT screens are for this topic, but how many (300...302 = 3 screens) as well. In the case of TEXT screens, just make the start TEXT id equal to the end id.

Before running MTH, make sure you also bring in any needed resources, such as my DLOG=301, which warns the user if the file “Help Data” was not found; otherwise my code will look for resources that aren’t there, and a system exception will result. All of the necessary resources are located in a file entitled Templates.RSRC.

One other point worth mentioning are the sizes of the ‘PICT’ items that are displayed: this was one thing I did hard-code, to enforce uniformity and structure. If you try to display a picture that is larger or smaller that what MTH expects, you will notice some distortion. Is this a drawback? This depends on your point of view. If you look at my PICT templates, they all have a gray border around them, telling me exactly what size I have available to me at screen design time. This forces a homogeneous, predictable construction of help screens. On the other hand, if I had allowed for dynamic picture sizing, then you would have no idea what your screen would look like in the dialog until runtime.

Alter to suit

From the start, the MTH dialog box was designed to be flexible. For instance, when my good friend Jim evaluated MTH, he said something like this: “this is nice, but resize this, move this over here, and get rid of that...” Most of his suggestions I implemented, while others I left for MacTutor readers to decide. Because MTH was designed from the start to be customized, this is no problem. Let’s say you want to stretch the “help topics” list vertically, to allow the user to see more at once. Will this cause problems? No - MTH will see what you have done, and adjust it’s scroll bars for that list, automatically. What if you want to move or resize the display area? Again, this isn’t a problem: MTH will correctly draw the text scrolling control no matter what you do to that user item.

If you want to modify this code to be completely modeless (i.e. switch back and forth between your application specific windows), this will just involve some modification to the Help_Event_Loop() function; I leave it to the reader to decide if this is necessary for your particular application.

Tools of The Trade

The two primary tools I’ve used for this project were THINK C™ by Symantec and the Prototyper™ by SmethersBarns. I used Prototyper to generate the code fragment for the scrolling list of topics in the help screen. Because scrolling lists of anything tend to be very tedious to code, it seemed like a good exercise for Prototyper.

If you don’t own Prototyper, you should consider buying it. While it will not generate a complete application ready for sale, it will generate valuable fragments that will literally save you weeks of research.

I do have one suggestion for future improvement though: Prototyper (version 2.1) hard-codes the coordinates of controls - rather than using the much more flexible user-items within DLOG templates; this really makes life difficult. Using user-items would add another dimension of flexibility to this programming tool.

Where To Go From Here

The complete sources to MTH and other supporting materials have been donated to MacTutor. Please contact MacTutor for more details on how to obtain the source code disk for this article.

The demo program for MTH also comes with a complete online manual - describing everything from how to call code resources from different languages to creating your own help screens. It contains all the information you’ll need to get MTH up and running within your own application.

I found MTH to be very robust, using about 150K of RAM - with room to spare. I tested it on a Mac Plus, Mac SE and Mac II, using version 6.0.2 of the system software.

The End...

A special thanks to Jim Goebel of Ypsilanti, Michigan and Darrell LeBlanc of THINK Technologies for their pointers on reading in custom resources.

In porting MacTutor Help into your own code, you may come up with even more enhancements. If you do, I’d like to hear about them. If you have any comments about this software, you can contact me by using the GEnie service (send electronic mail to JPILLERA).

BIBLIOGRAPHY

Apple Computer, Inc. (1989) “Resource Compiler and Decompiler,” pp. 301-340 in MPW 3.0 Development Environment Manual / Volume I, Apple Programmer’s and Developer’s Association, Cupertino, California.

Apple Computer, Inc. (1985) “The Control Manager,” pp. 309-338 in Inside Macintosh / Volume I, Addison-Wesley Publishing Company, Reading, Massachusetts.

Apple Computer, Inc. (1985) “The Dialog Manager,” pp. 397-433 in Inside Macintosh / Volume I, Addison-Wesley Publishing Company, Reading, Massachusetts.

Apple Computer, Inc. (1985) “The List Manager Package,” pp. 259-282 in Inside Macintosh / Volume IV, Addison-Wesley Publishing Company, Reading, Massachusetts.

Apple Computer, Inc. (1985) “The Resource Manager,” pp. 101-134 in Inside Macintosh / Volume I, Addison-Wesley Publishing Company, Reading, Massachusetts.

Apple Computer, Inc. (1985) “The Scrap Manager,” pp. 451-464 in Inside Macintosh / Volume I, Addison-Wesley Publishing Company, Reading, Massachusetts.

Apple Computer, Inc. (1985) “The TextEdit Manager,” pp. 371-396 in Inside Macintosh / Volume I, Addison-Wesley Publishing Company, Reading, Massachusetts.

Nedry, Larry (1989) “Mousehole Report - Executing Code Resources,” p. 104 in MacTutor Magazine, Volume 5, Number 8, Placentia, California.

Rausch, William (1987) “List Manager Inspires Help Function Solution,” p. 62 in MacTutor Magazine, Volume 3, Number 4, Placentia, California.

SmethersBarnes, Inc. (1989) “Prototyper User Manual,” SmethersBarnes Publishing Division, Portland, Oregon.

The Symantec Corporation (1988) “THINK’s LightspeedC User’s Manual,” Bedford, Massachusetts.

1. FullWrite is a trademark of Ashton-Tate, Inc.

2. GEnie is a trademark of the General Electric Company.

3. THINK C is a trademark of the Symantec Corporation.

4. MPW and ResEdit are trademarks of Apple Computer, Inc.

5. Prototyper is a trademark of SmethersBarnes, Inc.

6. ZBasic is a trademark of Zedcor, Inc.

Listing:  help.h

/**********************************************\
| Purpose :: This header file contains all of  |
| the global data structures and function      |
| prototypes necessary for this program to     |
| compile correctly into a code resource.      |
|----------------------------------------------|
| Copyright Joe Pillera, 1990.                 |
| Copyright MacTutor Magazine, 1990.           |
| All Rights Reserved.                         |
\**********************************************/
/* Maximum number of topics */
#define MAX_TOPICS 50

/* Dialog box information */
#define Help_Window300
#define Help_Not_Found  301
#define Help_Error 302
#define OK_Button1
#define Topics_Area2
#define Display_Area 3
#define Next_Button4
#define Prev_Button5
#define Message_Area 7

/* Constant for first screen in a series */
#define Initial_Picture 0

/* Are we in picture or text mode? */
typedef  enum {
 pict,
 text
}  ViewMode;

/* Errors I check for */
typedef  enum {
 err_no_HTBL, /* Missing ID=128 of the help table */
 err_min_res,  /* Not enough help screens specified */
 err_intro_pict, /* Intro screen must be a picture */
 err_bad_type, /* Not a TEXT or PICT screen (typo?) */
 err_no_pict, /* The PICT for this topic isn’t there */
 err_no_text /* The TEXT for this topic isn’t there */
}  ErrorTypes;

/* Read the HTBL resource information */
typedef struct {
 int  First_Menu;
 int  Last_Menu;
 int  Number_Of_Topics;
 long type;
 int  resource_start;
 int  resource_end;
 Str255 topic_name;
} **Help_Info_Handle;

/* Other constants */
#define NOT_FOUND-1
#define ON0
#define OFF 255
#define NIL 0L
#define FRONT    (WindowPtr)-1

/* Function prototypes */
extern  voidAdd_List_String ( Str255 *, ListHandle );
extern  voidBold_Button( DialogPtr, int );
extern  voidCenter_Window ( DialogPtr, int, Boolean );
extern Boolean Create_Help( void );
extern  voidDialog_String ( DialogPtr, int, char * );
extern  voidDisplay_Pict( int );
extern  voidDisplay_Text( void );
extern  voidError_Message( ErrorTypes );
extern  voidHelp_Event_Loop( void );
extern  voidHandle_List_Event( int );
extern  voidHandle_Update( void );
extern  voidInit_Help( void );
extern  voidKill_Window( void );
extern  pascal void main( void );
extern  pascal void My_Scroll_Filter ( ControlHandle, int );
extern  int ParseInt( char ** );
extern  longParseOSType( char ** );
extern  voidParseString( char *, char ** );
extern  voidpStrCat( char *, char *, char * );
extern  voidpStrCopy( char *, char * );
extern  voidRefresh_Topics( void );
extern  voidScroll_Picture( int );
extern  voidScroll_Text( int, int );
extern  voidSet_Button_State( int, Boolean );
extern  voidUser_Message( char * );
Listing:  help.c

/**********************************************\
| Purpose :: This is the code resource version |
| of the MacTutor Help facility.  The code is  |
| application independent, and can be used in  |
| just about any program.  To run the MTH, get |
| a handle to my HELP resource (ID=128) and    |
| dereference it twice.  Now execute what this |
| handle points to.  For more information,     |
| please refer to the examples I provide on    |
| this month’s source code disk.               |
|----------------------------------------------|
| Copyright Joe Pillera, 1990.                 |
| Copyright MacTutor Magazine, 1990.           |
| All Rights Reserved.                         |
\**********************************************/
#include “help.h”

/* -- Global variables -- */

/* Handle for list in dialog */
static  ListHandle  My_List_Handle; 
static  Rect  List_Rect; /* Rect (user-item) of topic list */
static  Rect  Display_Rect; /* Rect (user-item) of display */
static  Rect  Scroller_Rect; /* Rect (computed) of scrollbar*/
static  Rect  Dest_Rect; /* Destination of TE rectangle */
static  Rect  View_Rect; /* Actual viewing region of text */
static  Rect  Frame_Rect; /* Rectangle to frame for text */
static  int  Help_Topic; /* What topic user wants info on?*/
static  int  Current_Pict; /* Latest pict ID displayed */
static  DialogPtr  helpPtr; /* Pointer to help dialog box */
static  TEHandle  myTEHandle; /* TE Manager data structure */
static  Handle  myTEXTHandle; /* My handle to TEXT resource */
static  ViewMode  Last_Type; /* Was last one pict or text? */
static  ControlHandle  myControl; /* Handle to text scroller*/
static  GrafPtr  savePort; /* Save pointer to old grafport */
static  int  refnum; /* Result of trying to open help file*/
/* Keep track of the screen types */
static  ViewMode  screen_mode[MAX_TOPICS];
static  int  Number_Of_Topics; /* Number of  help topics */
static  int  First_Menu; /* Disable his menus */
static  int  Last_Menu;
static  int  START; /* Array traversal constants */
static  int  FINIS;
/* Log positions of ‘PICT’ & ‘TEXT’ resources */
static  Str255  topic_name[MAX_TOPICS];
static  int     resource_start[MAX_TOPICS];
static  int     resource_end[MAX_TOPICS];

static void  Add_List_String(theString,theList)
Str255   *theString;
ListHandle   theList;
{ 
 short  theRow;  /* The Row to add */
 Point  cSize;   /* Pointer to a list cell */
 
 if (theList != NIL) {
 cSize.h = 0;
 theRow = LAddRow(1, 32000, theList);
 cSize.v = theRow;
 LSetCell((*theString + 1), *theString[0], cSize,theList);
 LDraw(cSize, theList);
 } 
}

static void Bold_Button( dPtr, itemNum )
DialogPtr dPtr;
intitemNum;
{
 Rect iBox;   /* Rect of the button */
 int  iType;   /* Type of dialog item */
 Handle iHandle; /* Pointer to the item */
 PenState oldPenState;  /* Save old state */
 
 SystemTask();
 SetPort(dPtr);
 GetPenState(&oldPenState);
 GetDItem(dPtr,itemNum,&iType,&iHandle,&iBox);
 InsetRect(&iBox, -4, -4);
 PenSize(3,3);
 FrameRoundRect(&iBox, 16, 16);
 SetPenState(&oldPenState);
}

static void Center_Window(theWindow,bumpUp,isModeless)
DialogPtr theWindow;
intbumpUp;
Boolean isModeless;
{
 Rect  tempRect;  /* Temporary rectangle */
 int  pixels;     /* Raise from center */
 int  menuBar = 20; /* 20 pixels (menu bar) */
 
 /* Compute centering information */
 tempRect.top = theWindow->portRect.top;
 tempRect.left = theWindow->portRect.left;
 tempRect.bottom = theWindow->portRect.bottom;
 tempRect.right = theWindow->portRect.right;
 tempRect.top = ((screenBits.bounds.bottom +
 menuBar + (isModeless ? 20 : 0) - screenBits.bounds.top) - 
 (tempRect.bottom - tempRect.top)) / 2;
 tempRect.left = ((screenBits.bounds.right -
 screenBits.bounds.left) - 
 (tempRect.right - tempRect.left)) / 2;

 /* Apply any bump-up factor */
 pixels = tempRect.top * (bumpUp / 100.0);
 tempRect.top = tempRect.top - pixels;

 /* Now center window & make it visible */
 MoveWindow(theWindow,tempRect.left, tempRect.top, TRUE);
 SetPort(theWindow);
}

static Boolean Create_Help()
{
 int  whatHit; /* DITL item user selected */
 int  DType;   /* Type of dialog item */
 Point  cSize;  /* Ptr to cell in a list */
 Rect  tempRect; /* Temporary rectangle */
 Rect  dataBounds; /* Rect to setup list */
 Handle  DItem;  /* Handle to dialog item */
 int  index; /* Loop thru topic list names */
 Rect  iBox; /* The rect for next/prev btns */
 int  iType; /* Type of dialog item (button) */
 Handle  iHandle; /* Pointer to the item */
 /* Used to determine List element height */
 FontInfo  ThisFontInfo;

 /* Preserve pointer to former graf port */
 GetPort(&savePort);

 /* First make sure help file is there! */
 refnum = OpenResFile(“\pHelp Data”);
 if (refnum == NOT_FOUND) {
   helpPtr = GetNewDialog(Help_Not_Found, NIL, FRONT);
   Center_Window( helpPtr,50, FALSE );
   ShowWindow(helpPtr);
   Bold_Button( helpPtr, OK_Button );
   SysBeep(1);
   ModalDialog(NIL, &whatHit);
   DisposDialog(helpPtr);
   SetPort(savePort);
   return(FALSE);
 }
 
 /* Set up pointers to the intro screen */
 Help_Topic = START;
 Current_Pict = resource_start[START];
 Last_Type = screen_mode[START];
 
 /* Bring up the help screen */
 helpPtr = GetNewDialog(Help_Window, NIL, FRONT);
 Center_Window(helpPtr,0,TRUE);
 ShowWindow(helpPtr);
 SelectWindow(helpPtr);
 SetPort(helpPtr);
 Bold_Button(helpPtr,OK_Button);
 
 /* Hide next & prev btns for intro screen */
 GetDItem(helpPtr, Next_Button, &iType, &iHandle, &iBox);
 HideControl((ControlHandle)iHandle);
 GetDItem(helpPtr, Prev_Button, &iType, &iHandle, &iBox);
 HideControl((ControlHandle)iHandle);
 
 /* Disable Next & Previous buttons for now */
 Set_Button_State( Next_Button, FALSE );
 Set_Button_State( Prev_Button, FALSE );

 /* -- Now build the list -- */
 GetDItem(helpPtr, Topics_Area, &DType, &DItem, &tempRect);
 SetRect(&List_Rect, tempRect.left,
 tempRect.top, tempRect.right, tempRect.bottom);
 /* Compute list-area (user-item) */
 tempRect = List_Rect;
 /* Make room for scroll bar on the right */
 tempRect.right = tempRect.right - 15;
 /* Safety check */
 if (tempRect.right <= (tempRect.left + 15)) 
 tempRect.right = tempRect.left + 15;
 /* Frame it */
 InsetRect(&tempRect, -1, -1);
 FrameRect(&tempRect);
 InsetRect(&tempRect, 1, 1);
 SetRect(&dataBounds, 0, 0, 1, 0);
 /* Width of the list */
 cSize.h = tempRect.right - tempRect.left;
 /* Geneva lists look more professional... */
 thePort->txFont = geneva;
 thePort->txSize = 10;
 /* Get the current font sizes */
 GetFontInfo(&ThisFontInfo);
 /* Height of a cell */
 cSize.v = ThisFontInfo.ascent +
 ThisFontInfo.descent + ThisFontInfo.leading;
 /* Make the list */
 My_List_Handle =  LNew(&tempRect, &dataBounds,
 cSize, 0, helpPtr, TRUE, FALSE, FALSE, TRUE);
 /* Set the attributes */
 (*My_List_Handle)->selFlags = lOnlyOne + lNoNilHilite;
 /* Draw the list */
 LDoDraw(TRUE, My_List_Handle);
 cSize.v = 0;
 LSetSelect(TRUE, cSize, My_List_Handle);
 Refresh_Topics();  
 
 /* Fill in the initial list contents */
 for ( index = START + 1; index <= FINIS; index++ ) 
   Add_List_String((Str255 *)topic_name[index],
 My_List_Handle);
 
 /* Compute critical info once & for all */
 GetDItem(helpPtr, Display_Area, &DType, &DItem, &tempRect);
 
 /* The entire display area */
 SetRect(&Display_Rect, tempRect.left,
 tempRect.top, tempRect.right, tempRect.bottom);
                   
 /* The scroll bar to the right of it */
 SetRect(&Scroller_Rect, Display_Rect.right - 16, Display_Rect.top, Display_Rect.right, 
Display_Rect.bottom);

 /* Create the text-edit clipping regions */
 SetRect(&Frame_Rect,Display_Rect.left,
 Display_Rect.top, Display_Rect.right-17,
 Display_Rect.bottom);

 /* Compute Text Edit destination rectangle */
 SetRect(&Dest_Rect, Frame_Rect.left+4,
 Frame_Rect.top+4, Frame_Rect.right-17, 9000);
 
 /* The clipping region for the actual text */
 SetRect(&View_Rect,Dest_Rect.left,
 Dest_Rect.top+2, Dest_Rect.right, Display_Rect.bottom-7);

 /*  For simplicity’s sake, assume intro  */ 
 /*  screen is always a picture resource. */
 Display_Pict( Initial_Picture ); 
 return(TRUE);
}

static void 
Dialog_String( theDialog, theItem, theStr )
DialogPtr theDialog;
inttheItem;
char    *theStr;
{
 Rect  iBox;  /* The rect for the item */
 int  iType;  /* Type of dialog item */
 Handle  iHandle; /* Pointer to item */

 GetDItem(theDialog, theItem, &iType, &iHandle, &iBox);
 SetIText(iHandle, theStr);
}

static void Display_Pict( whichOne )
int whichOne;
{
 int  nextPict; /* Next pict to display */
 Rect  iBox;  /* The rect of the picture */
 int  iType;  /* Type of dialog item */
 Handle  iHandle; /* Pointer to the item */
 PicHandle  newPict; /* Ptr to pict area */
 int  mesg_this_one; /* Screen we’re on now */
 int  mesg_max_one; /* Tot # scrns in topic */
 Str255  s1,s2,s3,s4,s5; /* Temp strings */

 /* Dispose of previous text record, if */
 /* there was one */
 if (Last_Type == text) {
 DisposeControl(myControl);
 TEDispose(myTEHandle); 
 }

 /* Enable the next & previous buttons? */
 if ( (resource_end[Help_Topic] -
 resource_start[Help_Topic]) >= 1 ) {
 GetDItem(helpPtr, Next_Button, &iType, &iHandle, &iBox);
 ShowControl((ControlHandle)iHandle);
 GetDItem(helpPtr, Prev_Button, &iType, &iHandle, &iBox);
 ShowControl((ControlHandle)iHandle);
 }
 else {
 GetDItem(helpPtr, Next_Button, &iType, &iHandle, &iBox);
 HideControl((ControlHandle)iHandle);
 GetDItem(helpPtr, Prev_Button, &iType, &iHandle, &iBox);
 HideControl((ControlHandle)iHandle);
 }
 
 /* Compute which picture to display */
 nextPict = resource_start[ Help_Topic ] + whichOne;
 mesg_this_one = nextPict - resource_start[ Help_Topic ] + 1;
 mesg_max_one = resource_end[ Help_Topic ] -
 resource_start[ Help_Topic ] + 1;

 /* Display picture */
 newPict = GetPicture(nextPict);

 /* Saftey check! */
 if (newPict == NIL) 
   Error_Message( err_no_pict );

 GetDItem(helpPtr, Display_Area, &iType, &iHandle, &iBox);
 EraseRect(&iBox);
 DrawPicture(newPict,&iBox);
 
 /* Avoid an unnecessary update event */
 ValidRect(&iBox);

 /* Compute new text message */
 NumToString(mesg_this_one,s1);
 NumToString(mesg_max_one,s2);
 pStrCat( “\pStatus:  Screen “, (char *) s1, (char *) s3);
 pStrCat( (char *) s3, “\p of “, (char *) s4 );
 pStrCat( (char *) s4, (char *) s2, (char *) s5);
 User_Message((char *)s5);
 Current_Pict = nextPict;
 Last_Type = pict;
}

static void Display_Text()
{
 int  iType;  /* Type of dialog item */
 Handle iHandle; /* Ptr to the item */
 Rect  tempRect; /* For moving scroll bar */
 
 /* Dispose of previous text */
 /* record, if there was one */
 if (Last_Type == text) {
 HUnlock(myTEXTHandle);
 HUnlock(myTEHandle);
 DisposeControl(myControl);
   TEDispose(myTEHandle); 
 }

 /* Get a handle to the TEXT resource */
 myTEXTHandle = GetResource(‘TEXT’,
 resource_start[ Help_Topic ]);

 /* Saftey check! */
 if (myTEXTHandle == NIL) 
 Error_Message( err_no_text );

 HLock(myTEXTHandle)

 /* Hide the next & previous buttons */
 GetDItem(helpPtr, Next_Button, &iType, &iHandle, &tempRect);
 HideControl((ControlHandle)iHandle);
 GetDItem(helpPtr, Prev_Button, &iType, &iHandle, &tempRect);
 HideControl((ControlHandle)iHandle);

 /* Erase the display area */
 EraseRect(&Display_Rect);
 
 /* Bring up the control */
 myControl = NewControl(helpPtr,&Scroller_Rect,
 “”,TRUE,1,1,10,16,NIL);
 HiliteControl(myControl,ON);
 /* Dim the picture scrolling buttons */
 Set_Button_State( Next_Button, FALSE );
 Set_Button_State( Prev_Button, FALSE );
 
 /* Frame the text box */
 MoveTo(Frame_Rect.right+1,Frame_Rect.top);
 LineTo(Frame_Rect.left,Frame_Rect.top);
 LineTo(Frame_Rect.left,Frame_Rect.bottom-1); 
 LineTo(Frame_Rect.right+1, Frame_Rect.bottom-1);  
 
 /* Create a text record and get a */
 /* handle to the TEXT resource */
 myTEHandle = TENew(&Dest_Rect,&View_Rect);
 HLock(myTEHandle);
 
 /* Display the goods */
 TESetText(*myTEXTHandle,
 SizeResource(myTEXTHandle), myTEHandle);
 TEUpdate(&View_Rect,myTEHandle);
 User_Message((char *) “\pStatus:  < Text Mode >”);
    
 /* Do we need an active scroll bar? */
 if ( ( (View_Rect.bottom - View_Rect.top) /
 (*myTEHandle)->lineHeight)  < ( (*myTEHandle)->nLines ) )
   HiliteControl(myControl,ON);
 else
   HiliteControl(myControl,OFF);
 
 /* Avoid an unnecessary update event */
 ValidRect(&Display_Rect);
 
 /* Set important values and exit */
 SetCtlMax(myControl,((*myTEHandle)->nLines - 
 ((View_Rect.bottom - View_Rect.top)/
 (*myTEHandle)->lineHeight)));
 Last_Type = text;
}

static void Error_Message( theError )
ErrorTypestheError;
{
 Rect  iBox;   /* Rectangle for button */
 int  iType;   /* Type of dialog item */
 Handle  iHandle; /* Pointer to the item */
 DialogPtr  theBox;  /* Pointer to dialog */
 GrafPtr  savePort;  /* Save old graf port */
 short  whatHit;  /* User’s selection */
 
 /* Bring up the correct alert */
 theBox = GetNewDialog(Help_Error, NIL, FRONT);
 
 /* Position it */
 Center_Window(theBox,50,FALSE);
 ShowWindow(theBox);
 Bold_Button(theBox, OK_Button);
 SysBeep(1);

 /* Fill in text message */
 switch (theError) {
 case err_no_HTBL :
   Dialog_String(theBox, 3, 
 (char *) “\pNo HTBL (Help Table)
 [ID=128] resource”);
   Dialog_String(theBox, 4, (char *)
 “\pwas found on your resource fork!”);
   Dialog_String(theBox, 5, (char *)
 “\pPlease create one before continuing...”);
   break;
 case err_min_res :
   Dialog_String(theBox, 3, (char *) 
 “\pYou should have at least an intro”);
   Dialog_String(theBox, 4, (char *)
 “\pscreen and one help screen for your”);
   Dialog_String(theBox, 5, (char *) “\phelp system!”);
   break;
 case err_intro_pict :
   Dialog_String(theBox, 3, (char *) 
 “\pThis program assumes that the”);
   Dialog_String(theBox, 4, (char *) 
 “\pintroduction screen is always a”);
   Dialog_String(theBox, 5, (char *) 
 “\ppicture!  Please change this screen.”);
   break;
 case err_bad_type :
   Dialog_String(theBox, 3, (char *)
 “\pScreen types are either PICT or TEXT.”);
   Dialog_String(theBox, 4, (char *) 
 “\pOne of your HTBL fields for screen”);
   Dialog_String(theBox, 5, (char *) 
 “\ptype is incorrect...”);
   break;
 case err_no_pict :
   Dialog_String(theBox, 3, (char *)
  “\pThe PICT(s) for this topic do not”);
   Dialog_String(theBox, 4, (char *)
  “\pexist!  Very uncool.  I will exit”);
   Dialog_String(theBox, 5, (char *)
  “\pto the Finder rather than crash...”);
   break;
 case err_no_text :
   Dialog_String(theBox, 3, (char *)
  “\pThe TEXT for this topic does not”);
   Dialog_String(theBox, 4, (char *)
  “\pexist!  Very uncool.  I will exit”);
   Dialog_String(theBox, 5, (char *)
  “\pto the Finder rather than crash...”);
   break;
 default : break;
 }
 
 /* Enter an event loop and poll the user */ ModalDialog(NIL, &whatHit); 

 DisposDialog(theBox);
 ExitToShell();
}

static void Handle_List_Event( whatHit )
int  whatHit;
{
 /* Get the selected topic from the user */
 Help_Topic = whatHit + 1;
 /* If there is >1 screens, enable */
 /* the “Next” button */
 if (resource_end[Help_Topic] -
 resource_start[Help_Topic]) {
 Set_Button_State( Next_Button, TRUE );
 Set_Button_State( Prev_Button, FALSE );
 }
 else {
 Set_Button_State( Next_Button, FALSE );
 Set_Button_State(Prev_Button, FALSE );
 }
 
 /* Display first screen in topic */
 if ( screen_mode[ Help_Topic ] == pict )
   Display_Pict( Initial_Picture );
 else if ( screen_mode[ Help_Topic ] == text )
   Display_Text();
 else
   SysBeep(1);
}

static void Handle_Update()
{
 Rect  iBox;  /* The rect of the picture */
 int  iType; /* Type of dialog item */
 Handle  iHandle;  /* Ptr to the item */
 PicHandle  thePict; /* Ptr to the pict area */
 GrafPtr  oldPort; /* Save old graf port */

 GetPort(&oldPort);
 SetPort(helpPtr); 
 BeginUpdate(helpPtr);
 Refresh_Topics();
 Bold_Button(helpPtr,OK_Button);

 /* Specific code for text or pictures... */
 if ( screen_mode[ Help_Topic ] == text ) {
 MoveTo(Frame_Rect.right+1,Frame_Rect.top); 
 LineTo(Frame_Rect.left,Frame_Rect.top);
 LineTo(Frame_Rect.left, Frame_Rect.bottom-1); 
 LineTo(Frame_Rect.right+1, Frame_Rect.bottom-1);     
 TEUpdate(&View_Rect,myTEHandle);
 }
 else if (screen_mode[ Help_Topic ] == pict) {
 thePict = GetPicture(Current_Pict);
 /* Saftey check! */
 if (thePict == NIL) {
     SysBeep(1);
     return;
 }
 GetDItem(helpPtr, Display_Area, &iType, &iHandle, &iBox);
 EraseRect(&iBox);
 DrawPicture(thePict,&iBox);
 }
 DrawDialog(helpPtr);
 EndUpdate(helpPtr);
 SetPort(oldPort);
}

static void Help_Event_Loop()
{
 EventRecord  theEvent; /* Current real-time user event */
 Point  where; /* Where (mouse loc) the event occured */
 Rect  tempRect;  /* Temporary rectangle */
 Point  myPt; /* Current list selection point */
 WindowPtr  wPtr; /* The pointer to the current window */
 int thePart; /* The type of mouse-down that occured */
 int  key; /* Did the user hit the <return> key? */
 int  whatHit; /* Integer id of the dialog item user hit */
 Rect  DRect; /* Rectangle used for finding hit point */
 short  DType; /* Type of dialog item (for OK button) */
 int  itemHit; /* Get selection from the user */
 Handle  DItem; /* Handle to the dialog item */
 long  ticks; /* Highlight ok button when <return> hit */
 Boolean  DoubleClick; /*Flag to see if doubleclick on list*/
 int  oldValue; /* Former value of scroll bar before event */
 int  rc; /* Return code from TrackControl() - ignore */
 long  cellHit; /* Find out where user single-clicked */
 ControlHandle  aControl; /* Event took place in scroller */
 /* Nicety: prevent redundant processing */
 static  int  lastHit = -1;

 /* -- Enter the event loop -- */
 InitCursor();
 HiliteMenu(0);
 for (;;) {
 SetPort(helpPtr);
 SelectWindow(helpPtr);
 InitCursor();

 if (GetNextEvent(everyEvent, &theEvent)) {
    switch (theEvent.what) {
      case updateEvt:
 if ((WindowPtr)theEvent.message == helpPtr) 
   Handle_Update();
   break;
   
      case keyDown:
 /* If it’s <return> key I want it */
 key = ((unsigned char)theEvent.message & charCodeMask);
 if ( (key == ‘\r’) ) {
   /* Treat like mouse-down in OK */
   SetPort(helpPtr);
   GetDItem(helpPtr, OK_Button, &DType, &DItem, &DRect);

   HiliteControl((ControlHandle)
   DItem,  1);
   Delay(8,&ticks);
   Kill_Window();
   return;
 }
 break;

      case mouseDown:
 thePart = FindWindow(theEvent.where, &wPtr);
 /* The following is very tricky   */
 /* code: it allows you to switch  */
 /* tasks under Multifinder, but   */
 /* will not let you pull down any */
 /* of the disabled menus.         */
        if ((DialogPtr)wPtr != helpPtr) {
   if (thePart != inMenuBar)
     SysBeep(1);
     continue;
   } 
 switch (thePart) {
   case inMenuBar:
     rc = MenuSelect(theEvent.where);
     break;

   case inDrag:
     tempRect = screenBits.bounds;
     SetRect(&tempRect, tempRect.left
       + 10, tempRect.top + 25, 
       tempRect.right - 10, tempRect.bottom - 10);
     DragWindow(helpPtr, theEvent.where, &tempRect);
      break;

   case inContent:
     if (DialogSelect(&theEvent,
 &helpPtr, &whatHit)) {
       myPt = theEvent.where;

     GlobalToLocal(&myPt);
 
     /* Is the user all done? */
     if (whatHit == OK_Button) {
   Kill_Window();
 return;
     }
       else if ((whatHit == Next_Button) || 
       (whatHit == Prev_Button))
     Scroll_Picture( whatHit );

          /* Was it an event in the */
     /* topics list? */
     else if (PtInRect(myPt, &List_Rect) == TRUE) {
 DoubleClick = LClick(myPt,
    theEvent.modifiers, My_List_Handle);
 cellHit = LLastClick (My_List_Handle);
 itemHit = HiWord(cellHit);
 if ((itemHit >= 0) && (itemHit != lastHit)) {
   lastHit = itemHit;
  Handle_List_Event(itemHit);
 }
     } 
  
     /* A text scroller event? */
     else if ( (PtInRect(myPt,
  &Scroller_Rect))  && (screen_mode[ Help_Topic ]
  == text) ) {
   thePart = FindControl(myPt, wPtr, &aControl);
 oldValue = GetCtlValue(aControl);
   if ( thePart == inThumb ) {
          rc=TrackControl(aControl, myPt, (ProcPtr)0);
   Scroll_Text( oldValue, GetCtlValue(aControl));
   }
   else if ( (thePart == 
 inUpButton) || (thePart ==
 inDownButton) || (thePart == inPageUp)
 || (thePart == inPageDown) ) 
          rc=TrackControl(aControl, myPt, &My_Scroll_Filter);
 }
     }
     break;
   }
      }
 }
 }
}

void Init_Help( )
{
 int  index; /* Loop thru user’s info */
 char  *HTBL_ptr; /* The base pointer*/
 Help_Info_Handle  h; /* Cast HTBL to this */
 long  mode;  /* Longint of PICT or TEXT */
 char  ch;  /* First letter of PICT or TEXT */
 
 /* Get handle to the HTBL resource */
 h = (Help_Info_Handle) GetResource(‘HTBL’,128);
 
 /* No HTBL=128 resource? */
 if ((h == NIL) || (ResErr != noErr))
 Error_Message( err_no_HTBL );
 
 /* Lock it */
 HLock(h);
 
 /* Set pointer to start of HTBL resource */
 HTBL_ptr = (char *) *h;
 First_Menu = ParseInt( (char **) &HTBL_ptr );
 Last_Menu = ParseInt( (char **) &HTBL_ptr );
 START = 0;
 FINIS = ParseInt( (char **) &HTBL_ptr );

 /* Enough help screens to proceed? */
 if (FINIS < 1) 
 Error_Message( err_min_res );

 /* Now loop thru all screens */
 for (index = 0; index <= FINIS; index++) {
 /* Is this a ‘PICT’ or ‘TEXT’ screen? */
 mode = ParseOSType( (char **) &HTBL_ptr );
 ch = (char)(mode >> 24);
 if ( (ch == ‘P’) || (ch == ‘p’) ) 
    screen_mode[index] = pict;
 else if ( (ch == ‘T’) || (ch == ‘t’) )
    screen_mode[index] = text;
 else 
    /* Bad screen type! */
    Error_Message( err_bad_type );
     
     /* Is intro screen a PICT? */
     if ((index == 0) && (screen_mode[index] != pict)) 
   Error_Message( err_intro_pict );

 /* Get resource bounds */
 resource_start[index] = ParseInt( (char **) &HTBL_ptr );
 resource_end[index] = ParseInt( (char **) &HTBL_ptr );

 * Retrieve title of this topic */
 ParseString( (char *)topic_name[index],
    (char **) &HTBL_ptr );
 }
 HUnlock(h);
}

static void Kill_Window()
{
 if (Last_Type == text) {
   TEDispose(myTEHandle); 
 }
 LDispose(My_List_Handle);
 DisposDialog(helpPtr);
 CloseResFile(refnum);
 SetPort(savePort);
}

pascal void  main()
{
 MenuHandle thisMenu; /* Menu bar to disable */
 int  index;  /* Loop thru menu resources */
 Handle  h;   /* Pointer to HELP resource */
 
 /* Save the state of the registers */
 RememberA0();
 SetUpA4();
 
 /* Lock my code resource */
 asm {
 _RecoverHandle
 move.l  a0, h
 }
 HLock(h);
 
 /* Re-initialize QuickDraw! */
 InitGraf(&thePort);
 
 /* Initialize the help system */
 Init_Help();
 
 /* Bring up the help box */
 if ( Create_Help() ) {
 /* Disable The Menus */
 for (index = First_Menu; index <= Last_Menu; index++) {
     thisMenu = GetMHandle(index);
     DisableItem(thisMenu,0);
 }
 DrawMenuBar();
 
 /* Trap help window events */
 Help_Event_Loop();
 
 /* Re-enable the menu bar and exit */
 for (index = First_Menu; index <= Last_Menu; index++) {
     thisMenu = GetMHandle(index);
     EnableItem(thisMenu,0);
 }
 DrawMenuBar();
 }
 /* Restore the registers & exit */
 HUnlock(h);
 RestoreA4();
}

static pascal 
void My_Scroll_Filter( theControl, thePart )
ControlHandle  theControl;
int  thePart;
{
 int  start, stop; /* Outer boundry values of the scroller */
 int  page; /* Computed amount to correctly page   */
 int  newValue; /* The new value to scroll up or down */
 int  oldValue; /* The previous value of the control */
 /* Slow down the action to appear natural */
 long  finalTicks;

 /* Get upper/lower limits for saftey checks */
 start = (*theControl)->contrlMin;
 stop  = (*theControl)->contrlMax;
 oldValue = GetCtlValue(theControl);

 /* Compute amount of scrolling for */
 /* page ups & page downs */
 page = (Display_Rect.bottom - Display_Rect.top) / 
 (*myTEHandle)->lineHeight;
 
 /* Get the current value of the control */
 newValue = GetCtlValue(theControl);

 /* Process the event accordingly */
 switch ( thePart ) {
 /* Scroll up one line */
 case inUpButton : 
   newValue -= 1;
   /* Saftey check! */
   if (newValue < start)
     newValue = start;
   break;
 /* Scroll down one line */
 case inDownButton: 
   newValue += 1;
   /* Saftey check! */
   if (newValue > stop)
     newValue = stop;
   break;
 /* Perform a page up */
 case inPageUp : 
   newValue -= page;
   /* Saftey check! */
   if (newValue < start)
     newValue = start;
   break;
 /* Perform a page down */
 case inPageDown : 
   newValue += page;
   /* Saftey check! */
   if (newValue > stop)
     newValue = stop;
   break;
 default : break;
 }
 
 /* Now set the control to the proper amount */
 SetCtlValue( theControl, newValue );
 Delay(2,&finalTicks); 
 Scroll_Text( oldValue, newValue );
}

static int ParseInt( pPointer )
char  **pPointer;
{
 int  result=0;  /* Final integer result */
 /* Store 2 halves of the int */
 unsigned char  hiWord, lowWord;

 /* Get the raw data */
 hiWord = (unsigned char) *((*pPointer)++);
 lowWord = (unsigned char) *((*pPointer)++);
 
 /* Now pack these into the integer */
 result = result | hiWord;
 result = (result << 8) | lowWord;

 return(result);
}

static long ParseOSType( pPointer )
char  **pPointer;
{
 long  result=0; /* Final longint to return */
 char nextByte; /* 1/4 of the longint */
 int  index;  /* Loop thru bytes in a long */
 
 /* Get the first of four bytes */
 nextByte = (char) *((*pPointer)++);
 result = result | (long) nextByte;
 
 /* Now loop thru the rest! */
 for (index = 1; index < sizeof(long); index++)    {
 nextByte = (char) *((*pPointer)++);
 result = (result << 8) | (long) nextByte;
 }
 return(result);
}

static void ParseString( pDest, pSource )
char  *pDest, **pSource;
{
 int iLen = *pDest++ = *((*pSource)++);

 while (--iLen >= 0) 
   *pDest++ = *((*pSource)++);
}

static void pStrCat( p1, p2, p3 )
register char *p1, *p2, *p3; 
{
 int len1, len2;
 
 len1 = *p1++;
 len2 = *p2++;
 
 *p3++ = len1 + len2;
 while (--len1 >= 0) *p3++=*p1++;
 while (--len2 >= 0) *p3++=*p2++;
}

static void pStrCopy( p1, p2 )
register char *p1, *p2; 
{
 int len;
 
 len = *p2++ = *p1++;
 while (--len>=0) *p2++=*p1++;
}

static void Refresh_Topics() 
{ 
 Rect  tempRect; /* Temporary rectangle */
 short    DType; /* Type of dialog item */
 Handle   DItem; /* Handle to dialog item */

 LUpdate(helpPtr->visRgn,My_List_Handle);
 tempRect = List_Rect;
 InsetRect(&tempRect, -1, -1);
 FrameRect(&tempRect);
} 

static void Scroll_Picture( whatHit )
int  whatHit;
{
 int  theMax;  /* High bounds of help topic */
 int  theMin;  /* Low bounds of that topic */
 
 /* Find out which pictures are relivant */
 theMin = resource_start[ Help_Topic ];
 theMax = resource_end[ Help_Topic ];
 
 /* Compute the new picture to display! */
 if ( (whatHit == Next_Button) && (Current_Pict < theMax) ) {
   Current_Pict++;
   Set_Button_State( Prev_Button, TRUE );
   if (Current_Pict < theMax)
   Set_Button_State( Next_Button, TRUE );
   else
   Set_Button_State( Next_Button, FALSE );
   Display_Pict( Current_Pict - theMin );
 }
 else if ( (whatHit == Prev_Button) &&
 (Current_Pict > theMin) ) {
   Current_Pict--;
   Set_Button_State( Next_Button, TRUE );
   if (Current_Pict > theMin)
   Set_Button_State( Prev_Button, TRUE );
   else
   Set_Button_State( Prev_Button, FALSE );
   Display_Pict( Current_Pict - theMin );
 }
 else
     SysBeep(1);
}

static void Scroll_Text( oldValue, newValue )
int  oldValue;
int  newValue;
{
  TEScroll(0, (oldValue - newValue) *
 (*myTEHandle)->lineHeight, myTEHandle);
}

static void Set_Button_State(itemNum, state )
intitemNum;
Boolean state;
{
 Rect  iBox;  /* The rect for the button */
 int  iType;  /* Type of dialog item */
 Handle  iHandle; /* Pointer to the item */
 
 GetDItem(helpPtr, itemNum, &iType, &iHandle, &iBox);
 if ( state )
   HiliteControl((ControlHandle)iHandle, 0 );
 else
   HiliteControl((ControlHandle)iHandle, 255);
 }

static void User_Message( theStr )
char  *theStr;
{
 Rect  iBox;  /* The rect of the picture */
 int  iType;  /* Type of dialog item */
 Handle iHandle; /* Ptr to the item */

 GetDItem(helpPtr, Message_Area, &iType, &iHandle, &iBox);
 SetIText(iHandle, theStr);
 DrawDialog(helpPtr); 
}

Listing:  help.resources

/* ----------------------------------------*/
/* In order to operate correctly, MacTutor  */
/* Help needs these resources to be present */
/* on your application’s resource fork.     */
/* ----------------------------------------*/

#include “Types.r”
#include “SysTypes.r”

resource ‘DLOG’ (300, “Help Box”) {
 {38, 10, 320, 500},
 documentProc,
 invisible,
 noGoAway,
 0x1,
 300,
 “MacTutor Help System”
};

resource ‘DLOG’ (301, “No Data Box”) {
 {40, 40, 200, 380},
 dBoxProc,
 invisible,
 noGoAway,
 0x0,
 301,
 “Help Not Found”
};

resource ‘DLOG’ (302, “Error Msg”) {
 {60, 72, 250, 440},
 dBoxProc,
 invisible,
 noGoAway,
 0x0,
 302,
 “”
};

resource ‘DITL’ (300) {
{/* array DITLarray: 7 elements */
 /* [1] */
 {240, 363, 260, 423},
 Button {
 enabled,
 “Done”
 },
 /* [2] */
 {29, 315, 172, 470},
 UserItem {
 enabled
 },
 /* [3] */
 {5, 9, 277, 300},
 UserItem {
 enabled
 },
 /* [4] */
 {198, 401, 218, 461},
 Button {
 enabled,
 “Next”
 },
 /* [5] */
 {198, 321, 218, 391},
 Button {
 enabled,
 “Previous”
 },
 /* [6] */
 {12, 317, 27, 469},
 Picture {
 disabled,
 3001
 },
 /* [7] */
 {176, 333, 196, 441},
 StaticText {
 disabled,
 “Screen 1 of 1”
 }
 }
};

resource ‘DITL’ (301) {
{/* array DITLarray: 4 elements */
 /* [1] */
 {110, 242, 130, 302},
 Button {
 enabled,
 “OK”
 },
 /* [2] */
 {21, 19, 53, 51},
 Icon {
 disabled,
 2
 },
 /* [3] */
 {20, 77, 74, 319},
 StaticText {
 disabled,
 “I could not find the file \”Help 
    Data\”!  “
 “ It should be in the same folder as
    this”
 “ program.”
 },
 /* [4] */
 {86, 34, 137, 198},
 Picture {
 disabled,
 3002
 }
 }
};

resource ‘DITL’ (302) {
{/* array DITLarray: 6 elements */
 /* [1] */
 {152, 278, 172, 338},
 Button {
 enabled,
 “OK”
 },
 /* [2] */
 {22, 18, 54, 50},
 Icon {
 enabled,
 2
 },
 /* [3] */
 {55, 82, 76, 347},
 StaticText {
 disabled,
 “”
 },
 /* [4] */
 {83, 82, 103, 348},
 StaticText {
 disabled,
 “”
 },
 /* [5] */
 {110, 82, 130, 347},
 StaticText {
 disabled,
 “”
 },
 /* [6] */
 {21, 67, 39, 355},
 Picture {
 disabled,
 3003
 }
    }
};

resource ‘PICT’ (3001, purgeable) {
288,
{33, 23, 48, 175},
};

resource ‘PICT’ (3002, purgeable) {
621,
{85, 95, 136, 259},
};

resource ‘PICT’ (3003, purgeable) {
4134,
{45, 136, 63, 424},
};

 
AAPL
$119.00
Apple Inc.
+1.40
MSFT
$47.75
Microsoft Corpora
+0.28
GOOG
$540.37
Google Inc.
-0.71

MacTech Search:
Community Search:

Software Updates via MacUpdate

Skype 7.2.0.412 - Voice-over-internet ph...
Skype allows you to talk to friends, family and co-workers across the Internet without the inconvenience of long distance telephone charges. Using peer-to-peer data transmission technology, Skype... Read more
HoudahSpot 3.9.6 - Advanced file search...
HoudahSpot is a powerful file search tool built upon MacOS X Spotlight. Spotlight unleashed Create detailed queries to locate the exact file you need Narrow down searches. Zero in on files Save... Read more
RapidWeaver 6.0.3 - Create template-base...
RapidWeaver is a next-generation Web design application to help you easily create professional-looking Web sites in minutes. No knowledge of complex code is required, RapidWeaver will take care of... Read more
iPhoto Library Manager 4.1.10 - Manage m...
iPhoto Library Manager lets you organize your photos into multiple iPhoto libraries. Separate your high school and college photos from your latest summer vacation pictures. Or keep some photo... Read more
iExplorer 3.5.1.9 - View and transfer al...
iExplorer is an iPhone browser for Mac lets you view the files on your iOS device. By using a drag and drop interface, you can quickly copy files and folders between your Mac and your iPhone or... Read more
MacUpdate Desktop 6.0.3 - Discover and i...
MacUpdate Desktop 6 brings seamless 1-click installs and version updates to your Mac. With a free MacUpdate account and MacUpdate Desktop 6, Mac users can now install almost any Mac app on macupdate.... Read more
SteerMouse 4.2.2 - Powerful third-party...
SteerMouse is an advanced driver for USB and Bluetooth mice. It also supports Apple Mighty Mouse very well. SteerMouse can assign various functions to buttons that Apple's software does not allow,... Read more
iMazing 1.1 - Complete iOS device manage...
iMazing (was DiskAid) is the ultimate iOS device manager with capabilities far beyond what iTunes offers. With iMazing and your iOS device (iPhone, iPad, or iPod), you can: Copy music to and from... Read more
PopChar X 7.0 - Floating window shows av...
PopChar X helps you get the most out of your font collection. With its crystal-clear interface, PopChar X provides a frustration-free way to access any font's special characters. Expanded... Read more
OneNote 15.4 - Free digital notebook fro...
OneNote is your very own digital notebook. With OneNote, you can capture that flash of genius, that moment of inspiration, or that list of errands that's too important to forget. Whether you're at... Read more

Latest Forum Discussions

See All

Lucha Amigos (Games)
Lucha Amigos 1.0 Device: iOS Universal Category: Games Price: $1.99, Version: 1.0 (iTunes) Description: Forget Ninja Turtles, and meet Wrestlers Turtles! Crazier, Spicier and…Bouncier! Sling carapaces of 7 Luchadores to knock all... | Read more »
Raby (Games)
Raby 1.0.3 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0.3 (iTunes) Description: ***WARNING - Raby runs on: iPhone 5, iPhone 5C, iPhone 5S, iPhone 6, iPhone 6 Plus, iPad Mini Retina, iPad Mini 3, iPad 4, iPad Air,... | Read more »
Oddworld: Stranger's Wrath (Games)
Oddworld: Stranger's Wrath 1.0 Device: iOS Universal Category: Games Price: $5.99, Version: 1.0 (iTunes) Description: ** PLEASE NOTE: Oddworld Stranger's Wrath requires at least an iPhone 4S, iPad 2, iPad Mini or iPod Touch 5th gen... | Read more »
Bounce On Back (Games)
Bounce On Back 1.0 Device: iOS Universal Category: Games Price: $2.99, Version: 1.0 (iTunes) Description: | Read more »
Dwelp (Games)
Dwelp 1.0 Device: iOS Universal Category: Games Price: $.99, Version: 1.0 (iTunes) Description: === 50% off for a limited time, to celebrate release === Dwelp is an elegant little puzzler with a brand new game mechanic. To complete a... | Read more »
Make Way for Fat Chicken, from the Maker...
Make Way for Fat Chicken, from the Makers of Scrap Squad Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Relevant Games has announced they will be releasing their reverse tower defense game, | Read more »
Tripnary Review
Tripnary Review By Jennifer Allen on November 26th, 2014 Our Rating: :: TRAVEL BUCKET LISTiPhone App - Designed for the iPhone, compatible with the iPad Want to create a travel bucket list? Tripnary is a fun way to do exactly that... | Read more »
Ossian Studios’ RPG, The Shadow Sun, is...
Ossian Studios’ RPG, The Shadow Sun, is Now Available for $4.99 Posted by Jessica Fisher on November 26th, 2014 [ permalink ] Universal App - Designed for iPhone and iPad | Read more »
Mmmm, Tasty – Having the Angry Birds for...
The very first Angry Birds debuted on iOS back in 2009. When you sit back and tally up the number of Angry Birds games out there and the impact they’ve had on pop culture as a whole, you just need to ask yourself: “How would the birds taste... | Read more »
Rescue Quest Review
Rescue Quest Review By Jennifer Allen on November 26th, 2014 Our Rating: :: PATH BASED MATCH-3Universal App - Designed for iPhone and iPad Guide a wizard to safety by matching gems. Rescue Quest might not be an entirely original... | Read more »

Price Scanner via MacPrices.net

Black Friday: 15% off iTunes Gift Cards
Staples is offering 15% off $50 and $100 iTunes Gift Cards on their online store as part of their Black Friday sale. Click here for more information. Shipping is free. Best Buy is offering $100... Read more
BEVL Releases Dock Tailored for iPhone 6 and...
Seattle based BEVL has released their first product: an iPhone dock that is divergent in build quality, rock-solid function and visual simplicity to complement the iPhone. BEVL is now accepting... Read more
Black Friday: $150 off 13-inch Retina MacBook...
 Best Buy has 13-inch 2.6GHz Retina MacBook Pros on sale for $150 off MSRP on their online store as part of their Black Friday sale. Choose free shipping or free local store pickup (if available).... Read more
Black Friday: $300 off 15-inch Retina MacBook...
 B&H Photo has the new 2014 15″ Retina MacBook Pros on sale for $300 off MSRP as part of their Black Friday sale. Shipping is free, and B&H charges NY sales tax only: - 15″ 2.2GHz Retina... Read more
Black Friday: Up to $140 off MacBook Airs, fr...
 B&H Photo has 2014 MacBook Airs on sale for up to $140 off MSRP as part of their Black Friday sale. Shipping is free, and B&H charges NY sales tax only: - 11″ 128GB MacBook Air: $799 $100... Read more
Black Friday: 13-inch 2.5GHz MacBook Pro on s...
 Best Buy has the 13″ 2.5GHz MacBook Pro on sale for $899.99 on their online store as part of their Black Friday sale. Choose free shipping or free instant local store pickup (if available). Their... Read more
Black Friday: 21-inch 1.4GHz iMac on sale for...
 Best Buy has the 21″ 1.4GHz iMac on sale for $899.99 on their online store as part of their Black Friday sale. Their price is $200 off MSRP. Choose free shipping or free local store pick up. Price... Read more
Black Friday iPad Air 2 sale prices, $100 off...
 Best Buy has iPad Air 2s on sale for $100 off MSRP on their online store for Black Friday. Choose free shipping or free local store pickup (if available). Sale prices available for online orders... Read more
2014 1.4GHz Mac mini on sale for $449, save $...
 B&H Photo has the new 1.4GHz Mac mini on sale for $449.99 including free shipping plus NY tax only. Their price is $50 off MSRP, and it’s the lowest price available for this new model. Adorama... Read more
Early Black Friday pricing on 27-inch 5K iMac...
 B&H Photo continues to offer Black Friday sale prices on the 27″ 3.5GHz 5K iMac, in stock today and on sale for $2299 including free shipping plus NY sales tax only. Their price is $200 off MSRP... Read more

Jobs Board

*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
Senior Event Manager, *Apple* Retail Market...
…This senior level position is responsible for leading and imagining the Apple Retail Team's global event strategy. Delivering an overarching brand story; in-store, Read more
*Apple* Retail - Multiple Positions (US) - A...
Sales Specialist - Retail Customer Service and Sales Transform Apple Store visitors into loyal Apple customers. When customers enter the store, you're also the Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
*Apple* Solutions Consultant (ASC) - Apple (...
**Job Summary** The ASC is an Apple employee who serves as an Apple brand ambassador and influencer in a Reseller's store. The ASC's role is to grow Apple Read more
All contents are Copyright 1984-2011 by Xplain Corporation. All rights reserved. Theme designed by Icreon.