//-----------------------------------------------------------------------------
// File: flexcombobox.cpp
//
// Desc: Implements a combo box control similar to Windows combo box.
//       CFlexComboBox is derived from CFlexWnd.  It is used by the page
//       for player list and genre list.  When the combo box is open,
//       CFlexComboBox uses a CFlexListBox for the list window.
//
// Copyright (C) 1999-2000 Microsoft Corporation. All Rights Reserved.
//-----------------------------------------------------------------------------

#include "common.hpp"


CFlexComboBox::CFlexComboBox() :
	m_nTextHeight(-1),
	m_hWndNotify(NULL),
	m_rgbText(RGB(255,255,255)),
	m_rgbBk(RGB(0,0,0)),
	m_rgbSelText(RGB(0,0,255)),
	m_rgbSelBk(RGB(0,0,0)),
	m_rgbFill(RGB(0,0,255)),
	m_rgbLine(RGB(0,255,255)),
	m_dwFlags(0),
	m_dwListBoxFlags(0),
	m_bInSelMode(FALSE),
	m_nSBWidth(11),
	m_hFont(NULL),
	m_eCurState(FCBS_CLOSED),
	m_OldSel(-1)
{
}

CFlexComboBox::~CFlexComboBox()
{
}

CFlexComboBox *CreateFlexComboBox(FLEXCOMBOBOXCREATESTRUCT *pcs)
{
	CFlexComboBox *psb = new CFlexComboBox;
	
	if (psb && psb->Create(pcs))
		return psb;
	
	delete psb;
	return NULL;
}

BOOL CFlexComboBox::Create(FLEXCOMBOBOXCREATESTRUCT *pcs)
{
	if (this == NULL)
		return FALSE;

	if (pcs == NULL)
		return FALSE;

	if (pcs->dwSize != sizeof(FLEXCOMBOBOXCREATESTRUCT))
		return FALSE;

	m_hWndNotify = pcs->hWndNotify ? pcs->hWndNotify : pcs->hWndParent;

	m_dwFlags = pcs->dwFlags;
	m_dwListBoxFlags = pcs->dwListBoxFlags;

	SetFont(pcs->hFont);
	SetColors(pcs->rgbText, pcs->rgbBk, pcs->rgbSelText, pcs->rgbSelBk, pcs->rgbFill, pcs->rgbLine);
	m_nSBWidth = pcs->nSBWidth;
	m_rect = pcs->rect;

	if (!CFlexWnd::Create(pcs->hWndParent, GetRect(pcs->rect), pcs->bVisible))
		return FALSE;


	return TRUE;
}

void CFlexComboBox::OnPaint(HDC hDC)
{
	HDC hBDC = NULL, hODC = NULL;
	CBitmap *pbm = NULL;

	if (!InRenderMode())
	{
		hODC = hDC;
		pbm = CBitmap::Create(GetClientSize(), RGB(0,0,0), hDC);
		if (pbm != NULL)
		{
			hBDC = pbm->BeginPaintInto();
			if (hBDC != NULL)
			{
				hDC = hBDC;
			}
		}
	}

	InternalPaint(hDC);

	if (!InRenderMode())
	{
		if (pbm != NULL)
		{
			if (hBDC != NULL)
			{
				pbm->EndPaintInto(hBDC);
				pbm->Draw(hODC);
			}
			delete pbm;
		}
	}
}

void CFlexComboBox::SetSel(int i)
{
	m_ListBox.SelectAndShowSingleItem(i, TRUE);
}

int CFlexComboBox::GetSel()
{
	return m_ListBox.GetSel();
}

LPCTSTR CFlexComboBox::GetText()
{
	return m_ListBox.GetSelText();
}

void CFlexComboBox::InternalPaint(HDC hDC)
{
	HGDIOBJ hPen = (HGDIOBJ)CreatePen(PS_SOLID, 1, m_rgbLine);
	if (hPen != NULL)
	{
		HGDIOBJ	hOldPen = SelectObject(hDC, hPen);

		HGDIOBJ hBrush = (HGDIOBJ)CreateSolidBrush(m_rgbBk);
		if (hBrush != NULL)
		{
			HGDIOBJ hOldBrush = SelectObject(hDC, hBrush);

			RECT rect = {0,0,0,0};
			GetClientRect(&rect);
			Rectangle(hDC, rect.left, rect.top, rect.right, rect.bottom);

			RECT arect = rect;

			arect.left = arect.right - (arect.bottom - arect.top);

			// If we are read-only, only draw the text in gray. No border, no arrow.
			if (!GetReadOnly())
			{
				MoveToEx(hDC, arect.left, arect.top, NULL);
				LineTo(hDC, arect.left, arect.bottom);
			}

			rect.left++;
			rect.top++;
			rect.right = arect.left;
			rect.bottom--;

			SetTextColor(hDC, m_rgbText);
			SetBkMode(hDC, TRANSPARENT);

			LPTSTR lpText = (LPTSTR)GetText();
			if (lpText)
			{
				DrawText(hDC, lpText, -1, &rect, DT_NOPREFIX);
			}

			SelectObject(hDC, hOldBrush);
			DeleteObject(hBrush);

			if (!GetReadOnly())
			{
				hBrush = (HGDIOBJ)CreateSolidBrush(m_rgbFill);
				if (hBrush != NULL)
				{
					SelectObject(hDC, hBrush);

					InflateRect(&arect, -3, -3);
					DrawArrow(hDC, arect, TRUE, FALSE);

					SelectObject(hDC, hOldBrush);
					DeleteObject(hBrush);
				}
			}
		}

		SelectObject(hDC, hOldPen);
		DeleteObject(hPen);
	}
}

int CFlexComboBox::AddString(LPCTSTR str)
{
	return m_ListBox.AddString(str);
}

LRESULT CFlexComboBox::WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	RECT wrect = {-1, -1, -1, -1};
	POINT point = {-1, -1};
	BOOL bWithin = FALSE;

	switch (msg)
	{
		case WM_SIZE:
			Invalidate();
			SetRect();
			return 0;

		case WM_FLEXLISTBOX:
			assert(lParam == (LPARAM)(LPVOID)&m_ListBox);
			switch (wParam)
			{
				case FLBN_FINALSEL:
					StateEvent(FCBSE_UPLIST);
					break;
				case FLBN_CANCEL:
					StateEvent(FCBSE_DOWNOFF);
					break;
			}
			return 0;

		// make sure flexwnd doesn't do ANYTHING with our mouse messages
		case WM_MOUSEMOVE:
			// We initialize the tooltip to current selection text if the selected text is too long to fit.
			RECT rect;
			GetClientRect(&rect);
			rect.right = rect.right - (rect.bottom - rect.top);
			rect.left++;
			rect.top++;
			rect.bottom--;
			RECT ResultRect;
			ResultRect = rect;
			HDC hDC;
			hDC = CreateCompatibleDC(NULL);
			if (hDC)
			{
				LPTSTR lpText = (LPTSTR)GetText();
				if (lpText)
				{
					DrawText(hDC, lpText, -1, &ResultRect, DT_NOPREFIX|DT_CALCRECT);
				}
				DeleteDC(hDC);
			}
			if (rect.right < ResultRect.right || rect.bottom < ResultRect.bottom)
			{
				CFlexWnd::s_ToolTip.SetToolTipParent(GetParent(m_hWnd));
				TOOLTIPINITPARAM ttip;
				ttip.hWndParent = GetParent(m_hWnd);
				ttip.iSBWidth = 0;
				ttip.dwID = 0;
				ttip.hWndNotify = m_hWnd;
				ttip.tszCaption = m_ListBox.GetSelText();
				CFlexToolTip::UpdateToolTipParam(ttip);
				Notify(FCBN_MOUSEOVER);
			}

		case WM_LBUTTONUP:
		case WM_LBUTTONDOWN:
		case WM_RBUTTONUP:
		case WM_RBUTTONDOWN:
		case WM_LBUTTONDBLCLK:
		case WM_RBUTTONDBLCLK:
			GetClientRect(&wrect);
			point.x = int(LOWORD(lParam));
			point.y = int(HIWORD(lParam));
			bWithin = PtInRect(&wrect, point);
			break;
		case WM_TIMER:
		case WM_CAPTURECHANGED:
			break;
		default:
			return CFlexWnd::WndProc(hWnd, msg, wParam, lParam);
	}

	switch (msg)
	{
		case WM_LBUTTONDOWN:
			if (!GetReadOnly())
				StateEvent(bWithin ? FCBSE_DOWN : FCBSE_DOWNOFF);
			break;

		case WM_LBUTTONUP:
			if (!GetReadOnly())
				StateEvent(bWithin ? FCBSE_UPBOX : FCBSE_UPOFF);
			break;
	}

	return 0;
}

RECT CFlexComboBox::GetListBoxRect()
{
	HWND hParent = GetParent(m_hWnd);
	RECT rect;
	GetClientRect(&rect);
	BOOL bRet = ClientToScreen(m_hWnd, &rect);
	BOOL bRet2 = ScreenToClient(hParent, &rect);

	RECT lrect = m_rect;
	lrect.top = rect.bottom;
	lrect.right -= 12;  // UNDONE: remove this line when the clipping is working properly (scroll bars don't appear above other windows)

	return lrect;
}

void CFlexComboBox::DoSel()
{
	if (m_bInSelMode)
		return;
	
	if (m_hWnd == NULL)
		return;

	FLEXLISTBOXCREATESTRUCT cs;
	cs.dwSize = sizeof(FLEXLISTBOXCREATESTRUCT);
	cs.dwFlags = m_dwListBoxFlags;
	cs.hWndParent = GetParent(m_hWnd);
	cs.hWndNotify = m_hWnd;
	cs.bVisible = FALSE;
	cs.rect = GetListBoxRect();
	cs.hFont = m_hFont;
	cs.rgbText = m_rgbText;
	cs.rgbBk = m_rgbBk;
	cs.rgbSelText = m_rgbSelText;
	cs.rgbSelBk = m_rgbSelBk;
	cs.rgbFill = m_rgbFill;
	cs.rgbLine = m_rgbLine;
	cs.nSBWidth = m_nSBWidth;

	m_OldSel = m_ListBox.GetSel();

	m_bInSelMode = m_ListBox.Create(&cs);
	if (m_bInSelMode)
		SetWindowPos(m_ListBox.m_hWnd, HWND_TOP, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_SHOWWINDOW);
}

void CFlexComboBox::Notify(int code)
{
	if (!m_hWndNotify)
		return;

	SendMessage(m_hWndNotify, WM_FLEXCOMBOBOX,
		(WPARAM)code, (LPARAM)(LPVOID)this);
}

RECT CFlexComboBox::GetRect(const RECT &rect)
{
	int h = GetTextHeight(m_hFont);
	RECT ret = {rect.left, rect.top, rect.right, rect.top + h + 2};
	return ret;
}

RECT CFlexComboBox::GetRect()
{
	RECT rect;
	GetClientRect(&rect);
	return GetRect(rect);
}

void CFlexComboBox::SetFont(HFONT hFont)
{
	m_hFont = hFont;

	if (m_hWnd == NULL)
		return;

	Invalidate();
	SetRect();
}

void CFlexComboBox::SetRect()
{
	if (m_hWnd == NULL)
		return;

	RECT rect = GetRect();
	SetWindowPos(m_hWnd, NULL, rect.left, rect.top, rect.right, rect.bottom, SWP_NOZORDER | SWP_NOMOVE);
}

void CFlexComboBox::SetColors(COLORREF text, COLORREF bk, COLORREF seltext, COLORREF selbk, COLORREF fill, COLORREF line)
{
	m_rgbText = text;
	m_rgbBk = bk;
	m_rgbSelText = seltext;
	m_rgbSelBk = selbk;
	m_rgbFill = fill;
	m_rgbLine = line;
	Invalidate();
}

void CFlexComboBox::StateEvent(FCBSTATEEVENT e)
{
	if (e == FCBSE_DOWNOFF)
	{
		SetState(FCBS_CANCEL);
		return;
	}

	switch (m_eCurState)
	{
		case FCBS_CLOSED:
			if (e == FCBSE_DOWN)
				SetState(FCBS_OPENDOWN);
			break;

		case FCBS_OPENDOWN:
			switch (e)
			{
				case FCBSE_UPLIST:
					SetState(FCBS_SELECT);
					break;
				case FCBSE_UPBOX:
					SetState(FCBS_OPENUP);
					break;
				case FCBSE_UPOFF:
					SetState(FCBS_CANCEL);
					break;
			}

		case FCBS_OPENUP:
			if (e == FCBSE_DOWN)
				SetState(FCBS_OPENDOWN);
			break;

		default:
			assert(0);
			return;
	}
}

void CFlexComboBox::SetState(FCBSTATE s)
{
	FCBSTATE eOldState = m_eCurState;
	m_eCurState = s;

	switch (s)
	{
		case FCBS_OPENUP:
			if (eOldState == FCBS_CLOSED)
				DoSel();
			return;

		case FCBS_OPENDOWN:
			if (eOldState == FCBS_CLOSED)
				DoSel();
			m_ListBox.StartSel();
			return;

		case FCBS_CANCEL:
			m_ListBox.SetSel(m_OldSel);
			CFlexWnd::s_ToolTip.SetEnable(FALSE);
			SetState(FCBS_CLOSED);
			return;

		case FCBS_SELECT:
			CFlexWnd::s_ToolTip.SetEnable(FALSE);
			Invalidate();
			Notify(FCBN_SELCHANGE);
			SetState(FCBS_CLOSED);
			return;

		case FCBS_CLOSED:
			if (eOldState != FCBS_CLOSED)
				m_ListBox.Destroy();
			m_bInSelMode = FALSE;
			Invalidate();
			return;
	}
}
