1008 lines
31 KiB
C++
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;
|
||
|
}
|