/***********************************************************************/ /* Copyright (C) 2002 Definitive Solutions, Inc. All Rights Reserved. */ /* THIS COMPUTER PROGRAM IS PROPRIETARY AND CONFIDENTIAL TO DEFINITIVE */ /* SOLUTIONS, INC. AND ITS LICENSORS AND CONTAINS TRADE SECRETS OF */ /* DEFINITIVE SOLUTIONS, INC. THAT ARE PROVIDED PURSUANT TO A WRITTEN */ /* AGREEMENT CONTAINING RESTRICTIONS ON USE AND DISCLOSURE. ANY USE, */ /* REPRODUCTION, OR TRANSFER EXCEPT AS PROVIDED IN SUCH AGREEMENT */ /* IS STRICTLY PROHIBITED. */ /***********************************************************************/ // Orginially written by : DunnoWho // Modified by : Jeremy Davis, 24/07/1998 #include "stdafx.h" #include "MyEdit.h" #include "MyMenu.h" #include "MyApp.h" #include "MyLog.h" #include "Generic.h" #ifdef _DEBUG #define new DEBUG_NEW #undef THIS_FILE static char THIS_FILE[] = __FILE__; #endif ///////////////////////////////////////////////////////////////////////////// // Defines #define CTRL_X_CUT 24 #define CTRL_C_COPY 3 #define CTRL_V_PASTE 22 ///////////////////////////////////////////////////////////////////////////// // MyEdit class IMPLEMENT_DYNAMIC(MyEdit, CEdit) BEGIN_MESSAGE_MAP(MyEdit, CEdit) //{{AFX_MSG_MAP(MyEdit) ON_WM_CHAR() ON_WM_KEYDOWN() ON_WM_RBUTTONUP() ON_COMMAND(ID_EDIT_CUT, OnEditCut) ON_COMMAND(ID_EDIT_COPY, OnEditCopy) ON_COMMAND(ID_EDIT_PASTE, OnEditPaste) ON_COMMAND(ID_EDIT_CLEAR_ALL, OnEditClearAll) ON_COMMAND(ID_EDIT_SELECT_ALL, OnEditSelectAll) ON_COMMAND(ID_MYEDIT_FONT_UP, OnEditFontUp) ON_COMMAND(ID_MYEDIT_FONT_DOWN, OnEditFontDown) ON_COMMAND(ID_MYEDIT_EDIT, OnEditEdit) ON_WM_SETFOCUS() ON_WM_KILLFOCUS() //}}AFX_MSG_MAP END_MESSAGE_MAP() // Constructor. MyEdit::MyEdit(const CString& sRegPlacementsKey) : m_bUseMask(false) , m_bMaskKeyInProgress(false) , m_nPoints(0) , m_pFont(NULL) , m_bUseFocusIndicator(false) , m_sRegPlacementsKey(sRegPlacementsKey) { } // Destructor. /* virtual */ MyEdit::~MyEdit() { delete m_pFont; m_pFont = NULL; } // The mask is optional; if you want it, here is where you set it. bool MyEdit::SetMask(const CString& sMask, const CString& sLiteral, const CString& sMaskLiteral, const CString& sValid) { VALIDATE; _ASSERTE(! sMask.IsEmpty()); _ASSERTE(! sLiteral.IsEmpty()); _ASSERTE(! sMaskLiteral.IsEmpty()); _ASSERTE(! sValid.IsEmpty()); _ASSERTE(sLiteral.GetLength() == sMask.GetLength()); _ASSERTE(sMaskLiteral.GetLength() == sMask.GetLength()); // Set the values. m_sMask = sMask; m_sLiteral = sLiteral; m_sMaskLiteral = sMaskLiteral; m_sValid = sValid; m_bUseMask = true; // Set the text into the control. SetWindowText(m_sMaskLiteral); // Check the values. CString sBadChars(m_sLiteral.SpanExcluding(" _")); _ASSERTE(sBadChars.IsEmpty() && "Only the space and the underscore are legal!"); return static_cast (sBadChars.IsEmpty()); } // void MyEdit::SelectTheFont(const CString& sFace, int nPoints) { VALIDATE; _ASSERTE(! sFace.IsEmpty()); _ASSERTE(2 <= nPoints && 144 >= nPoints); // First delete any old font. delete m_pFont; m_pFont = NULL; // Create the new font. m_pFont = new CFont; m_nPoints = nPoints; m_sFaceName = sFace; m_pFont->CreatePointFont(nPoints * 10, sFace); SetFont(m_pFont); DOMYLOGD ("Font set to <%d> point <%s>.\n", m_nPoints, m_sFaceName); } // Have Windows process this character. void MyEdit::SendChar(UINT uiChar) { VALIDATE; _ASSERTE(! m_sMask.IsEmpty()); _ASSERTE(! m_sLiteral.IsEmpty()); _ASSERTE(! m_sMaskLiteral.IsEmpty()); _ASSERTE(! m_sValid.IsEmpty()); m_bMaskKeyInProgress = true; AfxCallWndProc(this, m_hWnd, WM_CHAR, uiChar, 1); m_bMaskKeyInProgress = false; } // WM_KEYDOWN. Ignore certain keystrokes if using a mask. void MyEdit::OnKeyDown(UINT uiChar, UINT nRepCnt, UINT nFlags) { VALIDATE; if (m_bUseMask) { _ASSERTE(! m_sMask.IsEmpty()); _ASSERTE(! m_sLiteral.IsEmpty()); _ASSERTE(! m_sMaskLiteral.IsEmpty()); _ASSERTE(! m_sValid.IsEmpty()); switch (uiChar) { case VK_DELETE: case VK_INSERT: ::MessageBeep((UINT) -1); return; break; } } CEdit::OnKeyDown(uiChar, nRepCnt, nFlags); } // Handle WM_CHAR. void MyEdit::OnChar(UINT uiChar, UINT nRepCnt, UINT nFlags) { VALIDATE; SetRedraw(false); if (m_bMaskKeyInProgress || CheckChar(uiChar, /* bBeepOnError */ true)) { if (m_bUseMask) { _ASSERTE(! m_sMask.IsEmpty()); _ASSERTE(! m_sLiteral.IsEmpty()); _ASSERTE(! m_sMaskLiteral.IsEmpty()); _ASSERTE(! m_sValid.IsEmpty()); // Handle normal characters. if (isprint(uiChar)) { // Cut out the existing character there, and let the base // method insert the character the user typed. int nPosBeg(0); int nPosEnd(0); GetSel(nPosBeg, nPosEnd); SetSel(nPosBeg, nPosEnd + 1, /* bNoScroll */ true); Clear(); } else if (VK_BACK == uiChar) { // Handle backspace key. int nPosBeg(0); int nPosEnd(0); GetSel(nPosBeg, nPosEnd); // If cursor is in the legal range, back space it, and // write the mask literal char over the char we're // backspacing over. Have to do it twice; second time // is because we have inserted the mask literal char. if (nPosBeg == nPosEnd && 1 <= nPosBeg && nPosBeg <= m_sMaskLiteral.GetLength()) { VERIFY(0 == SendMessage(WM_KEYDOWN, VK_LEFT, 0)); char cMaskLiteral(m_sMaskLiteral[nPosBeg - 1]); SendChar(cMaskLiteral); VERIFY(0 == SendMessage(WM_KEYDOWN, VK_LEFT, 0)); } else { // Out of range or have more than one char selected. ::MessageBeep((UINT) -1); } // The base method would delete the char to left. return; } else if (CTRL_V_PASTE == uiChar) { // Handle Ctrl-V - paste. OnEditPaste(); return; } } // We call the base method to insert characters. CEdit::OnChar(uiChar, nRepCnt, nFlags); } SetRedraw(true); } // bool MyEdit::CheckChar(UINT uiChar, bool bBeepOnError) { VALIDATE; bool bReturn(true); SetRedraw(false); if (m_bUseMask) { _ASSERTE(! m_sMask.IsEmpty()); _ASSERTE(! m_sLiteral.IsEmpty()); _ASSERTE(! m_sMaskLiteral.IsEmpty()); _ASSERTE(! m_sValid.IsEmpty()); // Printable characters are okay. if (isprint(uiChar)) { // Unselect all selections, if any. int nPosBeg(0); int nPosEnd(0); GetSel(nPosBeg, nPosEnd); SetSel(-1, 0, /* bNoScroll */ true); SetSel(nPosBeg, nPosBeg, /* bNoScroll */ true); GetSel(nPosBeg, nPosEnd); // Check the key against the mask. First, make // sure the string is not longer than the mask // (i.e., too much data). if (nPosEnd < m_sMask.GetLength()) { // Check to see if a literal is in this position. // If not, then this is not a user-enterable // position; insert the char from the mask literal. UINT uiLiteralChar(m_sLiteral.GetAt(nPosEnd)); if ('_' != uiLiteralChar) { uiLiteralChar = m_sMaskLiteral.GetAt(nPosEnd); SendChar(uiLiteralChar); GetSel(nPosBeg, nPosEnd); } DWORD dwStyle(::GetWindowLong(GetSafeHwnd(), GWL_STYLE)); _ASSERTE(dwStyle); // Correct for the styles of the edit control. if (dwStyle & ES_LOWERCASE) { uiChar = tolower(uiChar); } if (dwStyle & ES_UPPERCASE) { uiChar = toupper(uiChar); } // The character entered must be either (a) in the valid // character set string, or (b) the same as the characte // in the mask at the current offset. if (-1 != m_sValid.Find(static_cast (uiChar))) { // Check the character entered by the user against the mask. uiLiteralChar = m_sMask.GetAt(nPosEnd); switch (uiLiteralChar) { case '&': if (! isprint(uiChar)) bReturn = false; break; case 'L': if (! isalpha(uiChar)) bReturn = false; break; case '0': if (! isdigit(uiChar)) bReturn = false; break; case 'A': if (! isalnum(uiChar)) bReturn = false; break; case 'a': if (! isalnum(uiChar) && uiChar != VK_SPACE) bReturn = false; break; case '9': if (! isdigit(uiChar) && uiChar != VK_SPACE) bReturn = false; break; case '?': if (! isalpha(uiChar) && uiChar != VK_SPACE) bReturn = false; break; case '#': if (! isdigit(uiChar) && uiChar != VK_SPACE && uiChar != VK_ADD && uiChar != VK_SUBTRACT) bReturn = false; break; default: _ASSERTE(! "Bad format specifier"); bReturn = false; break; } } else if (0 < nPosEnd && uiChar == m_sMaskLiteral.GetAt(nPosEnd - 1)) { // The mask literal character; that is, the "-". 'nPosEnd' // has been incremented already because the "-" has been // reinserted above by the SendChar() call. So, we don't // want to insert this character again, but we don't want // to beep as if it's an error, either. bBeepOnError = false; bReturn = false; } else { // Not a legal character! bReturn = false; } } else { // We are at the end of the buffer. bReturn = false; } } else if (CTRL_V_PASTE == uiChar) { // Allow Ctrl-V. } else if (VK_BACK == uiChar) { // Allow backspace. } else { // All other characters are bad. bReturn = false; } } // Annoy the user. if (! bReturn && bBeepOnError) { ::MessageBeep((UINT) -1); } SetRedraw(true); return bReturn; } // User wants context menu. void MyEdit::OnRButtonUp(UINT uiFlags, CPoint point) { VALIDATE; UNUSED_ALWAYS(uiFlags); HRESULT hr(S_OK); // Create the context menu. If you forgot to update your apps RC2 file // with the MyEditDlg.h and MyEditDlg.rc2 items, this will fail. MyMenu menuContext; EC_B(menuContext.LoadMenu(IDR_MYEDIT)); EC_B(menuContext.LoadToolbar(IDR_MYEDIT)); // Pop the menu. ClientToScreen(&point); CMenu* pDummy = NULL; EC_B(pDummy = menuContext.GetSubMenu(0)); ASSERT_VALID(pDummy); EC_B(pDummy->TrackPopupMenu(TPM_LEFTALIGN | TPM_LEFTBUTTON | TPM_RIGHTBUTTON, point.x, point.y, this)); } // void MyEdit::OnEditCut() { VALIDATE; // Masked edits don't support cutting - it would alter the mask. if (m_bUseMask) { ::MessageBeep((UINT) -1); } else { // Not for read-only edits! DWORD dwStyle(::GetWindowLong(GetSafeHwnd(), GWL_STYLE)); if (dwStyle & ES_READONLY) { ::MessageBeep((UINT) -1); } else { VERIFY(PostMessage(WM_CUT)); } } } // Context menu Copy. void MyEdit::OnEditCopy() { VALIDATE; // Masked edits don't support copying, for security. if (m_bUseMask) { ::MessageBeep((UINT) -1); } else { VERIFY(PostMessage(WM_COPY)); } } // Paste the text in from the Clipboard. The only way to do this is to check // each character, and if the whole thing isn't legal, disallow them all. void MyEdit::OnEditPaste() { VALIDATE; HRESULT hr(S_OK); // Not for read-only edits! DWORD dwStyle(::GetWindowLong(GetSafeHwnd(), GWL_STYLE)); if (dwStyle & ES_READONLY) { ::MessageBeep((UINT) -1); } else { // First get the text off the clipboard. EC_B(OpenClipboard()); char* psz = NULL; EC_B(psz = static_cast (::GetClipboardData(CF_TEXT))); CString s(psz); EC_B_(::CloseClipboard()); if (m_bUseMask) { // Move the cursor to the correct position (the beginning). SetSel(-1, 0, /* bNoScroll */ true); SetSel( 0, 0, /* bNoScroll */ true); for (int nChar = 0, nCharAccepted = 0; nCharAccepted < m_sLiteral.GetLength() && nChar < s.GetLength(); ++nChar) { if ('_' == m_sLiteral[nCharAccepted]) { if (CheckChar(s[nChar], /* bBeepOnError */ false)) { ++nCharAccepted; VERIFY(PostMessage(WM_CHAR, s[nChar])); Generic::SpinTheMessageLoop(); } } else { ++nCharAccepted; } } } else { // Just past it all. VERIFY(PostMessage(WM_PASTE, reinterpret_cast ((LPCSTR) s))); } } } // From context menu, user wants to rest it to the mask. void MyEdit::OnEditClearAll() { VALIDATE; // Not for read-only edits! DWORD dwStyle(::GetWindowLong(GetSafeHwnd(), GWL_STYLE)); if (dwStyle & ES_READONLY) { ::MessageBeep((UINT) -1); } else { SetWindowText(m_sMaskLiteral); } } // From context menu, user wants to select all. void MyEdit::OnEditSelectAll() { VALIDATE; SetFocus(); SetSel(0, -1, /* bNoScroll */ true); } // From context menu, user wants to increase point size. void MyEdit::OnEditFontUp() { VALIDATE; // If we haven't created a font yet (that is, we just using the // default font of the parent dialog). if (! m_pFont) { CreateTheFont(); } // Create a new font one point bigger. if (72 > m_nPoints) { SelectTheFont(m_sFaceName, m_nPoints + 1); } else { ::MessageBeep((UINT) -1); } // Non true-type fonts don't grow at each point-size change. if (0 == m_sFaceName.CompareNoCase("MS Sans Serif") || 0 == m_sFaceName.CompareNoCase("MS Shell Dlg")) { SelectTheFont(m_sFaceName, m_nPoints + 1); } } // From context menu, user wants to decrease point size. void MyEdit::OnEditFontDown() { VALIDATE; // If we haven't created a font yet (that is, we just using the // default font of the parent dialog). if (! m_pFont) { CreateTheFont(); } // Create a new font one point bigger. if (6 < m_nPoints) { SelectTheFont(m_sFaceName, m_nPoints - 1); } else { ::MessageBeep((UINT) -1); } // Non true-type fonts don't grow at each point-size change. if (0 == m_sFaceName.CompareNoCase("MS Sans Serif") || 0 == m_sFaceName.CompareNoCase("MS Shell Dlg")) { SelectTheFont(m_sFaceName, m_nPoints - 1); } } // The font has never been created yet, and the user wants to change the point // size. All this function does is read the current font being used by this // control, and use that info to populate the m_nPoints and m_sFaceName members. // Once that is done, any other function can build and SelectObj a real font into // this control. (The CFont that is incidentally created here is just for temp // use and must not be stored.) void MyEdit::CreateTheFont() { VALIDATE; HRESULT hr(S_OK); CFont* pFont = NULL; EC_B(pFont = GetFont()); ASSERT_VALID(pFont); LOGFONT lf; ::ZeroMemory(&lf, sizeof lf); EC_B(pFont->GetLogFont(&lf)); CDC* pDC = NULL; EC_B(pDC = GetDC()); ASSERT_VALID(pDC); // Store the face and point size. EC_V(m_nPoints = -MulDiv(lf.lfHeight, 72, pDC->GetDeviceCaps(LOGPIXELSY))); m_sFaceName = lf.lfFaceName; // Done! EC_B(ReleaseDC(pDC)); } // Allow the user to edit the text is a larger font in a bigger edit control. void MyEdit::OnEditEdit() { VALIDATE; if (m_bUseMask) { ::MessageBeep((UINT) -1); } else { CreateTheFont(); DWORD dwStyle(::GetWindowLong(GetSafeHwnd(), GWL_STYLE)); bool bReadOnly((dwStyle & ES_READONLY)); _ASSERTE(0 < m_nPoints && "Must set the pointsize first"); MyEditDlg dlg(this, bReadOnly, m_nPoints + 2, m_sRegPlacementsKey); CString s; GetWindowText(s); dlg.m_sEdit = s; if (IDOK == dlg.DoModal()) { s = dlg.m_sEdit; SetWindowText(s); } } } // Return the real value (there may be extra mask bits at the end). CString MyEdit::GetValue() const { VALIDATE; CString s; GetWindowText(s); // Copy the non-mask characters. for (int nChar = 0; nChar < s.GetLength(); ++nChar) { if ('_' == m_sLiteral[nChar]) { if (s[nChar] == m_sMaskLiteral[nChar]) { break; } } } s = s.Left(nChar); // Remove any trailing "-". while ('-' == s.Right(1)) { s = s.Left(s.GetLength() - 1); } return s; } // void MyEdit::SetFocusIndicator(bool b) { VALIDATE; m_bUseFocusIndicator = b; } // This sets a special border around the focused edit control to make it easy // to spot where you are in a busy dialog. void MyEdit::OnSetFocus(CWnd* pOldWnd) { VALIDATE; CEdit::OnSetFocus(pOldWnd); if (m_bUseFocusIndicator) { ModifyStyleEx(0, WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, SWP_FRAMECHANGED); } } // This resets a special border around the focused edit control to make it easy // to spot where you are in a busy dialog. void MyEdit::OnKillFocus(CWnd* pNewWnd) { VALIDATE; CEdit::OnKillFocus(pNewWnd); if (m_bUseFocusIndicator) { ModifyStyleEx(WS_EX_DLGMODALFRAME | WS_EX_WINDOWEDGE, 0, SWP_FRAMECHANGED); } } ///////////////////////////////////////////////////////////////////////////// // MyEditDlg dialog // Constructor. MyEditDlg::MyEditDlg(CWnd* pParent, bool bReadOnly, int nPoints, const CString& sRegPlacementsKey, UINT uiDialogTitle /* = 0U */ ) : MyDialog(MyEditDlg::IDD, "MyEditDlg", sRegPlacementsKey, pParent) , m_bReadOnly(bReadOnly) , m_pFont(NULL) , m_nPoints(nPoints) , m_uiDialogTitle(uiDialogTitle) { //{{AFX_DATA_INIT(MyEditDlg) m_sEdit = _T(""); //}}AFX_DATA_INIT } // Destructor. /* virtual */ MyEditDlg::~MyEditDlg() { }; // void MyEditDlg::DoDataExchange(CDataExchange* pDX) { MyDialog::DoDataExchange(pDX); //{{AFX_DATA_MAP(MyEditDlg) DDX_Control(pDX, IDOK, m_buttonOk); DDX_Control(pDX, IDCANCEL, m_buttonCancel); DDX_Control(pDX, IDC_MYEDIT_EDIT, m_editEdit); DDX_Text(pDX, IDC_MYEDIT_EDIT, m_sEdit); //}}AFX_DATA_MAP } BEGIN_MESSAGE_MAP(MyEditDlg, MyDialog) //{{AFX_MSG_MAP(MyEditDlg) ON_WM_DESTROY() //}}AFX_MSG_MAP END_MESSAGE_MAP() ///////////////////////////////////////////////////////////////////////////// // MyEditDlg message handlers // Handle WM_INITDIALOG. BOOL MyEditDlg::OnInitDialog() { VALIDATE; MyDialog::OnInitDialog(); // Set the window text. if (m_uiDialogTitle) { SetWindowText(Generic::LoadString(m_uiDialogTitle)); } // Set icons on buttons. // Don't let them save their changes; we let them make changes so they // can paste them somewhere else if they want to. if (m_bReadOnly) { GetDlgItem(IDOK)->EnableWindow(false); } // Set the font to true-type, and the size requested by the caller. m_pFont = new CFont; _ASSERTE(2 < m_nPoints && "Seems kinda small"); m_pFont->CreatePointFont(m_nPoints * 10, "Arial"); GetDlgItem(IDC_MYEDIT_EDIT)->SetFont(m_pFont); return TRUE; // return TRUE unless you set the focus to a control // EXCEPTION: OCX Property Pages should return FALSE } // Handle WM_DESTROY. void MyEditDlg::OnDestroy() { VALIDATE; MyDialog::OnDestroy(); delete m_pFont; m_pFont = NULL; }