// 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.

// OXMaskedEdit.cpp : implementation file
//
/////////////////////////////////////////////////////////////////////////////
// Version: 9.3


#include "stdafx.h"
#include "OXMaskedEdit.h"    // COXMaskedEdit

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

/////////////////////////////////////////////////////////////////////////////
// CMaskData

IMPLEMENT_DYNCREATE(CMaskData, CObject)

CMaskData::CMaskData() : 
	m_eType  (MaskDataTypeLITERAL),
	m_chValue(chNULL)
{
}

/////////////////////////////////////////////////////////////////////////////
// CMaskData operations

void CMaskData::operator=(const CMaskData& src)
{
	m_eType=src.m_eType  ;
	m_chValue=src.m_chValue;
}
		
BOOL CMaskData::IsInputData()
{
	BOOL bIsInputData=FALSE;
	switch(m_eType)
	{
	// These are the input types. 
	case MaskDataTypeDIGIT             :
	case MaskDataTypeALPHANUMERIC      :
	case MaskDataTypeALPHABETIC        :
	case MaskDataTypeALPHAETICUPPER    :
	case MaskDataTypeALPHAETICLOWER    :
	case MaskDataTypeCHARACTER         :
		bIsInputData=TRUE ;
		break;
	}
	return bIsInputData;
}

BOOL CMaskData::IsValidInput(TCHAR chNewChar)
{
	BOOL bIsValidInput=FALSE;
	switch(m_eType)
	{
	// These are the input types. 
	case MaskDataTypeDIGIT             :
		bIsValidInput=_istdigit(chNewChar);
		break;
	case MaskDataTypeALPHANUMERIC      :
		bIsValidInput=_istalnum(chNewChar);
		break;
	case MaskDataTypeALPHABETIC        :
	case MaskDataTypeALPHAETICUPPER    :
	case MaskDataTypeALPHAETICLOWER    :
		bIsValidInput=_istalpha(chNewChar);
		break;
	case MaskDataTypeCHARACTER         :
		if((chNewChar >=  32) && (chNewChar <= 126))
			bIsValidInput=TRUE ;
		if((chNewChar >= 128) && (chNewChar <= 255))
			bIsValidInput=TRUE ;
		break;
	}
	return bIsValidInput;
}

TCHAR CMaskData::PreProcessChar(TCHAR chNewChar)
{
	TCHAR chProcessedChar=chNewChar;
	switch(m_eType)
	{
	case MaskDataTypeALPHAETICUPPER    :
		chProcessedChar=(TCHAR)_totupper(chNewChar);
		break;
	case MaskDataTypeALPHAETICLOWER    :
		chProcessedChar=(TCHAR)_totlower(chNewChar);
		break;
	}
	return chProcessedChar;
}

#ifdef _DEBUG
void CMaskData::AssertValid() const
{
	CObject::AssertValid();
	ASSERT( (m_eType >= 0) && (m_eType < MASKDATATYPECOUNT));
	ASSERT( m_chValue != chNULL);
}

void CMaskData::Dump(CDumpContext& dc) const
{
	CObject::Dump(dc);
}
#endif


/////////////////////////////////////////////////////////////////////////////
// COXMaskedEdit

IMPLEMENT_DYNAMIC(COXMaskedEdit, CEdit)

COXMaskedEdit::COXMaskedEdit(LPCTSTR pszMask/*=_T("")*/) : 
	m_bInsertMode(TRUE),
	m_chPromptSymbol(chSPACE),
	m_chIntlDecimal(chPERIOD),
	m_chIntlThousands(chCOMMA),
	m_chIntlTime(chCOLON),
	m_chIntlDate(chSLASH),
	m_bAutoTab(FALSE),
	m_nSetTextSemaphor(0),
	m_bNotifyParent(TRUE)
{
	int nLength;

	nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL, 
		&m_chIntlDecimal, 0);
	if(nLength)
	{
		::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDECIMAL ,
			&m_chIntlDecimal, nLength);
	}
	nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, 
		&m_chIntlThousands, 0);
	if(nLength)
	{
		::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STHOUSAND, 
			&m_chIntlThousands , nLength);
	}
	nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STIME, &m_chIntlTime, 0);
	if(nLength)
	{
		::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_STIME, &m_chIntlTime, nLength);
	}
	nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, &m_chIntlDate, 0);
	if(nLength)
	{
		::GetLocaleInfo(LOCALE_USER_DEFAULT, LOCALE_SDATE, &m_chIntlDate, nLength);
	}

	SetMask(pszMask);
}

COXMaskedEdit::~COXMaskedEdit()
{
	DeleteContents();
}


BEGIN_MESSAGE_MAP(COXMaskedEdit, CEdit)
	//{{AFX_MSG_MAP(COXMaskedEdit)
	ON_WM_KEYDOWN()
	ON_WM_CHAR()
	ON_WM_SETFOCUS()
	ON_CONTROL_REFLECT(EN_KILLFOCUS, OnKillfocus)
	ON_WM_LBUTTONDOWN()
	//}}AFX_MSG_MAP
	ON_MESSAGE(WM_CUT,OnCut)
	ON_MESSAGE(WM_COPY,OnCopy)
	ON_MESSAGE(WM_PASTE,OnPaste)
	ON_MESSAGE(WM_CLEAR,OnClear)
	ON_MESSAGE(WM_SETTEXT,OnSetText)
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// COXMaskedEdit operations

void COXMaskedEdit::DeleteContents()
{
	if(m_listData.GetCount()==0)
	{
		if(::IsWindow(GetSafeHwnd()))
			SetWindowText(_T(""));
		return;
	}

	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.GetHeadPosition(); pos;)
	{
		pobjData=m_listData.GetNext(pos);
		delete pobjData;
		pobjData=NULL;
	}
	m_listData.RemoveAll();
}

CString COXMaskedEdit::GetMask() const
{
	CString csMask;
	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.GetHeadPosition(); pos;)
	{
		pobjData=m_listData.GetNext(pos);
		switch(pobjData->m_eType)
		{
		case MaskDataTypeDECIMALSEPARATOR  :  csMask += chMaskPlaceholderDECIMALSEPARATOR  ;  break;
		case MaskDataTypeTHOUSANDSSEPARATOR:  csMask += chMaskPlaceholderTHOUSANDSSEPARATOR;  break;
		case MaskDataTypeTIMESEPARATOR     :  csMask += chMaskPlaceholderTIMESEPARATOR     ;  break;
		case MaskDataTypeDATESEPARATOR     :  csMask += chMaskPlaceholderDATESEPARATOR     ;  break;
		case MaskDataTypeDIGIT             :  csMask += chMaskPlaceholderDIGIT             ;  break;
		case MaskDataTypeALPHANUMERIC      :  csMask += chMaskPlaceholderALPHANUMERIC      ;  break;
		case MaskDataTypeALPHABETIC        :  csMask += chMaskPlaceholderALPHABETIC        ;  break;
		case MaskDataTypeALPHAETICUPPER    :  csMask += chMaskPlaceholderALPHABETICUPPER   ;  break;
		case MaskDataTypeALPHAETICLOWER    :  csMask += chMaskPlaceholderALPHABETICLOWER   ;  break;
		case MaskDataTypeCHARACTER         :  csMask += chMaskPlaceholderCHARACTER         ;  break;
		case MaskDataTypeLITERALESCAPE     :
			// Need to add the escape to things that were escaped. 
			csMask += chMaskPlaceholderLITERALESCAPE;
			csMask += pobjData->m_chValue           ;
			break;
		default:
			// Literals and everything else is kept the same. 
			csMask += pobjData->m_chValue;
			break;
		}
	}
	return csMask;
}

void COXMaskedEdit::SetMask(LPCTSTR pszMask)
{
	if(pszMask==NULL)
	{
		pszMask=_T("");
	}
	DeleteContents();
	
	CMaskData* pobjData=NULL;
	for(LPCTSTR pszInsertionPoint=pszMask; *pszInsertionPoint; pszInsertionPoint++)
	{
		TCHAR chNew=*pszInsertionPoint;
		pobjData=new CMaskData();
		m_listData.AddTail(pobjData);
		switch(chNew)
		{
		case chMaskPlaceholderDECIMALSEPARATOR  :
			pobjData->m_eType  =MaskDataTypeDECIMALSEPARATOR  ;
			pobjData->m_chValue=m_chIntlDecimal               ;
			break;
		case chMaskPlaceholderTHOUSANDSSEPARATOR:
			pobjData->m_eType  =MaskDataTypeTHOUSANDSSEPARATOR;
			pobjData->m_chValue=m_chIntlThousands             ;
			break;
		case chMaskPlaceholderTIMESEPARATOR     :
			pobjData->m_eType  =MaskDataTypeTIMESEPARATOR     ;
			pobjData->m_chValue=m_chIntlTime                  ;
			break;
		case chMaskPlaceholderDATESEPARATOR     :
			pobjData->m_eType  =MaskDataTypeDATESEPARATOR     ;
			pobjData->m_chValue=m_chIntlDate                  ;
			break;
		case chMaskPlaceholderDIGIT             :
			pobjData->m_eType  =MaskDataTypeDIGIT             ;
			pobjData->m_chValue=m_chPromptSymbol              ;
			break;
		case chMaskPlaceholderALPHANUMERIC      :
			pobjData->m_eType  =MaskDataTypeALPHANUMERIC      ;
			pobjData->m_chValue=m_chPromptSymbol              ;
			break;
		case chMaskPlaceholderALPHABETIC        :
			pobjData->m_eType  =MaskDataTypeALPHABETIC        ;
			pobjData->m_chValue=m_chPromptSymbol              ;
			break;
		case chMaskPlaceholderALPHABETICUPPER   :
			pobjData->m_eType  =MaskDataTypeALPHAETICUPPER    ;
			pobjData->m_chValue=m_chPromptSymbol              ;
			break;
		case chMaskPlaceholderALPHABETICLOWER   :
			pobjData->m_eType  =MaskDataTypeALPHAETICLOWER    ;
			pobjData->m_chValue=m_chPromptSymbol              ;
			break;
		case chMaskPlaceholderCHARACTER         :
			pobjData->m_eType  =MaskDataTypeCHARACTER         ;
			pobjData->m_chValue=m_chPromptSymbol              ;
			break;
		case chMaskPlaceholderLITERALESCAPE     :
			// It is the next character that is inserted. 
			pszInsertionPoint++;
			chNew=*pszInsertionPoint;
			if(chNew)
			{
				pobjData->m_eType  =MaskDataTypeLITERALESCAPE     ;
				pobjData->m_chValue=chNew                         ;
				break;
			}
			// If there is no character following the escape, 
			// just treat the escape as a literal so that the user 
			// will see the problem. 
		default:
			// Everything else is just a literal. 
			pobjData->m_eType  =MaskDataTypeLITERAL           ;
			pobjData->m_chValue=chNew                         ;
			break;
		}
	}
	ASSERT(GetMask()==pszMask);

	Update();

	if(::IsWindow(GetSafeHwnd()))
		SetModify(FALSE);
}

CString COXMaskedEdit::GetInputData() const
{
	CString csInputData;

	if(m_listData.GetCount()==0)
	{
		GetWindowText(csInputData);
		return csInputData;
	}

	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.GetHeadPosition(); pos;)
	{
		pobjData=m_listData.GetNext(pos);
		// Ignore everything that is not data. 
		if(pobjData->IsInputData())
			csInputData += pobjData->m_chValue;
	}
	return csInputData;
}

CString COXMaskedEdit::GetInputData(LPCTSTR lpszText) const
{
	CString csInputData=lpszText;
	int nSymbolCount=csInputData.GetLength();
	CMaskData* pobjData=NULL;
	CString sToExclude;
	int nStartPos=-1;
	int nEndPos=-1;
	int nIndex=0;
	int nRemovedCount=0;
	for(POSITION pos=m_listData.GetHeadPosition(); pos;)
	{
		pobjData=m_listData.GetNext(pos);
		if(!pobjData->IsInputData())
		{
			if(nStartPos==-1)
			{
				nStartPos=nIndex;
				sToExclude.Empty();
			}
			sToExclude+=pobjData->m_chValue;
		}
		else
		{
			if(nStartPos!=-1)
			{
				nEndPos=nIndex-1;
				if(csInputData.Mid(nStartPos-nRemovedCount,
					nEndPos-nStartPos+1)==sToExclude)
				{
					csInputData=csInputData.Left(nStartPos-nRemovedCount)+
						csInputData.Mid(nEndPos-nRemovedCount+1);
					nRemovedCount+=nEndPos-nStartPos+1;
				}
				nStartPos=-1;
			}
		}

		nIndex++;
		if(nIndex>=nSymbolCount)
			break;
	}

	return csInputData;
}

BOOL COXMaskedEdit::SetInputData(LPCTSTR pszInputData, int nBeginPos/*=0*/, 
								 BOOL bAllowPrompt/*=TRUE*/)
{
	CString csFullInput;
	// Start with existing data and append the new data. 
	csFullInput=GetInputData();
	csFullInput=csFullInput.Left(nBeginPos);
	if(bAllowPrompt)
	{
		csFullInput+=pszInputData;
	}
	else
	{
		// If the prompt symbol is not valid, then 
		// add the data one-by-one ignoring any prompt symbols. 
		for(; *pszInputData; pszInputData++)
		{
			if(*pszInputData!=m_chPromptSymbol)
				csFullInput+=*pszInputData;
		}
	}
	
	BOOL bCompleteSuccess=TRUE;
	LPCTSTR pszReplaceData=csFullInput;
	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.GetHeadPosition(); pos;)
	{
		pobjData=m_listData.GetNext(pos);
		// Ignore everything that is not data. 
		if(pobjData->IsInputData())
		{
			// If we run out of replacement data, then use the prompt symbol. 
			// Make sure we iterate through the entire list so that the 
			// prompt symbol is applied to any empty areas. 
			if(*pszReplaceData)
			{
				// This inner while loop is so that we can re-apply input data 
				// after an error.  This will allow us to skip over invalid 
				// input data and try the next character. 
				while(*pszReplaceData)
				{
					TCHAR chReplace=*pszReplaceData;
					pszReplaceData++;
					
					// Make sure to follow the input validation. 
					// The prompt symbol is always valid at this level. 
					// This allows the user to erase a string by overtyping a space. 
					// On error, just skip the character being inserted. 
					// This will allow the DeleteRange() function to have the remaining 
					// characters validated. 
					if((chReplace==m_chPromptSymbol) || pobjData->IsValidInput(chReplace))
					{
						pobjData->m_chValue=pobjData->PreProcessChar(chReplace);
						break;
					}
					else
						bCompleteSuccess=FALSE;
				}
			}
			else
			{
				pobjData->m_chValue=m_chPromptSymbol;
			}
		}
	}
	
	Update();

	return bCompleteSuccess;
}

TCHAR COXMaskedEdit::GetPromptSymbol()
{
	return m_chPromptSymbol;
}

void COXMaskedEdit::SetPromptSymbol(TCHAR chNewPromptSymbol)
{
	// The prompt symbol must be a valid edit box symbol. 
	ASSERT( (chNewPromptSymbol != chNULL) && (chNewPromptSymbol != chCR) && 
		(chNewPromptSymbol != chLF) && (chNewPromptSymbol != 127));

	if((chNewPromptSymbol != chNULL) && (chNewPromptSymbol != chCR) && 
		(chNewPromptSymbol != chLF) && (chNewPromptSymbol != 127))
	{
		// If the prompt symbol changes, 
		// go through and replace the existing prompts with the new prompt. 
		CMaskData* pobjData=NULL;
		for(POSITION pos=m_listData.GetHeadPosition(); pos;)
		{
			pobjData=m_listData.GetNext(pos);
			if(pobjData->IsInputData())
			{
				if(pobjData->m_chValue==m_chPromptSymbol)
					pobjData->m_chValue=chNewPromptSymbol;
			}
		}
		m_chPromptSymbol=chNewPromptSymbol;
	}
	
	// Don't update the insertion point if we are just setting the prompt symbol. 
	Update(-1);

	if(::IsWindow(GetSafeHwnd()))
		SetModify(FALSE);
}

void COXMaskedEdit::EmptyData(BOOL bOnlyInput/*=FALSE*/)
{
	if(m_listData.GetCount()==0)
	{
		DeleteContents();
		return;
	}

	if(bOnlyInput)
	{
		// If emptying only the data, then iterate through the list 
		// of data and replace input data with the prompt symbol. 
		CMaskData* pobjData=NULL;
		for(POSITION pos=m_listData.GetHeadPosition(); pos;)
		{
			pobjData=m_listData.GetNext(pos);
			if(pobjData->IsInputData())
				pobjData->m_chValue=m_chPromptSymbol;
		}
	}
	else
		DeleteContents();
	
	Update();
}

BOOL COXMaskedEdit::IsInputEmpty()
{
	if(m_listData.GetCount()==0)
	{
		CString csInputData;
		GetWindowText(csInputData);
		return csInputData.IsEmpty();
	}

	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.GetHeadPosition(); pos;)
	{
		pobjData=m_listData.GetNext(pos);
		if (pobjData->IsInputData() && pobjData->m_chValue!=m_chPromptSymbol)
			return FALSE;
	}
	return TRUE;
}

BOOL COXMaskedEdit::GetInsertMode() const
{
	// The standard CEdit control does not support over-typing. 
	// This flag is used to manage over-typing internally. 
	return m_bInsertMode;
}

void COXMaskedEdit::SetInsertMode(BOOL bInsertMode)
{
	// The standard CEdit control does not support over-typing. 
	// This flag is used to manage over-typing internally. 
	m_bInsertMode=bInsertMode;
}

BOOL COXMaskedEdit::GetAutoTab() const
{
	// The standard CEdit control does not support AutoTab mode. 
	// This flag is used to manage the AutoTab mode internally. 
	return m_bAutoTab;
}

void COXMaskedEdit::SetAutoTab(BOOL bAutoTab)
{
	// The standard CEdit control does not support AutoTab mode. 
	// This flag is used to manage AutoTab mode internally. 
	m_bAutoTab=bAutoTab;
}

CString COXMaskedEdit::ShowMask() const
{
	CString csShow;
	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.GetHeadPosition(); pos;)
	{
		pobjData=m_listData.GetNext(pos);
		ASSERT_VALID( pobjData);
		
		// There is no need to do any fancy string building because 
		// all validation is done when characters are inserted into the list. 
		// Literals and placeholders are converted properly at that time 
		// so all we have to do here is get the value. 
		csShow += pobjData->m_chValue;
	}
	return csShow;
}

BOOL COXMaskedEdit::IsInputData(int nPosition) const
{
	if(m_listData.GetCount()==0)
	{
		return TRUE;
	}

	// We frequently need to know if a position refers to 
	// input data or to a literal. 
	BOOL bIsInputData=FALSE;
	if(nPosition >= 0)
	{
		POSITION pos=m_listData.FindIndex(nPosition);
		if(pos)
		{
			CMaskData* pobjData=m_listData.GetAt(pos);
			if(pobjData)
			{
				bIsInputData=pobjData->IsInputData();
			}
		}
	}
	return bIsInputData;
}

int COXMaskedEdit::DeleteRange(int nSelectionStart, int nSelectionEnd)
{
	// In order to delete properly, we must count the number of 
	// input characters that are selected and only delete that many. 
	// This is because the selection can include literals. 
	int nCharIndex  =0;
	int nDeleteCount=0;
	CString csInputData;
	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.GetHeadPosition(); pos; nCharIndex++)
	{
		pobjData=m_listData.GetNext(pos);
		// Ignore everything that is not data. 
		// This is the same as GetInputData except that we 
		// will ignore the input data within the selection range. 
		if(pobjData->IsInputData())
		{
			if((nCharIndex < nSelectionStart) || (nCharIndex >= nSelectionEnd))
			{
				// The SetInputData() function will take care of validating 
				// the shifted characters. 
				csInputData += pobjData->m_chValue;
			}
			else
				nDeleteCount++;
		}
	}
	// Now apply the filtered data stream. 
	SetInputData(csInputData);
	// return the deleted count so that an error can be generated 
	// if nothing was deleted. 
	return nDeleteCount;
}

int COXMaskedEdit::InsertAt(int nSelectionStart, TCHAR chNewChar)
{
	// We could have some complex, yet efficient, routine 
	// that would raise an error if inserting pushed an existing character 
	// into an invalid region.  Instead, just save the current 
	// state and restore it on error. 
	CString csPreviousInput=GetInputData();

	int nCharIndex=0;
	int nInsertionPoint=-1;
	CString csInputData;
	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.GetHeadPosition(); pos; nCharIndex++)
	{
		pobjData=m_listData.GetNext(pos);
		// Ignore everything that is not data. 
		// This is just like we do in GetInputData except that we 
		// will ignore the input data within the selection range. 
		if(pobjData->IsInputData())
		{
			// Wait until a valid insertion point and 
			// only make sure to insert once. 
			if((nInsertionPoint < 0) && (nCharIndex >= nSelectionStart))
			{
				csInputData += chNewChar;
				nInsertionPoint=nCharIndex;
			}
			csInputData += pobjData->m_chValue;
		}
	}
	// Now apply the filtered data stream and check if it was successful. 
	if(!SetInputData(csInputData))
	{
		// If not successful, then restore the previous input and return -1. 
		SetInputData(csPreviousInput);
		return -1;
	}
	return nInsertionPoint;
}

int COXMaskedEdit::SetAt(int nSelectionStart, TCHAR chNewChar)
{
	if(nSelectionStart >= 0)
	{
		POSITION pos=m_listData.FindIndex(nSelectionStart);
		if(pos)
		{
			CMaskData* pobjData=m_listData.GetAt(pos);
			if(pobjData)
			{
				if(pobjData->IsInputData())
				{
					if((chNewChar==m_chPromptSymbol) || pobjData->IsValidInput(chNewChar))
						pobjData->m_chValue=pobjData->PreProcessChar(chNewChar);
					else
						return -1;  // Input value is invalid or not allowed. 
				}
			}
		}
	}
	return nSelectionStart;
}

int COXMaskedEdit::GetNextInputLocation(int nSelectionStart)
{
	// One of the functions of this edit control is that it skips over literals. 
	// We need a function to help skip to the next position. 
	int nNextInputLocation=nSelectionStart;
	if(nNextInputLocation < 0)
		nNextInputLocation=0;
	
	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.FindIndex(nNextInputLocation); pos; nNextInputLocation++)
	{
		pobjData=m_listData.GetNext(pos);
		if(pobjData->IsInputData())
		{
			break;
		}
	}

	return nNextInputLocation;
}

int COXMaskedEdit::GetPreviousInputLocation(int nSelectionStart)
{
	// One of the functions of this edit control is that it skips over literals. 
	// We need a function to help skip to the next position. 
	int nNextInputLocation=nSelectionStart;
	if(nNextInputLocation < 0)
		nNextInputLocation=0;
	// Need to determine if we moved to a previous location. 
	// There will need to be some correction. 
	int nInitialInputLocation=nNextInputLocation;
	
	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.FindIndex(nNextInputLocation); pos; nNextInputLocation--)
	{
		pobjData=m_listData.GetPrev(pos);
		if(pobjData->IsInputData())
		{
			if(nInitialInputLocation != nNextInputLocation)
			{
				// If we find a valid previous location, then move to the right of it. 
				// This backup and then move forward is typical when seeking in a backwards direction. 
				nNextInputLocation++;
			}
			break;
		}
	}
	// If there is no input data to the left of the selection, 
	// then seek forward to the next location. 
	if(nNextInputLocation < 0)
		return GetNextInputLocation(nSelectionStart);
	return nNextInputLocation;
}

int COXMaskedEdit::GetEmptyInputLocation(int nSelectionStart)
{
	int nEmptyInputLocation=nSelectionStart;
	if(nEmptyInputLocation < 0)
		nEmptyInputLocation=0;
	
	CMaskData* pobjData=NULL;
	for(POSITION pos=m_listData.FindIndex(nEmptyInputLocation); pos; nEmptyInputLocation++)
	{
		pobjData=m_listData.GetNext(pos);
		if(pobjData->IsInputData())
		{
			if(pobjData->m_chValue==m_chPromptSymbol)
				break;
		}
	}
	return nEmptyInputLocation;
}

void COXMaskedEdit::Update(int nSelectionStart/*=0*/)
{
	// Update the edit control if it exists. 
	if(::IsWindow(m_hWnd))
	{
		m_nSetTextSemaphor++;
		CString sText=ShowMask();
		SetWindowText(sText);
		m_nSetTextSemaphor--;
		SetModify(TRUE);
		// We usually need to update the insertion point. 
		if(nSelectionStart>=0)
			UpdateInsertionPointForward(nSelectionStart);
	}
}

void COXMaskedEdit::UpdateInsertionPointForward(int nSelectionStart)
{
	int nNewInsertionPoint=GetNextInputLocation(nSelectionStart);

	if(m_bAutoTab && nNewInsertionPoint==m_listData.GetCount())
	{
		CWnd* pParentWnd=GetParent();
		ASSERT(pParentWnd);
		CWnd* pNextTabCtrl=pParentWnd->GetNextDlgTabItem(this);
		if(pNextTabCtrl && pNextTabCtrl!=this)
		{
			pNextTabCtrl->SetFocus();
		}
	}
	else
	{
		SetSel(nNewInsertionPoint, nNewInsertionPoint);
	}
}

void COXMaskedEdit::UpdateInsertionPointBackward(int nSelectionStart)
{
	int nNewInsertionPoint=GetPreviousInputLocation(nSelectionStart);
	SetSel(nNewInsertionPoint, nNewInsertionPoint);
}

void COXMaskedEdit::ValidationError()
{
	::MessageBeep(MB_ICONEXCLAMATION);
}

/////////////////////////////////////////////////////////////////////////////
// COXMaskedEdit overrides

void COXMaskedEdit::PreSubclassWindow() 
{
	CEdit::PreSubclassWindow();
	// As of 01/07/98, this masked edit control was only designed 
	// to handle single lines.  At some point, the code can be reviewed 
	// to see if it can handle multiple lines. 
	ASSERT( !(GetStyle() & ES_MULTILINE));
	
	_AFX_THREAD_STATE* pThreadState=AfxGetThreadState();
	// hook not already in progress
	if(pThreadState->m_pWndInit==NULL)
	{
		// This is a great place to update the control as it is 
		// the first function called after a successful subclass. 
		// Don't update if there is no data. 
		if(m_listData.GetCount() != 0)
			Update();
	}
}

/////////////////////////////////////////////////////////////////////////////
// COXMaskedEdit message handlers

BOOL COXMaskedEdit::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID)
{
	// We override the create function so that we can update the text 
	// if a mask was provided in the constructor. 
	BOOL bReturn=CEdit::Create(dwStyle, rect, pParentWnd, nID);
	if(bReturn)
	{
		// Don't update if there is no data. 
		if(m_listData.GetCount() != 0)
			Update();
	}
	return bReturn;
}

void COXMaskedEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	// If there is no mask, then exit quickly performing the default operation. 
	if(m_listData.GetCount()==0 || GetStyle()&ES_READONLY)
	{
		CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
		return;
	}

	// Keep the OnKeyDown processing to a minimum.  This is because the edit 
	// control does lots of processing before OnChar() is sent and we want 
	// to let it continue. 
	BOOL bIsShiftKeyDown=::GetAsyncKeyState(VK_SHIFT)< 0;
	if(nChar==VK_DELETE)
	{
		int nSelectionStart=0;
		int nSelectionEnd  =0;
		GetSel(nSelectionStart, nSelectionEnd);

		if(nSelectionStart<m_listData.GetCount())
		{
			// Delete has two functions, it can delete the selection and
			// it can delete characters to the right.
			if(nSelectionStart==nSelectionEnd)
			{
				nSelectionEnd++; // Do the equivalent of a selection.
				if(DeleteRange(nSelectionStart, nSelectionEnd))
				{
					Update(nSelectionStart);
				}
				else	// Must be on a literal, so continue moving to right
						// and re-attempt the delete until we either delete
						// a character or run out of characters.
				{
					while (nSelectionEnd != m_listData.GetCount())
					{
						nSelectionStart++;
						nSelectionEnd++; // Do the equivalent of a selection.
						if(DeleteRange(nSelectionStart, nSelectionEnd))
						{
							Update(nSelectionStart);
							break;
						}
					}
				}
			}
			else if(DeleteRange(nSelectionStart, nSelectionEnd))
			{
				Update(nSelectionStart);
			}
			else	// Must be on a literal, so continue moving to the right
					// and reattempt the delete until we either delete
					// a character or run out of characters.
			{
				while (nSelectionEnd != m_listData.GetCount())
				{
					nSelectionStart++;
					nSelectionEnd++; // Do the equivalent of a selection.
					if(DeleteRange(nSelectionStart, nSelectionEnd))
					{
						Update(nSelectionStart);
						break;
					}
				}
			}
		}
	}
	else if(nChar==VK_HOME)
	{
		// If the shift key is not down, then HOME is a navigation and we need to 
		// move the insertion point to the first available position. 
		CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
		if(!bIsShiftKeyDown)
		{
			int nSelectionStart=0;
			int nSelectionEnd  =0;
			GetSel(nSelectionStart, nSelectionEnd);
			
			UpdateInsertionPointForward(nSelectionStart);
		}
	}
	else if(nChar==VK_LEFT)
	{
		// If the shift key is not down, then LEFT is a navigation and we need to 
		// move the insertion point to the previous available position. 
		CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
		if(!bIsShiftKeyDown)
		{
			int nSelectionStart=0;
			int nSelectionEnd  =0;
			GetSel(nSelectionStart, nSelectionEnd);
			
			UpdateInsertionPointBackward(nSelectionStart);
		}
	}
	else if(nChar==VK_UP)
	{
		// If the shift key is not down, then UP is a navigation and we need to 
		// move the insertion point to the previous available position. 
		CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
		if(!bIsShiftKeyDown)
		{
			int nSelectionStart=0;
			int nSelectionEnd  =0;
			GetSel(nSelectionStart, nSelectionEnd);
			
			UpdateInsertionPointBackward(nSelectionStart);
		}
	}
	else if(nChar==VK_RIGHT)
	{
		// If the shift key is not down, then RIGHT is a navigation and we need to 
		// move the insertion point to the next available position. 
		CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
		if(!bIsShiftKeyDown)
		{
			int nSelectionStart=0;
			int nSelectionEnd  =0;
			GetSel(nSelectionStart, nSelectionEnd);
			
			UpdateInsertionPointForward(nSelectionStart);
		}
	}
	else if(nChar==VK_DOWN)
	{
		// If the shift key is not down, then DOWN is a navigation and we need to 
		// move the insertion point to the next available position. 
		CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
		if(!bIsShiftKeyDown)
		{
			int nSelectionStart=0;
			int nSelectionEnd  =0;
			GetSel(nSelectionStart, nSelectionEnd);
			
			UpdateInsertionPointForward(nSelectionStart);
		}
	}
	else if(nChar==VK_INSERT)
	{
	// The standard CEdit control does not support over-typing. 
	// This flag is used to manage over-typing internally. 
		BOOL bOldInsertMode=GetInsertMode();
		BOOL bNewInsertMode=bOldInsertMode ? FALSE : TRUE;
		
		SetInsertMode(bNewInsertMode);
	}
	else
	{
		CEdit::OnKeyDown(nChar, nRepCnt, nFlags);
	}
}

void COXMaskedEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
{
	// If there is no mask, then exit quickly performing the default operation. 
	if(m_listData.GetCount()==0 || GetStyle()&ES_READONLY)
	{
		CEdit::OnChar(nChar, nRepCnt, nFlags);
		return;
	}
	
	int nSelectionStart=0;
	int nSelectionEnd  =0;
	GetSel(nSelectionStart, nSelectionEnd);
	
	// If character value is above 32, then it is ANSI or Extended. 
	// Below 32 are control and navigation characters. 
	if(nChar >= 32)
	{
		if(nSelectionStart==nSelectionEnd)
		{
			if(IsInputData(nSelectionStart))
			{
				int nActualInsertionPoint=nSelectionStart;
				if(m_bInsertMode)
					nActualInsertionPoint=InsertAt(nSelectionStart, (TCHAR)nChar);
				else
					nActualInsertionPoint=SetAt   (nSelectionStart, (TCHAR)nChar);
				
				// InsertAt will return -1 if the character cannot be inserted here. 
				if(nActualInsertionPoint >= 0)
					nSelectionStart=nActualInsertionPoint + 1;
				else
					ValidationError();
				
				UpdateInsertionPointForward(nSelectionStart);
			}
			else
			{
				// Beep if trying to type over a literal. 
				ValidationError();
				UpdateInsertionPointForward(nSelectionStart);
			}
		}
		else
		{
			// First delete the remaining selection. 
			// The function will return a valid count if 
			// some input characters were deleted. We use 
			// this value to determine if it makes sense to insert. 
			if(DeleteRange(nSelectionStart, nSelectionEnd))
			{
				// InsertAt will place the character at the next available position, 
				// then return that position
				int nActualInsertionPoint=nSelectionStart;
				nActualInsertionPoint=InsertAt(nSelectionStart, (TCHAR)nChar);
				
				// InsertAt will return -1 if the character cannot be inserted here. 
				if(nActualInsertionPoint >= 0)
					nSelectionStart=nActualInsertionPoint + 1;
				else
					ValidationError();
				
				UpdateInsertionPointForward(nSelectionStart);
			}
			else  // Must be on a literal, so beep and move to a valid location. 
			{
				ValidationError();
				UpdateInsertionPointForward(nSelectionStart);
			}
		}
	}
	else
	{
		if(nChar==VK_BACK)
		{
			// Backspace performs two functions. If there is a selection,
			// then the backspace is the same as deleting the selection.
			// If there is no selection, then the backspace deletes the
			// first non-literal character to the left.
			if(nSelectionStart==nSelectionEnd)
			{
				if (nSelectionStart >= 1)
				{
					while (nSelectionStart>=0)
					{
						nSelectionStart--; // Do the equivalent of a backspace.

						if (DeleteRange(nSelectionStart, nSelectionEnd))
						{
							Update(nSelectionStart);
							break;
						}

						nSelectionEnd--;
					}
				}
			}
			else if(DeleteRange(nSelectionStart, nSelectionEnd))
			{
				Update(nSelectionStart);
			}
			else	// Must be on a literal, so continue moving to left
					// and re-attempt the delete until we either delete
					// a character or run out of characters.
			{
				if (nSelectionStart >= 1)
				{
					while (nSelectionStart>=0)
					{
						nSelectionStart--; // Do the equivalent of a backspace.

						if (DeleteRange(nSelectionStart, nSelectionEnd))
						{
							Update(nSelectionStart);
							break;
						}
						
						nSelectionEnd--;
					}
				}
			}
		}
		else
			// let edit control do its job 
			CEdit::OnChar(nChar, nRepCnt, nFlags);
	}
}

void COXMaskedEdit::OnSetFocus(CWnd* pOldWnd) 
{
	CEdit::OnSetFocus(pOldWnd);
	// The default behavior is to highlight the entire string. 
	// If this is the case, then move the insertion to the first input position. 
	int nSelectionStart=0;
	int nSelectionEnd  =0;
	GetSel(nSelectionStart, nSelectionEnd);
	if((nSelectionStart==0) && ((nSelectionEnd==0) ||
		(nSelectionEnd==GetWindowTextLength())))
	{
		// Only update the insertion point if the entire string is selected. 
		// This will allow the mouse to be used to set the cursor without our interference. 
		UpdateInsertionPointForward(0);
	}
}


LRESULT COXMaskedEdit::OnCut(UINT wParam, LONG lParam)
{
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	if(m_listData.GetCount()==0 || GetStyle()&ES_READONLY)
	{
		return CEdit::Default();
	}

	int nSelectionStart=0;
	int nSelectionEnd  =0;
	GetSel(nSelectionStart, nSelectionEnd);

	// Before updating, let the control do its normal thing. 
	// This will save us the effort of filling the clipboard. 
	CEdit::Default();

// First do our version of the cut. 
	int nDeleteCount=DeleteRange(nSelectionStart, nSelectionEnd);

	// Now we update with our standard mask. 
	Update(nSelectionStart);
	if(nDeleteCount==0)
	{
		// I don't think we want to beep if no input characters were cut. 
		//ValidationError();
	}

	return 0;
}

LRESULT COXMaskedEdit::OnCopy(UINT wParam, LONG lParam)
{
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	// Let copy do its thing and copy the selected text. 
	return CEdit::Default();
}

LRESULT COXMaskedEdit::OnPaste(UINT wParam, LONG lParam) 
{
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	if(m_listData.GetCount()==0 || GetStyle()&ES_READONLY)
	{
		return CEdit::Default();
	}

	int nSelectionStart=0;
	int nSelectionEnd  =0;
	GetSel(nSelectionStart, nSelectionEnd);

	CEdit::Default();

	// This is a real dump paste routine that expects SetInputData to 
	// do the filtering.  There is probably no easy solution to this 
	// task because anything can be pasted.  We could try and match 
	// the literals, but maybe we will get to that later. 
	CString csNewString;
	GetWindowText(csNewString);
	// It is very important that we do not allow the prompt character 
	// in this scenario.  This is because we expect the pasted text 
	// to contain lots of literals and spaces. 
	SetInputData(csNewString, 0, FALSE);
	Update(-1);
	// Setting the insertion point after a paste is tricky because the 
	// expected location is after the last valid pasted character. 
	// Try and determine this location by setting the insertion point 
	// to the first empty location after the specified starting point. 
	int nNewInsertionPoint=GetEmptyInputLocation(nSelectionStart);
	SetSel(nNewInsertionPoint, nNewInsertionPoint);

	return 0;
}

LRESULT COXMaskedEdit::OnClear(UINT wParam, LONG lParam)
{
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	if(m_listData.GetCount()==0 || GetStyle()&ES_READONLY)
	{
		return CEdit::Default();
	}

	int nSelectionStart=0;
	int nSelectionEnd=0;
	GetSel(nSelectionStart, nSelectionEnd);

	// Before updating, let the control do its normal thing. 
	CEdit::Default();

	// First do our version of the cut. 
	int nDeleteCount=DeleteRange(nSelectionStart, nSelectionEnd);

	// Now we update with our standard mask. 
	Update(nSelectionStart);
	if(nDeleteCount==0)
	{
		// I don't think we want to beep if no input characters were cut. 
		//ValidationError();
	}

	return 0;
}


void COXMaskedEdit::OnKillfocus() 
{
	// TODO: Add your control notification handler code here

	// Send OXMEN_VALIDATE notification to parent to validate typed information
	// if the notification was handled. The return value have to be one of these:
	//
	//		-1	-	if typed info is invalid
	//		0	-	typed info is valid but virtual OnValidate function will be 
	//				called to verify typed info
	//		1	-	typed info is valid and OnValidate function won't be called

	CWnd* pParentWnd=GetParent();
	ASSERT(pParentWnd);

	MENMHDR MENMHdr;
	memset(&MENMHdr,0,sizeof(MENMHdr));
	MENMHdr.hdr.hwndFrom=GetSafeHwnd();
	MENMHdr.hdr.idFrom=GetDlgCtrlID();
	MENMHdr.hdr.code=OXMEN_VALIDATE;
	MENMHdr.bValid=TRUE;
	MENMHdr.bDefaultValidation=TRUE;
	MENMHdr.nPosition=0;
	
	pParentWnd->SendMessage(WM_NOTIFY,MENMHdr.hdr.idFrom,(LPARAM)&MENMHdr);

	if(!MENMHdr.bValid || !(MENMHdr.bDefaultValidation ? OnValidate() : TRUE))
	{
		SetFocus();
		ValidationError();
		// set insertion point at the first input location
		UpdateInsertionPointForward(MENMHdr.nPosition);
	}
}

BOOL COXMaskedEdit::OnValidate() 
{
	// by default return TRUE
	// one can overwrite this function to provide validation capability
	// in COXMaskedEdit derived class
	return TRUE;
}

void COXMaskedEdit::OnLButtonDown(UINT nFlags, CPoint point)
{
	if(::GetFocus()!=GetSafeHwnd() && IsInputEmpty())
	{
		SetFocus();
		UpdateInsertionPointForward(0);
	}
	else
	{
		CEdit::OnLButtonDown(nFlags,point);
	}
}

LRESULT COXMaskedEdit::OnSetText(UINT wParam, LPARAM lParam)
{
	UNREFERENCED_PARAMETER(wParam);
	UNREFERENCED_PARAMETER(lParam);

	if(m_listData.GetCount()==0 || GetStyle()&ES_READONLY)
	{
		return CEdit::Default();
	}

	CString csNewString=(LPCTSTR)lParam;
	if(m_nSetTextSemaphor>0)
	{
		LRESULT result=CEdit::Default();
/*		NotifyParent(EN_UPDATE);
		if(m_bNotifyParent)
		NotifyParent(EN_CHANGE);*/
		return result;
	}
	else
	{
		ASSERT(m_nSetTextSemaphor==0);

		csNewString=GetInputData(csNewString);
		// check if secified text is the same as input data
		CString sInputData=GetInputData();
		if(csNewString.Compare(sInputData)==0)
		{
			return TRUE;
		}

		m_bNotifyParent=FALSE;
		SetInputData(csNewString,0,TRUE);
		m_bNotifyParent=TRUE;
		
		return TRUE;
	}
}

int COXMaskedEdit::RPtoLP(int nRealPos) const 
{
	// All COXMaskedEdit functions that take a cursor position as argument interpret it
	// as a real position within the edit control (taking into account all symbols including 
	// literals). But sometimes we want to know which non-literal symbol is at a 
	// particular real position. In that case this function is really useful.

	if(nRealPos<0 || nRealPos>=m_listData.GetCount())
		return -1;
	
	int nLogicalPos=-1;
	CMaskData* pobjData=NULL;
	int nNextInputLocation=0;
	for(POSITION pos=m_listData.FindIndex(nNextInputLocation); pos; nNextInputLocation++)
	{
		pobjData=m_listData.GetNext(pos);
		if(pobjData->IsInputData())
		{
			nLogicalPos++;
		}
		if(nNextInputLocation==nRealPos)
		{
			return pobjData->IsInputData() ? nLogicalPos : -1;
		}
	}

	return -1;
}
		
int COXMaskedEdit::LPtoRP(int nLogicalPos) const 
{
	// All COXMaskedEdit functions that take a cursor position as an argument interpret it
	// as a real position within the edit control (taking into account all symbols including 
	// literals). But sometimes we want to set the cursor at a position before or after a
	// particular non-literal symbol. In that case this function is really useful.

	if(nLogicalPos<0 || nLogicalPos>=m_listData.GetCount())
		return -1;
	
	int nRealPos=-1;
	int nNonLiterals=-1;
	CMaskData* pobjData=NULL;
	int nNextInputLocation=0;
	for(POSITION pos=m_listData.FindIndex(nNextInputLocation); pos; nNextInputLocation++)
	{
		pobjData=m_listData.GetNext(pos);
		nRealPos++;
		if(pobjData->IsInputData())
		{
			nNonLiterals++;
			if(nNonLiterals==nLogicalPos)
			{
				return nRealPos;
			}
		}
	}

	return -1;
}


BOOL COXMaskedEdit::NotifyParent(UINT nNotificationID)
{
	CWnd* pParentWnd=GetParent();
	if(pParentWnd==NULL)
		return FALSE;

	pParentWnd->SendMessage(WM_COMMAND,MAKEWPARAM(GetDlgCtrlID(),nNotificationID),
		(LPARAM)GetSafeHwnd());
	return TRUE;
}

CString COXMaskedEdit::GetText()
{
	if (!::IsWindow(m_hWnd))
		return _T("");

	CString strText;
	GetWindowText(strText);
	return strText;
}


void COXMaskedEdit::SetText(LPCTSTR lpszText)
{
	if (!::IsWindow(m_hWnd))
		return;

	SetWindowText(lpszText);
}