windows-nt/Source/XPSP1/NT/shell/shell32/limitinput.cpp
2020-09-26 16:20:57 +08:00

1008 lines
31 KiB
C++

/* ************************************************************** *\
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;
}