Source code of Windows XP (NT5)
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

1007 lines
31 KiB

  1. /* ************************************************************** *\
  2. ToddB's Super Cool Balloon ToolTip InputLimiter
  3. Copyright Microsoft 1998
  4. \* ************************************************************** */
  5. #include "shellprv.h"
  6. #include "ids.h"
  7. #define IsTextPtr(pszText) ((LPSTR_TEXTCALLBACK != pszText) && !IS_INTRESOURCE(pszText))
  8. #define CHAR_IN_RANGE(ch,l,h) ((ch >= l) && (ch <= h))
  9. #define LIMITINPUTTIMERID 472
  10. // ************************************************************************************************
  11. // CInputLimiter class description
  12. // ************************************************************************************************
  13. class CInputLimiter : public tagLIMITINPUT
  14. {
  15. public:
  16. CInputLimiter();
  17. ~CInputLimiter();
  18. BOOL SubclassEditControl(HWND hwnd, const LIMITINPUT *pli);
  19. protected:
  20. BOOL OnChar(HWND hwnd, WPARAM & wParam, LPARAM lParam);
  21. LRESULT OnPaste(HWND hwnd, WPARAM wParam, LPARAM lParam);
  22. void ShowToolTip();
  23. void HideToolTip();
  24. void CreateToolTipWindow();
  25. BOOL IsValidChar(TCHAR ch, BOOL bPaste);
  26. static LRESULT CALLBACK SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uID, ULONG_PTR dwRefData);
  27. HWND m_hwnd; // the subclassed edit control hwnd
  28. HWND m_hwndToolTip; // the tooltip control
  29. UINT_PTR m_uTimerID; // the timer id
  30. BOOL m_dwCallbacks; // true if any data is callback data.
  31. };
  32. CInputLimiter::CInputLimiter()
  33. {
  34. // our allocation function should have zeroed our memory. Check to make sure:
  35. ASSERT(0==m_hwndToolTip);
  36. ASSERT(0==m_uTimerID);
  37. }
  38. CInputLimiter::~CInputLimiter()
  39. {
  40. // we might have allocated some strings, if we did delete them
  41. if (IsTextPtr(pszFilter))
  42. {
  43. delete pszFilter;
  44. }
  45. if (IsTextPtr(pszTitle))
  46. {
  47. delete pszTitle;
  48. }
  49. if (IsTextPtr(pszMessage))
  50. {
  51. delete pszMessage;
  52. }
  53. }
  54. BOOL CInputLimiter::SubclassEditControl(HWND hwnd, const LIMITINPUT *pli)
  55. {
  56. if (!IsWindow(hwnd))
  57. {
  58. // must have a valid hwnd
  59. TraceMsg(TF_WARNING, "Invalid HWND passed to CInputLimiter::SubclassEditControl");
  60. return FALSE;
  61. }
  62. m_hwnd = hwnd;
  63. // validate all the data passed in the pli structure. Return false if
  64. // any of it is out of whack.
  65. dwMask = pli->dwMask;
  66. if (LIM_FLAGS & dwMask)
  67. {
  68. dwFlags = pli->dwFlags;
  69. if ((LIF_FORCEUPPERCASE|LIF_FORCELOWERCASE) == ((LIF_FORCEUPPERCASE|LIF_FORCELOWERCASE) & dwFlags))
  70. {
  71. // cannot use both ForceUpperCase and ForceLowerCase flags
  72. TraceMsg(TF_WARNING, "cannot use both ForceUpperCase and ForceLowerCase flags");
  73. return FALSE;
  74. }
  75. }
  76. else
  77. {
  78. ASSERT(0==dwFlags);
  79. }
  80. if (LIM_HINST & dwMask)
  81. {
  82. hinst = pli->hinst;
  83. }
  84. else
  85. {
  86. ASSERT(0==hinst);
  87. }
  88. // keep track of which fields require a valid hwndNotify
  89. ASSERT(0==m_dwCallbacks);
  90. if (LIM_FILTER & dwMask)
  91. {
  92. if (LIF_CATEGORYFILTER & dwFlags)
  93. {
  94. // category filters are not callbacks or int resources even though the data looks like it is.
  95. // The don't need any validation.
  96. pszFilter = pli->pszFilter;
  97. }
  98. else if (LPSTR_TEXTCALLBACK == pli->pszFilter)
  99. {
  100. pszFilter = pli->pszFilter;
  101. m_dwCallbacks |= LIM_FILTER;
  102. }
  103. else if (IS_INTRESOURCE(pli->pszFilter))
  104. {
  105. if (!hinst)
  106. {
  107. // must have valid hinst in order to use int resources
  108. TraceMsg(TF_WARNING, "must have valid hinst in order to use int resources for filter");
  109. return FALSE;
  110. }
  111. // We need to load the target string upfront and store it in a buffer.
  112. DWORD cchSize = 64;
  113. DWORD cchLoaded;
  114. for (;;)
  115. {
  116. pszFilter = new TCHAR[cchSize];
  117. if (!pszFilter)
  118. {
  119. // Out of memory
  120. TraceMsg(TF_WARNING, "Out of memory in CInputLimiter::SubclassEditControl");
  121. return FALSE;
  122. }
  123. cchLoaded = LoadString(hinst, PtrToUint(pli->pszFilter), pszFilter, cchSize);
  124. if (0 == cchLoaded)
  125. {
  126. // Could not load filter resource, pszFilter will get deleted in our destructor
  127. TraceMsg(TF_WARNING, "Could not load filter resource");
  128. return FALSE;
  129. }
  130. else if (cchLoaded >= cchSize-1)
  131. {
  132. // didn't fit in the given buffer, try a larger buffer
  133. delete [] pszFilter;
  134. cchSize *= 2;
  135. }
  136. else
  137. {
  138. // the string loaded successfully
  139. break;
  140. }
  141. }
  142. ASSERT(IS_VALID_STRING_PTR(pszFilter,-1));
  143. }
  144. else
  145. {
  146. ASSERT(IS_VALID_STRING_PTR(pli->pszFilter,-1));
  147. pszFilter = new TCHAR[lstrlen(pli->pszFilter)+1];
  148. if (!pszFilter)
  149. {
  150. // Out of memory
  151. TraceMsg(TF_WARNING, "CInputLimiter Out of memory");
  152. return FALSE;
  153. }
  154. StrCpy(pszFilter, pli->pszFilter);
  155. }
  156. }
  157. else
  158. {
  159. ASSERT(0==pszFilter);
  160. }
  161. if (!(LIF_WARNINGOFF & dwFlags) && !((LIM_TITLE|LIM_MESSAGE) & dwMask))
  162. {
  163. // if warnings are on then at least one of Title or Message is required.
  164. TraceMsg(TF_WARNING, "if warnings are on then at least one of Title or Message is required");
  165. return FALSE;
  166. }
  167. if (LIM_TITLE & dwMask)
  168. {
  169. if (LPSTR_TEXTCALLBACK == pli->pszTitle)
  170. {
  171. pszTitle = pli->pszTitle;
  172. m_dwCallbacks |= LIM_TITLE;
  173. }
  174. else if (IS_INTRESOURCE(pli->pszTitle))
  175. {
  176. if (!hinst)
  177. {
  178. // must have valid hinst in order to use int resources
  179. TraceMsg(TF_WARNING, "must have valid hinst in order to use int resources for title");
  180. return FALSE;
  181. }
  182. // REVIEW: Does the title need to be laoded up fromt or will the ToolTip control do this
  183. // for us?
  184. pszTitle = pli->pszTitle;
  185. }
  186. else
  187. {
  188. ASSERT(IS_VALID_STRING_PTR(pli->pszTitle,-1));
  189. pszTitle = new TCHAR[lstrlen(pli->pszTitle)+1];
  190. StrCpy(pszTitle, pli->pszTitle);
  191. }
  192. }
  193. else
  194. {
  195. ASSERT(0==pszTitle);
  196. }
  197. if (LIM_MESSAGE & dwMask)
  198. {
  199. if (LPSTR_TEXTCALLBACK == pli->pszMessage)
  200. {
  201. pszMessage = pli->pszMessage;
  202. m_dwCallbacks |= LIM_MESSAGE;
  203. }
  204. else if (IS_INTRESOURCE(pli->pszMessage))
  205. {
  206. if (!hinst)
  207. {
  208. // must have valid hinst in order to use int resources
  209. TraceMsg(TF_WARNING, "must have valid hinst in order to use int resources for message");
  210. return FALSE;
  211. }
  212. // We will let the ToolTip control load this string for us
  213. pszMessage = pli->pszMessage;
  214. }
  215. else
  216. {
  217. ASSERT(IS_VALID_STRING_PTR(pli->pszMessage,-1));
  218. pszMessage = new TCHAR[lstrlen(pli->pszMessage)+1];
  219. StrCpy(pszMessage, pli->pszMessage);
  220. }
  221. }
  222. else
  223. {
  224. ASSERT(0==pszMessage);
  225. }
  226. if (LIM_ICON & dwMask)
  227. {
  228. hIcon = pli->hIcon;
  229. if (I_ICONCALLBACK == hIcon)
  230. {
  231. m_dwCallbacks |= LIM_ICON;
  232. }
  233. }
  234. if (LIM_NOTIFY & dwMask)
  235. {
  236. hwndNotify = pli->hwndNotify;
  237. }
  238. else
  239. {
  240. hwndNotify = GetParent(m_hwnd);
  241. }
  242. if (m_dwCallbacks && !IsWindow(hwndNotify))
  243. {
  244. // invalid notify window
  245. TraceMsg(TF_WARNING, "invalid notify window");
  246. return FALSE;
  247. }
  248. if (LIM_TIMEOUT & dwMask)
  249. {
  250. iTimeout = pli->iTimeout;
  251. }
  252. else
  253. {
  254. iTimeout = 10000;
  255. }
  256. if (LIM_TIPWIDTH & dwMask)
  257. {
  258. cxTipWidth = pli->cxTipWidth;
  259. }
  260. else
  261. {
  262. cxTipWidth = 500;
  263. }
  264. // everything in the *pli structure is valid
  265. TraceMsg(TF_GENERAL, "pli structure is valid");
  266. return SetWindowSubclass(hwnd, CInputLimiter::SubclassProc, 0, (LONG_PTR)this);
  267. }
  268. LRESULT CALLBACK CInputLimiter::SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam, UINT_PTR uID, ULONG_PTR dwRefData)
  269. {
  270. CInputLimiter * pthis = (CInputLimiter*)dwRefData;
  271. switch (uMsg)
  272. {
  273. case WM_CHAR:
  274. if (!pthis->OnChar(hwnd, wParam, lParam))
  275. {
  276. return 0;
  277. }
  278. break;
  279. case WM_KILLFOCUS:
  280. pthis->HideToolTip();
  281. break;
  282. case WM_TIMER:
  283. if (LIMITINPUTTIMERID == wParam)
  284. {
  285. pthis->HideToolTip();
  286. return 0;
  287. }
  288. break;
  289. case WM_PASTE:
  290. // Paste handler handles calling the super wnd proc when needed
  291. return pthis->OnPaste(hwnd, wParam, lParam);
  292. case WM_NCDESTROY:
  293. RemoveWindowSubclass(hwnd, CInputLimiter::SubclassProc, uID);
  294. delete pthis;
  295. break;
  296. default:
  297. break;
  298. }
  299. return DefSubclassProc(hwnd, uMsg, wParam, lParam);
  300. }
  301. BOOL CInputLimiter::IsValidChar(TCHAR ch, BOOL bPaste)
  302. {
  303. BOOL bValidChar = FALSE; // start by assuming the character is invalid
  304. if (LIF_CATEGORYFILTER & dwFlags)
  305. {
  306. TraceMsg(TF_GENERAL, "Processing LIF_CATEGORYFILTER: <0x%08x>", (WORD)pszFilter);
  307. // pszFilter is actually a bit field with valid character types
  308. WORD CharType = 0;
  309. #define GETSTRINGTYPEEX_MASK 0x1FF
  310. // We only need to call GetStringTypeEx if some of the CT_TYPE1 values are being asked for
  311. if (((WORD)pszFilter) & GETSTRINGTYPEEX_MASK)
  312. {
  313. TraceMsg(TF_GENERAL, "Calling GetStringTypeEx");
  314. // We treat ch as a one character long string.
  315. // REVIEW: How are DBCS characters handled? Is this fundamentally flawed for win9x?
  316. EVAL(GetStringTypeEx(LOCALE_USER_DEFAULT, CT_CTYPE1, (LPTSTR)&ch, 1, &CharType));
  317. }
  318. if (((WORD)pszFilter) & (WORD)CharType)
  319. {
  320. TraceMsg(TF_GENERAL, "GetStringTypeEx matched a character");
  321. // GetStringTypeEx found the string in one of the selected groups
  322. bValidChar = !(LIF_EXCLUDEFILTER & dwFlags);
  323. }
  324. else
  325. {
  326. TraceMsg(TF_GENERAL, "Checking the extra types not supported by GetStringTypeEx");
  327. // check for the string in our special groups. We will temporarily use bValidChar
  328. // to indicate whether the character was found, not whether it's valid.
  329. if (LICF_BINARYDIGIT & PtrToUint(pszFilter))
  330. {
  331. if (CHAR_IN_RANGE(ch, TEXT('0'), TEXT('1')))
  332. {
  333. bValidChar = TRUE;
  334. goto charWasFound;
  335. }
  336. }
  337. if (LICF_OCTALDIGIT & PtrToUint(pszFilter))
  338. {
  339. if (CHAR_IN_RANGE(ch, TEXT('0'), TEXT('7')))
  340. {
  341. bValidChar = TRUE;
  342. goto charWasFound;
  343. }
  344. }
  345. if (LICF_ATOZUPPER & PtrToUint(pszFilter))
  346. {
  347. if (CHAR_IN_RANGE(ch, TEXT('A'), TEXT('Z')))
  348. {
  349. bValidChar = TRUE;
  350. goto charWasFound;
  351. }
  352. }
  353. if (LICF_ATOZLOWER & PtrToUint(pszFilter))
  354. {
  355. if (CHAR_IN_RANGE(ch, TEXT('a'), TEXT('z')))
  356. {
  357. bValidChar = TRUE;
  358. goto charWasFound;
  359. }
  360. }
  361. charWasFound:
  362. // right now we have perverted the meaning of bValidChar to indicate if the
  363. // character was found or not. We now convert the meaning from "was the
  364. // character found" to "is the character valid" by considering LIF_EXCLUDEFILTER.
  365. if (LIF_EXCLUDEFILTER & dwFlags)
  366. {
  367. bValidChar = !bValidChar;
  368. }
  369. }
  370. }
  371. else
  372. {
  373. TraceMsg(TF_GENERAL, "Processing string based filter");
  374. // pszFilter points to a NULL terminated string of characters
  375. LPTSTR psz = StrChr(pszFilter, ch);
  376. if (LIF_EXCLUDEFILTER & dwFlags)
  377. {
  378. bValidChar = (NULL == psz);
  379. }
  380. else
  381. {
  382. bValidChar = (NULL != psz);
  383. }
  384. }
  385. return bValidChar;
  386. }
  387. BOOL CInputLimiter::OnChar(HWND hwnd, WPARAM & wParam, LPARAM lParam)
  388. {
  389. // if the char is a good one return TRUE, this will pass the char on to the
  390. // default window proc. For a bad character do a beep and then display the
  391. // ballon tooltip pointing at the control.
  392. TCHAR ch = (TCHAR)wParam;
  393. if (LIM_FILTER & m_dwCallbacks)
  394. {
  395. // If we have callbacks then we need to update the filter and/or mask text.
  396. // Otherwise the filter and/or mask text is already correct.
  397. NMLIFILTERINFO lidi = {0};
  398. lidi.hdr.hwndFrom = m_hwnd;
  399. lidi.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID);
  400. lidi.hdr.code = LIN_GETFILTERINFO;
  401. lidi.li.dwMask = LIM_FILTER & m_dwCallbacks;
  402. SendMessage(hwndNotify, WM_NOTIFY, lidi.hdr.idFrom, (LPARAM)&lidi);
  403. pszFilter = lidi.li.pszFilter;
  404. // REVIEW: we should have a way for the notify hanlder to say "store this
  405. // result and stop asking me for the filter to use every time".
  406. }
  407. if (LIF_FORCEUPPERCASE & dwFlags)
  408. {
  409. ch = (TCHAR)CharUpper((LPTSTR)ch);
  410. }
  411. else if (LIF_FORCELOWERCASE & dwFlags)
  412. {
  413. ch = (TCHAR)CharLower((LPTSTR)ch);
  414. }
  415. if (IsValidChar(ch, FALSE))
  416. {
  417. if (LIF_HIDETIPONVALID & dwFlags)
  418. {
  419. HideToolTip();
  420. }
  421. // We might have upper or lower cased ch, so reflect this in wParam. Since
  422. // wParam was passed by reference this will effect the message we forward
  423. // on to the original window proc.
  424. wParam = (WPARAM)ch;
  425. return TRUE;
  426. }
  427. else
  428. {
  429. // if we get here then an invalid character was entered
  430. if (LIF_NOTIFYONBADCHAR & dwFlags)
  431. {
  432. NMLIBADCHAR libc = {0};
  433. libc.hdr.hwndFrom = m_hwnd;
  434. libc.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID);
  435. libc.hdr.code = LIN_BADCHAR;
  436. libc.wParam = wParam; // use the original, non case shifted wParam
  437. libc.lParam = lParam;
  438. SendMessage(hwndNotify, WM_NOTIFY, libc.hdr.idFrom, (LPARAM)&libc);
  439. }
  440. if (!(LIF_SILENT & dwFlags))
  441. {
  442. MessageBeep(MB_OK);
  443. }
  444. if (!(LIF_WARNINGOFF & dwFlags))
  445. {
  446. ShowToolTip();
  447. }
  448. return FALSE;
  449. }
  450. }
  451. LRESULT CInputLimiter::OnPaste(HWND hwnd, WPARAM wParam, LPARAM lParam)
  452. {
  453. // There are hundreds of lines of code in user to successfully handle a paste into an edit control.
  454. // We need to leverage all that code while still disallowing invalid input to result from the paste.
  455. // As a result, what we need to do is to get the clip board data, validate that data, place the
  456. // valid data back onto the clipboard, call the default window proc to let user do it's thing, and
  457. // then restore the clipboard to it's original format.
  458. if (OpenClipboard(hwnd))
  459. {
  460. HANDLE hdata;
  461. UINT iFormat;
  462. DWORD cchBad = 0; // count of the number of bad characters
  463. // REVIEW: Should this be based on the compile type or the window type?
  464. // Compile time check for the correct clipboard format to use:
  465. if (sizeof(WCHAR) == sizeof(TCHAR))
  466. {
  467. iFormat = CF_UNICODETEXT;
  468. }
  469. else
  470. {
  471. iFormat = CF_TEXT;
  472. }
  473. hdata = GetClipboardData(iFormat);
  474. if (hdata)
  475. {
  476. LPTSTR pszData;
  477. pszData = (LPTSTR)GlobalLock(hdata);
  478. if (pszData)
  479. {
  480. // we need to copy the original data because the clipboard owns the hdata
  481. // pointer. That data will be invalid after we call SetClipboardData.
  482. // We start by calculating the size of the data:
  483. DWORD dwSize = (DWORD)GlobalSize(hdata);
  484. // Use the prefered GlobalAlloc for clipboard data
  485. HANDLE hClone = GlobalAlloc(GPTR, dwSize + sizeof(TCHAR));
  486. HANDLE hNew = GlobalAlloc(GPTR, dwSize + sizeof(TCHAR));
  487. if (hClone && hNew)
  488. {
  489. LPTSTR pszClone = (LPTSTR)GlobalLock(hClone);
  490. LPTSTR pszNew = (LPTSTR)GlobalLock(hNew);
  491. if (pszClone && pszNew)
  492. {
  493. int iNew = 0;
  494. // copy the original data as-is
  495. memcpy(pszClone, pszData, (size_t)dwSize);
  496. // ensure that it's NULL terminated
  497. pszClone[(dwSize / sizeof(TCHAR))] = TEXT('\0');
  498. // For a paste, we only call the filter callback once, not once for each
  499. // character. Why? Because.
  500. if (LIM_FILTER & m_dwCallbacks)
  501. {
  502. // If we have callbacks then we need to update the filter and/or mask text.
  503. // Otherwise the filter and/or mask text is already correct.
  504. NMLIFILTERINFO lidi = {0};
  505. lidi.hdr.hwndFrom = m_hwnd;
  506. lidi.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID);
  507. lidi.hdr.code = LIN_GETFILTERINFO;
  508. lidi.li.dwMask = LIM_FILTER & m_dwCallbacks;
  509. SendMessage(hwndNotify, WM_NOTIFY, lidi.hdr.idFrom, (LPARAM)&lidi);
  510. pszFilter = lidi.li.pszFilter;
  511. // REVIEW: we should have a way for the notify hanlder to say "store this
  512. // result and stop asking me for the filter to use every time".
  513. }
  514. for (LPTSTR psz = pszClone; *psz; psz++)
  515. {
  516. // we do the Upper/Lower casing one character at a time because we don't want to
  517. // alter pszClone. pszClone is used later to restore the ClipBoard.
  518. if (LIF_FORCEUPPERCASE & dwFlags)
  519. {
  520. pszNew[iNew] = (TCHAR)CharUpper((LPTSTR)*psz); // yes, this funky cast is correct.
  521. }
  522. else if (LIF_FORCELOWERCASE & dwFlags)
  523. {
  524. pszNew[iNew] = (TCHAR)CharLower((LPTSTR)*psz); // yes, this funky cast is correct.
  525. }
  526. else
  527. {
  528. pszNew[iNew] = *psz;
  529. }
  530. if (IsValidChar(pszNew[iNew], TRUE))
  531. {
  532. iNew++;
  533. }
  534. else
  535. {
  536. if (LIF_NOTIFYONBADCHAR & dwFlags)
  537. {
  538. NMLIBADCHAR libc = {0};
  539. libc.hdr.hwndFrom = m_hwnd;
  540. libc.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID);
  541. libc.hdr.code = LIN_BADCHAR;
  542. libc.wParam = (WPARAM)pszClone[iNew + cchBad]; // use the original, non case shifted chat
  543. libc.lParam = lParam;
  544. SendMessage(hwndNotify, WM_NOTIFY, libc.hdr.idFrom, (LPARAM)&libc);
  545. }
  546. cchBad++;
  547. if (LIF_PASTECANCEL & dwFlags)
  548. {
  549. iNew = 0;
  550. break;
  551. }
  552. if (LIF_PASTESTOP & dwFlags)
  553. {
  554. break;
  555. }
  556. }
  557. }
  558. pszNew[iNew] = NULL;
  559. // If there are any characters in the paste buffer then we paste the validated string
  560. if (*pszNew)
  561. {
  562. // we always set the new string. Worst case it's identical to the old string
  563. GlobalUnlock(hNew);
  564. pszNew = NULL;
  565. SetClipboardData(iFormat, hNew);
  566. hNew = NULL;
  567. // call the super proc to do the paste
  568. DefSubclassProc(hwnd, WM_PASTE, wParam, lParam);
  569. // The above call will have closed the clipboard on us. We try to re-open it.
  570. // If this fails it's no big deal, that simply means the SetClipboardData
  571. // call below will fail which is good if somebody else managed to open the
  572. // clipboard in the mean time.
  573. OpenClipboard(hwnd);
  574. // and then we set it back to the original value.
  575. GlobalUnlock(hClone);
  576. pszClone = NULL;
  577. if (LIF_KEEPCLIPBOARD & dwFlags)
  578. {
  579. SetClipboardData(iFormat, hClone);
  580. hClone = NULL;
  581. }
  582. }
  583. }
  584. if (pszClone)
  585. {
  586. GlobalUnlock(hClone);
  587. }
  588. if (pszNew)
  589. {
  590. GlobalUnlock(hNew);
  591. }
  592. }
  593. if (hClone)
  594. {
  595. GlobalFree(hClone);
  596. }
  597. if (hNew)
  598. {
  599. GlobalFree(hNew);
  600. }
  601. // at this point we are done with hdata so unlock it
  602. GlobalUnlock(hdata);
  603. }
  604. }
  605. CloseClipboard();
  606. if (0 == cchBad)
  607. {
  608. // the entire paste was valid
  609. if (LIF_HIDETIPONVALID & dwFlags)
  610. {
  611. HideToolTip();
  612. }
  613. }
  614. else
  615. {
  616. // if we get here then at least one invalid character was pasted
  617. if (!(LIF_SILENT & dwFlags))
  618. {
  619. MessageBeep(MB_OK);
  620. }
  621. if (!(LIF_WARNINGOFF & dwFlags))
  622. {
  623. ShowToolTip();
  624. }
  625. }
  626. }
  627. return TRUE;
  628. }
  629. void CInputLimiter::ShowToolTip()
  630. {
  631. TraceMsg(TF_GENERAL, "About to show the tooltip");
  632. if (!m_hwndToolTip)
  633. {
  634. CreateToolTipWindow();
  635. }
  636. // Set the tooltip display point
  637. RECT rc;
  638. GetWindowRect(m_hwnd, &rc);
  639. int x, y;
  640. x = (rc.left+rc.right)/2;
  641. if (LIF_WARNINGABOVE & dwFlags)
  642. {
  643. y = rc.top;
  644. }
  645. else if (LIF_WARNINGCENTERED & dwFlags)
  646. {
  647. y = (rc.top+rc.bottom)/2;
  648. }
  649. else
  650. {
  651. y = rc.bottom;
  652. }
  653. SendMessage(m_hwndToolTip, TTM_TRACKPOSITION, 0, MAKELONG(x,y));
  654. TOOLINFO ti = {0};
  655. ti.cbSize = sizeof(ti);
  656. ti.hwnd = m_hwnd;
  657. ti.uId = 1;
  658. if ((LIM_TITLE|LIM_MESSAGE|LIM_ICON) & m_dwCallbacks)
  659. {
  660. // If we have callbacks then we need to update the tooltip text.
  661. // Otherwise the tooltip text is already correct.
  662. NMLIDISPINFO lidi = {0};
  663. lidi.hdr.hwndFrom = m_hwnd;
  664. lidi.hdr.idFrom = GetWindowLong(m_hwnd, GWL_ID);
  665. lidi.hdr.code = LIN_GETDISPINFO;
  666. lidi.li.dwMask = (LIM_TITLE|LIM_MESSAGE|LIM_ICON) & m_dwCallbacks;
  667. SendMessage(hwndNotify, WM_NOTIFY, lidi.hdr.idFrom, (LPARAM)&lidi);
  668. // REARCHITECT How do we use the icon, bold title, message style tooltips?
  669. // Until I learn how I'm just using the message string.
  670. ti.lpszText = lidi.li.pszMessage;
  671. SendMessage(m_hwndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
  672. if (lidi.li.pszTitle || lidi.li.hIcon)
  673. {
  674. SendMessage(m_hwndToolTip, TTM_SETTITLE, (WPARAM)lidi.li.hIcon, (LPARAM)lidi.li.pszTitle);
  675. }
  676. }
  677. // Show the tooltip
  678. SendMessage(m_hwndToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
  679. // Set a timer to hide the tooltip
  680. if (m_uTimerID)
  681. {
  682. KillTimer(NULL,LIMITINPUTTIMERID);
  683. }
  684. m_uTimerID = SetTimer(m_hwnd, LIMITINPUTTIMERID, iTimeout, NULL);
  685. }
  686. // CreateToolTipWindow
  687. //
  688. // Creates our tooltip control. We share this one tooltip control and use it for all invalid
  689. // input messages. The control is hiden when not in use and then shown when needed.
  690. //
  691. void CInputLimiter::CreateToolTipWindow()
  692. {
  693. m_hwndToolTip = CreateWindow(
  694. TOOLTIPS_CLASS,
  695. NULL,
  696. WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON,
  697. CW_USEDEFAULT,
  698. CW_USEDEFAULT,
  699. CW_USEDEFAULT,
  700. CW_USEDEFAULT,
  701. m_hwnd,
  702. NULL,
  703. GetModuleHandle(NULL),
  704. NULL);
  705. if (m_hwndToolTip)
  706. {
  707. SetWindowPos(m_hwndToolTip, HWND_TOPMOST,
  708. 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
  709. TOOLINFO ti = {0};
  710. RECT rc = {2,2,2,2};
  711. ti.cbSize = sizeof(ti);
  712. ti.uFlags = TTF_TRACK | TTF_TRANSPARENT;
  713. ti.hwnd = m_hwnd;
  714. ti.uId = 1;
  715. ti.hinst = hinst;
  716. // REARCHITECT: How do we use the icon, bold title, message style tooltips?
  717. // Until I learn how I'm just using the message string.
  718. ti.lpszText = pszMessage;
  719. // set the version so we can have non buggy mouse event forwarding
  720. SendMessage(m_hwndToolTip, CCM_SETVERSION, COMCTL32_VERSION, 0);
  721. SendMessage(m_hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
  722. SendMessage(m_hwndToolTip, TTM_SETMAXTIPWIDTH, 0, cxTipWidth);
  723. SendMessage(m_hwndToolTip, TTM_SETMARGIN, 0, (LPARAM)&rc);
  724. if (pszTitle || hIcon)
  725. {
  726. // REARCHITECT: hIcon needs to be an image list index or some such. Get details
  727. // on how this really works.
  728. SendMessage(m_hwndToolTip, TTM_SETTITLE, (WPARAM)hIcon, (LPARAM)pszTitle);
  729. }
  730. }
  731. else
  732. {
  733. // failed to create tool tip window, now what should we do? Unsubclass ourselves?
  734. TraceMsg(TF_GENERAL, "Failed to create tooltip window");
  735. }
  736. }
  737. void CInputLimiter::HideToolTip()
  738. {
  739. // When the timer fires we hide the tooltip window
  740. if (m_uTimerID)
  741. {
  742. KillTimer(m_hwnd,LIMITINPUTTIMERID);
  743. m_uTimerID = 0;
  744. }
  745. if (m_hwndToolTip)
  746. {
  747. SendMessage(m_hwndToolTip, TTM_TRACKACTIVATE, FALSE, 0);
  748. }
  749. }
  750. // allows caller to pass in already contructed LIMITINPUT structure...
  751. HRESULT SHLimitInputEditWithFlags(HWND hwndEdit, LIMITINPUT * pli)
  752. {
  753. HRESULT hr;
  754. CInputLimiter *pInputLimiter = new CInputLimiter;
  755. if (pInputLimiter)
  756. {
  757. if (pInputLimiter->SubclassEditControl(hwndEdit, pli))
  758. {
  759. hr = S_OK;
  760. }
  761. else
  762. {
  763. hr = E_FAIL;
  764. delete pInputLimiter;
  765. }
  766. }
  767. else
  768. {
  769. hr = E_OUTOFMEMORY;
  770. }
  771. return hr;
  772. }
  773. // LimitInput
  774. //
  775. // Limits the characters that can be entered into an edit box. It intercepts WM_CHAR
  776. // messages and only allows certain characters through. Some characters, such as backspace
  777. // are always allowed through.
  778. //
  779. // Args:
  780. // hwndEdit Handle to an edit control. Results will be unpredictable if any other window
  781. // type is passed in.
  782. //
  783. // pli Pointer to a LIMITINPUT structure that determines how the input is limited.
  784. HRESULT SHLimitInputEditChars(HWND hwndEdit, LPCWSTR pszValidChars, LPCWSTR pszInvalidChars)
  785. {
  786. LPWSTR pszMessage = NULL;
  787. LIMITINPUT li = {0};
  788. li.cbSize = sizeof(li);
  789. li.dwMask = LIM_FLAGS | LIM_FILTER | LIM_MESSAGE | LIM_HINST;
  790. li.dwFlags = LIF_HIDETIPONVALID;
  791. li.hinst = g_hinst;
  792. if (pszValidChars)
  793. {
  794. // ick, li.pszFilter is used as const, but since CInputLimiter is derived from the struct itd be a
  795. // pain to define it as such.
  796. li.pszFilter = (LPWSTR)pszValidChars;
  797. li.dwFlags |= LIF_INCLUDEFILTER;
  798. }
  799. else
  800. {
  801. li.pszFilter = (LPWSTR)pszInvalidChars;
  802. li.dwFlags |= LIF_EXCLUDEFILTER;
  803. }
  804. // create the error message.
  805. PCWSTR pszChars = pszInvalidChars ? pszInvalidChars : pszValidChars;
  806. PWSTR pszSpacedChars = new WCHAR[2 * lstrlen(pszChars) + 1];
  807. if (pszSpacedChars)
  808. {
  809. // we're mimicing what IDS_INVALIDFN does for the known set of bad chars on the filesystem --
  810. // append each char and separate them by spaces.
  811. PWSTR psz = pszSpacedChars;
  812. for (int i = 0; i < lstrlen(pszChars); i++)
  813. {
  814. *psz++ = pszChars[i];
  815. *psz++ = L' ';
  816. }
  817. *psz = 0;
  818. int id = pszInvalidChars ? IDS_CHARSINVALID : IDS_CHARSVALID;
  819. pszMessage = ShellConstructMessageString(HINST_THISDLL, MAKEINTRESOURCE(id), pszSpacedChars);
  820. delete [] pszSpacedChars;
  821. }
  822. if (pszMessage)
  823. {
  824. li.pszMessage = pszMessage;
  825. }
  826. else
  827. {
  828. // fall back to the old message
  829. li.pszMessage = MAKEINTRESOURCE(IDS_INVALIDFN);
  830. }
  831. HRESULT hr = SHLimitInputEditWithFlags(hwndEdit, &li);
  832. if (pszMessage)
  833. {
  834. LocalFree(pszMessage);
  835. }
  836. return hr;
  837. }
  838. HRESULT SHLimitInputEdit(HWND hwndEdit, IShellFolder *psf)
  839. {
  840. IItemNameLimits *pinl;
  841. HRESULT hr = psf->QueryInterface(IID_PPV_ARG(IItemNameLimits, &pinl));
  842. if (SUCCEEDED(hr))
  843. {
  844. LPWSTR pszValidChars;
  845. LPWSTR pszInvalidChars;
  846. hr = pinl->GetValidCharacters(&pszValidChars, &pszInvalidChars);
  847. if (SUCCEEDED(hr))
  848. {
  849. hr = SHLimitInputEditChars(hwndEdit, pszValidChars, pszInvalidChars);
  850. if (pszValidChars)
  851. CoTaskMemFree(pszValidChars);
  852. if (pszInvalidChars)
  853. CoTaskMemFree(pszInvalidChars);
  854. }
  855. pinl->Release();
  856. }
  857. return hr;
  858. }
  859. typedef struct tagCBLIMITINPUT
  860. {
  861. HRESULT hr;
  862. IShellFolder *psf;
  863. } CBLIMITINPUT;
  864. // Limiting the input on a combo box is special cased because you first
  865. // have to find the edit box and then LimitInput on that.
  866. BOOL CALLBACK FindTheEditBox(HWND hwnd, LPARAM lParam)
  867. {
  868. // The combo box only has one child, subclass it
  869. CBLIMITINPUT *pcbli = (CBLIMITINPUT*)lParam;
  870. pcbli->hr = SHLimitInputEdit(hwnd, pcbli->psf);
  871. return FALSE;
  872. }
  873. HRESULT SHLimitInputCombo(HWND hwndComboBox, IShellFolder *psf)
  874. {
  875. CBLIMITINPUT cbli;
  876. cbli.hr = E_FAIL;
  877. cbli.psf = psf;
  878. EnumChildWindows(hwndComboBox, FindTheEditBox, (LPARAM)&cbli);
  879. return cbli.hr;
  880. }