//========================================================================== // Class Implementation : // COXEdit & COXNumericEdit & COXCurrencyEdit //========================================================================== // Implementation file : OXEdit.cpp // Version: 9.3 // 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. //////////////// // COXEdit implements a edit control which could be set to use // user-defined color and font to draw text. Also you can show tooltip // #include "StdAfx.h" #include "OXEdit.h" #include #include #pragma warning (push, 3) #include #include #pragma warning (pop) #include "UTBStrOp.h" #include "UTB64Bit.h" using namespace std; #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[]=__FILE__; #endif #if _MFC_VER<=0x0421 static void RemoveCharFromString(CString& sText, TCHAR chToRemove) { int nPos=sText.Find(chToRemove); while(nPos!=-1) { sText=sText.Left(nPos)+sText.Mid(nPos+1); nPos=sText.Find(chToRemove); } } static void ReplaceCharInString(CString& sText, TCHAR chToBeReplaced, TCHAR chToReplaceTo) { CString sCopy=sText; CString sToReplaceTo(chToReplaceTo); int nPos=sCopy.Find(chToBeReplaced); sText.Empty(); while(nPos!=-1) { sText=sText+sCopy.Left(nPos)+sToReplaceTo; sCopy=sCopy.Mid(nPos+1); nPos=sCopy.Find(chToBeReplaced); } sText+=sCopy; } #endif ////////////////////////////////////////////////// ////////////////////////////////////////////////// IMPLEMENT_DYNCREATE(COXEdit, CEdit) BEGIN_MESSAGE_MAP(COXEdit, CEdit) //{{AFX_MSG_MAP(COXEdit) ON_WM_CTLCOLOR_REFLECT() ON_WM_SIZE() ON_WM_DESTROY() ON_WM_KEYDOWN() ON_WM_CHAR() ON_WM_SETFOCUS() 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() /////////////////// // Constructor // // --- In : // --- Out : // --- Returns : // --- Effect : Constructs the object COXEdit::COXEdit() { m_clrText=::GetSysColor(COLOR_WINDOWTEXT); m_clrBack=::GetSysColor(COLOR_WINDOW); m_clrReadOnlyBack=::GetSysColor(COLOR_BTNFACE); m_clrReadOnlyFont=::GetSysColor(COLOR_GRAYTEXT); m_bInsertMode=TRUE; TCHAR chMask[2]; chMask[0]=OXEDITABLETEXT_SYMBOL; chMask[1]=_T('\0'); VERIFY(SetMask(chMask)); m_bInitialized=FALSE; m_nSetTextSemaphor=0; m_bNotifyParent=TRUE; m_bHighlightOnSetFocus=FALSE; m_bNoSaveFont=FALSE; } void COXEdit::SetTextColor(COLORREF clrText, BOOL bRedraw/*=TRUE*/) { if(m_clrText!=clrText) { m_clrText=clrText; if(bRedraw) RedrawWindow(); } } void COXEdit::SetBkColor(COLORREF clrBack, BOOL bRedraw/*=TRUE*/) { if(m_clrBack!=clrBack) { m_clrBack=clrBack; if(bRedraw) RedrawWindow(); } } BOOL COXEdit::SetToolTipText(LPCTSTR sText) { if(::IsWindow(m_ttc.GetSafeHwnd())) { m_sToolTipText=sText; m_ttc.UpdateTipText(m_sToolTipText, this, ID_OXEDIT_TOOLTIP); return TRUE; } return FALSE; } ////////////////// // Handle reflected WM_CTLCOLOR to set custom control color and font. HBRUSH COXEdit::CtlColor(CDC* pDC, UINT nCtlColor) { UNREFERENCED_PARAMETER(nCtlColor); COLORREF clrText=GetTextColor(); COLORREF clrBk=GetBkColor(); if(!IsWindowEnabled()) { clrText=::GetSysColor(COLOR_GRAYTEXT); clrBk=::GetSysColor(COLOR_BTNFACE); } else if(GetStyle()&ES_READONLY) { clrText=m_clrReadOnlyFont; clrBk=m_clrReadOnlyBack; } pDC->SetTextColor(clrText); pDC->SetBkColor(clrBk); if((HFONT)m_font!=NULL) { pDC->SelectObject(&m_font); } static CBrush brush; if((HBRUSH)brush!=NULL) brush.DeleteObject(); brush.CreateSolidBrush(clrBk); return (HBRUSH)brush; } // handle it to pass a mouse message to a tool tip control for processing BOOL COXEdit::PreTranslateMessage(MSG* pMsg) { if(::IsWindow(m_ttc.GetSafeHwnd())) { m_ttc.Activate(TRUE); m_ttc.RelayEvent(pMsg); } return CEdit::PreTranslateMessage(pMsg); } // handle it to initialize tooltip control and save the original size // of edit control window void COXEdit::PreSubclassWindow() { _AFX_THREAD_STATE* pThreadState=AfxGetThreadState(); // hook not already in progress if(pThreadState->m_pWndInit==NULL && !m_bInitialized) { if(!InitControl()) TRACE(_T("COXEdit::PreSubclassWindow: failed to initialize edit control\n")); } CEdit::PreSubclassWindow(); } // handle it to update original size of window and if m_bFitToText is TRUE then // resize it to fit to its text void COXEdit::OnSize(UINT nType, int cx, int cy) { CEdit::OnSize(nType, cx, cy); // update the original size of the edit control window CRect rect; GetWindowRect(rect); CWnd* pParent=GetParent(); if(pParent!=NULL) { pParent->ScreenToClient(rect); } // Change information about window's rect in tooltip control if(::IsWindow(m_ttc.GetSafeHwnd())) { CToolInfo toolInfo; if(m_ttc.GetToolInfo(toolInfo,this,ID_OXEDIT_TOOLTIP)) { toolInfo.rect.left=0; toolInfo.rect.top=0; toolInfo.rect.right=rect.Width(); toolInfo.rect.bottom=rect.Height(); UTBStr::tcscpy(toolInfo.szText, _tcsclen(m_sToolTipText) + 1, m_sToolTipText); m_ttc.SetToolInfo(&toolInfo); } } } BOOL COXEdit::InitControl() { ASSERT(::IsWindow(GetSafeHwnd())); GetLocaleSettings(); // Create the tooltip CRect rect; GetClientRect(rect); m_ttc.Create(this); m_ttc.AddTool(this, m_sToolTipText, rect, ID_OXEDIT_TOOLTIP); // save font VERIFY(SaveFont()); CString sText; GetWindowText(sText); EmptyData(); SetInputData(sText); m_bInitialized=TRUE; return TRUE; } BOOL COXEdit::Create(DWORD dwStyle, const RECT& rect, CWnd* pParentWnd, UINT nID) { BOOL bResult=CEdit::Create(dwStyle, rect, pParentWnd, nID); if(bResult) { // Initialize edit control if(!InitControl()) { TRACE(_T("COXEdit::Create: failed to initialize edit control")); return FALSE; } } return bResult; } void COXEdit::OnDestroy() { if(::IsWindow(m_ttc.GetSafeHwnd())) { m_ttc.DelTool(this, ID_OXEDIT_TOOLTIP); m_ttc.DestroyWindow(); } CEdit::OnDestroy(); } BOOL COXEdit::SaveFont() { LOGFONT lf; CFont* pFont=GetFont(); if(pFont!=NULL) { if(!pFont->GetLogFont(&lf)) return FALSE; } if((HFONT)m_font!=NULL) m_font.DeleteObject(); if(pFont!=NULL) { if(!m_font.CreateFontIndirect(&lf)) return FALSE; } return TRUE; } LRESULT COXEdit::WindowProc(UINT message, WPARAM wParam, LPARAM lParam) { // TODO: Add your specialized code here and/or call the base class #if defined (_WINDLL) #if defined (_AFXDLL) AFX_MANAGE_STATE(AfxGetAppModuleState()); #else AFX_MANAGE_STATE(AfxGetStaticModuleState()); #endif #endif LRESULT lResult=CEdit::WindowProc(message, wParam, lParam); switch(message) { case WM_SETFONT: { if(!m_bNoSaveFont) { VERIFY(SaveFont()); } break; } case WM_SETTINGCHANGE: { CString sInputData=GetInputData(); GetLocaleSettings(); EmptyData(TRUE); VERIFY(SetInputData(sInputData)); break; } } return lResult; } void COXEdit::OnKeyDown(UINT nChar, UINT nRepCnt, UINT nFlags) { // 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. if((GetStyle()&ES_READONLY)==ES_READONLY) { CEdit::OnKeyDown(nChar, nRepCnt, nFlags); return; } BOOL bIsShiftKeyDown=::GetAsyncKeyState(VK_SHIFT)<0; BOOL bIsCtrlKeyDown=::GetAsyncKeyState(VK_CONTROL)<0; if(nChar==VK_DELETE) { if (bIsShiftKeyDown)//cut operation { CEdit::OnKeyDown(nChar, nRepCnt, nFlags); return; } int nSelectionStart=0; int nSelectionEnd=0; GetSel(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)) { Update(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<= m_arrLeftLiterals.GetSize()+m_arrInputData.GetSize()) { 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 right // and reattempting the delete until we either delete // a character or run out of characters. { while(nSelectionEnd<= m_arrLeftLiterals.GetSize()+m_arrInputData.GetSize()) { 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); if(!IsInputPosition(nSelectionStart)) UpdateInsertionPointForward(nSelectionStart); } } else if(nChar==VK_END) { // 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); if(!IsInputPosition(nSelectionStart)) UpdateInsertionPointBackward(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) { if (!bIsShiftKeyDown && !bIsCtrlKeyDown) { // The standard CEdit control does not support over-typing. // This flag is used to manage over-typing internally. SetInsertMode(!GetInsertMode()); } else CEdit::OnKeyDown(nChar, nRepCnt, nFlags);//copy or paste } else { CEdit::OnKeyDown(nChar, nRepCnt, nFlags); } } void COXEdit::OnChar(UINT nChar, UINT nRepCnt, UINT nFlags) { if((GetStyle()&ES_READONLY)==ES_READONLY) { CEdit::OnChar(nChar, nRepCnt, nFlags); return; } int nSelectionStart=0; int nSelectionEnd=0; GetSel(nSelectionStart, nSelectionEnd); BOOL bAcceptReturnKey=(GetStyle()&ES_MULTILINE) && (GetStyle()&ES_WANTRETURN); if(nChar==10) { nChar=VK_RETURN; bAcceptReturnKey=(GetStyle()&ES_MULTILINE); } // If character value is above 32, then it is ANSI or Extended. // Below 32 are control and navigation characters. if(nChar>=32 || (nChar==VK_RETURN && bAcceptReturnKey)) { if(nSelectionStart==nSelectionEnd) { if(GetStyle()&ES_NUMBER && nChar!=VK_RETURN && (nChar<_T('0') || nChar>_T('9'))) { CEdit::OnChar(nChar, nRepCnt, nFlags); return; } if(IsInputPosition(nSelectionStart)) { int nActualInsertionPoint=nSelectionStart; if(m_bInsertMode) { if(nChar==VK_RETURN) { // InsertAt will return -1 if the character cannot be inserted here. nActualInsertionPoint=InsertAt(nSelectionStart,_T('\r')); if(nActualInsertionPoint>=0) { nSelectionStart=nActualInsertionPoint+1; nActualInsertionPoint=InsertAt(nSelectionStart,_T('\n')); } } else { nActualInsertionPoint=InsertAt(nSelectionStart,(TCHAR)nChar); } } else { if(nChar==VK_RETURN) { nActualInsertionPoint=SetAt(nSelectionStart,_T('\r')); // SetAt will return -1 if the character cannot be set here. if(nActualInsertionPoint>=0) { nSelectionStart=nActualInsertionPoint+1; nActualInsertionPoint=SetAt(nSelectionStart,_T('\n')); } } 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(); } Update(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; if(nChar==VK_RETURN) { // InsertAt will return -1 if the character cannot be inserted here. nActualInsertionPoint=InsertAt(nSelectionStart,_T('\r')); if(nActualInsertionPoint>=0) { nSelectionStart=nActualInsertionPoint+1; nActualInsertionPoint=InsertAt(nSelectionStart,_T('\n')); } } else { nActualInsertionPoint=InsertAt(nSelectionStart,(TCHAR)nChar); } // InsertAt will return -1 if the character cannot be inserted here. if(nActualInsertionPoint>=0) { nSelectionStart=nActualInsertionPoint+1; } else { ValidationError(); } Update(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) { while( nSelectionStart > 0 && nSelectionStart == nSelectionEnd ) { 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-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)) { Update(nSelectionStart); break; } nSelectionEnd--; } } } } else { // let edit control to do its job CEdit::OnChar(nChar, nRepCnt, nFlags); } } } void COXEdit::OnSetFocus(CWnd* pOldWnd) { CEdit::OnSetFocus(pOldWnd); if(!GetHighlightOnSetFocus()) { // 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==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 interfering. UpdateInsertionPointForward(0); } } m_bNoSaveFont=TRUE; SetFont(&m_font); m_bNoSaveFont=FALSE; } LONG COXEdit::OnCut(UINT wParam, LONG lParam) { UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(lParam); 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; } LONG COXEdit::OnCopy(UINT wParam, LONG lParam) { UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(lParam); // Just let copy do its thing and copy the selected text. CEdit::Default(); return 0; } LONG COXEdit::OnPaste(UINT wParam, LONG lParam) { UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(lParam); int nSelectionStart=0; int nSelectionEnd=0; GetSel(nSelectionStart, nSelectionEnd); CEdit::Default(); // 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. CString sNewString; GetWindowText(sNewString); if(nSelectionStart!=nSelectionEnd) { CString sLeftLiterals=GetLeftLiterals(); CString sRightLiterals=GetRightLiterals(); if(sNewString.Find(sLeftLiterals)==0) sNewString=sNewString.Mid(sLeftLiterals.GetLength()); int nPosition=sNewString.Find(sRightLiterals); if(nPosition!=-1 && nPosition+sRightLiterals.GetLength()==sNewString.GetLength()) sNewString=sNewString.Left(nPosition); } EmptyData(TRUE); SetInputData(sNewString,0); // 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=GetNextInputLocation(nSelectionStart); SetSel(nNewInsertionPoint,nNewInsertionPoint); return 0; } LONG COXEdit::OnClear(UINT wParam, LONG lParam) { UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(lParam); TRACE(_T("COXEdit::OnClear\n")); 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); UNREFERENCED_PARAMETER(nDeleteCount); // Now we update with our standard mask. Update(nSelectionStart); return 0; } void COXEdit::OnLButtonDown(UINT nFlags, CPoint point) { CEdit::OnLButtonDown(nFlags,point); int nSelectionStart=0; int nSelectionEnd=0; GetSel(nSelectionStart,nSelectionEnd); if(nSelectionStart==nSelectionEnd && !IsInputPosition(nSelectionStart)) UpdateInsertionPointForward(0); } LRESULT COXEdit::OnSetText(UINT wParam, LONG lParam) { UNREFERENCED_PARAMETER(wParam); UNREFERENCED_PARAMETER(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); CString sText=(LPCTSTR)(INT_PTR)lParam; // EmptyData(TRUE); m_arrInputData.RemoveAll(); m_bNotifyParent=FALSE; SetInputData(sText,0); m_bNotifyParent=TRUE; return TRUE; } } BOOL COXEdit::SetMask(LPCTSTR lpszMask) { CString sMask=lpszMask; if(sMask.IsEmpty() || sMask.Find(OXEDITABLETEXT_SYMBOL)==-1) return FALSE; DeleteContents(); BOOL bLeftLiteral=TRUE; for(int nIndex=0; nIndexm_arrInputData.GetSize()) return FALSE; CString sInputData=pszInputData; for(int nIndex=0; nIndex=m_arrLeftLiterals.GetSize()&& nPosition=m_arrLeftLiterals.GetSize() && nPosition<=m_arrLeftLiterals.GetSize()+ m_arrInputData.GetSize())? TRUE : FALSE); } int COXEdit::DeleteRange(int& nSelectionStart, int& nSelectionEnd) { int nDeleteCount=0; TCHAR chLastDeleted=_T('\0'); TCHAR chFirstDeleted=_T('\0'); int nIndex=0; for(nIndex=nSelectionEnd-1; nIndex>=nSelectionStart; nIndex--) { if(IsInputData(nIndex)) { if(chFirstDeleted==_T('\0')) { chFirstDeleted=m_arrInputData[RPtoLP(nIndex)]; } chLastDeleted=m_arrInputData[RPtoLP(nIndex)]; m_arrInputData.RemoveAt(RPtoLP(nIndex)); nDeleteCount++; } } if(chLastDeleted==_T('\r') && RPtoLP(nIndex+1)!=-1 && m_arrInputData[RPtoLP(nIndex+1)]==_T('\n')) { m_arrInputData.RemoveAt(RPtoLP(nIndex+1)); nDeleteCount++; } if(chFirstDeleted==_T('\n') && RPtoLP(nSelectionStart-1)!=-1 && m_arrInputData[RPtoLP(nSelectionStart-1)]==_T('\r')) { m_arrInputData.RemoveAt(RPtoLP(nSelectionStart-1)); nDeleteCount++; nSelectionStart--; } if(nDeleteCount) { Update(-1); } // return the deleted count so that an error can be generated // if none were deleted. return nDeleteCount; } int COXEdit::InsertAt(int nSelectionStart, TCHAR chNewChar) { int nInsertionPoint=nSelectionStart; if(!IsInputPosition(nInsertionPoint)) { nInsertionPoint=GetNextInputLocation(nInsertionPoint+1); ASSERT(IsInputPosition(nInsertionPoint)); } m_arrInputData.InsertAt(RPtoLP(nInsertionPoint),chNewChar); //nInsertionPoint+=ValidateInput(); nInsertionPoint+=Validate(); Update(-1); return nInsertionPoint; } int COXEdit::SetAt(int nSelectionStart, TCHAR chNewChar) { int nInsertionPoint=nSelectionStart; if(!IsInputData(nInsertionPoint)) { nInsertionPoint=GetNextInputLocation(nInsertionPoint+1); if(!IsInputData(nInsertionPoint)) return -1; } m_arrInputData.SetAt(RPtoLP(nInsertionPoint),chNewChar); nInsertionPoint+=Validate(); Update(-1); return nInsertionPoint; } int COXEdit::GetNextInputLocation(int nSelectionStart) { int nNextInputLocation=nSelectionStart; if(!IsInputPosition(nNextInputLocation)) { nNextInputLocation= PtrToInt(m_arrLeftLiterals.GetSize()+ m_arrInputData.GetSize()); for(int nIndex=nSelectionStart; nIndex=m_arrLeftLiterals.GetSize(); nIndex--) { if(IsInputPosition(nIndex)) { nPreviousInputLocation=nIndex; break; } } } ASSERT(IsInputPosition(nPreviousInputLocation)); return nPreviousInputLocation; } void COXEdit::Update(int nSelectionStart/*=0*/) { // Update the edit control if it exists. if(::IsWindow(GetSafeHwnd())) { m_nSetTextSemaphor++; SetWindowText(GetFormattedText()); m_nSetTextSemaphor--; SetModify(TRUE); // We usually need to update the insertion point. if(nSelectionStart>=0) UpdateInsertionPointForward(nSelectionStart); } } void COXEdit::UpdateInsertionPointForward(int nSelectionStart) { int nNewInsertionPoint=GetNextInputLocation(nSelectionStart); SetSel(nNewInsertionPoint,nNewInsertionPoint); } void COXEdit::UpdateInsertionPointBackward(int nSelectionStart) { int nNewInsertionPoint=GetPreviousInputLocation(nSelectionStart); SetSel(nNewInsertionPoint,nNewInsertionPoint); } int COXEdit::RPtoLP(int nRealPos)const { // All COXEdit functions that take cusor 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(nRealPosm_arrLeftLiterals.GetSize()+m_arrInputData.GetSize()) { return -1; } else { return PtrToInt(nRealPos-m_arrLeftLiterals.GetSize()); } } int COXEdit::LPtoRP(int nLogicalPos)const { if(nLogicalPos<0 || nLogicalPos>m_arrInputData.GetSize()) { return -1; } else { return PtrToInt(nLogicalPos+m_arrLeftLiterals.GetSize()); } } void COXEdit::ValidationError() { ::MessageBeep(MB_ICONEXCLAMATION); } CString COXEdit::GetLeftLiterals() const { CString sLeftLiterals; for(int nIndex=0; nIndexSendMessage(WM_COMMAND,MAKEWPARAM(GetDlgCtrlID(),nNotificationID), (LPARAM)GetSafeHwnd()); return TRUE; } ///////////////////////////////////////////////////////////////////////////// //////////////// // COXNumericEdit implements a edit control which numeric values. // ////////////////////////////////////////////////// ////////////////////////////////////////////////// IMPLEMENT_DYNCREATE(COXNumericEdit, COXEdit) BEGIN_MESSAGE_MAP(COXNumericEdit, COXEdit) //{{AFX_MSG_MAP(COXNumericEdit) ON_WM_CTLCOLOR_REFLECT() //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////// // Constructor COXNumericEdit::COXNumericEdit() { m_bNegativeValue=FALSE; m_clrTextNegative=m_clrText; m_bForceToUsePeriod=TRUE; // default values // m_nDecimalDigitCount=-1; m_chGroupSeparator=_T(','); m_nGroupLength=3; m_chDecimalSeparator=_T('.'); m_bShowLeadingZero=TRUE; m_nFractionalDigitCount=2; m_sFormatNegative=_T("-1.1"); m_sFormatPositive=_T("1.1"); m_nDecimalDigitCountDefault=-1; m_chGroupSeparatorDefault=_T(','); m_nGroupLengthDefault=3; m_chDecimalSeparatorDefault=_T('.'); m_bShowLeadingZeroDefault=TRUE; m_nFractionalDigitCountDefault=2; m_sFormatNegativeDefault=_T("-1.1"); m_sFormatPositiveDefault=_T("1.1"); // ////////////////////// } BOOL COXNumericEdit::InitControl() { if(!COXEdit::InitControl()) return FALSE; return TRUE; } void COXNumericEdit::DeleteContents() { COXEdit::DeleteContents(); m_dValue=0.0; } int COXNumericEdit::InsertAt(int nSelectionStart, TCHAR chNewChar) { return TranslateSymbol(nSelectionStart,chNewChar,FALSE); } int COXNumericEdit::SetAt(int nSelectionStart, TCHAR chNewChar) { return TranslateSymbol(nSelectionStart,chNewChar,TRUE); } int COXNumericEdit::DeleteRange(int& nSelectionStart, int& nSelectionEnd) { ASSERT(nSelectionStart >= 0); ASSERT(nSelectionStart <= nSelectionEnd); nSelectionStart = GetNextInputLocation( nSelectionStart ); // remember the number of group delimeter symbols before deleting range int nGroupDelimiterSymbolCount = GetGroupSeparatorSymbolsInserted(); int nGroupDelimiterSymbolCountBeforeRangeStartPos = GetGroupSeparatorSymbolsInserted(0,RPtoLP(nSelectionStart)); int nDeletedCount = 0; int nDecimalDelimiterPos = FindSymbol(GetDecimalSeparator()); if(nDecimalDelimiterPos == -1 || nDecimalDelimiterPos < nSelectionStart || nDecimalDelimiterPos > nSelectionEnd) { nDeletedCount = COXEdit::DeleteRange(nSelectionStart,nSelectionEnd); if(nDeletedCount > 0) { //ValidateInput(); Validate(); } } else { if(IsNegativeValue() && nSelectionStart <= m_arrLeftLiterals.GetSize() && nSelectionEnd >= GetWindowTextLength() - m_arrRightLiterals.GetSize()) { // Clear the negative value flag when all contents of the edit conrol is deleted m_bNegativeValue = FALSE; } if(nDecimalDelimiterPos < nSelectionEnd) { int nPosition = nDecimalDelimiterPos+1; nDeletedCount = COXEdit::DeleteRange(nPosition,nSelectionEnd); } if(nDecimalDelimiterPos > nSelectionStart) { nDeletedCount += COXEdit::DeleteRange( nSelectionStart,nDecimalDelimiterPos); } if(nDeletedCount>0) { //ValidateInput(); Validate(); } } // take into account the fact that due to deleting of the range // some of group delimeter symbols might get deleted nDeletedCount += nGroupDelimiterSymbolCount - GetGroupSeparatorSymbolsInserted(); // adjust selection range based on the fact that number of // group delimeter symbols before the original start of the range // might have changed int nShiftRange = nGroupDelimiterSymbolCountBeforeRangeStartPos - GetGroupSeparatorSymbolsInserted(0,RPtoLP(nSelectionStart)); nSelectionStart -= nShiftRange; nSelectionStart = (nSelectionStart < 0 ? 0 : nSelectionStart); nSelectionEnd -= nShiftRange; nSelectionEnd = (nSelectionEnd < 0 ? 0 : nSelectionEnd); return nDeletedCount; } CString COXNumericEdit::GetInputData()const { CString sInputData=COXEdit::GetInputData(); if(GetGroupLength()>0) { #if _MFC_VER>0x0421 sInputData.Remove(GetGroupSeparator()); #else RemoveCharFromString(sInputData,GetGroupSeparator()); #endif } return sInputData; } BOOL COXNumericEdit::IsInputData(int nPosition) const { if(!COXEdit::IsInputData(nPosition)) { return FALSE; } if(m_arrInputData[RPtoLP(nPosition)]==GetDecimalSeparator() || m_arrInputData[RPtoLP(nPosition)]==GetGroupSeparator()) { return FALSE; } return TRUE; } BOOL COXNumericEdit::GetLocaleSettings() { int nLength; BOOL bSetNewValue; nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SDECIMAL,NULL,0); if(nLength) { CString sExisting(m_chDecimalSeparator); CString sDefault(m_chDecimalSeparatorDefault); bSetNewValue=(sExisting.CompareNoCase(sDefault)==0); ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SDECIMAL, &m_chDecimalSeparatorDefault,nLength); if(bSetNewValue) { m_chDecimalSeparator=m_chDecimalSeparatorDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_STHOUSAND,NULL,0); if(nLength) { CString sExisting(m_chGroupSeparator); CString sDefault(m_chGroupSeparatorDefault); bSetNewValue=(sExisting.CompareNoCase(sDefault)==0); ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_STHOUSAND, &m_chGroupSeparatorDefault,nLength); if(bSetNewValue) { m_chGroupSeparator=m_chGroupSeparatorDefault; } } TCHAR* buffer; nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ILZERO,NULL,0); if(nLength) { bSetNewValue=(m_bShowLeadingZero==m_bShowLeadingZeroDefault); buffer=new TCHAR[nLength]; ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ILZERO,(LPTSTR)buffer,nLength); m_bShowLeadingZeroDefault=(BOOL)_ttoi(buffer); delete[] buffer; if(bSetNewValue) { m_bShowLeadingZero=m_bShowLeadingZeroDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SGROUPING,NULL,0); if(nLength) { bSetNewValue=(m_nGroupLength==m_nGroupLengthDefault); buffer=new TCHAR[nLength]; ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SGROUPING,(LPTSTR)buffer,nLength); CString sGrouping=buffer; int nPosition=sGrouping.Find(_T(";0")); if(nPosition!=-1) { ASSERT(nPosition>0); sGrouping=sGrouping.Left(nPosition); m_nGroupLengthDefault=_ttoi(sGrouping); } delete[] buffer; if(bSetNewValue) { m_nGroupLength=m_nGroupLengthDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_IDIGITS,NULL,0); if(nLength) { bSetNewValue=(m_nFractionalDigitCount==m_nFractionalDigitCountDefault); buffer=new TCHAR[nLength]; ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_IDIGITS,(LPTSTR)buffer,nLength); m_nFractionalDigitCountDefault=_ttoi(buffer); delete[] buffer; if(bSetNewValue) { m_nFractionalDigitCount=m_nFractionalDigitCountDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_INEGNUMBER,NULL,0); if(nLength) { bSetNewValue=(m_sFormatNegative.CompareNoCase(m_sFormatNegativeDefault)==0); buffer=new TCHAR[nLength]; ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_INEGNUMBER,(LPTSTR)buffer,nLength); int nNegativeNumberFormatType=_ttoi(buffer); switch(nNegativeNumberFormatType) { case 0: m_sFormatNegativeDefault=_T("(1.1)"); break; case 1: m_sFormatNegativeDefault=_T("-1.1"); break; case 2: m_sFormatNegativeDefault=_T("- 1.1"); break; case 3: m_sFormatNegativeDefault=_T("1.1-"); break; case 4: m_sFormatNegativeDefault=_T("1.1 -"); break; } delete[] buffer; if(bSetNewValue) { m_sFormatNegative=m_sFormatNegativeDefault; } } return TRUE; } int COXNumericEdit::FindSymbol(TCHAR chSymbol) const { int nPosition=-1; for(int nIndex=0; nIndex=0; nIndex--) { if(!(m_arrInputData[nIndex]>=_T('0') && m_arrInputData[nIndex]<=_T('9'))) { if(nIndex==0 && m_arrInputData[nIndex]==_T('-')) bSetToNegative=TRUE; if(m_arrInputData[nIndex]==m_chDecimalSeparator) { if(!bDecimalSeparatorFound) bDecimalSeparatorFound=TRUE; else m_arrInputData.RemoveAt(nIndex); } else m_arrInputData.RemoveAt(nIndex); } } int nDecimalDelimiterPos=FindSymbol(GetDecimalSeparator()); int nDecimalDigitsInserted=GetDecimalDigitsInserted(); int nFractionalDigitsInserted=GetFractionalDigitsInserted(); for(;m_arrInputData.GetSize()>0;) { if(m_arrInputData[0]==_T('0') && nDecimalDigitsInserted>1) { m_arrInputData.RemoveAt(0); nDecimalDigitsInserted--; if(nDecimalDelimiterPos!=-1) nDecimalDelimiterPos--; } else break; } BOOL bUpdate=FALSE; int nFractionalOvertypedCount= nFractionalDigitsInserted-GetFractionalDigitCount(); if(nFractionalOvertypedCount>0) { for(int nIndex=0; nIndex0 && GetDecimalDigitCount()!=-1) { if(m_arrInputData[0]!=_T('0')) { int nDecimalOvertypedCount=nDecimalDigitsInserted-GetDecimalDigitCount(); if(nDecimalOvertypedCount>0) { for(int nIndex=0; nIndexnFractionalDigitsInserted) { for(int nIndex=nFractionalDigitsInserted; nIndex0) { int nDecimalDelimiterPos=RPtoLP(FindSymbol(m_chDecimalSeparator)); nDecimalDelimiterPos=(nDecimalDelimiterPos==-1) ? PtrToInt(m_arrInputData.GetSize()) : nDecimalDelimiterPos; int nDigitsInRow=0; for(int nIndex=nDecimalDelimiterPos-1; nIndex>=0; nIndex--) { if(nDigitsInRow==GetGroupLength()) { if(m_arrInputData[nIndex]!=GetGroupSeparator()) { m_arrInputData.InsertAt(nIndex+1,GetGroupSeparator()); bUpdate=TRUE; } nDigitsInRow=1; } else { if(m_arrInputData[nIndex]!=GetGroupSeparator()) nDigitsInRow++; } } } if(bSetToNegative) ChangeSign(bSetToNegative); else ChangeSign(IsNegativeValue()); if(bUpdate) Update(-1); CString sInputData=GetInputData(); if(GetGroupLength()>0) { #if _MFC_VER>0x0421 sInputData.Remove(GetGroupSeparator()); sInputData.Replace(GetDecimalSeparator(),_T('.')); #else RemoveCharFromString(sInputData,GetGroupSeparator()); ReplaceCharInString(sInputData,GetDecimalSeparator(),_T('.')); #endif } TCHAR* pchStop; m_dValue=_tcstod(sInputData,&pchStop); if(IsNegativeValue()) m_dValue*=-1; int nSymbolsAfter=PtrToInt(m_arrInputData.GetSize()); return nSymbolsAfter-nSymbolsBefore; } int COXNumericEdit::ChangeSign(BOOL bNegative) { m_bNegativeValue=bNegative; CString sMask=GetMask(); int nPosInputData=sMask.Find(OXEDITABLETEXT_SYMBOL); CString sLeftLiterals=(nPosInputData>0 ? sMask.Left(nPosInputData) : _T("")); CString sRightLiterals=(nPosInputData==-1 ? _T("") : sMask.Mid(nPosInputData+1)); int nLeftLiteralsBefore=PtrToInt(m_arrLeftLiterals.GetSize()); m_arrLeftLiterals.RemoveAll(); m_arrRightLiterals.RemoveAll(); int nIndex=0; for(nIndex=0; nIndex0 ? m_sFormatNegative.Left(nPosInNegative) : _T("")); CString sRightNegativeLiterals=(nPosInNegative==-1 ? _T("") : m_sFormatNegative.Mid(nPosInNegative+ OXNUMERICVALUE_PLACEHOLDER_LENGTH)); for(nIndex=0; nIndex0 ? m_sFormatPositive.Left(nPosInPositive) : _T("")); CString sRightPositiveLiterals=(nPosInPositive==-1 ? _T("") : m_sFormatPositive.Mid(nPosInPositive+ OXNUMERICVALUE_PLACEHOLDER_LENGTH)); for(nIndex=0; nIndex=0) || (!m_bNegativeValue && m_dValue<0)) m_dValue*=-1; return PtrToInt(m_arrLeftLiterals.GetSize())-nLeftLiteralsBefore; } BOOL COXNumericEdit::SetDecimalDigitCount(int nDecimalDigitCount) { if(nDecimalDigitCount<-1) { TRACE(_T("COXNumericEdit::SetDecimalDigitCount: invalid decimal digit count has been specified\n")); return FALSE; } if(m_nDecimalDigitCount==nDecimalDigitCount) return TRUE; CString sInputData=GetInputData(); m_nDecimalDigitCount=nDecimalDigitCount; EmptyData(TRUE); VERIFY(SetInputData(sInputData)); return TRUE; } BOOL COXNumericEdit::SetDecimalSeparator(TCHAR chDecimalSeparator) { if((chDecimalSeparator>=_T('0') && chDecimalSeparator<=_T('9')) || chDecimalSeparator==_T('-') || chDecimalSeparator==GetGroupSeparator() || chDecimalSeparator==NULL) { TRACE(_T("COXNumericEdit::SetDecimalSeparator: invalid decimal separator has been specified\n")); return FALSE; } int nDecimalDelimiterPos=FindSymbol(GetDecimalSeparator()); m_chDecimalSeparator=chDecimalSeparator; if(nDecimalDelimiterPos!=-1) { m_arrInputData.SetAt(RPtoLP(nDecimalDelimiterPos),chDecimalSeparator); Update(-1); } return TRUE; } BOOL COXNumericEdit::SetFractionalDigitCount(int nFractionalDigitCount) { if(nFractionalDigitCount<0) { TRACE(_T("COXNumericEdit::SetFractionalDigitCount: invalid fractional digit count has been specified\n")); return FALSE; } if(m_nFractionalDigitCount==nFractionalDigitCount) return TRUE; CString sInputData=GetInputData(); m_nFractionalDigitCount=nFractionalDigitCount; EmptyData(TRUE); VERIFY(SetInputData(sInputData)); return TRUE; } BOOL COXNumericEdit::SetGroupLength(int nGroupLength) { if(nGroupLength<0) { TRACE(_T("COXNumericEdit::SetGroupLength: invalid group length has been specified\n")); return FALSE; } if(m_nGroupLength==nGroupLength) { return TRUE; } CString sInputData=GetInputData(); m_nGroupLength=nGroupLength; EmptyData(TRUE); VERIFY(SetInputData(sInputData)); return TRUE; } BOOL COXNumericEdit::SetGroupSeparator(TCHAR chGroupSeparator) { if((chGroupSeparator>=_T('0') && chGroupSeparator<=_T('9')) || chGroupSeparator==_T('-') || chGroupSeparator==GetDecimalSeparator() || chGroupSeparator==NULL) { TRACE(_T("COXNumericEdit::SetGroupSeparator: invalid group separator has been specified\n")); return FALSE; } if(m_chGroupSeparator==chGroupSeparator) return TRUE; CString sInputData=GetInputData(); m_chGroupSeparator=chGroupSeparator; EmptyData(TRUE); VERIFY(SetInputData(sInputData)); return TRUE; } BOOL COXNumericEdit::SetNegativeFormat(LPCTSTR lpszFormatNegative) { ASSERT(lpszFormatNegative!=NULL); CString sNegativeFormat=lpszFormatNegative; if(sNegativeFormat.Find(OXNUMERICVALUE_PLACEHOLDER)==-1) { TRACE(_T("COXNumericEdit::SetNegativeFormat: invalid format for displaying negative value has been specified\n")); return FALSE; } m_sFormatNegative=sNegativeFormat; ChangeSign(IsNegativeValue()); return TRUE; } BOOL COXNumericEdit::SetPositiveFormat(LPCTSTR lpszFormatPositive) { ASSERT(lpszFormatPositive!=NULL); CString sPositiveFormat=lpszFormatPositive; if(sPositiveFormat.Find(OXNUMERICVALUE_PLACEHOLDER)==-1) { TRACE(_T("COXNumericEdit::SetPositiveFormat: invalid format for displaying positive value has been specified\n")); return FALSE; } m_sFormatPositive=sPositiveFormat; ChangeSign(IsNegativeValue()); return TRUE; } BOOL COXNumericEdit::SetValue(double dValue) { m_dValue=dValue; m_bNegativeValue=(m_dValue<0 ? TRUE : FALSE); BOOL bSetNegative=m_bNegativeValue; int decimal, sign; #if _MSC_VER >= 1400 int count = GetFractionalDigitCount(); char buffer[MAX_PATH]; _fcvt_s(buffer, MAX_PATH, dValue, count, &decimal,&sign); #else char *buffer= UTBStr::fcvt(m_dValue,GetFractionalDigitCount(),&decimal,&sign); #endif int nSymbolCount=PtrToInt(strlen(buffer)); TCHAR* chInputData=new TCHAR[nSymbolCount+1]; #ifdef _UNICODE UTBStr::mbstowcs(chInputData,nSymbolCount+1,buffer,nSymbolCount); #else UTBStr::strncpy(chInputData,nSymbolCount+1, buffer,nSymbolCount); #endif chInputData[nSymbolCount]=_T('\0'); CString sInputData=chInputData; CString sDecimalSeparator(GetDecimalSeparator()); if(!sInputData.IsEmpty()) { for(int nIndex=decimal; nIndex<1; nIndex++) { sInputData=_T("0")+sInputData; decimal++; nSymbolCount++; } sInputData=sInputData.Left(decimal)+sDecimalSeparator+sInputData.Mid(decimal); if(sign!=0) { sInputData=_T("-")+sInputData; } } EmptyData(TRUE); VERIFY(COXEdit::SetInputData(sInputData)); delete[] chInputData; ChangeSign(bSetNegative); return TRUE; } void COXNumericEdit::SetShowLeadingZero(BOOL bShowLeadingZero) { if(m_bShowLeadingZero==bShowLeadingZero) { return; } CString sInputData=GetInputData(); m_bShowLeadingZero=bShowLeadingZero; EmptyData(TRUE); VERIFY(SetInputData(sInputData)); return; } void COXNumericEdit::SetNegativeTextColor(COLORREF clrTextNegative, BOOL bRedraw/*=TRUE*/) { if(m_clrTextNegative!=clrTextNegative) { m_clrTextNegative=clrTextNegative; if(bRedraw && IsNegativeValue()) RedrawWindow(); } } int COXNumericEdit::TranslateSymbol(int nSelectionStart, TCHAR chNewChar, BOOL bReplaceExisting) { if((chNewChar>=_T('0') && chNewChar<=_T('9')) || chNewChar==m_chDecimalSeparator || chNewChar==_T('-') || (m_bForceToUsePeriod && chNewChar==_T('.'))) { if(chNewChar==_T('-')) { nSelectionStart+=ChangeSign(!IsNegativeValue())-1; return nSelectionStart; } if(chNewChar==m_chDecimalSeparator || (m_bForceToUsePeriod && chNewChar==_T('.'))) { if(GetFractionalDigitCount()==0) return -1; int nPos=FindSymbol(m_chDecimalSeparator); if(nPos==-1) { nSelectionStart=PtrToInt(m_arrLeftLiterals.GetSize()+m_arrInputData.GetSize()); return COXEdit::InsertAt(nSelectionStart,chNewChar); } else return GetNextInputLocation(nPos); } if(m_arrInputData.GetSize()>0 && chNewChar==_T('0') && RPtoLP(nSelectionStart)==0 && IsInputData(nSelectionStart)) { if(bReplaceExisting) { m_arrInputData.RemoveAt(0); //ValidateInput(); Validate(); if(m_arrInputData[0]==_T('0') || m_arrInputData[0]==GetDecimalSeparator()) return nSelectionStart; else return -1; } else return -1; } if(m_arrInputData.GetSize()>0 && RPtoLP(nSelectionStart)==0) { if(bReplaceExisting && m_arrInputData[0]==GetDecimalSeparator()) return COXEdit::InsertAt(nSelectionStart,chNewChar); else if(!bReplaceExisting && m_arrInputData[0]==_T('0')) return COXEdit::SetAt(nSelectionStart,chNewChar); } int nInsertedAtPos=-1; if(bReplaceExisting) { nInsertedAtPos=COXEdit::SetAt(nSelectionStart,chNewChar); } else { int nDecimalDelimiterPos=FindSymbol(GetDecimalSeparator()); if(nDecimalDelimiterPos==-1 || nDecimalDelimiterPos>=nSelectionStart) { if(nDecimalDelimiterPos==-1 && GetDecimalDigitsInserted()==1 && m_arrInputData[0]==_T('0')) nInsertedAtPos=COXEdit::SetAt(nSelectionStart-1,chNewChar); else if(GetDecimalDigitsInserted()=_T('0') && m_arrInputData[nIndex]<=_T('9')) { nCount++; } } return nCount; } int COXNumericEdit::GetFractionalDigitsInserted() const { int nCount=0; int nDecimalDelimiterPos=FindSymbol(GetDecimalSeparator()); nDecimalDelimiterPos=(nDecimalDelimiterPos==-1 ? PtrToInt(m_arrInputData.GetSize()) : RPtoLP(nDecimalDelimiterPos)); for(int nIndex=nDecimalDelimiterPos+1; nIndex=_T('0') && m_arrInputData[nIndex]<=_T('9')) { nCount++; } } return nCount; } int COXNumericEdit::GetGroupSeparatorSymbolsInserted(int nStartPos/* = 0*/, int nEndPos/* = -1*/) const { if(nEndPos == -1) { nEndPos = PtrToInt(m_arrInputData.GetSize()) - 1; } ASSERT(nEndPos >= nStartPos); ASSERT(nStartPos >= 0 && nStartPos <= m_arrInputData.GetSize()); ASSERT(nEndPos >= 0 && nEndPos <= m_arrInputData.GetSize()); int nCount = 0; int nDecimalDelimiterPos = FindSymbol(GetDecimalSeparator()); nDecimalDelimiterPos = (nDecimalDelimiterPos == -1 ? PtrToInt(m_arrInputData.GetSize()) : RPtoLP(nDecimalDelimiterPos)); for(int nIndex = nStartPos; nIndex < min(nDecimalDelimiterPos,nEndPos); nIndex++) { if(m_arrInputData[nIndex] == GetGroupSeparator()) { nCount++; } } return nCount; } ////////////////// // Handle reflected WM_CTLCOLOR to set custom control color and font. // HBRUSH COXNumericEdit::CtlColor(CDC* pDC, UINT nCtlColor) { HBRUSH hBrush=COXEdit::CtlColor(pDC,nCtlColor); if(IsNegativeValue()) pDC->SetTextColor(GetNegativeTextColor()); return hBrush; } // global: void AFXAPI DDX_OXNumericEdit(CDataExchange* pDX, int nIDC, double& dValue) { // be sure that the control is a COXNumericEdit COXNumericEdit* pEdit = (COXNumericEdit*)pDX->m_pDlgWnd->GetDlgItem(nIDC); ASSERT(pEdit->IsKindOf(RUNTIME_CLASS(COXNumericEdit))); ASSERT_VALID(pEdit); if (pDX->m_bSaveAndValidate) { // retreive data dValue = pEdit->GetValue(); } else { // set data pEdit->SetValue(dValue); } } void AFXAPI DDX_OXNumericEdit(CDataExchange* pDX, int nIDC, float& fValue) { // be sure that the control is a COXNumericEdit COXNumericEdit* pEdit = (COXNumericEdit*)pDX->m_pDlgWnd->GetDlgItem(nIDC); ASSERT(pEdit->IsKindOf(RUNTIME_CLASS(COXNumericEdit))); ASSERT_VALID(pEdit); if (pDX->m_bSaveAndValidate) { // retreive data fValue = (float)pEdit->GetValue(); } else { // set data pEdit->SetValue((double)fValue); } } void AFXAPI DDX_OXNumericEdit(CDataExchange* pDX, int nIDC, int& nValue) { // be sure that the control is a COXNumericEdit COXNumericEdit* pEdit = (COXNumericEdit*)pDX->m_pDlgWnd->GetDlgItem(nIDC); ASSERT(pEdit->IsKindOf(RUNTIME_CLASS(COXNumericEdit))); ASSERT_VALID(pEdit); if (pDX->m_bSaveAndValidate) { // retreive data nValue = (int)pEdit->GetValue(); } else { // set data pEdit->SetValue((double)nValue); } } void AFXAPI DDX_OXNumericEdit(CDataExchange* pDX, int nIDC, long& nValue) { // be sure that the control is a COXNumericEdit COXNumericEdit* pEdit = (COXNumericEdit*)pDX->m_pDlgWnd->GetDlgItem(nIDC); ASSERT(pEdit->IsKindOf(RUNTIME_CLASS(COXNumericEdit))); ASSERT_VALID(pEdit); if (pDX->m_bSaveAndValidate) { // retreive data nValue = (long)pEdit->GetValue(); } else { // set data pEdit->SetValue((double)nValue); } } ///////////////////////////////////////////////////////////////////////////// //////////////// // COXCurrencyEdit implements a edit control which numeric values. // ////////////////////////////////////////////////// ////////////////////////////////////////////////// IMPLEMENT_DYNCREATE(COXCurrencyEdit, COXNumericEdit) BEGIN_MESSAGE_MAP(COXCurrencyEdit, COXNumericEdit) //{{AFX_MSG_MAP(COXCurrencyEdit) //}}AFX_MSG_MAP END_MESSAGE_MAP() /////////////////// // Constructor COXCurrencyEdit::COXCurrencyEdit() { m_nDecimalDigitCount=-1; m_chGroupSeparator=_T(','); m_nGroupLength=3; m_chDecimalSeparator=_T('.'); m_bShowLeadingZero=TRUE; m_nFractionalDigitCount=2; m_sFormatNegative=_T("($1.1)"); m_sFormatPositive=_T("$1.1"); m_sCurrency=_T("$"); m_nDecimalDigitCountDefault=-1; m_chGroupSeparatorDefault=_T(','); m_nGroupLengthDefault=3; m_chDecimalSeparatorDefault=_T('.'); m_bShowLeadingZeroDefault=TRUE; m_nFractionalDigitCountDefault=2; m_sFormatNegativeDefault=_T("($1.1)"); m_sFormatPositiveDefault=_T("$1.1"); m_sCurrencyDefault=_T("$"); } BOOL COXCurrencyEdit::GetLocaleSettings() { int nLength; BOOL bSetNewValue; nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SMONDECIMALSEP,NULL,0); if(nLength) { CString sExisting(m_chDecimalSeparator); CString sDefault(m_chDecimalSeparatorDefault); bSetNewValue=(sExisting.CompareNoCase(sDefault)==0); ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SMONDECIMALSEP, &m_chDecimalSeparatorDefault,nLength); if(bSetNewValue) { m_chDecimalSeparator=m_chDecimalSeparatorDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SMONTHOUSANDSEP,NULL,0); if(nLength) { CString sExisting(m_chGroupSeparator); CString sDefault(m_chGroupSeparatorDefault); bSetNewValue=(sExisting.CompareNoCase(sDefault)==0); ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SMONTHOUSANDSEP, &m_chGroupSeparatorDefault,nLength); if(bSetNewValue) { m_chGroupSeparator=m_chGroupSeparatorDefault; } } TCHAR* buffer; nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ILZERO,NULL,0); if(nLength) { bSetNewValue=(m_bShowLeadingZero==m_bShowLeadingZeroDefault); buffer=new TCHAR[nLength]; ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ILZERO,(LPTSTR)buffer,nLength); m_bShowLeadingZeroDefault=(BOOL)_ttoi(buffer); delete[] buffer; if(bSetNewValue) { m_bShowLeadingZero=m_bShowLeadingZeroDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SMONGROUPING,NULL,0); if(nLength) { bSetNewValue=(m_nGroupLength==m_nGroupLengthDefault); buffer=new TCHAR[nLength]; ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SMONGROUPING,(LPTSTR)buffer,nLength); CString sGrouping=buffer; int nPosition=sGrouping.Find(_T(";0")); if(nPosition!=-1) { ASSERT(nPosition>0); sGrouping=sGrouping.Left(nPosition); m_nGroupLengthDefault=_ttoi(sGrouping); } delete[] buffer; if(bSetNewValue) { m_nGroupLength=m_nGroupLengthDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ICURRDIGITS,NULL,0); if(nLength) { bSetNewValue=(m_nFractionalDigitCount==m_nFractionalDigitCountDefault); buffer=new TCHAR[nLength]; ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ICURRDIGITS,(LPTSTR)buffer,nLength); m_nFractionalDigitCountDefault=_ttoi(buffer); delete[] buffer; if(bSetNewValue) { m_nFractionalDigitCount=m_nFractionalDigitCountDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SCURRENCY,NULL,0); if(nLength) { bSetNewValue=(m_sCurrency.CompareNoCase(m_sCurrencyDefault)==0); ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_SCURRENCY, m_sCurrencyDefault.GetBuffer(nLength-1),nLength); m_sCurrencyDefault.ReleaseBuffer(); if(bSetNewValue) { m_sCurrency=m_sCurrencyDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ICURRENCY,NULL,0); if(nLength) { bSetNewValue=(m_sFormatPositive.CompareNoCase(m_sFormatPositiveDefault)==0); buffer=new TCHAR[nLength]; ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_ICURRENCY,(LPTSTR)buffer,nLength); int nPositiveNumberFormatType=_ttoi(buffer); switch(nPositiveNumberFormatType) { case 0: m_sFormatPositiveDefault=_T("$1.1"); break; case 1: m_sFormatPositiveDefault=_T("1.1$"); break; case 2: m_sFormatPositiveDefault=_T("$ 1.1"); break; case 3: m_sFormatPositiveDefault=_T("1.1 $"); break; } delete[] buffer; if(bSetNewValue) { m_sFormatPositive=m_sFormatPositiveDefault; } } nLength=::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_INEGCURR,NULL,0); if(nLength) { bSetNewValue=(m_sFormatNegative.CompareNoCase(m_sFormatNegativeDefault)==0); buffer=new TCHAR[nLength]; ::GetLocaleInfo(LOCALE_USER_DEFAULT,LOCALE_INEGCURR,(LPTSTR)buffer,nLength); int nNegativeNumberFormatType=_ttoi(buffer); switch(nNegativeNumberFormatType) { case 0: m_sFormatNegativeDefault=_T("($1.1)"); break; case 1: m_sFormatNegativeDefault=_T("-$1.1"); break; case 2: m_sFormatNegativeDefault=_T("$-1.1"); break; case 3: m_sFormatNegativeDefault=_T("$1.1-"); break; case 4: m_sFormatNegativeDefault=_T("(1.1$)"); break; case 5: m_sFormatNegativeDefault=_T("-1.1$"); break; case 6: m_sFormatNegativeDefault=_T("1.1-$"); break; case 7: m_sFormatNegativeDefault=_T("1.1$-"); break; case 8: m_sFormatNegativeDefault=_T("-1.1 $"); break; case 9: m_sFormatNegativeDefault=_T("-$ 1.1"); break; case 10: m_sFormatNegativeDefault=_T("1.1 $-"); break; case 11: m_sFormatNegativeDefault=_T("$ 1.1-"); break; case 12: m_sFormatNegativeDefault=_T("$ -1.1"); break; case 13: m_sFormatNegativeDefault=_T("1.1- $"); break; case 14: m_sFormatNegativeDefault=_T("($ 1.1)"); break; case 15: m_sFormatNegativeDefault=_T("(1.1 $)"); break; } delete[] buffer; if(bSetNewValue) { m_sFormatNegative=m_sFormatNegativeDefault; } } return TRUE; } BOOL COXCurrencyEdit::SetCurrencyName(LPCTSTR lpszCurrency) { if(lpszCurrency==NULL) return FALSE; CString sCurrency=lpszCurrency; if(sCurrency.GetLength()==0) return FALSE; CString sInputData=GetInputData(); m_sCurrency=sCurrency; EmptyData(TRUE); VERIFY(SetInputData(sInputData)); return TRUE; } BOOL COXCurrencyEdit::SetNegativeFormat(LPCTSTR lpszFormatNegative) { ASSERT(lpszFormatNegative!=NULL); CString sNegativeFormat=lpszFormatNegative; if(sNegativeFormat.Find(OXNUMERICVALUE_PLACEHOLDER)==-1 || sNegativeFormat.Find(_T("$"))==-1) { TRACE(_T("COXCurrencyEdit::SetNegativeFormat: invalid format for displaying negative value has been specified\n")); return FALSE; } return COXNumericEdit::SetNegativeFormat(lpszFormatNegative); } BOOL COXCurrencyEdit::SetPositiveFormat(LPCTSTR lpszFormatPositive) { ASSERT(lpszFormatPositive!=NULL); CString sPositiveFormat=lpszFormatPositive; if(sPositiveFormat.Find(OXNUMERICVALUE_PLACEHOLDER)==-1 || sPositiveFormat.Find(_T("$"))==-1) { TRACE(_T("COXCurrencyEdit::SetPositiveFormat: invalid format for displaying positive value has been specified\n")); return FALSE; } return COXNumericEdit::SetPositiveFormat(lpszFormatPositive); } void COXCurrencyEdit::UpdateMask() { COXNumericEdit::UpdateMask(); CString sCurrency=GetCurrencyName(); ASSERT(sCurrency.GetLength()>0); CString sFormat=(IsNegativeValue() ? GetNegativeFormat() : GetPositiveFormat()); int nPosCurrency=sFormat.Find(OXCURRENCY_SYMBOL); ASSERT(nPosCurrency!=-1); int nPosValue=sFormat.Find(OXNUMERICVALUE_PLACEHOLDER); ASSERT(nPosValue!=-1); CString sMask=GetMask(); int nPosInputData=sMask.Find(OXEDITABLETEXT_SYMBOL); if(nPosCurrencym_pDlgWnd->GetDlgItem(nIDC); ASSERT(pEdit->IsKindOf(RUNTIME_CLASS(COXCurrencyEdit))); ASSERT_VALID(pEdit); if (pDX->m_bSaveAndValidate) { // retreive data dValue = pEdit->GetValue(); } else { // set data pEdit->SetValue(dValue); } } void AFXAPI DDX_OXCurrencyEdit(CDataExchange* pDX, int nIDC, float& fValue) { // be sure that the control is a COXNumericEdit COXCurrencyEdit* pEdit = (COXCurrencyEdit*)pDX->m_pDlgWnd->GetDlgItem(nIDC); ASSERT(pEdit->IsKindOf(RUNTIME_CLASS(COXCurrencyEdit))); ASSERT_VALID(pEdit); if (pDX->m_bSaveAndValidate) { // retreive data fValue = (float)pEdit->GetValue(); } else { // set data pEdit->SetValue((double)fValue); } } void AFXAPI DDX_OXCurrencyEdit(CDataExchange* pDX, int nIDC, int& nValue) { // be sure that the control is a COXNumericEdit COXCurrencyEdit* pEdit = (COXCurrencyEdit*)pDX->m_pDlgWnd->GetDlgItem(nIDC); ASSERT(pEdit->IsKindOf(RUNTIME_CLASS(COXCurrencyEdit))); ASSERT_VALID(pEdit); if (pDX->m_bSaveAndValidate) { // retreive data nValue = (int)pEdit->GetValue(); } else { // set data pEdit->SetValue((double)nValue); } } /////////////////////////////////////////////////////////////////////////////// int COXEdit::Validate() { static CString sFullText; int nRet=ValidateInput(); CString sNewText=GetInputData(); if ((::IsWindow(m_hWnd)) && (UINT) sNewText.GetLength()>GetLimitText()) { int n=0; for (n=0; n