/*	ScianDialogs.c
	creates and manages simple dialog windows for scian

	Jim Lyons
	10/12/90

	4/10/91 added error alerts
	4/27/91 added Ask alert
	5/4/91	added Choose alert
	5/29/91 EMP removed library headers
	7/10/91 made Choose buttons dynamically sized
	7/17/91 changed Ask callback method
	7/19/91 changed names of Ask and Choose to AskUser and AlertUser
	9/6/91	made AskUser and AlertUser return window pointer
	10/14/91 added help strings to buttons
	10/25/91 made deferred task routine to close dialog windows
	6/1/92  EMP fixed DismissError not to use DoClose
	6/13/92 EMP changed to use method declarations, not prototypes
*/

#include "Scian.h"
#include "ScianStyle.h"
#include "ScianTypes.h"
#include "ScianIDs.h"
#include "ScianArrays.h"
#include "ScianWindows.h"
#include "ScianDraw.h"
#include "ScianTextBoxes.h"
#include "ScianObjWindows.h"
#include "ScianColors.h"
#include "ScianControls.h"
#include "ScianButtons.h"
#include "ScianLists.h"
#include "ScianEvents.h"
#include "ScianTimers.h"
#include "ScianErrors.h"
#include "ScianDialogs.h"
#include "ScianTitleBoxes.h"
#include "ScianScripts.h"

/* EMP static method declarations */

static ObjPtr DrawAlertPanel();
static ObjPtr PressAlertPanel();
static ObjPtr DismissError();
static ObjPtr DoRevert();
static ObjPtr DoCancel();
static ObjPtr DoOK();
static ObjPtr DoEnter();
static ObjPtr CheckForEnter();
static ObjPtr AlertResponse();
static ObjPtr PrevError();
static ObjPtr NextError();

/* static function prototypes */
#ifdef PROTO

static void PopDialog(void);
static void DrawBorder(int , int );

#else

static void PopDialog();
static void DrawBorder();

#endif

extern WinInfoPtr allWindows;

ObjPtr	dialogClass, errAlertStuff;

/*** parts stuff ***/
ObjPtr	partsClass;

/* globals used by PopDialog and Ask and Choose callbacks */
static FuncTyp CallbackFunction = NULL;
static WinInfoPtr theDlg1 = NULL, theDlg2 = NULL;
static int btnNumber;
static ObjPtr theReply = NULL;

void InitDialogs()
{
	dialogClass = NewObject(objWindowClass, 0);
    	AddToReferenceList(dialogClass);
	SetVar(dialogClass, NAME, NewString("Dialog"));

	/* create dummy object to store parameters for the error alert.
		ERRORLIST: list of errors not yet dismissed by user
		ERRORNUMBER: currently displayed error message
	*/
	errAlertStuff = NewObject(NULLOBJ, 0);
	AddToReferenceList(errAlertStuff);
	SetVar(errAlertStuff, NAME, NewString("Alert stuff"));
	SetVar(errAlertStuff, ERRORLIST, NewList());
	SetVar(errAlertStuff, ERRORNUMBER, NewInt(0));

	partsClass = NewObject(NULLOBJ, 0);
	AddToReferenceList(partsClass);
	SetVar(partsClass, NAME, NewString("Parts"));
}

void KillDialogs()
/*Kills the dialog windows */
{
	DeleteThing(dialogClass);
	DeleteThing(errAlertStuff);
	DeleteThing(partsClass);
}

/*************************************************************************** PARTS */

#ifdef PROTO
ObjPtr NewAlertPanel(int left, int right, int bottom, int top, char *name)
#else
ObjPtr NewAlertPanel(left, right, bottom, top, name)
int left, right, bottom, top;
char *name;
#endif
{
	ObjPtr retVal;

	retVal = NewObject(partsClass, 0);
	if (retVal)
	{
		SetVar(retVal, NAME, NewString(name));
		Set2DIntBounds(retVal, left, right, bottom, top);
		SetMethod(retVal, DRAW, DrawAlertPanel);
		SetMethod(retVal, PRESS, PressAlertPanel);
		return retVal;
	}
 	else return NULLOBJ;
}

static ObjPtr DrawAlertPanel(thePanel)
ObjPtr thePanel;
{
#ifdef GRAPHICS
	ObjPtr theColor;
	int left, right, bottom, top;
	int color;

	Get2DIntBounds(thePanel, &left, &right, &bottom, &top);

	if (theColor = GetVar(thePanel, COLOR)) color = GetInt(theColor);
	else color = UIINFOALERT;

	/* draw the outside edge */
	DrawRaisedEdge(left+1, right-1, bottom+1, top-1);

	/* draw the color border */
	FrameUIWideRect(left+EDGE+1, right-EDGE-1, bottom+EDGE+1, top-EDGE-1, ALERTBORDER, color);

	/* draw the inside edge */
	DrawSunkenEdge(left+ALERTBORDER+EDGE, right-ALERTBORDER-EDGE,
			bottom+ALERTBORDER+EDGE, top-ALERTBORDER-EDGE);

	/* draw the surface */
	FillUIRect(left+ALERTBORDER+2*EDGE, right-ALERTBORDER-2*EDGE,
			bottom+ALERTBORDER+2*EDGE, top-ALERTBORDER-2*EDGE, UIALERTBG);

	/* draw the panel outline */
	FrameUIRect(left, right, bottom, top, UIBLACK);
#endif
	return NULLOBJ;
}

static ObjPtr PressAlertPanel(thePanel, mouseX, mouseY, flags)
ObjPtr thePanel;
int mouseX, mouseY;
long flags;
{
#ifdef INTERACTIVE
	int left, right, bottom, top;

	Get2DIntBounds(thePanel, &left, &right, &bottom, &top);

	/* return if mouse outside panel */
	if (mouseX < left || mouseX > right || mouseY < bottom 
			|| mouseY > top) return ObjFalse;

	/* pop dialog window */
	if (theDlg1 = (WinInfoPtr) GetVar(GetVar(thePanel, PARENT), PARENT))
	{
	    DoTask(PopDialog);
	}

	/* do help if active */
	if (TOOL(flags) == T_HELP)
	{
		ContextHelp(thePanel);
		return ObjTrue;
	}
#endif
	/* "pass" press to contents */
	return ObjFalse;
}

static void PopDialog(void) /* deferred task to pop dialog window */
{
	if (theDlg1)
	{
		PopWindow(theDlg1);
	}
	theDlg1 = NULL;
}


/**************************************************************************** GET DIALOG */
#ifdef PROTO
WinInfoPtr GetDialog(WinInfoPtr owner, ObjPtr object, char *title, 
	int minWidth, int minHeight, int maxWidth, int maxHeight, long flags)
#else
WinInfoPtr GetDialog(owner, object, title, minWidth, minHeight, maxWidth, maxHeight, flags)
WinInfoPtr owner;
ObjPtr object;
char *title;
int minWidth, minHeight, maxWidth, maxHeight;
long flags;
#endif
/*	Returns ptr to dialog with owner owner associated with object object if it
	already exists, else calls NewOpened to make one.  
*/
{
	WinInfoPtr retVal;

	/* first see if this dialog already exists */
	if ( retVal = DialogExists(owner, object) )
	{
		PopWindow(retVal);
	}
	else
	{
		retVal = NewObjWindow(dialogClass, title, flags, minWidth,
					minHeight, maxWidth, maxHeight);
		SetVar((ObjPtr) retVal, CONTENTS, NewList());
		SetVar((ObjPtr) retVal, OWNERWINDOW, (ObjPtr) owner);
		SetVar((ObjPtr) retVal, WHICHDIALOG, object);
		SetVar((ObjPtr) retVal, CLASSID, NewInt(CLASS_DIALOG));
	}
	return retVal;	
}

WinInfoPtr DialogExists(owner, object)
WinInfoPtr owner;
ObjPtr object;
/*	determine if a dialog already exists with owner owner 
	and associated object object. If so return its pointer, else NULL. 
*/
{
	WinInfoPtr w;

	w = allWindows;
	while (w)
	{
		if (IsDialog((ObjPtr) w))
		{
			if(GetVar((ObjPtr) w, OWNERWINDOW) == (ObjPtr) owner 
					&& Eql(GetVar((ObjPtr) w, WHICHDIALOG), object))
				return w;
		}
		w = w->next;
	}
	return (WinInfoPtr) 0;
}


/************************************************************************* REPORT ERROR */
#ifdef PROTO
ObjPtr ReportError(char *name, char *text)
#else
ObjPtr ReportError(name, text)
char *name, *text;
#endif
{
	char errMsg[256];

	if (logging)
	{
		sprintf(errMsg, "# Error in %s: %s\n",name, text);
		Log(errMsg);
	}
	fprintf(stderr,"Error in %s: %s\n",name, text);
	return NULLOBJ;
}


/********************************************************************************* ASK */
#ifdef PROTO
WinInfoPtr AskUser(WinInfoPtr ownerWin, char *prompt, FuncTyp Callback, char *reply)
#else
WinInfoPtr AskUser(ownerWin, prompt, Callback, reply)
WinInfoPtr ownerWin;
char *prompt, *reply;
FuncTyp Callback;
#endif
{
	WinInfoPtr askDlg;
	ObjPtr panel, alertPanel, contents, promptBox, replyBox, revertBtn, cancelBtn, okBtn;
	ObjPtr thePrompt, obj;

	if (runningRemote) /* print prompt to stdout and get response now */
	{
#define MAXLINELENGTH 80
		char chrbuf[MAXLINELENGTH], *nl;

		printf("\n***\n%s\n",prompt); /*** fix for multiline prompts? ***/
		if (*reply) printf("(Default = %s)\n? ", reply);
		else printf("? ");
		fgets(chrbuf, MAXLINELENGTH, stdin);
		if ( nl = strchr(chrbuf, '\n') ) *nl = '\0'; /* eliminate newline */
		if (*chrbuf) Callback(ownerWin, chrbuf);
		else Callback(ownerWin, reply); /* default */
		return (WinInfoPtr) NULL;
	}

	/* see if dialog exists */
	thePrompt = NewString(prompt);
	askDlg = GetDialog(ownerWin, thePrompt, "Ask",
		ASKWIDTH, ASKHEIGHT, ASKWIDTH, ASKHEIGHT,
		WINDBUF + WINFIXEDSIZE + WINNOFRAME + WINCENTERED);
	if(!askDlg) return (WinInfoPtr) NULL;

	if (GetVar((ObjPtr) askDlg, REPOBJ)) /* dialog exists */
	{
		/* pop ask dialog */
		PopWindow(askDlg);
	}
	else /* make the dialog box */
	{
		SetVar((ObjPtr) askDlg, REPOBJ, thePrompt);
		contents = GetVar((ObjPtr) askDlg, CONTENTS); /* contents of window */
		ContentsExpectWindowSize(askDlg, ASKWIDTH, ASKHEIGHT);

		/* put in panel */
		panel = NewPanel(greyPanelClass, 0, ASKWIDTH, 0, ASKHEIGHT);
		PrefixList(contents, panel);
		SetVar(panel, PARENT, (ObjPtr) askDlg);

		contents = GetVar(panel, CONTENTS); /* contents of panel */

		/* put in pretty panel */
		alertPanel = NewAlertPanel(0, ASKWIDTH, 0, ASKHEIGHT, "Ask Panel");
		PrefixList(contents, alertPanel);
		SetVar(alertPanel, PARENT, panel);

		/* put in prompt */
		promptBox = NewTextBox(SIDEMARGIN, ASKWIDTH - SIDEMARGIN,
				ASKHEIGHT - TOPMARGIN - PROMPTHT, ASKHEIGHT - TOPMARGIN,
				PLAIN, "Prompt Box", prompt);
		PrefixList(contents, promptBox);
		SetVar(promptBox, PARENT, panel);
		SetTextFont(promptBox, PROMPTTEXTFONT);
		SetTextSize(promptBox, PROMPTTEXTSIZE);
		SetTextColor(promptBox, PROMPTTEXTCOLOR);

		/* put in reply field */
		replyBox = NewTextBox(SIDEMARGIN, ASKWIDTH - SIDEMARGIN,
				BOTMARGIN + OKBTNHT + INTERSPACE,
				BOTMARGIN + OKBTNHT + INTERSPACE + EDTEXTHT,
				EDITABLE + WITH_PIT + ONE_LINE, "Reply", reply);
		PrefixList(contents, replyBox);
		SetVar(replyBox, PARENT, panel);
		SetVar(replyBox, DATA, NewString(reply)); /* ref to by DoRevert */
		SetMethod(replyBox, CALLBACK, Callback); /* ref by DoEnter (via DoOK) */
		SetMethod(replyBox, ENTERMETHOD, DoEnter);
		SelectAll(replyBox);

		/* put in buttons */
		if (*reply) /* put in Revert button only if given a default reply */
		{
			revertBtn = NewButton(SIDEMARGIN, SIDEMARGIN + OKBTNWID, 
					BOTMARGIN, BOTMARGIN + OKBTNHT, "Revert");
			PrefixList(contents, revertBtn);
			SetVar(revertBtn, PARENT, panel);
			SetVar(revertBtn, REPOBJ, replyBox);
			SetMethod(revertBtn, CHANGEDVALUE, DoRevert);
			SetVar(revertBtn, HELPSTRING, NewString("Pressing this button \
will undo all editing and restore the text to its original state without closing the \
dialog window."));
		}

		cancelBtn = NewButton(ASKWIDTH - SIDEMARGIN - 2*OKBTNWID - SMALLGAP,
				ASKWIDTH - SIDEMARGIN - OKBTNWID - SMALLGAP,
				BOTMARGIN, BOTMARGIN + OKBTNHT, "Cancel");
		PrefixList(contents, cancelBtn);
		SetVar(cancelBtn, PARENT, panel);
		SetMethod(cancelBtn, CHANGEDVALUE, DoCancel);
		SetVar(cancelBtn, HELPSTRING, NewString("Pressing this button \
will cancel this dialog window without causing any other action."));
		okBtn = NewButton(ASKWIDTH - SIDEMARGIN - OKBTNWID,
				ASKWIDTH - SIDEMARGIN,
				BOTMARGIN, BOTMARGIN + OKBTNHT, "OK");
		PrefixList(contents, okBtn);
		SetVar(okBtn, PARENT, panel);
		SetMethod(okBtn, CHANGEDVALUE, DoOK);
		SetVar(replyBox, BUTTON, okBtn); /* ref by DoEnter routine */
		SetVar(okBtn, HELPSTRING, NewString("Pressing this button \
will dismiss this dialog window and cause the indicated action to proceed."));

		/* put border around ok button to indicate default action */
		obj = NewTitleBox(ASKWIDTH - SIDEMARGIN - OKBTNWID - EDGE,
				ASKWIDTH - SIDEMARGIN + EDGE - 1,
				BOTMARGIN - EDGE, BOTMARGIN + OKBTNHT + EDGE - 1, NIL);
		PrefixList(contents, obj);
		SetVar(obj, PARENT, panel);
		
		SetVar(okBtn, REPOBJ, replyBox);
	}
	return askDlg;
}

static ObjPtr DoRevert(btn)
ObjPtr btn;
{
	ObjPtr textBox;

	textBox = GetVar(btn, REPOBJ);
	SetTextBox(textBox, GetString(GetVar(textBox, DATA)));
	SelectAll(textBox);
	return NULLOBJ;
}

static ObjPtr DoCancel(btn)
ObjPtr btn;
{
	theDlg2 = (WinInfoPtr) GetVar(GetVar(btn, PARENT), PARENT);
	DeferMessage((ObjPtr) theDlg2, DISPOSE); /* close dialog... */
	return NULLOBJ;
}

static void AskCallback()
{
	if (!theReply)
	{
		ReportError("AskCallback", "No reply!");
		return;
	}
	if (!CallbackFunction)
	{
		ReportError("AskCallback","No completion routine!");
		return;
	}
	if (!theDlg1)
	{
		ReportError("AskCallback", "No owner window!");
		return;
	}
	IdleAllWindows();
	SelWindow(theDlg1);
	(*CallbackFunction)(theDlg1, GetString(theReply));
	theReply = NULL;
	CallbackFunction = NULL;
	theDlg1 = NULL;
}

static ObjPtr DoOK(btn)
ObjPtr btn;
{
	ObjPtr replyBox = GetVar(btn, REPOBJ);
	CallbackFunction = GetMethod(replyBox, CALLBACK);
	theReply = GetVar(replyBox, VALUE);
	theDlg1 = theDlg2 = (WinInfoPtr) GetVar(GetVar(replyBox, PARENT), PARENT);
	DeferMessage((ObjPtr) theDlg2, DISPOSE); /* close dialog... */
	DoTask(AskCallback); /* ...then do completion routine */
	return NULLOBJ;
}

static ObjPtr DoEnter(replyBox)
ObjPtr replyBox;
{
	ObjPtr btn;

	btn = GetVar(replyBox, BUTTON); /* dialog OK button */
	SetValue(btn, ObjTrue);
	return NULLOBJ;
}

/************************************************************************** ALERT USER */
#ifdef PROTO
WinInfoPtr AlertUser(int level, WinInfoPtr owner, char *prompt, FuncTyp Callback,
		int nBtns, ...)
#else
WinInfoPtr AlertUser(level, owner, prompt, Callback, nBtns, ...)
int level;
WinInfoPtr owner;
char *prompt;
FuncTyp Callback;
int nBtns;
#endif
{
	WinInfoPtr alertWin;
	ObjPtr thePrompt;

	if (runningRemote) /* print prompt to stdout and get response now */
	{
		printf("\n***\n%s\n",prompt); /*** fix for multiline prompts? ***/
		if (nBtns > 1) 
		{
			int i;
			char **p, c[4];

			while (1)
			{
				printf("\n  Enter number:\n");
				p = (char **)((&nBtns) + 1); /* point to first label */
				for (i=1; i<=nBtns; ++i) printf("  %d. %s\n", i, *(p+nBtns-i));
				printf("? ");
				fgets(c, 4, stdin);
				*c -= '0';
				if (*c < 1 || *c > nBtns) continue;
				if (Callback) Callback(owner, nBtns - *c);
				break;
			}
		}
		return (WinInfoPtr) NULL;
	}

	/* see if dialog exists */
	thePrompt = NewString(prompt);
	alertWin = GetDialog(owner, thePrompt, "Alert",
		ALERTWIDTH, ALERTHEIGHT, ALERTWIDTH, ALERTHEIGHT,
		WINDBUF + WINFIXEDSIZE + WINNOFRAME + WINCENTERED);
	if(!alertWin) return (WinInfoPtr) NULL;

	if (GetVar((ObjPtr) alertWin, REPOBJ)) /* dialog exists */
	{
		/* pop alert */
		PopWindow(alertWin);
	}
	else /* make the dialog box */
	{
		ObjPtr panel, alertPanel, contents, promptBox, btn, obj;
		char **p;
		int i, btnWid, nextPos,  defBtnLeft;

		SetVar((ObjPtr) alertWin, REPOBJ, thePrompt);
		SetMethod((ObjPtr) alertWin, KEYDOWN, CheckForEnter);
		SetMethod((ObjPtr) alertWin, DATA, Callback); /* save completion routine */
		contents = GetVar((ObjPtr) alertWin, CONTENTS); /* contents of window */
		ContentsExpectWindowSize(alertWin, ALERTWIDTH, ALERTHEIGHT);

		/* put in panel */
		panel = NewPanel(greyPanelClass, 0, ALERTWIDTH, 0, ALERTHEIGHT);
		PrefixList(contents, panel);
		SetVar(panel, PARENT, (ObjPtr) alertWin);

		contents = GetVar(panel, CONTENTS); /* contents of panel */

		/* put in pretty panel */
		alertPanel = NewAlertPanel(0, ALERTWIDTH, 0, ALERTHEIGHT, "Alert Panel");
		PrefixList(contents, alertPanel);
		SetVar(alertPanel, PARENT, panel);
		SetVar(alertPanel, COLOR, NewInt(level));

		/* put in prompt */
		promptBox = NewTextBox(SIDEMARGIN, ALERTWIDTH - SIDEMARGIN,
				BOTMARGIN + OKBTNHT + INTERSPACE, ALERTHEIGHT - TOPMARGIN,
				PLAIN, "Prompt Box", prompt);
		PrefixList(contents, promptBox);
		SetVar(promptBox, PARENT, panel);
		SetTextFont(promptBox, PROMPTTEXTFONT);
		SetTextSize(promptBox, PROMPTTEXTSIZE);

		/* put in the buttons */
		p = (char **)((&nBtns) + 1); /* point to first button label parameter */
		SetupFont(BTNFONT, BTNFONTSIZE);
		if (nBtns <= 0) /* default to OK button only */
		{
			btn = NewButton((defBtnLeft = ALERTWIDTH - SIDEMARGIN - OKBTNWID), ALERTWIDTH - SIDEMARGIN,
					BOTMARGIN, BOTMARGIN + OKBTNHT, "OK");
			PrefixList(contents, btn);
			SetVar(btn, PARENT, panel);
			/* if callback is NULL just cancel the dialog, else call the routine */
			if (Callback)
			{
				SetVar(btn, DATA, NewInt(0));
				SetMethod(btn, CHANGEDVALUE, AlertResponse);
			}
			else SetMethod(btn, CHANGEDVALUE, DoCancel);
			SetVar(btn, REPOBJ, (ObjPtr) alertWin);
		}
		else
		{
			nextPos = ALERTWIDTH - SIDEMARGIN + SMALLGAP; /* set up for loop */
			for (i=0; i<nBtns; ++i, ++p) /* from right to left; rightmost button is zero */
			{
				btnWid = 2*BTNMARGIN + StrWidth(*p);
				btnWid = GRIDSIZE * (int) (btnWid/GRIDSIZE + 1); /* constrain to grid */ 
				nextPos = nextPos - SMALLGAP - btnWid;
				if (i == 0) defBtnLeft = nextPos; /* remeber default btn position */
				if (nextPos < SIDEMARGIN) break; /* not enough room */
				btn = NewButton(nextPos, nextPos + btnWid,
						BOTMARGIN, BOTMARGIN + OKBTNHT, *p);
				PrefixList(contents, btn);
				SetVar(btn, PARENT, panel);
				SetVar(btn, DATA, NewInt(i));
				SetMethod(btn, CHANGEDVALUE, AlertResponse);
				SetVar(btn, REPOBJ, (ObjPtr) alertWin);
			}
		}
		/* put border around rightmost button to indicate default action */
		obj = NewTitleBox(defBtnLeft - EDGE,
				ALERTWIDTH - SIDEMARGIN + EDGE - 1,
				BOTMARGIN - EDGE, BOTMARGIN + OKBTNHT + EDGE - 1, NIL);
		PrefixList(contents, obj);
		SetVar(obj, PARENT, panel);
	}
	return alertWin;
}

static void AlertCallback()
{
	if (!CallbackFunction)
	{
		return;
	}
	IdleAllWindows();
/***	SelWindow(theDlg1); this doesn't work ***/
	(*CallbackFunction)(theDlg1, btnNumber);
	CallbackFunction = NULL;
	theDlg1 = NULL;
}

static ObjPtr AlertResponse(btn)
ObjPtr btn;
{
	WinInfoPtr repObj;

	btnNumber = GetInt(GetVar(btn, DATA)); /* button number, 0 is rightmost */

	repObj = (WinInfoPtr) GetVar(btn, REPOBJ); /* the alert dialog */
	theDlg2 = repObj; /* save for close task */
	theDlg1 = (WinInfoPtr) GetVar((ObjPtr) repObj, OWNERWINDOW); /* save for cpl rtn */

	CallbackFunction = GetMethod((ObjPtr) repObj, DATA); /* save completion routine */

	DeferMessage((ObjPtr) theDlg2, DISPOSE); /* close dialog... */
	DoTask(AlertCallback); /* ...then do completion routine */
	return ObjTrue;
}

static ObjPtr CheckForEnter(dlg, key, flags)
ObjPtr dlg; /* really a WinInfoPtr but who cares */
int key;
long flags;
{
    if (key == '\r')
    {
	btnNumber = 0; /* default button */
	theDlg2 = (WinInfoPtr) dlg; /* save for close task */
	theDlg1 = (WinInfoPtr) GetVar(dlg, OWNERWINDOW); /* save for cpl rtn */

	CallbackFunction = GetMethod(dlg, DATA); /* save completion routine */

	DeferMessage((ObjPtr) theDlg2, DISPOSE); /* close dialog... */
	DoTask(AlertCallback); /* ...then do completion routine */
	return ObjTrue;
    }
    else
    {
	return ObjFalse;
    }
}
