// CJMaskEdit.cpp : implementation file
// Copyright © 1998-1999 CodeJock.com, All Rights Reserved.
// See ReadMe.txt for TERMS OF USE.
//
// Based on the article "A masked edit control (2)" by Jeremy Davis [jmd@jvf.co.uk]
// http://www.codeguru.com/editctrl/masked_edit2.shtml
//
/////////////////////////////////////////////////////////////////////////////
/****************************************************************************
 *
 * $Date: 1999/11/30 21:45:49 $
 * $Revision: 1.1.1.3 $
 * $Archive: /CodeJock/CJLibrary/CJMaskEdit.cpp $
 *
 * $History: CJMaskEdit.cpp $
 * 
 * *****************  Version 11  *****************
 * User: Kirk Stowell Date: 10/30/99   Time: 1:53p
 * Updated in $/CodeJock/CJLibrary
 * Overrode OnEraseBkgnd(...) and OnPaint(...) to handle flicker free
 * drawing.
 * 
 * *****************  Version 10  *****************
 * User: Kirk Stowell Date: 10/24/99   Time: 12:01a
 * Updated in $/CodeJock/CJLibrary
 * Fixed potential resource and memory leak problems.
 * 
 * *****************  Version 9  *****************
 * User: Kirk Stowell Date: 8/31/99    Time: 1:11a
 * Updated in $/CodeJockey/CJLibrary
 * Updated copyright and contact information.
 * 
 * *****************  Version 8  *****************
 * User: Kirk Stowell Date: 8/29/99    Time: 9:14p
 * Updated in $/CodeJockey/CJLibrary
 * Added Unicode compliance, thanks to Barry Burton for his help with
 * this.
 * 
 * 
 * *****************  Version 7  *****************
 * User: Kirk Stowell Date: 6/23/99    Time: 12:04a
 * Updated in $/CodeJockey/CJLibrary
 * 
 * *****************  Version 6  *****************
 * User: Kirk Stowell Date: 7/25/99    Time: 10:00p
 * Updated in $/CodeJockey/CJLibrary
 * 
 * *****************  Version 5  *****************
 * User: Kirk Stowell Date: 7/25/99    Time: 12:42a
 * Updated in $/CodeJockey/CJLibrary
 * 
 * *****************  Version 4  *****************
 * User: Kirk Stowell Date: 7/25/99    Time: 12:30a
 * Updated in $/CodeJockey/CJLibrary
 * 
 * *****************  Version 3  *****************
 * User: Kirk Stowell Date: 7/25/99    Time: 12:15a
 * Updated in $/CodeJockey/CJLibrary
 * 
 * *****************  Version 2  *****************
 * User: Kirk Stowell Date: 6/23/99    Time: 12:33a
 * Updated in $/CodeJockey/CJLibrary
 * 
 * *****************  Version 1  *****************
 * User: Kirk Stowell Date: 7/19/99    Time: 12:35a
 * Created in $/CodeJockey/CJLibrary
 * Initial creation and addition to library.
 * 
 ***************************************************************************/
/////////////////////////////////////////////////////////////////////////////

#include "StdAfx.h"
#include "CJMaskEdit.h"

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

/////////////////////////////////////////////////////////////////////////////
// CCJMaskEdit

CCJMaskEdit::CCJMaskEdit()
{
	m_bUseMask				= true;
	m_bMaskKeyInProgress	= false;
	m_strWindowText			= _T("");
	m_strMask				= _T("");
	m_strLiteral			= _T("");
	m_strValid				= _T("");
	m_strMaskLiteral		= _T("");
}

CCJMaskEdit::~CCJMaskEdit()
{
	// fix potential resource leak - KStowell - 10-22-99.
	m_Font.DeleteObject();
}

IMPLEMENT_DYNAMIC(CCJMaskEdit, CEdit)

BEGIN_MESSAGE_MAP(CCJMaskEdit, CEdit)
	//{{AFX_MSG_MAP(CCJMaskEdit)
	ON_WM_KEYDOWN()
	ON_WM_CHAR()
	ON_WM_ERASEBKGND()
	ON_WM_PAINT()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CCJMaskEdit message handlers

void CCJMaskEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if( m_bUseMask )
	{
		switch( nChar )
		{
		case VK_DELETE:
		case VK_INSERT: return;
		}
	}
	
	CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
}

void CCJMaskEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	if( !m_bMaskKeyInProgress ) {
		if( !CheckChar( nChar )) {
			return;
		}
	}
	
	if( m_bUseMask )
	{
		int nStartPos;
		int nEndPos;
		GetSel( nStartPos, nEndPos );

		if( isprint( nChar ))
		{
			SetSel( nStartPos, nEndPos+1 );
			
			Clear();

			m_strWindowText.SetAt( nEndPos, static_cast<TCHAR>(nChar));
		}
		else if( nChar == VK_BACK )
		{
			if(( nStartPos == nEndPos ) && 
			   ( nStartPos >= 1 ) && 
			   ( nStartPos <= m_strWindowText.GetLength( )))
			{
				UINT c = 0x20;
				
				// get the masked literal representation.
				if( m_strMaskLiteral.IsEmpty() == FALSE )
				{
					c = m_strMaskLiteral[ nStartPos - 1 ];
				}

				// backspace the cursor.
				SendMessage( WM_KEYDOWN, VK_LEFT, 0 );

				if( m_strMaskLiteral.IsEmpty() == FALSE )
				{
					// update the char backspacing over.
					SendChar( c );

					// backspace the cursor again.
					SendMessage( WM_KEYDOWN, VK_LEFT, 0 );
				}
			}
			else 
			{
				MessageBeep((UINT)-1);
			}

			return;
		}
	}

	CEdit::OnChar(nChar, nRepCnt, nFlags);

	if( !m_bMaskKeyInProgress && m_bUseMask && !m_strLiteral.IsEmpty( ))
	{
		int nStartPos;
		int nEndPos;
		GetSel( nStartPos, nEndPos );

		// make sure the string is not longer than the mask
		if( nEndPos < m_strLiteral.GetLength( ))
		{
			UINT c = m_strLiteral.GetAt( nEndPos );
			if (c != '_') {
				SetSel( nEndPos+1, nEndPos+1 );
			}
		}
	}
}

bool CCJMaskEdit::CheckChar(UINT nChar)
{
	// do not use mask
	if( !m_bUseMask ) {
		return true;
	}
	
	// control character, OK
	if( !isprint( nChar )) {
		return true;
	}
	
	// unselect all selections, if any
	int nStartPos, nEndPos;
	GetSel( nStartPos, nEndPos );
	SetSel( -1, 0 );
	
	// advance to the first character input position.
	for( nStartPos; nStartPos < m_strLiteral.GetLength(); ++nStartPos )
	{
		if( m_strLiteral.GetAt( nStartPos ) == '_' ) {
			SetSel( nStartPos, nStartPos );
			break;
		}
	}
	
	// check the key against the mask
	GetSel( nStartPos, nEndPos );
	
	// make sure the string is not longer than the mask
	if( nEndPos >= m_strMask.GetLength( )) {
		MessageBeep((UINT)-1);
		return false;
	}
	
	// check to see if a literal is in this position
	UINT c = '_';
	if( !m_strLiteral.IsEmpty( )) {
		c = m_strLiteral.GetAt( nEndPos );
	}
	
	if( c != '_' ) {
		SendChar( c );
		GetSel( nStartPos, nEndPos );
	}
	
	// check the valid string character
	if( m_strValid.Find( static_cast<TCHAR>(nChar)) != -1 ) {
		return true;
	}
	
	if( ProcessMask( nChar, nEndPos ))
		return true;

	MessageBeep((UINT)-1);
	return false;
}

void CCJMaskEdit::SendChar(UINT nChar)
{
	m_bMaskKeyInProgress = true;
#ifdef WIN32
    AfxCallWndProc(this, m_hWnd, WM_CHAR, nChar, 1);
#else
    SendMessage(WM_CHAR, nChar, 1);
#endif
	m_bMaskKeyInProgress = false;
}

bool CCJMaskEdit::ProcessMask(UINT nChar, int nEndPos)
{
	// check the key against the mask
	switch( m_strMask.GetAt( nEndPos ))
	{
    case '0':		// digit only //completely changed this
		{
			if( isdigit( nChar )) {
				return true;
			}
			break;
		}
    case '9':		// digit or space
		{
			if( isdigit( nChar )) {
				return true;
			}
			if( nChar == VK_SPACE ) {
				return true;
			}
			break;
		}
    case '#':		// digit or space or '+' or '-'
		{
			if( isdigit( nChar )) {
				return true;
			}
			if( nChar == VK_SPACE || nChar == VK_ADD || nChar == VK_SUBTRACT ) {
				return true;
			}
			break;
		}
    case 'L':		// alpha only
		{
			if( isalpha( nChar )) {
				return true;
			}
			break;
		}
    case '?':		// alpha or space
		{
			if( isalpha( nChar )) {
				return true;
			}
			if( nChar == VK_SPACE ) {
				return true;
			}
			break;
		}
    case 'A':		// alpha numeric only
		{
			if( isalnum( nChar )) {
				return true;
			}
			break;
		}
    case 'a':		// alpha numeric or space
		{
			if( isalnum( nChar )) {
				return true;
			}
			if( nChar == VK_SPACE ) {
				return true;
			}
			break;
		}
    case '&':		// all print character only
		{
			if( isprint( nChar )) {
				return true;
			}
			break;
		}
    case 'H':		// hex digit
		{
			if( isxdigit( nChar )) {
				return true;
			}
			break;
		}
	case 'X':		// hex digit or space
		{
			if( isxdigit( nChar )) {
				return true;
			}
			if( nChar == VK_SPACE ) {
				return true;
			}
			break;
		}
	}

	return false;
}

void CCJMaskEdit::SetEditMask(LPCTSTR lpszMask, LPCTSTR lpszLiteral, LPCTSTR lpszWindowText)
{
	ASSERT(lpszMask);
	ASSERT(lpszLiteral);
	ASSERT(lpszWindowText);

	// initialize the mask for the control.
	m_strMask        = lpszMask;
	m_strLiteral     = lpszLiteral;
	m_strWindowText  = lpszWindowText;
	m_strMaskLiteral = m_strWindowText;

	// set the window text for the control.
	SetWindowText( lpszWindowText );

	// initialize the font used by the control.
	UpdateFont();
}

/////////////////////////////////////////////////////////////////////////////
// DDX for date mask control

void AFXAPI DDX_OleDateTime(CDataExchange* pDX, int nIDC, CCJDateEdit& rControl, COleDateTime& rDateTime)
{
	DDX_Control( pDX, nIDC, rControl );
	
	if( pDX->m_bSaveAndValidate ) {
		rDateTime = rControl.GetDateTime();
	}
	else {
		rControl.SetDateTime( rDateTime );
	}
}

/////////////////////////////////////////////////////////////////////////////
// CCJDateEdit class

IMPLEMENT_DYNAMIC(CCJDateEdit, CCJMaskEdit)

CCJDateEdit::CCJDateEdit()
{
	m_bUseMask		= true;
	m_strMask		= _T("00/00/0000");
	m_strLiteral	= _T("__/__/____");
}

COleDateTime CCJDateEdit::ReadOleDateTime(LPCTSTR lpszData)
{
	COleDateTime dt;
	dt.ParseDateTime(lpszData);
	return dt;
}

void CCJDateEdit::FormatOleDateTime(CString &strData, COleDateTime dt)
{
	strData = dt.Format( _T("%d/%m/%Y"));
}

void CCJDateEdit::SetDateTime(COleDateTime& dt)
{
	CString strText;
	FormatOleDateTime(strText, dt);
	m_strWindowText = m_strMaskLiteral = strText;
	SetWindowText(strText);
}

void CCJDateEdit::SetDateTime(CString strDate)
{
	m_strWindowText = m_strMaskLiteral = strDate;
	SetWindowText(strDate);
}

COleDateTime CCJDateEdit::GetDateTime()
{
	CString strText;
	GetWindowText(strText);
	return ReadOleDateTime(strText);
}

CString CCJDateEdit::GetWindowDateTime()
{
	CString strText;
	GetWindowText(strText);
	return strText;
}

bool CCJDateEdit::ProcessMask(UINT nChar, int nEndPos)
{
	// check the key against the mask
	switch( m_strMask.GetAt( nEndPos ))
	{
    case '0':		// digit only //completely changed this
		{
			bool bReturn = true;

			if( isdigit( nChar ))
			{
				if( nEndPos == 0 )
				{
					if( nChar > '3' ) {
						bReturn = false;
					}
				}
				else if( nEndPos == 1 )
				{
					if( m_strWindowText.GetAt(0) == '3' )
					{
						if( nChar > '1' ) {
							bReturn = false;
						}
					}
				}
				else if( nEndPos == 3 )
				{
					if( nChar > '1' ) {
						bReturn = false;
					}
				}
				else if( nEndPos == 4 )
				{
					if( m_strWindowText.GetAt(3) == '1' )
					{
						if( nChar > '2') {
							bReturn = false;
						}
					}
				}
				return bReturn;
			}
			break;
		}
	}

	MessageBeep((UINT)-1);
	return false;
}

/////////////////////////////////////////////////////////////////////////////
// CCJTimeEdit class

IMPLEMENT_DYNAMIC(CCJTimeEdit, CCJDateEdit)

CCJTimeEdit::CCJTimeEdit()
{
	m_bUseMask		= true;
	m_strMask		= _T("00:00");
	m_strLiteral	= _T("__:__");
	m_strHours		= _T("47");
	m_strMins		= _T("59");
}

void CCJTimeEdit::FormatOleDateTime(CString &strData, COleDateTime dt)
{
	if( dt.m_dt == 0 ) { 
		strData = "00:00"; 
	}
	else {
		strData = dt.Format( _T("%H:%M")); 
	}
}

bool CCJTimeEdit::ProcessMask(UINT nChar, int nEndPos)
{
	// check the key against the mask
	switch( m_strMask.GetAt( nEndPos ))
	{
    case '0':		// digit only //completely changed this
		{
			bool bReturn = true;

			if( isdigit( nChar ))
			{
				if( nEndPos == 0 )
				{
					if(nChar > (UINT)m_strHours[0]) {
						bReturn = false;
					}
				}
				else if( nEndPos == 1 )
				{
					if(m_strWindowText.GetAt(0) == m_strHours[0])
					{
						if( nChar > (UINT)m_strHours[1] ) {
							bReturn = false;
						}
					}
				}
				else if( nEndPos == 3 )
				{
					if( nChar > (UINT)m_strMins[0] ) {
						bReturn = false;
					}
				}
				else if( nEndPos == 4 )
				{
					if( m_strWindowText.GetAt(3) == m_strMins[0] )
					{
						if( nChar > (UINT)m_strMins[1] ) {
							bReturn = false;
						}
					}
				}
				return bReturn;
			}
			break;
		}
	}

	MessageBeep((UINT)-1);
	return false;
}

void CCJTimeEdit::SetHours(int nHours)
{
	m_strHours.Format( _T("%d"), nHours);
}

void CCJTimeEdit::SetMins(int nMins)
{
	m_strMins.Format( _T("%d"), nMins);
}

void CCJMaskEdit::UpdateFont()
{
	m_Font.DeleteObject();
	NONCLIENTMETRICS ncm;
	ncm.cbSize = sizeof(ncm);
	SystemParametersInfo(SPI_GETNONCLIENTMETRICS, sizeof(ncm), &ncm, 0);
	VERIFY(m_Font.CreateFontIndirect(&ncm.lfMessageFont));
	SetFont(&m_Font);
}

BOOL CCJMaskEdit::OnEraseBkgnd(CDC* pDC) 
{
	// KStowell - overridden for flicker-free drawing.
	UNUSED_ALWAYS(pDC);
	return TRUE;
}

void CCJMaskEdit::OnPaint() 
{
    // Use a "Offscreen" DC to fill rect and send to DefWindowProc...  
	CClientDC memDC(this);
	
    CRect rcClip;
    GetClientRect(&rcClip);
    memDC.FillSolidRect(rcClip, ::GetSysColor(COLOR_WINDOW));
    DefWindowProc( WM_PAINT, (WPARAM)memDC.m_hDC, (LPARAM)0 );
	
    // Default is called for cleanup.
    Default();
}
