/////////////////////////////////////////////////////////////////////////////
// Name:        wxMaskedTextCtrl.cpp
// Purpose:     wxMaskedTextCtrl: masked edit control class
// Author:      Thomas H‰rtel
// Modified by:
// Created:     2002-11-14
// RCS-ID:      $Id: wxMaskedTextCtrl.cpp,v $
// Copyright:   (c) Thomas H‰rtel
// Licence:     wxWindows licence
/////////////////////////////////////////////////////////////////////////////
// For compilers that support precompilation, includes "wx/wx.h".
#if defined(__WXGTK__) || defined(__WXMOTIF__)
        #include "wx/wx.h"
#endif


#include "wx/wxprec.h"
#include "maskedtextctrl.h"

IMPLEMENT_DYNAMIC_CLASS(wxMaskData, wxObject)
IMPLEMENT_DYNAMIC_CLASS(wxMaskedTextCtrl, wxTextCtrl)

BEGIN_EVENT_TABLE(wxMaskedTextCtrl, wxTextCtrl)
    EVT_KEY_DOWN(wxMaskedTextCtrl::OnKeyDown)
    EVT_CHAR(wxMaskedTextCtrl::OnChar)

	EVT_SET_FOCUS(wxMaskedTextCtrl::OnSetFocus)
	EVT_KILL_FOCUS(wxMaskedTextCtrl::OnKillFocus)
	EVT_LEFT_DOWN(wxMaskedTextCtrl::OnLeftButtonDown)

	EVT_UPDATE_UI(wxID_CUT, wxMaskedTextCtrl::OnCut)
    EVT_UPDATE_UI(wxID_COPY, wxMaskedTextCtrl::OnCopy)
    EVT_UPDATE_UI(wxID_PASTE, wxMaskedTextCtrl::OnPaste)
END_EVENT_TABLE()



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

wxMaskData::~wxMaskData()
{
}

void wxMaskData::operator=(const wxMaskData& src)
{
	m_eType = src.m_eType;
	m_chValue = src.m_chValue;
}

bool wxMaskData::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;
		default:
			break;
	}
	return bIsInputData;
}

bool wxMaskData::IsValidInput(wxChar chNewChar)
{
	bool bIsValidInput=FALSE;
	switch(m_eType)
	{
		// These are the input types.
		case MaskDataTypeDIGIT:
			bIsValidInput=wxIsdigit(chNewChar) != 0;
			break;

		case MaskDataTypeALPHANUMERIC:
			bIsValidInput=wxIsalnum(chNewChar) != 0;
			break;

		case MaskDataTypeALPHABETIC:
		case MaskDataTypeALPHAETICUPPER:
		case MaskDataTypeALPHAETICLOWER:
			bIsValidInput=wxIsalpha(chNewChar) != 0;
			break;

		case MaskDataTypeCHARACTER:
			if((chNewChar >=  32) && (chNewChar <= 126))
				bIsValidInput=TRUE;
			if((chNewChar >= 128) && (chNewChar <= 255))
				bIsValidInput = TRUE;
			break;
		default:
			break;
	}
	return bIsValidInput;
}

wxChar wxMaskData::PreProcessChar(wxChar chNewChar)
{
	wxChar chProcessedChar=chNewChar;
	switch(m_eType)
	{
		case MaskDataTypeALPHAETICUPPER:
			chProcessedChar=(wxChar) wxToupper(chNewChar);
			break;

		case MaskDataTypeALPHAETICLOWER:
			chProcessedChar=(wxChar) wxTolower(chNewChar);
			break;
		default:
			break;
	}
	return chProcessedChar;
}


wxMaskedTextCtrl::wxMaskedTextCtrl(wxWindow *parent, wxWindowID id, const wxString& mask, const wxString &value, const wxPoint &pos, const wxSize &size, int style = 0)
        : wxTextCtrl(parent, id, value, pos, size, style), m_bInsertMode(TRUE), m_chPromptSymbol(chSPACE),
			m_chIntlDecimal(chPERIOD),
			m_chIntlThousands(chCOMMA),
			m_chIntlTime(chCOLON),
			m_chIntlDate(chSLASH),
			m_bAutoTab(FALSE),
			m_bBackwardLocationRight(TRUE),
			m_bNeedValidation(TRUE),
			m_bValidation(FALSE),
			m_dtDateTime(wxInvalidDateTime),
			m_dtMinDateTime(wxInvalidDateTime),
			m_dtMaxDateTime(wxInvalidDateTime)
{
	wxString str = mask;
	SetMask(str);

	if(m_listData.GetCount() != 0)
		UpdateControl();
}

wxMaskedTextCtrl::wxMaskedTextCtrl()
{
}

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

void wxMaskedTextCtrl::DeleteContents()
{
	if(m_listData.GetCount() == 0)
		return;

	m_listData.DeleteContents(TRUE);

	m_bNeedValidation = FALSE;
	m_bValidation = TRUE;
}

wxString wxMaskedTextCtrl::GetMask()
{
	wxString csMask;
	wxMaskData* pobjData=NULL;
	for(unsigned long pos = 0; pos < m_listData.GetCount();pos++)
	{
		pobjData= (wxMaskData *) (m_listData.Item(pos))->GetData();
		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 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 MaskDataTypeDIGIT:
				switch(pobjData->m_eSubType)
				{
					case MaskDataSubTypeDATEDAY:
						csMask += chMaskPlaceholderDATEDAY;
						break;

					case MaskDataSubTypeDATEMONTH:
						csMask += chMaskPlaceholderDATEMONTH;
						break;

					case MaskDataSubTypeDATEYEAR:
						csMask += chMaskPlaceholderDATEYEAR;
						break;

					case MaskDataSubTypeTIMEHOUR:
						csMask += chMaskPlaceholderTIMEHOUR;
						break;

					case MaskDataSubTypeTIMEMINUTE:
						csMask += chMaskPlaceholderTIMEMINUTE;
						break;

					case MaskDataSubTypeTIMESECOND:
						csMask += chMaskPlaceholderTIMESECOND;
						break;

					default:
						csMask += chMaskPlaceholderDIGIT;
						break;
				}
				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 wxMaskedTextCtrl::SetMask(wxString& mask)
{
	if(mask.IsEmpty())
	{
		mask = wxT("");
	}
	DeleteContents();

	wxMaskData* pobjData = NULL;
	for(unsigned int i = 0;i < mask.Length(); i++)
	{
		wxChar chNew = mask[i];
		pobjData = new wxMaskData();
		if(pobjData)
		{
			m_listData.Append(pobjData);
			pobjData->m_eSubType = MaskDataSubTypeNONE;

			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;

				default:
					if(chNew == chMaskPlaceholderLITERALESCAPE)
					{
						// It is the next character that is inserted.
						chNew = mask[++i];
						if(chNew)
						{
							pobjData->m_eType = MaskDataTypeLITERALESCAPE;
							pobjData->m_chValue = chNew;
							break;
						}
					}
					else if(chNew == chMaskPlaceholderDATEDAY)
					{
						// It is the next character that is inserted.
						wxChar chNext = (i < (mask.Length()-1) ? mask[i+1] : wxT('\0'));
						wxChar chBefore = (i > 0 ? mask[i-1] : wxT('\0'));
						if((chNext == chMaskPlaceholderDATEDAY || chBefore == chMaskPlaceholderDATEDAY) && chBefore != chNext)
						{
							pobjData->m_eType = MaskDataTypeDIGIT;
							pobjData->m_eSubType = MaskDataSubTypeDATEDAY;
							pobjData->m_chValue = m_chPromptSymbol;
							break;
						}
					}
					else if(chNew == chMaskPlaceholderDATEMONTH)
					{
						// It is the next character that is inserted.
						wxChar chNext = (i < (mask.Length()-1) ? mask[i+1] : wxT('\0'));
						wxChar chBefore = (i > 0 ? mask[i-1] : wxT('\0'));
						if((chNext == chMaskPlaceholderDATEMONTH || chBefore == chMaskPlaceholderDATEMONTH) && chBefore != chNext)
						{
							pobjData->m_eType = MaskDataTypeDIGIT;
							pobjData->m_eSubType = MaskDataSubTypeDATEMONTH;
							pobjData->m_chValue = m_chPromptSymbol;
							break;
						}
					}
					else if(chNew == chMaskPlaceholderDATEYEAR)
					{
						// It is the next character that is inserted.
						wxChar chNext = (i < (mask.Length()-1) ? mask[i+1] : wxT('\0'));
						wxChar chBefore = (i > 0 ? mask[i-1] : wxT('\0'));
						if(chNext == chMaskPlaceholderDATEYEAR || chBefore == chMaskPlaceholderDATEYEAR)
						{
							pobjData->m_eType = MaskDataTypeDIGIT;
							pobjData->m_eSubType = MaskDataSubTypeDATEYEAR;
							pobjData->m_chValue = m_chPromptSymbol;
							break;
						}
					}
					else if(chNew == chMaskPlaceholderTIMEHOUR)
					{
						// It is the next character that is inserted.
						wxChar chNext = (i < (mask.Length()-1) ? mask[i+1] : wxT('\0'));
						wxChar chBefore = (i > 0 ? mask[i-1] : wxT('\0'));
						if((chNext == chMaskPlaceholderTIMEHOUR || chBefore == chMaskPlaceholderTIMEHOUR) && chBefore != chNext)
						{
							pobjData->m_eType = MaskDataTypeDIGIT;
							pobjData->m_eSubType = MaskDataSubTypeTIMEHOUR;
							pobjData->m_chValue = m_chPromptSymbol;
							break;
						}
					}
					else if(chNew == chMaskPlaceholderTIMEMINUTE)
					{
						// It is the next character that is inserted.
						wxChar chNext = (i < (mask.Length()-1) ? mask[i+1] : wxT('\0'));
						wxChar chBefore = (i > 0 ? mask[i-1] : wxT('\0'));
						if((chNext == chMaskPlaceholderTIMEMINUTE || chBefore == chMaskPlaceholderTIMEMINUTE) && chBefore != chNext)
						{
							pobjData->m_eType = MaskDataTypeDIGIT;
							pobjData->m_eSubType = MaskDataSubTypeTIMEMINUTE;
							pobjData->m_chValue = m_chPromptSymbol;
							break;
						}
					}
					else if(chNew == chMaskPlaceholderTIMESECOND)
					{
						// It is the next character that is inserted.
						wxChar chNext = (i < (mask.Length()-1) ? mask[i+1] : wxT('\0'));
						wxChar chBefore = (i > 0 ? mask[i-1] : wxT('\0'));
						if((chNext == chMaskPlaceholderTIMESECOND || chBefore == chMaskPlaceholderTIMESECOND) && chBefore != chNext)
						{
							pobjData->m_eType = MaskDataTypeDIGIT;
							pobjData->m_eSubType = MaskDataSubTypeTIMESECOND;
							pobjData->m_chValue = m_chPromptSymbol;
							break;
						}
					}

					// Everything else is just a literal.
					pobjData->m_eType = MaskDataTypeLITERAL;
					pobjData->m_chValue = chNew;
					break;
			}
		}
	}
	wxTextCtrl::SetMaxLength(m_listData.GetCount());
	UpdateControl();
}

wxString wxMaskedTextCtrl::GetInputData()
{
	wxString csInputData;

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

	wxMaskData* pobjData=NULL;
	for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
	{
		pobjData= (wxMaskData *) (m_listData.Item(pos))->GetData();
		// Ignore everything that is not data.
		if(pobjData->IsInputData())
			csInputData += pobjData->m_chValue;
	}
	return csInputData;
}

wxString wxMaskedTextCtrl::GetInputData(wxString& value)
{
	wxString csInputData = value;
	unsigned int nSymbolCount = csInputData.Length();
	wxMaskData* pobjData = NULL;
	wxString sToExclude;
	int nStartPos = -1;
	int nEndPos = -1;
	unsigned int nIndex = 0;
	int nRemovedCount = 0;

	for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
	{
		pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
		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 wxMaskedTextCtrl::SetInputData(const wxString& value, int nBeginPos/*=0*/, bool bAllowPrompt/*=TRUE*/)
{
	wxString csFullInput;

	m_bNeedValidation = TRUE;
	m_bValidation = FALSE;


	// Start with existing data and append the new data.
	csFullInput = GetInputData();
	csFullInput = csFullInput.Left(nBeginPos);

	if(bAllowPrompt)
		csFullInput += value;
	else
	{
		// If the prompt symbol is not valid, then
		// add the data one-by-one ignoring any prompt symbols.
		for(unsigned int i = 0;i < value.Length();i++)
		{
			if(value[i] != m_chPromptSymbol)
				csFullInput += value[i];
		}
	}

	bool bCompleteSuccess=TRUE;
	wxString pszReplaceData=csFullInput;
	wxMaskData* pobjData=NULL;
	unsigned int posReplaceData=0;

	for(unsigned long pos = 0; pos < m_listData.GetCount();pos++)
	{
		pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();

		// 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(posReplaceData < pszReplaceData.Length())
			{
				// 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(posReplaceData< pszReplaceData.Length())
				{
					wxChar chReplace = pszReplaceData[posReplaceData++];

					// 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;
		}
	}

	UpdateControl();

	return bCompleteSuccess;
}

wxChar wxMaskedTextCtrl::GetPromptSymbol()
{
	return m_chPromptSymbol;
}

void wxMaskedTextCtrl::SetPromptSymbol(wxChar chNewPromptSymbol)
{
	// The prompt symbol must be a valid edit box symbol.

	if(wxIsprint((int) chNewPromptSymbol))
	{
		// Just for the heck of it, if the prompt symbol changes,
		// go through and replace the existing prompts with the new prompt.
		wxMaskData* pobjData = NULL;
		for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
		{
			pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
			if(pobjData->IsInputData())
			{
				if(pobjData->m_chValue == m_chPromptSymbol)
					pobjData->m_chValue = chNewPromptSymbol;
			}
		}
		m_chPromptSymbol = chNewPromptSymbol;
	}

	// Don't update the insertion point if just setting the prompt symbol.
	UpdateControl(-1);
}

wxChar wxMaskedTextCtrl::GetDecimalSeperator()
{
	return m_chIntlDecimal;
}

void wxMaskedTextCtrl::SetDecimalSeperator(wxChar chNewDecimalSeperator)
{
	// The decimal seperator must be a valid edit box symbol.

	if(wxIsprint((int) chNewDecimalSeperator))
	{
		// Just for the heck of it, if the decimal symbol changes,
		// go through and replace the existing symbols with the new symbol.
		wxMaskData* pobjData = NULL;
		for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
		{
			pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
			if(pobjData->m_eType == MaskDataTypeDECIMALSEPARATOR)
				pobjData->m_chValue = chNewDecimalSeperator;
		}
		m_chIntlDecimal = chNewDecimalSeperator;
	}

	// Don't update the insertion point if just setting the decimal symbol.
	UpdateControl(-1);
}

wxChar wxMaskedTextCtrl::GetThousandsSeperator()
{
	return m_chIntlThousands;
}

void wxMaskedTextCtrl::SetThousandsSeperator(wxChar chNewThousandsSeperator)
{
	// The thousands seperator must be a valid edit box symbol.

	if(wxIsprint((int) chNewThousandsSeperator))
	{
		// Just for the heck of it, if the thousands symbol changes,
		// go through and replace the existing symbols with the new symbol.
		wxMaskData* pobjData = NULL;
		for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
		{
			pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
			if(pobjData->m_eType == MaskDataTypeTHOUSANDSSEPARATOR)
				pobjData->m_chValue = chNewThousandsSeperator;
		}
		m_chIntlThousands = chNewThousandsSeperator;
	}

	// Don't update the insertion point if just setting the thousands symbol.
	UpdateControl(-1);
}

wxChar wxMaskedTextCtrl::GetTimeSeperator()
{
	return m_chIntlTime;
}

void wxMaskedTextCtrl::SetTimeSeperator(wxChar chNewTimeSeperator)
{
	// The time seperator must be a valid edit box symbol.

	if(wxIsprint((int) chNewTimeSeperator))
	{
		// Just for the heck of it, if the time symbol changes,
		// go through and replace the existing symbols with the new symbol.
		wxMaskData* pobjData = NULL;
		for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
		{
			pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
			if(pobjData->m_eType == MaskDataTypeTIMESEPARATOR)
				pobjData->m_chValue = chNewTimeSeperator;
		}
		m_chIntlTime = chNewTimeSeperator;
	}

	// Don't update the insertion point if just setting the time symbol.
	UpdateControl(-1);
}

wxChar wxMaskedTextCtrl::GetDateSeperator()
{
	return m_chIntlDate;
}

void wxMaskedTextCtrl::SetDateSeperator(wxChar chNewDateSeperator)
{
	// The time seperator must be a valid edit box symbol.

	if(wxIsprint((int) chNewDateSeperator))
	{
		// Just for the heck of it, if the date symbol changes,
		// go through and replace the existing symbols with the new symbol.
		wxMaskData* pobjData=NULL;
		for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
		{
			pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
			if(pobjData->m_eType == MaskDataTypeDATESEPARATOR)
				pobjData->m_chValue = chNewDateSeperator;
		}
		m_chIntlDate = chNewDateSeperator;
	}

	// Don't update the insertion point if just setting the date symbol.
	UpdateControl(-1);
}

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

	if(bOnlyInput)
	{
		// If emptying only the data, the iterate through the list
		// of data and replace input data with the prompt symbol.
		wxMaskData* pobjData = NULL;
		for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
		{
			pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
			if(pobjData->IsInputData())
				pobjData->m_chValue=m_chPromptSymbol;
		}
	}
	else
		DeleteContents();

	UpdateControl();
}

bool wxMaskedTextCtrl::IsInputEmpty() const
{
	if(m_listData.GetCount() == 0)
	{
		wxString csInputData;
		csInputData = GetValue();
		return csInputData.IsEmpty();
	}

	wxMaskData* pobjData=NULL;
	for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
	{
		pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
		if (pobjData->IsInputData() && pobjData->m_chValue!=m_chPromptSymbol)
			return FALSE;
	}
	return TRUE;
}

bool wxMaskedTextCtrl::GetInsertMode()
{
	return m_bInsertMode;
}

void wxMaskedTextCtrl::SetInsertMode(bool bInsertMode)
{
	m_bInsertMode=bInsertMode;
}

bool wxMaskedTextCtrl::GetAutoTab()
{
	return m_bAutoTab;
}

void wxMaskedTextCtrl::SetAutoTab(bool bAutoTab)
{
	m_bAutoTab=bAutoTab;
}

void wxMaskedTextCtrl::SetBackwardLocationRight(bool bRight)
{
	m_bBackwardLocationRight = bRight;
}

bool wxMaskedTextCtrl::GetBackwardLocationRight()
{
	return m_bBackwardLocationRight;
}

wxString wxMaskedTextCtrl::ShowMask()
{
	wxString csShow;
	wxMaskData* pobjData=NULL;
	for(unsigned long pos = 0;pos < m_listData.GetCount();pos++)
	{
		pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();

		// 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.
		if(pobjData->m_chValue != chNULL)
			csShow += pobjData->m_chValue;
	}
	return csShow;
}

bool wxMaskedTextCtrl::IsInputData(int nPosition)
{
	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 && nPosition < (int) m_listData.GetCount())
	{
		wxMaskData* pobjData = (wxMaskData*) (m_listData.Item(nPosition))->GetData();
		if(pobjData)
		{
			bIsInputData = pobjData->IsInputData();
		}
	}
	return bIsInputData;
}

int wxMaskedTextCtrl::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;
	wxString csInputData;
	wxMaskData* pobjData = NULL;

	m_bNeedValidation = TRUE;
	m_bValidation = FALSE;

	for(unsigned long pos = 0;pos < m_listData.GetCount(); pos++,nCharIndex++)
	{
		pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
		// 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())
		{
			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 none were deleted.
	return nDeleteCount;
}

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

	int nCharIndex = 0;
	int nInsertionPoint = -1;
	wxString csInputData;
	wxMaskData* pobjData = NULL;
	for(unsigned long pos = 0;pos < m_listData.GetCount(); pos++,nCharIndex++)
	{
		pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
		// 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 wxMaskedTextCtrl::SetAt(int nSelectionStart, wxChar chNewChar)
{
	if(nSelectionStart >= 0)
	{
		m_bNeedValidation = TRUE;
		m_bValidation = FALSE;

		wxMaskData* pobjData=(wxMaskData*) (m_listData.Item(nSelectionStart))->GetData();
		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 wxMaskedTextCtrl::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;

	wxMaskData* pobjData = NULL;
	while(nNextInputLocation < (int) m_listData.GetCount())
	{
		pobjData = (wxMaskData *) (m_listData.Item(nNextInputLocation))->GetData();
		if(pobjData->IsInputData())
			break;

		nNextInputLocation++;
	}

	return nNextInputLocation;
}

int wxMaskedTextCtrl::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;

	wxMaskData* pobjData = NULL;
	for(wxNode* node = m_listData.Item(nNextInputLocation);node;nNextInputLocation--)
	{
		pobjData = (wxMaskData *) node->GetData();
		if(pobjData->IsInputData())
		{
			if(nInitialInputLocation != nNextInputLocation)
			{
				// If we find a valid previous location, then move to the right of it.
				// This backup, then move forward is typical when seeking backward.
				if(m_bBackwardLocationRight)
					nNextInputLocation++;
			}
			break;
		}
		node = node->GetPrevious();
	}
	// 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 wxMaskedTextCtrl::GetEmptyInputLocation(int nSelectionStart)
{
	int nEmptyInputLocation=nSelectionStart;
	if(nEmptyInputLocation < 0)
		nEmptyInputLocation = 0;

	wxMaskData* pobjData=NULL;
	for(unsigned long pos = nEmptyInputLocation;;pos++,nEmptyInputLocation++)
	{
		pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
		if(pobjData->IsInputData())
		{
			if(pobjData->m_chValue == m_chPromptSymbol)
				break;
		}
	}
	return nEmptyInputLocation;
}

void wxMaskedTextCtrl::UpdateControl(int nSelectionStart)
{
	// Update the edit control if it exists.
	wxString sText = ShowMask();
	wxTextCtrl::SetValue(sText);
	// We usually need to update the insertion point.
	if(nSelectionStart >= 0)
		UpdateInsertionPointForward(nSelectionStart);
}

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

	if(m_bAutoTab && (unsigned int) nNewInsertionPoint == m_listData.GetCount())
	{
		wxNavigationKeyEvent eventNav;
		eventNav.SetDirection(TRUE);
		eventNav.SetWindowChange(FALSE);
		eventNav.SetEventObject(this);

		GetParent()->GetEventHandler()->ProcessEvent(eventNav);
	}
	else
	{
		SetSelection(nNewInsertionPoint, nNewInsertionPoint);
	}
}

void wxMaskedTextCtrl::UpdateInsertionPointBackward(int nSelectionStart)
{
	int nNewInsertionPoint=GetPreviousInputLocation(nSelectionStart);
	SetSelection(nNewInsertionPoint, nNewInsertionPoint);
}

void wxMaskedTextCtrl::ValidationError()
{
	wxBell();
}


void wxMaskedTextCtrl::OnKeyDown(wxKeyEvent& event)
{
	// If there is no mask, then exit quickly performing the default operation.
	long keycode = event.GetKeyCode();

	if(m_listData.GetCount()== 0)
	{
		event.Skip();
		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=event.ShiftDown();
	if(keycode == WXK_DELETE)
	{
		long nSelectionStart = 0;
		long nSelectionEnd = 0;
		GetSelection(&nSelectionStart, &nSelectionEnd);

		// 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))
			{
				UpdateControl(nSelectionStart);
			}
			else	// Must be on a literal, so continue moving to right
					// and re-attempting the delete until we either delete
					// a character or run out of characters.
			{
				while (nSelectionEnd != (long) m_listData.GetCount())
				{
					nSelectionStart++;
					nSelectionEnd++; // Do the equivalent of a selection.
					if(DeleteRange(nSelectionStart, nSelectionEnd))
					{
						UpdateControl(nSelectionStart);
						break;
					}
				}
			}
		}
		else if(DeleteRange(nSelectionStart, nSelectionEnd))
		{
			UpdateControl(nSelectionStart);
		}
		else	// Must be on a literal, so continue moving to right
				// and reattempting the delete until we either delete
				// a character or run out of characters.
		{
			while (nSelectionEnd != (long) m_listData.GetCount())
			{
				nSelectionStart++;
				nSelectionEnd++; // Do the equivalent of a selection.
				if(DeleteRange(nSelectionStart, nSelectionEnd))
				{
					UpdateControl(nSelectionStart);
					break;
				}
			}
		}
	}
	else if(keycode == WXK_HOME && !bIsShiftKeyDown)
	{
		// 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.

		UpdateInsertionPointForward(0);
	}
	else if(keycode == WXK_LEFT && !bIsShiftKeyDown)
	{
		// 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.
		long nSelectionStart = 0;
		long nSelectionEnd = 0;
		GetSelection(&nSelectionStart, &nSelectionEnd);

		UpdateInsertionPointBackward(nSelectionStart-1);
	}
	else if(keycode == WXK_UP && !bIsShiftKeyDown)
	{
		// 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.
		long nSelectionStart = 0;
		long nSelectionEnd = 0;
		GetSelection(&nSelectionStart, &nSelectionEnd);

		UpdateInsertionPointBackward(nSelectionStart-1);
	}
	else if(keycode == WXK_RIGHT && !bIsShiftKeyDown)
	{
		// 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.
		long nSelectionStart = 0;
		long nSelectionEnd = 0;
		GetSelection(&nSelectionStart, &nSelectionEnd);

		UpdateInsertionPointForward(nSelectionStart+1);
	}
	else if(keycode == WXK_DOWN && !bIsShiftKeyDown)
	{
		// 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.
		long nSelectionStart = 0;
		long nSelectionEnd = 0;
		GetSelection(&nSelectionStart, &nSelectionEnd);

		UpdateInsertionPointForward(nSelectionStart+1);
	}
	else if(keycode == WXK_INSERT)
	{
		// The standard CEdit control does not support over-typing.
		// This flag is used to manage over-typing internally.
		SetInsertMode(!GetInsertMode());
	}
	else
		event.Skip();
}

void wxMaskedTextCtrl::OnChar(wxKeyEvent& event)
{
	long keycode = event.GetKeyCode();
	// If there is no mask, then exit quickly performing the default operation.
	if(m_listData.GetCount()==0)
	{
		wxTextCtrl::OnChar(event);
		return;
	}

	long nSelectionStart = 0;
	long nSelectionEnd = 0;
	GetSelection(&nSelectionStart, &nSelectionEnd);

	// If character value is above 32, then it is ANSI or Extended.
	// Below 32 are control and navigation characters.
	if(keycode >= 32 && wxIsprint((int) keycode))
	{
		if(nSelectionStart == nSelectionEnd)
		{
			if(IsInputData(nSelectionStart))
			{
				int nActualInsertionPoint=nSelectionStart;
				if(m_bInsertMode)
					nActualInsertionPoint=InsertAt(nSelectionStart, (wxChar) keycode);
				else
					nActualInsertionPoint=SetAt(nSelectionStart, (wxChar) keycode);

				// InsertAt will return -1 if the character cannot be inserted here.
				if(nActualInsertionPoint >= 0)
					nSelectionStart=nActualInsertionPoint + 1;
				else
					ValidationError();

				UpdateControl(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 positition
				int nActualInsertionPoint=nSelectionStart;
				nActualInsertionPoint=InsertAt(nSelectionStart, (wxChar) keycode);

				// InsertAt will return -1 if the character cannot be inserted here.
				if(nActualInsertionPoint >= 0)
					nSelectionStart=nActualInsertionPoint + 1;
				else
					ValidationError();

				UpdateControl(nSelectionStart);
			}
			else  // Must be on a literal, so beep and move to a valid location.
			{
				ValidationError();
				UpdateInsertionPointForward(nSelectionStart);
			}
		}
	}
	else
	{
		if(keycode==WXK_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))
						{
							UpdateControl(nSelectionStart);
							break;
						}

						nSelectionEnd--;
					}
				}
			}
			else if(DeleteRange(nSelectionStart, nSelectionEnd))
			{
				UpdateControl(nSelectionStart);
			}
			else	// Must be on a literal, so continue moving to left
					// and re-attempting 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))
						{
							UpdateControl(nSelectionStart);
							break;
						}
						nSelectionEnd--;
					}
				}
			}
		}
		else
			// let edit control to do its job
			event.Skip();
	}
}

void wxMaskedTextCtrl::OnSetFocus(wxFocusEvent& /*event*/)
{
	wxTextCtrl::SetFocus();
	// The default behavior is to highlight the entire string.
	// If this is the case, then move the insertion to the first input position.
	long nSelectionStart = 0;
	long nSelectionEnd = 0;
	GetSelection(&nSelectionStart, &nSelectionEnd);
	if((nSelectionStart == 0) && ((unsigned long) nSelectionEnd == GetValue().Length()))
	{
		// 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 interfering.
		UpdateInsertionPointForward(0);
	}
}

void wxMaskedTextCtrl::OnKillFocus(wxFocusEvent& /*event*/)
{
	Validate();
}


void wxMaskedTextCtrl::OnCut(wxUpdateUIEvent& event)
{
	if(m_listData.GetCount() == 0)
	{
		event.Skip();
		return;
	}

	long nSelectionStart = 0;
	long nSelectionEnd = 0;
	GetSelection(&nSelectionStart, &nSelectionEnd);

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

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

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

void wxMaskedTextCtrl::OnCopy(wxUpdateUIEvent& event)
{
	// Just let copy do its thing and copy the selected text.
	event.Skip();
}

void wxMaskedTextCtrl::OnPaste(wxUpdateUIEvent& event)
{
	if(m_listData.GetCount() == 0)
	{
		event.Skip();
		return;
	}

	long nSelectionStart = 0;
	long nSelectionEnd = 0;
	GetSelection(&nSelectionStart, &nSelectionEnd);

	wxTextCtrl::OnPaste(event);


	// This is a real dump paste routine that expects SetInputData do
	// 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.
	wxString csNewString = GetValue();

	// 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);
	UpdateControl(-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);
	SetSelection(nNewInsertionPoint, nNewInsertionPoint);
}

void wxMaskedTextCtrl::Clear()
{
	if(m_listData.GetCount() == 0)
		wxTextCtrl::Clear();
	else
	{
		long nSelectionStart = 0;
		long nSelectionEnd = 0;
		GetSelection(&nSelectionStart, &nSelectionEnd);

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

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

bool wxMaskedTextCtrl::Validate()
{
	int day = 0, month = 0, year = 0, hour = 0, minute = 0, second = 0;
	bool bDay = FALSE, bMonth = FALSE, bYear = FALSE, bHour = FALSE, bMinute = FALSE, bSecond = FALSE;
	bool DateValid, TimeValid;
	int year_len = 0;
	unsigned long used = 0;
	long StartPosDay = -1, EndPosDay = -1, StartPosMonth = -1, EndPosMonth = -1, StartPosYear = -1, EndPosYear = -1;
	long StartPosHour = -1, EndPosHour = -1, StartPosMinute = -1, EndPosMinute = -1, StartPosSecond = -1, EndPosSecond = -1;

	if(!m_bNeedValidation)
		return m_bValidation;

	wxMaskData* pobjData=NULL;
	m_dtDateTime = wxInvalidDateTime;
	for(unsigned long pos = 0; pos < m_listData.GetCount();pos++)
	{
		pobjData= (wxMaskData *) (m_listData.Item(pos))->GetData();
		if(pobjData->m_eType == MaskDataTypeDIGIT)
		{
			if(pobjData->m_chValue != m_chPromptSymbol)
				used++;

			wxMaskData* pobjDataNext = NULL;

			switch(pobjData->m_eSubType)
			{
				case MaskDataSubTypeDATEDAY:
					bDay = TRUE;
					if(StartPosDay == -1)
					{
						StartPosDay = pos;
						EndPosDay = pos;
					}
					EndPosDay++;

					pobjDataNext = (wxMaskData *) (pos < (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
					if(pobjData->m_chValue != m_chPromptSymbol)
					{
						day = (day * 10) + (pobjData->m_chValue - 48);

						if(pobjDataNext != NULL)
						{
							if(pobjDataNext->m_eType == MaskDataTypeDIGIT && pobjDataNext->m_eSubType == MaskDataSubTypeDATEDAY)
							{
								day = (day * 10) + (pobjDataNext->m_chValue != m_chPromptSymbol ? (pobjDataNext->m_chValue - 48) : 0);
								EndPosDay++;
								pos++;
							}
						}
					}
					break;

				case MaskDataSubTypeDATEMONTH:
					bMonth = TRUE;
					if(StartPosMonth == -1)
					{
						StartPosMonth = pos;
						EndPosMonth = pos;
					}
					EndPosMonth++;

					pobjDataNext = (wxMaskData *) (pos < (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
					if(pobjData->m_chValue != m_chPromptSymbol)
					{
						month = (month * 10) + (pobjData->m_chValue - 48);

						if(pobjDataNext != NULL)
						{
							if(pobjDataNext->m_eType == MaskDataTypeDIGIT && pobjDataNext->m_eSubType == MaskDataSubTypeDATEMONTH)
							{
								month = (month * 10) + (pobjDataNext->m_chValue != m_chPromptSymbol ? (pobjDataNext->m_chValue - 48) : 0);
								EndPosMonth++;
								pos++;
							}
						}
					}
					break;

				case MaskDataSubTypeDATEYEAR:
					bYear = TRUE;
					if(StartPosYear == -1)
					{
						StartPosYear = pos;
						EndPosYear = pos;
					}
					EndPosYear++;

					if(pobjData->m_chValue != m_chPromptSymbol)
					{
						year = (year * 10) + (pobjData->m_chValue - 48);
						year_len++;
					}
					break;

				case MaskDataSubTypeTIMEHOUR:
					bHour = TRUE;
					if(StartPosHour == -1)
					{
						StartPosHour = pos;
						EndPosHour = pos;
					}
					EndPosHour++;

					pobjDataNext = (wxMaskData *) (pos < (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
					if(pobjData->m_chValue != m_chPromptSymbol)
					{
						hour = (hour * 10) + (pobjData->m_chValue - 48);

						if(pobjDataNext != NULL)
						{
							if(pobjDataNext->m_eType == MaskDataTypeDIGIT && pobjDataNext->m_eSubType == MaskDataSubTypeTIMEHOUR)
							{
								hour = (hour * 10) + (pobjDataNext->m_chValue != m_chPromptSymbol ? (pobjDataNext->m_chValue - 48) : 0);
								EndPosHour++;
								pos++;
							}
						}
					}
					break;

				case MaskDataSubTypeTIMEMINUTE:
					bMinute = TRUE;
					if(StartPosMinute == -1)
					{
						StartPosMinute = pos;
						EndPosMinute = pos;
					}
					EndPosMinute++;

					pobjDataNext = (wxMaskData *) (pos < (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
					if(pobjData->m_chValue != m_chPromptSymbol)
					{

						minute = (minute * 10) + (pobjData->m_chValue - 48);

						if(pobjDataNext != NULL)
						{
							if(pobjDataNext->m_eType == MaskDataTypeDIGIT && pobjDataNext->m_eSubType == MaskDataSubTypeTIMEMINUTE)
							{
								minute = (minute * 10) + (pobjDataNext->m_chValue != m_chPromptSymbol ? (pobjDataNext->m_chValue - 48) : 0);
								EndPosMinute++;
								pos++;
							}
						}
					}
					break;

				case MaskDataSubTypeTIMESECOND:
					bSecond = TRUE;
					if(StartPosSecond == -1)
					{
						StartPosSecond = pos;
						EndPosSecond = pos;
					}
					EndPosSecond++;

					pobjDataNext = (wxMaskData *) (pos < (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
					if(pobjData->m_chValue != m_chPromptSymbol)
					{
						second = (second * 10) + (pobjData->m_chValue - 48);

						if(pobjDataNext != NULL)
						{
							if(pobjDataNext->m_eType == MaskDataTypeDIGIT && pobjDataNext->m_eSubType == MaskDataSubTypeTIMESECOND)
							{
								second = (second * 10) + (pobjDataNext->m_chValue != m_chPromptSymbol ? (pobjDataNext->m_chValue - 48) : 0);
								EndPosSecond++;
								pos++;
							}
						}
					}
					break;
				default:
					break;
			}
		}
	}

	m_bNeedValidation = FALSE;

	if(used == 0)
		return (m_bValidation = TRUE);

	bool minValid = m_dtMinDateTime.IsValid();
	bool maxValid = m_dtMaxDateTime.IsValid();

	DateValid = TRUE;
	if(bDay || bMonth || bYear)
	{
		long pos;
		wxDateTime minDate = m_dtMinDateTime;
		wxDateTime maxDate = m_dtMaxDateTime;
		wxDateTime Date = wxDateTime(1, wxDateTime::Jan, (wxDateTime::Now()).GetYear(), 0, 0, 0, 0);

		if((bDay && day == 0) || (bMonth && month == 0))
			DateValid = FALSE;
		else if((bDay && day > 31) || (bMonth && month > 12))
			DateValid = FALSE;

		if(DateValid)
		{
			if(bYear && year_len < 3 && year < 100)
			{
				if(year_len == 0)
				{
					year = (wxDateTime::Now()).GetYear();
					year_len = 1;
				}
				else
					year += ((wxDateTime::Now()).GetYear() / 1000) * 1000;
			}

			if(bYear)
			{
				if(StartPosYear != -1)
				{
					wxString Year_Str = wxString::Format(wxT("%04d"), year);

					for(pos = StartPosYear; pos < EndPosYear;pos++)
						SetAt(pos, Year_Str[(int) (Year_Str.Length() + pos - EndPosYear)]);
				}

				Date.SetYear(year);

				if(minValid)
				{
					minDate.SetMonth(wxDateTime::Jan);
					minDate.SetDay(1);
				}

				if(maxValid)
				{
					maxDate.SetMonth(wxDateTime::Dec);
					maxDate.SetDay(31);
				}
			}
			else
			{
				if(minValid)
					minDate.SetYear(Date.GetYear());

				if(maxValid)
					maxDate.SetYear(Date.GetYear());
			}

			if(bMonth)
			{
				if(StartPosMonth != -1)
				{
					wxString Month_Str = wxString::Format(wxT("%02d"), month);

					for(pos = StartPosMonth; pos < EndPosMonth;pos++)
						SetAt(pos, Month_Str[(int) (pos - StartPosMonth)]);
				}

				Date.SetMonth(wxDateTime::Month(month-1));

				if(minValid)
					minDate.SetMonth(m_dtMinDateTime.GetMonth());

				if(maxValid)
				{
					maxDate.SetDay(1);
					maxDate.SetMonth(m_dtMaxDateTime.GetMonth());
					maxDate.SetDay(m_dtMaxDateTime.GetNumberOfDays(m_dtMaxDateTime.GetMonth(), m_dtMaxDateTime.GetYear()));
				}
			}
			else
			{
				if(minValid)
					minDate.SetMonth(m_dtMinDateTime.GetMonth());

				if(maxValid)
					maxDate.SetMonth(m_dtMaxDateTime.GetMonth());
			}

			if(bDay)
			{
				if(StartPosDay != -1)
				{
					wxString Day_Str = wxString::Format(wxT("%02d"), day);

					for(pos = StartPosDay; pos < EndPosDay;pos++)
						SetAt(pos, Day_Str[(int) (pos - StartPosDay)]);
				}

				if(day > Date.GetNumberOfDays(Date.GetMonth(), Date.GetYear()))
					DateValid = FALSE;

				if(DateValid)
				{
					Date.SetDay(day);

					if(minValid)
						minDate.SetDay(m_dtMinDateTime.GetDay());

					if(maxValid)
						maxDate.SetDay(m_dtMaxDateTime.GetDay());
				}
			}
			else
			{
				if(minValid)
					minDate.SetDay(1);

				if(maxValid)
					maxDate.SetDay(m_dtMaxDateTime.GetNumberOfDays(m_dtMaxDateTime.GetMonth(), m_dtMaxDateTime.GetYear()));
			}

			if(DateValid)
			{
				if(!Date.IsValid())
					DateValid = FALSE;

				if(minValid && m_bValidation)
				{
					if(Date.IsEarlierThan(minDate))
						DateValid = FALSE;
				}

				if(maxValid && m_bValidation)
				{
					if(Date.IsLaterThan(maxDate))
						DateValid = FALSE;
				}

				m_dtDateTime = Date;
			}
		}
	}

	TimeValid = TRUE;
	if(bHour || bMinute || bSecond)
	{
		long pos;
		wxDateTime minDate = m_dtMinDateTime;
		wxDateTime maxDate = m_dtMaxDateTime;
		wxDateTime Date;

		if(m_dtDateTime.IsValid())
			Date = m_dtDateTime;
		else
			Date = wxDateTime(1, wxDateTime::Jan, (wxDateTime::Now()).GetYear(), 0, 0, 0, 0);

		if((bHour && hour > 23) || (bMinute && minute > 59) || (bSecond && second > 59))
			TimeValid = FALSE;

		if(TimeValid)
		{
			if(bHour)
			{
				if(StartPosHour != -1)
				{
					wxString Hour_Str = wxString::Format(wxT("%02d"), hour);

					for(pos = StartPosHour; pos < EndPosHour;pos++)
						SetAt(pos, Hour_Str[(int) (pos - StartPosHour)]);
				}

				Date.SetHour(hour);
			}
			else
			{
				if(minValid)
					minDate.SetHour(0);

				if(maxValid)
					maxDate.SetHour(1);
			}

			if(bMinute)
			{
				if(StartPosMinute != -1)
				{
					wxString Minute_Str = wxString::Format(wxT("%02d"), minute);

					for(pos = StartPosMinute; pos < EndPosMinute;pos++)
						SetAt(pos, Minute_Str[(int) (pos - StartPosMinute)]);
				}

				Date.SetMinute(minute);
			}
			else
			{
				if(minValid)
					minDate.SetMinute(0);

				if(maxValid)
					maxDate.SetMinute(59);
			}

			if(bSecond)
			{
				if(StartPosSecond != -1)
				{
					wxString Second_Str = wxString::Format(wxT("%02d"), second);

					for(pos = StartPosSecond; pos < EndPosSecond;pos++)
						SetAt(pos, Second_Str[(int) (pos - StartPosSecond)]);
				}

				Date.SetSecond(second);
			}
			else
			{
				if(minValid)
					minDate.SetSecond(0);

				if(maxValid)
					maxDate.SetSecond(59);
			}

			if(!Date.IsValid())
				TimeValid = FALSE;

			if(TimeValid)
			{
				if(minValid)
				{
					if(Date.IsEarlierThan(minDate))
						TimeValid = FALSE;
				}

				if(maxValid)
				{
					if(Date.IsLaterThan(maxDate))
						TimeValid = FALSE;
				}

				m_dtDateTime.Set(hour, minute, second);
			}
		}
	}

	UpdateControl(-1);

	m_bValidation = DateValid & TimeValid;
	m_bNeedValidation = FALSE;

	return m_bValidation;
}

bool wxMaskedTextCtrl::IsValid(void)
{
	if(m_bNeedValidation)
		Validate();

	return m_bValidation;
}

void wxMaskedTextCtrl::OnLeftButtonDown(wxMouseEvent& event)
{
	if(FindFocus()!= this && IsInputEmpty())
	{
		SetFocus();
		UpdateInsertionPointForward(0);
	}
	else
		event.Skip();
}

bool wxMaskedTextCtrl::IsEmpty(void) const
{
	return IsInputEmpty();
}

wxDateTime wxMaskedTextCtrl::GetMinDateTime(void)
{
	return m_dtMinDateTime;
}

void wxMaskedTextCtrl::SetMinDateTime(wxDateTime& minDateTime)
{
	m_dtMinDateTime = minDateTime;
}

wxDateTime wxMaskedTextCtrl::GetMaxDateTime(void)
{
	return m_dtMaxDateTime;
}

void wxMaskedTextCtrl::SetMaxDateTime(wxDateTime& maxDateTime)
{
	m_dtMaxDateTime = maxDateTime;
}

void wxMaskedTextCtrl::SetDateTimeRange(wxDateTime& minDateTime, wxDateTime& maxDateTime)
{
	m_dtMinDateTime = minDateTime;
	m_dtMaxDateTime = maxDateTime;
}

void wxMaskedTextCtrl::SetValue(const wxString& value)
{
	if(m_listData.GetCount() == 0)
		wxTextCtrl::SetValue(value);
	else
		SetInputData(value, 0, TRUE);
}

void wxMaskedTextCtrl::SetDateTimeValue(wxDateTime& value)
{
	wxMaskData *pobjData = NULL, *pobjDataNext = NULL;
	wxString str_Value;
	long length;
	int year_len;

	if(m_listData.GetCount() == 0 && value.IsValid())
		return;

	for(long pos = 0;pos < (long) m_listData.GetCount();pos++)
	{
		pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
		if(pobjData->m_eType == MaskDataTypeDIGIT)
		{
			if(pobjData->m_eSubType == MaskDataSubTypeDATEDAY)
			{
				pobjDataNext = (wxMaskData *) (pos < (long) (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
				if(pobjDataNext)
				{
					if(pobjData->m_eType == MaskDataTypeDIGIT && pobjData->m_eSubType == MaskDataSubTypeDATEDAY)
						str_Value = wxString::Format(wxT("%02d"), value.GetDay());
					else
						str_Value = wxString::Format(wxT("%d"), value.GetDay() % 10);
				}

			}
			else if(pobjData->m_eSubType == MaskDataSubTypeDATEMONTH)
			{
				pobjDataNext = (wxMaskData *) (pos < (long) (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
				if(pobjDataNext)
				{
					if(pobjData->m_eType == MaskDataTypeDIGIT && pobjData->m_eSubType == MaskDataSubTypeDATEMONTH)
						str_Value = wxString::Format(wxT("%02d"), value.GetMonth());
					else
						str_Value = wxString::Format(wxT("%d"), value.GetMonth() % 10);
				}
			}
			else if(pobjData->m_eSubType == MaskDataSubTypeDATEYEAR)
			{
				year_len = 1;
				for(long i = pos; i < (long) m_listData.GetCount();i++)
				{
					pobjDataNext = (wxMaskData *) (m_listData.Item(pos+1))->GetData();
					if(pobjDataNext->m_eType == MaskDataTypeDIGIT && pobjDataNext->m_eSubType == MaskDataSubTypeDATEYEAR)
						year_len++;
					else
						break;
				}
				str_Value = (wxString::Format(wxT("%04d"), value.GetYear())).Right(year_len);;
			}
			else if(pobjData->m_eSubType == MaskDataSubTypeTIMEHOUR)
			{
				pobjDataNext = (wxMaskData *) (pos < (long) (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
				if(pobjDataNext)
				{
					if(pobjData->m_eType == MaskDataTypeDIGIT && pobjData->m_eSubType == MaskDataSubTypeTIMEHOUR)
						str_Value = wxString::Format(wxT("%02d"), value.GetHour());
					else
						str_Value = wxString::Format(wxT("%d"), value.GetHour() % 10);
				}
			}
			else if(pobjData->m_eSubType == MaskDataSubTypeTIMEMINUTE)
			{
				pobjDataNext = (wxMaskData *) (pos < (long) (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
				if(pobjDataNext)
				{
					if(pobjData->m_eType == MaskDataTypeDIGIT && pobjData->m_eSubType == MaskDataSubTypeTIMEMINUTE)
						str_Value = wxString::Format(wxT("%02d"), value.GetMinute());
					else
						str_Value = wxString::Format(wxT("%d"), value.GetMinute() % 10);
				}
			}
			else if(pobjData->m_eSubType == MaskDataSubTypeTIMESECOND)
			{
				pobjDataNext = (wxMaskData *) (pos < (long) (m_listData.GetCount()-1) ? (m_listData.Item(pos+1))->GetData() : NULL);
				if(pobjDataNext)
				{
					if(pobjData->m_eType == MaskDataTypeDIGIT && pobjData->m_eSubType == MaskDataSubTypeTIMESECOND)
						str_Value = wxString::Format(wxT("%02d"), value.GetSecond());
					else
						str_Value = wxString::Format(wxT("%d"), value.GetSecond() % 10);
				}
			}

			length = str_Value.Length();
			if(pos + length > (long) m_listData.GetCount())
				length = (long) str_Value.Length() - pos;

			for(long i = pos; pos < (long) (i + length);pos++)
				SetAt(pos, str_Value[(int) (pos-i)]);
		}
	}
	UpdateControl();
}

wxDateTime wxMaskedTextCtrl::GetDateTimeValue()
{
	if(m_bNeedValidation)
		Validate();

	return m_dtDateTime;
}

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

	if(nRealPos < 0 || nRealPos >= (int) m_listData.GetCount())
		return -1;

	int nLogicalPos = -1;
	wxMaskData* pobjData = NULL;
	int nNextInputLocation = 0;
	for(unsigned long pos = nNextInputLocation;pos < m_listData.GetCount();pos++, nNextInputLocation++)
	{
		pobjData = (wxMaskData *) (m_listData.Item(pos))->GetData();
		if(pobjData->IsInputData())
		{
			nLogicalPos++;
		}
		if(nNextInputLocation == nRealPos)
		{
			return pobjData->IsInputData() ? nLogicalPos : -1;
		}
	}

	return -1;
}

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

	if(nLogicalPos < 0 || nLogicalPos >= (int) m_listData.GetCount())
		return -1;

	int nRealPos = -1;
	int nNonLiterals = -1;
	wxMaskData* pobjData = NULL;
	int nNextInputLocation = 0;
	for(unsigned long pos = nNextInputLocation; pos < m_listData.GetCount();pos++, nNextInputLocation++)
	{
		pobjData= (wxMaskData *) (m_listData.Item(pos))->GetData();
		nRealPos++;
		if(pobjData->IsInputData())
		{
			nNonLiterals++;
			if(nNonLiterals == nLogicalPos)
			{
				return nRealPos;
			}
		}
	}

	return -1;
}
