#include "stdafx.h"
#include "apl_gui.h"

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

static WNDPROC m_pWndProc = 0;
static CaplCheckComboBox *m_pComboBox = 0;


BEGIN_MESSAGE_MAP(CaplCheckComboBox, CComboBox)
	//{{AFX_MSG_MAP(CaplCheckComboBox)
	ON_MESSAGE(WM_CTLCOLORLISTBOX, OnCtlColorListBox)
	ON_MESSAGE(WM_GETTEXT, OnGetText)
	ON_MESSAGE(WM_GETTEXTLENGTH, OnGetTextLength)
	ON_WM_LBUTTONDOWN()
	ON_WM_KILLFOCUS()
	ON_WM_SIZE()
	ON_CONTROL_REFLECT(CBN_DROPDOWN, OnDropDown)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

void CaplCheckComboBox::SetDropDownHeight()
{
	//Get rectangles
	CRect rctDropDown;
	//DropDownList rect
	GetDroppedControlRect(&rctDropDown); 		
	//Converts coordinates
	GetParent()->ScreenToClient(&rctDropDown); 
	//Set height
	rctDropDown.bottom = rctDropDown.top + m_rctDropDownHeight; 
	//apply changes
	MoveWindow(&rctDropDown); 	
}

//
// The subclassed COMBOLBOX message handler
//
extern "C" LRESULT FAR PASCAL ComboBoxListBoxProc(HWND hWnd, UINT nMsg, WPARAM wParam, LPARAM lParam)
{

	switch (nMsg) {


		case WM_RBUTTONDOWN: {
			// If you want to select all/unselect all using the
			// right button, remove this ifdef. Personally, I don't really like it
#if FALSE

			if (m_pComboBox != 0) {
				INT nCount = m_pComboBox->GetCount();
				INT nSelCount = 0;

				for (INT i = 0; i < nCount; i++) {
					if (m_pComboBox->GetCheck(i))
						nSelCount++;
				}


				m_pComboBox->SelectAll(nSelCount != nCount);

				// Make sure to invalidate this window as well
				InvalidateRect(hWnd, 0, FALSE);
				m_pComboBox->GetParent()->SendMessage(WM_COMMAND, MAKELONG(GetWindowLong(m_pComboBox->m_hWnd, GWL_ID), CBN_SELCHANGE), (LPARAM)m_pComboBox->m_hWnd);

			}
#endif

			break;
							 }

							 // Make the combobox always return -1 as the current selection. This
							 // causes the lpDrawItemStruct->itemID in DrawItem() to be -1
							 // when the always-visible-portion of the combo is drawn
		case LB_GETCURSEL: {
			return -1;
						   }


		case WM_CHAR: {
			if (wParam == VK_SPACE) {
				// Get the current selection
				INT nIndex = CallWindowProcA(m_pWndProc, hWnd, LB_GETCURSEL, wParam, lParam);

				CRect rcItem;
				SendMessage(hWnd, LB_GETITEMRECT, nIndex, (LONG)(VOID *)&rcItem);
				InvalidateRect(hWnd, rcItem, FALSE);

				// Invert the check mark
				m_pComboBox->SetCheck(nIndex, !m_pComboBox->GetCheck(nIndex));

				// Notify that selection has changed
				m_pComboBox->GetParent()->SendMessage(WM_COMMAND, MAKELONG(GetWindowLong(m_pComboBox->m_hWnd, GWL_ID), CBN_SELCHANGE), (LPARAM)m_pComboBox->m_hWnd);
				return 0;
			}

			break;
					  }


		case WM_LBUTTONDOWN: {

			CRect rcClient;
			GetClientRect(hWnd, rcClient);

			CPoint pt;
			pt.x = LOWORD(lParam);
			pt.y = HIWORD(lParam);


			if (PtInRect(rcClient, pt)) {
				INT nItemHeight = SendMessage(hWnd, LB_GETITEMHEIGHT, 0, 0);
				INT nTopIndex   = SendMessage(hWnd, LB_GETTOPINDEX, 0, 0);

				// Compute which index to check/uncheck
				INT nIndex = nTopIndex + pt.y / nItemHeight;

				CRect rcItem;
				SendMessage(hWnd, LB_GETITEMRECT, nIndex, (LONG)(VOID *)&rcItem);

				if (PtInRect(rcItem, pt)) {
					// Invalidate this window
					InvalidateRect(hWnd, rcItem, FALSE);

					if(!m_pComboBox->GetUseSpecialFirstString())
					{
						m_pComboBox->SetCheck(nIndex, !m_pComboBox->GetCheck(nIndex));
					}
					else 
					{
						if(nIndex == 0)
						{
							BOOL check = m_pComboBox->GetCheck(nIndex);
							if(check) 
							{
								m_pComboBox->ShowItemsCheckBoxes();
								m_pComboBox->SetCheck(nIndex, false);
							}
							else 
							{
								m_pComboBox->HideItemsCheckBoxes();
								m_pComboBox->SetCheck(nIndex, true);
							}

							m_pComboBox->Invalidate(FALSE);
							InvalidateRect(hWnd, rcItem, FALSE);
							m_pComboBox->ShowDropDown();
							m_pComboBox->InvalidateList();
						}
						else 
						{
							// ЯАИ: По просьбе Созвездия, чтобы не надо было сначала снимать галочку "Все"
							if(m_pComboBox->m_bHideItemsCheckBoxes)
							{
								m_pComboBox->ShowItemsCheckBoxes();
								m_pComboBox->SetCheck(0, false);

							}

							if(!m_pComboBox->m_bHideItemsCheckBoxes)
							{
								CString str; m_pComboBox->GetLBText(nIndex, str);
								if(0 != m_pComboBox->m_sUnselectable.CompareNoCase(str))
									m_pComboBox->SetCheck(nIndex, !m_pComboBox->GetCheck(nIndex));
							}
							m_pComboBox->InvalidateList();

						}
					}
					

					// Notify that selection has changed
					m_pComboBox->GetParent()->SendMessage(WM_COMMAND, MAKELONG(GetWindowLong(m_pComboBox->m_hWnd, GWL_ID), CBN_SELCHANGE), (LPARAM)m_pComboBox->m_hWnd);


				}
			}

			// Do the default handling now (such as close the popup
			// window when clicked outside)
			break;
							 }

		case WM_LBUTTONUP: {
			// Don't do anything here. This causes the combobox popup
			// windows to remain open after a selection has been made
			return 0;
						   }		
		case WM_SIZE: {
			// Don't do anything here. This causes the combobox popup
			// windows to remain open after a selection has been made			
			break;
						   }	
	}

	return CallWindowProc(m_pWndProc, hWnd, nMsg, wParam, lParam);
}





CaplCheckComboBox::CaplCheckComboBox()
{
	m_hListBox       = 0;	
	m_bTextUpdated   = FALSE;
	m_bItemHeightSet = FALSE;

	m_bUseSpecialFirstString = false;
	m_bHideItemsCheckBoxes = false;

	m_rctDropDownHeight = 0;
}


CaplCheckComboBox::~CaplCheckComboBox()
{
}

void CaplCheckComboBox::GetDefaultDropRectHeight()
{
	CRect rctDropDown;
	GetDroppedControlRect(&rctDropDown); 
	m_rctDropDownHeight = rctDropDown.Height();
};


BOOL CaplCheckComboBox::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{

	// Remove the CBS_SIMPLE and CBS_DROPDOWN styles and add the one I'm designed for
	dwStyle &= ~0xF;
	dwStyle |= CBS_DROPDOWNLIST;

	// Make sure to use the CBS_OWNERDRAWVARIABLE style
	dwStyle |= CBS_OWNERDRAWVARIABLE;

	// Use default strings. We need the itemdata to store checkmarks
	dwStyle |= CBS_HASSTRINGS;

	BOOL bRes = CComboBox::Create(dwStyle, rect, pParentWnd, nID);	

	return bRes;
}

void CaplCheckComboBox::InvalidateList()
{
	CWnd* pWnd = CWnd::FromHandle(m_hListBox);
	if(pWnd) pWnd->Invalidate();
}

LRESULT CaplCheckComboBox::OnCtlColorListBox(WPARAM wParam, LPARAM lParam) 
{
	// If the listbox hasn't been subclassed yet, do so...
	if (m_hListBox == 0) {
		HWND hWnd = (HWND)lParam;

		if (hWnd != 0 && hWnd != m_hWnd) {
			// Save the listbox handle
			m_hListBox = hWnd;

			// Do the subclassing
			m_pWndProc = (WNDPROC)GetWindowLong(m_hListBox, GWL_WNDPROC);
			SetWindowLong(m_hListBox, GWL_WNDPROC, (LONG)ComboBoxListBoxProc);
		}
	}


	return DefWindowProc(WM_CTLCOLORLISTBOX, wParam, lParam);
}

void CaplCheckComboBox::SelectAllCategoriesByFirstString()
{
	if(GetCount() > 0)
	{
		HideItemsCheckBoxes();
		SetCheck(0, TRUE);
		SetCurSel(0);
	}

	GetDefaultDropRectHeight();
}

void CaplCheckComboBox::UnSelectAllCategoriesByFirstString()
{
	if(GetCount() > 0)
	{
		ShowItemsCheckBoxes();		
		SetCheck(0, FALSE);
	}
}

void CaplCheckComboBox::DrawItem(LPDRAWITEMSTRUCT lpDrawItemStruct) 
{
	HDC dc = lpDrawItemStruct->hDC;
	
	CRect rcBitmap = lpDrawItemStruct->rcItem;
	CRect rcText   = lpDrawItemStruct->rcItem;

	CString strText;

	// 0 - No check, 1 - Empty check, 2 - Checked
	INT nCheck = 0;

	// Check if we are drawing the static portion of the combobox
	if ((LONG)lpDrawItemStruct->itemID < 0) {
		// Make sure the m_strText member is updated
		RecalcText();

		// Get the text
		strText = m_strText;

		// Don't draw any boxes on this item
		nCheck = 0;
	}

	// Otherwise it is one of the items
	else {
		GetLBText(lpDrawItemStruct->itemID, strText);
		nCheck = 1 + (GetCheck(lpDrawItemStruct->itemID) != 0);

		TEXTMETRIC metrics;
		GetTextMetrics(dc, &metrics);

		rcBitmap.left    = 0;
		rcBitmap.right   = rcBitmap.left + metrics.tmHeight + metrics.tmExternalLeading + 6;
		rcBitmap.top    += 1;
		rcBitmap.bottom -= 1;

		rcText.left = rcBitmap.right;
	}

	if(strText == m_sUnselectable) 
		nCheck = 0;
	if(m_bHideItemsCheckBoxes && strText != m_sAllCategories) 
		nCheck = 0;

	if (nCheck > 0) {
		SetBkColor(dc, GetSysColor(COLOR_WINDOW));
		SetTextColor(dc, GetSysColor(COLOR_WINDOWTEXT));

		UINT nState = DFCS_BUTTONCHECK;

		if (nCheck > 1)
			nState |= DFCS_CHECKED;

		// Draw the checkmark using DrawFrameControl
		DrawFrameControl(dc, rcBitmap, DFC_BUTTON, nState);
	}

	if (lpDrawItemStruct->itemState & ODS_SELECTED) {
		SetBkColor(dc, GetSysColor(COLOR_HIGHLIGHT));
		SetTextColor(dc, GetSysColor(COLOR_HIGHLIGHTTEXT));
	}
	else {
		SetBkColor(dc, GetSysColor(COLOR_WINDOW));
		SetTextColor(dc, GetSysColor(COLOR_WINDOWTEXT));
	}

	// Erase and draw
	ExtTextOut(dc, 0, 0, ETO_OPAQUE, &rcText, 0, 0, 0);
	DrawText(dc, _T(' ') + strText, strText.GetLength() + 1, &rcText, DT_SINGLELINE|DT_VCENTER|DT_END_ELLIPSIS);

	if ((lpDrawItemStruct->itemState & (ODS_FOCUS|ODS_SELECTED)) == (ODS_FOCUS|ODS_SELECTED))
		DrawFocusRect(dc, &rcText);

}

void CaplCheckComboBox::HideItemsCheckBoxes()
{
	m_bHideItemsCheckBoxes = true;
	for(int i=0; i<GetCount(); i++) SetCheck(i, false);
}

void CaplCheckComboBox::ShowItemsCheckBoxes()
{
	m_bHideItemsCheckBoxes = false;
}

void CaplCheckComboBox::MeasureItem(LPMEASUREITEMSTRUCT lpMeasureItemStruct) 
{
	CClientDC dc(this);
	CFont *pFont = dc.SelectObject(GetFont());

	if (pFont != 0) {

		TEXTMETRIC metrics;
		dc.GetTextMetrics(&metrics);

		lpMeasureItemStruct->itemHeight = metrics.tmHeight + metrics.tmExternalLeading;

		// An extra height of 2 looks good I think. 
		// Otherwise the list looks a bit crowded...
		lpMeasureItemStruct->itemHeight += 2;


		// This is needed since the WM_MEASUREITEM message is sent before
		// MFC hooks everything up if used in i dialog. So adjust the
		// static portion of the combo box now
		if (!m_bItemHeightSet) {
			m_bItemHeightSet = TRUE;
			SetItemHeight(-1, lpMeasureItemStruct->itemHeight);
		}

		dc.SelectObject(pFont);
	}
}


//
// Make sure the combobox window handle is updated since
// there may be many CaplCheckComboBox windows active
//
void CaplCheckComboBox::OnDropDown() 
{
	m_pComboBox = this;		
}

//
// Selects/unselects all items in the list
//
void CaplCheckComboBox::SelectAll(BOOL bCheck)
{
	INT nCount = GetCount();

	for (INT i = 0; i < nCount; i++)
		SetCheck(i, bCheck);

}


//
// By adding this message handler, we may use CWnd::GetText()
//
LRESULT CaplCheckComboBox::OnGetText(WPARAM wParam, LPARAM lParam)
{
	// Make sure the text is updated
	RecalcText();

	if (lParam == 0)
		return 0;

	// Copy the 'fake' window text
	_strncpy((LPTSTR)lParam, (LPCTSTR)m_strText, (int)wParam);
	//lstrcpyn((LPSTR)lParam, m_strText, (INT)wParam);
	return m_strText.GetLength();
}


//
// By adding this message handler, we may use CWnd::GetTextLength()
//
LRESULT CaplCheckComboBox::OnGetTextLength(WPARAM, LPARAM)
{
	// Make sure the text is updated
	RecalcText();
	return m_strText.GetLength();
}


//
// This routine steps thru all the items and builds
// a string containing the checked items
//
void CaplCheckComboBox::RecalcText()
{
	if (!m_bTextUpdated) {
		CString strText;

		// Get the list count
		INT nCount    = GetCount();

		// Get the list separator
		TCHAR szBuffer[10] = {0};
		GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SLIST, szBuffer, sizeof(szBuffer));

		CString strSeparator = szBuffer;

		// If none found, the the ';'
		if (strSeparator.GetLength() == 0)
			strSeparator = _T(';');

		// Trim extra spaces
		strSeparator.TrimRight();

		// And one...
		strSeparator += _T(' ');

		for (INT i = 0; i < nCount; i++) {

			if (GetCheck(i)) {
				CString strItem;
				GetLBText(i, strItem);

				if (!strText.IsEmpty())
					strText += strSeparator;

				strText += strItem;
			}
		}

		// Set the text
		m_strText = strText;

		m_bTextUpdated = TRUE;
	}
}

INT CaplCheckComboBox::SetCheck(INT nIndex, BOOL bFlag)
{
	INT nResult = SetItemData(nIndex, bFlag);

	if (nResult < 0)
		return nResult;

	// Signal that the text need updating
	m_bTextUpdated = FALSE;

	// Redraw the window
	Invalidate(FALSE);

	return nResult;
}

BOOL CaplCheckComboBox::GetCheck(INT nIndex)
{
	return GetItemData(nIndex);
}

void CaplCheckComboBox::OnLButtonDown(UINT nFlags, CPoint point)
{
	CComboBox::OnLButtonDown(nFlags, point);
}

void CaplCheckComboBox::OnKillFocus(CWnd* pNewWnd)
{
	CComboBox::OnKillFocus(pNewWnd);
}

void CaplCheckComboBox::OnSize(UINT nType, int cx, int cy)
{
	__super::OnSize(nType, cx, cy);
}
