// ==========================================================================
// 			Class Implementation : COXCalendarEdit, COXMonthCalPopup
// ==========================================================================

// Source file : OXCalendarEdit.cpp

// Version: 9.3

// This software along with its related components, documentation and files ("The Libraries")
// is © 1994-2007 The Code Project (1612916 Ontario Limited) and use of The Libraries is
// governed by a software license agreement ("Agreement").  Copies of the Agreement are
// available at The Code Project (www.codeproject.com), as part of the package you downloaded
// to obtain this file, or directly from our office.  For a copy of the license governing
// this software, you may contact us at legalaffairs@codeproject.com, or by calling 416-849-8900.   

#include "stdafx.h"
#include "OXCalendarEdit.h"

#pragma warning(disable : 4706)
#include <multimon.h>

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


/////////////////////////////////////////////////////////////////////////////
// COXMonthCalPopup

COXMonthCalPopup::COXMonthCalPopup()
{
}

COXMonthCalPopup::~COXMonthCalPopup()
{
}


BEGIN_MESSAGE_MAP(COXMonthCalPopup, CMonthCalCtrl)
	//{{AFX_MSG_MAP(COXMonthCalPopup)
		// NOTE - the ClassWizard will add and remove mapping macros here.
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

BOOL COXMonthCalPopup::Pick(CRect rect, CRect rectParent, COleDateTime dtInitialDate, COleDateTime& dtPickedDate)
{
	AdjustDisplayRectangle(rect, rectParent);

	SetCurSel(dtInitialDate);

	MoveWindow(rect);
	ShowWindow(SW_SHOW);
	SetCapture();

	// init message loop
	bool bBreak = false;
	BOOL bValidDatePicked = FALSE;
	while (!bBreak)
	{
		MSG msg;
		VERIFY(::GetMessage(&msg, NULL, 0, 0));


		if (msg.message == WM_LBUTTONDOWN)
		{
			DispatchMessage(&msg);
			SetCapture();
		}
		else if (msg.message == WM_LBUTTONUP)
		{
			DispatchMessage(&msg);

			MCHITTESTINFO hti;
			::memset(&hti, 0, sizeof(hti));
			hti.pt.x = GET_X_LPARAM(msg.lParam);
			hti.pt.y = GET_Y_LPARAM(msg.lParam);
			hti.cbSize = sizeof(hti);
			HitTest(&hti);
			DWORD dwResult = HitTest(&hti);
			if (dwResult == MCHT_CALENDARDATE)
			{
				// The user clicked on a date, so save and close
				SYSTEMTIME stOut;
				GetCurSel(&stOut);
				stOut.wHour = 0;
				stOut.wMinute = 0;
				stOut.wSecond = 0;
				stOut.wMilliseconds = 0;
		
				dtPickedDate = COleDateTime(stOut);
				bValidDatePicked = TRUE;
				bBreak = true;
			}
			else if (dwResult == MCHT_NOWHERE)
			{
				// The user clicked outside
				bBreak = true;
			}

			SetCapture();
		}
		else if (msg.message == WM_KEYDOWN)
		{
			// Handle ESCAPE and ENTER
			if (msg.wParam == VK_ESCAPE)
				bBreak = true;
			else if (msg.wParam == VK_RETURN)
			{
				DispatchMessage(&msg);

				SYSTEMTIME stOut;
				GetCurSel(&stOut);
				stOut.wHour = 0;
				stOut.wMinute = 0;
				stOut.wSecond = 0;
				stOut.wMilliseconds = 0;
		
				dtPickedDate = COleDateTime(stOut);
				bValidDatePicked = TRUE;
				bBreak = true;
			}
			else
				DispatchMessage(&msg);
		}
		else
		{
			DispatchMessage(&msg);
		}
	}

	ReleaseCapture();
	ShowWindow(SW_HIDE);

	return bValidDatePicked;
}


/////////////////////////////////////////////////////////////////////////////
// COXCalendarEdit

IMPLEMENT_DYNCREATE(COXCalendarEdit, COXMaskedEdit)

COXCalendarEdit::COXCalendarEdit()
{
	m_strDateFormat = _T("");
}

COXCalendarEdit::COXCalendarEdit(LPCTSTR lpszDateFormat)
{
	SetDateFormat(lpszDateFormat);
}

COXCalendarEdit::~COXCalendarEdit()
{
}


BEGIN_MESSAGE_MAP(COXCalendarEdit, COXMaskedEdit)
	//{{AFX_MSG_MAP(COXCalendarEdit)
		ON_WM_KILLFOCUS()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// COXCalendarEdit message handlers

BOOL COXCalendarEdit::InitializeDropEdit()
{
	if(!m_Calendar.CreateEx(WS_EX_TOOLWINDOW | WS_EX_TOPMOST, _T("SysMonthCal32"),_T(""),
		WS_POPUP | WS_BORDER, CRect(0, 0, 0, 0), this, 0, NULL))
	{
		TRACE(_T("Unable to create drop list.\n"));
		AfxThrowResourceException();
	}
	m_Calendar.SetOwner(this);

	return TRUE;
}

void COXCalendarEdit::OnDropButton()
{
	CRect rect;
	GetClientRect(rect);
	ClientToScreen(rect);
	rect.right += GetButtonWidth();
	CRect rectParent = rect;
	rect.top = rect.bottom;
	rect.left = rect.right;

	// Get the size of the calendar control
	RECT rectSize;
	m_Calendar.GetMinReqRect(&rectSize);
	rect.left -= rectSize.right;
	rect.bottom += rectSize.bottom;
	
	// Read the initial date
	CString strText;
	GetWindowText(strText);
	COleDateTime dtInitialDate = GetDateFromString(strText);
	if (dtInitialDate.GetStatus() != COleDateTime::valid)
		dtInitialDate = COleDateTime::GetCurrentTime();

	// Display the calendar popup window
	COleDateTime dtPickedDate;
	BOOL bDatePicked = m_Calendar.Pick(rect, rectParent, dtInitialDate, dtPickedDate);
	if (bDatePicked)
		SetWindowText(GetStringFromDate(dtPickedDate));
}

CString COXCalendarEdit::GetErrorString()
{
	return _T("<bad date>");
}

void COXCalendarEdit::SetDateFormat(LPCTSTR lpszDateFormat)
{
	m_strDateFormat = lpszDateFormat;
	m_strDateFormat.MakeUpper();

	// Set the masked edit format
	CString strMaskedEditFormat(m_strDateFormat);
	strMaskedEditFormat.Replace('D', '#');
	strMaskedEditFormat.Replace('M', '#');
	strMaskedEditFormat.Replace('Y', '#');
	SetMask(strMaskedEditFormat);
}

COleDateTime COXCalendarEdit::GetDateFromString(LPCTSTR lpszDateString)
{
	CString strDate = lpszDateString;

	// First get the year

	// 1. Find where the year starts and where it ends
	int iYearStartIdx = m_strDateFormat.Find('Y');
	int iYearEndIdx = m_strDateFormat.ReverseFind('Y');
	CString strYearOnly = strDate.Mid(iYearStartIdx, iYearEndIdx - iYearStartIdx + 1);
	int iYear = ::_ttoi(strYearOnly);

	// 2. Find where the month starts and where it ends
	int iMonthStartIdx = m_strDateFormat.Find('M');
	int iMonthEndIdx = m_strDateFormat.ReverseFind('M');
	CString strMonthOnly = strDate.Mid(iMonthStartIdx, iMonthEndIdx - iMonthStartIdx + 1);
	int iMonth = ::_ttoi(strMonthOnly);

	// 3. Find where the day starts and where it ends
	int iDayStartIdx = m_strDateFormat.Find('D');
	int iDayEndIdx = m_strDateFormat.ReverseFind('D');
	CString strDayOnly = strDate.Mid(iDayStartIdx, iDayEndIdx - iDayStartIdx + 1);
	int iDay = ::_ttoi(strDayOnly);

	return COleDateTime(iYear, iMonth, iDay, 0, 0, 0);
}

void COXCalendarEdit::OnKillFocus(CWnd* pNewWnd) 
{
	COXMaskedEdit::OnKillFocus(pNewWnd);
}

CString COXCalendarEdit::GetStringFromDate(COleDateTime dtDate)
{
	// Format the picked date and put it into the edit box

	// We need to convert the COXCalendarEdit date format string into
	// and strftime() style format string.
	CString strNewFormat(m_strDateFormat);
	strNewFormat.Replace(_T("YYYY"), _T("%Y"));
	strNewFormat.Replace(_T("YY"), _T("%y"));
	strNewFormat.Replace(_T("MM"), _T("%m"));
	strNewFormat.Replace(_T("DD"), _T("%d"));

	return dtDate.Format(strNewFormat);
}

CString COXCalendarEdit::GetText()
{
	CString strText = COXMaskedEdit::GetText();
	
	if (IsEmptyDate(strText))
		return _T("");
	else if (IsValidDate(strText))
		return strText;
	else
		return GetErrorString();
}

BOOL COXCalendarEdit::IsValidDate(LPCTSTR lpszTestDate)
{
	ASSERT(lpszTestDate != NULL);
	
	if (IsEmptyDate(lpszTestDate))
		return TRUE;

	COleDateTime dtDate = GetDateFromString(lpszTestDate);
	if (dtDate.GetStatus() == COleDateTime::valid)
		return TRUE;
	else
		return FALSE;
}

void COXMonthCalPopup::AdjustDisplayRectangle(CRect &rect, CRect rectParent)
{
	// Get the rectangle of the monitor closest to the menu rectangle
	HMONITOR hMonitor = ::MonitorFromRect(rect, MONITOR_DEFAULTTONEAREST);
	MONITORINFO mi;
	mi.cbSize = sizeof(MONITORINFO);
	::GetMonitorInfo(hMonitor, &mi);

	const int iMixScreenX = mi.rcMonitor.left;
	const int iMaxScreenX = mi.rcMonitor.right;
	const int iMaxScreenY = mi.rcMonitor.bottom;

	CSize sizeParent(rectParent.Width(), rectParent.Height());
	CPoint ptTopLeft(rect.left, rect.top);

	if (ptTopLeft.x < iMixScreenX)
		ptTopLeft.x = iMixScreenX;
	if (ptTopLeft.x + rect.Width() > iMaxScreenX)
		ptTopLeft.x = iMaxScreenX - rect.Width();

	// Make sure the popup is not clipped at the bottom
	if (rect.bottom > iMaxScreenY)
	{
		// The popup should be above the item
		ptTopLeft.y = rectParent.top - rect.Height();
	}

	CSize sizeTemp = rect.Size();
	rect.SetRect(ptTopLeft.x, ptTopLeft.y,
		ptTopLeft.x + sizeTemp.cx, ptTopLeft.y + sizeTemp.cy);
}

BOOL COXCalendarEdit::IsEmptyDate(LPCTSTR lpszTestDate)
{
	CString strTestDate = lpszTestDate;
	if (strTestDate.IsEmpty())
		return TRUE;

	// Produce the empty date string
	CString strEmptyDate = m_strDateFormat;
	strEmptyDate.Replace('Y', ' ');
	strEmptyDate.Replace('M', ' ');
	strEmptyDate.Replace('D', ' ');

	if (strTestDate == strEmptyDate)
		return TRUE;
	else
		return FALSE;
}

void COXCalendarEdit::SetText(LPCTSTR lpszDate)
{
	if (IsValidDate(lpszDate))
		COXMaskedEdit::SetText(lpszDate);
	else
		COXMaskedEdit::SetText(_T(""));
}
