#define USE_PICTS	1	/* 1 if PICTs are to be displayed, 0 for text only */
#define ONE_RESOURCE 0	/* 1 for Get1Resource, 0 for GetResource, etc. */
#define COMPRESSION	1	/* compressed TEXT/styl */
#define STANDALONE	0

#define CHECKPOINTS 0
#define ASSERTIONS	0

#if STANDALONE
	#define		Show_help	main
#else
	void	main( void );
#endif

#include "Show_help.h"

/*
	Show_help by James W. Walker, June 1991
	
	version 1.6, updated March 1992
	
	This code is freely usable.  If you want to show your gratitude,
	you could send me a free copy of whatever program you develop
	with it.
	
	e-mail:
		Internet			76367.2271@compuserve.com
		CIS					76367,2271
		America Online		JWWalker
	
	This code displays scrolling text in a dialog box.  The text comes
	from TEXT/styl resources, which can be created with ResEdit 2.1 or
	with an accompanying HyperCard stack.
	The text cannot be edited, but one can select text and copy it to
	the clipboard using command-C, or save it as a TeachText file.
	
	Pictures can be included in the text using the same scheme as
	TeachText: Each option-space character indicates where the top
	edge of a picture should go, and pictures are centered horizontally.
	Pictures come from consecutively-numbered PICT resources.  
	
	A popup menu can be used to jump to "bookmarks", which are indicated
	by tab characters at ends of lines.
	
	Prototype:
	
	pascal void Show_help( short help_dlog_id, StringPtr help_text_name,
		short base_pict_id, StringPtr default_filename,
		StringPtr default_menuname );
	
	help_dlog_id  is the resource ID of the DLOG resource.  The dialog
	              should have an OK button as item 1 and a userItem, to
	              display the text, in item 2.
	
	help_text_name is the suffix of the resource name of the TEXT and
	              styl resources.  For instance if help_text_name is
	              "\pblah", and COMPRESSION is 1, then Show_help will
	              look for '4CMP' resources named "TEXTblah" and "stylblah".
	              If COMPRESSION is 0, Show_help looks for a 'TEXT'
	              resource named "blah" and a 'styl' resource named "blah".
	
	base_pict_id is the resource ID of the first PICT resource.

	default_filename is the filename initially presented in the Save dialog
	              when the user saves the help text as TeachText.
	
	default_menuname is the menu item used for a line that consists of
	              a tab and nothing else.
	              
	
	TO DO: error recovery, support for modeless use.
*/

#if __option(a4_globals)
	#define		RESTORE_A4	asm { move.L (SP)+, A4 }
	#if __option(pcrel_strings)		// single-segment code resource
		#define		SETUP_A4	asm { move.L	A4, -(SP) }\
								asm { LEA		main, A4 }
	#else							// multi-segment code resource
		#include <SetUpA4.h>
		#define		SETUP_A4	SetUpA4()
	#endif
#else
	#define		SETUP_A4
	#define		RESTORE_A4
#endif


#ifndef NIL
	#define		NIL		0L
#endif

#if ONE_RESOURCE
	#define GetResource			Get1Resource
	#define CountResources		Count1Resources
	#define GetNamedResource	Get1NamedResource
#endif ONE_RESOURCE

#if ASSERTIONS
	#define		ASSERT(x,y)		if (!(x)) {DebugStr("\p" y);}
#else
	#define		ASSERT(x,y)
#endif ASSERTIONS

#if CHECKPOINTS
	#define CKPT(x)		DebugStr( "\p" x )
#else
	#define CKPT(x)
#endif CHECKPOINTS

enum {
	c_OK = 1,	/* OK button */
	c_help,		/* userItem for our help display */
	c_save,		/* Button to save as TeachText */
	c_menu		/* userItem for popup menu */
};

#define		SCROLLBAR_WIDTH	16
#define		TEXT_INSET		4

typedef struct {
	Rect		bounds;
	PicHandle	pict;
} pict_info;

typedef struct {
	short		array_size;
	short		high_waiting;
	Rect		high_rect[];
} high_info;

#define		INITIAL_HIGHLIGHTS		8

typedef struct { // piggyback some other info on the dialog record
	DialogRecord	dialog;
	ControlHandle	scrollbar;
#if USE_PICTS
	high_info		**high;
	short			pict_count;	/* how many pictures */
	pict_info		*pict_data;	/* pointer to an array */
	Boolean			high_defer_flag;
#endif
}	help_record, *help_ptr;

#if COMPRESSION
typedef struct {
	ResType	srcType;
	Handle	srcHandle;
	ResType	dstType;
	Handle	dstHandle;
} ParmInfo;

/* We don't actually use the first parameter passed to the CNVT code,
	but it is a pointer to a structure as below.
*/
typedef struct {
	ProcPtr		entryPoint;
	short		resID;
	short		parmCount;
	Boolean		useDefault; /* 2 bytes? */
} RoutineInfo;

typedef pascal OSErr (*CNVT_routine)(RoutineInfo *, ParmInfo *);
#endif COMPRESSION


/* private global variables */
static	RgnHandle	save_clip;
static	CursHandle	ibeam_cursor;

/* Prototypes of private routines
*/
static void Push_highlight( high_info **hh, Rect *rect );
static Boolean Pop_highlight( high_info **hh, Rect *rect );
static pascal Boolean Help_filter( DialogPtr dialog,
	EventRecord	*event, short *itemHit);
static pascal void  Text_userItem_proc( WindowPtr the_window, short item_num );
static pascal void  Menu_userItem_proc( WindowPtr the_window, short item_num );
static pascal void Scroll_text( ControlHandle the_bar, int part_code );
static pascal Auto_scroll( void );
static void Handle_scroll( DialogPtr dialog,
	short the_part, Point local_point );
static void Adjust_text( DialogPtr dialog );
static void Save_text( TEHandle the_text, short base_pict_id,
	StringPtr default_filename );
static void Topic_menu( DialogPtr dlog, MenuHandle help_popup );
static MenuHandle Build_popup( TEHandle	the_text, StringPtr default_menuname );
static short	Find_char(
			Handle	data_h,		// handle to a block of characters
			short	offset,		// initial offset within block
			char	what );		// the character we're looking for

#if COMPRESSION
static Handle Get_compressed_resource( ResType	the_type, StringPtr the_name );
static void Release_compressed_resource( Handle rsrc_h );
#else
#define Get_compressed_resource			GetNamedResource
#define Release_compressed_resource		ReleaseResource
#endif

#if USE_PICTS
static void Find_pictures( DialogPtr dlog, short first_pict_id );
static void Draw_picts( WindowPtr the_window, Rect *update_rect );
static pascal void High_hook( Rect *high_rect );
static void High_hook_glue( void );
static void Do_deferred_hilites( help_ptr  hptr, Rect *update_rect );
#endif

/* ------------------------- Show_help --------------------------------- */
pascal void Show_help( short help_dlog_id, StringPtr help_text_name,
	short base_pict_id, StringPtr default_filename,
	StringPtr default_menuname )
{
	register	DialogPtr	dptr;
	register	TEHandle	the_text;
	short		itype, ihit;
	Handle		item_h;
	Rect		help_item_box, box;
	Handle			help_TEXT;
	StScrpHandle	help_styl;
	GrafPtr		save_port;
	Rect		dest, view;
	ControlHandle	the_bar;
	short		max_scroll, nLines;
	Point		place;
	MenuHandle	help_popup;
	CursHandle	watch_cursor;
#if __option(a4_globals)
	#if __option(pcrel_strings)	// single-segment code resource
	asm {
		LEA		main, A4
	}
	#else						// multi-segment code resource
		RememberA4();
	#endif
#endif

	watch_cursor = GetCursor( watchCursor );
	SetCursor( *watch_cursor );
	ibeam_cursor = GetCursor( iBeamCursor );
	dptr = (DialogPtr) NewPtr( sizeof(help_record) );
	dptr = GetNewDialog( help_dlog_id, (DialogPeek) dptr, (WindowPtr)-1L );
	ASSERT( dptr != NIL, "Failed GetNewDialog" );
	GetPort( &save_port );
	SetPort( dptr );
	
	((help_ptr) dptr)->high = (high_info **) NewHandle(
				sizeof(high_info) + INITIAL_HIGHLIGHTS * sizeof(Rect) );
	ASSERT( ((help_ptr) dptr)->high != NIL, "Failed to get highlight record");
	(**((help_ptr) dptr)->high).array_size = 2;
	(**((help_ptr) dptr)->high).high_waiting = 0;
	
	help_TEXT = Get_compressed_resource( 'TEXT', help_text_name );
	if (help_TEXT == NIL)
	{
		ASSERT(false, "Failed to find help TEXT resource" );
		SysBeep(1);
		goto getout;
	}
	help_styl = (StScrpHandle)
		Get_compressed_resource( 'styl', help_text_name );
	if (help_styl == NIL)
	{
		DisposHandle( help_TEXT );
		ASSERT( false, "Failed to find styl resource" );
		SysBeep(1);
		goto getout;
	}
	HLock( help_TEXT );
	
	GetDItem( dptr, c_help, &itype, &item_h, &help_item_box );
	SetDItem( dptr, c_help, itype, (Handle) Text_userItem_proc, &help_item_box );
	view = help_item_box;
	InsetRect( &view, 1, 1 );
	view.right -= SCROLLBAR_WIDTH;
	dest = view;
	InsetRect( &dest, TEXT_INSET, 0 );
	the_text = TEStylNew( &dest, &view );
	ASSERT( the_text != NIL, "Failed TEStylNew." );
	
	TEStylInsert( *help_TEXT, GetHandleSize(help_TEXT),
		help_styl, the_text );
	TEActivate( the_text );
	Release_compressed_resource( (Handle) help_styl );
	Release_compressed_resource( help_TEXT );
	nLines = (**the_text).nLines;
	SetWRefCon( dptr, (long)the_text );
	max_scroll = TEGetHeight( (long) nLines, 1L, the_text )
		- (view.bottom - view.top);
	
	help_item_box.left = help_item_box.right - SCROLLBAR_WIDTH;
	the_bar = NewControl( dptr, &help_item_box, "\p", true,
		0, 0, max_scroll, scrollBarProc, NIL );
	ASSERT( the_bar != NIL, "Failed NewControl for scroll bar." );
	((help_ptr) dptr)->scrollbar = the_bar;

#if USE_PICTS
	Find_pictures( dptr, base_pict_id );
#endif
	help_popup = Build_popup( the_text, default_menuname );

	TEAutoView( TRUE, the_text );	/* Permit auto-scrolling */
	CKPT( "Installing ClikLoop" );
	(**the_text).clikLoop = (ProcPtr)Auto_scroll;
#if USE_PICTS
	(**the_text).highHook = (ProcPtr) High_hook_glue;
	((help_ptr)dptr)->high_defer_flag = false;
#endif

	GetDItem( dptr, c_menu, &itype, &item_h, &box );
	SetDItem( dptr, c_menu, itype, (Handle) Menu_userItem_proc, &box );
	
	save_clip = NewRgn();	/* Used in Draw_picts */
	ShowWindow( dptr );
	InitCursor();

	do {
		ModalDialog( (ProcPtr) Help_filter, &ihit );
		if (ihit == c_save)
			Save_text( the_text, base_pict_id, default_filename );
		else if (ihit == c_menu)
			Topic_menu( dptr, help_popup );
	} while (ihit != c_OK);
	
	
	DisposeRgn( save_clip );
#if USE_PICTS
	DisposPtr( (Ptr) ((help_ptr)dptr)->pict_data );
#endif
	TEDispose( the_text );
getout:
	DisposDialog( dptr );
	DisposeMenu( help_popup );
	SetPort( save_port );
}

/* --------------------------- Push_highlight -------------------------- */
static void Push_highlight( high_info **hh, Rect *rect )
{
	if ( (**hh).high_waiting >= (**hh).array_size )
	{
		SetHandleSize( (Handle)hh, sizeof(high_info) +
			(INITIAL_HIGHLIGHTS + (**hh).array_size) * sizeof(Rect) );
		ASSERT( MemError() == noErr, "Can't expand highlight array" );
		(**hh).array_size = ( GetHandleSize( (Handle)hh ) -
									sizeof(high_info) ) / sizeof(Rect);
	}
	if ( (**hh).high_waiting < (**hh).array_size )
	{
		(**hh).high_rect[ (**hh).high_waiting ] = *rect;
		(**hh).high_waiting++;
	}
}

/* --------------------------- Pop_highlight -------------------------- */
static Boolean Pop_highlight( high_info **hh, Rect *rect )
{
	if ( (**hh).high_waiting > 0 )
	{
		(**hh).high_waiting--;
		*rect = (**hh).high_rect[ (**hh).high_waiting ];
		return true;
	}
	else
		return false;
}

/* --------------------------- Find_char -------------------------- */
/*
	Find a character within a handle.  In a previous version I did this
	with Munger().
	
	returns: the offset of the character, or -1 if not found.
*/
static short	Find_char(
			Handle	data_h,		// handle to a block of characters
			short	offset,		// initial offset within block
			char	what )		// the character we're looking for
{
	Ptr		text;
	short	text_size, scan;
	
	text_size = (short) GetHandleSize( data_h );
	text = *data_h;
	for (scan = offset; (text[scan] != what) && (scan < text_size); ++scan)
		;
	if (scan == text_size) // not found
		scan = -1;
	return scan;
}

/* --------------------------- Build_popup ------------------------- */
/*
	Build a popup menu of the sections of the help text.  We scan for
	tab characters.  The text between the tab character and the preceding
	line break will be a menu item, unless it is the null string; then we
	use the default menu name that was passed to Show_help.
*/
static MenuHandle Build_popup( TEHandle	the_text, StringPtr default_menuname )
{
	MenuHandle	popup;
	short		menu_id;
	SignedByte	text_state;
	Handle		text_h;	/* handle to just the text */
	Str255		menu_data;
	char		*text;	/* pointer to the help text */
	register short		scan, line_start;
	short		text_size, title_length;
	
	/* Find an unused menu ID */
	menu_id = 1300; /* no particular reason */
	while (GetMHandle(menu_id))
		++menu_id;
	
	popup = NewMenu( menu_id, "\p" );

	text_h = (**the_text).hText;
	text_state = HGetState( text_h );
	HLock( text_h );
	text = *text_h;
	text_size = (short) GetHandleSize( text_h );
	line_start = 0;
	for (scan = 0; scan < text_size; scan++ )
	{
		if (text[scan] == '\r')
		{
			line_start = scan + 1;
		}
		else if (text[scan] == '\t')
		{
			title_length = scan - line_start;
			if (title_length == 0)
				BlockMove( default_menuname, menu_data, 256 );
			else
			{
				menu_data[0] = title_length; // note: <= 255
				BlockMove( &text[line_start], &menu_data[1], menu_data[0] );
			}
			/*
				AppendMenu recognizes meta-characters like slash,
				which is probably not what we want in this case.  So
				we use SetItem, which does not use meta-characters.
			*/
			AppendMenu( popup, "\p " );
			SetItem( popup, CountMItems(popup), menu_data );
		}
	}
	
	HSetState( text_h, text_state );
	return popup;
}

/* ------------------------- Topic_menu ------------------------ */
/*
	This routine is called when the menu title is clicked.
	It pops up the menu and scrolls to the indicated tab character.
*/
static void Topic_menu( DialogPtr dptr, MenuHandle menu )
{
	short			menu_id;
	Handle			item_h;
	short			itype;
	Rect			box;
	Point			where;
	long			menu_return;
	short			menu_choice;
	ControlHandle	bar;
	register short		i;
	register TEHandle	the_text;
	Handle			text_h;
	register short		offset;
	TextStyle		what_style;
	short			line_height, font_ascent;
	
	if (menu == NIL) return;
	InsertMenu( menu, -1 );
	GetDItem( dptr, c_menu, &itype, &item_h, &box );
	where.h = box.left;
	where.v = box.bottom;
	LocalToGlobal( &where );
	HiliteMode &= ~(1 << hiliteBit);
	InvertRect( &box );
	menu_return = PopUpMenuSelect( menu, where.v, where.h, 0 );
	HiliteMode &= ~(1 << hiliteBit);
	InvertRect( &box );
	if (HiWord(menu_return))	/* Something selected */
	{
		menu_choice = LoWord( menu_return );
		bar = ((help_ptr)dptr)->scrollbar;
		the_text = (TEHandle) GetWRefCon( dptr );
		text_h = (**the_text).hText;
		
		/* Find tab character number menu_choice */
		offset = -1L;
		for (i = 1; i <= menu_choice; ++i)
		{
			++offset; /* so we don't find the same thing twice */
			offset = Find_char( text_h, offset, '\t' );
		}

		where = TEGetPoint( (short)offset, the_text );
		TEGetStyle( (short)offset, &what_style, &line_height,
			&font_ascent, the_text );
		where.v -= line_height;	/* align to TOP of tab */
		/*
			Now where.v is in local coordinates.
		*/
		where.v -= (**the_text).destRect.top;
		SetCtlValue( bar,  where.v );
		
		Adjust_text( dptr );
	}
	menu_id = (**menu).menuID;
	DeleteMenu( menu_id );
}

/* ------------------------- Save_text ------------------------ */
/*
	This is called when the user clicks on the "Save as TeachText"
	button.
*/
static void Save_text( TEHandle the_text, short base_pict_id,
	StringPtr default_filename )
{
	Point		where;
	SFReply		reply;
	OSErr		err;
	short		data_refnum, res_refnum, old_resfile;
	Handle		text_data;
	SignedByte	state;
	long		count;
#if USE_PICTS
	register short		num_picts, pict_id;
	Handle		old_pict, new_pict;
#endif
	
	where.h = where.v = 100;
	SFPutFile( where, "\pName of TeachText file:",
		default_filename, NIL, &reply );
	if (reply.good)
	{
		old_resfile = CurResFile();
#if USE_PICTS
		num_picts = CountResources( 'PICT' );
#endif
		
		(void) FSDelete( reply.fName, reply.vRefNum );
		(void) Create( reply.fName, reply.vRefNum, 'ttxt', 'ttro' );
		(void) FSOpen( reply.fName, reply.vRefNum, &data_refnum );
		text_data = (**the_text).hText;
		state = HGetState(text_data );
		HLock( text_data );
		count = GetHandleSize( text_data );
		(void) FSWrite( data_refnum, &count, *text_data );
		(void) FSClose( data_refnum );
		HSetState( text_data, state );

#if USE_PICTS
		if (num_picts > 0)
		{
			(void) SetVol( NIL, reply.vRefNum );
			CreateResFile( reply.fName );
			ASSERT( ResError() == noErr, "\pCreateResFile error" );
			res_refnum = OpenResFile( reply.fName );
			ASSERT( ResError() == noErr, "\pOpenResFile error" );
			for (pict_id = base_pict_id;
				pict_id < base_pict_id + num_picts; ++pict_id )
			{
				UseResFile( old_resfile );
				old_pict = GetResource( 'PICT', pict_id );
				if (old_pict == NIL)
					break;
				new_pict = old_pict;
				(void) HandToHand( &new_pict );
				UseResFile( res_refnum );
				AddResource( new_pict, 'PICT',
					pict_id - base_pict_id + 1000, "\p" );
				ASSERT( ResError() == noErr, "\pAddResource error" );
			}
			CloseResFile( res_refnum );
			(void) FlushVol( NIL, reply.vRefNum );
		}
#endif
	}
}

#if USE_PICTS
/* ------------------------- High_hook_glue ------------------------ */
static void High_hook_glue( void )
{
	asm {
		move.L	(SP)+, A0	; get address of rectangle
		movem.L	A2-A5/D3-D7, -(SP)	; save registers
		move.L	A0, -(SP)
	}
	CKPT( "High_hook_glue" );
	asm {
		JSR		High_hook
		movem.L	(SP)+, A2-A5/D3-D7	; restore registers
		RTS
	}
}

/* ------------------------- High_hook -------------------------- */
/*
	This deferred highlighting scheme is used to ensure that highlighting
	will be done after any pictures have been drawn, not before.  To do
	otherwise can cause pictures to be incorrectly highlighted during
	auto-scrolling.  This effect can be seen in TeachText.
*/
static pascal void High_hook( Rect *the_rect )
{
	register help_ptr	front;
	
	CKPT( "High_hook");
	front = (help_ptr) FrontWindow();
	if ( (front != NIL) &&
		(GetPtrSize((Ptr)front) == sizeof(help_record)) &&
		!EmptyRect( the_rect ) )
	{
		if (!front->high_defer_flag)
		{
			HiliteMode &= ~(1 << hiliteBit);
			InvertRect( the_rect );
		}
		else
		{
			Push_highlight( front->high, the_rect );
		}
	}
}
#endif /* USE_PICTS */

/* ------------------------- Auto_scroll ----------------------------- */
/*
	This is a ClikLoop routine, called repeatedly by TEClick when the
	mouse is down.
*/
static pascal Auto_scroll()
{
	register	WindowPtr	the_display;
	register	ControlHandle	the_bar;
	Point					mouse_point;
	Rect					view_rect;
	register 	TEHandle	the_text;
	
	asm {
		movem.l		a1-a5/d1-d7, -(SP)
	}
	CKPT( "Auto_scroll");
	the_display = FrontWindow();
	if ( (the_display != NIL) &&
		(GetPtrSize((Ptr)the_display) == sizeof(help_record)) )
	{
		the_text = (TEHandle) GetWRefCon( the_display );
		the_bar = ((help_ptr) the_display)->scrollbar;
		
		GetMouse( &mouse_point );
		view_rect = (**the_text).viewRect;
		if (mouse_point.v < view_rect.top)
			Scroll_text( the_bar, inUpButton );
		else if (mouse_point.v > view_rect.bottom)
			Scroll_text( the_bar, inDownButton );
	}
	asm {
		movem.L		(SP)+, a1-a5/d1-d7
		moveQ		#1, D0
	}
}

#if USE_PICTS
/* ------------------------- Draw_picts --------------------------------- */
/*
	Called by Adjust_text and Text_userItem_proc to draw pictures.
*/
static void Draw_picts( WindowPtr the_window, Rect *update_rect )
{
	register TEHandle	the_text;
	register short		pict_count, pict_index;
	PicHandle	the_pict;
	short 		v_offset;
	Rect		pict_loc, dummy;
	
	CKPT( "Draw_picts");
	the_text = (TEHandle) GetWRefCon( the_window );
	v_offset = (**the_text).destRect.top - (**the_text).viewRect.top
		- TEXT_INSET;
	pict_count = ((help_ptr) the_window)->pict_count;
	for (pict_index = 0; pict_index < pict_count; pict_index++)
	{
		pict_loc = ((help_ptr) the_window)->pict_data[pict_index].bounds;
		OffsetRect( &pict_loc, 0, v_offset );
		if (!SectRect( &pict_loc, update_rect, &dummy ))
			continue;
		the_pict = ((help_ptr) the_window)->pict_data[pict_index].pict;
		LoadResource( (Handle) the_pict );
		GetClip( save_clip );
		ClipRect( update_rect );
		DrawPicture( the_pict, &pict_loc );
		SetClip( save_clip );
	}
}

#define		OPTION_SPACE_CHAR	0xCA

/* ---------------------- Find_pictures ---------------------------- */
static void Find_pictures( DialogPtr dlog, short first_pict_id )
{
	register TEHandle	the_text;
	Handle		text_h;
	SignedByte	text_state;
	register short		offset;
	short		num_picts;
	register short		which_pict;
	pict_info	*pict;
	Point		place;
	short		line_height, font_ascent;
	TextStyle	what_style;
	
	CKPT( "Find_pictures");
	the_text = (TEHandle) GetWRefCon( dlog );
	text_h = (**the_text).hText;
	text_state = HGetState( text_h );
	HLock( text_h );
	
	/* Count option-space characters in the text. */
	offset = 0;
	num_picts = 0;
	offset = Find_char( text_h, offset, OPTION_SPACE_CHAR );
	while ( offset >= 0 )
	{
		num_picts++;
		offset++;
		offset = Find_char( text_h, offset, OPTION_SPACE_CHAR );
	}
	
	/* Allocate storage for an array of picture bounds. */
	pict = (pict_info *) NewPtr( sizeof(pict_info) * num_picts );
	((help_ptr)dlog)->pict_data = pict;
	
	/*
		Initialize the picture info.  For each picture we record the
		picture handle and its rectangle, in unscrolled window
		coordinates.
	*/
	offset = 0;
	for (which_pict = 0; which_pict < num_picts; which_pict++)
	{
		pict[which_pict].pict = (PicHandle) GetResource( 'PICT',
			first_pict_id + which_pict );
		if ( pict[which_pict].pict == NIL )
			break;
		offset = Find_char( text_h, offset, OPTION_SPACE_CHAR );
		place = TEGetPoint( offset, the_text );
		TEGetStyle( offset, &what_style, &line_height,
			&font_ascent, the_text );
		place.v -= line_height;	/* align picture with TOP of option-space */
		offset++;
		pict[which_pict].bounds = (**pict[which_pict].pict).picFrame;
		OffsetRect( &pict[which_pict].bounds,
			( ((**the_text).destRect.right + (**the_text).destRect.left) -
			(pict[which_pict].bounds.right + pict[which_pict].bounds.left)
			) / 2,
			- pict[which_pict].bounds.top + place.v );
	}
	((help_ptr)dlog)->pict_count = which_pict;
	
getout:
	HSetState( text_h, text_state );
}
#endif /* USE_PICTS */

/* ---------------------- Scroll_text ---------------------------- */
/*
	This is used as a TrackControl actionProc for scrolling, and also
	called by Auto_scroll for automatic scrolling.
*/
static pascal void Scroll_text( ControlHandle the_bar, int part_code )
{
	register TEHandle	the_text;
	register short		delta;
	register WindowPtr	the_display;
	short				old_value;
	short				offset, line;
	Point				place;
	Rect				view;
	TextStyle			style;
	short				line_height, font_ascent;
	
	CKPT( "Scroll_text");
	SETUP_A4;				// for access to static global save_clip
	if (part_code != 0)
	{
		the_display = (**the_bar).contrlOwner;
		the_text = (TEHandle) GetWRefCon( the_display );
		view = (**the_text).viewRect;
		place.h = view.left + TEXT_INSET;
		
		switch (part_code)
		{
			case inUpButton:
				place.v = view.top - 4;
				offset = TEGetOffset( place, the_text );
				place = TEGetPoint( offset, the_text );
				TEGetStyle( offset, &style, &line_height, &font_ascent,
					the_text );
				delta = place.v - line_height - view.top;
				break;
			case inDownButton:
				place.v = view.bottom + 2;
				offset = TEGetOffset( place, the_text );
				place = TEGetPoint( offset, the_text );
				/* Now place.v is at the baseline of the border line. */
				delta = place.v - view.bottom;
				break;
			case inPageUp:
				/*
					I want top border line to remain visible, and
					the top of a line should end up at view.top.
				*/
				place.v = view.top + 2;
				offset = TEGetOffset( place, the_text );
				place = TEGetPoint( offset, the_text );
				/* place.v is at the baseline of the top border line. */
				TEGetStyle( offset, &style, &line_height, &font_ascent,
					the_text );
				place.v += line_height - font_ascent;
				place.v -= view.bottom - view.top;
				offset = TEGetOffset( place, the_text );
				place = TEGetPoint( offset, the_text );
				TEGetStyle( offset, &style, &line_height, &font_ascent,
					the_text );
				delta = place.v - view.top;
				if (offset == 0)
					delta -= line_height;
				break;
			case inPageDown:
				/*
					I want bottom border line to remain visible, and
					the bottom of a line should end up at view.bottom.
				*/
				place.v = view.bottom - 2;
				offset = TEGetOffset( place, the_text );
				place = TEGetPoint( offset, the_text );
				/* place.v is at the baseline of the bottom border line. */
				TEGetStyle( offset, &style, &line_height, &font_ascent,
					the_text );
				place.v -= font_ascent; /* Top edge of bottom border line */
				place.v += view.bottom - view.top;
				/* We're looking at the bottom border of the next page. */
				offset = TEGetOffset( place, the_text );
				place = TEGetPoint( offset, the_text );
				TEGetStyle( offset, &style, &line_height, &font_ascent,
					the_text );
				delta =  place.v - line_height - view.bottom;
				if (offset == (**the_text).teLength)
					delta += line_height;
				break;
		}
		old_value = GetCtlValue( the_bar );
		if ( ((delta < 0) && (old_value > 0)) ||
			((delta > 0) && (old_value < GetCtlMax(the_bar))) )
		{
			/*
				When this routine is called, TextEdit may have set the
				clipping region to the view rectangle, so we reset it
				here to make sure the scroll bar gets drawn.
			*/
			GetClip( save_clip );
			ClipRect( &the_display->portRect );
			SetCtlValue( the_bar, old_value + delta );
			SetClip( save_clip );
		}
		Adjust_text( the_display );
	}
	RESTORE_A4;
}

/* ---------------------- Adjust_text ---------------------------- */
/*
	Called by Handle_scroll and Scroll_text to scroll the text and
	pictures into sync with the scroll bar's control value.
*/
static void Adjust_text( DialogPtr	dialog )
{
	register	TEHandle	the_text;
	register	short	scroll_down;
	short			old_scroll;
	Rect			update_rect;
	ControlHandle	the_bar;
	
	CKPT( "Adjust_text");
	the_text = (TEHandle) GetWRefCon( dialog );
	the_bar = ((help_ptr) dialog)->scrollbar;
	old_scroll = (**the_text).viewRect.top - (**the_text).destRect.top;
	scroll_down = old_scroll - GetCtlValue( the_bar );
	if (scroll_down == 0)
		return;
#if USE_PICTS
	((help_ptr) dialog)->high_defer_flag = true;
	//((help_ptr) dialog)->high_waiting = 0;
#endif
	TEScroll( 0, scroll_down, the_text );
#if USE_PICTS
	update_rect = (**the_text).viewRect;
	if (scroll_down > 0)
	{
		if (scroll_down < (update_rect.bottom - update_rect.top))
			update_rect.bottom = update_rect.top + scroll_down;
	}
	else
		if (- scroll_down < (update_rect.bottom - update_rect.top))
			update_rect.top = update_rect.bottom + scroll_down;
	Draw_picts( dialog, &update_rect );
	Do_deferred_hilites( (help_ptr) dialog, &update_rect );
#endif
}

#if USE_PICTS
/* ---------------------- Do_deferred_hilites ---------------------- */
static void Do_deferred_hilites( help_ptr  hptr, Rect *update_rect )
{
	Rect	hilite;
	
	while (Pop_highlight( hptr->high, &hilite ))
	{
		if (SectRect( &hilite, update_rect, &hilite ))
		{
			HiliteMode &= ~(1 << hiliteBit);
			InvertRect( &hilite );
		}
	}
	hptr->high_defer_flag = false;
}
#endif

/* ---------------------- Handle_scroll ---------------------------- */
/*
	Called by Help_filter to handle mouseDown events in the scroll bar.
*/
static void Handle_scroll( DialogPtr dialog, short the_part, Point where )
{
	register	ControlHandle the_bar;
	
	CKPT( "Handle_scroll"); SetPort( dialog );
	the_bar = ((help_ptr) dialog)->scrollbar;
	if (the_part == inThumb)
	{
		(void) TrackControl( the_bar, where, NIL );
		Adjust_text( dialog );
	}
	else
		(void) TrackControl( the_bar, where, (ProcPtr)Scroll_text );
	
}

/* ---------------------- Help_filter ------------------------- */
/*
	This is the dialog event filter for our help window.
*/
#define		RETURN_CHAR		0x0D
#define		TILDE_CHAR		0x7E
#define		ENTER_CHAR		0x03
#define		ESCAPE_CHAR		0x1B

static pascal Boolean Help_filter( DialogPtr dialog,
	EventRecord	*event, short *itemHit)
{
	Point	local_point;
	short	the_part;
	ControlHandle	the_control;
	short	charcode;
	register TEHandle	the_text;
	Rect	item_box;
	short	cursor;
	
	SETUP_A4;	// for access to static global ibeam_cursor
	the_text = (TEHandle) GetWRefCon( dialog );
	GetMouse( &local_point );
	if (PtInRect( local_point, &(**the_text).viewRect ))
		SetCursor( *ibeam_cursor );
	else
		InitCursor();
	TEIdle( the_text );
	switch (event->what) {
		case nullEvent:
			break;
		case mouseDown:
			CKPT( "Help_filter mousedown");
			local_point = event->where;
			GlobalToLocal( &local_point );
			the_part = FindControl( local_point, dialog, &the_control );
			if (the_part && ((**the_control).contrlMax > 1) )
			{
				Handle_scroll( dialog, the_part, local_point );	
				*itemHit = 2;
				break;
			}
			if (PtInRect( local_point, &(**the_text).viewRect ))
			{
				if (event->modifiers & shiftKey)
					TEClick( local_point, true, the_text );
				else
					TEClick( local_point, false, the_text );
			}
			break;
		case keyDown :	
		case autoKey :
			charcode = event->message & charCodeMask;
			/*
				There's no Cancel button, so we treat the OK button
				the same as a Cancel button.
			*/
			if ( (charcode == RETURN_CHAR) || (charcode == ENTER_CHAR) ||
				(charcode == TILDE_CHAR) || (charcode == ESCAPE_CHAR) ||
				((charcode == '.') && (event->modifiers & cmdKey)) )
			{
				*itemHit = c_OK;	/* OK */
				Flash_button( dialog, *itemHit );
				RESTORE_A4;
				return( TRUE );
			}
			if ( (charcode == 'c') && (event->modifiers & cmdKey) )
			{
				(void) ZeroScrap();
				TECopy( the_text );
				SystemEdit(3);
				local_point = (**the_text).selPoint;
				*(long *)&local_point = PinRect( &(**the_text).viewRect,
					local_point );
				cursor = TEGetOffset( local_point, the_text );
				TESetSelect( (long)cursor, (long)cursor, the_text );
				event->what = nullEvent;
			}
			break;
	} /* end switch */
	
	/* tell the Dialog Manager that the event has NOT been handled and that it should
	** take further action on this event.
	*/
	RESTORE_A4;
	return false;
}


/* ---------------------- Text_userItem_proc ------------------------- */
static pascal void  Text_userItem_proc( WindowPtr the_window, short item_num )
{
	Handle		item_h;
	Rect		item_box;
	short		item_type;
	TEHandle	the_text;
	
	SETUP_A4;	// for access to static global save_clip
	
	CKPT( "Text_userItem_proc");
	the_text = (TEHandle) GetWRefCon( the_window );
	item_box = (**the_text).viewRect;
#if USE_PICTS
	((help_ptr) the_window)->high_defer_flag = true;
#endif
	TEUpdate( &item_box, the_text );
	
#if USE_PICTS
	Draw_picts( the_window, &item_box );
	Do_deferred_hilites( (help_ptr) the_window, &item_box );
#endif

	/*
		Get the item's rectangle, and frame it.
	*/
	GetDItem( the_window, item_num, &item_type, &item_h, &item_box );
	FrameRect( &item_box );

	RESTORE_A4;
}

/* ---------------------- Menu_userItem_proc ------------------------- */
static pascal void  Menu_userItem_proc( WindowPtr the_window, short item_num )
{
	Handle		item_h;
	Rect		item_box;
	short		item_type;
	
	asm {
		MOVEM.L	a1-a5/d0-d7, -(SP)
	}
	
	CKPT( "Menu_UserItem_proc");
	
	/*
		Get the item's rectangle, and frame it.
	*/
	GetDItem( the_window, item_num, &item_type, &item_h, &item_box );
	InsetRect(&item_box, -1, -1);
	FrameRect( &item_box );
	
	/* Draw the drop-shadow */
	MoveTo( item_box.left + 3, item_box.bottom );
	LineTo( item_box.right, item_box.bottom );
	LineTo( item_box.right, item_box.top + 3 );		

	asm {
		movem.l	(SP)+, a1-a5/d0-d7		; restore registers
	}
}

/* ---------------------- Flash_button ----------------------------- */
pascal void Flash_button( DialogPtr the_dialog, short item_number )
{
	ControlHandle	item_h;
	long	time;
	short	itype;
	Rect	box;
	
	GetDItem( the_dialog, item_number, &itype, (Handle *)&item_h, &box );
	HiliteControl( item_h, inButton );
	Delay( 9L, &time );
	HiliteControl( item_h, 0 );
}

#if COMPRESSION
/* ---------------------- Get_compressed_resource ----------------------- */
static Handle Get_compressed_resource( ResType the_type, StringPtr the_name )
{
	register Handle		CNVT_h;
	ParmInfo		info;
	Str255			name;
	CNVT_routine	Converter;
	
	name[0] = the_name[0] + 4;
	BlockMove( &the_type, &name[1], 4 );
	BlockMove( &the_name[1], &name[5], the_name[0] );
	info.srcHandle = GetNamedResource( '4CMP', name );
	CNVT_h = GetNamedResource( 'CNVT', "\p4CMPUncompress 4CMP" );
	if ( (info.srcHandle == NIL) ||	/* maybe there's an uncompressed one */
		(CNVT_h == NIL) )
	{
		info.dstHandle = GetNamedResource( the_type, the_name );
	}
	else	/* found a compressed resource */
	{
		info.srcType = '4CMP';

		HLock(CNVT_h);
		Converter = (CNVT_routine) StripAddress( *CNVT_h );
		CKPT("\pAbout to call the CNVT");
		(void) Converter( NIL, &info );
		CKPT("\pAfter the CNVT");
		HUnlock(CNVT_h);
		
		ReleaseResource( info.srcHandle );
	}
	return	info.dstHandle;
}

#define		mem_resource	0x20

/* ------------------------- Release_compressed_resource ------------- */
/*
	If it's a resource handle, release it, otherwise dispose of it.
*/
static void Release_compressed_resource( Handle rsrc_h )
{
	if ( HGetState( rsrc_h ) & mem_resource )
		ReleaseResource( rsrc_h );
	else
		DisposHandle( rsrc_h );
}
#endif COMPRESSION