// XTPMaskEditT.h interface for the CXTPMaskEditT class.
//
// This file is a part of the XTREME TOOLKIT PRO MFC class library.
// (c)1998-2011 Codejock Software, All Rights Reserved.
//
// THIS SOURCE FILE IS THE PROPERTY OF CODEJOCK SOFTWARE AND IS NOT TO BE
// RE-DISTRIBUTED BY ANY MEANS WHATSOEVER WITHOUT THE EXPRESSED WRITTEN
// CONSENT OF CODEJOCK SOFTWARE.
//
// THIS SOURCE CODE CAN ONLY BE USED UNDER THE TERMS AND CONDITIONS OUTLINED
// IN THE XTREME TOOLKIT PRO LICENSE AGREEMENT. CODEJOCK SOFTWARE GRANTS TO
// YOU (ONE SOFTWARE DEVELOPER) THE LIMITED RIGHT TO USE THIS SOFTWARE ON A
// SINGLE COMPUTER.
//
// CONTACT INFORMATION:
// support@codejock.com
// http://www.codejock.com
//
/////////////////////////////////////////////////////////////////////////////

//{{AFX_CODEJOCK_PRIVATE
#if !defined(__XTMASKEDITEX_H__)
#define __XTMASKEDITEX_H__
//}}AFX_CODEJOCK_PRIVATE

#if _MSC_VER >= 1000
#pragma once
#endif // _MSC_VER >= 1000

// Depricated
#define CXTMaskEditT CXTPMaskEditT

#include "Common/XTPVC80Helpers.h"

//////////////////////////////////////////////////////////////////////
// Summary:
//     CXTPMaskEditT is a template class. It allows text masking to be
//     applied to the control to format it for special editing restrictions.
//////////////////////////////////////////////////////////////////////
template <class TBase>
class CXTPMaskEditT : public TBase
{

public:

	//-----------------------------------------------------------------------
	// Summary:
	//     Constructs a CXTPMaskEditT object
	//-----------------------------------------------------------------------
	CXTPMaskEditT()
		: m_nStartChar(0)
		, m_nEndChar(0)
		, m_bOverType(FALSE)
		, m_bUseMask(TRUE)
		, m_bRedo(FALSE)
		, m_bModified(FALSE)
		, m_strWindowText(_T(""))
		, m_strMask(_T(""))
		, m_strLiteral(_T(""))
		, m_strDefault(_T(""))
		, m_strUndoBuffer(_T(""))
		, m_chPrompt(_T('_'))
		, m_bUpdateUndo(TRUE)
	{
	}

public:

	//-----------------------------------------------------------------------
	// Parameters:
	//     bUseMask - TRUE to enable the mask. FALSE to disable the mask.
	// Summary:
	//     Call this member function to enable or disable the mask for the mask
	//     edit control.
	//-----------------------------------------------------------------------
	void SetUseMask(BOOL bUseMask) {
		m_bUseMask = bUseMask;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to determine if the mask for the edit
	//     control can be used.
	// Returns:
	//     TRUE if the mask can be used, otherwise returns FALSE.
	//-----------------------------------------------------------------------
	BOOL CanUseMask() const {
		return m_bUseMask && m_hWnd && ((GetStyle() & ES_READONLY) == 0) && !m_strMask.IsEmpty();
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to enable or disable type over, also
	//     known as insert mode.
	// Parameters:
	//     bOverType - TRUE to enable type over.
	//-----------------------------------------------------------------------
	void SetOverType(BOOL bOverType) {
		m_bOverType = bOverType;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to determine if type over has been enabled.
	// Returns:
	//     TRUE if type over is enabled, otherwise returns FALSE.
	//-----------------------------------------------------------------------
	BOOL CanOverType() const {
		return m_bOverType;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to determine if the index specified
	//     by 'iPos' is a valid index for the currently displayed edit text.
	// Parameters:
	//     iPos - Index of the character to check.
	// Returns:
	//     TRUE if the index is valid, otherwise returns FALSE.
	//-----------------------------------------------------------------------
	BOOL PosInRange(int iPos) const {
		return ((iPos >= 0) && (iPos < m_strLiteral.GetLength()));
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function retrieves the character that is currently used as
	//     the mask prompt. The mask prompt indicates that the field is editable.
	// Returns:
	//     A TCHAR data type.
	//-----------------------------------------------------------------------
	TCHAR GetPromptChar() const {
		return m_chPrompt;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This helper member function retrieves the string contains nLength prompt characters.
	// Parameters:
	//     nLength - Length of string to create.
	// Returns:
	//     CString contains nLength prompt characters
	//-----------------------------------------------------------------------
	CString GetPromptString(int nLength) const {
		CString strPrompt;

		while (nLength--)
			strPrompt += m_chPrompt;

		return strPrompt;
	}


	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to set the prompt character that is
	//     displayed to the user that indicates the field can be edited.
	// Parameters:
	//     ch - A TCHAR data type.
	//     bAutoReplace - TRUE to auto replace
	//-----------------------------------------------------------------------
	void SetPromptChar(TCHAR ch, BOOL bAutoReplace = TRUE)
	{
		if (m_chPrompt == ch)
			return;

		if (bAutoReplace)
		{
			GetMaskState();

			for (int i = 0; i < m_strLiteral.GetLength(); i++)
				if (m_strLiteral[i] == m_chPrompt) m_strLiteral.SetAt(i, ch);

			for (int j = 0; j < m_strWindowText.GetLength(); j++)
				if (m_strWindowText[j] == m_chPrompt) m_strWindowText.SetAt(j, ch);

			SetMaskState();
		}

		m_chPrompt = ch;

	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to perform a cut operation using the
	//     currently selected text.
	//-----------------------------------------------------------------------
	afx_msg BOOL MaskCut()
	{
		if (!CanUseMask())
			return (BOOL)DefWindowProc(WM_CUT, 0, 0);

		MaskCopy();
		MaskClear();

		return TRUE;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to perform a copy operation using the
	//     currently selected text.
	//-----------------------------------------------------------------------
	afx_msg BOOL MaskCopy()
	{
		if (!CanUseMask())
			return (BOOL)DefWindowProc(WM_COPY, 0, 0);

		GetMaskState();

		CString strMaskedText = GetMaskedText(m_nStartChar, m_nEndChar);
		CopyToClipboard(strMaskedText);

		return TRUE;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to perform a replace operation using the
	//     currently selected text.
	// Parameters:
	//     lpszNewText - Text to replace.
	//-----------------------------------------------------------------------
	void MaskReplaceSel(LPCTSTR lpszNewText)
	{

		ASSERT(CanUseMask());

		if (m_nStartChar != m_nEndChar)
			MaskDeleteSel();

		int x = m_nStartChar, nNewTextLen = (int)_tcslen(lpszNewText);
		int nWindowTextLen = m_strWindowText.GetLength();

		if (x >= nWindowTextLen)
			return;

		for (int i = 0; i < nNewTextLen; ++i)
		{
			TCHAR ch = lpszNewText[i];

			if (ch == m_chPrompt || CheckChar(ch, x))
			{
				InsertCharAt(x, ch);
				x++;

				while (x < nWindowTextLen && !IsPromptPos(x))
					x++;

				if (x >= m_strWindowText.GetLength())
					break;
			}
		}
		CorrectPosition(x);
		m_nStartChar = m_nEndChar = x;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to perform a paste operation using the
	//     current clipboard text.
	//-----------------------------------------------------------------------
	afx_msg BOOL MaskPaste()
	{
		if (!CanUseMask())
			return (BOOL)DefWindowProc(WM_PASTE, 0, 0);

		GetMaskState();

		if (!OpenClipboard())
			return FALSE;

	#ifndef _UNICODE
		HGLOBAL hglbPaste = ::GetClipboardData(CF_TEXT);
	#else
		HGLOBAL hglbPaste = ::GetClipboardData(CF_UNICODETEXT);
	#endif

		if (hglbPaste != NULL)
		{
			TCHAR* lpszClipboard = (TCHAR*)GlobalLock(hglbPaste);

			MaskReplaceSel(lpszClipboard);

			GlobalUnlock(hglbPaste);

			SetMaskState();
		}
		::CloseClipboard();

		return TRUE;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to delete the selection.
	//-----------------------------------------------------------------------
	void MaskDeleteSel()
	{
		if (m_nStartChar == m_nEndChar)
			return;

		CString strMaskedText = GetMaskedText(m_nEndChar);
		SetMaskedText(strMaskedText, m_nStartChar, FALSE);

		m_nEndChar = m_nStartChar;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to clear the current text selection.
	//-----------------------------------------------------------------------
	afx_msg BOOL MaskClear()
	{
		if (!CanUseMask())
			return (BOOL)DefWindowProc(WM_CLEAR, 0, 0);

		GetMaskState();

		MaskDeleteSel();

		SetMaskState();

		return TRUE;

	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to undo the previous action.
	//-----------------------------------------------------------------------
	afx_msg BOOL MaskUndo()
	{
		if (!CanUseMask())
		{
			return (BOOL)DefWindowProc(EM_UNDO, 0, 0);
		}
		else
		{
			MaskGetSel();

			if (m_bRedo)
			{
				SetWindowText(m_strRedoBuffer);
			}
			else
			{
				GetWindowText(m_strRedoBuffer);
				SetWindowText(m_strUndoBuffer);
			}

			m_bRedo = !m_bRedo;
			m_bModified = TRUE;

			m_nEndChar = m_nStartChar;
			SetSel(m_nStartChar, m_nEndChar);
		}

		return TRUE;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is called to select all text in the mask edit
	//     control.
	//-----------------------------------------------------------------------
	afx_msg void MaskSelectAll()
	{
		if (!CanUseMask())
		{
			SetSel(0, -1);
		}
		else
		{
			m_nStartChar = 0;
			CorrectPosition(m_nStartChar);
			SetSel(m_nStartChar, -1);
		}
	}

	//-----------------------------------------------------------------------}
	// Summary:
	//     This member function is called to determine if the text has been modified.
	// Returns:
	//     TRUE if the text has changed, otherwise returns FALSE.
	//-----------------------------------------------------------------------
	BOOL IsModified() const {
		return m_bModified;
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This method called to set masked text for the edit control.
	// Parameters:
	//     lpszMaskedText - Text string without mask.
	//     iPos          - Start position from which current masked text
	//                     will be updated.
	//                     Default value 0 (the first position).
	//     bUpdateWindow - If TRUE - edit control window text and selection
	//                     will be updated, otherwise only internal members
	//                     take changes.
	// Remarks:
	//     If a mask is used, then the mask will be applied to the text
	//     in lpszMaskedText.
	//-----------------------------------------------------------------------
	void SetMaskedText(LPCTSTR lpszMaskedText, int iPos = 0, BOOL bUpdateWindow = TRUE)
	{
		int nMaskedTextLength = (int)_tcslen(lpszMaskedText);

		m_strWindowText = m_strWindowText.Left(iPos);
		int nIndex = 0;

		for (; (iPos <  m_strLiteral.GetLength()) && (nIndex < nMaskedTextLength) ; iPos++)
		{
			TCHAR uChar = lpszMaskedText[nIndex];

			if (IsPromptPos(iPos) && ((uChar == m_chPrompt) || ProcessMask(uChar, iPos)))
			{
				m_strWindowText += (TCHAR)uChar;
				nIndex ++;
			}
			else
			{
				m_strWindowText += m_strLiteral[iPos];
			}
		}

		if (bUpdateWindow)
		{
			SetMaskState(FALSE);
		}
		else
		{
			CorrectWindowText();
		}
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function will set the mask for the edit control.
	// Parameters:
	//     lpszMask    - The format for the mask field. For example, if you wanted to set
	//                   the mask for a phone number, and you only wanted digits to be entered,
	//                   your mask might look like this; _T("(000) 000-0000").
	//     lpszLiteral - The literal format is entered here. Wherever you place an underscore
	//                   ('_') is where the user will be allowed to enter data only. Using
	//                   the phone number example; _T("(___) ___-____").
	//     lpszDefault - Text that is to be displayed when the control is initialized. For
	//                   example; _T("(800) 555-1212"). If NULL, 'lpszLiteral' is used to initialize
	//                   the edit text.
	// Remarks:
	//     The values that can be set are:
	//     <TABLE>
	//          <b>Mask Character</b>  <b>Description</b>
	//          ---------------------  ------------------------
	//          0                      Numeric (0-9)
	//          9                      Numeric (0-9) or space (' ')
	//          #                      Numeric (0-9) or space (' ') or ('+') or ('-')
	//          L                      Alpha (a-Z)
	//          ?                      Alpha (a-Z) or space (' ')
	//          A                      Alpha numeric (0-9 and a-Z)
	//          a                      Alpha numeric (0-9 and a-Z) or space (' ')
	//          &                      All print character only
	//          H                      Hex digit (0-9 and A-F)
	//          X                      Hex digit (0-9 and A-F) and space (' ')
	//          >                      Forces characters to upper case (A-Z)
	//          <                      Forces characters to lower case (a-z)
	//     </TABLE>
	//-----------------------------------------------------------------------
	virtual BOOL SetEditMask(LPCTSTR lpszMask, LPCTSTR lpszLiteral, LPCTSTR lpszDefault=NULL)
	{
		ASSERT(lpszMask);
		ASSERT(lpszLiteral);

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

		ASSERT(m_strMask.GetLength() == m_strLiteral.GetLength());

		if (m_strMask.GetLength() != m_strLiteral.GetLength())
			return FALSE;

		if (lpszDefault == NULL)
		{
			m_strWindowText = m_strDefault = lpszLiteral;
		}
		else
		{
			m_strWindowText = m_strDefault = lpszDefault;

			if (m_strDefault.GetLength() != m_strLiteral.GetLength())
			{
				SetMaskedText(m_strDefault, 0, FALSE);
				m_strDefault = m_strWindowText;
			}
		}

		ASSERT(m_strWindowText.GetLength() == m_strLiteral.GetLength());

		// set the window text for the control.
		m_bRedo = FALSE;
		m_bModified = FALSE;
		SetWindowText(m_strWindowText);

		m_strUndoBuffer = m_strWindowText;
		return TRUE;

	}

	//-----------------------------------------------------------------------
	// Summary:
	//     Converts character to Upper/Lower case.
	// Parameters:
	//     nChar - Char to be converted
	//     bUpperCase  - TRUE to convert to upper case
	// Returns:
	//     Converted character.
	//-----------------------------------------------------------------------
	TCHAR ConvertUnicodeAlpha(TCHAR nChar, BOOL bUpperCase) const
	{
		CString strTemp(nChar);
		if (bUpperCase) strTemp.MakeUpper(); else strTemp.MakeLower();
		return strTemp[0];
	}



	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is used internally to validate the character indicated
	//     by 'nChar'.
	// Parameters:
	//     nChar - Contains the character code value of the key.
	//     nPos - Sting length.
	// Returns:
	//     TRUE if successful, otherwise returns FALSE.
	//-----------------------------------------------------------------------
	virtual BOOL CheckChar(TCHAR& nChar, int nPos)
	{
		// do not use mask
		if (!CanUseMask())
			return FALSE;

		// control character, OK
		if (!IsPrintChar(nChar))
			return TRUE;

		// make sure the string is not longer than the mask
		if (nPos >= m_strMask.GetLength())
			return FALSE;

		if (!IsPromptPos(nPos))
			return FALSE;

		return ProcessMask(nChar, nPos);
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     This member function is used internally to process the character passed
	//     in by 'nChar' whose index is specified by 'nEndPos'.
	// Parameters:
	//     nChar - Contains the character code value of the key.
	//     nEndPos - Index of character in display string.
	// Returns:
	//     TRUE if successful, otherwise returns FALSE.
	//-----------------------------------------------------------------------
	virtual BOOL ProcessMask(TCHAR& nChar, int nEndPos)
	{
		ASSERT(nEndPos < m_strMask.GetLength());
		if (nEndPos < 0 || nEndPos >= m_strMask.GetLength())
			return FALSE;

		// check the key against the mask
		switch (m_strMask.GetAt(nEndPos))
		{
		case '0':       // digit only //completely changed this
			return _istdigit(nChar);

		case '9':       // digit or space
			return _istdigit(nChar) || _istspace(nChar);

		case '#':       // digit or space or '+' or '-'
			return _istdigit(nChar) || (_istspace(nChar) || nChar == _T('-') || nChar == _T('+'));

		case 'd':       // decimal
			return _istdigit(nChar) || (_istspace(nChar) || nChar == _T('-') || nChar == _T('+') || nChar == _T('.') || nChar == _T(','));

		case 'L':       // alpha only
			return IsAlphaChar(nChar);

		case '?':       // alpha or space
			return IsAlphaChar(nChar) || _istspace(nChar);

		case 'A':       // alpha numeric only
			return _istalnum(nChar) || IsAlphaChar(nChar);

		case 'a':       // alpha numeric or space
			return _istalnum(nChar) || IsAlphaChar(nChar) || _istspace(nChar);

		case '&':       // all print character only
			return IsPrintChar(nChar);

		case 'H':       // hex digit
			return _istxdigit(nChar);

		case 'X':       // hex digit or space
			return _istxdigit(nChar) || _istspace(nChar);

		case '>':
			if (IsAlphaChar(nChar))
			{
				nChar = ConvertUnicodeAlpha(nChar, TRUE);
				return TRUE;
			}
			return FALSE;

		case '<':
			if (IsAlphaChar(nChar))
			{
				nChar = ConvertUnicodeAlpha(nChar, FALSE);
				return TRUE;
			}
			return FALSE;
		}

		return FALSE;
	}

public:

	//-----------------------------------------------------------------------
	// Summary:
	//     Used by class CWinApp to translate window messages before they are dispatched to theTranslateMessage andDispatchMessage Windows functions.
	// Parameters:
	//     pMsg - Points to a MSG structure that contains the message to process.
	// Returns:
	//     Nonzero if the message was translated and should not be dispatched; 0 if the message was not translated and should be dispatched.
	//-----------------------------------------------------------------------
	virtual BOOL PreTranslateMessage(MSG* pMsg)
	{
		if (!CanUseMask())
			return TBase::PreTranslateMessage(pMsg);


		// intercept Ctrl+C (copy), Ctrl+V (paste), Ctrl+X (cut) and Ctrl+Z (undo)
		// before CEdit base class gets a hold of them.

		if (pMsg->message == WM_KEYDOWN)
		{
			if (::GetKeyState(VK_SUBTRACT) < 0)
			{
				OnChar('-', 1, 1);
				return TRUE;
			}

			if (::GetKeyState(VK_ADD) < 0)
			{
				OnChar('+', 1, 1);
				return TRUE;
			}

			if (::GetKeyState(VK_CONTROL) < 0)
			{
				switch (pMsg->wParam)
				{
				case 'X':
				case 'x':
					{
						MaskCut();
						return TRUE;
					}

				case 'C':
				case 'c':
					{
						MaskCopy();
						return TRUE;
					}

				case 'V':
				case 'v':
					{
						MaskPaste();
						return TRUE;
					}

				case 'Z':
				case 'z':
					{
						MaskUndo();
						return TRUE;
					}
				}
			}

		}

		return TBase::PreTranslateMessage(pMsg);

	}

	//-----------------------------------------------------------------------
	// Summary:
	//     Deletes character in specified position
	// Parameters:
	//     iPos - Position for character to be deleted.
	//-----------------------------------------------------------------------
	void DeleteCharAt(int iPos)
	{
		ASSERT(PosInRange(iPos));

		if (!PosInRange(iPos))
			return;

		CString strMaskedText = GetMaskedText(iPos + 1) + m_chPrompt;

		SetMaskedText(strMaskedText, iPos, FALSE);
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     Inserts character to specified position.
	// Parameters:
	//     iPos - Position to insert
	//     nChar - Character to be inserted
	//-----------------------------------------------------------------------
	void InsertCharAt(int iPos, TCHAR nChar)
	{
		ASSERT(PosInRange(iPos));

		if (!PosInRange(iPos))
			return;

		CString strMaskedText = CString(nChar) + GetMaskedText(iPos);

		SetMaskedText(strMaskedText, iPos, FALSE);
	}

	//-----------------------------------------------------------------------
	// Summary:
	//     Copies text to system clipboard
	// Parameters:
	//     strText - Text to be copied
	// Returns:
	//     TRUE if successful; otherwise returns FALSE
	//-----------------------------------------------------------------------
	BOOL CopyToClipboard(const CString& strText)
	{
		if (!OpenClipboard())
			return FALSE;

		::EmptyClipboard();

		int iLen = (strText.GetLength() + 1) * sizeof(TCHAR);

		HGLOBAL hglbCopy = ::GlobalAlloc(GMEM_MOVEABLE, iLen);

		if (hglbCopy == NULL)
		{
			::CloseClipboard();
			return FALSE;
		}

		LPTSTR lptstrCopy = (TCHAR*)GlobalLock(hglbCopy);
		STRCPY_S(lptstrCopy, strText.GetLength() + 1, (LPCTSTR)strText);
		GlobalUnlock(hglbCopy);

	#ifndef _UNICODE
		::SetClipboardData(CF_TEXT, hglbCopy);
	#else
		::SetClipboardData(CF_UNICODETEXT, hglbCopy);
	#endif

		if (!::CloseClipboard())
			return FALSE;

		return TRUE;

	}

	//-----------------------------------------------------------------------
	// Summary:
	//     Retrieves masked text of the control
	// Parameters:
	//     nStartPos - Start position
	//     nEndPos - End position
	// Returns:
	//     Masked text of the control.
	//-----------------------------------------------------------------------
	CString GetMaskedText(int nStartPos = 0, int nEndPos = -1) const
	{
		if (nEndPos == -1)
			nEndPos = m_strWindowText.GetLength();
		else
			nEndPos = min(nEndPos, m_strWindowText.GetLength());

		CString strBuffer;

		for (int i = nStartPos; i < nEndPos; ++i)
		{
			if (IsPromptPos(i))
			{
				strBuffer += m_strWindowText[i];
			}
		}

		return strBuffer;

	}


protected:


//{{AFX_CODEJOCK_PRIVATE
	afx_msg void OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags)
	{
		if (!CanUseMask())
		{
			TBase::OnKeyDown(nChar, nRepCnt, nFlags); // default processing.
			return;
		}

		BOOL bShift = (::GetKeyState(VK_SHIFT) < 0);
		BOOL bCtrl = (::GetKeyState(VK_CONTROL) < 0);

		switch (nChar)
		{
		case VK_UP:
		case VK_LEFT:
		case VK_HOME:
			{
				TBase::OnKeyDown(nChar, nRepCnt, nFlags);

				GetMaskState(FALSE);

				int iStartChar = m_nStartChar;
				CorrectPosition(iStartChar, FALSE);

				if (m_nStartChar < iStartChar)
				{
					m_nStartChar = iStartChar;

					if (!bShift)
						m_nEndChar = iStartChar;
				}

				SetMaskState();
			}
			return;

		case VK_DOWN:
		case VK_RIGHT:
		case VK_END:
			{
				TBase::OnKeyDown(nChar, nRepCnt, nFlags);

				GetMaskState(FALSE);

				int iEndChar = m_nEndChar;
				CorrectPosition(iEndChar);

				if (m_nEndChar > iEndChar)
				{
					m_nEndChar = iEndChar;

					if (!bShift)
						m_nStartChar = iEndChar;
				}

				SetMaskState();
			}
			return;


		case VK_INSERT:
			{
				if (bCtrl)
				{
					MaskCopy();
				}
				else if (bShift)
				{
					MaskPaste();
				}
				else
				{
					m_bOverType = !m_bOverType; // set the type-over flag
				}

			}
			return;

		case VK_DELETE:
			{
				GetMaskState();

				if (m_nStartChar == m_nEndChar)
				{
					m_nEndChar = m_nStartChar +1;
				}
				else if (bShift)
				{
					MaskCopy();
				}

				MaskDeleteSel();
				SetMaskState();
			}
			return;

		case VK_SPACE:
			{
				GetMaskState();

				if (!PosInRange(m_nStartChar) || !IsPromptPos(m_nStartChar))
				{
					NotifyPosNotInRange();
					return;
				}

				TCHAR chSpace = _T(' ');

				if (!ProcessMask(chSpace, m_nStartChar))
					chSpace = m_chPrompt;

				ProcessChar(chSpace);

				SetMaskState();
			}
			return;

		case VK_BACK:
			{
				GetMaskState(FALSE);

				if (((m_nStartChar > 0) || (m_nStartChar == 0 && m_nEndChar != 0))  &&
					(m_nStartChar <= m_strLiteral.GetLength()))
				{
					if (m_nStartChar == m_nEndChar)
					{
						m_nStartChar--;
						CorrectPosition(m_nStartChar, FALSE);

						if (m_bOverType && PosInRange(m_nStartChar))
						{
							m_strWindowText.SetAt(m_nStartChar, m_strDefault[m_nStartChar]);
							m_nEndChar = m_nStartChar;
						}
					}

					MaskDeleteSel();
					SetMaskState();
				}
				else
				{
					NotifyPosNotInRange();
				}
			}
			return;
		}

		TBase::OnKeyDown(nChar, nRepCnt, nFlags);

	}

	void ProcessChar(TCHAR nChar)
	{
		int iLen = m_strLiteral.GetLength();

		if (m_nStartChar >= iLen)
		{
			NotifyPosNotInRange();
			return;
		}

		if (m_nStartChar != m_nEndChar)
		{
			MaskDeleteSel();
		}

		ASSERT(m_nStartChar == m_nEndChar);

		CorrectPosition(m_nStartChar);

		if (CanOverType())
		{
			m_strWindowText.SetAt(m_nStartChar, nChar);
		}
		else
		{
			InsertCharAt(m_nStartChar, nChar);
		}

		if (m_nStartChar < iLen)
			m_nStartChar++;

		CorrectPosition(m_nStartChar);

		m_nEndChar = m_nStartChar;
	}

	afx_msg void OnChar(UINT nChar, UINT nRepCnt, UINT nFlags)
	{
		if (!CanUseMask())
		{
			TBase::OnChar(nChar, nRepCnt, nFlags);
			return;
		}

		switch (nChar)
		{
		case VK_SPACE:
		case VK_BACK:
			return; // handled in WM_KEYDOWN
		}

		GetMaskState();

		if (!PosInRange(m_nStartChar) || !IsPromptPos(m_nStartChar))
		{
			NotifyPosNotInRange();
			return;
		}

		TCHAR ch = (TCHAR)nChar;

		if (!CheckChar(ch, m_nStartChar))
		{
			NotifyInvalidCharacter(ch, m_strMask[m_nStartChar]);
			return;
		}

		if (IsPrintChar(ch))
		{
			ProcessChar(ch);
			SetMaskState(FALSE);
		}
		else
		{
			if (nChar != 127)
				TBase::OnChar(nChar, nRepCnt, nFlags);
		}
	}

	afx_msg void OnSetFocus(CWnd* pOldWnd)
	{
		TBase::OnSetFocus(pOldWnd);

		if (!CanUseMask())
		{
			return;
		}

		MaskGetSel();
		CorrectPosition(m_nStartChar);

		m_nEndChar = m_nStartChar;
		SetSel(m_nStartChar, m_nEndChar);

	}

	afx_msg void OnUpdateEditUndo(CCmdUI* pCmdUI)
	{
		if (!CanUseMask())
		{
			pCmdUI->Enable(CanUndo());
		}
		else
		{
			pCmdUI->Enable(m_bModified);
		}

	}
//}}AFX_CODEJOCK_PRIVATE


//{{AFX_CODEJOCK_PRIVATE

	// Some goodies
	BOOL CorrectPosition(int& iPos, BOOL bForward = TRUE) // used internally
	{
		int iLen = m_strLiteral.GetLength();

		if (IsPromptPos(iPos))
			return TRUE;

		if (bForward)
		{
			for (; iPos < iLen; iPos++)
			{
				if (IsPromptPos(iPos))
					return TRUE;
			}

			for (; iPos >= 0; iPos--)
			{
				if (IsPromptPos(iPos - 1))
					return FALSE;
			}
		}
		else
		{
			for (; iPos >= 0; iPos--)
			{
				if (IsPromptPos(iPos))
					return TRUE;
			}

			for (; iPos < iLen; iPos++)
			{
				if (IsPromptPos(iPos))
					return FALSE;
			}
		}

		return FALSE;
	}

	virtual BOOL IsPrintChar(TCHAR nChar)
	{
		return _istprint(nChar) || IsAlphaChar(nChar);
	}
	//-----------------------------------------------------------------------
	// Summary:
	//     This method determines if nChar is alpha character
	// Parameters:
	//     nChar - Character need to test
	// Returns:
	//     TRUE if nChar is alpha character.
	//-----------------------------------------------------------------------
	virtual BOOL IsAlphaChar(TCHAR nChar)
	{
		if (_istalpha(nChar))
			return TRUE;

		if (ConvertUnicodeAlpha(nChar, TRUE) != nChar)
			return TRUE;

		if (ConvertUnicodeAlpha(nChar, FALSE) != nChar)
			return TRUE;

		return FALSE;
	}

	virtual void NotifyPosNotInRange()
	{
		::MessageBeep((UINT)-1);
	}

	virtual void NotifyInvalidCharacter(TCHAR /*nChar*/, TCHAR /*chMask*/)
	{
		::MessageBeep((UINT)-1);
	}

	BOOL IsPromptPos(int nPos) const
	{
		return IsPromptPos(m_strLiteral, nPos);
	}

	BOOL IsPromptPos(const CString& strLiteral, int nPos) const
	{
		return (nPos >= 0 && nPos < strLiteral.GetLength()) && (strLiteral[nPos] == m_chPrompt);
	}

	void CorrectWindowText()
	{
		int nLiteralLength = m_strLiteral.GetLength();
		int nWindowTextLength = m_strWindowText.GetLength();

		if (nWindowTextLength > nLiteralLength)
		{
			m_strWindowText = m_strWindowText.Left(nLiteralLength);
		}
		else if (nWindowTextLength < nLiteralLength)
		{
			m_strWindowText += m_strLiteral.Mid(nWindowTextLength, nLiteralLength - nWindowTextLength);
		}
	}

	void GetMaskState(BOOL bCorrectSelection = TRUE)
	{
		if (!m_hWnd)
			return;

		ASSERT(m_bUseMask);

		MaskGetSel();
		GetWindowText(m_strWindowText);

		ASSERT(m_strDefault.GetLength() == m_strLiteral.GetLength());
		ASSERT(m_strMask.GetLength() == m_strLiteral.GetLength());

		CorrectWindowText();

		if (bCorrectSelection)
		{
			CorrectPosition(m_nStartChar);
			CorrectPosition(m_nEndChar);

			if (m_nEndChar < m_nStartChar)
				m_nEndChar = m_nStartChar;
		}
	}

	void MaskGetSel() // To allow CEdit and CRichEditCtrl
	{
		if (m_hWnd)
		{
			SendMessage(EM_GETSEL, (WPARAM)&m_nStartChar, (LPARAM)&m_nEndChar);
		}
	}

	void SetMaskState(BOOL bUpdateUndo = TRUE)
	{
		if (!m_hWnd)
			return;

		ASSERT(m_bUseMask);

		CString strWindowText;
		GetWindowText(strWindowText);

		CorrectWindowText();
		HideCaret();

		if (strWindowText != m_strWindowText)
		{
			SetWindowText(m_strWindowText);

			if (bUpdateUndo || m_bUpdateUndo)
				m_strUndoBuffer = strWindowText;

			m_bRedo = FALSE;
			m_bModified = TRUE;
		}

		m_bUpdateUndo = bUpdateUndo;

		SetSel(m_nStartChar, m_nEndChar);
		ShowCaret();
	}


//}}AFX_CODEJOCK_PRIVATE


	//-----------------------------------------------------------------------
	// Summary:
	//     The framework calls this member function when the user selects an item from a menu
	// Parameters:
	//     wParam - The low-order word of wParam identifies the command ID of the menu item, control, or accelerator. The high-order word of wParam specifies the notification message if the message is from a control. If the message is from an accelerator, the high-order word is 1. If the message is from a menu, the high-order word is 0
	//     lParam - Identifies the control that sends the message if the message is from a control. Otherwise, lParam is 0.
	// Returns:
	//     An application returns nonzero if it processes this message; otherwise 0.
	//-----------------------------------------------------------------------
	virtual BOOL OnCommand(WPARAM wParam, LPARAM lParam)
	{
		switch (LOWORD(wParam))
		{
			case ID_EDIT_CUT:
				MaskCut();
				return TRUE;

			case ID_EDIT_COPY:
				MaskCopy();
				return TRUE;

			case ID_EDIT_PASTE:
				MaskPaste();
				return TRUE;

			case ID_EDIT_CLEAR:
				MaskClear();
				return TRUE;

			case ID_EDIT_UNDO:
				MaskUndo();
				return TRUE;

			case ID_EDIT_SELECT_ALL:
				MaskSelectAll();
				return TRUE;
		}
		return TBase::OnCommand(wParam, lParam);
	}

protected:

	int         m_nStartChar;       // Current position of the first character in the current selection.
	int         m_nEndChar;         // Current position of the first non-selected character past the end of the current selection.
	BOOL        m_bUseMask;         // TRUE to use the edit mask.
	BOOL        m_bOverType;        // TRUE to over type the text, set with VK_INSERT key press.
	BOOL        m_bRedo;            // TRUE to redo, or FALSE to undo.
	BOOL        m_bModified;        // TRUE if mask edit has been modified.
	TCHAR       m_chPrompt;         // Prompt character used to identify the text entry.
	CString     m_strMask;          // Buffer that holds the actual edit mask value.
	CString     m_strDefault;       // Contains the edit controls default display text.
	CString     m_strUndoBuffer;    // Holds the contents of the undo buffer.
	CString     m_strRedoBuffer;    // Holds the contents of the redo buffer.
	CString     m_strWindowText;    // Buffer that holds the actual edit text.
	CString     m_strLiteral;       // Literal format that restricts where the user can enter text.
	BOOL        m_bUpdateUndo;      // TRUE to update undo
};

//{{AFX_CODEJOCK_PRIVATE
#define ON_MESSAGE_BOOL(message, memberFxn) \
	{ message, 0, 0, 0, AfxSig_bv, \
		(AFX_PMSG)(AFX_PMSGW)(BOOL (AFX_MSG_CALL CWnd::*)(void))&memberFxn },

#define ON_MASKEDIT_REFLECT\
	ON_MESSAGE_BOOL(WM_CUT, MaskCut)\
	ON_MESSAGE_BOOL(WM_PASTE, MaskPaste)\
	ON_MESSAGE_BOOL(WM_CLEAR, MaskClear)\
	ON_MESSAGE_BOOL(WM_UNDO, MaskUndo)\
	ON_MESSAGE_BOOL(WM_COPY, MaskCopy)\
	ON_WM_KEYDOWN()\
	ON_WM_CHAR()\
	ON_WM_SETFOCUS

//}}AFX_CODEJOCK_PRIVATE
//////////////////////////////////////////////////////////////////////


#endif // #if !defined(__XTMASKEDITEX_H__)
