|
|
/* ************************************************************** *\
ToddB's Super Cool Balloon ToolTip InputLimiter
Copyright Microsoft 1998 \* ************************************************************** */
#include "shellprv.h"
#include "ids.h"
#define IsTextPtr(pszText) ((LPSTR_TEXTCALLBACK != pszText) && !IS_INTRESOURCE(pszText))
#define CHAR_IN_RANGE(ch,l,h) ((ch >= l) && (ch <= h))
#define LIMITINPUTTIMERID 472
// ************************************************************************************************
// CInputLimiter class description
// ************************************************************************************************
class CInputLimiter : public tagLIMITINPUT { public: CInputLimiter(); ~CInputLimiter();
BOOL SubclassEditControl(HWND hwnd, const LIMITINPUT *pli);
protected: BOOL OnChar(HWND hwnd, WPARAM & wParam, LPARAM lParam); LRESULT OnPaste(HWND hwnd, WPARAM wParam, LPARAM lParam); void ShowToolTip(); void HideToolTip(); void CreateToolTipWindow(); BOOL IsValidChar(TCHAR ch, BOOL bPaste);
static LRESULT CALLBACK SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uID, ULONG_PTR dwRefData);
HWND m_hwnd; // the subclassed edit control hwnd
HWND m_hwndToolTip; // the tooltip control
UINT_PTR m_uTimerID; // the timer id
BOOL m_dwCallbacks; // true if any data is callback data.
};
CInputLimiter::CInputLimiter() { // our allocation function should have zeroed our memory. Check to make sure:
ASSERT(0==m_hwndToolTip); ASSERT(0==m_uTimerID); }
CInputLimiter::~CInputLimiter() { // we might have allocated some strings, if we did delete them
if (IsTextPtr(pszFilter)) { delete pszFilter; } if (IsTextPtr(pszTitle)) { delete pszTitle; } if (IsTextPtr(pszMessage)) { delete pszMessage; } }
BOOL CInputLimiter::SubclassEditControl(HWND hwnd, const LIMITINPUT *pli) { if (!IsWindow(hwnd)) { // must have a valid hwnd
TraceMsg(TF_WARNING, "Invalid HWND passed to CInputLimiter::SubclassEditControl"); return FALSE; }
m_hwnd = hwnd;
// validate all the data passed in the pli structure. Return false if
// any of it is out of whack.
dwMask = pli->dwMask;
if (LIM_FLAGS & dwMask) { dwFlags = pli->dwFlags;
if ((LIF_FORCEUPPERCASE|LIF_FORCELOWERCASE) == ((LIF_FORCEUPPERCASE|LIF_FORCELOWERCASE) & dwFlags)) { // cannot use both ForceUpperCase and ForceLowerCase flags
TraceMsg(TF_WARNING, "cannot use both ForceUpperCase and ForceLowerCase flags"); return FALSE; } } else { ASSERT(0==dwFlags); }
if (LIM_HINST & dwMask) { hinst = pli->hinst; } else { ASSERT(0==hinst); }
// keep track of which fields require a valid hwndNotify
ASSERT(0==m_dwCallbacks);
if (LIM_FILTER & dwMask) { if (LIF_CATEGORYFILTER & dwFlags) { // category filters are not callbacks or int resources even though the data looks like it is.
// The don't need any validation.
pszFilter = pli->pszFilter; } else if (LPSTR_TEXTCALLBACK == pli->pszFilter) { pszFilter = pli->pszFilter; m_dwCallbacks |= LIM_FILTER; } else if (IS_INTRESOURCE(pli->pszFilter)) { if (!hinst) { // must have valid hinst in order to use int resources
TraceMsg(TF_WARNING, "must have valid hinst in order to use int resources for filter"); return FALSE; }
// We need to load the target string upfront and store it in a buffer.
DWORD cchSize = 64; DWORD cchLoaded;
for (;;) { pszFilter = new TCHAR[cchSize]; if (!pszFilter) { // Out of memory
TraceMsg(TF_WARNING, "Out of memory in CInputLimiter::SubclassEditControl"); return FALSE; }
cchLoaded = LoadString(hinst, PtrToUint(pli->pszFilter), pszFilter, cchSize); if (0 == cchLoaded) { // Could not load filter resource, pszFilter will get deleted in our destructor
TraceMsg(TF_WARNING, "Could not load filter resource"); return FALSE; } else if (cchLoaded >= cchSize-1) { // didn't fit in the given buffer, try a larger buffer
delete [] pszFilter; cchSize *= 2; } else { // the string loaded successfully
break; } }
ASSERT(IS_VALID_STRING_PTR(pszFilter,-1)); } else { ASSERT(IS_VALID_STRING_PTR(pli->pszFilter,-1)); pszFilter = new TCHAR[lstrlen(pli->pszFilter)+1]; if (!pszFilter) { // Out of memory
TraceMsg(TF_WARNING, "CInputLimiter Out of memory"); return FALSE; } StrCpy(pszFilter, pli->pszFilter); } } else { ASSERT(0==pszFilter); }
if (!(LIF_WARNINGOFF & dwFlags) && !((LIM_TITLE|LIM_MESSAGE) & dwMask)) { // if warnings are on then at least one of Title or Message is required.
TraceMsg(TF_WARNING, "if warnings are on then at least one of Title or Message is required"); return FALSE; }
if (LIM_TITLE & dwMask) { if (LPSTR_TEXTCALLBACK == pli->pszTitle) { pszTitle = pli->pszTitle; m_dwCallbacks |= LIM_TITLE; } else if (IS_INTRESOURCE(pli->pszTitle)) { if (!hinst) { // must have valid hinst in order to use int resources
TraceMsg(TF_WARNING, "must have valid hinst in order to use int resources for title"); return FALSE; } // REVIEW: Does the title need to be laoded up fromt or will the ToolTip control do this
// for us?
pszTitle = pli->pszTitle; } else { ASSERT(IS_VALID_STRING_PTR(pli->pszTitle,-1)); pszTitle = new TCHAR[lstrlen(pli->pszTitle)+1]; StrCpy(pszTitle, pli->pszTitle); } } else { ASSERT(0==pszTitle); }
if (LIM_MESSAGE & dwMask) { if (LPSTR_TEXTCALLBACK == pli->pszMessage) { pszMessage = pli->pszMessage; m_dwCallbacks |= LIM_MESSAGE; } else if (IS_INTRESOURCE(pli->pszMessage)) { if (!hinst) { // must have valid hinst in order to use int resources
TraceMsg(TF_WARNING, "must have valid hinst in order to use int resources for message"); return FALSE; } // We will let the ToolTip control load this string for us
pszMessage = pli->pszMessage; } else { ASSERT(IS_VALID_STRING_PTR(pli->pszMessage,-1)); pszMessage = new TCHAR[lstrlen(pli->pszMessage)+1]; StrCpy(pszMessage, pli->pszMessage); } } else { ASSERT(0==pszMessage); }
if (LIM_ICON & dwMask) { hIcon = pli->hIcon;
if (I_ICONCALLBACK == hIcon) { m_dwCallbacks |= LIM_ICON; } }
if (LIM_NOTIFY & dwMask) { hwndNotify = pli->hwndNotify; } else { hwndNotify = GetParent(m_hwnd); }
if (m_dwCallbacks && !IsWindow(hwndNotify)) { // invalid notify window
TraceMsg(TF_WARNING, "invalid notify window"); return FALSE; }
if (LIM_TIMEOUT & dwMask) { iTimeout = pli->iTimeout; } else { iTimeout = 10000; }
if (LIM_TIPWIDTH & dwMask) { cxTipWidth = pli->cxTipWidth; } else { cxTipWidth = 500; }
// everything in the *pli structure is valid
TraceMsg(TF_GENERAL, "pli structure is valid");
return SetWindowSubclass(hwnd, CInputLimiter::SubclassProc, 0, (LONG_PTR)this); }
LRESULT CALLBACK CInputLimiter::SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uID, ULONG_PTR dwRefData) { CInputLimiter * pthis = (CInputLimiter*)dwRefData;
switch (uMsg) { case WM_CHAR: if (!pthis->OnChar(hwnd, wParam, lParam)) { return 0; } break;
case WM_KILLFOCUS: pthis->HideToolTip(); break;
case WM_TIMER: if (LIMITINPUTTIMERID == wParam) { pthis->HideToolTip(); return 0; } break;
case WM_PASTE: // Paste handler handles calling the super wnd proc when needed
return pthis->OnPaste(hwnd, wParam, lParam);
case WM_NCDESTROY: RemoveWindowSubclass(hwnd, CInputLimiter::SubclassProc, uID); delete pthis; break;
default: break; }
return DefSubclassProc(hwnd, uMsg, wParam, lParam); }
BOOL CInputLimiter::IsValidChar(TCHAR ch, BOOL bPaste) { BOOL bValidChar = FALSE; // start by assuming the character is invalid
if (LIF_CATEGORYFILTER & dwFlags) { TraceMsg(TF_GENERAL, "Processing LIF_CATEGORYFILTER: <0x%08x>", (WORD)pszFilter); // pszFilter is actually a bit field with valid character types
WORD CharType = 0; #define GETSTRINGTYPEEX_MASK 0x1FF
// We only need to call GetStringTypeEx if some of the CT_TYPE1 values are being asked for
if (((WORD)pszFilter) & GETSTRINGTYPEEX_MASK) { TraceMsg(TF_GENERAL, "Calling GetStringTypeEx");
// We treat ch as a one character long string.
// REVIEW: How are DBCS characters handled? Is this fundamentally flawed for win9x?
EVAL(GetStringTypeEx(LOCALE_USER_DEFAULT, CT_CTYPE1, (LPTSTR)&ch, 1, &CharType)); }
if (((WORD)pszFilter) & (WORD)CharType) { TraceMsg(TF_GENERAL, "GetStringTypeEx matched a character"); // GetStringTypeEx found the string in one of the selected groups
bValidChar = !(LIF_EXCLUDEFILTER & dwFlags); } else { TraceMsg(TF_GENERAL, "Checking the extra types not supported by GetStringTypeEx"); // check for the string in our special groups. We will temporarily use bValidChar
// to indicate whether the character was found, not whether it's valid.
if (LICF_BINARYDIGIT & PtrToUint(pszFilter)) { if (CHAR_IN_RANGE(ch, TEXT('0'), TEXT('1'))) { bValidChar = TRUE; goto charWasFound; } } if (LICF_OCTALDIGIT & PtrToUint(pszFilter)) { if (CHAR_IN_RANGE(ch, TEXT('0'), TEXT('7'))) { bValidChar = TRUE; goto charWasFound; } } if (LICF_ATOZUPPER & PtrToUint(pszFilter)) { if (CHAR_IN_RANGE(ch, TEXT('A'), TEXT('Z'))) { bValidChar = TRUE; goto charWasFound; } } if (LICF_ATOZLOWER & PtrToUint(pszFilter)) { if (CHAR_IN_RANGE(ch, TEXT('a'), TEXT('z'))) { bValidChar = TRUE; goto charWasFound; } }
charWasFound: // right now we have perverted the meaning of bValidChar to indicate if the
// character was found or not. We now convert the meaning from "was the
// character found" to "is the character valid" by considering LIF_EXCLUDEFILTER.
if (LIF_EXCLUDEFILTER & dwFlags) { bValidChar = !bValidChar; } } } else { TraceMsg(TF_GENERAL, "Processing string based filter"); // pszFilter points to a NULL terminated string of characters
LPTSTR psz = StrChr(pszFilter, ch);
if (LIF_EXCLUDEFILTER & dwFlags) { bValidChar = (NULL == psz); } else { bValidChar = (NULL != psz); } }
return bValidChar; }
BOOL CInputLimiter::OnChar(HWND hwnd, WPARAM & wParam, LPARAM lParam) { // if the char is a good one return TRUE, this will pass the char on to the
// default window proc. For a bad character do a beep and then display the
// ballon tooltip pointing at the control.
TCHAR ch = (TCHAR)wParam;
if (LIM_FILTER & m_dwCallbacks) { // If we have callbacks then we need to update the filter and/or mask text.
// Otherwise the filter and/or mask text is already correct.
NMLIFILTERINFO lidi = {0}; lidi.hdr.hwndFrom = m_hwnd; lidi.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID); lidi.hdr.code = LIN_GETFILTERINFO; lidi.li.dwMask = LIM_FILTER & m_dwCallbacks;
SendMessage(hwndNotify, WM_NOTIFY, lidi.hdr.idFrom, (LPARAM)&lidi);
pszFilter = lidi.li.pszFilter;
// REVIEW: we should have a way for the notify hanlder to say "store this
// result and stop asking me for the filter to use every time".
}
if (LIF_FORCEUPPERCASE & dwFlags) { ch = (TCHAR)CharUpper((LPTSTR)ch); } else if (LIF_FORCELOWERCASE & dwFlags) { ch = (TCHAR)CharLower((LPTSTR)ch); }
if (IsValidChar(ch, FALSE)) { if (LIF_HIDETIPONVALID & dwFlags) { HideToolTip(); }
// We might have upper or lower cased ch, so reflect this in wParam. Since
// wParam was passed by reference this will effect the message we forward
// on to the original window proc.
wParam = (WPARAM)ch;
return TRUE; } else { // if we get here then an invalid character was entered
if (LIF_NOTIFYONBADCHAR & dwFlags) { NMLIBADCHAR libc = {0}; libc.hdr.hwndFrom = m_hwnd; libc.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID); libc.hdr.code = LIN_BADCHAR; libc.wParam = wParam; // use the original, non case shifted wParam
libc.lParam = lParam;
SendMessage(hwndNotify, WM_NOTIFY, libc.hdr.idFrom, (LPARAM)&libc); }
if (!(LIF_SILENT & dwFlags)) { MessageBeep(MB_OK); }
if (!(LIF_WARNINGOFF & dwFlags)) { ShowToolTip(); }
return FALSE; } }
LRESULT CInputLimiter::OnPaste(HWND hwnd, WPARAM wParam, LPARAM lParam) { // There are hundreds of lines of code in user to successfully handle a paste into an edit control.
// We need to leverage all that code while still disallowing invalid input to result from the paste.
// As a result, what we need to do is to get the clip board data, validate that data, place the
// valid data back onto the clipboard, call the default window proc to let user do it's thing, and
// then restore the clipboard to it's original format.
if (OpenClipboard(hwnd)) { HANDLE hdata; UINT iFormat; DWORD cchBad = 0; // count of the number of bad characters
// REVIEW: Should this be based on the compile type or the window type?
// Compile time check for the correct clipboard format to use:
if (sizeof(WCHAR) == sizeof(TCHAR)) { iFormat = CF_UNICODETEXT; } else { iFormat = CF_TEXT; }
hdata = GetClipboardData(iFormat);
if (hdata) { LPTSTR pszData; pszData = (LPTSTR)GlobalLock(hdata); if (pszData) {
// we need to copy the original data because the clipboard owns the hdata
// pointer. That data will be invalid after we call SetClipboardData.
// We start by calculating the size of the data:
DWORD dwSize = (DWORD)GlobalSize(hdata);
// Use the prefered GlobalAlloc for clipboard data
HANDLE hClone = GlobalAlloc(GPTR, dwSize + sizeof(TCHAR)); HANDLE hNew = GlobalAlloc(GPTR, dwSize + sizeof(TCHAR)); if (hClone && hNew) { LPTSTR pszClone = (LPTSTR)GlobalLock(hClone); LPTSTR pszNew = (LPTSTR)GlobalLock(hNew); if (pszClone && pszNew) { int iNew = 0;
// copy the original data as-is
memcpy(pszClone, pszData, (size_t)dwSize); // ensure that it's NULL terminated
pszClone[(dwSize / sizeof(TCHAR))] = TEXT('\0');
// For a paste, we only call the filter callback once, not once for each
// character. Why? Because.
if (LIM_FILTER & m_dwCallbacks) { // If we have callbacks then we need to update the filter and/or mask text.
// Otherwise the filter and/or mask text is already correct.
NMLIFILTERINFO lidi = {0}; lidi.hdr.hwndFrom = m_hwnd; lidi.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID); lidi.hdr.code = LIN_GETFILTERINFO; lidi.li.dwMask = LIM_FILTER & m_dwCallbacks;
SendMessage(hwndNotify, WM_NOTIFY, lidi.hdr.idFrom, (LPARAM)&lidi);
pszFilter = lidi.li.pszFilter;
// REVIEW: we should have a way for the notify hanlder to say "store this
// result and stop asking me for the filter to use every time".
}
for (LPTSTR psz = pszClone; *psz; psz++) { // we do the Upper/Lower casing one character at a time because we don't want to
// alter pszClone. pszClone is used later to restore the ClipBoard.
if (LIF_FORCEUPPERCASE & dwFlags) { pszNew[iNew] = (TCHAR)CharUpper((LPTSTR)*psz); // yes, this funky cast is correct.
} else if (LIF_FORCELOWERCASE & dwFlags) { pszNew[iNew] = (TCHAR)CharLower((LPTSTR)*psz); // yes, this funky cast is correct.
} else { pszNew[iNew] = *psz; }
if (IsValidChar(pszNew[iNew], TRUE)) { iNew++; } else { if (LIF_NOTIFYONBADCHAR & dwFlags) { NMLIBADCHAR libc = {0}; libc.hdr.hwndFrom = m_hwnd; libc.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID); libc.hdr.code = LIN_BADCHAR; libc.wParam = (WPARAM)pszClone[iNew + cchBad]; // use the original, non case shifted chat
libc.lParam = lParam;
SendMessage(hwndNotify, WM_NOTIFY, libc.hdr.idFrom, (LPARAM)&libc); }
cchBad++;
if (LIF_PASTECANCEL & dwFlags) { iNew = 0; break; } if (LIF_PASTESTOP & dwFlags) { break; } } } pszNew[iNew] = NULL;
// If there are any characters in the paste buffer then we paste the validated string
if (*pszNew) { // we always set the new string. Worst case it's identical to the old string
GlobalUnlock(hNew); pszNew = NULL; SetClipboardData(iFormat, hNew); hNew = NULL;
// call the super proc to do the paste
DefSubclassProc(hwnd, WM_PASTE, wParam, lParam);
// The above call will have closed the clipboard on us. We try to re-open it.
// If this fails it's no big deal, that simply means the SetClipboardData
// call below will fail which is good if somebody else managed to open the
// clipboard in the mean time.
OpenClipboard(hwnd);
// and then we set it back to the original value.
GlobalUnlock(hClone); pszClone = NULL; if (LIF_KEEPCLIPBOARD & dwFlags) { SetClipboardData(iFormat, hClone); hClone = NULL; } } }
if (pszClone) { GlobalUnlock(hClone); }
if (pszNew) { GlobalUnlock(hNew); } }
if (hClone) { GlobalFree(hClone); }
if (hNew) { GlobalFree(hNew); }
// at this point we are done with hdata so unlock it
GlobalUnlock(hdata); } } CloseClipboard();
if (0 == cchBad) { // the entire paste was valid
if (LIF_HIDETIPONVALID & dwFlags) { HideToolTip(); } } else { // if we get here then at least one invalid character was pasted
if (!(LIF_SILENT & dwFlags)) { MessageBeep(MB_OK); }
if (!(LIF_WARNINGOFF & dwFlags)) { ShowToolTip(); } } } return TRUE; }
void CInputLimiter::ShowToolTip() { TraceMsg(TF_GENERAL, "About to show the tooltip");
if (!m_hwndToolTip) { CreateToolTipWindow(); }
// Set the tooltip display point
RECT rc; GetWindowRect(m_hwnd, &rc); int x, y; x = (rc.left+rc.right)/2; if (LIF_WARNINGABOVE & dwFlags) { y = rc.top; } else if (LIF_WARNINGCENTERED & dwFlags) { y = (rc.top+rc.bottom)/2; } else { y = rc.bottom; } SendMessage(m_hwndToolTip, TTM_TRACKPOSITION, 0, MAKELONG(x,y));
TOOLINFO ti = {0}; ti.cbSize = sizeof(ti); ti.hwnd = m_hwnd; ti.uId = 1; if ((LIM_TITLE|LIM_MESSAGE|LIM_ICON) & m_dwCallbacks) { // If we have callbacks then we need to update the tooltip text.
// Otherwise the tooltip text is already correct.
NMLIDISPINFO lidi = {0}; lidi.hdr.hwndFrom = m_hwnd; lidi.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID); lidi.hdr.code = LIN_GETDISPINFO; lidi.li.dwMask = (LIM_TITLE|LIM_MESSAGE|LIM_ICON) & m_dwCallbacks;
SendMessage(hwndNotify, WM_NOTIFY, lidi.hdr.idFrom, (LPARAM)&lidi);
// REARCHITECT How do we use the icon, bold title, message style tooltips?
// Until I learn how I'm just using the message string.
ti.lpszText = lidi.li.pszMessage;
SendMessage(m_hwndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti); if (lidi.li.pszTitle || lidi.li.hIcon) { SendMessage(m_hwndToolTip, TTM_SETTITLE, (WPARAM)lidi.li.hIcon, (LPARAM)lidi.li.pszTitle); } }
// Show the tooltip
SendMessage(m_hwndToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
// Set a timer to hide the tooltip
if (m_uTimerID) { KillTimer(NULL,LIMITINPUTTIMERID); } m_uTimerID = SetTimer(m_hwnd, LIMITINPUTTIMERID, iTimeout, NULL); }
// CreateToolTipWindow
//
// Creates our tooltip control. We share this one tooltip control and use it for all invalid
// input messages. The control is hiden when not in use and then shown when needed.
//
void CInputLimiter::CreateToolTipWindow() { m_hwndToolTip = CreateWindow( TOOLTIPS_CLASS, NULL, WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, CW_USEDEFAULT, m_hwnd, NULL, GetModuleHandle(NULL), NULL);
if (m_hwndToolTip) { SetWindowPos(m_hwndToolTip, HWND_TOPMOST, 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
TOOLINFO ti = {0}; RECT rc = {2,2,2,2};
ti.cbSize = sizeof(ti); ti.uFlags = TTF_TRACK | TTF_TRANSPARENT; ti.hwnd = m_hwnd; ti.uId = 1; ti.hinst = hinst; // REARCHITECT: How do we use the icon, bold title, message style tooltips?
// Until I learn how I'm just using the message string.
ti.lpszText = pszMessage;
// set the version so we can have non buggy mouse event forwarding
SendMessage(m_hwndToolTip, CCM_SETVERSION, COMCTL32_VERSION, 0); SendMessage(m_hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti); SendMessage(m_hwndToolTip, TTM_SETMAXTIPWIDTH, 0, cxTipWidth); SendMessage(m_hwndToolTip, TTM_SETMARGIN, 0, (LPARAM)&rc); if (pszTitle || hIcon) { // REARCHITECT: hIcon needs to be an image list index or some such. Get details
// on how this really works.
SendMessage(m_hwndToolTip, TTM_SETTITLE, (WPARAM)hIcon, (LPARAM)pszTitle); } } else { // failed to create tool tip window, now what should we do? Unsubclass ourselves?
TraceMsg(TF_GENERAL, "Failed to create tooltip window"); } }
void CInputLimiter::HideToolTip() { // When the timer fires we hide the tooltip window
if (m_uTimerID) { KillTimer(m_hwnd,LIMITINPUTTIMERID); m_uTimerID = 0; } if (m_hwndToolTip) { SendMessage(m_hwndToolTip, TTM_TRACKACTIVATE, FALSE, 0); } }
// allows caller to pass in already contructed LIMITINPUT structure...
HRESULT SHLimitInputEditWithFlags(HWND hwndEdit, LIMITINPUT * pli) { HRESULT hr; CInputLimiter *pInputLimiter = new CInputLimiter; if (pInputLimiter) { if (pInputLimiter->SubclassEditControl(hwndEdit, pli)) { hr = S_OK; } else { hr = E_FAIL; delete pInputLimiter; } } else { hr = E_OUTOFMEMORY; }
return hr; }
// LimitInput
//
// Limits the characters that can be entered into an edit box. It intercepts WM_CHAR
// messages and only allows certain characters through. Some characters, such as backspace
// are always allowed through.
//
// Args:
// hwndEdit Handle to an edit control. Results will be unpredictable if any other window
// type is passed in.
//
// pli Pointer to a LIMITINPUT structure that determines how the input is limited.
HRESULT SHLimitInputEditChars(HWND hwndEdit, LPCWSTR pszValidChars, LPCWSTR pszInvalidChars) { LPWSTR pszMessage = NULL;
LIMITINPUT li = {0}; li.cbSize = sizeof(li); li.dwMask = LIM_FLAGS | LIM_FILTER | LIM_MESSAGE | LIM_HINST; li.dwFlags = LIF_HIDETIPONVALID; li.hinst = g_hinst; if (pszValidChars) { // ick, li.pszFilter is used as const, but since CInputLimiter is derived from the struct itd be a
// pain to define it as such.
li.pszFilter = (LPWSTR)pszValidChars; li.dwFlags |= LIF_INCLUDEFILTER; } else { li.pszFilter = (LPWSTR)pszInvalidChars; li.dwFlags |= LIF_EXCLUDEFILTER; }
// create the error message.
PCWSTR pszChars = pszInvalidChars ? pszInvalidChars : pszValidChars; PWSTR pszSpacedChars = new WCHAR[2 * lstrlen(pszChars) + 1]; if (pszSpacedChars) { // we're mimicing what IDS_INVALIDFN does for the known set of bad chars on the filesystem --
// append each char and separate them by spaces.
PWSTR psz = pszSpacedChars; for (int i = 0; i < lstrlen(pszChars); i++) { *psz++ = pszChars[i]; *psz++ = L' '; } *psz = 0;
int id = pszInvalidChars ? IDS_CHARSINVALID : IDS_CHARSVALID; pszMessage = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(id), pszSpacedChars);
delete [] pszSpacedChars; }
if (pszMessage) { li.pszMessage = pszMessage; } else { // fall back to the old message
li.pszMessage = MAKEINTRESOURCE(IDS_INVALIDFN); }
HRESULT hr = SHLimitInputEditWithFlags(hwndEdit, &li);
if (pszMessage) { LocalFree(pszMessage); } return hr; }
HRESULT SHLimitInputEdit(HWND hwndEdit, IShellFolder *psf) { IItemNameLimits *pinl; HRESULT hr = psf->QueryInterface(IID_PPV_ARG(IItemNameLimits, &pinl)); if (SUCCEEDED(hr)) { LPWSTR pszValidChars; LPWSTR pszInvalidChars; hr = pinl->GetValidCharacters(&pszValidChars, &pszInvalidChars); if (SUCCEEDED(hr)) { hr = SHLimitInputEditChars(hwndEdit, pszValidChars, pszInvalidChars);
if (pszValidChars) CoTaskMemFree(pszValidChars); if (pszInvalidChars) CoTaskMemFree(pszInvalidChars); } pinl->Release(); }
return hr; }
typedef struct tagCBLIMITINPUT { HRESULT hr; IShellFolder *psf; } CBLIMITINPUT;
// Limiting the input on a combo box is special cased because you first
// have to find the edit box and then LimitInput on that.
BOOL CALLBACK FindTheEditBox(HWND hwnd, LPARAM lParam) { // The combo box only has one child, subclass it
CBLIMITINPUT *pcbli = (CBLIMITINPUT*)lParam;
pcbli->hr = SHLimitInputEdit(hwnd, pcbli->psf); return FALSE; }
HRESULT SHLimitInputCombo(HWND hwndComboBox, IShellFolder *psf) { CBLIMITINPUT cbli; cbli.hr = E_FAIL; cbli.psf = psf;
EnumChildWindows(hwndComboBox, FindTheEditBox, (LPARAM)&cbli);
return cbli.hr; }
|