/*	SCCS Id: @(#)mmodal.c	3.1	93/01/24		  */
/* Copyright (c) Jon W{tte, Hao-Yang Wang, Jonathan Handler 1992. */
/* NetHack may be freely redistributed.  See license for details. */

#include "hack.h"
#include "mactty.h"
#include "macpopup.h"
#include <Dialogs.h>
#include <OSUtils.h>

/* Flash a dialog button when its accelerator key is pressed */
void
FlashButton (WindowPtr wind, short item) {
	short type;
	Handle handle;
	Rect rect;
	unsigned long ticks;

	/* Apple recommends 8 ticks */
	GetDItem(wind, item, &type, &handle, &rect);
	HiliteControl((ControlHandle)handle, kControlButtonPart);
	Delay(8, &ticks);
	HiliteControl((ControlHandle)handle, 0);
	return;
}


#if ENABLE_MAC_POPUP
static void mv_handle_click (EventRecord *theEvent);

#define MAX_MV_DIALOGS 20
static int old_dialog_count = 0;
static struct {
	short	  id;
	Boolean   init_visible;
	DialogPtr dialog;
} old_dialog[MAX_MV_DIALOGS];


static short frame_corner;
static pascal void FrameItem(DialogPtr dlog, short item);
static UserItemUPP FrameItemUPP = NULL;

static pascal void
FrameItem (DialogPtr dlog, short item) {
	short k;
	Handle h;
	Rect r;

	GetDItem (dlog, item, &k, &h, &r);
	PenSize (3, 3);
	FrameRoundRect (&r, frame_corner, frame_corner);
	PenNormal ();
}


static void
SetFrameItem (DialogPtr dlog, short frame, short item) {
	Rect r, r2;
	short kind;
	Handle h;

	if (!FrameItemUPP)	/* initialize handler routine */
		FrameItemUPP = NewUserItemProc(FrameItem);
		
	GetDItem (dlog, item, &kind, &h, &r);
	InsetRect (&r, -4, -4);
	r2 = r;
	GetDItem (dlog, frame, &kind, &h, &r);
	SetDItem (dlog, frame, kind, (Handle) FrameItemUPP, &r2);
	frame_corner = 16;
}


/* Instead of calling GetNewDialog everytime, just call
   SelectWindow/ShowWindow for the old dialog to remember its location.
*/
/*
 *	Unfortunately, this does not work, as it doesn't handle old text
 *	in edit text boxes, and not ParamText parameters either.
 *
 */
static DialogPtr
mv_get_new_dialog(short dialogID) {
	DialogPtr dialog;
	int d_idx = old_dialog_count;
	Rect oldRect;
	Boolean hadOld = 0;

	old_dialog[0].id = dialogID;
	while (old_dialog[d_idx].id != dialogID)
		--d_idx;

/*
 *	This routine modified so that the old dialog is
 *	disposed, and the new one read in after we remember
 *	the old dialog's position.
 *
 *	This takes care of strange default strings and ParamTexts
 *
 */

	if (d_idx) {
		dialog = old_dialog [d_idx] . dialog;
		oldRect = dialog->portBits . bounds;
		DisposeDialog (dialog);
		old_dialog [d_idx] . dialog = (DialogPtr) 0;
		hadOld = 1;

	} else {
		d_idx = ++ old_dialog_count;
	}

	dialog = GetNewDialog(dialogID, nil, (WindowPtr)-1);
	if (dialog) {
		if (hadOld) {
			MoveWindow (dialog, - oldRect . left, - oldRect . top, FALSE);
		}
		old_dialog[d_idx].id = dialogID;
		old_dialog[d_idx].init_visible
			= ((WindowPeek)dialog)->visible;
		old_dialog[d_idx].dialog = dialog;
	}
	return dialog;
}

/* Instead of actually closing the dialog, just hide it so its location
   is remembered. */
static void mv_close_dialog(DialogPtr dialog) {
	HideWindow(dialog);
}

/* This routine is stolen/borrowed from HandleClick (macwin.c).  See the
   comments in mv_modal_dialog for more information. */
static void
mv_handle_click (EventRecord *theEvent) {
	int code;
	WindowPtr theWindow;
	Rect r = (*GetGrayRgn ())->rgnBBox;

	InsetRect (&r, 4, 4);
	InitCursor ();

	code = FindWindow (theEvent->where, &theWindow);

	switch (code) {
	case inContent :
		if (theWindow != FrontWindow ()) {
			nhbell ();
		}
		break;
	case inDrag :
		SetCursor(&qd.arrow);
		DragWindow (theWindow, theEvent->where, &r);
		SaveWindowPos (theWindow);
		break;
	default :
		HandleEvent (theEvent);
	}
}

static void
mv_modal_dialog(ModalFilterProcPtr filterProc, short *itemHit) {
	GrafPtr org_port;
	GetPort(&org_port);

	for (;;) {
		DialogPtr dialog = FrontWindow();
		EventRecord evt;

		WaitNextEvent(everyEvent, &evt, GetCaretTime(), (RgnHandle) nil);

		if (evt.what == keyDown)
			if (evt.modifiers &cmdKey) {
				if ((evt.message & charCodeMask) == '.') {
					/* 0x351b is the key code and character code of the esc key. */
					evt.message = 0x351b;
					evt.modifiers &= ~cmdKey;
				}
			} else
				trans_num_keys(&evt);

		if (filterProc) {
			if ((*filterProc)(dialog, &evt, itemHit))
				break;
		} else if (evt.what == keyDown) {
			char ch = evt.message & charCodeMask;
			if (ch == CHAR_CR || ch == CHAR_ENTER) {
				*itemHit = ok;
				FlashButton(dialog, ok);
				break;
			}
		}

		if (IsDialogEvent(&evt)) {
			DialogPtr dont_care;
			if (DialogSelect(&evt, &dont_care, itemHit))
				break;

		/* The following part is problemmatic: (1) Calling HandleEvent
		   here may cause some re-entrance problem (seems ok, but I am
		   not sure). (2) It is ugly to treat mouseDown events as a
		   special case.  If we can just say "else HandleEvent(&evt);"
		   here it will be better. */
		} else if (evt.what == mouseDown)
				mv_handle_click(&evt);
			else
				HandleEvent(&evt);

		SetPort(org_port);
	}
}

/*********************************************************************************
 * mactopl routines using dialogs
 *********************************************************************************/
 
#define YN_DLOG 133
#define YNQ_DLOG 134
#define YNAQ_DLOG 135
#define YNNAQ_DLOG 136

static int yn_user_item [] = {5, 6, 7, 8};
static short gEnterItem, gEscItem;
static const char *gRespStr = (const char *)0;
static char gDef = 0;
static short dlogID;


static void
SetEnterItem (DialogPtr dp, const short newEnterItem) {
	short kind;
	Handle item;
	Rect r, r2;

	if (gEnterItem != newEnterItem) {
		GetDItem (dp, gEnterItem, &kind, &item, &r2);
		InsetRect (&r2, - 4, - 4);
		EraseRect (&r2);
		InvalRect (&r2);

		gEnterItem = newEnterItem;

		GetDItem (dp, newEnterItem, &kind, &item, &r2);
		frame_corner = kind == ctrlItem + btnCtrl ? 16 : 0;
		InsetRect (&r2, - 4, - 4);
		InvalRect (&r2);
		r = r2;
		GetDItem (dp, yn_user_item [dlogID - YN_DLOG], &kind, &item, &r2);
		SetDItem (dp, yn_user_item [dlogID - YN_DLOG], kind, item, &r);
	}
}


static void
do_tabbing (DialogPtr dp) {
	SetEnterItem(dp, gEnterItem == 1 ? strlen(gRespStr) : gEnterItem - 1);
}


static void
set_yn_number(DialogPtr dp) {
	if (gRespStr && gRespStr[gEnterItem-1] == '#') {
		short k;
		Handle h;
		Rect r;
		Str255 s;
		GetDItem(dp, gEnterItem, &k, &h, &r);
		GetIText(h, s);
		if (s[0])
			StringToNum(s, &yn_number);
	}
}


static pascal Boolean
YNAQFilter (DialogPtr dp, EventRecord *ev, short *itemHit) {
	unsigned char code;
	char ch;
	char *re = (char *) gRespStr;

	if (ev->what != keyDown) {

		return 0;
	}
	code = (ev->message & 0xff00) >> 8;
	ch = ev->message & 0xff;

	switch (code) {
	case 0x24 :
	case 0x4c :
		set_yn_number (dp);
		*itemHit = gEnterItem;
		FlashButton (dp, *itemHit);
		return 1;
	case 0x35 :
	case 0x47 :
		*itemHit = gEscItem;
		FlashButton (dp, *itemHit);
		return 1;
	case 0x30 :
		do_tabbing (dp);
		return 0;
	}
	switch (ch) {
	case '\r' :
	case '\n' :
	case ' ' :
	case 3 :
		set_yn_number (dp);
		*itemHit = gEnterItem;
		FlashButton (dp, *itemHit);
		return 1;

	case 9 :
		do_tabbing (dp);
		return 0;

	case 27 :
		*itemHit = gEscItem;
		FlashButton (dp, *itemHit);
		return 1;

	case CHAR_BS :
	case 28 : case 29 : case 30 : case 31 : /* the four arrow keys */
	case '0' : case '1' : case '2' : case '3' : case '4' :
	case '5' : case '6' : case '7' : case '8' : case '9' : {
		char *loc = strchr (gRespStr, '#');
		if (loc) {
			SetEnterItem(dp, loc - gRespStr + 1);
			return 0; /* Dialog Manager will then put this key into the text field. */
		}
	}
	}

	while (*re) {
		if (*re == ch) {
			*itemHit = (re - gRespStr) + 1;
			FlashButton (dp, *itemHit);
			return 1;
		}
		re ++;
	}

	nhbell ();
	ev->what = nullEvent;
	return 0;
}


static char
do_question_dialog (char *query, int dlog, int defbut, char *resp) {
	Str255 p;
	DialogPtr dp;
	short item;

	char c = queued_resp ((char *) resp);
	if (c)
		return c;

	dlogID = dlog;
	strcpy ((char *) p, query);
	ParamText (CtoPstr ((char *) p), (uchar *) 0, (uchar *) 0, (uchar *) 0);
	dp = mv_get_new_dialog (dlog);
	if (! dp) {
		return 0;
	}
	SetPort (dp);
	ShowWindow (dp);

	gEscItem = strlen (resp);
	gEnterItem = defbut;
	gRespStr = resp;

	SetFrameItem (dp, yn_user_item [dlogID - YN_DLOG], gEnterItem);

	InitCursor ();
	mv_modal_dialog (YNAQFilter, &item);
	mv_close_dialog (dp);
	return resp [item - 1];
}


static pascal Boolean
OneCharDLOGFilter (DialogPtr dp, EventRecord *ev, short *item) {
	char ch;
	short k;
	Handle h;
	Rect r;
	unsigned char com [2];

	if (ev->what != keyDown) {
		return 0;
	}
	ch = ev->message & 0xff;

	com [0] = 1;
	com [1] = ch;

	if (ch == 27) {
		GetDItem (dp, 4, &k, &h, &r);
		SetIText (h, com);
		*item = 2;
		FlashButton (dp, 2);
		return 1;
	}
	if (! gRespStr || strchr (gRespStr, ch)) {
		GetDItem (dp, 4, &k, &h, &r);
		SetIText (h, com);
		*item = 1;
		FlashButton (dp, 1);
		return 1;
	}
	if (ch == 10 || ch == 13 || ch == 3 || ch == 32) {
		com [1] = gDef;
		GetDItem (dp, 4, &k, &h, &r);
		SetIText (h, com);
		*item = 1;
		FlashButton (dp, 1);
		return 1;
	}
	if (ch > 32 && ch < 127) {
		GetDItem (dp, 4, &k, &h, &r);
		SetIText (h, com);
		*item = 1;
		FlashButton (dp, 1);
		return 1;
	}
	nhbell ();
	ev->what = nullEvent;
	return 1;
}


static char
generic_yn_function (query, resp, def)
const char *query, *resp;
char def;
{
	DialogPtr dp;
	short k, item;
	Handle h;
	Rect r;
	unsigned char com [32] = {1, 27}; // margin for getitext
	Str255 pQuery;

	char c = queued_resp ((char *) resp);
	if (c)
		return c;

	dp = mv_get_new_dialog (137);
	if (! dp) {
		return 0;
	}
	SetPort (dp);
	ShowWindow (dp);
	InitCursor ();
	SetFrameItem (dp, 6, 1);
	if (def) {
		com [1] = def;
	}
	strcpy ((char *) pQuery, query);
	if (resp && *resp) {
		strcat ((char *) pQuery, " (");
		strcat ((char *) pQuery, resp);
		strcat ((char *) pQuery, ")");
	}
	ParamText (CtoPstr ((char *) pQuery), (uchar *) 0, (uchar *) 0, (uchar *) 0);
	GetDItem (dp, 4, &k, &h, &r);
	SetIText (h, com);
	SelIText (dp, 4, 0, 0x7fff);
	InitCursor ();
	SetFrameItem (dp, 6, 1);
	gRespStr = resp;
	gDef = def;
	do {
		mv_modal_dialog (OneCharDLOGFilter, &item);

	} while (item != 1 && item != 2);
	GetIText (h, com);

	mv_close_dialog (dp);
	if (item == 2 || ! com [0]) {
		return 27; // escape
	}
	return com [1];
}


static char
ynaq_dialog (query, resp, def)
const char *query, *resp;
char def;
{
	int dia = 0;

	if (resp) {
		if (! strcmp (resp, ynchars)) {
			dia = YN_DLOG;
		}
		if (! strcmp (resp, ynqchars)) {
			dia = YNQ_DLOG;
		}
		if (! strcmp (resp, ynaqchars)) {
			dia = YNAQ_DLOG;
		}
		if (! strcmp (resp, ynNaqchars)) {
			dia = YNNAQ_DLOG;
		}
	}
	if (! dia) {
		return generic_yn_function (query, resp, def);
	}

	return do_question_dialog ((char *) query, dia ,
		(strchr (resp, def) - resp) + 1, (char *) resp);
}


char
popup_yn_function(const char *query, const char *resp, char def) {
	char ch;

	if (ch = ynaq_dialog (query, resp, def))
		return ch;

	return topl_yn_function(query, resp, def);
}


/*********************************************************************************
 * mgetline routines using dialogs
 *********************************************************************************/

static pascal Boolean
getlinFilter (DialogPtr dp, EventRecord *ev, short *itemHit) {
	if (ev->what == keyDown) {
		int key = ev->message & keyCodeMask,
			ch	= ev->message & charCodeMask;

		if (ch == 0x1b || key == 0x3500 || key == 0x4700) {
			*itemHit = 2;
			FlashButton(dp, 2);
			return true;
		} else if (ch == CHAR_CR || ch == CHAR_ENTER) {
			*itemHit = 1;
			FlashButton(dp, 1);
			return true;
		}
	}
	return false;
}



static Boolean
ExtendedCommandDialogFilter (DialogPtr dp, EventRecord *ev, short *item) {
	int ix;
	Handle h;
	Rect r;
	short k;
	Str255 s;
	unsigned char com [2];

	if (ev->what != keyDown) {
		return 0;
	}
	com [0] = 1;
	com [1] = ev->message & 0xff;

	if (com [1] == 10 || com [1] == 13 || com [1] == 32 ||
		com [1] == 3) { // various "OK"
		*item = 1;
		FlashButton (dp, 1);
		return 1;
	}
	if (com [1] == 27 || (ev->message & 0xff00 == 0x3500)) { // escape
		*item = 2;
		FlashButton (dp, 2);
		return 1;
	}
	for (ix = 3; ix; ix ++) {
		h = (Handle) 0;
		k = 0;
		GetDItem (dp, ix, &k, &h, &r);
		if (! k || ! h) {
			return 0;
		}
		if (k == 6) {	//	Radio Button Item
			GetCTitle ((ControlHandle) h, s);
			s [0] = 1;
			if (! IUEqualString (com, s)) {
				*item = ix;
				return 1;
			}
		}
	}
/*NOTREACHED*/
	return 0;
}


void
popup_getlin (const char *query, char *bufp) {
	ControlHandle	ctrl;
	DialogPtr		promptDialog;
	short			itemHit, type;
	Rect			box;
	Str255			pasStr;

	if (get_line_from_key_queue (bufp))
		return;

	/*
	** Make a copy of the prompt string and convert the copy to a Pascal string.
	*/
	
	strcpy((char *) pasStr, query);
	CtoPstr((char *) pasStr);
	
	/*
	** Set the query line as parameter text.
	*/
	
	ParamText(pasStr, "\p", "\p", "\p");
	
	promptDialog = mv_get_new_dialog(130);
	ShowWindow(promptDialog);

	InitCursor ();
	SetFrameItem (promptDialog, 6, 1);
	do {
		mv_modal_dialog(&getlinFilter, &itemHit);
	} while ((itemHit != 1) && (itemHit != 2));
	
	if (itemHit != 2) {
		/*
		** Get the text from the text edit item.
		*/
		
		GetDItem(promptDialog, 4, &type, (Handle *) &ctrl, &box);
		GetIText((Handle) ctrl, pasStr);
		
		/*
		** Convert it to a 'C' string and copy it into the return value.
		*/
		
		PtoCstr(pasStr);
		strcpy(bufp, (char *) pasStr);
	} else {
		/*
		** Return a null-terminated string consisting of a single <ESC>.
		*/
		
		bufp[0] = '\033';
		bufp[1] = '\0';
	}
	
	mv_close_dialog(promptDialog);
}

#endif /* ENABLE_MAC_POPUP */
