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.

1747 lines
51 KiB

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