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

1855 lines
51 KiB

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