Leaked source code of windows server 2003
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.

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