Source to jet/events.c


Enter a symbol's name here to quickly find it.

#include <Desk.h>
#include <Events.h>
#include <Fonts.h>
#include <Menus.h>
#include <Resources.h>
#include <Scrap.h>
#include <SysEqu.h>
#include <ToolUtils.h>
#include <Windows.h>
#include <Controls.h>
#define HiliteFlag (*(Ptr)HiliteMode)

#include <AppleEvents.h>
#include <GestaltEqu.h>
#include <Traps.h>

#include "jet.h"

/* constants */

#define TRANS /* define this if you want a transfer menu */
/* #define LAYERS define this if you want layers style cmd-/ */

#define SBARWIDTH 16

#define abs(a) ((a) < 0 ? -(a) : (a))
#define topLeft(r) (((Point *) &(r))[0])
#define botRight(r) (((Point *) &(r))[1])

/* types */

typedef struct {
	OSErr (*handler)(AppleEvent *theAppleEvent, AppleEvent *reply,
			 long handlerRefcon);
	long handlerRefcon;
} AEDispatchRec;

/* functions */

static void trackCursor(void);
static void handledrag(Point pt);
static void handlegrow(Point pt);
static void handlekey(long message, short modifiers);
static void setupmenu(void);
static void updatemenu(void);
static void handlemenu(long mResult);
INLINE static void tscroll(int dist);
static void handlescroll(short part, Point pt);
static void handleselect(long when, Point where);
static void handleDoubleClick(Point where);
static void handleMouseDrag(Point where, Boolean extend);
static void copySelection(void);
static void invertSelection(Boolean);
static RgnHandle selectedRgn(Point start, Point cur, RgnHandle rgn);
static Boolean specialChar(char c);
static void rectToScreen(Rect *r);
static int ptToY(Point p);
static int ptToX(Point p);

static OSErr OAppHandler(AppleEvent *theAppleEvent, AppleEvent *reply,
			 long handlerRefcon);
static OSErr ODocHandler(AppleEvent *theAppleEvent, AppleEvent *reply,
			 long handlerRefcon);
static OSErr PDocHandler(AppleEvent *theAppleEvent, AppleEvent *reply,
			 long handlerRefcon);
static OSErr QuitHandler(AppleEvent *theAppleEvent, AppleEvent *reply,
			 long handlerRefcon);

/* variables */

WindowPtr myWindow;
Rect conRect;
short fascent, fheight, fwidth, ftotal;
short curVal, maxVal;
Rect selectedChars;		/* selection in x,y coords */

static WindowRecord wRecord;
static ControlHandle myScrollbar;
static Handle clip;
static long cliplen, clipoff;
static Boolean wactive = true;
static Boolean quitFlag = false;

static int wneSupport;

static void (*nextmenu)(short theMenu, short theItem);

static int aeSupport;
AEDispatchRec aeDT[] = {
	{ OAppHandler, 0 },
	{ ODocHandler, 0 },
	{ PDocHandler, 0 },
	{ QuitHandler, 0 }
};

#ifndef XXX
#define NewAEEventHandlerProc(userRoutine) (ProcPtr)(userRoutine)
#endif

void init_events(void)
{
	FontInfo fInfo;
	long response;
	OSErr s;
	Boolean fail;

	sysvar.eventloop = eventloop;
	
	setupmenu();
	
	myWindow = NewWindow(&wRecord, &pref.wrect, "\pConsole", false, 0,
		(WindowPtr)-1L, 0, 0L);
	SetPort(myWindow);
	myScrollbar = NewControl(myWindow, &pref.wrect, "\pScrollbar",
				 false, 0, 0, 0, scrollBarProc, 0L);

	TextFont(monaco);
	TextSize(9);
	TextMode(srcCopy);
	GetFontInfo(&fInfo);
	fascent = fInfo.ascent;
	ftotal = fInfo.ascent + fInfo.descent + fInfo.leading;
	fheight = ftotal + 1;
	fwidth = fInfo.widMax;

	curVal = maxVal = TOTROWS - maxy - 1;

	SetRect(&conRect, 0, 0,
		(maxx+1) * fwidth + (LEFT*2), (maxy+1) * fheight);
	SizeWindow(myWindow,
		   conRect.right + SBARWIDTH, conRect.bottom, false);
	ShowWindow(myWindow);
	SizeControl(myScrollbar, SBARWIDTH,
		    myWindow->portRect.bottom - myWindow->portRect.top - 13);
	MoveControl(myScrollbar,
		    myWindow->portRect.right-(SBARWIDTH-1),
		    myWindow->portRect.top - 1);
	SetCtlMax(myScrollbar, maxVal);
	SetCtlValue(myScrollbar, curVal);
	ShowControl(myScrollbar);

	clip = NewHandle(0L);
	SetRect(&selectedChars, -1, -1, -1, -1);
	updatemenu();

	/* At this point it should be safe to use the console */
	c_conws("\033Yz "); /* jump to bottom of console */

	wneSupport = TrapAvailable(_WaitNextEvent);

	Gestalt(gestaltAppleEventsAttr, &response);
	aeSupport = response & (1 << gestaltAppleEventsPresent);
	if (aeSupport) {
		fail = false;
		s = AEInstallEventHandler(kCoreEventClass, kAEOpenApplication,
					  NewAEEventHandlerProc(pAEHandler), 0, false);
		fail |= s != noErr;
		s = AEInstallEventHandler(kCoreEventClass, kAEOpenDocuments,
					  NewAEEventHandlerProc(pAEHandler), 1, false);
		fail |= s != noErr;
		s = AEInstallEventHandler(kCoreEventClass, kAEPrintDocuments,
					  NewAEEventHandlerProc(pAEHandler), 2, false);
		fail |= s != noErr;
		s = AEInstallEventHandler(kCoreEventClass, kAEQuitApplication,
					  NewAEEventHandlerProc(pAEHandler), 3, false);
		fail |= s != noErr;
		if (fail) {
			c_conws("Error installing AppleEvent handlers\r\n");
			p_term0();
		}
	}
	
	eventloop();
}

void eventloop(void)
{
	EventRecord myEvent;
	Boolean gotEvent;
	short code;
	GrafPtr whichWindow;
	ControlHandle whichControl;
	Rect r;
	int i, n;
	
	SetPort(myWindow);
	while (1) {
		if (wneSupport)
			gotEvent = WaitNextEvent(everyEvent, &myEvent, 0, 0L);
		else {
			gotEvent = GetNextEvent(everyEvent, &myEvent);
			SystemTask();
		}
		if (!gotEvent) {
			if (cliplen) {
				HLock(clip);
				n = cliplen > 8 ? 8 : cliplen;
				for (i = 0; i < n; i++) {
					keymsg = (*clip)[clipoff++];
					keymod = 0;
					(*(void (*)(void))kbd.ikbdsys)();
				}
				HUnlock(clip);
				cliplen -= n;
				if (cliplen == 0)
					SetHandleSize(clip, 0L);
			}
			if (wactive && EmptyRgn(((WindowPeek)myWindow)->updateRgn))
				swap_curs();
			trackCursor();
			return;
		}
		switch(myEvent.what) {
		case mouseDown:
			code = FindWindow(myEvent.where, &whichWindow);
			switch (code) {
			case inMenuBar:
				updatemenu();
				handlemenu(MenuSelect(myEvent.where));
				break;
			case inSysWindow:
				SystemClick(&myEvent, whichWindow);
				break;
			case inDrag:
				handledrag(myEvent.where);
				break;
			case inGrow:
				handlegrow(myEvent.where);
				break;
			case inContent:
				if (whichWindow != FrontWindow())
					SelectWindow(whichWindow);
				else {
					GlobalToLocal(&myEvent.where);
					code = FindControl(myEvent.where,
							   whichWindow,
							   &whichControl);
					if (whichControl == myScrollbar)
						handlescroll(code,
							     myEvent.where);
					else
						handleselect(myEvent.when,
							     myEvent.where);
				}
				break;
			}
			break;

		case activateEvt:
			if(myEvent.message == (long)myWindow) {
				SetPort(myWindow);
				wactive = (myEvent.modifiers & activeFlag) != 0;
				invertSelection(false);
				HiliteControl(myScrollbar, wactive ? 0 : 255);
				if (!wactive) {
					curs_off();
					curs_disable();
				} else {
					curs_enable();
				}
				SetRectRgn(myWindow->clipRgn,
					   conRect.right + 1,
					   conRect.bottom - 14,
					   conRect.right + SBARWIDTH,
					   conRect.bottom);
				DrawGrowIcon(myWindow);
				RectRgn(myWindow->clipRgn,
					&myWindow->portRect);
			}
			break;

		case updateEvt:
			if(myEvent.message == (long)myWindow) {
				SetPort(myWindow);
				BeginUpdate(myWindow);
				if (wactive)
					invertSelection(true);
				refresh();
				if (wactive)
					invertSelection(false);
				r.top = conRect.top;
				r.bottom = conRect.bottom;
				r.left = conRect.right + 1;
				r.right = r.left + SBARWIDTH;
				if (RectInRgn(&r, myWindow->visRgn)) {
					RectRgn(myWindow->clipRgn,
						&r);
					DrawGrowIcon(myWindow);
					DrawControls(myWindow);
					RectRgn(myWindow->clipRgn,
						&myWindow->portRect);
				}
				EndUpdate(myWindow);
				swap_curs();
			}
			break;

	 	case keyDown:
		case autoKey:
			handlekey(myEvent.message, myEvent.modifiers);
			break;
		case kHighLevelEvent:
			if (aeSupport)
				AEProcessAppleEvent(&myEvent);
			if (quitFlag)
				ExitToShell();
			break;

		}
	}
	return;
}

/* Cursor Stuff */

static void trackCursor(void)
{
	GrafPtr savePort;
	Point pt;
	static Cursor *cursor, *oldcursor = 0, ibeam;
	CursHandle ch;
	
	if (!(FrontWindow() == myWindow))
		return;
	
	if (!oldcursor) {			/* if first time */
		if ((ch = GetCursor(iBeamCursor))) {
			HLock((Handle)ch);
			ibeam = **ch;
			ReleaseResource((Handle)ch);
		} else
			ibeam = qd.arrow;
	}	
	
	GetPort(&savePort);
	SetPort(myWindow);
	GetMouse(&pt);			/* local coords */
	
	if (PtInRect(pt, &conRect))
		cursor = &ibeam;
	else
		cursor = &qd.arrow;
	
#ifdef notdef
	if (cursor != oldcursor)
#endif
		SetCursor(cursor);
	
	oldcursor = cursor;
	SetPort(savePort);
}

/* Drag Stuff */

static void handledrag(Point pt)
{
	Rect dragRect;

	SetRect(&dragRect, 4, 24,
		qd.screenBits.bounds.right-4, qd.screenBits.bounds.bottom-4);
	DragWindow(myWindow, pt, &dragRect);
	writeprefs();
}

/* Grow Stuff */

static void handlegrow(Point pt)
{
	long wsize;
	Rect r;
	short v, h, rows, columns;

	SetRect(&r, LEFT*2+fwidth*20+SBARWIDTH, 10*fheight,
		qd.screenBits.bounds.right, qd.screenBits.bounds.bottom);
	if ((wsize = GrowWindow(myWindow, pt, &r)) == 0)
		return;
	v = ((short *)&wsize)[0];
	h = ((short *)&wsize)[1];
	h -= LEFT*2+SBARWIDTH;
	rows = v / fheight;
	columns = h / fwidth;
	if (columns > TOTCOLS)
		columns = TOTCOLS;

	resize(rows, columns);
	offsetSelection(curVal - (TOTROWS - maxy - 1));
	curVal = maxVal = TOTROWS - maxy - 1;

	SetRect(&conRect, 0, 0,
		(maxx+1) * fwidth + (LEFT*2), (maxy+1) * fheight);
	SizeWindow(myWindow,
		conRect.right + SBARWIDTH, conRect.bottom, false);
	SizeControl(myScrollbar,
		    SBARWIDTH,
		    myWindow->portRect.bottom - myWindow->portRect.top - 13);
	MoveControl(myScrollbar,
		    myWindow->portRect.right-(SBARWIDTH-1),
		    myWindow->portRect.top - 1);
	SetCtlMax(myScrollbar, maxVal);
	SetCtlValue(myScrollbar, curVal);

	RectRgn(myWindow->clipRgn, &myWindow->portRect);
	EraseRect(&myWindow->portRect);
	InvalRect(&myWindow->portRect);

	writeprefs();
}

/* Keyboard Stuff */

static void handlekey(long message, short modifiers)
{
	long scrollVal;
	
	if (modifiers & cmdKey) {
		handlemenu(MenuKey(message & charCodeMask));
	} else {
		switch ((unsigned short)message >> 8) {
		case 0x74: /* page up */
			scrollVal = -maxy;
			goto scroll;
		case 0x79: /* page down */
			scrollVal = maxy;
			goto scroll;
		case 0x73: /* home */
			scrollVal = -curVal;
			goto scroll;
		case 0x77: /* end */
			scrollVal = maxVal - curVal;
scroll:
			curs_off();
			tscroll(scrollVal);
			break;
		default:
			keymsg = message;
			keymod = modifiers;
			(*(void (*)(void))kbd.ikbdsys)();
			break;
		}
	}
}

/* Menu Stuff */

#ifdef DEBUG
/* This is used to add a debugging menu.  It is also an example of
 * how to add menus to the default menus.  This can also be done
 * from a MiNT process.
 */

static void loadfile(void)
{
	Point where;
	SFTypeList typeList;
	SFReply reply;
	char buf[1024], *p;
	short f;
	long count;
	
	where.h = 40;
	where.v = 60;
	typeList[0] = TEXT;
	SFGetFile(where, "\p", 0L, 1, typeList, 0L, &reply);
	if (!reply.good)
		return;
	if (FSOpen(reply.fName, reply.vRefNum, &f) != 0)
		return;
	while (1) {
		count = 1024;
		FSRead(f, &count, (Ptr)buf);
		if (count == 0)
			break;
		for (p = buf; count; count--)
			b_conout(2, *p++);
	}
	FSClose(f);	
}

static short debugMenu;
static void (*debugnextmenu)(short theMenu, short theItem);

static void debugmenu(short theMenu, short theItem)
{
	if (theMenu == debugMenu) {
		switch (theItem) {
		case 1:
			c_conws("\033H"); /* Home */
			break;
		case 2:
			c_conws("\033E"); /* Clear Home */
			break;
		case 3:
			c_conws("\033J"); /* Clear End of Screen */
			break;
		case 4:
			c_conws("\033M"); /* Delete Line */
			break;
		case 5:
			c_conws("\033L"); /* Insert Line */
			break;
		case 6:
			c_conws("\033p"); /* Inverse On */
			break;
		case 7:
			c_conws("\033q"); /* Inverse Off */
			break;
		case 8:
			loadfile();
			break;
		}
	} else {
		if (debugnextmenu)
			(*debugnextmenu)(theMenu, theItem);
	}
}

static void setupdebugmenu(void)
{
	MenuHandle debugM;
	
	debugMenu = 255;
	while (GetMHandle(debugMenu) != 0)
		debugMenu++;
	debugM = NewMenu(debugMenu, "\pDebug");
	AppendMenu(debugM, "\pHome");
	AppendMenu(debugM, "\pClear Home");
	AppendMenu(debugM, "\pClear End of Screen");
	AppendMenu(debugM, "\pDelete Line");
	AppendMenu(debugM, "\pInsert Line");
	AppendMenu(debugM, "\pInverse On");
	AppendMenu(debugM, "\pInverse Off");
	AppendMenu(debugM, "\pLoad File…");
	InsertMenu(debugM, 0);
	debugnextmenu = nextmenu;
	nextmenu = debugmenu;
}
#endif

#ifdef TRANS
#define	TRANS_ALPHA 1
#define	TRANS_BBEDIT 2

static short transMenu;
static void (*transnextmenu)(short theMenu, short theItem);

static void switchTo(long sig) 
{
	ProcessSerialNumber process;
	ProcessInfoRec infoRec;
	FSSpec spec;
	Str255 name;
	
	process.highLongOfPSN = 0;
	process.lowLongOfPSN = kNoProcess;
	infoRec.processInfoLength = sizeof(ProcessInfoRec);
	infoRec.processName = name;
	infoRec.processAppSpec = &spec;
	while (GetNextProcess(&process) == noErr) {
		if (GetProcessInformation(&process, &infoRec) == noErr) {
			if ((infoRec.processType == APPL) &&
			    (infoRec.processSignature == sig)) {
				SetFrontProcess(&process);
				return;
			}
		}
	}
	SysBeep(5);
}

static void transmenu(short theMenu, short theItem)
{
	if (theMenu == transMenu) {
		switch (theItem) {
		case TRANS_ALPHA:
			switchTo(ALPHA);
			break;
		case TRANS_BBEDIT:
			switchTo(BBEDIT);
			break;
		}
	} else {
		if (transnextmenu)
			(*transnextmenu)(theMenu, theItem);
	}
}

static void setuptransmenu(void)
{
	long response;
	MenuHandle transM;

	Gestalt(gestaltSystemVersion, &response);
	if (response & 0xff00 < 0x700)
		return;
	transMenu = 255;
	while (GetMHandle(transMenu) != 0)
		transMenu++;
	transM = NewMenu(transMenu, "\pTransfer");
 	AppendMenu(transM, "\p/`Alpha");
	AppendMenu(transM, "\pBBEdit");
	InsertMenu(transM, 0);
	transnextmenu = nextmenu;
	nextmenu = transmenu;
}
#endif

#define	appleMenu 255
#define   APPLE_ABOUT 1
#define	fileMenu 256
#define   FILE_QUIT 1
#define editMenu 257
#define	  EDIT_UNDO 1
#define	  EDIT_CUT 3
#define	  EDIT_COPY 4
#define	  EDIT_PASTE 5
#define	  EDIT_COPY_PASTE 6
#define optionsMenu 258
#define   OPTIONS_SWAPDEL 1
#define   OPTIONS_SWAPCNTL 2
#define   OPTIONS_CFLASH 3
#define   OPTIONS_WRAP 4

#define DRVR 0x44525652

MenuHandle appleM;
MenuHandle fileM;
MenuHandle editM;
MenuHandle optionsM;

static void setupmenu(void)
{
	nextmenu = 0;

	appleM = NewMenu(appleMenu, "\p\024");
	AppendMenu(appleM, "\pHelp\311");
	AppendMenu(appleM, "\p(-");
	AddResMenu(appleM, DRVR);
	InsertMenu(appleM, 0);

	fileM = NewMenu(fileMenu, "\pFile");
	AppendMenu(fileM, "\p(/QQuit\311");
	InsertMenu(fileM, 0);

	editM = NewMenu(editMenu, "\pEdit");
	AppendMenu(editM, "\pUndo");
	AppendMenu(editM, "\p(-");
	AppendMenu(editM, "\p(/XCut");
	AppendMenu(editM, "\p(/CCopy");
	AppendMenu(editM, "\p(/VPaste");
#ifdef LAYERS
	AppendMenu(editM, "\p(//Copy-Paste");
#endif
	InsertMenu(editM, 0);

	optionsM = NewMenu(optionsMenu, "\pOptions");
	AppendMenu(optionsM, "\pSwap Delete Key");
	AppendMenu(optionsM, "\pSwap Control Modifier");
	AppendMenu(optionsM, "\pFlash Cursor");
	AppendMenu(optionsM, "\pAuto Wrap");
	InsertMenu(optionsM, 0);


#ifdef DEBUG
	setupdebugmenu();
#endif
#ifdef TRANS
	setuptransmenu();
#endif

	DrawMenuBar();
}

static void updatemenu(void)
{
	EnableItem(fileM, FILE_QUIT);
	EnableItem(editM, EDIT_PASTE);
	if (!emptySelection())
		EnableItem(editM, EDIT_COPY);
	else
		DisableItem(editM, EDIT_COPY);
#ifdef LAYERS
	if (!emptySelection())
		EnableItem(editM, EDIT_COPY_PASTE);
	else
		DisableItem(editM, EDIT_COPY_PASTE);
#endif
	CheckItem(optionsM, OPTIONS_SWAPDEL, pref.swap_delete);
	CheckItem(optionsM, OPTIONS_SWAPCNTL, pref.swap_control);
	CheckItem(optionsM, OPTIONS_CFLASH, pref.curs_flash);
	CheckItem(optionsM, OPTIONS_WRAP, pref.fwrap);
}

static void handlemenu(long mResult)
{
	short theItem, theMenu;
	Str255 name;
	long offset;

	theMenu = ((short *)&mResult)[0];
	theItem = ((short *)&mResult)[1];
	switch(theMenu) {
	case appleMenu:
		if (theItem == APPLE_ABOUT) {
			c_conws("\r\nSorry. Try the man pages, the faq, etc.\r\n");
		} else {
			GetItem(appleM, theItem, name);
			OpenDeskAcc(name);
		}
		break;
	case fileMenu:
		if (theItem == FILE_QUIT) {
			c_conws("\r\nAre you sure you want to quit? ");
			if ((b_conin(2) & 0xff) == 'y')
				ExitToShell();
			c_conws("\r\n");
		}
		break;
	case editMenu:
		if (!SystemEdit(theItem - 1)) {
			switch (theItem) {
			case EDIT_PASTE:
				cliplen = GetScrap(clip, TEXT, &offset);
				clipoff = 0;
				break;
				
			case EDIT_COPY:
				copySelection();
				break;
				
#ifdef LAYERS
			case EDIT_COPY_PASTE:
				copySelection();
				cliplen = GetScrap(clip, TEXT, &offset);
				clipoff = 0;
				break;
#endif
			}
		}
		break;
	case optionsMenu:
		switch (theItem) {
		case OPTIONS_SWAPDEL:
			pref.swap_delete = !pref.swap_delete;
			break;
		case OPTIONS_SWAPCNTL:
			pref.swap_control = !pref.swap_control;
			break;
		case OPTIONS_CFLASH:
			pref.curs_flash = !pref.curs_flash;
			break;
		case OPTIONS_WRAP:
			pref.fwrap = !pref.fwrap;
			break;
		}
		writeprefs();
		break;
	default:
		if (nextmenu)
			(*nextmenu)(theMenu, theItem);
		break;
	}
	HiliteMenu(0);
}

/* Scroll Stuff */

INLINE static void tscroll(int dist)
{
	int delta;		/* amount to adjust scroll region */

	if (!dist)
		return;
	delta = curVal;
	curVal += dist;
	if (curVal < 0)
		curVal = 0;
	else
		if (curVal > maxVal)
			curVal = maxVal;
	SetCtlValue(myScrollbar, curVal);	
	InvalRect(&conRect);
	offsetSelection(delta - curVal);
	refresh();
}

void ScrollText(ControlHandle cntl, short part)
{
	switch (part) {
	case inUpButton:
		tscroll(-1);
		break;
	case inDownButton:
		tscroll(1);
		break;
	case inPageUp:
		tscroll(-maxy);
		break;
	case inPageDown:
		tscroll(maxy);
		break;
	}
}

#ifndef XXX
typedef ProcPtr ControlActionUPP;
#define NewControlActionProc(userRoutine) (ProcPtr)(userRoutine)
#endif

static ControlActionUPP theScrollTextUPP = NULL;

static void handlescroll(short part, Point pt)
{
	int delta;		/* amount to adjust selection */

	curs_off();
	delta = curVal;
	if (part == inThumb) {
		part = TrackControl(myScrollbar, pt, 0L);
		curVal = GetCtlValue(myScrollbar);
		InvalRect(&conRect);
		offsetSelection(delta - curVal);
		refresh();
	} else {
		if (theScrollTextUPP == NULL)
			theScrollTextUPP = NewControlActionProc(pScrollText);
		part = TrackControl(myScrollbar, pt, theScrollTextUPP);
	}
}

void scrollBottom(void)
{
	if (curVal != maxVal) {
		offsetSelection(curVal - maxVal);
		curVal = maxVal;
		SetCtlValue(myScrollbar, curVal);
		InvalRect(&conRect);
		refresh();
	}
}

/* Selection Stuff */

static void handleselect(long when, Point where)
{
	static long lastWhen = 0;
	static Point lastWhere = {0, 0};

	if (((when - lastWhen) < GetDblTime()) &&
	    (abs(where.h - lastWhere.h) < 5) &&
	    (abs(where.v - lastWhere.v) < 5)) {
		handleDoubleClick(where);
		if (StillDown())
			handleMouseDrag(where, true);
	} else
		handleMouseDrag(where, false);
	lastWhen = when;
	lastWhere = where;
}

static void handleMouseDrag(Point where, Boolean extend)
{
	Point start, pt;			/* local coords */
	GrafPtr savePort;
	RgnHandle newRgn = 0, oldRgn = 0, diffRgn = 0, tmpRgn;
	Rect boundary;
	
	newRgn = NewRgn();
	oldRgn = NewRgn();
	diffRgn = NewRgn();
	if (!newRgn || !oldRgn || !diffRgn)
		goto xit;
	
	GetPort(&savePort);
	SetPort(myWindow);
	
	if (!extend) {
		clearSelection();
		start = where;		/* get initial point (already local) */
		SetRect(&boundary, 0, 0, 0, 0);
	} else {
		boundary = selectedChars;
		rectToScreen(&boundary);
		InsetRect(&boundary, 1, 1);
		
		start = topLeft(boundary);
		pt = botRight(boundary);
		oldRgn = selectedRgn(start, pt, oldRgn);
	}
	
	while (StillDown()) {
		GetMouse(&pt);
		
		/*
		 * If extending and we are within the original rectangle, 
		 * always set the selection to the original rectangle.
		 */
		if (extend && PtInRect(pt, &boundary)) {
			start = topLeft(boundary);
			pt = botRight(boundary);
		}
		
		/*
		 * If extend, we have to dynamically pick start appropriately, 
		 * depending on if we are moving to the left or right of the
		 * initial starting rectangle.
		 */
		if (extend) {
			int x1, y1, x2, y2;
			
			x1 = ptToX(start);
			y1 = ptToY(start);
			x2 = ptToX(pt);
			y2 = ptToY(pt);
			
			if ((y1 > y2) ||
			    ((y1 == y2) && (x1 > x2)))  /* if reverse direction */
				start = botRight(boundary);
			else
				start = topLeft(boundary);
		}
		
		newRgn = selectedRgn(start, pt, newRgn);
		XorRgn(newRgn, oldRgn, diffRgn);
		if (!EmptyRgn(diffRgn)) {
			HiliteFlag &= ~(1 << hiliteBit);
			InvertRgn(diffRgn);
		}
		tmpRgn = oldRgn;		/* swap regions */
		oldRgn = newRgn;
		newRgn = tmpRgn;
	}
	
 xit:
	if (newRgn)
		DisposeRgn(newRgn);
	if (oldRgn)
		DisposeRgn(oldRgn);
	if (diffRgn)
		DisposeRgn(diffRgn);
	SetPort(savePort);
	updatemenu();
}


/*
 * Erase or Invert the selection region
 */
static void invertSelection (Boolean erase)
{
	Rect r;
	RgnHandle rgn;
	GrafPtr savePort;
	
	if (emptySelection())
		return;
	
	if (!(rgn = NewRgn()))
		return;
	
	GetPort(&savePort);
	SetPort(myWindow);
	
	r = selectedChars;
	rectToScreen(&r);
	r.bottom -= 1;			/* account for line height fudge */
	rgn = selectedRgn(topLeft(r), botRight(r), rgn);
	if (erase)
		EraseRgn(rgn);
	else {
		HiliteFlag &= ~(1 << hiliteBit);
		InvertRgn(rgn); 
	}
	
	DisposeRgn(rgn);
	SetPort(savePort);
}

Boolean emptySelection(void)
{
	if ((selectedChars.top == -1) && (selectedChars.bottom == -1) &&
	    (selectedChars.left == -1) && (selectedChars.right == -1))
		return true;
	return false;
}

void clearSelection(void)
{
	invertSelection(false);
	SetRect(&selectedChars, -1, -1, -1, -1);
	updatemenu();
}


static RgnHandle selectedRgn (Point start, Point cur, RgnHandle rgn)
{
	int y, x1, y1, x2, y2, t, curx, startx;
	Rect r;
	RgnHandle tmp;
	
	SetEmptyRgn(rgn);
	
	if ((abs(start.h-cur.h) < 2) && (abs(start.v-cur.v) < 2)) /* if move too small */
		return rgn;
	
	x1 = ptToX(start);
	y1 = ptToY(start);
	x2 = ptToX(cur);
	y2 = ptToY(cur);
	curx = cur.h;
	startx = start.h;
	
	/* check for out of bounds */
	if ((y1 == y2) &&
	    (((cur.h < LEFT) && (start.h < LEFT)) ||
	     ((cur.h > (maxx*fwidth + LEFT)) && (start.h > (maxx*fwidth + LEFT)))))
		return rgn;
	
	if ((y1 > y2) ||
	    ((y1 == y2) && (x1 > x2))) {	/* if reverse direction */
		t = x1;				/* flip start, end */
		x1 = x2;
		x2 = t;
		t = y1;
		y1 = y2;
		y2 = t;
		curx = start.h;
		startx = cur.h;
	}
	
	/*
	 * Handle cases where we drag past left or right margins.
	 */
	if ((curx < (LEFT + fwidth/2)) && (y2 > y1)) {
		y2--;
		x2 = maxx;
	}
	if ((startx > (maxx*fwidth + LEFT)) && (y1 < y2)) {
		y1++;
		x1 = 0;
	}
	
	if (!(tmp = NewRgn()))
		return rgn;
	
	SetRect(&selectedChars, x1, y1, x2, y2); /* save current selection */
	
	if (y1 == y2) {			/* if one line */
		SetRect(&r, x1, y1, x2, y2);
		rectToScreen(&r);
		RectRgn(rgn, &r);
	} else {
		SetRect(&r, x1, y1, maxx, y1); /* first line */
		rectToScreen(&r);
		RectRgn(tmp, &r);
		UnionRgn(rgn, tmp, rgn);
		
		for (y = y1+1; y < y2; y++) {	/* full lines inbetween */
			SetRect(&r, 0, y, maxx, y);
			rectToScreen(&r);
			RectRgn(tmp, &r);
			UnionRgn(rgn, tmp, rgn);
		}
		
		SetRect(&r, 0, y2, x2, y2);	/* last line */
		rectToScreen(&r);
		RectRgn(tmp, &r);
		UnionRgn(rgn, tmp, rgn);
	}
	
	DisposeRgn(tmp);
	return rgn;
}

static void copySelection(void)
{
	long l, n, y;
	char *src, *dst, *buf, *buflim;
	
	/*
	 * Calc max buffer which is the number of lines times the
	 * max line size, and allocate it.
	 */
	l = selectedChars.bottom - selectedChars.top + 1;
	l *= (long)(TOTCOLS + 1);
	if (!(buf = (char *)NewPtr(l)))
		return;
	dst = buf;
	buflim = &buf[l];
	
	for (y = selectedChars.top; y <= selectedChars.bottom; y++) {
		src = base[y+curVal];
		if (y == selectedChars.top)	/* if first line */
			src += selectedChars.left;
		
		if (selectedChars.top == selectedChars.bottom)	/* if one line */
			n = selectedChars.right - selectedChars.left + 1; /* sel length */
		else if (y == selectedChars.top)	/* if first line */
			n = maxx + 1 - selectedChars.left; /* first to eol */
		else if (y == selectedChars.bottom) /* if last line */
			n = selectedChars.right + 1; /* bol to last */
		else
			n = maxx + 1;
		
		while (n--) {
			if (dst >= buflim)
				break;
			*dst++ = *src++;
		}
		
		/* trim trailing blanks */
		while ((dst != buf) && (dst[-1] == ' '))
			dst--;

		/*
		 * Add eol if not last line. If last line, add eol if the whole line is
		 * selected. This seems a little hokey, but there is no other way to
		 * tell if we selected the whole line or just part of it.
		 */
		if ((dst < buflim) &&
		    ((y != selectedChars.bottom) || /* if not last line */
			 (selectedChars.right == maxx))) /* last line selected to right margin */
			*dst++ = 0x0d;
	}

	ZeroScrap();
	PutScrap(dst-buf, 0x54455854, (Ptr)buf); /* TEXT */
	DisposePtr(buf);
	
	clearSelection();
}

static void handleDoubleClick(Point where)
{
	int x1, x2, y;
	char *left, *right;
	Rect r;
	RgnHandle rgn;
	GrafPtr savePort;
	
	GetPort(&savePort);
	SetPort(myWindow);
	
	x1 = x2 = ptToX(where);		/* get mouse down point */
	y = ptToY(where);
	
	left = right = (char *)base[y+curVal] + x1;
	/* 
	 * Expand selection to boundaries.
	 * If double click on a special char, just select that char.
	 */
	if (!specialChar(*left)) {
		while ((x1 > 0) && !specialChar(left[-1])) {
			x1--;
			left--;
		}
		while ((x2 < maxx) && !specialChar(right[1])) {
			x2++;
			right++;
		}
	}
	
	SetRect(&selectedChars, x1, y, x2, y);
	r = selectedChars;
	rectToScreen(&r);
	r.bottom -= 1;			/* account for line height fudge */
	rgn = NewRgn();
	rgn = selectedRgn(topLeft(r), botRight(r), rgn);
	HiliteFlag &= ~(1 << hiliteBit);
	InvertRgn(rgn); 
	DisposeRgn(rgn);
	SetPort(savePort);
	updatemenu();
}

void offsetSelection(int delta)
{
	if (delta && !emptySelection()) {
		invertSelection(true);
		selectedChars.top += delta;
		selectedChars.bottom += delta;
	}
}

/* Utility Stuff */

static Boolean specialChar(char c)
{
	if ((c >= 'A') && (c <= 'Z'))
		return false;
	if ((c >= 'a') && (c <= 'z'))
		return false;
	if ((c >= '0') && (c <= '9'))
		return false;
	return true;
}

/*
 * Convert a rect in x,y coords to screen coords (local)
 */
static void rectToScreen (Rect *r)
{
	r->left = r->left * fwidth + LEFT;
	r->right = r->right * fwidth + LEFT + (fwidth - 1); /* flush right char */
	
	r->top *= fheight;
	
	r->bottom *= fheight;
	r->bottom += fheight;
}


/*
 * Convert a point to x (chars)
 */
static int ptToX (Point p)
{
	int x;
	
	x = p.h - LEFT;
	if (x < 0)
		x = 0;
	x /= fwidth;
	if (x > maxx)
		x = maxx;
	return x;
}


/*
 * Convert a point to y (chars)
 */
static int ptToY (Point p)
{
	int y;
	
	y = p.v;
	if (y >= 0)
		y /= fheight;
	else
		y = (y - fheight + 1) / fheight;
	return y;
}

/* AppleEvent Stuff */

OSErr AEHandler(AppleEvent *theAppleEvent, AppleEvent *reply,
		long handlerRefcon)
{
	OSErr result;
	result = (*aeDT[handlerRefcon].handler)
		(theAppleEvent, reply, aeDT[handlerRefcon].handlerRefcon);
	return result;
}

static OSErr OAppHandler(AppleEvent *theAppleEvent, AppleEvent *reply,
			 long handlerRefcon)
{
	return noErr;
}

static OSErr ODocHandler(AppleEvent *theAppleEvent, AppleEvent *reply,
			 long handlerRefcon)
{
	return errAEEventNotHandled;
}

static OSErr PDocHandler(AppleEvent *theAppleEvent, AppleEvent *reply,
			 long handlerRefcon)
{
	return errAEEventNotHandled;
}

static OSErr QuitHandler(AppleEvent *theAppleEvent, AppleEvent *reply,
			 long handlerRefcon)
{
	quitFlag = true;
	return noErr;
}