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


/*
Briefly -- this is a very small program that demonstrates the UserLand Outline Sharing
protocol. For detailed info, see the enclosed "About Outline Sharing" file.

Please contact UserLand Software at on AppleLink at the UserLand Discussion board under
the Third Parties icon, or GO USERLAND on CompuServe for further information on outline 
sharing. 

We hope you like outline sharing and add it to your Macintosh application.

Thanks!

UserLand Software

7/1/92 DW: Added support for system event handlers.
*/


#include <menusharing.h>
#include <iac.h>
#include "outlinesharing.h"


#define applemenu 128 /*the resource id of the apple menu*/

#define aboutitem 1 /*the about command*/

#define filemenu 129 /*resource id of file menu, shared menus appear to right of this*/

#define firstsharedmenu filemenu+1 /*resource id for first shared menu*/

#define quititem 1 /*the single item in the file menu -- this is a very simple program!*/

MenuHandle happlemenu, hfilemenu; /*the two fixed, non-shared menus in this program*/

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

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

short flexitmainloop = false; /*when true we fall thru the main event loop*/
	
	


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.
	*/

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


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.
	*/
	
	register char len;
	register 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.
	*/
	
	register short rh = r.bottom - r.top;
	register short rw = r.right - r.left;
	register 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 Boolean setwindowmessage (Str255 s) {
	
	copystring (s, windowmessage);
	
	SetPort (mainwindow);
	
	updatemainwindow ();
	
	return (true);
	} /*setwindowmessage*/


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


static void handleupdate (EventRecord *ev) {
	
	register WindowPtr w;
	
	w = (WindowPtr) (*ev).message;
	
	BeginUpdate (w);
	
	SetPort (w);
	
	updatemainwindow ();
	
	EndUpdate (w);
	} /*handleupdate*/


static void handledrag (EventRecord *ev, WindowPtr w) {
	
	Rect r;

	r = qd.screenBits.bounds; 
	
	r.top = r.top + GetMBarHeight (); 
	
	InsetRect (&r, 4, 4);
	
	DragWindow (w, (*ev).where, &r);
	} /*handledrag*/


static void handlemenu (long codeword) {
	
	register short idmenu, iditem;
	
	iditem = LoWord (codeword);
	
	idmenu = HiWord (codeword);
	
	if (SharedMenuHit (idmenu, iditem)) 		
		goto exit;
	
	switch (idmenu) {
	
		case applemenu: 
			switch (iditem) {
				
				case aboutitem:
					Alert (262, nil);
					
					break;
			
				default: {
				
					Str255 s;
					
					GetItem (happlemenu, iditem, s);
					
					OpenDeskAcc (s);
					
					break;
					}
				} /*switch*/
			
			break; /*apple menu*/

		case filemenu: 
			switch (iditem) {
				
				case quititem:
				
					flexitmainloop = true;
					
					break;
				} /*switch*/
			
			break; /*file menu*/
			
		} /*switching on which menu was invoked*/
		
	exit:
	
	HiliteMenu (0);
	} /*handlemenu*/


static void handlemouse (EventRecord *ev) {

	register short part;
	WindowPtr w;
	
	part = FindWindow ((*ev).where, &w);
	
	if (w != nil) 
	
		if (w != FrontWindow ()) { /*just like all other Mac programs*/
			
			SelectWindow (w);
							
			return; /*the mouse click is consumed by the bringtofront operation*/
			}
	
	switch (part) {
	
		case inMenuBar: 
			handlemenu (MenuSelect ((*ev).where)); 
			
			break;
		
		case inSysWindow:
			SystemClick (ev, w); 
			
			break;
		
		case inDrag:
			handledrag (ev, w);
			
			break;
			
		} /*switch*/
	} /*handlemouse*/


static void handlekeystroke (EventRecord *ev) {

	register char ch = (*ev).message & charCodeMask;
	
	if (SharedScriptRunning ()) { /*cmd-period terminates the script*/
	
		if (((*ev).modifiers & cmdKey) && (ch == '.')) { 
			
			CancelSharedScript (); /*cancel the shared menu script, if one is running*/
		
			return;
			}
		}
	
	handlemenu (MenuKey (ch)); /*not cmd-period, process the normal way*/
	} /*handlekeystroke*/


static void handleevent (EventRecord *ev) {
	
	switch ((*ev).what) {
	
		case keyDown: case autoKey: 
			handlekeystroke (ev);
			
			break;
			
		case mouseDown:
			handlemouse (ev);
			
			break;
		
		case updateEvt:
			handleupdate (ev);
			
			break;
			
		case kHighLevelEvent:
			AEProcessAppleEvent (ev);
			
			break;
		} /*switch*/
	} /*handleevent*/


static void maineventloop (void) {
	
	EventRecord ev;
	
	while (!flexitmainloop) {
		
		if (WaitNextEvent (everyEvent, &ev, 1, nil)) 
			handleevent (&ev);
		
		CheckSharedMenus (firstsharedmenu); 
		} /*while*/
	} /*maineventloop*/


static void initmenus (void) {
	
	/*
	set up our apple and file menus.  nothing fancy.
	*/
	
	happlemenu = GetMenu (applemenu); 
	
	AddResMenu (happlemenu, 'DRVR'); 
	
	InsertMenu (happlemenu, 0); 
	
	hfilemenu = GetMenu (filemenu); 
	
	InsertMenu (hfilemenu, 0);
	
	DrawMenuBar ();
	} /*initmenus*/


static void initmacintosh (void) {

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

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


static pascal OSErr handlequit (AppleEvent *event, AppleEvent *reply, long refcon) {
	
	/*DebugStr ("\pquitapp");*/
	
	flexitmainloop = true;
	
	return (noErr);
	} /*handlequit*/
	
	
static pascal OSErr handleopenapp (AppleEvent *event, AppleEvent *reply, long refcon) {
	
	/*DebugStr ("\popenapp");*/
	
	return (noErr);
	} /*handleopenapp*/


static pascal Boolean openfilespec (FSSpec *fs) {

	/*DebugStr ((*fs).name);*/
	
	return (true);
	} /*openfilespec*/
	

static pascal Boolean printfilespec (FSSpec *fs) {

	/*DebugStr ((*fs).name);*/
	
	return (true);
	} /*printfilespec*/
	

static pascal OSErr handleopen (AppleEvent *event, AppleEvent *reply, long refcon) {
	
	IACglobals.event = event;
	
	IACglobals.reply = reply;
	
	IACglobals.refcon = refcon;
	
	return (IACdrivefilelist (&openfilespec));
	} /*handleopen*/
	
		
static pascal OSErr handleprint (AppleEvent *event, AppleEvent *reply, long refcon) {
	
	IACglobals.event = event;
	
	IACglobals.reply = reply;
	
	IACglobals.refcon = refcon;
	
	return (IACdrivefilelist (&printfilespec));
	} /*handleprint*/
	
		
static pascal OSErr setmessageverb (AppleEvent *event, AppleEvent *reply, long refcon) {
	
	/*
	display a message in OutlineSharer's window.
	*/

	Str255 s;
	
	if (SharedScriptCancelled (event, reply)) 
		return (noErr);
	
	IACglobals.event = event;
	
	IACglobals.reply = reply;
	
	if (!IACgetstringparam ((OSType) keyDirectObject, s))
		return (noErr);
	
	IACreturnboolean (setwindowmessage (s));
	
	return (noErr);
	} /*setmessageverb*/
	

static pascal OSErr setoutlineverb (AppleEvent *event, AppleEvent *reply, long refcon) {
	
	/*
	set the current outline from the outline parameter passed as part
	of the Apple Event.
	*/

	hdloutlinerecord houtline;
		
	if (SharedScriptCancelled (event, reply)) 
		return (noErr);
	
	IACglobals.event = event;
	
	IACglobals.reply = reply;
	
	if (!IACgetoutlineparam ((OSType) keyDirectObject, &houtline))
		return (noErr);
		
	opDisposeOutlineRecord (outlinedata); /*only one outline managed at a time*/
	
	outlinedata = houtline; /*set the current outline*/
	
	IACreturnboolean (true);
	
	return (noErr);
	} /*setoutlineverb*/
	
	
static Boolean nooutlineerror (void) {

	if (outlinedata == nil) {
		
		IACreturnerror (1, "\pNo outline has been sent to OutlineSharer");
		
		return (true);
		}
	
	return (false);
	} /*nooutlineerror*/
	
	
static pascal OSErr getoutlineverb (AppleEvent *event, AppleEvent *reply, long refcon) {
	
	/*
	return the outline that's resident in OutlineSharer.
	*/
	
	if (SharedScriptCancelled (event, reply)) 
		return (noErr);
	
	IACglobals.event = event;
	
	IACglobals.reply = reply;
	
	if (nooutlineerror ()) 
		return (noErr);
	
	IACreturnoutline (outlinedata);
	
	return (noErr);
	} /*getoutlineverb*/
	
	
static void allupper (Str255 bs) {
	
	register char len = bs [0];
	register char *p = (char *) &bs [1];
	register char ch;
	
	while (len--) {
		
		ch = *p;
		
		if ((ch >= 'a') && (ch <= 'z'))
			*p -= 32;
			
		p++;
		} /*while*/
	} /*allupper*/
	
	
static Boolean uppercasevisit (hdlheadrecord hnode) {
	
	Str255 bs;
	
	opGetHeadString (hnode, bs);
	
	allupper (bs);
	
	opSetHeadString (hnode, bs);
	
	return (true);
	} /*uppercasevisit*/
	
	
static pascal OSErr uppercaseoutlineverb (AppleEvent *event, AppleEvent *reply, long refcon) {

	/*
	convert all the characters in the current outline to lowercase.
	*/
	
	if (SharedScriptCancelled (event, reply)) /*part of the menu sharing protocol*/
		return (noErr);
	
	IACglobals.event = event;
	
	IACglobals.reply = reply;
	
	if (nooutlineerror ()) /*no outline to uppercase*/
		return (noErr);
	
	opVisitOutline (uppercasevisit); /*visit every node in the outline*/
	
	IACreturnboolean (true);
	
	return (noErr);
	} /*uppercaseoutlineverb*/
	
	
static void alllower (Str255 bs) {
	
	register char len = bs [0];
	register char *p = (char *) &bs [1];
	register char ch;
	
	while (len--) {
		
		ch = *p;
		
		if ((ch >= 'A') && (ch <= 'Z'))
			*p += 32;
			
		p++;
		} /*while*/
	} /*alllower*/
	
	
static Boolean lowercasevisit (hdlheadrecord hnode) {
	
	Str255 bs;
	
	opGetHeadString (hnode, bs);
	
	alllower (bs);
	
	opSetHeadString (hnode, bs);
	
	return (true);
	} /*lowercasevisit*/
	
	
static pascal OSErr lowercaseoutlineverb (AppleEvent *event, AppleEvent *reply, long refcon) {
	
	/*
	convert all the characters in the current outline to lowercase.
	*/
	
	if (SharedScriptCancelled (event, reply)) /*part of the menu sharing protocol*/
		return (noErr);
	
	IACglobals.event = event;
	
	IACglobals.reply = reply;
	
	if (nooutlineerror ()) /*no outline to lowercase*/
		return (noErr);
		
	opVisitOutline (lowercasevisit); /*visit every node in the outline*/
	
	IACreturnboolean (true);
	
	return (noErr);
	} /*lowercaseoutlineverb*/
	
	
static pascal void errordialog (Str255 s) {
	
	ParamText (s, "\p", "\p", "\p"); 
	
	Alert (263, nil);
	} /*errordialog*/


static pascal void eventfilter (EventRecord *ev) {
	
	handleevent (ev);
	} /*eventfilter*/
	

void main (void) {
	
	initmacintosh ();
	
	if (!InitSharedMenus (&errordialog, &eventfilter))
		goto error;
	
	if (!IACinstallhandler ('OUTS', 'smsg', (ProcPtr) setmessageverb))
		goto error;
	
	if (!IACinstallsystemhandler ('outs', 'sout', (ProcPtr) setoutlineverb))
		goto error;
	
	if (!IACinstallsystemhandler ('outs', 'gout', (ProcPtr) getoutlineverb))
		goto error;
	
	if (!IACinstallsystemhandler ('outs', 'lcas', (ProcPtr) lowercaseoutlineverb))
		goto error;
	
	if (!IACinstallsystemhandler ('outs', 'ucas', (ProcPtr) uppercaseoutlineverb))
		goto error;
	
	if (!IACinstallhandler (kCoreEventClass, kAEOpenApplication, (ProcPtr) &handleopenapp))
		goto error;
	
	if (!IACinstallhandler (kCoreEventClass, kAEOpenDocuments, (ProcPtr) &handleopen))
		goto error;
	
	if (!IACinstallhandler (kCoreEventClass, kAEPrintDocuments, (ProcPtr) &handleprint))
		goto error;
	
	if (!IACinstallhandler (kCoreEventClass, kAEQuitApplication, (ProcPtr) &handlequit))
		goto error;

	initmenus ();
	
	initmainwindow ();
	
	maineventloop ();
		
	ExitToShell ();
	
	error:
	
	Alert (261, nil); /*this application requires system 7.0 or higher*/
		
	ExitToShell ();
	} /*main*/


