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.

510 lines
15 KiB

  1. /****************************************************************************
  2. Copyright (c) 1998-1999 Microsoft Corporation
  3. Module Name: cplinputlimiter.cpp
  4. Author: toddb - 10/06/98
  5. ****************************************************************************/
  6. #include "cplPreComp.h"
  7. class CInputLimiter
  8. {
  9. public:
  10. BOOL SubclassWindow(HWND hwnd, DWORD dwFlags);
  11. static VOID HideToolTip();
  12. protected:
  13. BOOL OnChar( HWND hwnd, TCHAR wParam );
  14. LRESULT OnPaste(HWND hwnd, WPARAM wParam, LPARAM lParam);
  15. BOOL IsValidChar(TCHAR ch, BOOL bPaste);
  16. BOOL UnsubclassWindow(HWND hwnd);
  17. void ShowToolTip(HWND hwnd);
  18. void CreateToolTipWindow(HWND hwnd);
  19. static LRESULT CALLBACK SubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  20. static LRESULT CALLBACK ListenerProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
  21. static VOID CALLBACK TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime);
  22. DWORD m_dwFlags; // determines which characters are allowed
  23. WNDPROC m_pfnSuperProc; // the super class proc
  24. static HWND s_hwndToolTip; // shared by all instances
  25. static UINT_PTR s_uTimerID; // shared timer
  26. static TCHAR s_szTipText[512]; // the text to be shown in the tooltip
  27. };
  28. HWND CInputLimiter::s_hwndToolTip = NULL;
  29. UINT_PTR CInputLimiter::s_uTimerID = 0;
  30. TCHAR CInputLimiter::s_szTipText[512] = {0};
  31. // Limiting the input on a combo box is a special case because you first
  32. // have to find the edit box and then LimitInput on that.
  33. BOOL CALLBACK FindTheEditBox( HWND hwnd, LPARAM lParam )
  34. {
  35. // The combo box only has one child, subclass it
  36. LimitInput(hwnd,(DWORD)lParam);
  37. return FALSE;
  38. }
  39. BOOL LimitCBInput(HWND hwnd, DWORD dwFlags)
  40. {
  41. return EnumChildWindows(hwnd, FindTheEditBox, dwFlags);
  42. }
  43. BOOL LimitInput(HWND hwnd, DWORD dwFlags)
  44. {
  45. CInputLimiter * pil = new CInputLimiter;
  46. if (!pil)
  47. {
  48. return FALSE;
  49. }
  50. BOOL bResult = pil->SubclassWindow(hwnd, dwFlags);
  51. if (!bResult)
  52. {
  53. delete pil;
  54. }
  55. return bResult;
  56. }
  57. void HideToolTip()
  58. {
  59. CInputLimiter::HideToolTip();
  60. }
  61. BOOL CInputLimiter::SubclassWindow(HWND hwnd, DWORD dwFlags)
  62. {
  63. if ( !IsWindow(hwnd) )
  64. return FALSE;
  65. m_dwFlags = dwFlags;
  66. SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)this);
  67. m_pfnSuperProc = (WNDPROC)GetWindowLongPtr(hwnd, GWLP_WNDPROC);
  68. SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)CInputLimiter::SubclassProc);
  69. return TRUE;
  70. }
  71. BOOL CInputLimiter::UnsubclassWindow(HWND hwnd)
  72. {
  73. SetWindowLongPtr(hwnd, GWLP_WNDPROC, (LONG_PTR)m_pfnSuperProc);
  74. m_dwFlags = 0;
  75. delete this;
  76. return TRUE;
  77. }
  78. LRESULT CALLBACK CInputLimiter::SubclassProc( HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam )
  79. {
  80. CInputLimiter * pthis = (CInputLimiter*)GetWindowLongPtr(hwnd, GWLP_USERDATA);
  81. // cache pthis->m_pfnSuperProc because we always need in and
  82. // pthis might be deleted before we get around to using it
  83. WNDPROC pfn = pthis->m_pfnSuperProc;
  84. switch (uMsg)
  85. {
  86. case WM_CHAR:
  87. if (!pthis->OnChar(hwnd, (TCHAR)wParam))
  88. {
  89. return 0;
  90. }
  91. break;
  92. case WM_PASTE:
  93. return pthis->OnPaste(hwnd, wParam, lParam);
  94. case WM_KILLFOCUS:
  95. HideToolTip();
  96. break;
  97. case WM_DESTROY:
  98. pthis->UnsubclassWindow(hwnd);
  99. break;
  100. default:
  101. break;
  102. }
  103. return CallWindowProc(pfn, hwnd, uMsg, wParam, lParam);
  104. }
  105. BOOL CInputLimiter::OnChar( HWND hwnd, TCHAR ch )
  106. {
  107. // if the char is a good one return TRUE, this will pass the char on to the
  108. // default window proc. For a bad character do a beep and then display the
  109. // ballon tooltip pointing at the control.
  110. if ( IsValidChar(ch, FALSE) )
  111. return TRUE;
  112. // if we get here then an invalid character was entered
  113. MessageBeep(MB_OK);
  114. ShowToolTip(hwnd);
  115. return FALSE;
  116. }
  117. BOOL CInputLimiter::IsValidChar(TCHAR ch, BOOL bPaste)
  118. {
  119. // certain characters get converted into WM_CHAR messages even though we don't want
  120. // to consider them. We check for these characters first. Currently, this list includes:
  121. // backspace
  122. // control characters, such as ctrl-x and ctrl-v
  123. if ( ch == TEXT('\b') )
  124. return TRUE;
  125. if ( !bPaste && (0x8000 & GetKeyState(VK_CONTROL)) )
  126. return TRUE;
  127. if ( m_dwFlags & LIF_ALLOWALPHA )
  128. {
  129. if ( (ch >= TEXT('a') && ch <= TEXT('z')) || (ch >= TEXT('A') && ch <= TEXT('Z')) )
  130. {
  131. return TRUE;
  132. }
  133. }
  134. if ( m_dwFlags & LIF_ALLOWNUMBER )
  135. {
  136. if ( ch >= TEXT('0') && ch <= TEXT('9') )
  137. {
  138. return TRUE;
  139. }
  140. }
  141. if ( m_dwFlags & LIF_ALLOWDASH )
  142. {
  143. if ( ch == TEXT('-') || ch == TEXT('(') || ch == TEXT(')'))
  144. {
  145. return TRUE;
  146. }
  147. }
  148. if ( m_dwFlags & LIF_ALLOWPOUND )
  149. {
  150. if ( ch == TEXT('#') )
  151. {
  152. return TRUE;
  153. }
  154. }
  155. if ( m_dwFlags & LIF_ALLOWSTAR )
  156. {
  157. if ( ch == TEXT('*') )
  158. {
  159. return TRUE;
  160. }
  161. }
  162. if ( m_dwFlags & LIF_ALLOWSPACE )
  163. {
  164. if ( ch == TEXT(' ') )
  165. {
  166. return TRUE;
  167. }
  168. }
  169. if ( m_dwFlags & LIF_ALLOWCOMMA )
  170. {
  171. if ( ch == TEXT(',') )
  172. {
  173. return TRUE;
  174. }
  175. }
  176. if ( m_dwFlags & LIF_ALLOWPLUS )
  177. {
  178. if ( ch == TEXT('+') )
  179. {
  180. return TRUE;
  181. }
  182. }
  183. if ( m_dwFlags & LIF_ALLOWBANG )
  184. {
  185. if ( ch == TEXT('!') )
  186. {
  187. return TRUE;
  188. }
  189. }
  190. if ( m_dwFlags & LIF_ALLOWATOD )
  191. {
  192. if ( (ch >= TEXT('a') && ch <= TEXT('d')) || (ch >= TEXT('A') && ch <= TEXT('D')) )
  193. {
  194. return TRUE;
  195. }
  196. }
  197. return FALSE;
  198. }
  199. void CInputLimiter::ShowToolTip(HWND hwnd)
  200. {
  201. if ( !s_hwndToolTip )
  202. {
  203. CreateToolTipWindow(hwnd);
  204. }
  205. // Set the tooltip display point
  206. RECT rc;
  207. GetWindowRect(hwnd, &rc);
  208. SendMessage(s_hwndToolTip, TTM_TRACKPOSITION, 0, MAKELONG((rc.left+rc.right)/2,rc.bottom));
  209. TOOLINFO ti = {0};
  210. ti.cbSize = sizeof(ti);
  211. ti.hwnd = NULL;
  212. ti.uId = 1;
  213. // Set the tooltip text
  214. UINT iStrID;
  215. if ( m_dwFlags == LIF_ALLOWNUMBER )
  216. {
  217. // use the "0-9" text
  218. iStrID = IDS_DIGITSONLY;
  219. }
  220. else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWSPACE) )
  221. {
  222. // use the "0-9, ' '" text
  223. iStrID = IDS_DIGITLIST;
  224. }
  225. else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWSPACE|LIF_ALLOWCOMMA) )
  226. {
  227. // use the "0-9, ' ', ','" text
  228. iStrID = IDS_MULTIDIGITLIST;
  229. }
  230. else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWSTAR|LIF_ALLOWPOUND|LIF_ALLOWCOMMA) )
  231. {
  232. // use the "0-9, #, *, ','" text
  233. iStrID = IDS_PHONEPADCHAR;
  234. }
  235. else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWPOUND|LIF_ALLOWSTAR|LIF_ALLOWSPACE|LIF_ALLOWCOMMA) )
  236. {
  237. // use the "0-9, #, *, ' ', ','" text
  238. iStrID = IDS_PHONENUMBERCHAR;
  239. }
  240. else if ( m_dwFlags == (LIF_ALLOWNUMBER|LIF_ALLOWPOUND|LIF_ALLOWSTAR|LIF_ALLOWSPACE|LIF_ALLOWCOMMA|LIF_ALLOWPLUS|LIF_ALLOWBANG|LIF_ALLOWATOD) )
  241. {
  242. // use the "0-9, A-D, a-d, #, *, +, !, ' ', ',' " text
  243. iStrID = IDS_PHONENUMBERCHAREXT;
  244. }
  245. else
  246. {
  247. // We should never reach this point, but if we do then we display a generic invalid character dialog
  248. iStrID = IDS_ALLPHONECHARS;
  249. }
  250. LoadString(GetUIInstance(),iStrID,s_szTipText,ARRAYSIZE(s_szTipText));
  251. ti.lpszText = s_szTipText;
  252. SendMessage(s_hwndToolTip, TTM_UPDATETIPTEXT, 0, (LPARAM)&ti);
  253. // Show the tooltip
  254. SendMessage(s_hwndToolTip, TTM_TRACKACTIVATE, TRUE, (LPARAM)&ti);
  255. // Set a timer to hide the tooltip
  256. if ( s_uTimerID )
  257. {
  258. KillTimer(NULL,s_uTimerID);
  259. }
  260. s_uTimerID = SetTimer(NULL, 0, 10000, (TIMERPROC)CInputLimiter::TimerProc);
  261. }
  262. // CreateToolTipWindow
  263. //
  264. // Creates our tooltip control. We share this one tooltip control and use it for all invalid
  265. // input messages. The control is hiden when not in use and then shown when needed.
  266. //
  267. void CInputLimiter::CreateToolTipWindow(HWND hwnd)
  268. {
  269. HWND hwndParent;
  270. do
  271. {
  272. hwndParent = hwnd;
  273. hwnd = GetParent(hwnd);
  274. } while (hwnd);
  275. s_hwndToolTip = CreateWindow(TOOLTIPS_CLASS, NULL,
  276. WS_POPUP | TTS_NOPREFIX | TTS_ALWAYSTIP | TTS_BALLOON,
  277. CW_USEDEFAULT, CW_USEDEFAULT,
  278. CW_USEDEFAULT, CW_USEDEFAULT,
  279. hwndParent, NULL, GetUIInstance(),
  280. NULL);
  281. if (s_hwndToolTip)
  282. {
  283. SetWindowPos(s_hwndToolTip, HWND_TOPMOST,
  284. 0, 0, 0, 0, SWP_NOMOVE | SWP_NOSIZE | SWP_NOACTIVATE);
  285. TOOLINFO ti = {0};
  286. RECT rc = {2,2,2,2};
  287. ti.cbSize = sizeof(ti);
  288. ti.uFlags = TTF_TRACK | TTF_TRANSPARENT;
  289. ti.hwnd = NULL;
  290. ti.uId = 1;
  291. ti.lpszText = s_szTipText;
  292. // set the version so we can have non buggy mouse event forwarding
  293. SendMessage(s_hwndToolTip, CCM_SETVERSION, COMCTL32_VERSION, 0);
  294. SendMessage(s_hwndToolTip, TTM_ADDTOOL, 0, (LPARAM)&ti);
  295. SendMessage(s_hwndToolTip, TTM_SETMAXTIPWIDTH, 0, 500);
  296. SendMessage(s_hwndToolTip, TTM_SETMARGIN, 0, (LPARAM)&rc);
  297. }
  298. }
  299. VOID CALLBACK CInputLimiter::TimerProc(HWND hwnd, UINT uMsg, UINT idEvent, DWORD dwTime)
  300. {
  301. // When the timer fires we hide the tooltip window
  302. HideToolTip();
  303. }
  304. void CInputLimiter::HideToolTip()
  305. {
  306. if ( s_uTimerID )
  307. {
  308. KillTimer(NULL,s_uTimerID);
  309. s_uTimerID = 0;
  310. }
  311. if ( s_hwndToolTip )
  312. {
  313. PostMessage(s_hwndToolTip, TTM_TRACKACTIVATE, FALSE, 0);
  314. }
  315. }
  316. LRESULT CInputLimiter::OnPaste(HWND hwnd, WPARAM wParam, LPARAM lParam)
  317. {
  318. // There are hundred of lines of code in user to successfully handle a paste into an edit control.
  319. // We need to leverage all that code while still disallowing invalid input to result from the paste.
  320. // As a result, what we need to do is to get the clip board data, validate that data, place the
  321. // valid data back onto the clipboard, call the default window proc to let user do it's thing, and
  322. // then restore the clipboard to it's original format.
  323. if ( OpenClipboard(hwnd) )
  324. {
  325. HANDLE hdata;
  326. UINT iFormat;
  327. DWORD cchBad = 0; // count of the number of bad characters
  328. // REVIEW: Should this be based on the compile type or the window type?
  329. // Compile time check for the correct clipboard format to use:
  330. if ( sizeof(WCHAR) == sizeof(TCHAR) )
  331. {
  332. iFormat = CF_UNICODETEXT;
  333. }
  334. else
  335. {
  336. iFormat = CF_TEXT;
  337. }
  338. hdata = GetClipboardData(iFormat);
  339. if ( hdata )
  340. {
  341. LPTSTR pszData;
  342. pszData = (LPTSTR)GlobalLock(hdata);
  343. if ( pszData )
  344. {
  345. DWORD dwSize;
  346. HANDLE hClone;
  347. HANDLE hNew;
  348. // we need to copy the original data because the clipboard owns the hdata
  349. // pointer. That data will be invalid after we call SetClipboardData.
  350. // We start by calculating the size of the data:
  351. dwSize = (DWORD)GlobalSize(hdata)+sizeof(TCHAR);
  352. // Use the prefered GlobalAlloc for clipboard data
  353. hClone = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, dwSize);
  354. hNew = GlobalAlloc(GMEM_MOVEABLE|GMEM_DDESHARE, dwSize);
  355. if ( hClone && hNew )
  356. {
  357. LPTSTR pszClone;
  358. LPTSTR pszNew;
  359. pszClone = (LPTSTR)GlobalLock(hClone);
  360. pszNew = (LPTSTR)GlobalLock(hNew);
  361. if ( pszClone && pszNew )
  362. {
  363. int iNew = 0;
  364. // copy the original data as-is
  365. memcpy((LPVOID)pszClone, (LPVOID)pszData, (size_t)dwSize);
  366. // ensure that it's NULL terminated
  367. pszClone[ (dwSize/sizeof(TCHAR))-1 ] = NULL;
  368. for ( LPTSTR psz = pszClone; *psz; psz++ )
  369. {
  370. if ( IsValidChar(*psz, TRUE) )
  371. {
  372. pszNew[iNew++] = *psz;
  373. }
  374. else
  375. {
  376. cchBad++;
  377. }
  378. }
  379. pszNew[iNew] = NULL;
  380. // If there are any characters in the paste buffer then we paste the validated string
  381. if ( *pszNew )
  382. {
  383. // we always set the new string. Worst case it's identical to the old string
  384. GlobalUnlock(hNew);
  385. pszNew = NULL;
  386. SetClipboardData(iFormat, hNew);
  387. hNew = NULL;
  388. // call the super proc to do the paste
  389. CallWindowProc(m_pfnSuperProc, hwnd, WM_PASTE, wParam, lParam);
  390. // The above call will have closed the clipboard on us. We try to re-open it.
  391. // If this fails it's no big deal, that simply means the SetClipboardData
  392. // call below will fail which is good if somebody else managed to open the
  393. // clipboard in the mean time.
  394. OpenClipboard(hwnd);
  395. // and then we always set it back to the original value.
  396. GlobalUnlock(hClone);
  397. pszClone = NULL;
  398. SetClipboardData(iFormat, hClone);
  399. hClone = NULL;
  400. }
  401. }
  402. if ( pszClone )
  403. {
  404. GlobalUnlock(hClone);
  405. }
  406. if ( pszNew )
  407. {
  408. GlobalUnlock(hNew);
  409. }
  410. }
  411. if ( hClone )
  412. {
  413. GlobalFree( hClone );
  414. }
  415. if ( hNew )
  416. {
  417. GlobalFree( hNew );
  418. }
  419. // at this point we are done with hdata so unlock it
  420. GlobalUnlock(hdata);
  421. }
  422. }
  423. CloseClipboard();
  424. if ( cchBad )
  425. {
  426. // Show the error balloon
  427. MessageBeep(MB_OK);
  428. ShowToolTip(hwnd);
  429. }
  430. }
  431. return TRUE;
  432. }