/*	XGDialog.cpp
 *
 *		This is the dialog handling routines. This knows how to
 *	handle both modal and modeless dialogs.
 *
 *		A modeless dialog is really not much more than a window.
 *	A modal dialog is the same, except that the rest of the application
 *	is deactivated until the window is dismissed.
 */

/**********************************************************************

  YAAF is Copyright 1997-1999 by William Woody.

  Redistribution and use in source and binary forms, with or without 
  modification, are permitted provided that the following conditions 
  are met:

  Redistributions of source code must retain the above copyright notice, 
  this list of conditions and the following disclaimer.

  Neither the name of the copyright holder or other contributors may
  be used to promote products derived from this software without
  specific written prior permission.

  THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 
  EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES 
  OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 
  NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS 
  BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN 
  ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 
  CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
  SOFTWARE.
  
  Contact:   woody@alumni.caltech.edu
  Web page:  http://www.pandawave.com/yaaf/

**********************************************************************/

#include <XConfig.h>			// must be first to optimize compile on MSVC++
#include <string.h>
#include <stdio.h>

#include <XDraw.h>
#include <XDialog.h>
#include <XApplication.h>
#include <XMessages.h>
#include <XDataUtil.h>
#include <XList.h>

#include <XTextControls.h>
#include <XButtons.h>

/************************************************************************/
/*																		*/
/*	Constants															*/
/*																		*/
/************************************************************************/

/*	InitRecord
 *
 *		The initialization record for my popup view.
 */

static XGSViewInitRecord GInitRecord = {
	'****', -1, 0,
	true, true, true, false,
	0, 0, 0, 20,
	true, true, false
};

/************************************************************************/
/*																		*/
/*	Globals																*/
/*																		*/
/************************************************************************/

#if OPT_XWIN == 1
extern bool		_GDialogFlag;
#endif

/************************************************************************/
/*																		*/
/*	Modal Dialog Declarations											*/
/*																		*/
/************************************************************************/

/*$	XGDialog::XGDialog
 *
 *		Construct me. This is normally called internally by YAAF.
 */

XGDialog::XGDialog(XGSWindowInitRecord &x, XGForm *f) : 
	XGWindow(x,f)
{
	fDataPtr = NULL;
	fDialogProc = NULL;
	fTermValue = 0;
	fTerminate = false;
	fModal = false;
	
	fOK = 0;
	fCancel = 0;
}

/*$	XGDialog::XGDialog
 *
 *		Construct me. This is normally called internally by YAAF.
 */

XGDialog::XGDialog(XGArgStream &x, XGForm *f) : 
	XGWindow(x,f)
{
	fDataPtr = NULL;
	fDialogProc = NULL;
	fTermValue = 0;
	fTerminate = false;
	fModal = false;
	
	fOK = x.ReadInteger();
	fCancel = x.ReadInteger();		/* Get OK/Cancel controls */
}

/*$	XGDialog::~XGDialog
 *
 *		Destroy me. Normally called internally by YAAF.
 */

XGDialog::~XGDialog()
{
	if (fDialogProc) {
		SendMessage(KXGDialogMessage,KXGDMEndDialog,NULL);
	}
	
	fDialogProc = NULL;
	fDataPtr = NULL;
	fTerminate = true;
}

/*$	XGDialog::CloseWindow
 *
 *		An alternate way to close this; on the Microsoft Windows
 *	platform this is called when the window's close box is clicked
 */

bool XGDialog::CloseWindow()
{
	EndDialog(0);
	return true;
}

/************************************************************************/
/*																		*/
/*	Messages															*/
/*																		*/
/************************************************************************/

/*$	XGDialog::DoDrawView
 *
 *		Handle drawing the background.
 */

void XGDialog::DoDrawView(XRect r)
{
	XGDraw draw(this);
	draw.Draw3DRect(r,KXGEBackground);
}

/************************************************************************/
/*																		*/
/*	Modeless creation/destruction										*/
/*																		*/
/************************************************************************/

/*$	XGDialog::CreateInit
 *
 *		Internal routine for creating this thing.
 */

XGDialog *XGDialog::CreateInit(XGDialog *d, XGPDialogProc proc, void *data)
{
	XGCommandButton *b;
	long ret;
	
	/*
	 *	Now tell me what's up
	 */
	
	if (d) {
#if OPT_WINOS
		/* on Windows, the convention is for the OK button to be on
		 * the left, and the Cancel button on the right.  The arg
		 * stream template has these buttons in their Mac positions,
		 * so the code below just swaps the buttons on Windows.  If
		 * the buttons are not side-by-side, no change is made (they
		 * might be vertically aligned, and I don't know if there
		 * is any convention in that case).
		 */
		XGView *v_ok = d->FindViewByID(d->fOK);
		XGView *v_cancel = d->FindViewByID(d->fCancel);
		if (v_ok && v_cancel) {
			XPoint p_ok = v_ok->GetPos();
			XPoint p_cancel = v_cancel->GetPos();
			if (p_ok.v == p_cancel.v) {
				XRect r = v_ok->GetContentRect();
				OffsetRect(&r, p_cancel.h, p_cancel.v);
				v_ok->SetLocation(r);
				r = v_cancel->GetContentRect();
				OffsetRect(&r, p_ok.h, p_ok.v);
				v_cancel->SetLocation(r);
			}
		}
#endif

		d->fDataPtr = data;
		d->fDialogProc = proc;
		
		ret = d->SendMessage(KXGDialogMessage,KXGDMInitDialog,NULL);
		if (ret == -1) ret = 0;
		if (!ret) {
			XGView *v;
			
			v = d->FindFirstView();
			if (v) v->SetFocus();

			b = dynamic_cast<XGCommandButton *>(d->FindViewByID(d->fOK));
			if (b) b->SetDefault(true);
		}
		if (d->fTerminate) {
			delete d;
			return NULL;
		}
	}
	return d;
}

/*$	XGDialog::Create
 *
 *		Create this thing from the resource ID. This is the normal way
 *	you create a modal or modeless dialog box. If you are creating a
 *	modal dialog box, you can also use the XGModalDialog class.
 */

XGDialog *XGDialog::Create(short resID, XGPDialogProc proc, void *data)
{
	XGDialog *d;
	
	/*
	 *	Create my window
	 */
	
	d = (XGDialog *)XGWindowFactory::CreateFromResource(resID);
	return CreateInit(d,proc,data);
}

/*$	XGDialog::Create
 *
 *		Create this thing from the data. This is the normal way
 *	you create a modal or modeless dialog box. If you are creating a
 *	modal dialog box, you can also use the XGModalDialog class.
 */

XGDialog *XGDialog::Create(const unsigned char *dptr, 
						   XGPDialogProc proc, 
						   void *data)
{
	XGDialog *d;
	
	/*
	 *	Create my window
	 */
	
	d = (XGDialog *)XGWindowFactory::CreateFromData(dptr);
	return CreateInit(d,proc,data);
}

/************************************************************************/
/*																		*/
/*	Modal Processing													*/
/*																		*/
/************************************************************************/

/*$	XGDialog::ModalRun
 *
 *		The heart of the modal processing code. After creating and
 *	initializing an XGDialog window, you would call this in order
 *	to handle a modal dialog box.
 */

long XGDialog::ModalRun(XGDialog *d)
{
	long l;
	
	if (d == NULL) return 0;
	
	/*
	 *	Run the event loop until I'm told to terminate
	 */
	
#if OPT_WINOS == 1
	EnableWindow(_GMainWindow,FALSE);		// Disable the main window
#endif

	d->fModal = true;						// I'm a modal dialog
	for (;;) {
		/* I find this useful for dialog that need some idle time.
		 * It's easier than doing an XGPeriodic mix-in, or creating
		 * a thread.
		 */
		d->SendMessage(KXGDialogMessage, KXGDMIdleDialog, NULL);
		XGAppCore::GetApplication()->RunOnce(d);
		if (d->fTerminate) break;
	}

#if OPT_WINOS == 1
	EnableWindow(_GMainWindow,TRUE);		// Re-enable it again
#endif
	
	/*
	 *	Now it's time to quit. Quit.
	 */
	
	l = d->fTermValue;
	delete d;
	return l;
}

/*$	XGDialog::ModalDialog
 *
 *		This is the modal dialog version of the dialog creation routine.
 *	On the Macintosh and on Windows I do all sorts of wierd magic stuff.
 *	This is a single-call mechanism for creating a modal window from a
 *	resource identifier.
 */

long XGDialog::ModalDialog(short resID, XGPDialogProc proc, void *data)
{
	XGDialog *d;
	
	/*
	 *	Create the thing
	 */

#if OPT_XWIN == 1
	_GDialogFlag = true;				/* (XWindows) force window floating */
#endif
	d = Create(resID,proc,data);
#if OPT_XWIN == 1
	_GDialogFlag = false;
#endif

	return ModalRun(d);
}

/*$	XGDialog::ModalDialog
 *
 *		This is the modal dialog version of the dialog creation routine.
 *	On the Macintosh and on Windows I do all sorts of wierd magic stuff.
 *	This is a single-call mechanism for creating a modal window from a
 *	block of data.
 */

long XGDialog::ModalDialog(const unsigned char *dptr,
						   XGPDialogProc proc, 
						   void *data)
{
	XGDialog *d;
	
	/*
	 *	Create the thing
	 */

#if OPT_XWIN == 1
	_GDialogFlag = true;				/* (XWindows) force window floating */
#endif
	d = Create(dptr,proc,data);
#if OPT_XWIN == 1
	_GDialogFlag = false;
#endif

	return ModalRun(d);
}

/*$	XGDialog::ModalAlert
 *
 *		Alert routine; this spins until a button is pressed. This
 *	simplifies the implementation of custom alerts. this is similar
 *	to the c ModalDialog routine, except it also allows you to substitute
 *	up to four string arguments into the dialog box.
 */

long XGDialog::ModalAlert(short al, 
						  const char *a, 
						  const char *b, 
						  const char *c, 
						  const char *d)
{
	const char *list[4];
	
	list[0] = a;
	list[1] = b;
	list[2] = c;
	list[3] = d;
	return ModalDialog(al,AlertProc,(void *)list);
}

/*$	XGDialog::ModalAlert
 *
 *		Alert routine; this spins until a button is pressed. This
 *	simplifies the implementation of custom alerts. this is similar
 *	to the c ModalDialog routine, except it also allows you to substitute
 *	up to four string arguments into the dialog box.
 */

long XGDialog::ModalAlert(const unsigned char *data, 
						  const char *a, 
						  const char *b, 
						  const char *c, 
						  const char *d)
{
	const char *list[4];
	
	list[0] = a;
	list[1] = b;
	list[2] = c;
	list[3] = d;
	return ModalDialog(data,AlertProc,(void *)list);
}

/*$	XGDialog::AlertProc
 *
 *		run the alert proc by inserting the text of the four
 *	fields into the locations specified by '^0' through '^3'
 *	in the text fields
 */

long XGDialog::AlertProc(XGDialog *d, void *ptr, long msg, long arg, void *)
{
	XGStaticText *text;
	XGView *view;
	
	if (msg == KXGDialogMessage) {
		if (arg == KXGDMInitDialog) {
			char buffer[256];

			/*
			 *	Run through all of the text items in the alert
			 *	and perform text substitution
			 */
			
			for (view = d->GetChild(); view != NULL; view = view->GetSibling()) {
				text = dynamic_cast<XGStaticText *>(view);
				if (text) {
					text->GetTitle(buffer);
					ModifyText(buffer,(const char **)ptr);
					text->SetTitle(buffer);
				}
			}
			
			/*
			 *	Now do the same for me
			 */
			
			d->GetTitle(buffer);
			ModifyText(buffer,(const char **)ptr);
			d->SetTitle(buffer);
		}
		return -1;
	}
	
	if (msg == KXGControlClick) {
		d->EndDialog(arg);
		return 0;
	}
	
	return -1;
}

/*$	XGDialog::ModifyText
 *
 *		Modify the text in a field by inserting into '^n', where n is
 *	from 0 to 3.
 */

void XGDialog::ModifyText(char *text, const char **list)
{
	char buffer[256];
	char *b;
	const char *c;
	short i;
	
	b = text;
	i = 0;
	while (*b && (i < 255)) {
		if (*b == '^') {
			if ((b[1] >= '0') && (b[1] <= '3')) {
				c = list[b[1] - '0'];
				b += 2;
				
				while ((*c) && (i < 255)) {
					buffer[i++] = *c++;
				}
				continue;
			}
		}
		buffer[i++] = *b++;
	}
	buffer[i] = 0;
	
	strcpy(text,buffer);
}

/*$	XGDialog::EndDialog
 *
 *		End this dialog. Destroy if we're a modeless. Normally your
 *	callback routine would call this routine during event processing if
 *	the dialog box should be shut down.
 */

void XGDialog::EndDialog(long v)
{
	fTerminate = true;
	fTermValue = v;
	
	if (!fModal) delete this;
}

/************************************************************************/
/*																		*/
/*	Dispatch Support													*/
/*																		*/
/************************************************************************/

BEGINDISPATCHPROC(XGDialog,XGWindow,XGDialog::Dispatch)
	DISPATCHTABLE(KXGKeyEvent,XGDialog::DoKey)
	
#if OPT_MACOS
	DISPATCHTABLE(KXGMenuKey,XGDialog::DoMenuKey)
#endif

ENDDISPATCH

/*	XGDialog::Dispatch
 *
 *		Process dispatch message
 */

long XGDialog::Dispatch(long msg, long arg, void *parg)
{
	long l;
	
	l = SendMessage(msg,arg,parg);
	if ((l == -1) && (!fTerminate)) {
		l = XGWindow::Dispatch(msg,arg,parg);
	}
	return l;
}

#if OPT_MACOS

/*	XGDialog::DoMenuKey
 *
 *		Handle menu key
 */

long XGDialog::DoMenuKey(long k, void *)
{
	short key,code;
	XGCommandButton *b;
	
	code = GETHIWORD(k);
	key  = GETLOWORD(k);

	if ((key == '.') && (code & KKeyCommand)) {
		/* Use command+'.' as escape */
		b = dynamic_cast<XGCommandButton *>(FindViewByID(fCancel));
		if (b) b->PushButton();
		return 0;
	}
	return -1;
}

#endif

/*	XGDialog::DoKey
 *
 *		Handle key
 */

long XGDialog::DoKey(long k, void *)
{
	short key,code;
	XGCommandButton *b;
	
	code = GETHIWORD(k);
	key  = GETLOWORD(k);

#if OPT_MACOS
	/* Ignore control key combinations */
	if (code & KKeyControl) return -1;
#endif

#if OPT_XWIN
	if (((key == 'c') || (key == 'C')) && (code & KKeyControl)) {
		/* Translate 'control+c' into escape */
		key = KKeyEscape;
	}	
#endif

	if ((key == 0x0D) && !(code & KSpecialKey) && fOK) {
		b = dynamic_cast<XGCommandButton *>(FindViewByID(fOK));
		if (b) b->PushButton();
		return 0;
	}
	
	if ((key == KKeyEscape) && (code & KSpecialKey) && fCancel) {
		b = dynamic_cast<XGCommandButton *>(FindViewByID(fCancel));
		if (b) b->PushButton();
		return 0;
	}
	

	return -1;
}

/************************************************************************/
/*																		*/
/*	Dispatch Support													*/
/*																		*/
/************************************************************************/

/*$	XGDialog::SendMessage
 *
 *		Send a message to me
 */

long XGDialog::SendMessage(long msg, long arg, void *parg)
{
	if (fDialogProc) {
		return fDialogProc(this,fDataPtr,msg,arg,parg);
	} else {
		return 0;
	}
}

/************************************************************************/
/*																		*/
/*	Contents Management													*/
/*																		*/
/************************************************************************/

/*$	XGDialog::GetText
 *
 *		Get the text
 */

void XGDialog::GetText(short id, short len, char *msg)
{
	char buffer[256];
	XGView *view;
	
	/*
	 *	Get the title from the appropriate view. If this fails, it
	 *	fails.
	 */
	
	view = FindViewByID(id);
	if (view == NULL) return;				/* No view exists by ID		*/
	view->GetTitle(buffer);
	if (len <= 255) buffer[len-1] = 0;
	strcpy(msg,buffer);
}

/*$	XGDialog::SetText
 *
 *		Set the text for this thing
 */

void XGDialog::SetText(short id, const char *msg)
{
	XGView *view;
	
	/*
	 *	Set the title from the appropriate view. If this fails, it
	 *	fails.
	 */
	
	view = FindViewByID(id);
	if (view == NULL) return;				/* No view exists by ID		*/
	view->SetTitle(msg);
}

/*$	XGDialog::GetValue
 *
 *		Get the integer value of this thing. This converts from text
 *	if this is a text object
 */

long XGDialog::GetValue(short id)
{
	char buffer[256];
	XGListControl *list;
	XGControl *btn;
	XGView *view;
	
	/*
	 *	First, get the view by it's ID.
	 */
	
	view = FindViewByID(id);
	if (view == NULL) return 0;				/* No view exists by ID		*/
		
	/*
	 *	Try massaging this into a button
	 */
	
	btn = dynamic_cast<XGControl *>(view);
	if (btn) {
		/*
		 *	This was a button. Do a 'get'
		 */
		
		return btn->GetValue();
	}
	
	list = dynamic_cast<XGListControl *>(view);
	if (list) {
		return list->GetValue();
	}
	
	/*
	 *	If this isn't a control, go ahead and get the title instead.
	 */
	 
	view->GetTitle(buffer);
	return atol(buffer);
}

/*$	XGDialog::SetValue
 *
 *		Get the integer value of this thing. This converts from text
 *	if this is a text object
 */

void XGDialog::SetValue(short id, long value)
{
	char buffer[256];
	XGListControl *list;
	XGControl *btn;
	XGView *view;
	
	/*
	 *	First, get the view by it's ID.
	 */
	
	view = FindViewByID(id);
	if (view == NULL) return;				/* No view exists by ID		*/
		
	/*
	 *	Try massaging this into a button
	 */
	
	btn = dynamic_cast<XGControl *>(view);
	if (btn) {
		btn->SetValue(value);
		return;
	}
	
	list = dynamic_cast<XGListControl *>(view);
	if (list) {
		list->SetValue(value);
		return;
	}
	
	
	
	/*
	 *	Set thte title instead.
	 */

	sprintf(buffer,"%ld",value);
	view->SetTitle(buffer);
}

/*$	XGDialog::SetDefaultOK
 *
 *		Set the default OK button control
 */

void XGDialog::SetDefaultOK(short id)
{
	XGCommandButton *b;
	
	if (fOK == id) return;
	
	if (fOK) {
		b = dynamic_cast<XGCommandButton *>(FindViewByID(fOK));
		if (b) b->SetDefault(false);
	}
	if (id) {
		b = dynamic_cast<XGCommandButton *>(FindViewByID(id));
		if (b) b->SetDefault(true);
	}
	fOK = id;
	
	InvalView();
}

/*$	XGDialog::SetDefaultCancel
 *
 *		Set the default cancel button control
 */

void XGDialog::SetDefaultCancel(short id)
{
	fCancel = id;
	InvalView();
}

