/*	XGPopControl.cpp
 *
 *		Popup control
 */

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

  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 <XApplication.h>
#include <XPopup.h>
#include <XDraw.h>
#include <XWindow.h>
#include <XDataUtil.h>
#include <XMessages.h>
#include <XInline.h>
#include <XMisc.h>

#if OPT_MACOS == 1
#include <appearance.h>
#include <string.h>
#endif

/************************************************************************/
/*																		*/
/*	Popup controls														*/
/*																		*/
/************************************************************************/

/*$	XGPopControl::XGPopControl
 *
 *		Pop up control
 */

XGPopControl::XGPopControl(XGView *parent, XGArgStream &stream) : 
	XGControl(parent,stream)
{
	fFontID = stream.ReadInteger();
	fFont = XGFont::LoadFont(fFontID);
	fValue = 0;
	fPop = NULL;

#if OPT_MACOS
	ClearTabStop();
	InitControl();
#endif

#if OPT_WINOS
	InitControl();
#endif
}

/*$	XGPopControl::~XGPopControl
 *
 *		Delete me
 */

XGPopControl::~XGPopControl()
{
	if (fPop) delete fPop;
	DeleteAll();
	fFont->Detach();
#if OPT_MACOS
	if (fControl) {
		MenuHandle menu = (**(PopupPrivateDataHandle)((**fControl).contrlData)).mHandle;
		(**(PopupPrivateDataHandle)((**fControl).contrlData)).mHandle = NULL;
		DeleteMenu(0x4000 + GetViewID());
		DisposeMenu(menu);
	}
#endif
#if OPT_WINOS
	if (fControl) {
		::DestroyWindow(fControl);
	}
#endif
}

/************************************************************************/
/*																		*/
/*	Control List Management												*/
/*																		*/
/************************************************************************/

/*$	XGPopControl::GetItem
 *
 *		Get the specified item in a zero-based index
 */

void XGPopControl::GetItem(long index, char *data) const
{
	fList.GetString(index,data);
}

/*$	XGPopControl::Length
 *
 *		# items in me
 */

long XGPopControl::Length() const
{
	return fList.GetNumStrings();
}

/*$	XGPopControl::Insert
 *
 *		Insert this item
 */

void XGPopControl::Insert(long index, const char *buffer)
{
	fList.Insert(index,buffer);
#if OPT_MACOS || OPT_WINOS
	ReloadItems();
#endif
	InvalView();
}

/*$	XGPopControl::Delete
 *
 *		Delete this item
 */

void XGPopControl::Delete(long index)
{
	fList.Delete(index);
	if (fValue >= Length()) fValue = 0;
#if OPT_MACOS || OPT_WINOS
	ReloadItems();
#endif
	InvalView();
}

/*$	XGPopControl::DeleteAll
 *
 *		Delete all items
 */

void XGPopControl::DeleteAll(void)
{
	fList.DeleteAll();
#if OPT_MACOS || OPT_WINOS
	ReloadItems();
#endif
	InvalView();
}

 
/************************************************************************/
/*																		*/
/*	Control List Management												*/
/*																		*/
/************************************************************************/

/*$	XGPopControl::GetValue
 *
 *		Get the value of this thing
 */

long XGPopControl::GetValue()
{
	return fValue;
}

/*$	XGPopControl::SetValue
 *
 *		Set the value of this thing
 */

void XGPopControl::SetValue(long value)
{
	if (value >= Length()) value = Length()-1;
	if (value < 0) value = 0;
	if (fValue == value) return;
	fValue = value;
#if OPT_MACOS
	if (fControl)
		SetControlValue(fControl, fValue + 1);
#endif
#if OPT_WINOS
	if (fControl)
		SendMessage(fControl, CB_SETCURSEL, fValue, 0L);
#endif
	InvalView();
}
 
/************************************************************************/
/*																		*/
/*	Control Events														*/
/*																		*/
/************************************************************************/

/*$	XGPopControl::DoDrawView
 *
 *		Draw the view.
 */

void XGPopControl::DoDrawView(XRect)
{
#if OPT_MACOS
	if (fControl) {
		XGDraw draw(this,false);
		if (fFont) {
			fFont->Attach();
			draw.SetFont(fFont);
		}
		
		if (IsEnabled() && IsActive()) {
			/*
			 *	Enabled
			 */
			
			::HiliteControl(fControl, 0);
		} else {
			::HiliteControl(fControl, 255);
		}
		::ShowControl(fControl);
		::Draw1Control(fControl);
		return;
	}
#endif

#if OPT_WINOS
	if (fControl) {
		/*
		 *	The invalidate here may seem a little wierd, but this
		 *	causes the control to redraw itself separately, and after
		 *	this draw view command is issued. The upshot of this is
		 *	that the control is forced to be redrawn.
		 */
		
		::ShowWindow(fControl, SW_SHOW);
		::InvalidateRect(fControl, NULL, false);
	}
	return;
#endif

#if OPT_XWIN || OPT_MACOS
	/*
	 *	(MacOS): if we get here, use simulated popup. Happens when we don't
	 *	have the appearance manager.
	 */
	
	XRect r;
	XRect s;
	XRect t;
	XGDraw draw(this);
	fFont->Attach();
	draw.SetFont(fFont);
	
	r = GetContentRect();
	s = r;
	::InsetRect(&s,1,1);
	t = s;
	s.left = s.right - (s.bottom - s.top);
	t.right = s.left;
	
	draw.Draw3DRect(r,KXGEBackground);
	if (IsEnabled() && IsActive()) {
		if (fPop) {
			draw.Draw3DRect(r,KXGEFrame);
			draw.Draw3DRect(s,KXGEInset);
			draw.Draw3DRect(t,KXGEInset);
		} else {
			draw.Draw3DRect(r,KXGEFrame);
			draw.Draw3DRect(s,KXGEOutset);
			draw.Draw3DRect(t,KXGEOutset);
		}
	} else {
		draw.Draw3DRect(r,KXGEGrayFrame);
		draw.SetForeColor(KXGColorBtnShadow);
	}
	::InsetRect(&s,5,5);
	
	draw.MoveTo(s.left,(s.top*3+s.bottom)/4);
	draw.LineTo(s.right,(s.top*3+s.bottom)/4);
	draw.LineTo((s.left+s.right)/2,(s.top+3*s.bottom)/4);
	draw.LineTo(s.left,(s.top*3+s.bottom)/4);
	
	s = r;
	s.right -= s.bottom - s.top;
	::InsetRect(&s,1,1);
	draw.ClipRect(&s);
	
#if OPT_WINOS || OPT_XWIN
	if (HasFocus() && IsEnabled() && IsActive()) {
		::InsetRect(&s,1,1);
		draw.DrawFocusRect(s);
		::InsetRect(&s,-1,-1);
	}
#endif

	draw.MoveTo(s.left + 3,
				(s.top + s.bottom - draw.GetFontHeight())/2+draw.GetFontAscent());

	if (fValue < Length()) {
		char buffer[256];
		GetItem(fValue,buffer);
		draw.DrawString(buffer);
	}
#endif
}


/*$	XGPopControl::DoMoveView
 *
 *		Move the view.
 */

void XGPopControl::DoMoveView()
{
	XRect r;

#if OPT_XWIN
	XGControl::DoMoveView();
#endif

#if OPT_MACOS || OPT_WINOS	
	if (!fControl) {
		XGControl::DoMoveView();
		return;
	}

	r = GetContentRect();
	ViewToGlobal(&r);
	
#if OPT_MACOS
	XGDraw draw(this,false);
	if (fFont) {
		fFont->Attach();
		draw.SetFont(fFont);
	}
	::HideControl(fControl);
	::MoveControl(fControl,r.left,r.top);
	::SizeControl(fControl,r.right-r.left,r.bottom-r.top);
#endif
#if OPT_WINOS
	::SetWindowPos(fControl,
				   NULL,
				   r.left,
				   r.top,
				   r.right-r.left,
				   r.bottom-r.top,
				   SWP_NOZORDER | SWP_HIDEWINDOW);
#endif
#endif

}

/*$	XGPopControl::DoSizeView
 *
 *		Handle resize event
 */

void XGPopControl::DoSizeView()
{
	DoMoveView();

	/*
	 *	Pass upwards
	 */
	
	XGControl::DoSizeView();
}

/*$	XGPopControl::DoShowHide
 *
 *		This is called when the control is to be hidden or
 *	shown. This only needs to hide the control; the call to
 *	DoDrawView() will show the control if needed.
 */

void XGPopControl::DoShowHide()
{
#if OPT_MACOS || OPT_WINOS
	if (fControl) {
#if OPT_MACOS
		if (!IsVisible()) {
			XGDraw draw(this,false);
			if (fFont) {
				fFont->Attach();
				draw.SetFont(fFont);
			}
			::HideControl(fControl);
			::MoveControl(fControl,-20,-20);
			::SizeControl(fControl,10,10);
		} else {
			XRect r;

			r = GetContentRect();
			ViewToGlobal(&r);
			::MoveControl(fControl,r.left,r.top);
			::SizeControl(fControl,r.right-r.left,r.bottom-r.top);
		}
#endif
#if OPT_WINOS
		if (!IsVisible())
			::ShowWindow(fControl, SW_HIDE);
#endif
	}
#endif
	XGControl::DoShowHide();
}

/*$	XGPopControl::DoActivate
 *
 *		Handle activate/deactivate
 */

void XGPopControl::DoActivate(bool)
{
#if OPT_MACOS || OPT_XWIN
	if (IsEnabled())
		InvalView();
#endif
#if OPT_WINOS
	/* Does nothing; activation/deactivation handled automatically */
#endif
}

/*$	XGPopControl::DoMouseDown
 *
 *		Handle mouse down event
 */

bool XGPopControl::DoMouseDown(XPoint where, short flags)
{
#if OPT_MACOS || OPT_XWIN
	#pragma unused(flags)
#endif

#if OPT_MACOS
	if (fControl) {
		XGDraw draw(this,false);
		if (fFont) {
			fFont->Attach();
			draw.SetFont(fFont);
		}
		ViewToGlobal(&where);
		if (::TrackControl(fControl,where.MacPoint(),NULL)) {
			fValue = GetControlValue(fControl) - 1;
			DoDispatch(KXGPopValue,GetViewID(),NULL);
		}
		return false;
	}
#endif

#if OPT_WINOS
	if (fControl) {
		SetFocus();
		return XGControl::DoMouseDown(where, flags);
	}
	return false;
#endif

#if OPT_XWIN || OPT_MACOS
	/*
	 *	If we get here, simulating popup. Happens when we dont' have
	 *	an appearance manager
	 */
	
	XRect r,s;
	XGDraw draw(this);
	fFont->Attach();
	draw.SetFont(fFont);
	
	/*
	 *	Capture focus
	 */
	
#if OPT_WINOS || OPT_XWIN
	SetFocus();
#endif
	
	/*
	 *	Draw the gadget down
	 */
	
	r = GetContentRect();
	draw.Draw3DRect(r,KXGEInButton);
	s = r;
	s.left = s.right - (s.bottom - s.top);
	::InsetRect(&s,2,2);
	draw.Draw3DRect(s,KXGEInset);
	
	/*
	 *	Pop up the list
	 */
	
	r = GetContentRect();
	fPop = XGInlinePopup::Create(this,r,fFontID,&fList,fValue);
	SetCapture(fPop);

	return true;
#endif
}


void XGPopControl::DoEnable()
{
#if OPT_WINOS
	if (fControl)
		::EnableWindow(fControl,IsEnabled());
#endif
}

#if OPT_WINOS

/*$	XGButton::SetFocus
 *
 *		Set the focus. This overrides stuff on Windows by setting the
 *	MS Window focus as well.
 */

void XGPopControl::SetFocus(void)
{
	XGControl::SetControlFocus(fControl);
}

#endif

/************************************************************************/
/*																		*/
/*	Keyboard navigation													*/
/*																		*/
/************************************************************************/

/*$	XGPopControl::DoGainFocus
 *
 *		Gain the focus. Redraw me
 */

void XGPopControl::DoGainFocus()
{
	InvalView();
}

/*$	XGPopControl::DoLoseFocus
 *
 *		Lose focus. Redraw me
 */

void XGPopControl::DoLoseFocus()
{
	InvalView();
}

BEGINDISPATCH(XGPopControl,XGControl)
	DISPATCHTABLE(KXGKeyEvent,XGPopControl::DoKey)
	DISPATCHTABLE(KXGPopupInlineClose,XGPopControl::DoInlineClose)
#if OPT_WINOS
	DISPATCHTABLE(KXGWInternal,XGPopControl::DoWInternal)
#endif
ENDDISPATCH

/*	XGPopControl::DoInlineClose
 *
 *		Handle inline close operation. This is only sent when a
 *	popup control is created...
 */

long XGPopControl::DoInlineClose(long, void *parg)
{
	XGSPopupInline *p = (XGSPopupInline *)parg;
	XRect r,s;
	
	if (fPop) {
		fPop = NULL;
		
		/*
		 *	Alter my value
		 */
		
		fValue = p->value;
		DoDispatch(KXGPopValue,GetViewID(),NULL);
		
		/*
		 *	Reddraw me in the 'up' position
		 */
		
		XGDraw draw(this);
		fFont->Attach();
		draw.SetFont(fFont);

		r = GetContentRect();
		draw.Draw3DRect(r,KXGEButton);
		s = r;
		s.left = s.right - (s.bottom - s.top);
		::InsetRect(&s,2,2);
		draw.Draw3DRect(s,KXGEOutset);
	}
	
	return 0;
}

/*	XGPopControl::DoKey
 *
 *		Handle keyboard event. This only handles up, down keys
 */

long XGPopControl::DoKey(long arg, void *)
{
	short hi,lo;
	
	hi = GETHIWORD(arg);
	lo = GETLOWORD(arg);
	if (hi & KSpecialKey) {
		switch (lo) {
			case KKeyUpCursor:
				SetValue(GetValue()-1);
				DoDispatch(KXGPopValue,GetViewID(),NULL);
				return 0;
			case KKeyDownCursor:
				SetValue(GetValue()+1);
				DoDispatch(KXGPopValue,GetViewID(),NULL);
				return 0;
		}
	} else {
	}
	return -1;
}

#if OPT_MACOS

/*$	XGPopControl::ReloadItems
 *
 *		updates the menu contents
 */

void XGPopControl::ReloadItems (void)
{
	MenuHandle menu;
	int ix, len;
	char buf[256];

	if (!fControl)
		return;

	menu = (**(PopupPrivateDataHandle)((**fControl).contrlData)).mHandle;
	if (!menu)
		return;

	for (ix = CountMItems(menu); ix > 0; ix--)
		DeleteMenuItem(menu, ix);


	for (ix = 0; ix < fList.GetNumStrings(); ix++) {
		fList.GetString(ix, buf);
		len = strlen(buf);
		memmove(&buf[1], buf, len);
		buf[0] = len;
		AppendMenu(menu, "\px");
		SetMenuItemText(menu, ix + 1, (unsigned char *)buf);
	}

	SetControlMaximum(fControl, CountMItems(menu));
	SetControlValue(fControl, fValue + 1);
}

/*$	XGPopControl::InitControl
 *
 *		Initialize the control. Use appearance manager variant if available.
 */

void XGPopControl::InitControl (void)
{
	XRect r;

	fControl = NULL;

	if (IsVisible()) {
		r = GetContentRect();
		ViewToGlobal(&r);
	} else {
		r.left = -20,
		r.top = -20,
		r.right = -10,
		r.bottom = -10;
	}
	if (!_GAppearance) return;
	
/*
 * #warning I inserted the kControlPopupFixedWidthVariant into here; how to deal???
 * maybe use an additional bool in the ArgStream that indicates whether or not to
 * size the control to fit.
 */

	Rect mr = r.MacRect();
	fControl = ::NewControl(GetRootWindow(),
							&mr,
							"\pP",
							false,
							popupTitleLeftJust,
							-12345,
							0,
							kControlPopupButtonProc | kControlPopupUseWFontVariant |
								kControlPopupFixedWidthVariant,
							GetViewID());
	if (!fControl) return;

	MenuHandle menu = NewMenu(0x4000 + GetViewID(), "\pP");
	(**(PopupPrivateDataHandle)((**fControl).contrlData)).mHandle = menu;
}

#endif

#if OPT_WINOS

/*$	XGPopControl::DoWInternal
 *
 *		Deal with the internal popup control events, translating them
 *	into YAAF events
 */

long XGPopControl::DoWInternal(long, void *parg)
{
	XGSWInternalRecord *r = (XGSWInternalRecord *)parg;
	long newval;
	
	switch (GETHIWORD(r->wp)) {
		case CBN_CLOSEUP:
			/*
			 *	This message is sent when the value of this control has been
			 *	changed.
			 */
			
			newval = SendMessage(fControl,CB_GETCURSEL,0,NULL);
			if (newval != fValue) {
				fValue = newval;
				DoDispatch(KXGPopValue,GetViewID(),NULL);
			}
			break;
		default:
			break;
	}
	return 0;
}

/*$	XGPopControl::ReloadItems
 *
 *		updates the menu contents
 */

void XGPopControl::ReloadItems (void)
{
	int ix;
	char buf[256];

	if (!fControl)
		return;

	SendMessage(fControl, CB_RESETCONTENT, 0L, 0L);
	for (ix = 0; ix < fList.GetNumStrings(); ix++) {
		fList.GetString(ix, buf);
		SendMessage(fControl, CB_ADDSTRING, 0L, (LPARAM)(LPCTSTR)buf);
	}
	SendMessage(fControl, CB_SETCURSEL, fValue, 0L);
}

/*$	XGPopControl::InitControl
 *
 *		Initialize the control
 */

void XGPopControl::InitControl (void)
{
	XRect r;
	
	r = GetContentRect();
	int forceHeight = 22;
	int realHeight = r.bottom - r.top;
	int diff = realHeight - forceHeight;
	if (diff) {
		bool left, top, right, bottom;
		GetAutoLocate( &r, &left, &top, &right, &bottom );
		r.top += diff >> 1;
		r.bottom -= diff - (diff >> 1);
		SetAutoLocate( r, left, top, right, bottom );
		r = GetContentRect();
	}
	ViewToGlobal(&r);
	
	fControl = ::CreateWindow("COMBOBOX",
							  "",
							  WS_CHILD | CBS_DROPDOWNLIST,
							  r.left,
							  r.top,
							  r.right - r.left,
							  r.bottom - r.top + 500,
							  GetRootWindow(),
							  NULL,
							  _GInstance,
							  NULL);
	
	::SetProp(fControl,_GControlClass,(HANDLE)(XGView *)this);
	if (fFont) {
		::SendMessage(fControl,WM_SETFONT,(WPARAM)(fFont->GetFont()),0L);
	}
}

#endif

