
/*© copyright 1991-96 UserLand Software, Inc. All Rights Reserved.*/


/*
Frontier Do-Script -- Drives Frontier's "do-script" Apple event. You can send 
any valid Frontier script across this channel. 

This program starts by sending a script with a syntax error, to test the error
reporting mechanism.

This is a very bare bones program with one window and no menu bar. To quit FDS
just close the window.

IMPORTANT NOTE: This version of Frontier Do-Script is primarily intended for
developers who are building directly on top of the Apple Event Manager. If you're
using the IAC Tools library implemented in iac.c, crib the version of FDS from
the Applet Toolkit doscript.c file. It calls IAC Tools routines to talk to 
Frontier.

New in Frontier 3.0 -- see the Component Do-Script folder. If you know that
you're talking to Frontier 3.0, and can assume that your users have System 7.1
or QuickTime, the component version of FDS is much faster than this version.
*/


#include <AppleEvents.h>

WindowPtr mainwindow = nil; /*the menu sharing test window*/

Str255 windowmessage; /*the message that's displayed in the main window*/
	
	


static void initmacintosh (void) {

	/*
	the magic stuff that every Macintosh application needs to do 
	before doing anything else.
	*/

	short i;
		
	MaxApplZone ();
	
	InitGraf (&qd.thePort);
	
	InitFonts ();
	
	FlushEvents (everyEvent, 0);
	
	InitWindows ();
	
	InitMenus ();
	
	TEInit ();
	
	InitDialogs (nil);
	
	InitCursor ();
	
	for (i = 0; i < 5; i++) { /*register with Multifinder*/
		
		EventRecord ev;
		
		EventAvail (everyEvent, &ev); /*see TN180 -- splash screen*/
		} /*for*/
		
	DrawMenuBar ();
	} /*initmacintosh*/


static void copystring (Str255 source, Str255 dest) {

	/*
	create a copy of source in dest.  copy the length byte and
	all the characters in the source string.

	assume the strings are pascal strings, with the length byte in
	the first character of the string.
	*/

	short i, len;
	
	len = (short) source [0];
	
	for (i = 0; i <= len; i++) 
		dest [i] = source [i];
	} /*copystring*/


static Boolean pushstring (Str255 bssource, Str255 bsdest) {

	short lensource = bssource [0];
	short lendest = bsdest [0];
	char *psource, *pdest;
	
	if ((lensource + lendest) > 255)
		return (false);
		
	pdest = (char *) bsdest + (char) lendest + 1;
	
	psource = (char *) bssource + 1;
	
	bsdest [0] += (char) lensource;
	
	while (lensource--) *pdest++ = *psource++;
	
	return (true);
	} /*pushstring*/
	
	
static Boolean pushlong (long num, Str255 bsdest) {

	Str255 bsint;
	
	NumToString (num, bsint);
	
	return (pushstring (bsint, bsdest));
	} /*pushlong*/
	

static void ellipsize (Str255 s, short width) {

	/*
	if the string fits inside the given number of pixels, fine -- do nothing
	and return.
	
	if not, return a string that does fit, with ellipses representing the 
	deleted characters.  ellipses are generated by pressing option-semicolon.
	*/
	
	char len;
	short newwidth;
	
	if ((newwidth = StringWidth (s)) <= width) /*nothing to do, the string fits*/
		return;
	
	len = s [0]; /* current length in characters*/
	
	width -= CharWidth ('É'); /* subtract width of ellipses*/
		
	do { /*until it fits (or we run out of characters)*/
	
		newwidth -= CharWidth (s [len]);
		
		--len;
	} while ((newwidth > width) && (len != 0));
	
	++len; /*make room for the ellipses*/
	
	s [len] = 'É'; 
	
	s [0] = (char) len;
	} /*ellipsize*/


static void centerstring (Rect r, Str255 s) {
	
	/*
	draw the string in the current font, size and style, centered inside
	the indicated rectangle.
	*/
	
	short rh = r.bottom - r.top;
	short rw = r.right - r.left;
	short h, v;
	FontInfo fi;
	
	GetFontInfo (&fi);
	
	ellipsize (s, rw); /*make sure it fits inside the rectangle, width-wise*/
	
	h = r.left + ((rw - StringWidth (s)) / 2);
	
	v = r.top + ((rh - (fi.ascent + fi.descent)) / 2) + fi.ascent;
	
	MoveTo (h, v);
	
	ClipRect (&r);
	
	DrawString (s);
	} /*centerstring*/


static void setfontsizestyle (short fontnum, short fontsize, short fontstyle) {

	TextFont (fontnum);
	
	TextSize (fontsize);
	
	TextFace (fontstyle);
	} /*setfontsizestyle*/
	
	
static void updatemainwindow (void) {
	
	Rect r;
	Str255 s;
	
	r = (*mainwindow).portRect;
	
	EraseRect (&r);
	
	setfontsizestyle (helvetica, 12, 0);
	
	centerstring (r, windowmessage);
	
	NumToString (FreeMem () / 1024, s);
	
	MoveTo (r.left + 3, r.bottom - 3);
	
	setfontsizestyle (geneva, 9, 0);
	
	DrawString (s);
	
	DrawString ("\pK");
	} /*updatemainwindow*/
	
	
static void setwindowmessage (Str255 script, Str255 result) {
	
	copystring (script, windowmessage);
			
	pushstring ("\p returns ", windowmessage);
			
	pushstring (result, windowmessage);
	
	pushstring ("\p", windowmessage);
			
	SetPort (mainwindow);
	
	updatemainwindow ();
	} /*setwindowmessage*/


static Boolean initmainwindow (void) {
	
	WindowPtr w;
	
	w = mainwindow = GetNewWindow (128, nil, (WindowPtr) -1);
	
	if (w == nil)
		return (false);
	
	ShowWindow (w);
	
	return (true);
	} /*initmainwindow*/


static Boolean stringtotext (Str255 s, Handle *htext) {
	
	long len;
	Handle h;
	
	len = s [0];
	
	h = NewHandle (len);
	
	if (h == nil)
		return (false);
		
	BlockMove (&s [1], *h, len);
	
	*htext = h;
	
	return (true);
	} /*stringtotext*/
	
	
static Boolean texttostring (Handle htext, Str255 s) {
	
	long len;
	
	len = GetHandleSize (htext);
	
	if (len > 255)
		len = 255;
		
	BlockMove (*htext, &s [1], len);
	
	s [0] = (char) len;
	
	return (true);
	} /*texttostring*/
	
	
static Boolean IACpushtextparam (AppleEvent *event, Handle val, OSType keyword) {

	AEDesc desc;
	OSErr ec;
	
	desc.descriptorType = 'TEXT';
	
	desc.dataHandle = val;
	
	ec = AEPutParamDesc (event, (AEKeyword) keyword, &desc);
	
	return (ec == noErr);
	} /*IACpushtextparam*/


static Boolean IACgettextparam (AppleEvent *event, OSType keyword, Handle *htext) {
	
	AEDesc result;
	OSErr ec;
	Boolean fl;
	
	ec = AEGetParamDesc (event, (AEKeyword) keyword, 'TEXT', &result);
	
	if (ec != noErr)
		return (false);
		
	*htext = result.dataHandle;
	
	fl = HandToHand (htext) == noErr;
	
	AEDisposeDesc (&result);
	
	return (fl);
	} /*IACgettextparam*/


static Boolean IACerrorreply (AppleEvent *reply, Str255 errorstring) {
	
	/*
	return true if the reply is an error -- if so it has an 'errn' value
	and an 'errs' value. if we return false, the reply is not an error.
	*/
	
	OSErr ec;
	AEDesc desc;
	
	ec = AEGetParamDesc (reply, (AEKeyword) 'errn', 'shor', &desc);
	
	if (ec != noErr) /*the reply isn't an error*/
		return (false);

	AEDisposeDesc (&desc);
	
	ec = AEGetParamDesc (reply, (AEKeyword) 'errs', 'TEXT', &desc);
	
	if (ec != noErr) {
		
		copystring ("\pUnknown error.", errorstring);
		
		return (true);
		}
	else
		texttostring (desc.dataHandle, errorstring);
	
	AEDisposeDesc (&desc);
	
	return (true); /*there was an error returned*/
	} /*IACerrorreply*/
	

static pascal short WaitRoutine (ev, sleep, mousergn) EventRecord *ev; long *sleep; RgnHandle *mousergn; {
	
	return (0); /*keep waiting*/
	} /*WaitRoutine*/


#if GENERATINGCFM
	
	static RoutineDescriptor WaitRoutineDesc = BUILD_ROUTINE_DESCRIPTOR (uppAEIdleProcInfo, WaitRoutine);
	
	#define WaitRoutineUPP (&WaitRoutineDesc)
	
#else
	
	#define WaitRoutineUPP ((AEIdleUPP) WaitRoutine)

#endif

static Boolean FrontierDoScript (Str255 script, Str255 returns) {
	
	/*
	Send a Do Script message to Frontier. The first parameter contains a short
	script to be run. The second parameter is the string that Frontier returned 
	as the value generated by running the script. 
	
	Returns true if we were able to send the message to Frontier, and Frontier
	was able to compile and run the script and Frontier replied with a returned
	value. FrontierDoScript returns false if Frontier isn't running, or if the
	script didn't compile or if there was a communications error.
	
	Frontier is not limited to running 255-character scripts or returning 
	255-character returned values. This routine can easily be enhanced to handle 
	larger scripts and returned values. 
	
	11/6/91 DW: Set return string to "IAC Error" if we failed to create an Apple
	event descriptor or if the send failed. We're not suggesting that your
	error messages should be so brief, rather they should be custom-fit to the
	appropriate audience. Here, we want to show you how to locate errors relating
	to the IAC channel -- either you're low on memory, or the Apple Event Manager
	isn't present, or Frontier isn't running. Watch for this string in FDS's 
	little window...
	*/

	OSErr ec;
	AEAddressDesc adr; 
	AppleEvent event, reply;
	OSType idfrontier;
	Handle htext = nil;
	Boolean flhavereply = false;
	
	copystring ("\pIAC Error.", returns); /*default return string*/
	
	idfrontier = 'LAND';
	
	ec = AECreateDesc (typeApplSignature, (Ptr) &idfrontier, sizeof (idfrontier), &adr);
	
	if (ec != noErr)
		return (false);
	
	ec = AECreateAppleEvent (
		
		'misc', 'dosc', &adr, kAutoGenerateReturnID, 
		
		kAnyTransactionID, &event);
	
	AEDisposeDesc (&adr);
	
	if (ec != noErr)
		return (false);
		
	if (!stringtotext (script, &htext))
		return (false);
		
	if (!IACpushtextparam (&event, htext, '----')) {
		
		DisposeHandle (htext);
		
		return (false);
		}
	
	DisposeHandle (htext);
	
	ec = AESend (
		
		&event, &reply, kAEWaitReply + kAECanInteract + kAECanSwitchLayer, 
		
		kAEHighPriority, kNoTimeOut, WaitRoutineUPP, (AEFilterUPP) nil);
	
	if (ec != noErr)
		goto error;
		
	flhavereply = true;
	
	if (IACerrorreply (&reply, returns)) /*syntax error or runtime error*/
		goto error;
		
	if (IACgettextparam (&reply, '----', &htext)) {
	
		texttostring (htext, returns);
	
		DisposeHandle (htext);
		}
	
	AEDisposeDesc (&event);	
	
	AEDisposeDesc (&reply);
	
	return (true);
	
	error:
	
	AEDisposeDesc (&event);	
	
	if (flhavereply)
		AEDisposeDesc (&reply);
	
	return (false);
	} /*FrontierDoScript*/
	
	
static void handledrag (EventRecord ev, WindowPtr w) {
	
	Rect r;

	r = qd.screenBits.bounds; 
   
	r.top = r.top + GetMBarHeight (); 
               
	r.left = r.left + 4;  
               
	r.right = r.right - 4;
               
	r.bottom = r.bottom - 4;
             
	DragWindow (w, ev.where, &r);   
	} /*handledrag*/
	
	
static Boolean exitprogram (void) {
	
	/*
	returns true if the user clicks in the go-away box.
	*/
	
	EventRecord ev;
	WindowPtr w;
	
	if (!WaitNextEvent (everyEvent, &ev, 1, nil))
		return (false);
	
	if (ev.what != mouseDown)
		return (false);
		
	switch (FindWindow (ev.where, &w)) {
	
		case inGoAway: /*click in go-away box to exit program*/
			return (TrackGoAway (w, ev.where));
		
		case inSysWindow:
			SystemClick (&ev, w); 
			
			return (false);
		
		case inDrag:
			handledrag (ev, w);
			
			return (false);
			
		} /*switch*/
		
	return (false); /*don't exit the program*/
	} /*exitprogram*/


static void waitseconds (long n) {
	
	long endticks;
	
	endticks = TickCount () + (n * 60);
	
	while (TickCount () < endticks)
		SystemTask ();
	} /*waitseconds*/


void main (void) {
	
	Str255 script, result;
	Boolean error;
	short i;
	
	initmacintosh ();
	
	initmainwindow (); 
	
	copystring ("\p94 + ", script);
	
	if (FrontierDoScript (script, result))
		copystring ("\pFrontierDoScript should have returned false.", result);
	
	setwindowmessage (script, result); /*should say "Can't compile this script..."*/
	
	waitseconds (1); /*give user a chance to see error message*/
	
	for (i = 1; i <= 50; ++i) {
		
		if (exitprogram ()) /*returns true if user clicks in go away box*/
			return;
		
		copystring ("\pstates.nthState (", script);
		
		pushlong (i, script);
		
		pushstring ("\p)", script);
		
		error = !FrontierDoScript (script, result);
		
		setwindowmessage (script, result);
		
		if (error)
			break;
		} /*while*/
	
	waitseconds (2); /*give user a chance to see the final result*/
	} /*main*/


