/************************************************************************
 * This program is Copyright (C) 1986 by Jonathan Payne.  JOVE is	*
 * provided to you without charge, and with no warranty.  You may give	*
 * away copies of JOVE, including sources, provided that this notice is *
 * included in all the files.						*
 ************************************************************************/
/*+++*
 *  title:	mac.c
 *  abstract:	Macintosh-specific stuff for JOVE
 *  author:	T.R.Hageman, Groningen, The Netherlands.
 *		(loosely based on Ken Mitchum's original 4.14 port
 *		 using the LightSpeed C compiler.)
 *  created:	june 1991
 *  modified:
 *  description:
 *	this file complements jove.c, proc.c, misc.c and tty.c
 *	and completely replaces fkeys.c and term.c
 *
 * TODO:
 *	probably need to supply <sys/types.h> / <sys/stat.h> / <varargs.h>
 *	Get my hands on a Macintosh C Compiler and TEST IT!
 *---*/

#include "tune.h"

#ifdef MAC

RCS("$Id: mac.c,v 0.13 1993/10/29 02:43:03 tom Exp tom $")

#include "jove.h"
#include "ctype.h"
#include "io.h"
#include "process.h"
#include "readdir.h"
#include "screen.h"
#include "termcap.h"

#include <MacTypes.h>
#include <QuickDraw.h>
#include <WindowMgr.h>
#include <FontMgr.h>
#include <ListMgr.h>
#include <EventMgr.h>
#include <ControlMgr.h>
#include <DialogMgr.h>
#include <ResourceMgr.h>
#include <ToolboxUtil.h>
#include <HFS.h>
#include <StdFilePkg.h>
#include <MenuMgr.h>
#include <pascal.h>
#include <SegmentLdr.h>

#include <time.h>	/* for clock() */
/*
 * clock support (if not in <time.h>)
 */
#ifndef CLK_TCK
typedef unsigned long clock_t;
#   define CLK_TCK		60
#   define clock()		TickCount()
#endif

/*======================================================================*/
/*				SCREEN.C				*/
/*======================================================================*/

#ifdef COLOR
void
set_color(color)
{
	register int	bg_color,
			fg_color;

	if (color == CurrColor)
		return;

	if (False(UseColor))
		return;

	CurrColor = color;

	bg_color = BG_COLOR(color);
	fg_color = FG_COLOR(color);

	if (fg_color == bg_color)
		fg_color ^= WHITE;	/* choose complementary color */

	printf("\033[%d;%dm", fg_color + 30, bg_color + 40);
}
#endif /* COLOR */

private void scr_inval __(( void ));
private void
scr_inval()
{
	register int	i = ILI;

	do {
		i_set(i, 0);
		v_inval();
	} while (--i >= 0);
}

/*=================== ANSI video terminal emulation ====================*/

/* Initial font (should be mono-spaced) */
#define FONT_ID		monaco
#define FONT_WIDTH	6
#define FONT_SIZE	9
#define FONT_DESCENT	2
#define FONT_HEIGHT	(FONT_SIZE + FONT_DESCENT)

/* These are system constants, really. */

#define MENUHEIGHT	20		/* height (in pixels) of menu bar */
#define WTITLEHEIGHT	20		/* height (in pixels) of title bar */

private WindowPtr	theScreen;	/* Mac window used as screen for JOVE */
private WindowRecord	theScreenRecord;/* window record for JOVEs screen */
private Rect		theLimitRect,	/* limiting rect for window moves */
			theBoundsRect;	/* current bounds rect for window */

#define MAX_WIDTH	(theLimitRect.right - theLimitRect.left)
#define MAX_HEIGTH	(theLimitRect.bottom - theLimitRect.top - WTITLEHEIGHT)

#define MIN_ROWS	10		/* JOVEs window at least this big */
#define MIN_COLS	40

private struct wind_config {
	int	w_width,	/* size of the screen */
		w_height;
	int	w_rows;		/* rows of characters which fit the window */
	int	w_cols;
} wc_std, wc_user, *wc;

private Rect workarea;
/* the text portion of theScreen,
   i.e. ((0, 0), (CO*char_width-1, LI*char_height-1)) */
/* [TRH] Does quickdraw use Rects inclusive or exclusive lower/right boundary? */

#define scr_width	(workarea.right)
#define scr_height	(workarea.bottom)

#define SetWork(wc)	(scr_width = (wc)->w_width - SCROLL_WIDTH - 1,	\
			 scr_height = (wc)->w_height - 1)

#define SetBounds(wc)	(SetRect(&theBoundsRect, theLimitRect.left,	\
				 theLimitRect.top + WTITLEHEIGHT,	\
				 theLimitRect.left + (wc)->w_width,	\
				 theLimitRect.top + WTITLEHEIGHT +	\
					(wc)->w_height))

private short	char_width = FONT_WIDTH,
		char_height = FONT_HEIGHT,
		char_descent = FONT_DESCENT;

private void wc_adjust __(( struct wind_config *_(wc), int _(width), int _(height) ));
private void
wc_adjust(wc, width, height)
{
	int	rows = (height - 1) / char_height,
		max_rows = (MAX_HEIGHT - 1) / char_height,
		cols = (width - SCROLL_WIDTH - 1) / char_width,
		max_cols = (MAX_WIDTH - SCROLL_WIDTH - 1) / char_width;

	if (rows < MIN_ROWS)
		rows = MIN_ROWS;
	if (rows > max_rows)
		rows = max_rows;
	if (cols < MIN_COLS)
		cols = min_cols;
	if (cols > max_cols)
		cols = max_cols;
	if (cols > MAXCOLS)		/* this is JOVEs limit */
		cols = MAXCOLS;
	wc->w_rows = rows;
	wc->w_cols = cols;
	wc->w_height = rows * char_height + 1;
	wc->w_width = cols * char_width + SCROLL_WIDTH + 1;
	SetWork(wc);
}

private void
wc_init()
{
	wc = &wc_std;
	wc_adjust(wc, MAX_WIDTH, MAX_HEIGHT);
	SetBounds(wc);
	wc_user = wc_std;
}

private void scroll_lines __(( int _(top), int _(bottom), int _(num) ));
private void
scroll_lines(top, bottom, num)
{
	register int	cheight = char_height;
	Rect		r;
	RgnHandle	updateRgn = NewRgn();

	SetRect(&r, 0, top * cheight, scr_width, (bottom + 1) * cheight - 1);
	ScrollRect(&r, 0, num * cheight, updateRgn);
	DisposeRgn(updateRgn);
}

#ifdef FAST_IDLINE
private void MACins_line __(( int _(top), int _(bottom), int _(num) ));
private void
MACins_line(top, bottom, num)
{
	SetPort(theScreen);
	scroll_lines(top, bottom, num);
}

private void MACdel_line __(( int _(top), int _(bottom), int _(num) ));
private void
MACdel_line(top, bottom, num)
{
	SetPort(theScreen);
	scroll_lines(top, bottom, -num);
}

void
IDline_setup(tname)
const char *tname;
{
	TTins_line = MACins_line;
	TTdel_line = MACdel_line;

	ScrollLimit = 100;
}
#endif

#ifdef FAST_FLASH
void
flash()
{
	cursoff();
	SetPort(theScreen);
	InvertRect(&workarea);
	DoSit(1);
	SetPort(theScreen);
	InvertRect(&workarea);
}
#endif /* FAST_FLASH */

/*#define FULL	/* uncomment this if you want full implementation */

#ifdef FULL
#   define MAX_ESC_ARGS	6	/* should be enough... */
#else
#   define MAX_ESC_ARGS 2	/* minimal version */
#endif

private int	esc_arg[MAX_ESC_ARGS+1], /* esc_args[0] used as accumulator */
		esc_state;
/*
 * escape state:
 *	= 0	outside escape sequence,
 *	> 0	getting argument (index into esc_args),
 *	< 0	intermediate states.
 */
#define NO_ESC_SEEN	0
#define ESC_SEEN	-1
#define ESC_BRACKET_SEEN -2

private char	qe_seen;		/* set if "ESC [ =" or "ESC [ ?" seen */

private int	Vcol, Vrow;		/* Virtual cursor position */

private int	Vraw ZERO;		/* raw/cooked newline mode */

#define Vfgcolor	(thePort->fgColor)
#define Vbgcolor	(thePort->bgColor)

#ifdef COLOR
private int colormap[] = {
	blackColor,	redColor,	greenColor,	yellowColor,
	blueColor,	magentaColor,	cyanColor,	whiteColor
};
#endif

#define Ring()		(SysBeep(2))

#define ClrEol()	{						\
	Rect	r;							\
	int	y = Vrow * char_height;					\
	SetRect(&r, Vcol * char_width, y, scr_width - 1, y + char_height - 1);	\
	EraseRect(&r);							\
}

#define ClrEos()	{						\
	EraseRect(&workarea);						\
	Vcol = Vrow = 0;			/* home cursor */	\
}

#define CurAbs(row, col) {						\
	if ((Vrow = row) > ILI) Vrow = ILI;				\
	if ((Vcol = col) >= CO) Vcol = CO-1;				\
}

#define CurRight(i) 	{						\
	if ((Vcol += i) >= CO) Vcol = CO-1;				\
}

#define CurLeft(i)	{						\
	if ((Vcol -= i) < 0) Vcol = 0;					\
}

#define CurUp(i) {							\
	if ((Vrow -= i) < 0) Vrow = 0;					\
}

#define CurDown(i) {							\
	if ((Vrow += i) > ILI) Vrow = ILI;				\
}

#ifdef FULL
private int	saved_Vpos;

#define CurSave()	(saved_Vpos = Vpos)
#define CurRestore()	(Vpos = saved_Vpos)

#endif /* FULL */

/* cursor display: */
private int	Vcurs ZERO;	/* >0 ON, =0 OFF, <0 INHIBITED */
private Rect	Vcursrect;
private clock_t	Vcursflip;

private void HideTxtCursor __((void));
private void
HideTxtCursor()			/* Remove cursor */
{
	if (Vcurs > OFF) {		/* cursor is on */
		Vcurs = OFF;
		InvertRect(&Vcursrect);	/* so turn it off */
	}
}

private void ShowTxtCursor __((void));
private void
ShowTxtCursor()			/* Display cursor (if not inhibited) */
{
	if (Vcurs == OFF) {
		register int	x = Vcol * char_width,
				y = Vrow * char_height;
		SetRect(&Vcursrect, x, y, x + char_width - 1, y + char_height - 1);
 		InvertRect(&Vcursrect);
		Vcurs = ON;
		Vcursflip = clock() + GetCaretTime();
	}
}

#define BlinkTxtCursor() do {	/* toggle cursor if it's time */	\
		if (Vcurs >= OFF && clock() >= Vcursflip) {		\
			SetPort(theScreen);				\
			InvertRect(&Vcursrect);				\
			Vcurs ^= ON;					\
			Vcursflip = clock() + GetCaretTime();		\
		}							\
	} while (0)


private void SetModes __(( int _(on) ));
private void
SetModes(on)
{
	if (esc_arg[1] == 25)		/* inhibit cursor */
		Vcurs = on - ON;
}

				/* ANSI attribute definitions */
#define ANSI_CLEARALL	0
#define ANSI_BOLD	1
#define ANSI_UNDERLINE	4
#define ANSI_BLINK	5
#define ANSI_REVERSE	7
#define ANSI_INVIS	8
#define ANSI_FG(i)	(30+i)
#define ANSI_BG(i)	(40+i)

private void SetAttrib __(( void ));
private void
SetAttrib()
{
#ifdef COLOR
	static char	reversed;
#endif
	register int	i = 0;

	while (++i < esc_state) switch (esc_arg[i]) {

	case ANSI_CLEARALL:
#ifdef COLOR
		if (reversed) {
			long temp;

			reversed = 0;
			temp = Vfgcolor;
			Vfgcolor = Vbgcolor;
			Vbgcolor = temp;
		}
#else
		Vfgcolor = blackColor;
		Vbgcolor = whiteColor;
#endif
#ifdef FULL
		/* clear Text Attributes */
#endif
		continue;

	case ANSI_REVERSE:
#ifdef COLOR
		if (reversed)
			continue;
		reversed = 1;
	    {	/* swap */
		long temp = Vfgcolor;
		Vfgcolor = Vbgcolor;
		Vbgcolor = temp;
	    }
#else
		Vfgcolor = whiteColor;
		Vbgcolor = blackColor;
#endif
		continue;

	case ANSI_BOLD:
		/* add Text attribute */
		continue;
#ifdef FULL
	case ANSI_UNDERLINE:
		/* add Text attribute */
		continue;

	case ANSI_BLINK:
		continue;

	case ANSI_INVIS:
		/* set FG color to BG color */
		Vfgcolor = Vbgcolor;
		continue;
#endif
#ifdef COLOR
	case ANSI_FG(0): case ANSI_FG(1): case ANSI_FG(2): case ANSI_FG(3):
	case ANSI_FG(4): case ANSI_FG(5): case ANSI_FG(6): case ANSI_FG(7):

		if (reversed)
			Vbgcolor = colormap[esc_arg[i] - ANSI_FG(0)];
		else
			Vfgcolor = colormap[esc_arg[i] - ANSI_FG(0)];
		continue;

	case ANSI_BG(0): case ANSI_BG(1): case ANSI_BG(2): case ANSI_BG(3):
	case ANSI_BG(4): case ANSI_BG(5): case ANSI_BG(6): case ANSI_BG(7):

		if (reversed)
			Vfgcolor = colormap[esc_arg[i] - ANSI_BG(0)];
		else
			Vbgcolor = colormap[esc_arg[i] - ANSI_BG(0)];
		continue;
#endif
	}
}


ssize_t
write(fd, buf, nbytes)
#undef write	/* use the real thing */
int		fd;
const void	*buf;
size_t		nbytes;
{
	/* "normal" files are just written out */
	if (fd != 1) {
		return write(fd, buf, nbytes);
	}
    {	/* screen output is handled specially */
	register char	*p = (char *) buf,
			*base;
	register char	*end = p + nbytes;
	register int	arg1;

	SetPort(theScreen);
	HideTxtCursor();
	/*
	 * We can be left off in the middle of an escape sequence.
	 * The switch brings us back to the "right" place in the loop.
	 */
	switch (esc_state) {

	case NO_ESC_SEEN:

		for (; p < end; p++) {

			switch (*p) {
			default:
				base = p;
				while (++p < end && !isctrl(*p)) ;

				MoveTo((Vcol) * char_width,
				       (Vrow + 1) * char_height - char_descent);
				DrawText(base, 0, (int)(p - base));
				CurRight((int)(p - base));
				--p;
				continue;
			case '\b':
				CurLeft(1);
				continue;
			case '\t':
				CurRight(8 - (Vcol & 7));
				continue;
			case '\n':
				if (Vrow < ILI)
					++Vrow;
				else
					scroll_lines(0, LI, -1);
				if (Vraw)
					continue;
			case '\r':
				Vcol = 0;
				continue;
			case '\7':
				Ring();
				continue;
			case ESC:
				break;
			}
			/* here we saw the escape... */

			p++;
			esc_state = ESC_SEEN;

	case ESC_SEEN:
			if (p >= end)
				break;
			if (*p != '[') {
				Ring();
				esc_state = NO_ESC_SEEN;
				continue;
			}
		    	p++;
			esc_state = ESC_BRACKET_SEEN;

	case ESC_BRACKET_SEEN:
			if (p >= end)
				break;
			qe_seen = 0;
			if (*p == '?' || *p == '=')
				p++, qe_seen++;
			esc_state = 1;

			do {
				esc_arg[0] = 0;
	default:
				while (p < end && isdigit(*p))
					esc_arg[0] = esc_arg[0] * 10 + (*p++ - '0');
				if (p >= end)
					break;
				if (esc_state <= MAX_ESC_ARGS)
					esc_arg[esc_state++] = esc_arg[0];

			} while (*p == ';' && (p++, TRUE));

			if (p >= end)
				break;

			/* now, handle the escape codes */

			if ((arg1 = esc_arg[1]) == 0)	/* usual arg. */
				arg1 = 1;

			if (qe_seen) {
				if (*p == 'h')
					SetModes(1);
				else if (*p == 'l')
					SetModes(0);
				else
					Ring();
			}
			else switch (*p) {
#ifdef FULL
			case 'f':
#endif
			case 'H':
				--arg1;
				if (esc_state <= 2 || --esc_arg[2] < 0)
					esc_arg[2] = 0;
				CurAbs(arg1, esc_arg[2]);
				break;
			case 'A':
				CurUp(arg1);
				break;
#ifdef FULL
			case 'B':
				CurDown(arg1);
				break;
			case 'C':
				CurRight(arg1);
				break;
			case 'D':
				CurLeft(arg1);
				break;
			case 'M':		/* delete lines */
				arg1 = -arg1;
			case 'L':		/* add lines */
				scroll_lines(Vrow, LI, arg1);
				break;
			case 's':
				CurSave();
				break;
			case 'u':
				CurRestore();
				break;
#endif /* FULL */
			case 'm':
				SetAttrib();
				break;
			case 'K':
				ClrEol();
				break;
			case 'J':
				ClrEos();
				break;
			default:
				Ring();
				break;
			} /* switch (*p) */

			esc_state = NO_ESC_SEEN;	/* reset!! */

		} /* for ( ; p < end; p++) */

	} /* switch (esc_state) */
    }
	ShowTxtCursor();

	return nbytes;
}

/*======================================================================*/
/*				TERM.C					*/
/*======================================================================*/
/*
 * Hardwired terminal capabilities, so we don't need termcap database
 */

void
getTERM()
{
	register char	*t = getenv("TERM");

	if (t == NULL)
		t = "mac";

	TermName = t;

	MetaKey = ESC;
	CanScroll = YES;
	IDline_setup(t);
#ifdef COLOR
	/* {{should check whether we are a color system...}} */
	UseColor++;
#endif
}

/*======================================================================*/
/*				FKEYS.C					*/
/*======================================================================*/

/* shift key mask definitions */
#define K_ALT		cmdKey
#define K_SHIFT		shiftKey
#define K_CAPS		alphalockKey
#define K_OPT		optionKey
#define K_CTRL		controlKey

/* key codes for the above, to be used with KeyDown */
#define ALT_KEY		0x37
#define SHIFT_KEY	0x38
#define CAPS_KEY	0x39
#define OPT_KEY		0x3A
#define CTRL_KEY	0x3B	/* really? */

#define PAD		0100	/* "keypad" flag in Fkey table */

/*
 * character codes >= 0200 are used to define function keys.
 */
enum FKEYS {
	ku=0200,kd,	kr,	kl,	K1,	K2,	K3,	K4,
	K5,	kq,	kF,	kH,	kI,	kN,	kP,	kR,
	kh,	kY,	kt,	kT,	ka,	kA,	kz,	kZ,
	k0,	k1,	k2,	k3,	k4,	k5,	k6,	k7,
	k8,	k9,	k_,	F1,	F2,	F3,	F4,	F5,
	F6,	F7,	F8,	F9,	F0,	ML,	MR
};

/*
 * This is the function key name table.
 * All (macro) references to function keys are by these names,
 * to make it independent of the actual code of the function key
 * (which vary from machine to machine)
 * {{warning: the byte order in the XX macro is processor-dependent}}
 */

#define XX(a,b)	(a<<8|b)

short	FKeyMap[NFKEYS] = {
	XX('k','u'),	XX('k','d'),	XX('k','r'),	XX('k','l'),
	XX('K','1'),	XX('K','2'),	XX('K','3'),	XX('K','4'),
	XX('K','5'),	XX('k','q'),	XX('k','F'),	XX('k','H'),
	XX('k','I'),	XX('k','N'),	XX('k','P'),	XX('k','R'),
	XX('k','h'),	XX('k','Y'),	XX('k','t'),	XX('k','T'),
	XX('k','a'),	XX('k','A'),	XX('k','z'),	XX('k','Z'),
	XX('k','0'), 	XX('k','1'),	XX('k','2'),	XX('k','3'),
	XX('k','4'),	XX('k','5'),	XX('k','6'),	XX('k','7'),
	XX('k','8'),	XX('k','9'),	XX('k',';'),	XX('F','1'),
	XX('F','2'),	XX('F','3'),	XX('F','4'),	XX('F','5'),
	XX('F','6'),	XX('F','7'),	XX('F','8'),	XX('F','9'),
	XX('F','0'),	XX('M','L'),	XX('M','R')
};

/*
 * The next table is indexed with the keyboard scan code to determine if
 * it was a function key. The keyboard (scancode) layout is as follows:
 * (shown is Mac II; in |..| for extended keyboards only; in {..} incompatible
 *  with Mac Plus (..) / Classic :..:)
 *
 *	As you can see the Mac Plus has the  {= / *} :- < >: (= / *) (48 4D 42)
 *	scancodes originally assigned to the     {+}     :^:     (-)       (4E)
 *	cursor keys all mixed up. This should    {-}     :v:     (+)       (46)
 *	somehow be compensated for.
 *	                                                             :4E 46 42:
 *	|35   7A 7B 63 76   60 61 62 64   65 6D 67 6F    69 6B 71|         :4D:
 *	                                                                   :48:
 *	 32  12 13 14 15 17 16 1A 1C 19 1D 1B 18     33 |72 73 74| 47{51 4B 43}
 *	 30   0C 0D 0E 0F 11 10 20 22 1F 23 21 1E       |75 77 79| 59 5B 5C{45}
 *	 Ctrl  00 01 02 03 05 04 26 28 25 29 27   24 2A            56 57 58{4E}
 *	 [38]   06 07 08 09 0B 2D 2E 2B 2F 2C  Shift(4D)|   7E   | 53 54 55
 *	 [39] [37] --------Space--------- [3A](46 42 48)|7B 7D 7C|   52  41 4C
 *
 * These are assigned the following FKEY codes:
 *	                                                             :kR kl kr:
 *	|Esc  k1 k2 k3 k4   k5 k6 k7 k8   k9 k; kq kY    ?? ?? ??|         :ku:
 *	                                                                   :kd:
 *	 `    1  2  3  4  5  6  7  8  9  0  -  =    Bsp |kI kh kP|Esc{k2 k3 k4}
 *	 Tab   q  w  e  r  t  y  u  i  o  p  [  ]      |Del kH kN| K1 kP K3{kF}
 *	 Ctrl   a  s  d  f  g  h  j  k  l  ;  '  Ret  \            kt K2 kT{kR}
 *	 Shift   z  x  c  v  b  n  m  ,  .  /  Shift(ku)|   ku   | K4 kN K5
 *	 Caps Cmd  --------Space--------- Opt (kl kr kd)|kl kd kr|   k0  ka kA
 *
 * In combination with <Option> the FKEY assignments are thus:
 * ("--" means key not changed from previous assignment)
 *	                                                             :-- kz kZ:
 *	|Esc  F1 F2 F3 F4   F5 F6 F7 F8   F9 F0 -- --    -- -- --|         :kF:
 *	                                                                   :kR:
 *	 `   k1 k2 k3 k4 k5 k6 k7 k8 k9 k; kq kY    Bsp |-- -- --|Esc{-- -- --}
 *	 Tab   q  w  e  r  t  y  u  i  o  p  [  ]       |-- -- --| k7 k8 k9{--}
 *	 Ctrl   a  s  d  f  g  h  j  k  l  ;  '  Ret  \            k4 k5 k6{--}
 *	 Shift   z  x  c  v  b  n  m  ,  .  /  Shift(kR)|   kF   | k1 k2 k3
 *	 Caps Cmd  --------Space--------- Opt (kz kZ kF)|kz kR kZ|   k0  kI --
 *
 * and in combination with <Shift><Option> they are thus:
 *
 *	|Esc  F1 F2 F3 F4   F5 F6 F7 F8   F9 F0 kq kY    -- -- --|
 *
 *	 `   F1 F2 F3 F4 F5 F6 F7 F8 F9 F0 kq kY    Bsp |-- -- --|Esc{-- -- --}
 *	 Tab   q  w  e  r  t  y  u  i  o  p  [  ]       |-- -- --| F7 F8 F9{--}
 *	 Ctrl   a  s  d  f  g  h  j  k  l  ;  '  Ret  \            F4 F5 F6{--}
 *	 Shift   z  x  c  v  b  n  m  ,  .  /  Shift(kR)|   kR   | F1 F2 F3
 *	 Opt  Cmd  --------Space--------- Opt (kz kZ kF)|kz kF kZ|   F0  kI --
 *
 */

private const unsigned char
FKeyTable[] = {
/*00*/	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
/*10*/	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
/*20*/	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
/*30*/	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
/*40*/	0,	ka|PAD,	kr,	k4|PAD,	0,	kF|PAD,	kl,	0,
	kd,	0,	0,	k3|PAD,	kA|PAD,	ku,	kR|PAD,	0,
/*50*/	0,	k2|PAD,	k0|PAD,	K4|PAD,	kN|PAD,	K5|PAD,	kt|PAD,	K2|PAD,
	kT|PAD,	K1|PAD,	0,	kP|PAD,	K3|PAD,	0,	0,	0,
/*60*/	k5,	k6,	k7,	k3,	k8,	k9,	0,	kq,
	0,	0,	0,	0,	0,	k_,	0,	kY,
/*70*/	0,	0,	kI,	kh,	kP,	0,	k4,	kH,
	k2,	kN,	k1,	kl,	kr,	kd,	ku,	0
},

OptKeyTable[] = {
/*00*/	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
/*10*/	0,	0,	k1|PAD,	k2|PAD,	k3|PAD,	k4|PAD,	k6|PAD,	k5|PAD,
	kY|PAD,	k9|PAD,	k7|PAD,	kq|PAD,	k8|PAD,	k_|PAD,	0,	0,
/*20*/	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
/*30*/	0,	0,	0,	0,	0,	0,	0,	0,
	0,	0,	0,	0,	0,	0,	0,	0,
/*40*/	0,	0,	kZ,	0,	0,	0,	kz,	0,
	kR,	0,	0,	0,	0,	kF,	0,	0,
/*50*/	0,	0,	k0|PAD,	k1|PAD,	k2|PAD,	k3|PAD,	k4|PAD,	k5|PAD,
	k6|PAD,	k7|PAD,	0,	k8|PAD,	k9|PAD,	0,	0,	0,
/*60*/	F5,	F6,	F7,	F3,	F8,	F9,	0,	0,
	0,	0,	0,	0,	0,	F0,	0,	0,
/*70*/	0,	0,	0,	0	0	0,	F4,	0,
	F2,	0,	F1,	kz,	kZ,	kR,	kF,	0
},

MacPlusFKeyPatch[] = {
	k3|PAD,	/* 4D:ku */
	k2|PAD,	/* 48:kd */
	k4|PAD,	/* 42:kr */
	kR|PAD	/* 46:kl */
};

/* Here we use that cursor keys have charcodes 0x1C..0x1F */

#define MACPLUS_FKEY_PATCH(keycode, charcode) do {			\
		if ((keycode) <= kl && (charcode) >= ' ')		\
			(keycode) = MacPlusFkeyPatch[(keycode) - ku];	\
	} while (0)

/* this depends on the implementation of "packed array of boolean" */
#if 1
#define TESTBIT(bitvec, i)	((keyvec)[(i)>>3] & ((1<<7)>>((i)&7)))
#else
#define TESTBIT(bitvec, i)	((keyvec)[(i)>>3] & (1<<((i)&7)))
#endif

/* Test if a key is pressed. */
private int KeyDown __(( int scancode ));
private int
KeyDown(scancode)
{
	char keyvec[128 >> 3];

	GetKeys(keyvec);
	return TESTBIT(keyvec, scancode);
}

/*============================ Mouse stuff =============================*/

#ifdef MOUSE
#   define INIT_MOUSE()			(mouse_on++)
#   define EXIT_MOUSE()
#   define SHOW_MOUSE_CURSOR()
#   define HIDE_MOUSE_CURSOR()
#   define ALT_KEY_DEPRESSED()		KeyDown(ALT_KEY)
#   define LEFT_BUTTON	1
#   define GET_MOUSE_STATE(b,x,y) {					\
		Point	p;						\
		GetMouse(&p);						\
		if ((*(x) = p.h) < 0)					\
			*(x) = 0;					\
		else if (*(x) > scr_width)				\
			*(x) = scr_width;				\
		if ((*(y) = p.v) < 0)					\
			*(y) = 0;					\
		else if (*(y) > scr_height)				\
			*(y) = scr_height;				\
		*(b) = StillDown() ? KeyDown(SHIFT_KEY) ? 2 : 1 : 0;	\
		/* emulate second mouse button with <Shift> held down	\
		   simultaneously */					\
	}

#   include "mouse.ci"

#endif /* MOUSE */

/*======================================================================*/
/*				TTY.C					*/
/*======================================================================*/

void
ttsize()
{
	CO = wc->w_cols;
	LI = wc->w_rows;
	ILI = LI - 1;
}

void
do_sgtty()
{
}

extern int	done_ttinit;

void
ttinit()
{
	do_sgtty();
	done_ttinit = YES;
}

void
ttexit()
{
	signal(SIGINT, SIG_DFL);	/* to de-install signal handler */
}

/* If n is OFF reset to original modes */

void
ttyset(on)
register int	on;
{
	if (!done_ttinit)		/* Try to reset before we've set! */
		return;

	if (on) {
		Vraw = OFF;
	} else {
		Vraw = ON;
	}
}


/*
 * Emulate alarm()
 */
private sig_tp	(*alarm_proc)__((int _(sig))) = SIG_DFL;
private clock_t	next_alarm;

ALARM_T
alarm(seconds)
register unsigned	seconds;
{
	register clock_t	now = clock(),
				next = next_alarm;

	next_alarm = (seconds) ? now + seconds * CLK_TCK : 0;

	return (unsigned)(now >= next ? 0 : (next - now + CLK_TCK - 1) / CLK_TCK);
}

private sig_tp	(*sigint_proc)__((int _(sig))) = SIG_DFL;

/*
 * Emulate signal()
 * currently this only handles SIGALRM and SIGINT requests; others are ignored.
 */
sig_tp (*signal(sig, proc))()
int	sig;
sig_tp	(*proc)__((int _(sig)));
{
	register sig_tp	(*prev)();

	switch (sig) {
	case SIGALRM:
		prev = alarm_proc;
		alarm_proc = proc;
		break;

	case SIGINT:
		prev = sigint_proc;
		sigint_proc = proc;
		break;

	default:
		prev = SIG_DFL;
		break;
	}
	return prev;
}

/*----------------------------------------------------------------------*/
/*			  Scroll Bar stuff.				*/
/*----------------------------------------------------------------------*/

#define MAXC	100

private void MakeScrollBar __(( Window *_(w), int _(topline) ));
private void
MakeScrollBar(w, topline)
register Window	*w;
{
	register WindowPtr	theWindow = theScreen;
	Rect		BarRect;
	register int	wtop = theWindow.portRect.top + topline * char_height,
			wright = theWindow.portRect.right + 1,

	SetRect(&BarRect, wright - SCROLL_WIDTH, wtop,
		wright,	wtop + w->w_height * char_height + 1);
	w->w_control = NewControl(theWindow, &BarRect, "\psbar", 1, 0,
				  0, MAXC, scrollBarProc, w);
	SetTop(w, w->w_top);
	/* to be sure that `w->w_topnum' is valid */
}

private void AdjScrollBar __(( Window *_(w), int _(topline) ));
private void
AdjScrollBar(w, topline)
register Window	*w;
{
	register ControlHandle	theControl;
	register WindowPtr	theWindow;

	if ((theControl = (ControlHandle) w->w_control) == NULL)
		return;

	theWindow = (*handle)->controlOwner;

	SizeControl(theControl, SCROLLWIDTH, w->w_height * char_height + 1);
	MoveControl(theControl, theWindow.portRect.right - SCROLLWIDTH + 1,
		    theWindow.portRect.top + topline * char_height);
}

/* this gets called by redisplay() on a window configuration change */
void
docontrols()
{
	register Window	*w = fwind;
	register int	top = 0;

	Windchange = 0;
	do {
		if (w->w_control)
			HideControl(w->w_control);
	} while ((w = w->w_next) != fwind);
	do {
		if (w->w_control)
			AdjScrollBar(w, top);
		else
			MakeScrollBar(w, top);
		ShowControl(w->w_control);
		top += w->w_height;
	} while ((w = w->w_next) != fwind);
	DrawControls(theScreen);
}

void
RemoveScrollBar(w)
register Window	*w;
{
	register ControlHandle	theControl;

	if (theControl = (ControlHandle) w->w_control) {
		DisposeControl(theControl);
		w->w_control = NULL;
	}
}

private int	tot_lines;
private Line	*prev_top;

private int ltoc __((void));
private int
ltoc()
{
	register Window	*w = curwind;

	/* for efficiency, only calculate tot_lines if top-line changed. */
	if (UpdModLine || w->w_top != prev_top) {
		register Line	*lp = w->w_top;

		prev_top = lp;
		tot_lines = w->w_topnum;
		while (lp = lp->l_next)
			tot_lines++;
	}
	return (long) w->w_topnum * MAXC / tot_lines;
}

private Line *ctol __(( int _(ctlv) ));
private Line *
ctol(ctlv)
register int	ctlv;
{
	register Window	*w = curwind;
	/* assume that tot_lines has been updated by a previous call to ltoc */
	register int	newpos = ((long) tot_lines * ctlv) / MAXC;

	return next_line(w->w_top, newpos - w->w_topnum);
}

/* this assumes that control == curwind->w_control! */
void
SetScrollBar(control)
void	*control;
{
	SetCtlValue((ControlHandle) control, ltoc());
}

/*----------------------------------------------------------------------*/
/*				Event Handler.				*/
/*----------------------------------------------------------------------*/

/*
 * Support routines for event handler; all take `theEvent' as implicit
 * parameter.
 */
private EventRecord	theEvent;

/* Mouse handler: inside JOVEs screen */

private pascal void doControl __((ControlHandle _(theControl), int _(thePart) ));
private pascal void
doControl(theControl, thePart)
ControlHandle	theControl;
int		thePart;
{
	int	dir = FORWARD,
		page;

	switch (thePart) {
	case inPageUp:
		dir = -dir;
	case inPageDown:
		page = YES;
		break;
	case inUpButton:
		dir = -dir;
	case inDownButton:
		page = NO;
		break;
	default:
		return;
	}
	DoScroll(dir, page);
	redisplay();
}

private int doWindow __(( WindowPtr _(theWindow) ));
private int
doWindow(theWindow)
WindowPtr	theWindow;			/* NB. this is a Mac window! */
{
	ControlHandle	theControl;
	Window		*w,
			*orgwind = curwind;	/* NB. this is a JOVE window! */
	int		thePart;

	GlobalToLocal(&theEvent.where);	/* need local Point coords. */

	if (!(thePart = FindControl(theEvent.where, theWindow, &theControl)))
		return (theEvent.modifiers & K_SHIFT) ? MR : ML;

	if ((w = (Window *) theControl.contrlRfCon) != orgwind)
		SetWind(w);
	SetScrollBar(w->w_control);
	if (TrackControl(theControl, theEvent.where, (ProcPtr) doControl) &&
	    thePart == inThumb) {
		register int	ctlv;

		if ((ctlv = GetCtlValue(theControl)) == 0)
			Bol();
		else if (ctlv == MAXC)
			Eol();
		else
			SetLine(ctol(ctlv));
	}
	if (w != orgwind)
		SetWind(orgwind);
	redisplay();

	return 0;
}

#define SYS_ID		100
private MenuHandle	SysMenu;

private	void	handle_menu __(( int _(menu), int _(item) )),
		about_jove __(( void )),
		update_menu __(( void )),
		allow_menu __(( int _(on) ));

private int doMenu __(( WindowPtr _(theWindow) ));
private int
doMenu(theWindow)
WindowPtr	theWindow;
{
	/* adapt menus if something has changed */
	update_menu();
    {
	unsigned long	selection = MenuSelect(theEvent.where);
	short		menu = selection >> 16,
			item = selection;
	int		result;

	if (menu && item) {
		if (menu != SYS_ID) {
			handle_menu(menu, item);
			result = -1;
		}
		else {
			GrafPtr	oldPort;

			GetPort(&oldPort);
			if (item == 1)
				about_jove();
			else {
				Str255	name;

				GetItem(SysMenu, item, name);
				OpenDeskAcc(name);
			}
			SetPort(oldPort);
			result = 0;
		}
		HiLiteMenu(0);
	}
    }
	return result;
}

private int doSysClick __(( WindowPtr _(theWindow) ));
private int
doSysClick(theWindow)
WindowPtr	theWindow;
{
	SystemClick(&theEvent, theWindow);
	return 0;
}

#define w_state(w)	(*(WStateData **) ((WindowPeek)(w))->dataHandle)
#define std_state(w)	(w_state(w)->stdState)
#define user_state(w)	(w_state(w)->userState)

private int doDrag __(( WindowPtr _(theWindow) ));
private int
doDrag(theWindow)
WindowPtr	theWindow;
{
	DragWindow(theWindow, theEvent.where, &LimitRect);
	if (wc == &wc_std) {
		user_state(theWindow) = std_state(theWindow);
		ZoomWindow(theWindow, inZoomIn, 1);
		wc_init();
		wc = &wc_user;
	}
	return 0;
}

private int doGrow __(( WindowPtr _(theWindow) ));
private int
doGrow(theWindow)
WindowPtr	theWindow;
{
	long	size;

	if (size = GrowWindow(theWindow, theEvent.where, &LimitRect)) {
		if (wc == &wc_std) {
			user_state(theWindow) = std_state(theWindow);
			ZoomWindow(theWindow, inZoomIn, 1);
			wc_init();
			wc = &wc_user;
		}
		wc_adjust(wc, LoWord(size), HiWord(size));
		EraseRect(&theWindow->portRect);
		SizeWindow(theWindow, wc_user.w_width, wc_user.w_height, TRUE);
		win_reshape();
	}
	return 0;
}

private int doZoomIn __(( WindowPtr _(theWindow) ));
private int
doZoomIn(theWindow)
WindowPtr	theWindow;
{
	if (TrackBox(theWindow, theEvent.where, inZoomIn)) {
		EraseRect(&theWindow->portRect);
		ZoomWindow(theWindow, inZoomIn, 1);
		wc = &wc_user;
		win_reshape();
	}
	return 0;
}

private int doZoomOut __(( WindowPtr _(theWindow) ));
private int
doZoomOut(theWindow)
WindowPtr	theWindow;
{
	if (TrackBox(theWindow, theEvent.where, inZoomOut)) {
		EraseRect(&theWindow->portRect);
		ZoomWindow(theWindow, inZoomOut, 1);
		wc = &wc_std;
		win_reshape();
	}
	return 0;
}

private int doGoAway __(( WindowPtr _(theWindow) ));
private int
doGoAway(theWindow)
WindowPtr	theWindow;
{
	if (TrackGoAway(theWindow, theEvent.where))
		Leave();
	return 0;
}


private int doMouse __((void));
private int
doMouse()
{
	static int (*mouselist[])__(( WindowPtr _(theWindow) )) = {
		(int (*)()) no_op,	/* inDesk */
		doMenu,			/* inMenuBar */
		doSysClick,		/* inSysWindow */
		doWindow,		/* inContents */
		doDrag,			/* inDrag */
		doGrow,			/* inGrow */
		doGoAway,		/* inGoAway */
		doZoomIn,		/* inZoomIn */
		doZoomOut		/* inZoomOut */
	};
	WindowPtr	theWindow;
	int		thePart = FindWindow(theEvent.where, &theWindow);

	if (theWindow != theScreen && part != inSysWindow) {
		/* this shouldn't happen */
		f_mess("BUG: unknown Mac window");
		return 0;
	}
	if (thePart >= sizeof mouselist / sizeof mouselist[0])
		return 0;

	return (*mouselist[thePart])(theWindow);
}


private void doActivate __((void));
private void
doActivate()
{
	WindowPtr	theWindow = (WindowPtr) theEvent.message;
	ControlHandle	theControl;
	int		hilite = (theEvent.modifiers & activeFlag) ? 0 : 255;

	SetPort(theWindow);
	if (theControl = (ControlHandle) ((WindowPeek) theWindow)->controlList) do {
		HiliteControl(theControl, hilite);
	} while (theControl = (*theControl)->nextControl);

}

private void doUpdate __((void));
private void
doUpdate()
{
	GrafPtr	oldPort;

	/* first, check that we can handle it */
	if ((WindowPtr) theEvent.message != theScreen)
		return;

	GetPort(&oldPort);
	scr_inval();
	redisplay();
	DrawControls(theScreen);
	SetPort(oldPort);
}

/*
 * Main event handler: polled in kbhit(), dispatched in getchar()
 */
#define keyEvent	(keyDown|autoKey)

int	CapsLock;

private int		eventMask = everyEvent;
private int		got_event;
private CursHandle	*cross;

/*
 * Check for new events.
 * Also handles alarms, mouse pointer shape, text cursor blinking,
 * monitoring of CapsLock state (for indicator in mode line).
 *
 */
private int kbhit __(( void ));
private int
kbhit()
{
	int	prev = CapsLock;

	if ((CapsLock = KeyDown(CAPS_KEY)) != prev) {
		updmodline();
		redisplay();
	}
	if (next_alarm && (clock() >= next_alarm)) {
		next_alarm = 0;
		if (alarm_proc != SIG_DFL && alarm_proc != SIG_IGN)
			(*alarm_proc)(SIGALRM);
	}
	if (FrontWindow() == theScreen) {
		Point	Mousep;
		SetPort(theScreen);
		GetMouse(&Mousep);	/* Local to theScreen? */
		SetCursor((PtInRect(Mousep, &workarea)) ? *cross : &arrow);
		BlinkTxtCursor();
	}
	if (!got_event) {
		SystemTask();
		got_event = GetNextEvent(eventMask, &theEvent);
	}
	return got_event;
}

private int	peekc = -1;

int
getchar()
{
	register unsigned short rawc;
	register int	c,
			scancode,
			mode;

	/* slowpoke timeout is now handled here. */
	extern void	(*timeout_proc)__(( void ));
	clock_t		time_out = clock() + ALARM_1_SEC * CLK_TCK;

	if ((c = peekc) >= 0) {
		peekc = -1;
		return c;
	}

	/* don't allow menus during macro definition. */
	allow_menu(!Defining);

	for (;;) {
		eventMask = everyEvent;

		while (!kbhit()) {		/* wait for event */
			if (timeout_proc && clock() >= time_out)
				(*timeout_proc)();
		}

		eventMask = keyEvent;	/* only allow key events to interrupt
					   redisplay during event handling. */

		got_event = NO;		/* reset for next time */

		switch (theEvent.what) {

		case keyDown:
		case autoKey:
			rawc = theEvent.message;
			mode = theEvent.modifiers;
			scancode = rawc >> 8;

			if ((mode & K_OPT) && (c = OptKeyTable[scancode])) {
				/*
				 * <Option> FKeys that we're prepared to handle.
				 * convert k1--k10 to F1--F10 if <Shift>ed.
				 */
				if ((c >= k1|PAD) && (mode & K_SHIFT))
					c += F1 - k1;
			}
			else if (c = FKeyTable[scancode]) {
				MACPLUS_FKEY_PATCH(c, rawc & 0377);
			}
			/*
			 * Keypad FKeys can be switched off.  Also we
			 * don't want FKey code when <Alt> or <Opt> key pressed
			 * and Metakey is off.
			 */
			if (!c || ((c & PAD) &&
				   (c &= ~PAD, False(UseKeyPad) ||
				    (mode & (K_ALT|K_OPT)) && !MetaKey))) {

				c = rawc & 0377;
				if (c >= 0200)
					return peekc = c, QuoteChar;
			}
			if (mode & K_ALT) {
				/* emulate a <Ctrl> key with <Alt><Shift> */
				if ((mode & K_SHIFT) &&
				    ((c < '@' + 040) ? ('@' <= c || c == ' ') :
						       ('{' <= c && c <= '~')))
					c &= 037;
				else if (MetaKey)
					peekc = c, c = MetaKey;
				else
					peekc = c | 0200, c = QuoteChar;
			}
			eventMask = everyEvent;
			return c;

		case mouseDown:
			if ((c = doMouse()) == 0)
				continue;
			if (c < 0)
				eventMask = everyEvent;
			return c;

		case activateEvt:
			doActivate();
			continue;

		case updateEvt:
			doUpdate();
			continue;

		default:
			/* just eat (and ignore) the event */
			continue;
		}
	}
}

/* Returns non-zero if a character waiting */

int
charp()
{
	if (inIOread)
		return NO;
	if (InJoverc || peekc >= 0)
		return YES;

	return kbhit();
}

/*
 * This does the actual pause, without redisplay.
 */
int
DoSit(delay)
register int	delay;
{
	register clock_t	goal = clock() + delay * (CLK_TCK / 10);

	while (clock() < goal) {
		if (InputPending = charp())
			return YES;
	}
	return NO;
}

/*======================================================================*/
/*				PROC.C					*/
/*======================================================================*/

/* Mac cannot execute sub-processes */

/*======================================================================*/
/*				UTIL.C					*/
/*======================================================================*/

/*----------------------- Directory routines ---------------------------*/

/* As far as I [TRH] understand it, Mac's current directory is defined
   by the (system-wide) variables `SFSaveDisk' (the negated current volume)
   and `CurDirStore' (current directory on current volume). These can be
   manipulated by Desk Accessories and the File selector.  We define
   the `root' directory (quite arbitrarily) as the top-level directory of
   the current drive.
*/

#define M_IFDIR	0x10

private int	cur_vol;
private  long	cur_dir;

/* convert Unix- to Mac-style filenames and vice-versa. */

#define ux2mac(macname, uxname)	ux4mac(macname, uxname, ':', '/')
#define mac2ux(uxname, macname)	ux4mac(uxname, macname, '/', ':')

private char *
ux4mac(dest, src, destsep, srcsep)
char		*dest;
const char	*src;
char		destsep, srcsep;
{
	register const char	*s = dest;
	register char		*d = src;

	if (*s == srcsep)
		s++;
	else
		*d++ = destsep;
	while (*d = *s++) {
		if (*d++ == srcsep)
			d[-1] = destsep;
	}
	return dest;
}

private int
XltErr(mac_errno)
{
	switch (mac_errno) {
	case noErr:
		return 0;
	case dirFulErr:
	case dskFulErr:
		return ENOSPC;
	case ioErr:
	case noMacDskErr:
	case extFsErr:
		return EIO;
	case fsRnErr:
	case badMDBErr:
	case wrPermErr:
		return EPERM;
	case wPrErr:
		return EROFS;
	case eofErr:		/* dubious */
	case posErr:
		return ESPIPE;
	case badNamErr:
	case opWrErr:
	case paramErr:
		return EINVAL;
	case fnOpnErr:		/*  dubious */
	case rfNumErr:
		return EBADF;
	case fLckdErr:
	case permErr:
		return EACCES;
	case gpfErr:
	case volOffLinErr:
	case volOnLinErr:
	case nsDrvErr:
		return ENODEV;
	case fBsyErr:
		return EBUSY;
	case dupFnErr:
		return EEXIST;
	default:
		return ENOENT;
	}
}

private int
initdir()
{
	WDPBRec	p;

	p.ioCompletion = 0;
	p.ioNamePtr = 0;
	if (errno = XltErr(PBHGetVol(&p, 0)))
		return -1;			/* BIG trouble! */
	SFSaveDisk = -(cur_vol = p.ioVRefNum);
	CurDirStore = (cur_dir = p.ioWDDirID);

	return 0;
}

private int
setdir(vol, dir)
{
	WDPBRec	p;

	p.ioCompletion = 0;
	p.ioNamePtr = 0;
	p.ioVRefNum = vol;
	p.ioWDDirID = dir;
	if (errno = XltErr(PBHSetVol(&p, 0)))
		return -1;
	SFSaveDisk = -(cur_vol = vol);
	CurDirStore = (cur_dir = dir);
	return 0;
}

private char *
gethome()
{
	static char	*home ZERO;

	if (home == NULL) {
		char	work[FILESIZE];

		if (initdir() != 0 ||
		    (home = malloc(strlen(getwd(work)) + 1)) == NULL)
			_exit(ENOMEM);		/* again, BIG trouble! */
		strcpy(home, work);
	}
	return home;
}

/* determine name of directory given by (`vol_id', `dir_id') */
private char *
getdir(buf, vol_id, dir_id)
char	*buf;
int	vol_id;
long	dir_id;
{
	CInfoPBRec	d;
	char		dir[FILESIZE],
			tmp[FILESIZE];
	register char	*base = buf;

	/* work our way back to the root */
	buf[0] = '\0';
	for (d.dirInfo.ioDrDirID = dir_id; ;
	     d.dirInfo.ioDrDirID = d.dirInfo.ioDrParID) {
		d.dirInfo.ioComletion = 0;
		d.dirInfo.ioNamePtr = (StringPtr) dir;
		d.dirInfo.ioVRefNum = vol_id;
		d.dirInfo.ioFDirIndex = -1;
		if (errno = XltErr(PBGetCatInfo(&d, 0)))
			return NullStr;

		strcpy(tmp, base);
		appcpy(appcpy(appcpy(base, "/"), PtoCstr(dir)), tmp);

		if (d.dirInfo.ioDrParId == 0)	/* root == Volume name */
			break;
	}
	return buf;
}

/* support routine for `chdir' and `opendir' */
private int
getdirID(dir, vol_id_p, dir_id_p)
register const char	*dir;
int			*vol_id_p;
long			*dir_id_p;
{
	char		work[FILESIZE];
	CInfoPBRec	d;

	*vol_id_p = cur_vol;
	*dir_id_p = cur_dir;

	if (dir[1] == '\0') {
		if (dir[0] == '/')
			return -1;	/* no root */
		if (dir[0] == '.')
			return 0;	/* current directory */
	}

	d.dirInfo.ioCompletion = 0;
	d.dirInfo.ioNamePtr = CtoPstr(ux2mac(work, dir));
	d.dirInfo.ioVRefNum = cur_vol;
	d.dirInfo.ioFDirIndex = 0;
	d.dirInfo.ioDrDirID = 0;
	if ((errno = XltErr(PBGetCatInfo(&d, 0))) ||
	    !(d.dirInfo.ioFlAttrib & M_IFDIR))
		return -1;

	*vol_id_p = d.ioVRefNum;
	*dir_id_p = d.ioDrDirID;
	return 0;
}

public char *
getwd(buf)
char	*buf;
{
	return getdir(buf, cur_vol, cur_dir);
}

public int
chdir(path)
register const char	*path;
{
	int	vol_id;
	long	dir_id;

	if (getdirID(path, &vol_id, &dir_id) < 0 ||
	    setdir(vol_id, dir_id) < 0)
		return -1;
	return 0;
}

public DIR *
opendir(dir)
const char	*dir;
{
	static DIR	theDIR;

	if (getdirID(dir, &theDIR.d_vol_id, &theDIR.d_dir_id) < 0)
		return NULL;
	theDIR.d_index = 0;

	return &theDIR;
}

public struct direct *
readdir(dp)
register DIR	*dp;
{
	static struct direct	dir;
	CInfoPBRec		d;

#ifdef CHDIR
#   ifndef TINY
    do {
#   endif
#endif
	d.dirInfo.ioCompletion = 0;
	d.dirInfo.ioFVersNum = 0;
	d.dirInfo.ioNamePtr = (StringPtr) dir.d_name;
	d.dirInfo.ioVRefNum = dp->d_vol_id;
	d.dirInfo.ioFDirIndex = ++dp->index;
	d.dirInfo.ioDrDirID = dp->d_dir_id;
	if (errno = XltErr(PBGetCatInfo(&d, 0)))
		return NULL;
#ifdef CHDIR
#   ifndef TINY
    } while (ask_dir_only >= 0 && !(d.dirInfo.ioFlAttrib & M_IFDIR));
#   endif
#endif
	PtoCstr(dir.d_name);
	return &dir;
}

public void
closedir(dp)
{
	/* aint this simple! */
}

/*--------------------- UNIX system call emulation ---------------------*/
/*
 * These are all jackets for the library routines (to handle the filename
 * translation), except `stat' and `access' that are not in the library
 * and so need some real work done.
 */
public int
access(filename, mode)
const char	*filename;
{
	int	fd;

	if ((fd = open(filename, (mode == W_OK) ? O_WRONLY : O_RDONLY)) < 0)
		return -1;
	close(fd);
	return 0;
}

public int
creat(filename, mode)
#undef creat
const char	*filename;
{
	char	temp[FILESIZE];

	return creat(ux2mac(temp, filename), mode);
}

public int
DEFVARG( open, (const char *filename, int mode, ...),
	       (filename, mode)
		const char	*filename; )
#undef open
{
	char	temp[FILESIZE];
	va_list	ap;
	int	extra;

	va_begin(ap, mode);
	extra = va_arg(ap, int);
	va_end(ap);
	return open(ux2mac(temp, filename), mode, extra);
}

public int
rename(from, to)
#undef rename
const char	*from, *to;
{
	char	fromtemp[FILESIZE], totemp[FILESIZE];

	return rename(ux2mac(fromtemp, from), ux2mac(totemp, to));
}

int
stat(filename, st)
const char		*filename;
register struct stat	*st;
{
	char		temp[FILESIZE];
	CInfoPBRec	p;

	p.hFileInfo.ioCompletion = 0;
	p.hFileInfo.ioNamePtr = CtoPstr(ux2mac(temp, filename));
	p.hFileInfo.ioFVersNum = 0;
	p.hFileInfo.ioFDirIndex = 0;
	p.hFileInfo.ioVRefNum = cur_vol;
	p.hFileInfo.ioDirID = cur_dir;

	if (errno = XltErr(PBGetCatInfo(&p, 0)))
		return -1;

	st->st_dev = p.hFileInfo.ioVRefNum + 1;		/* avoid 0 */
	st->st_ino = p.hFileInfo.ioDirID;
	st->st_mode = (p.hFileInfo.ioFlAttrib & M_IFDIR) ?
			S_IFDIR|S_IREAD|S_IWRITE|S_IEXEC :
			S_IFREG|S_IREAD|S_IWRITE;
	/* and probably something like:
	if (p.hFileInfo.ioFlFndrInfo.fdType == 'APPL')
		st->st_mode |= S_IEXEC;
	 */
	st->st_nlink = 1;
	st->st_uid = 0;
	st->st_gid = 0;
	st->st_size = p.hFileInfo.ioFlLgLen;
	st->st_ctime = p.hFileInfo.ioFlCrDat;
	st->st_atime = st->st_mtime = p.hFileInfo.ioFlMdDat;

	return 0;
}

public int
unlink(filename)
#undef unlink
const char	*filename;
{
	char	temp[FILESIZE];

	return unlink(ux2mac(temp, filename));	/* delete? */
}

/*----------------------------- miscellaneous --------------------------*/

private char *
gettemp()
{
	/* TODO: something smart, like finding out if SuperDisk exists,
	   and if so return its name; (for now, use home directory ...) */
	return gethome();
}

/* rudimentary getenv */
public char *
getenv(envvar)
register const char	*envvar;
{
	if (strcmp(envvar, "HOME") == 0)
		return gethome();
	if (strcmp(envvar, "TMP") == 0)
		return gettemp();
	if (strcmp(envvar, "JOVEDIR") == 0)
		return gethome();
		/* this assumes JOVE executable also resides in JOVEDIR */
	return NULL;
}

/* library abort() and _assert() possibly use stdio */

void
abort()
{
	_exit(3);
}

void
_assert(mess, file, line)
char	*mess, file;
{
	curstoLL();
	printf("Assertion Failed: %s (file %s, line %s)%s\r\n",
	       mess, file, line, CE);
	finish(QUIT);
}

int
getpid()
{
	extern time_t	time0;

	return (*(short *)&time0 ^ *((short *)&time0 + 1)) & 0x7fff;
}

/*----------------------------------------------------------------------*/
/*			    Menu Handling.				*/
/*----------------------------------------------------------------------*/

extern data_obj	*FileMenu[], *BufMenu[], *WindMenu[],
		*PointMenu[], *CmdMenu[], *VarMenu[]; /* from menumaps.txt */

typedef struct menu {
  const	char		menu_name;
	data_obj	**menu_obj;
	MenuHandle	menu_handle;
} Menu;

private Menu Menus[] = {
	{ "\pFile",	FileMenu	},
	{ "\pEdit",	0		}, /* (dummy for now...) */
	{ "\pBuffer",	BufMenu		},
	{ "\pWindow",	WindMenu	},
	{ "\pPoint",	PointMenu	},
	{ "\pCommand",	CmdMenu		},
	{ "\pSet",	SetMenu		}
};
#define NMENUS	(sizeof Menus / sizeof Menus[0])

#define BUF_ID	(SYS_ID + 3)		/* menu id of buffer menu */

/*
 * Support routines for menu handling.
 */
private void MacSetVar __(( Variable *_(vp), int _(menu), int _(item) ));
private void
MacSetVar(vp, menu, item)
register Variable	*vp;
{
	switch (V_TYPE(vp)) {

	case V_BOOL:
		/* toggle value directly */
		*vp->v_value = False(*vp->v_value);
		CheckItem(Menus[menu - SYS_ID - 1].menu_handle, item,
			  *vp->v_value);
		break;

	default:
		/* need a parameter */
		DoSetVar(vp);
		Modechange = 0;
		break;
	}
}

private void MakeBufMenu __(( void ));
private void
MakeBufMenu()
{
	register Buffer		*b;
	register data_obj	**dp = Menus[BUF_ID].menu_obj;
	register int		item = 0;
	int			last_nonbuf;

	Bufchange = 0;

	/* skip over non-buffer items */
	for (; *dp && ((*dp)->Type & TYPEMASK) != BUFFER; dp++)
		item++;

	last_nonbuf = item;

	if (b = world) do {
		++item;
		if (*dp == NULL) {
			/* calculate size of buffer pointer list;
			   this includes one extra slot for the sentinel */
			register size_t	bufsiz = item * sizeof b;
			register Buffer	*bb = b;

			do bufsiz += sizeof b; while (bb = bb->b_next);

			/* (re)allocate buffer pointer list */
			if ((dp = malloc(bufsiz)) == NULL)
				return;
			byte_copy(Menus[BUF_ID].menu_obj, dp, item * sizeof b);
			if (Menus[BUF_ID].menu_obj != BufMenu)
				free(Menus[BUF_ID].menu_obj);
			Menus[BUF_ID].menu_obj = dp;
			/* fill vacant slots with (non-NULL) dummy pattern */
#define NOBUF ((data_obj *)-1)
			dp += item - 1;
			bufsiz = bufsiz / sizeof b - item;
			do *dp++ = NOBUF; while (--bufsiz);

			dp = &Menus[BUF_ID].menu_obj[item - 1];
		}

		if (*dp == NOBUF)
			AppendMenu(Menus[BUF_ID].menu_handle, "\p ");
		/* don't set item string directly because file name may
		   contain menu meta-characters. */
	    {
		const char	*fname = filename(b);
		int		flen = strlen(fname);
		const char	*dots = NullStr;

#define MAXFLEN	30
		if (flen > MAXFLEN) {
			dots = "...";
			fname += flen - (MAXFLEN - 3);
		}
		SetItem(Menus[BUF_ID].menu_handle, item,
			CtoPstr(sprint("%-2d %-11b \"%s%s\"",
				       (item - last_nonbuf), b, dots, fname)));
	    }
		*dp++ = (data_obj *) b;
	} while (b = b->b_next);

	/* truncate menu if there are deleted buffers */
	++item;
	while (*dp != NOBUF && *dp) {
		DelMenuItem(Menus[BUF_ID].menu_handle, item);
		*dp++ = NOBUF;
	}
}

/* check Major/Minor modes, Boolean variables, current buffer */

private void MarkModes __(( void ));
private void
MarkModes()
{
	register Buffer	*cb = curbuf;
	register Menu	*mp = Menus;

	Modechange = 0;

	do {
		register data_obj	**dp = mp->menu_obj,
					*d;

		while (d = *dp++) {
			register int	checked;

			switch (d->Type & TYPEMASK) {

			default:
				continue;

			case FUNCTION|MAJOR_MODE:
				checked = BufMajorMode(cb, d->Type >> 8);
				break;

			case FUNCTION|MINOR_MODE:
				checked = BufMinorMode(cb, d->Type >> 8) != 0;
				break;

			case VARIABLE|V_SET(V_BOOL, 0):
				checked = True(*((Variable *) d)->v_value);
				break;

			case BUFFER:
				checked = (Buffer *) d == cb;
				break;
			}
			/* only get here when there is to (de)check something */

			CheckItem(mp->menu_handle, (int)(dp - mp->menu_obj),
				  checked);
		}
	} while (++mp < &Menus[NMENUS]);
}

/*
 * Main menu handling routines.
 */
private void
handle_menu(menu, item)
{
	register data_obj	*dp;

	d = Menus[menu - SYS_ID - 1].menu_obj[item - 1];

	switch (d->Type & REALTYPEMASK) {

	case FUNCTION:
	case MACRO:			/* maybe later... */
		ExecCmd(d);
		break;

	case BUFFER:
		SetABuf(curbuf);
		tiewind(curwind, (Buffer *) d);
		SetBuf((Buffer *) d);
		break;

	case VARIABLE:
		MacSetVar((Variable *) d, menu, item);
		break;

	default:
		break;
	}
}

/* create the menu bar */
private void init_menu __(( void ));
private void
init_menu()
{
	register data_obj	**dp, *d;
	register Menu		*mp = Menus;
	register MenuHandle	theMenu;
	register int		menu_id = SYS_ID;

	SysMenu = theMenu = NewMenu(menu_id, "\p\24");	/* apple symbol */
	AppendMenu(theMenu, "\pAbout Jove");
	AddResMenu(theMenu, 'DRVR');
	InsertMenu(theMenu, 0);

	do {
		mp->menu_handle = theMenu = NewMenu(++menu_id, mp->menu_name);
		if ((dp = mp->menu_obj) == NULL) {
			DisableItem(theMenu, 0);
		}
		else while (d = *dp++) {
			char	itemname[256];

			switch (d->Type & REALTYPEMASK) {
			case VARIABLE:
				SetItemMark(theMenu,
					    (int)(dp - mp->menu_obj), '\22');
			default:
				break;
			}
			AppendMenu(theMenu, CtoPstr(strcpy(itemname, d->Name)));
		}
		InsertMenu(theMenu, 0);
	} while (++mp < &Menus[NMENUS]);

	DrawMenuBar();
}

private void
update_menu()
{
	if (Bufchange)
		MakeBufMenu();
	if (Modechange)
		MarkModes();
}

private void
allow_menu(on)
{
	static int	menus_off ZERO;	/* menus are initially on */

	if (on ^ menus_off)
		return;		/* no change. */

	menus_off ^= ON;
    {
	register Menu	*mp = Menus;

	do {
		if (mp->menu_handle == NULL || mp->menu_obj == NULL)
			continue;
		if (on)
			EnableItem(mp->menu_handle, 0);
		else
			DisableItem(mp->menu_handle, 0);

	} while (++mp < &Menus[NMENUS]);
    }
	DrawMenuBar();
}

/*----------------------------------------------------------------------*/
/*			       About Jove.				*/
/*----------------------------------------------------------------------*/

private void
about_jove()
{
	/* I [TRH] decided to throw out the dialog showing the binding list,
	   since JOVE already has perfectly good methods to show these:
	   either `describe-bindings', or `apropos' with an empty apropos
	   string.  IMHO nothing is gained in having it again in a (modal)
	   dialog. */
	ShowVersion();		/* for now... */
}

/*----------------------------------------------------------------------*/
/*			     File Selector.				*/
/*----------------------------------------------------------------------*/

private pascal Boolean
Filter(p)
register FileParam *p;
{
	register Boolean result = FALSE;

	if (p->ioFlFndrInfo.fdType == 'APPL')
		result = TRUE;
#ifdef F_COMPLETION
#   ifndef TINY
	else if (ask_dir_only >= 0)
		result = (p->ioFlAttrib & M_ISDIR) != 0;	/* really? */
#   endif
#endif
	return result;
}

public char *
FileSelector(prompt, def, buf)
const char	*prompt,
		*def;
char		*buf;
{
	char		name[FILESIZE];
	static Point	At = {100, 100};
	SFReply		frec;

	strcpy(buf, pwd());
	name[0] = '\0';
	if (def)
		strcpy(name, def);

	f_mess((def && def[0]) ? "%s(default %s)" : "%s", prompt, def);

	SFSaveDisk = -cur_vol;	/* in case a Desk Accessory changed them */
	CurDirStore = cur_dir;

	if (FSelMode == READ) {
		SFGetFile(At, (StringPtr) 0, Filter, -1,
			  (void *)0, (void *)0, &frec);
	}
	else {
		char	header[MESG_SIZE];

		sprintf(header, ":%s::", prompt);
		SFPutFile(At, CtoPstr(header), CtoPstr(ux2mac(buf, name)),
			  (void *)0, &frec);
	}
	FSelMode = READ;		/* reset for next time */

	if (!frec.good)
		complain("[Aborted]");

	/* Actual directory changed? get its name... */
	if (CurDirStore != cur_dir || SFSaveDisk != -cur_vol) {
		getdir(buf, -SFSaveDisk, CurDirStore);
		/* should we chdir to it? */
	}

	/* use default if empty name given */
	if (*mac2ux(name, PtoCstr((char *)frec.fName)) == '\0')
		return NULL;

	make_filename(buf, buf, name);

	s_mess("%s%s", prompt, buf);

	return buf;
}

/*----------------------------------------------------------------------*/
/*			     Initialization.				*/
/*----------------------------------------------------------------------*/

private void MACinit __((void));
private void
MACinit()
{
	extern char		*version;
	register WindowPtr	theWindow;

	InitGraf(&thePort);
	InitWindows();
	InitCursor();
	InitFonts();
	InitMenus();
	InitDialogs((ProcPtr) 0);	/* no restart proc */

	/* figure limiting rectangle for window moves */
#define SOME		3	/* a little margin, in pixels */
	SetRect(&LimitRect,
		screenBits.bounds.left + SOME, screenbits.bounds.top + MENUHEIGHT,
		screenBits.bounds.right - SOME, screenBits.bounds.bottom - SOME);

	/* init window configuration (and bounds rectangle) */
	wc_init();

	/* create the window; and adjust it */
	theScreen = theWindow = NewWindow(&theScreenRecord, &theBoundsRect,
					  CtoPstr(sprint("Jove (%s)", version)),
					  1, 8, (WindowPtr) -1, 1, 0L);

	user_state(theWindow) = std_state(theWindow);
	SetPort(theWindow);
	theWindow->txFont = FONT;
	theWindow->txSize = FONT_SIZE;
	theWindow->txMode = patCopy;
	theWindow->pnMode = patCopy;
	PenNormal();
	ShowPen();

	menu_init();
	cross = GetCursor(crossCursor);
}

private void MACexit __((void))
private void
MACexit()
{
	/* any Mac-dependent cleanups go here */
}

private int getargv __(( char ***_(argvp) ));
private int
getargv(argvp)
char	***argvp;
{
	static char	*minimal_argv[] = { "jove", NULL };
	register char	*argv;
	int		nargs, type;

	*argvp = minimal_argv;

	CountAppFiles(&type, &nargs);

	if (nargs  && (argv = (char **) malloc((nargs + 2) * sizeof(char *)))) {
		char		namebuf[FILESIZE],
				cwd[FILESIZE];
		register int	argc = 0;
		register char	*name;

		*argvp = argv;
		*argv++ = minimal_argv[0];
		for (argc = 0; ++argc <= nargs; ClrAppFiles(argc)) {
			AppFile	p;
			WDPBRec	d;

			GetAppFiles(argc, &p);
			if (type != 0)		/* i.e., print */
				continue;

			d.ioCompletion = 0;
			d.ioNamePtr = 0;
			d.ioVRefNum = p.vRefNum;
			d.ioWDIndex = 0;
			PBGetWDInfo(&d, 0);
			/* make this one the current directory */
			if (argc == 1) {
				setdir(d.ioWDVRefNum, d.ioWDDirID);
				getwd(cwd);
			}
			name = namebuf;
			name[0] = '\0';
			if (d.ioWDDirId != cur_dir || d.ioWDVRefNum != cur_vol)
				name += numcomp(cwd, getdir(name, d.ioWDVRefNum,
							    d.ioWDDirId));
			if (name[0])
				name[0] = SLASH, name[1] = '\0';
			strcat(name, PtoCstr((char *)p.fName));
			if ((*argv = malloc(strlen(name) + 1)) == NULL)
				break;
			strcpy(*argv++, name);
		}
		*argv = NULL;
		if (type == 0)
			return argc;
	}
	return 1;
}

#undef main
void
main()
{
	char	**argv;
	int	argc;

	MACinit();
	gethome();			/* get home directory BEFORE anyone
					   can change it! */
	argc = getargv(&argv);
	Jmain(argc, argv);		/* this is JOVEs main */
	/* NOTREACHED */
}

void
_exit(st)
{
	MACexit();
	exit(st);
}
#endif /* MAC */

/*======================================================================
 * $Log: mac.c,v $
 * Revision 0.13  1993/10/29  02:43:03  tom
 * SetAttrib(): fix reverse <-> color interaction.
 *
 * Revision 0.12  1993/02/18  01:42:31  tom
 * remove (void) casts; fix done_ttinit; use ALARM_T on alarm().
 *
 * Revision 0.11  1993/02/08  19:16:17  tom
 * cleanup whitespace; some random optimizations.
 *
 * Revision 0.10  1992/09/22  21:22:07  tom
 * convert to "port{ansi,defs}.h" conventions.
 *
 * Revision 0.9  1992/09/21  13:15:59  tom
 * replace CTL('Q') with QuoteChar.
 *
 * Revision 0.8  1992/08/27  02:05:16  tom
 * add RCS directives.
 *
 */
