1462 lines
38 KiB
C++
1462 lines
38 KiB
C++
// 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);
|
|
} |