// ResizableComboBox.cpp : implementation file
//
/////////////////////////////////////////////////////////////////////////////
//
// Copyright (C) 2000-2004 by Paolo Messina
// (http://www.geocities.com/ppescher - ppescher@hotmail.com)
//
// The contents of this file are subject to the Artistic License (the "License").
// You may not use this file except in compliance with the License. 
// You may obtain a copy of the License at:
// http://www.opensource.org/licenses/artistic-license.html
//
// If you find this code useful, credits would be nice!
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "ResizableComboBox.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

/////////////////////////////////////////////////////////////////////////////
// CResizableComboBox

CResizableComboBox::CResizableComboBox()
{
	m_bClipMaxHeight = TRUE;
	m_bIntegralHeight = TRUE;
}

CResizableComboBox::~CResizableComboBox()
{
	if (m_ctrlListBox.GetSafeHwnd() != NULL)
		m_ctrlListBox.UnsubclassWindow();
}


BEGIN_MESSAGE_MAP(CResizableComboBox, CComboBox)
	//{{AFX_MSG_MAP(CResizableComboBox)
	ON_WM_CTLCOLOR()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CResizableComboBox message handlers

HBRUSH CResizableComboBox::OnCtlColor(CDC* pDC, CWnd* pWnd, UINT nCtlColor) 
{
	HBRUSH hbr = CComboBox::OnCtlColor(pDC, pWnd, nCtlColor);
	
	if (nCtlColor == CTLCOLOR_LISTBOX)
	{
		if (!(GetStyle() & CBS_SIMPLE)
			&& (m_ctrlListBox.m_hWnd == NULL))
		{
			TRACE("ComboLBox: 0x%08X\n", pWnd->m_hWnd);

			// attach to the owned listbox
			m_ctrlListBox.m_pOwnerCombo = this;
			m_ctrlListBox.SubclassWindow(pWnd->m_hWnd);
		}
	}

	return hbr;
}

LRESULT CResizableComboBox::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) 
{
	switch (message)
	{
	case CB_GETDROPPEDCONTROLRECT:
		*(LPRECT)lParam = m_rectDropDown;
		MapWindowPoints(NULL, (LPRECT)lParam);
		return TRUE;
	}

	LRESULT lResult = CComboBox::WindowProc(message, wParam, lParam);

	// if listbox is attached, update horizontal extent

	switch (message)
	{
	case CB_INSERTSTRING:
	case CB_ADDSTRING:
		if (lResult != CB_ERR && lResult != CB_ERRSPACE)
			UpdateHorizontalExtent((LPCTSTR)lParam);
		break;

	case CB_DIR:
		if (lResult != CB_ERR && lResult != CB_ERRSPACE)
			InitHorizontalExtent();
		break;

	case CB_RESETCONTENT:
		InitHorizontalExtent();
		break;
	}

	return lResult;
}

void CResizableComboBox::InitHorizontalExtent()
{
	CClientDC dc(this);
	CFont* pOldFont = dc.SelectObject(GetFont());

	CString str;
	
	m_iExtent = 0;
	int n = GetCount();
	for (int i=0; i<n; i++)
	{
		GetLBText(i, str);
		int cx = dc.GetTextExtent(str).cx;
		if (cx > m_iExtent)
			m_iExtent = cx;
	}

	SetHorizontalExtent(m_iExtent
		+ LOWORD(GetDialogBaseUnits()));

	dc.SelectObject(pOldFont);
}

void CResizableComboBox::UpdateHorizontalExtent(LPCTSTR szText)
{
	CClientDC dc(this);
	CFont* pOldFont = dc.SelectObject(GetFont());

	int cx = dc.GetTextExtent(szText, lstrlen(szText)).cx;
	if (cx > m_iExtent)
	{
		m_iExtent = cx;

		SetHorizontalExtent(m_iExtent
			+ LOWORD(GetDialogBaseUnits()));
	}

	dc.SelectObject(pOldFont);
}

void CResizableComboBox::PreSubclassWindow() 
{
	ASSERT(GetStyle() & CBS_NOINTEGRALHEIGHT);

	InitHorizontalExtent();
	
	GetDroppedControlRect(&m_rectDropDown);
	::MapWindowPoints(NULL, GetSafeHwnd(),
		(LPPOINT)&m_rectDropDown, 2);
	
	CComboBox::PreSubclassWindow();
}

int CResizableComboBox::MakeIntegralHeight(const int height)
{
	int inth = height;	// integral height (result)
	int availh = height;	// available height
	int n = GetCount();

	DWORD dwStyle = GetStyle();

	if (!m_bIntegralHeight || n == 0)
		return inth;
	
	if (dwStyle & CBS_OWNERDRAWVARIABLE)
	{
		inth = 0;	// try to reach availh by integral steps
		int i = 0;
		// use items below the first visible
		for (i=GetTopIndex(); availh>0 && i<n; i++)
		{
			int h = GetItemHeight(i);
			if (h == CB_ERR)
				break;

			inth += h;
			availh -= h;
		}
		// to fill the remaining height, use items above
		for (i=GetTopIndex()-1; availh>0 && i>=0; i--)
		{
			int h = GetItemHeight(i);
			if (h == CB_ERR)
				break;

			inth += h;
			availh -= h;
		}
		// scroll into view
		SetTopIndex(i);

		if (!m_bClipMaxHeight) // it can be higher than all the items
		{
			// to fill the remaining height, use last item
			int h = GetItemHeight(n-1);
			if (h != CB_ERR)
			{
				inth += availh - availh % h;
			}
		}
	}
	else
	{
		// every item has the same height (take the first)
		int h = GetItemHeight(0);
		if (h != CB_ERR && n != CB_ERR)
		{
			int rows = availh / h;
			// can't be higher than all the items
			if (m_bClipMaxHeight && rows > n)
				rows = n;
			inth = rows * h;
			// scroll into view
			if (n - rows < GetTopIndex())
				SetTopIndex(n-rows);
		}
	}

	return inth;
}
