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.

1829 lines
52 KiB

  1. #include "ctlspriv.h"
  2. #define EDIT_SELECTALL( hwnd ) Edit_SetSel(hwnd, 0, 0); \
  3. Edit_SetSel(hwnd, 0, -1);
  4. const TCHAR c_szComboBox[] = TEXT("combobox");
  5. const TCHAR c_szComboBoxEx[] = WC_COMBOBOXEX;
  6. #ifdef DPITEST
  7. #define ComboEx_IsDPIScaled() TRUE
  8. #else
  9. #define ComboEx_IsDPIScaled() (CCDPIScale(pce->ci))
  10. #endif
  11. #define COMBO_MARGIN 4
  12. #define COMBO_WIDTH g_cxSmIcon
  13. #define COMBO_HEIGHT g_cySmIcon
  14. #define COMBO_BORDER 3
  15. typedef struct {
  16. LPTSTR pszText;
  17. int iImage;
  18. int iSelectedImage;
  19. int iOverlay;
  20. int iIndent;
  21. LPARAM lParam;
  22. } CEITEM, *PCEITEM;
  23. typedef struct {
  24. CCONTROLINFO ci;
  25. HWND hwndCombo;
  26. HWND hwndEdit;
  27. DWORD dwExStyle;
  28. HIMAGELIST himl;
  29. HFONT hFont;
  30. int cxIndent;
  31. WPARAM iSel;
  32. CEITEM cei;
  33. BOOL fEditItemSet :1;
  34. BOOL fEditChanged :1;
  35. BOOL fFontCreated :1;
  36. BOOL fInEndEdit :1;
  37. BOOL fInDrop :1;
  38. } COMBOEX, *PCOMBOBOXEX;
  39. void ComboEx_OnWindowPosChanging(PCOMBOBOXEX pce, LPWINDOWPOS pwp);
  40. HFONT ComboEx_GetFont(PCOMBOBOXEX pce);
  41. BOOL ComboEx_OnGetItem(PCOMBOBOXEX pce, PCOMBOBOXEXITEM pceItem);
  42. int ComboEx_ComputeItemHeight(PCOMBOBOXEX pce, BOOL);
  43. int ComboEx_OnFindStringExact(PCOMBOBOXEX pce, int iStart, LPCTSTR lpsz);
  44. int WINAPI ShellEditWordBreakProc(LPTSTR lpch, int ichCurrent, int cch, int code);
  45. LRESULT CALLBACK ComboSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
  46. LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
  47. LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
  48. LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData);
  49. int ComboEx_StrCmp(PCOMBOBOXEX pce, LPCTSTR psz1, LPCTSTR psz2);
  50. #define ComboEx_Editable(pce) (((pce)->ci.style & CBS_DROPDOWNLIST) == CBS_DROPDOWN)
  51. void ComboEx_OnSetFont(PCOMBOBOXEX pce, HFONT hFont, BOOL fRedraw)
  52. {
  53. int iHeight;
  54. HFONT hfontOld = NULL;
  55. if (pce->fFontCreated)
  56. hfontOld = ComboEx_GetFont(pce);
  57. if (!hFont) {
  58. LOGFONT lf;
  59. SystemParametersInfo(SPI_GETICONTITLELOGFONT, sizeof(lf), &lf, FALSE);
  60. hFont = CreateFontIndirect(&lf);
  61. pce->fFontCreated = TRUE;
  62. } else {
  63. pce->fFontCreated = FALSE;
  64. }
  65. pce->ci.uiCodePage = GetCodePageForFont(hFont);
  66. SendMessage(pce->hwndCombo, WM_SETFONT, (WPARAM)hFont, fRedraw);
  67. if (pce->hwndEdit)
  68. {
  69. SendMessage(pce->hwndEdit, WM_SETFONT, (WPARAM)hFont, fRedraw);
  70. SendMessage(pce->hwndEdit, EM_SETMARGINS, EC_USEFONTINFO, 0L);
  71. }
  72. iHeight = ComboEx_ComputeItemHeight(pce, FALSE);
  73. SendMessage(pce->ci.hwnd, CB_SETITEMHEIGHT, (WPARAM)-1, (LPARAM)iHeight);
  74. SendMessage(pce->hwndCombo, CB_SETITEMHEIGHT, 0, (LPARAM)iHeight);
  75. // do this last so that we don't have a nuked font as we try to create the new one
  76. if (hfontOld)
  77. DeleteObject(hfontOld);
  78. }
  79. void ComboEx_OnDestroy(PCOMBOBOXEX pce)
  80. {
  81. // don't need do destroy hwndCombo.. it will be destroyed along with us.
  82. SendMessage(pce->hwndCombo, CB_RESETCONTENT, 0, 0);
  83. // we may still have string allocated for the item in the edit box so free it
  84. if (pce->cei.pszText)
  85. Str_Set(&(pce->cei.pszText), NULL);
  86. if (pce->fFontCreated) {
  87. DeleteObject(ComboEx_GetFont(pce));
  88. }
  89. if (pce->hwndEdit)
  90. RemoveWindowSubclass(pce->hwndEdit, EditSubclassProc, 0);
  91. if (pce->hwndCombo)
  92. RemoveWindowSubclass(pce->hwndCombo, ComboSubclassProc, 0);
  93. SetWindowPtr(pce->ci.hwnd, 0, 0);
  94. LocalFree(pce);
  95. }
  96. // this gets the client rect without the scrollbar part and the border
  97. void ComboEx_GetComboClientRect(PCOMBOBOXEX pce, LPRECT lprc)
  98. {
  99. GetClientRect(pce->hwndCombo, lprc);
  100. InflateRect(lprc, -g_cxEdge, -g_cyEdge);
  101. lprc->right -= g_cxScrollbar;
  102. }
  103. // returns the edit box (creating it if necessary) or NULL if the combo does
  104. // not have an edit box
  105. HWND ComboEx_GetEditBox(PCOMBOBOXEX pce)
  106. {
  107. HFONT hfont;
  108. DWORD dwStyle;
  109. DWORD dwExStyle = 0;
  110. if (pce->hwndEdit)
  111. return(pce->hwndEdit);
  112. if (!ComboEx_Editable(pce))
  113. return(NULL);
  114. dwStyle = WS_VISIBLE | WS_CLIPSIBLINGS | WS_CHILD | ES_LEFT;
  115. if (pce->ci.style & CBS_AUTOHSCROLL)
  116. {
  117. dwStyle |= ES_AUTOHSCROLL;
  118. }
  119. if (pce->ci.style & CBS_OEMCONVERT)
  120. {
  121. dwStyle |= ES_OEMCONVERT;
  122. }
  123. dwExStyle = pce->ci.dwExStyle & (WS_EX_RIGHT | WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR);
  124. pce->hwndEdit = CreateWindowEx(dwExStyle, WC_EDIT, c_szNULL, dwStyle, 0, 0, 0, 0,
  125. pce->hwndCombo, IntToPtr_(HMENU, GetDlgCtrlID(pce->ci.hwnd)), HINST_THISDLL, 0);
  126. if (!pce->hwndEdit ||
  127. !SetWindowSubclass(pce->hwndEdit, EditSubclassProc, 0, (DWORD_PTR)pce))
  128. {
  129. return NULL;
  130. }
  131. // Override the edit's theme with combobox
  132. SetWindowTheme(pce->hwndEdit, L"Combobox", NULL);
  133. hfont = ComboEx_GetFont(pce);
  134. if (hfont)
  135. FORWARD_WM_SETFONT(pce->hwndEdit, hfont,
  136. FALSE, SendMessage);
  137. return(pce->hwndEdit);
  138. }
  139. ///
  140. /// the edit box handling...
  141. /*
  142. we want the edit box up on CBN_SETFOCUS and CBN_CLOSEUP
  143. remove it on CBN_DROPDOWN and on CBN_KILLFOCUS
  144. this assumes that CBN_SETFOCUS and CBN_KILLFOCUS will come before and after
  145. CBN_DROPDOWN and CBN_CLOSEUP respectively
  146. */
  147. // Really a BOOL return value
  148. LRESULT ComboEx_EndEdit(PCOMBOBOXEX pce, int iWhy)
  149. {
  150. NMCBEENDEDIT nm;
  151. LRESULT fRet;
  152. if (!ComboEx_GetEditBox(pce))
  153. return(FALSE);
  154. pce->fInEndEdit = TRUE;
  155. GetWindowText(pce->hwndEdit, nm.szText, ARRAYSIZE(nm.szText));
  156. nm.fChanged = pce->fEditChanged;
  157. nm.iWhy = iWhy;
  158. nm.iNewSelection = ComboEx_OnFindStringExact(pce, ComboBox_GetCurSel(pce->hwndCombo) - 1, nm.szText);
  159. fRet = BOOLFROMPTR(CCSendNotify(&pce->ci, CBEN_ENDEDIT, &nm.hdr));
  160. pce->fInEndEdit = FALSE;
  161. if (!fRet)
  162. {
  163. if (nm.iNewSelection != ComboBox_GetCurSel(pce->hwndCombo))
  164. {
  165. if (nm.iNewSelection != -1)
  166. {
  167. SendMessage(pce->ci.hwnd, CB_SETCURSEL, nm.iNewSelection, 0);
  168. }
  169. else
  170. {
  171. //if the selection is -1 and if we do a CB_SETCURSEL on comboboxex then it nukes the text in
  172. //the edit window. Which is not the desired behavior. We need to update the Current Selection in the
  173. //child combobox but leave the text as it is.
  174. SendMessage(pce->hwndCombo, CB_SETCURSEL, nm.iNewSelection,0);
  175. }
  176. }
  177. pce->fEditChanged = FALSE;
  178. }
  179. InvalidateRect(pce->hwndCombo, NULL, FALSE);
  180. return(fRet);
  181. }
  182. void ComboEx_SizeEditBox(PCOMBOBOXEX pce)
  183. {
  184. RECT rc;
  185. int cxIcon = 0, cyIcon = 0;
  186. ComboEx_GetComboClientRect(pce, &rc);
  187. InvalidateRect(pce->hwndCombo, &rc, TRUE); // erase so that the selection highlight is erased
  188. if (pce->himl && !(pce->dwExStyle & CBES_EX_NOEDITIMAGEINDENT))
  189. {
  190. // Make room for icons.
  191. CCGetIconSize(&pce->ci, pce->himl, &cxIcon, &cyIcon);
  192. if (cxIcon)
  193. cxIcon += COMBO_MARGIN;
  194. }
  195. // combobox edit field is one border in from the entire combobox client
  196. // rect -- thus add one border to edit control's left side
  197. rc.left += g_cxBorder + cxIcon;
  198. rc.bottom -= g_cyBorder;
  199. rc.top = rc.bottom - ComboEx_ComputeItemHeight(pce, TRUE) - g_cyBorder;
  200. SetWindowPos(pce->hwndEdit, NULL, rc.left, rc.top, RECTWIDTH(rc), RECTHEIGHT(rc),
  201. SWP_NOACTIVATE | SWP_NOZORDER | SWP_SHOWWINDOW);
  202. }
  203. BOOL ComboEx_GetCurSelText(PCOMBOBOXEX pce, LPTSTR pszText, int cchText)
  204. {
  205. COMBOBOXEXITEM cei;
  206. BOOL bRet = TRUE;
  207. cei.mask = CBEIF_TEXT;
  208. cei.pszText = pszText;
  209. cei.cchTextMax = cchText;
  210. cei.iItem = (INT)ComboBox_GetCurSel(pce->hwndCombo);
  211. if (cei.iItem == -1 )
  212. {
  213. pszText[0] = 0;
  214. bRet = FALSE;
  215. }
  216. else
  217. {
  218. ComboEx_OnGetItem(pce, &cei);
  219. }
  220. return bRet;
  221. }
  222. void ComboEx_UpdateEditText(PCOMBOBOXEX pce, BOOL fClearOnNoSel)
  223. {
  224. if (!pce->fInEndEdit)
  225. {
  226. TCHAR szText[CBEMAXSTRLEN];
  227. HWND hwndEdit = ComboEx_Editable(pce) ? pce->hwndEdit : pce->hwndCombo;
  228. if (ComboEx_GetCurSelText(pce, szText, ARRAYSIZE(szText)) || fClearOnNoSel) {
  229. SendMessage(hwndEdit, WM_SETTEXT, 0, (LPARAM)szText);
  230. EDIT_SELECTALL( hwndEdit );
  231. }
  232. }
  233. }
  234. BOOL ComboEx_BeginEdit(PCOMBOBOXEX pce)
  235. {
  236. if (!ComboEx_GetEditBox(pce))
  237. return(FALSE);
  238. SetFocus(pce->hwndEdit);
  239. return(TRUE);
  240. }
  241. BOOL ComboSubclass_HandleButton(PCOMBOBOXEX pce, UINT uMsg, WPARAM wParam, LPARAM lParam)
  242. {
  243. RECT rc;
  244. POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
  245. ComboEx_GetComboClientRect(pce, &rc);
  246. InflateRect(&rc, g_cxEdge, g_cyEdge);
  247. if (PtInRect(&rc, pt)) {
  248. //
  249. // CheckForDragBegin yields, so we must revalidate on the way back.
  250. //
  251. HWND hwndCombo = pce->hwndCombo;
  252. if (CheckForDragBegin(pce->hwndCombo, LOWORD(lParam), HIWORD(lParam)))
  253. {
  254. NMCBEDRAGBEGIN nmcbebd;
  255. LRESULT fRet;
  256. nmcbebd.iItemid = -1;
  257. GetWindowText(pce->hwndEdit, nmcbebd.szText, ARRAYSIZE(nmcbebd.szText));
  258. fRet = CCSendNotify(&pce->ci, CBEN_DRAGBEGIN, &nmcbebd.hdr);
  259. return TRUE;
  260. }
  261. // CheckForDragBegin yields, so revalidate before continuing
  262. else if (IsWindow(hwndCombo)) {
  263. if (uMsg == WM_LBUTTONDOWN)
  264. {
  265. // Post a fake WM_LBUTTONUP message because CheckForDragBegin may have
  266. // removed it from the combo's message queue but the combo expects to
  267. // get it.
  268. PostMessage(hwndCombo, WM_LBUTTONUP, 0, 0);
  269. }
  270. // a click on our border should start edit mode as well
  271. if (ComboEx_Editable(pce)) {
  272. if (!ComboEx_BeginEdit(pce))
  273. SetFocus(pce->hwndCombo);
  274. return TRUE;
  275. }
  276. return FALSE;
  277. }
  278. }
  279. return FALSE;
  280. }
  281. BOOL ComboSubclass_HandleCommand(PCOMBOBOXEX pce, WPARAM wParam, LPARAM lParam)
  282. {
  283. UINT idCmd = GET_WM_COMMAND_ID(wParam, lParam);
  284. UINT uCmd = GET_WM_COMMAND_CMD(wParam, lParam);
  285. HWND hwnd = GET_WM_COMMAND_HWND(wParam, lParam);
  286. switch (uCmd)
  287. {
  288. case EN_SETFOCUS:
  289. if (!pce->fInDrop)
  290. {
  291. EDIT_SELECTALL( pce->hwndEdit );
  292. CCSendNotify(&pce->ci, CBEN_BEGINEDIT, NULL);
  293. pce->fEditChanged = FALSE;
  294. }
  295. break;
  296. case EN_KILLFOCUS:
  297. {
  298. HWND hwndFocus;
  299. hwndFocus = GetFocus();
  300. if (hwndFocus != pce->hwndCombo)
  301. {
  302. ComboEx_EndEdit(pce, CBENF_KILLFOCUS);
  303. SendMessage(pce->hwndCombo, WM_KILLFOCUS, (WPARAM)hwndFocus, 0);
  304. }
  305. break;
  306. }
  307. case EN_CHANGE:
  308. {
  309. TCHAR szTextOrig[CBEMAXSTRLEN];
  310. TCHAR szTextNow[CBEMAXSTRLEN];
  311. WPARAM iItem;
  312. iItem = ComboBox_GetCurSel(pce->hwndCombo);
  313. if(iItem == -1)
  314. {
  315. if (pce->fEditItemSet && pce->cei.pszText)
  316. {
  317. Str_GetPtr(pce->cei.pszText, szTextOrig, ARRAYSIZE(szTextOrig));
  318. }
  319. else
  320. {
  321. szTextOrig[0] = TEXT('\0');
  322. }
  323. }
  324. else
  325. {
  326. ComboEx_GetCurSelText(pce,szTextOrig, ARRAYSIZE(szTextOrig));
  327. }
  328. GetWindowText(pce->hwndEdit, szTextNow, ARRAYSIZE(szTextNow));
  329. pce->fEditChanged = (ComboEx_StrCmp(pce, szTextOrig, szTextNow) != 0);
  330. SendMessage(pce->ci.hwndParent, WM_COMMAND,
  331. GET_WM_COMMAND_MPS(idCmd, pce->ci.hwnd, CBN_EDITCHANGE));
  332. break;
  333. }
  334. }
  335. return(hwnd == pce->hwndEdit);
  336. }
  337. LRESULT CALLBACK EditSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
  338. LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
  339. {
  340. PCOMBOBOXEX pce = (PCOMBOBOXEX)dwRefData;
  341. if (uMsg == WM_SETFONT ||
  342. uMsg == WM_WININICHANGE)
  343. {
  344. return DefSubclassProc(hwnd, uMsg, wParam, lParam);
  345. }
  346. switch(uMsg)
  347. {
  348. case WM_DESTROY:
  349. RemoveWindowSubclass(hwnd, EditSubclassProc, 0);
  350. break;
  351. case WM_CHAR:
  352. switch ((TCHAR)wParam) {
  353. case TEXT('\n'):
  354. case TEXT('\r'):
  355. // return... don't go to wndproc because
  356. // the edit control beeps on enter
  357. return 0;
  358. }
  359. break;
  360. case WM_SIZE:
  361. if (GetFocus() != hwnd) {
  362. Edit_SetSel(pce->hwndEdit, 0, 0); // makesure everything is scrolled over first
  363. }
  364. break;
  365. case WM_KEYDOWN:
  366. switch(wParam) {
  367. case VK_RETURN:
  368. if (!ComboEx_EndEdit(pce, CBENF_RETURN))
  369. // we know we have an edit window, so FALSE return means
  370. // app returned FALSE to CBEN_ENDEDIT notification
  371. ComboEx_BeginEdit(pce);
  372. break;
  373. case VK_ESCAPE:
  374. pce->fEditChanged = FALSE;
  375. if (!ComboEx_EndEdit(pce, CBENF_ESCAPE)) {
  376. if(pce->fEditItemSet) {
  377. if(pce->cei.pszText) {
  378. SendMessage(pce->hwndEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)pce->cei.pszText);
  379. EDIT_SELECTALL( pce->hwndEdit );
  380. }
  381. RedrawWindow(pce->hwndCombo, NULL, NULL, RDW_ERASE | RDW_INVALIDATE);
  382. }else {
  383. ComboEx_BeginEdit(pce);
  384. }
  385. }
  386. break;
  387. // Pass these to the combobox itself to make it work properly...
  388. case VK_HOME:
  389. case VK_END:
  390. if (!pce->fInDrop)
  391. break;
  392. case VK_F4:
  393. case VK_UP:
  394. case VK_DOWN:
  395. case VK_PRIOR:
  396. case VK_NEXT:
  397. if (pce->hwndCombo)
  398. return SendMessage(pce->hwndCombo, uMsg, wParam, lParam);
  399. break;
  400. }
  401. break;
  402. case WM_LBUTTONDOWN:
  403. if (GetFocus() != pce->hwndEdit)
  404. {
  405. SetFocus(pce->hwndEdit);
  406. // since we disabled autoselection on first click in address bar,
  407. // we should not eat this message. This allows the dragging to begin with
  408. // the first click.
  409. return(0L); // eat this message
  410. }
  411. break;
  412. case WM_SYSKEYDOWN:
  413. switch(wParam) {
  414. // Pass these to the combobox itself to make it work properly...
  415. case VK_UP:
  416. case VK_DOWN:
  417. {
  418. LRESULT lR;
  419. if (pce->hwndCombo)
  420. {
  421. lR=SendMessage(pce->hwndCombo, uMsg, wParam, lParam);
  422. //notify of navigation key usage
  423. CCNotifyNavigationKeyUsage(&(pce->ci), UISF_HIDEFOCUS);
  424. return lR;
  425. }
  426. }
  427. }
  428. }
  429. return DefSubclassProc(hwnd, uMsg, wParam, lParam);
  430. }
  431. LRESULT ComboEx_GetLBText(PCOMBOBOXEX pce, UINT uMsg, WPARAM wParam, LPARAM lParam)
  432. {
  433. DWORD cchResult = CB_ERR;
  434. TCHAR szText[CBEMAXSTRLEN];
  435. COMBOBOXEXITEM cei;
  436. cei.mask = CBEIF_TEXT;
  437. cei.pszText = szText;
  438. cei.cchTextMax = ARRAYSIZE(szText);
  439. cei.iItem = (INT)wParam;
  440. if (ComboEx_OnGetItem(pce, &cei))
  441. {
  442. cchResult = lstrlen(szText);
  443. if (lParam && uMsg == CB_GETLBTEXT)
  444. {
  445. // REVIEW: trusts that the lParam points to a buffer of sufficient
  446. // size to support the string and null terminator.
  447. StringCchCopy((LPTSTR)lParam, cchResult+1, szText);
  448. }
  449. }
  450. return cchResult;
  451. }
  452. LRESULT CALLBACK ComboSubclassProc(HWND hwnd, UINT uMsg, WPARAM wParam,
  453. LPARAM lParam, UINT_PTR uIdSubclass, DWORD_PTR dwRefData)
  454. {
  455. PCOMBOBOXEX pce = (PCOMBOBOXEX)dwRefData;
  456. switch (uMsg)
  457. {
  458. case CB_GETLBTEXT:
  459. case CB_GETLBTEXTLEN:
  460. return ComboEx_GetLBText(pce, uMsg, wParam, lParam);
  461. case WM_RBUTTONDOWN:
  462. case WM_LBUTTONDOWN:
  463. if (ComboSubclass_HandleButton(pce, uMsg, wParam, lParam))
  464. {
  465. return 0;
  466. }
  467. break;
  468. case WM_PAINT:
  469. if (pce->hwndEdit)
  470. {
  471. RedrawWindow(pce->hwndEdit, NULL, NULL, RDW_INVALIDATE);
  472. }
  473. break;
  474. case WM_COMMAND:
  475. if (ComboSubclass_HandleCommand(pce, wParam, lParam))
  476. {
  477. return 0;
  478. }
  479. break;
  480. case WM_DESTROY:
  481. RemoveWindowSubclass(hwnd, ComboSubclassProc, 0);
  482. break;
  483. case WM_SETCURSOR:
  484. if (pce)
  485. {
  486. NMMOUSE nm = {0};
  487. nm.dwHitInfo = lParam;
  488. if (CCSendNotify(&pce->ci, NM_SETCURSOR, &nm.hdr))
  489. {
  490. return 0;
  491. }
  492. }
  493. break;
  494. }
  495. return DefSubclassProc(hwnd, uMsg, wParam, lParam);
  496. }
  497. BOOL ComboEx_OnCreate(HWND hwnd, LPCREATESTRUCT lpcs)
  498. {
  499. PCOMBOBOXEX pce;
  500. DWORD dwStyle;
  501. DWORD dwExStyle = 0;
  502. pce = (PCOMBOBOXEX)LocalAlloc(LPTR, sizeof(COMBOEX));
  503. if (!pce)
  504. return FALSE;
  505. SetWindowPtr(hwnd, 0, pce);
  506. // force off borders off ourself
  507. lpcs->style &= ~(WS_BORDER | WS_VSCROLL | WS_HSCROLL | CBS_UPPERCASE | CBS_LOWERCASE);
  508. SetWindowLong(hwnd, GWL_STYLE, lpcs->style);
  509. CIInitialize(&pce->ci, hwnd, lpcs);
  510. // or in CBS_SIMPLE because we can never allow the sub combo box
  511. // to have just drop down.. it's either all simple or dropdownlist
  512. dwStyle = CBS_OWNERDRAWFIXED | CBS_SIMPLE | CBS_NOINTEGRALHEIGHT | WS_VISIBLE |WS_VSCROLL | WS_CLIPCHILDREN | WS_CLIPSIBLINGS;
  513. dwStyle |= (lpcs->style & (CBS_DROPDOWNLIST | CBS_HASSTRINGS | WS_CHILD));
  514. if ((lpcs->style & CBS_DROPDOWNLIST) == CBS_SIMPLE)
  515. dwStyle |= (lpcs->style & (CBS_AUTOHSCROLL | CBS_OEMCONVERT | CBS_UPPERCASE | CBS_LOWERCASE));
  516. dwExStyle = lpcs->dwExStyle & (WS_EX_RIGHT | WS_EX_RTLREADING | WS_EX_LEFTSCROLLBAR);
  517. pce->hwndCombo = CreateWindowEx(dwExStyle, WC_COMBOBOX, lpcs->lpszName,
  518. dwStyle,
  519. 0, 0, lpcs->cx, lpcs->cy,
  520. hwnd, lpcs->hMenu, lpcs->hInstance, 0);
  521. if (!pce->hwndCombo ||
  522. !SetWindowSubclass(pce->hwndCombo, ComboSubclassProc, 0, (DWORD_PTR)pce) ||
  523. (!ComboEx_GetEditBox(pce) && ComboEx_Editable(pce)))
  524. {
  525. ComboEx_OnDestroy(pce);
  526. return FALSE;
  527. }
  528. ComboEx_OnSetFont(pce, NULL, FALSE);
  529. pce->cxIndent = 10;
  530. pce->iSel = -1;
  531. ComboEx_OnWindowPosChanging(pce, NULL);
  532. return TRUE;
  533. }
  534. HIMAGELIST ComboEx_OnSetImageList(PCOMBOBOXEX pce, HIMAGELIST himl)
  535. {
  536. int iHeight;
  537. HIMAGELIST himlOld = pce->himl;
  538. pce->himl = himl;
  539. iHeight = ComboEx_ComputeItemHeight(pce, FALSE);
  540. SendMessage(pce->ci.hwnd, CB_SETITEMHEIGHT, (WPARAM)-1, iHeight);
  541. SendMessage(pce->hwndCombo, CB_SETITEMHEIGHT, 0, iHeight);
  542. InvalidateRect(pce->hwndCombo, NULL, TRUE);
  543. if (pce->hwndEdit)
  544. ComboEx_SizeEditBox(pce);
  545. return himlOld;
  546. }
  547. void ComboEx_OnDrawItem(PCOMBOBOXEX pce, LPDRAWITEMSTRUCT pdis)
  548. {
  549. HDC hdc = pdis->hDC;
  550. RECT rc = pdis->rcItem;
  551. TCHAR szText[CBEMAXSTRLEN];
  552. int offset = 0;
  553. int xString, yString, xCombo;
  554. int cxIcon = 0, cyIcon = 0;
  555. int iLen;
  556. BOOL fSelected = FALSE;
  557. SIZE sizeText;
  558. COMBOBOXEXITEM cei;
  559. BOOL fNoText = FALSE;
  560. BOOL fEnabled = IsWindowEnabled(pce->hwndCombo);
  561. BOOL fRTLReading = FALSE;
  562. UINT OldTextAlign;
  563. // Setup the dc before we use it.
  564. fRTLReading = GetWindowLong(pdis->hwndItem, GWL_EXSTYLE) & WS_EX_RTLREADING;
  565. if (fRTLReading)
  566. {
  567. OldTextAlign = GetTextAlign(hdc);
  568. SetTextAlign(hdc, OldTextAlign|TA_RTLREADING);
  569. }
  570. rc.top += g_cyBorder;
  571. szText[0] = 0;
  572. if (pdis->itemID != -1)
  573. {
  574. cei.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_OVERLAY | CBEIF_SELECTEDIMAGE| CBEIF_INDENT;
  575. cei.pszText = szText;
  576. cei.cchTextMax = ARRAYSIZE(szText);
  577. cei.iItem = (INT)pdis->itemID;
  578. ComboEx_OnGetItem(pce, &cei);
  579. if (pce->iSel == (int)pdis->itemID ||
  580. ((pce->iSel == -1) && ((int)pdis->itemID == ComboBox_GetCurSel(pce->hwndCombo))))
  581. fSelected = TRUE;
  582. }
  583. else
  584. {
  585. if (pce->fEditItemSet)
  586. {
  587. cei.mask = CBEIF_TEXT | CBEIF_IMAGE | CBEIF_OVERLAY | CBEIF_SELECTEDIMAGE| CBEIF_INDENT;
  588. cei.pszText = szText;
  589. cei.cchTextMax = ARRAYSIZE(szText);
  590. cei.iItem = (INT)pdis->itemID;
  591. ComboEx_OnGetItem(pce, &cei);
  592. }
  593. }
  594. if (pce->himl && !(pce->dwExStyle & CBES_EX_NOEDITIMAGEINDENT))
  595. {
  596. CCGetIconSize(&pce->ci, pce->himl, &cxIcon, &cyIcon);
  597. if (cxIcon)
  598. cxIcon += COMBO_MARGIN;
  599. }
  600. // if we're not drawing the edit box, figure out how far to indent
  601. // over
  602. if (!(pdis->itemState & ODS_COMBOBOXEDIT))
  603. {
  604. offset = (pce->cxIndent * cei.iIndent) + COMBO_BORDER;
  605. }
  606. else
  607. {
  608. if (pce->hwndEdit)
  609. fNoText = TRUE;
  610. if (pce->dwExStyle & CBES_EX_NOEDITIMAGEINDENT)
  611. cxIcon = 0;
  612. }
  613. xCombo = rc.left + offset;
  614. rc.left = xString = xCombo + cxIcon;
  615. iLen = lstrlen(szText);
  616. GetTextExtentPoint(hdc, szText, iLen, &sizeText);
  617. rc.right = rc.left + sizeText.cx;
  618. rc.left--;
  619. rc.right++;
  620. if (pdis->itemAction != ODA_FOCUS)
  621. {
  622. int yMid;
  623. BOOL fTextHighlight = FALSE;;
  624. yMid = (rc.top + rc.bottom) / 2;
  625. // center the string within rc
  626. yString = yMid - (sizeText.cy/2);
  627. if (pdis->itemState & ODS_SELECTED)
  628. {
  629. if (!(pdis->itemState & ODS_COMBOBOXEDIT) ||
  630. !ComboEx_Editable(pce))
  631. {
  632. fTextHighlight = TRUE;
  633. }
  634. }
  635. if ( !fEnabled ) {
  636. SetBkColor(hdc, g_clrBtnFace);
  637. SetTextColor(hdc, GetSysColor(COLOR_GRAYTEXT));
  638. } else {
  639. SetBkColor(hdc, GetSysColor(fTextHighlight ?
  640. COLOR_HIGHLIGHT : COLOR_WINDOW));
  641. SetTextColor(hdc, GetSysColor(fTextHighlight ?
  642. COLOR_HIGHLIGHTTEXT : COLOR_WINDOWTEXT));
  643. }
  644. if ((pdis->itemState & ODS_COMBOBOXEDIT) &&
  645. (rc.right > pdis->rcItem.right))
  646. {
  647. // Need to clip as user does not!
  648. rc.right = pdis->rcItem.right;
  649. }
  650. if (!fNoText) {
  651. ExtTextOut(hdc, xString, yString, ETO_OPAQUE | ETO_CLIPPED, &rc, szText, iLen, NULL);
  652. }
  653. if (pce->himl && (pdis->itemID != -1 || pce->fEditItemSet) &&
  654. !((pce->dwExStyle & (CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT)) &&
  655. (pdis->itemState & ODS_COMBOBOXEDIT)))
  656. {
  657. DWORD dwDrawFlags = ILD_NORMAL;
  658. if ((pdis->itemState & ODS_COMBOBOXEDIT) && !fEnabled)
  659. {
  660. dwDrawFlags = ILD_TRANSPARENT;
  661. }
  662. if (pce->himl && (pdis->itemID != -1 || pce->fEditItemSet) &&
  663. !((pce->dwExStyle & (CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT))))
  664. {
  665. if (pdis->itemState & ODS_SELECTED)
  666. dwDrawFlags |= ILD_SELECTED | ILD_FOCUS;
  667. if (ComboEx_IsDPIScaled())
  668. {
  669. dwDrawFlags |= ILD_DPISCALE;
  670. }
  671. ImageList_Draw(pce->himl,
  672. (fSelected) ? cei.iSelectedImage : cei.iImage,
  673. hdc, xCombo, yMid - (cyIcon/2),
  674. INDEXTOOVERLAYMASK(cei.iOverlay) | dwDrawFlags);
  675. }
  676. }
  677. }
  678. if ((pdis->itemAction == ODA_FOCUS ||
  679. (pdis->itemState & ODS_FOCUS)) &&
  680. !(CCGetUIState(&(pce->ci)) & UISF_HIDEFOCUS))
  681. {
  682. if (!fNoText)
  683. {
  684. DrawFocusRect(hdc, &rc);
  685. }
  686. }
  687. // Restore the text align in the dc.
  688. if (fRTLReading)
  689. {
  690. SetTextAlign(hdc, OldTextAlign);
  691. }
  692. }
  693. int ComboEx_ComputeItemHeight(PCOMBOBOXEX pce, BOOL fTextOnly)
  694. {
  695. HDC hdc;
  696. HFONT hfontOld;
  697. int dyDriveItem;
  698. SIZE siz;
  699. hdc = GetDC(NULL);
  700. hfontOld = ComboEx_GetFont(pce);
  701. if (hfontOld)
  702. hfontOld = SelectObject(hdc, hfontOld);
  703. GetTextExtentPoint(hdc, TEXT("W"), 1, &siz);
  704. dyDriveItem = siz.cy;
  705. if (hfontOld)
  706. SelectObject(hdc, hfontOld);
  707. ReleaseDC(NULL, hdc);
  708. if (fTextOnly)
  709. return dyDriveItem;
  710. dyDriveItem += COMBO_BORDER;
  711. // now take into account the icon
  712. if (pce->himl)
  713. {
  714. int cxIcon = 0, cyIcon = 0;
  715. CCGetIconSize(&pce->ci, pce->himl, &cxIcon, &cyIcon);
  716. if (dyDriveItem < cyIcon)
  717. dyDriveItem = cyIcon;
  718. }
  719. return dyDriveItem;
  720. }
  721. void ComboEx_OnMeasureItem(PCOMBOBOXEX pce, LPMEASUREITEMSTRUCT pmi)
  722. {
  723. pmi->itemHeight = ComboEx_ComputeItemHeight(pce, FALSE);
  724. }
  725. void ComboEx_ISetItem(PCOMBOBOXEX pce, PCEITEM pcei, PCOMBOBOXEXITEM pceItem)
  726. {
  727. if (pceItem->mask & CBEIF_INDENT)
  728. pcei->iIndent = pceItem->iIndent;
  729. if (pceItem->mask & CBEIF_IMAGE)
  730. pcei->iImage = pceItem->iImage;
  731. if (pceItem->mask & CBEIF_SELECTEDIMAGE)
  732. pcei->iSelectedImage = pceItem->iSelectedImage;
  733. if (pceItem->mask & CBEIF_OVERLAY)
  734. pcei->iOverlay = pceItem->iOverlay;
  735. if (pceItem->mask & CBEIF_TEXT) {
  736. Str_Set(&pcei->pszText, pceItem->pszText);
  737. }
  738. if (pceItem->mask & CBEIF_LPARAM) {
  739. pcei->lParam = pceItem->lParam;
  740. }
  741. }
  742. #define ComboEx_GetItemPtr(pce, iItem) \
  743. ((PCEITEM)SendMessage((pce)->hwndCombo, CB_GETITEMDATA, iItem, 0))
  744. #define ComboEx_Count(pce) \
  745. ((int)SendMessage((pce)->hwndCombo, CB_GETCOUNT, 0, 0))
  746. BOOL ComboEx_OnGetItem(PCOMBOBOXEX pce, PCOMBOBOXEXITEM pceItem)
  747. {
  748. PCEITEM pcei;
  749. NMCOMBOBOXEX nm;
  750. if(pceItem->iItem != -1) {
  751. pcei = ComboEx_GetItemPtr(pce, pceItem->iItem);
  752. }
  753. else {
  754. pcei = &(pce->cei);
  755. }
  756. if ((!pcei) || (pcei == (PCEITEM)-1))
  757. return FALSE;
  758. nm.ceItem.mask = 0;
  759. if (pceItem->mask & CBEIF_TEXT) {
  760. if (pcei->pszText == LPSTR_TEXTCALLBACK) {
  761. nm.ceItem.mask |= CBEIF_TEXT;
  762. }
  763. else
  764. {
  765. if(pceItem->iItem != -1)
  766. {
  767. Str_GetPtr(pcei->pszText, pceItem->pszText, pceItem->cchTextMax);
  768. }
  769. else if (pce->hwndEdit)
  770. {
  771. SendMessage(pce->hwndEdit, WM_GETTEXT, (WPARAM)pceItem->cchTextMax, (LPARAM)pceItem->pszText);
  772. }
  773. }
  774. }
  775. if (pceItem->mask & CBEIF_IMAGE) {
  776. if (pcei->iImage == I_IMAGECALLBACK) {
  777. nm.ceItem.mask |= CBEIF_IMAGE;
  778. }
  779. pceItem->iImage = pcei->iImage;
  780. }
  781. if (pceItem->mask & CBEIF_SELECTEDIMAGE) {
  782. if (pcei->iSelectedImage == I_IMAGECALLBACK) {
  783. nm.ceItem.mask |= CBEIF_SELECTEDIMAGE;
  784. }
  785. pceItem->iSelectedImage = pcei->iSelectedImage;
  786. }
  787. if (pceItem->mask & CBEIF_OVERLAY) {
  788. if (pcei->iOverlay == I_IMAGECALLBACK) {
  789. nm.ceItem.mask |= CBEIF_OVERLAY;
  790. }
  791. pceItem->iOverlay = pcei->iOverlay;
  792. }
  793. if (pceItem->mask & CBEIF_INDENT) {
  794. if (pcei->iIndent == I_INDENTCALLBACK) {
  795. nm.ceItem.mask |= CBEIF_INDENT;
  796. pceItem->iIndent = 0;
  797. } else {
  798. pceItem->iIndent = pcei->iIndent;
  799. }
  800. }
  801. if (pceItem->mask & CBEIF_LPARAM) {
  802. pceItem->lParam = pcei->lParam;
  803. }
  804. // is there anything to call back for?
  805. if (nm.ceItem.mask) {
  806. UINT uMask = nm.ceItem.mask;
  807. nm.ceItem = *pceItem;
  808. nm.ceItem.lParam = pcei->lParam;
  809. nm.ceItem.mask = uMask;
  810. if ((nm.ceItem.mask & CBEIF_TEXT) &&
  811. nm.ceItem.cchTextMax) {
  812. // null terminate just in case they don't respond
  813. *nm.ceItem.pszText = 0;
  814. }
  815. CCSendNotify(&pce->ci, CBEN_GETDISPINFO, &nm.hdr);
  816. if (nm.ceItem.mask & CBEIF_INDENT)
  817. pceItem->iIndent = nm.ceItem.iIndent;
  818. if (nm.ceItem.mask & CBEIF_IMAGE)
  819. pceItem->iImage = nm.ceItem.iImage;
  820. if (nm.ceItem.mask & CBEIF_SELECTEDIMAGE)
  821. pceItem->iSelectedImage = nm.ceItem.iSelectedImage;
  822. if (nm.ceItem.mask & CBEIF_OVERLAY)
  823. pceItem->iOverlay = nm.ceItem.iOverlay;
  824. if (nm.ceItem.mask & CBEIF_TEXT)
  825. pceItem->pszText = CCReturnDispInfoText(nm.ceItem.pszText, pceItem->pszText, pceItem->cchTextMax);
  826. if (nm.ceItem.mask & CBEIF_DI_SETITEM) {
  827. ComboEx_ISetItem(pce, pcei, &nm.ceItem);
  828. }
  829. }
  830. return TRUE;
  831. }
  832. BOOL ComboEx_OnGetItemA(PCOMBOBOXEX pce, PCOMBOBOXEXITEMA pceItem)
  833. {
  834. LPWSTR pwszText;
  835. LPSTR pszTextSave;
  836. BOOL fRet;
  837. if (!(pceItem->mask & CBEIF_TEXT)) {
  838. return ComboEx_OnGetItem(pce, (PCOMBOBOXEXITEM)pceItem);
  839. }
  840. pwszText = (LPWSTR)LocalAlloc(LPTR, (pceItem->cchTextMax+1)*sizeof(WCHAR));
  841. if (!pwszText)
  842. return FALSE;
  843. pszTextSave = pceItem->pszText;
  844. ((PCOMBOBOXEXITEM)pceItem)->pszText = pwszText;
  845. fRet = ComboEx_OnGetItem(pce, (PCOMBOBOXEXITEM)pceItem);
  846. pceItem->pszText = pszTextSave;
  847. if (fRet)
  848. {
  849. // WCTMB failes w/ ERROR_INSUFFICIENT_BUFFER whereas the native-A implementation truncates
  850. WideCharToMultiByte(CP_ACP, 0, pwszText, -1,
  851. (LPSTR)pszTextSave, pceItem->cchTextMax, NULL, NULL);
  852. }
  853. LocalFree(pwszText);
  854. return fRet;
  855. }
  856. BOOL ComboEx_OnSetItem(PCOMBOBOXEX pce, PCOMBOBOXEXITEM pceItem)
  857. {
  858. if(pceItem->iItem != -1) {
  859. PCEITEM pcei = ComboEx_GetItemPtr(pce, pceItem->iItem);
  860. UINT rdwFlags = 0;
  861. if (pcei == (PCEITEM)-1)
  862. return FALSE;
  863. ComboEx_ISetItem(pce, pcei, pceItem);
  864. if (rdwFlags & (CBEIF_INDENT | CBEIF_IMAGE |CBEIF_SELECTEDIMAGE | CBEIF_TEXT | CBEIF_OVERLAY)) {
  865. rdwFlags = RDW_ERASE | RDW_INVALIDATE;
  866. }
  867. if (rdwFlags)
  868. {
  869. RedrawWindow(pce->hwndCombo, NULL, NULL, rdwFlags);
  870. }
  871. if (pceItem->iItem == (INT)ComboBox_GetCurSel(pce->hwndCombo))
  872. ComboEx_UpdateEditText(pce, FALSE);
  873. // FEATURE: notify item changed
  874. return TRUE;
  875. } else {
  876. pce->cei.iImage = -1;
  877. pce->cei.iSelectedImage = -1;
  878. ComboEx_ISetItem(pce, &(pce->cei), pceItem);
  879. pce->fEditItemSet = TRUE;
  880. if (!pce->hwndEdit){
  881. Str_Set(&pce->cei.pszText, NULL);
  882. pce->fEditItemSet = FALSE;
  883. return(CB_ERR);
  884. }
  885. if(pce->cei.pszText) {
  886. SendMessage(pce->hwndEdit, WM_SETTEXT, (WPARAM)0, (LPARAM)pce->cei.pszText);
  887. EDIT_SELECTALL( pce->hwndEdit );
  888. }
  889. RedrawWindow(pce->hwndCombo, NULL, NULL, RDW_ERASE | RDW_INVALIDATE);
  890. return TRUE;
  891. }
  892. }
  893. void ComboEx_HandleDeleteItem(PCOMBOBOXEX pce, LPDELETEITEMSTRUCT pdis)
  894. {
  895. PCEITEM pcei = (PCEITEM)pdis->itemData;
  896. if (pcei) {
  897. NMCOMBOBOXEX nm;
  898. Str_Set(&pcei->pszText, NULL);
  899. nm.ceItem.iItem = (INT)pdis->itemID;
  900. nm.ceItem.mask = CBEIF_LPARAM;
  901. nm.ceItem.lParam = pcei->lParam;
  902. CCSendNotify(&pce->ci, CBEN_DELETEITEM, &nm.hdr);
  903. LocalFree(pcei);
  904. }
  905. }
  906. LRESULT ComboEx_OnInsertItem(PCOMBOBOXEX pce, PCOMBOBOXEXITEM pceItem)
  907. {
  908. LRESULT iRet;
  909. PCEITEM pcei = (PCEITEM)LocalAlloc(LPTR, sizeof(CEITEM));
  910. if (!pcei)
  911. return -1;
  912. pcei->iImage = -1;
  913. pcei->iSelectedImage = -1;
  914. //pcei->iOverlay = 0;
  915. //pcei->iIndent = 0;
  916. ComboEx_ISetItem(pce, pcei, pceItem);
  917. iRet = ComboBox_InsertString(pce->hwndCombo, pceItem->iItem, pcei);
  918. if (iRet != -1) {
  919. NMCOMBOBOXEX nm;
  920. nm.ceItem = *pceItem;
  921. CCSendNotify(&pce->ci, CBEN_INSERTITEM, &nm.hdr);
  922. }
  923. return iRet;
  924. }
  925. void ComboEx_OnWindowPosChanging(PCOMBOBOXEX pce, LPWINDOWPOS pwp)
  926. {
  927. RECT rcWindow, rcClient;
  928. RECT rc;
  929. int cxInner;
  930. int cy;
  931. GetWindowRect(pce->ci.hwnd, &rcWindow);
  932. if (pwp) {
  933. // check to see if our size & position aren't actually changing (rebar, for one,
  934. // does lots of DeferWindowPos calls that don't actually change our size or position
  935. // but still generate WM_WINDOWPOSCHANGING msgs). we avoid flicker by bailing here.
  936. RECT rcWp;
  937. SetRect(&rcWp, pwp->x, pwp->y, pwp->x + pwp->cx, pwp->y + pwp->cy);
  938. MapWindowRect(GetParent(pce->ci.hwnd), HWND_DESKTOP, (LPPOINT)&rcWp);
  939. if (EqualRect(&rcWp, &rcWindow)) {
  940. // this is a noop, so bail
  941. return;
  942. }
  943. }
  944. GetClientRect(pce->ci.hwnd, &rcClient);
  945. if (pwp)
  946. cxInner = pwp->cx + RECTWIDTH(rcWindow) - RECTWIDTH(rcClient);
  947. else
  948. cxInner = RECTWIDTH(rcClient);
  949. GetWindowRect(pce->hwndCombo, &rc);
  950. if (cxInner) {
  951. // don't size the inner combo if width is 0; otherwise, the below
  952. // computation will make the comboEX the height of the inner combo
  953. // top + inner combo dropdown instead of JUST the inner combo top
  954. cy = (pwp && ((pce->ci.style & CBS_DROPDOWNLIST) == CBS_SIMPLE)) ? pwp->cy : RECTHEIGHT(rc);
  955. SetWindowPos(pce->hwndCombo, NULL, 0, 0, cxInner, cy,
  956. SWP_NOACTIVATE | (pce->hwndEdit ? SWP_NOREDRAW : 0));
  957. }
  958. GetWindowRect(pce->hwndCombo, &rc);
  959. cy = RECTHEIGHT(rc) + (RECTHEIGHT(rcWindow) - RECTHEIGHT(rcClient));
  960. if (pwp)
  961. {
  962. if (cy < pwp->cy || !(pce->dwExStyle & CBES_EX_NOSIZELIMIT))
  963. {
  964. pwp->cy = cy;
  965. }
  966. }
  967. else
  968. {
  969. if (cy < RECTHEIGHT(rcWindow) || !(pce->dwExStyle & CBES_EX_NOSIZELIMIT))
  970. {
  971. SetWindowPos(pce->ci.hwnd, NULL, 0, 0,
  972. RECTWIDTH(rcWindow),
  973. cy,
  974. SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
  975. }
  976. }
  977. if (pce->hwndEdit)
  978. {
  979. ComboEx_SizeEditBox(pce);
  980. InvalidateRect(pce->hwndCombo, NULL, TRUE);
  981. }
  982. }
  983. LRESULT ComboEx_HandleCommand(PCOMBOBOXEX pce, WPARAM wParam, LPARAM lParam)
  984. {
  985. LRESULT lres;
  986. UINT idCmd = GET_WM_COMMAND_ID(wParam, lParam);
  987. UINT uCmd = GET_WM_COMMAND_CMD(wParam, lParam);
  988. if (!pce)
  989. return 0;
  990. if (uCmd == CBN_SELCHANGE)
  991. // update the edit text before forwarding this notification 'cause in
  992. // a normal combobox, the edit control will have already been updated
  993. // upon receipt of this notification
  994. ComboEx_UpdateEditText(pce, FALSE);
  995. lres = SendMessage(pce->ci.hwndParent, WM_COMMAND, GET_WM_COMMAND_MPS(idCmd, pce->ci.hwnd, uCmd));
  996. switch (uCmd)
  997. {
  998. case CBN_DROPDOWN:
  999. pce->iSel = ComboBox_GetCurSel(pce->hwndCombo);
  1000. ComboEx_EndEdit(pce, CBENF_DROPDOWN);
  1001. if (GetFocus() == pce->hwndEdit)
  1002. SetFocus(pce->hwndCombo);
  1003. pce->fInDrop = TRUE;
  1004. break;
  1005. case CBN_KILLFOCUS:
  1006. ComboEx_EndEdit(pce, CBENF_KILLFOCUS);
  1007. break;
  1008. case CBN_CLOSEUP:
  1009. pce->iSel = -1;
  1010. ComboEx_BeginEdit(pce);
  1011. pce->fInDrop = FALSE;
  1012. break;
  1013. case CBN_SETFOCUS:
  1014. ComboEx_BeginEdit(pce);
  1015. break;
  1016. }
  1017. return lres;
  1018. }
  1019. LRESULT ComboEx_OnGetItemData(PCOMBOBOXEX pce, WPARAM i)
  1020. {
  1021. PCEITEM pcei = (PCEITEM)SendMessage(pce->hwndCombo, CB_GETITEMDATA, i, 0);
  1022. if (pcei == NULL || pcei == (PCEITEM)CB_ERR)
  1023. {
  1024. return CB_ERR;
  1025. }
  1026. return pcei->lParam;
  1027. }
  1028. LRESULT ComboEx_OnSetItemData(PCOMBOBOXEX pce, int i, LPARAM lParam)
  1029. {
  1030. PCEITEM pcei = (PCEITEM)SendMessage(pce->hwndCombo, CB_GETITEMDATA, i, 0);
  1031. if (pcei == NULL || pcei == (PCEITEM)CB_ERR)
  1032. {
  1033. return CB_ERR;
  1034. }
  1035. pcei->lParam = lParam;
  1036. return 0;
  1037. }
  1038. int ComboEx_OnFindStringExact(PCOMBOBOXEX pce, int iStart, LPCTSTR lpsz)
  1039. {
  1040. int i;
  1041. int iMax = ComboEx_Count(pce);
  1042. TCHAR szText[CBEMAXSTRLEN];
  1043. COMBOBOXEXITEM cei;
  1044. if (iStart < 0)
  1045. iStart = -1;
  1046. cei.mask = CBEIF_TEXT;
  1047. cei.pszText = szText;
  1048. cei.cchTextMax = ARRAYSIZE(szText);
  1049. for (i = iStart + 1 ; i < iMax; i++)
  1050. {
  1051. cei.iItem = i;
  1052. if (ComboEx_OnGetItem(pce, &cei))
  1053. {
  1054. if (!ComboEx_StrCmp(pce, lpsz, szText))
  1055. {
  1056. return i;
  1057. }
  1058. }
  1059. }
  1060. for (i = 0; i <= iStart; i++)
  1061. {
  1062. cei.iItem = i;
  1063. if (ComboEx_OnGetItem(pce, &cei))
  1064. {
  1065. if (!ComboEx_StrCmp(pce, lpsz, szText))
  1066. {
  1067. return i;
  1068. }
  1069. }
  1070. }
  1071. return CB_ERR;
  1072. }
  1073. int ComboEx_StrCmp(PCOMBOBOXEX pce, LPCTSTR psz1, LPCTSTR psz2)
  1074. {
  1075. if (pce->dwExStyle & CBES_EX_CASESENSITIVE)
  1076. {
  1077. return lstrcmp(psz1, psz2);
  1078. }
  1079. return lstrcmpi(psz1, psz2);
  1080. }
  1081. DWORD ComboEx_OnSetExStyle(PCOMBOBOXEX pce, DWORD dwExStyle, DWORD dwExMask)
  1082. {
  1083. DWORD dwRet;
  1084. DWORD dwChange;
  1085. if (dwExMask)
  1086. dwExStyle = (pce->dwExStyle & ~ dwExMask) | (dwExStyle & dwExMask);
  1087. dwRet = pce->dwExStyle;
  1088. dwChange = (pce->dwExStyle ^ dwExStyle);
  1089. pce->dwExStyle = dwExStyle;
  1090. if (dwChange & (CBES_EX_NOEDITIMAGE | CBES_EX_NOEDITIMAGEINDENT))
  1091. {
  1092. InvalidateRect(pce->ci.hwnd, NULL, TRUE);
  1093. if (pce->hwndEdit)
  1094. {
  1095. ComboEx_SizeEditBox(pce);
  1096. InvalidateRect(pce->hwndEdit, NULL, TRUE);
  1097. }
  1098. }
  1099. if (dwChange & CBES_EX_PATHWORDBREAKPROC)
  1100. SetPathWordBreakProc(pce->hwndEdit, (pce->dwExStyle & CBES_EX_PATHWORDBREAKPROC));
  1101. return dwRet;
  1102. }
  1103. HFONT ComboEx_GetFont(PCOMBOBOXEX pce)
  1104. {
  1105. if (pce->hwndCombo)
  1106. return (HFONT)SendMessage(pce->hwndCombo, WM_GETFONT, 0, 0);
  1107. return NULL;
  1108. }
  1109. LRESULT CALLBACK ComboExWndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
  1110. {
  1111. LRESULT lres = 0;
  1112. PCOMBOBOXEX pce = (PCOMBOBOXEX)GetWindowPtr(hwnd, 0);
  1113. if (!pce)
  1114. {
  1115. if (uMsg != WM_NCCREATE &&
  1116. uMsg != WM_CREATE)
  1117. goto DoDefault;
  1118. }
  1119. switch (uMsg)
  1120. {
  1121. HANDLE_MSG(pce, WM_SETFONT, ComboEx_OnSetFont);
  1122. case WM_ENABLE:
  1123. if (pce->hwndCombo)
  1124. EnableWindow(pce->hwndCombo, (BOOL) wParam);
  1125. if (pce->hwndEdit)
  1126. EnableWindow(pce->hwndEdit, (BOOL) wParam);
  1127. break;
  1128. case WM_WININICHANGE:
  1129. InitGlobalMetrics(wParam);
  1130. // only need to re-create this font if we created it in the first place
  1131. // and somebody changed the font (or did a wildcard change)
  1132. //
  1133. // NOTE: Some people broadcast a nonclient metrics change when they
  1134. // change the icon title logfont, so watch for both.
  1135. //
  1136. if (pce && pce->fFontCreated &&
  1137. ((wParam == 0 && lParam == 0) ||
  1138. wParam == SPI_SETICONTITLELOGFONT ||
  1139. wParam == SPI_SETNONCLIENTMETRICS))
  1140. {
  1141. ComboEx_OnSetFont(pce, NULL, TRUE);
  1142. }
  1143. break;
  1144. case WM_SYSCOLORCHANGE:
  1145. InitGlobalColors();
  1146. break;
  1147. case WM_NOTIFYFORMAT:
  1148. return CIHandleNotifyFormat(&pce->ci, lParam);
  1149. break;
  1150. case WM_NCCREATE:
  1151. // strip off the scroll bits
  1152. SetWindowBits(hwnd, GWL_STYLE, WS_BORDER | WS_VSCROLL | WS_HSCROLL, 0);
  1153. goto DoDefault;
  1154. case WM_CREATE:
  1155. if (!ComboEx_OnCreate(hwnd, (LPCREATESTRUCT)lParam))
  1156. lres = -1; // OnCreate falied. Fail WM_CREATE
  1157. break;
  1158. case WM_PRINTCLIENT:
  1159. CCSendPrint(&pce->ci, (HDC)wParam);
  1160. break;
  1161. case WM_DESTROY:
  1162. ASSERT(pce);
  1163. ComboEx_OnDestroy(pce);
  1164. break;
  1165. case WM_WINDOWPOSCHANGING:
  1166. ComboEx_OnWindowPosChanging(pce, (LPWINDOWPOS)lParam);
  1167. break;
  1168. case WM_DRAWITEM:
  1169. ComboEx_OnDrawItem(pce, (LPDRAWITEMSTRUCT)lParam);
  1170. break;
  1171. case WM_MEASUREITEM:
  1172. ComboEx_OnMeasureItem(pce, (LPMEASUREITEMSTRUCT)lParam);
  1173. break;
  1174. case WM_COMMAND:
  1175. return ComboEx_HandleCommand(pce, wParam, lParam);
  1176. case WM_GETFONT:
  1177. return (LRESULT)ComboEx_GetFont(pce);
  1178. case WM_SETFOCUS:
  1179. if (pce->hwndCombo)
  1180. SetFocus(pce->hwndCombo);
  1181. break;
  1182. case WM_DELETEITEM:
  1183. ComboEx_HandleDeleteItem(pce, (LPDELETEITEMSTRUCT)lParam);
  1184. return TRUE;
  1185. case WM_UPDATEUISTATE:
  1186. //not sure need to set bit, will probably not use it, on the other hand this
  1187. // is consistent with remaining of common controls and not very expensive
  1188. CCOnUIState(&(pce->ci), WM_UPDATEUISTATE, wParam, lParam);
  1189. goto DoDefault;
  1190. // this is for backcompat only.
  1191. case CBEM_SETEXSTYLE:
  1192. return ComboEx_OnSetExStyle(pce, (DWORD)wParam, 0);
  1193. case CBEM_SETEXTENDEDSTYLE:
  1194. return ComboEx_OnSetExStyle(pce, (DWORD)lParam, (DWORD)wParam);
  1195. case CBEM_GETEXTENDEDSTYLE:
  1196. return pce->dwExStyle;
  1197. case CBEM_GETCOMBOCONTROL:
  1198. return (LRESULT)pce->hwndCombo;
  1199. case CBEM_SETIMAGELIST:
  1200. return (LRESULT)ComboEx_OnSetImageList(pce, (HIMAGELIST)lParam);
  1201. case CBEM_GETIMAGELIST:
  1202. return (LRESULT)pce->himl;
  1203. case CBEM_GETITEMA:
  1204. return ComboEx_OnGetItemA(pce, (PCOMBOBOXEXITEMA)lParam);
  1205. case CBEM_GETITEM:
  1206. return ComboEx_OnGetItem(pce, (PCOMBOBOXEXITEM)lParam);
  1207. case CBEM_SETITEMA:
  1208. {
  1209. LRESULT lResult;
  1210. LPWSTR lpStrings;
  1211. UINT uiCount;
  1212. LPSTR lpAnsiString = (LPSTR) ((PCOMBOBOXEXITEM)lParam)->pszText;
  1213. if ((((PCOMBOBOXEXITEM)lParam)->mask & CBEIF_TEXT) &&
  1214. (((PCOMBOBOXEXITEM)lParam)->pszText != LPSTR_TEXTCALLBACK)) {
  1215. uiCount = lstrlenA(lpAnsiString)+1;
  1216. lpStrings = LocalAlloc(LPTR, (uiCount) * sizeof(TCHAR));
  1217. if (!lpStrings)
  1218. return -1;
  1219. MultiByteToWideChar(CP_ACP, 0, (LPCSTR) lpAnsiString, uiCount,
  1220. lpStrings, uiCount);
  1221. ((PCOMBOBOXEXITEMA)lParam)->pszText = (LPSTR)lpStrings;
  1222. lResult = ComboEx_OnSetItem(pce, (PCOMBOBOXEXITEM)lParam);
  1223. ((PCOMBOBOXEXITEMA)lParam)->pszText = lpAnsiString;
  1224. LocalFree(lpStrings);
  1225. return lResult;
  1226. } else {
  1227. return ComboEx_OnSetItem(pce, (PCOMBOBOXEXITEM)lParam);
  1228. }
  1229. }
  1230. case CBEM_SETITEM:
  1231. return ComboEx_OnSetItem(pce, (PCOMBOBOXEXITEM)lParam);
  1232. case CBEM_INSERTITEMA:
  1233. {
  1234. LRESULT lResult;
  1235. LPWSTR lpStrings;
  1236. UINT uiCount;
  1237. LPSTR lpAnsiString = (LPSTR) ((PCOMBOBOXEXITEM)lParam)->pszText;
  1238. if (!lpAnsiString || lpAnsiString == (LPSTR)LPSTR_TEXTCALLBACK)
  1239. return ComboEx_OnInsertItem(pce, (PCOMBOBOXEXITEM)lParam);
  1240. uiCount = lstrlenA(lpAnsiString)+1;
  1241. lpStrings = LocalAlloc(LPTR, (uiCount) * sizeof(TCHAR));
  1242. if (!lpStrings)
  1243. return -1;
  1244. MultiByteToWideChar(CP_ACP, 0, (LPCSTR) lpAnsiString, uiCount,
  1245. lpStrings, uiCount);
  1246. ((PCOMBOBOXEXITEMA)lParam)->pszText = (LPSTR)lpStrings;
  1247. lResult = ComboEx_OnInsertItem(pce, (PCOMBOBOXEXITEM)lParam);
  1248. ((PCOMBOBOXEXITEMA)lParam)->pszText = lpAnsiString;
  1249. LocalFree(lpStrings);
  1250. return lResult;
  1251. }
  1252. case CBEM_INSERTITEM:
  1253. return ComboEx_OnInsertItem(pce, (PCOMBOBOXEXITEM)lParam);
  1254. case CBEM_GETEDITCONTROL:
  1255. return (LRESULT)pce->hwndEdit;
  1256. case CBEM_HASEDITCHANGED:
  1257. return pce->fEditChanged;
  1258. case CBEM_SETWINDOWTHEME:
  1259. if (lParam)
  1260. {
  1261. SetWindowTheme(hwnd, (LPWSTR)lParam, NULL);
  1262. if (pce->hwndCombo)
  1263. SetWindowTheme(pce->hwndCombo, (LPWSTR)lParam, NULL);
  1264. if (pce->hwndEdit)
  1265. SetWindowTheme(pce->hwndEdit, (LPWSTR)lParam, NULL);
  1266. }
  1267. break;
  1268. case CB_GETITEMDATA:
  1269. return ComboEx_OnGetItemData(pce, (int)wParam);
  1270. case CB_SETITEMDATA:
  1271. return ComboEx_OnSetItemData(pce, (int)wParam, lParam);
  1272. case CB_LIMITTEXT:
  1273. if (ComboEx_GetEditBox(pce))
  1274. Edit_LimitText(pce->hwndEdit, wParam);
  1275. break;
  1276. case CB_FINDSTRINGEXACT:
  1277. {
  1278. LPCTSTR psz = (LPCTSTR)lParam;
  1279. return ComboEx_OnFindStringExact(pce, (int)wParam, psz);
  1280. }
  1281. case CB_SETITEMHEIGHT:
  1282. lres = SendMessage(pce->hwndCombo, uMsg, wParam, lParam);
  1283. if (wParam == (WPARAM)-1)
  1284. {
  1285. RECT rcWindow, rcClient;
  1286. int cy;
  1287. GetWindowRect(pce->hwndCombo, &rcWindow);
  1288. cy = RECTHEIGHT(rcWindow);
  1289. GetWindowRect(pce->ci.hwnd, &rcWindow);
  1290. GetClientRect(pce->ci.hwnd, &rcClient);
  1291. cy = cy + (RECTHEIGHT(rcWindow) - RECTHEIGHT(rcClient));
  1292. SetWindowPos(pce->ci.hwnd, NULL, 0, 0,
  1293. RECTWIDTH(rcWindow),
  1294. cy,
  1295. SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
  1296. }
  1297. break;
  1298. case CB_INSERTSTRING:
  1299. case CB_ADDSTRING:
  1300. case CB_SETEDITSEL:
  1301. case CB_FINDSTRING:
  1302. case CB_DIR:
  1303. // override to do nothing
  1304. break;
  1305. case CB_SETCURSEL:
  1306. case CB_RESETCONTENT:
  1307. case CB_DELETESTRING:
  1308. lres = SendMessage(pce->hwndCombo, uMsg, wParam, lParam);
  1309. ComboEx_UpdateEditText(pce, uMsg == CB_SETCURSEL);
  1310. break;
  1311. case WM_SETTEXT:
  1312. if (!pce->hwndEdit)
  1313. return(CB_ERR);
  1314. lres = SendMessage(pce->hwndEdit, uMsg, wParam, lParam);
  1315. EDIT_SELECTALL( pce->hwndEdit );
  1316. RedrawWindow(pce->hwndCombo, NULL, NULL, RDW_ERASE | RDW_INVALIDATE);
  1317. return(lres);
  1318. case WM_CUT:
  1319. case WM_COPY:
  1320. case WM_PASTE:
  1321. case WM_GETTEXT:
  1322. case WM_GETTEXTLENGTH:
  1323. if (!pce->hwndEdit)
  1324. return 0;
  1325. return(SendMessage(pce->hwndEdit, uMsg, wParam, lParam));
  1326. case WM_SETREDRAW:
  1327. if (pce->hwndEdit)
  1328. SendMessage(pce->hwndEdit, uMsg, wParam, lParam);
  1329. break;
  1330. case CB_GETEDITSEL:
  1331. if (pce->hwndEdit)
  1332. return SendMessage(pce->hwndEdit, EM_GETSEL, wParam, lParam);
  1333. // else fall through
  1334. // Handle it being in a dialog...
  1335. // May want to handle it differently when edit control has
  1336. // focus...
  1337. case WM_GETDLGCODE:
  1338. case CB_SHOWDROPDOWN:
  1339. case CB_SETEXTENDEDUI:
  1340. case CB_GETEXTENDEDUI:
  1341. case CB_GETDROPPEDSTATE:
  1342. case CB_GETDROPPEDCONTROLRECT:
  1343. case CB_GETCURSEL:
  1344. case CB_GETCOUNT:
  1345. case CB_SELECTSTRING:
  1346. case CB_GETITEMHEIGHT:
  1347. case CB_SETDROPPEDWIDTH:
  1348. return SendMessage(pce->hwndCombo, uMsg, wParam, lParam);
  1349. case CB_GETLBTEXT:
  1350. case CB_GETLBTEXTLEN:
  1351. return ComboEx_GetLBText(pce, uMsg, wParam, lParam);
  1352. default:
  1353. if (CCWndProc(&pce->ci, uMsg, wParam, lParam, &lres))
  1354. return lres;
  1355. DoDefault:
  1356. return DefWindowProc(hwnd, uMsg, wParam, lParam);
  1357. }
  1358. return lres;
  1359. }
  1360. BOOL InitComboExClass(HINSTANCE hinst)
  1361. {
  1362. WNDCLASS wc;
  1363. wc.lpfnWndProc = ComboExWndProc;
  1364. wc.hCursor = LoadCursor(NULL, IDC_ARROW);
  1365. wc.hIcon = NULL;
  1366. wc.lpszMenuName = NULL;
  1367. wc.hInstance = hinst;
  1368. wc.lpszClassName = c_szComboBoxEx;
  1369. wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // NULL;
  1370. wc.style = CS_GLOBALCLASS;
  1371. wc.cbWndExtra = sizeof(PCOMBOBOXEX);
  1372. wc.cbClsExtra = 0;
  1373. return (RegisterClass(&wc) || (GetLastError() == ERROR_CLASS_ALREADY_EXISTS));
  1374. }
  1375. //---------------------------------------------------------------------------
  1376. // SetPathWordBreakProc does special break processing for edit controls.
  1377. //
  1378. // The word break proc is called when ctrl-(left or right) arrow is pressed in the
  1379. // edit control. Normal processing provided by USER breaks words at spaces or tabs,
  1380. // but for us it would be nice to break words at slashes, backslashes, & periods too
  1381. // since it may be common to have paths or url's typed in.
  1382. void WINAPI SetPathWordBreakProc(HWND hwndEdit, BOOL fSet)
  1383. {
  1384. PROC lpfnOld;
  1385. // Don't shaft folks who set their own break proc - leave it alone.
  1386. lpfnOld = (FARPROC)SendMessage(hwndEdit, EM_GETWORDBREAKPROC, 0, 0L);
  1387. if (fSet)
  1388. {
  1389. if (!lpfnOld)
  1390. SendMessage(hwndEdit, EM_SETWORDBREAKPROC, 0, (LPARAM)ShellEditWordBreakProc);
  1391. }
  1392. else
  1393. {
  1394. if (lpfnOld == (FARPROC)ShellEditWordBreakProc)
  1395. SendMessage(hwndEdit, EM_SETWORDBREAKPROC, 0, 0L);
  1396. }
  1397. }
  1398. BOOL IsDelimiter(TCHAR ch)
  1399. {
  1400. return (ch == TEXT(' ') ||
  1401. ch == TEXT('\t') ||
  1402. ch == TEXT('.') ||
  1403. ch == TEXT('/') ||
  1404. ch == TEXT('\\'));
  1405. }
  1406. int WINAPI ShellEditWordBreakProc(LPTSTR lpch, int ichCurrent, int cch, int code)
  1407. {
  1408. LPTSTR lpchT = lpch + ichCurrent;
  1409. int iIndex;
  1410. BOOL fFoundNonDelimiter = FALSE;
  1411. static BOOL fRight = FALSE; // hack due to bug in USER
  1412. switch (code)
  1413. {
  1414. case WB_ISDELIMITER:
  1415. fRight = TRUE;
  1416. // Simple case - is the current character a delimiter?
  1417. iIndex = (int)IsDelimiter(*lpchT);
  1418. break;
  1419. case WB_LEFT:
  1420. // Move to the left to find the first delimiter. If we are
  1421. // currently at a delimiter, then skip delimiters until we
  1422. // find the first non-delimiter, then start from there.
  1423. //
  1424. // Special case for fRight - if we are currently at a delimiter
  1425. // then just return the current word!
  1426. while ((lpchT = CharPrev(lpch, lpchT)) != lpch)
  1427. {
  1428. if (IsDelimiter(*lpchT))
  1429. {
  1430. if (fRight || fFoundNonDelimiter)
  1431. break;
  1432. }
  1433. else
  1434. {
  1435. fFoundNonDelimiter = TRUE;
  1436. fRight = FALSE;
  1437. }
  1438. }
  1439. iIndex = (int) (lpchT - lpch);
  1440. // We are currently pointing at the delimiter, next character
  1441. // is the beginning of the next word.
  1442. if (iIndex > 0 && iIndex < cch)
  1443. iIndex++;
  1444. break;
  1445. case WB_RIGHT:
  1446. fRight = FALSE;
  1447. // If we are not at a delimiter, then skip to the right until
  1448. // we find the first delimiter. If we started at a delimiter, or
  1449. // we have just finished scanning to the first delimiter, then
  1450. // skip all delimiters until we find the first non delimiter.
  1451. //
  1452. // Careful - the string passed in to us may not be NULL terminated!
  1453. fFoundNonDelimiter = !IsDelimiter(*lpchT);
  1454. if (lpchT != (lpch + cch))
  1455. {
  1456. while ((lpchT = FastCharNext(lpchT)) != (lpch + cch))
  1457. {
  1458. if (IsDelimiter(*lpchT))
  1459. {
  1460. fFoundNonDelimiter = FALSE;
  1461. }
  1462. else
  1463. {
  1464. if (!fFoundNonDelimiter)
  1465. break;
  1466. }
  1467. }
  1468. }
  1469. // We are currently pointing at the next word.
  1470. iIndex = (int) (lpchT - lpch);
  1471. break;
  1472. }
  1473. return iIndex;
  1474. }