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.

3810 lines
112 KiB

  1. /***************************************************************************\
  2. *
  3. * LBOXCTL2.C -
  4. *
  5. * Copyright (c) 1985 - 1999, Microsoft Corporation
  6. *
  7. * List box handling routines
  8. *
  9. * 18-Dec-1990 ianja Ported from Win 3.0 sources
  10. * 14-Feb-1991 mikeke Added Revalidation code
  11. \***************************************************************************/
  12. #include "precomp.h"
  13. #pragma hdrstop
  14. #define LB_KEYDOWN WM_USER+1
  15. #define NOMODIFIER 0 /* No modifier is down */
  16. #define SHIFTDOWN 1 /* Shift alone */
  17. #define CTLDOWN 2 /* Ctl alone */
  18. #define SHCTLDOWN (SHIFTDOWN + CTLDOWN) /* Ctrl + Shift */
  19. /*
  20. * Variables for incremental type search support
  21. */
  22. #define MAX_TYPESEARCH 256
  23. BOOL LBGetDC(PLBIV plb);
  24. void LBReleaseDC(PLBIV plb);
  25. /***************************************************************************\
  26. *
  27. * LBInvalidateRect()
  28. *
  29. * If the listbox is visible, invalidates a rectangle in the listbox.
  30. * If the listbox is not visible, sets the defer update flag for the listbox
  31. *
  32. \***************************************************************************/
  33. BOOL xxxLBInvalidateRect(PLBIV plb, LPRECT lprc, BOOL fErase)
  34. {
  35. CheckLock(plb->spwnd);
  36. if (IsLBoxVisible(plb)) {
  37. NtUserInvalidateRect(HWq(plb->spwnd), lprc, fErase);
  38. return(TRUE);
  39. }
  40. if (!plb->fRedraw)
  41. plb->fDeferUpdate = TRUE;
  42. return(FALSE);
  43. }
  44. /***************************************************************************\
  45. *
  46. * LBGetBrush()
  47. *
  48. * Gets background brush & colors for listbox.
  49. *
  50. \***************************************************************************/
  51. HBRUSH xxxLBGetBrush(PLBIV plb, HBRUSH *phbrOld)
  52. {
  53. HBRUSH hbr;
  54. HBRUSH hbrOld;
  55. TL tlpwndParent;
  56. CheckLock(plb->spwnd);
  57. SetBkMode(plb->hdc, OPAQUE);
  58. //
  59. // Get brush & colors
  60. //
  61. if ((plb->spwnd->spwndParent == NULL) ||
  62. (REBASEPWND(plb->spwnd, spwndParent) == _GetDesktopWindow())) {
  63. ThreadLock(plb->spwndParent, &tlpwndParent);
  64. hbr = GetControlColor(HW(plb->spwndParent), HWq(plb->spwnd),
  65. plb->hdc, WM_CTLCOLORLISTBOX);
  66. ThreadUnlock(&tlpwndParent);
  67. } else
  68. hbr = GetControlBrush(HWq(plb->spwnd), plb->hdc, WM_CTLCOLORLISTBOX);
  69. //
  70. // Select brush into dc
  71. //
  72. if (hbr != NULL) {
  73. hbrOld = SelectObject(plb->hdc, hbr);
  74. if (phbrOld)
  75. *phbrOld = hbrOld;
  76. }
  77. return(hbr);
  78. }
  79. /***************************************************************************\
  80. *
  81. * LBInitDC()
  82. *
  83. * Initializes dc for listbox
  84. *
  85. \***************************************************************************/
  86. void LBInitDC(PLBIV plb)
  87. {
  88. RECT rc;
  89. // Set font
  90. if (plb->hFont)
  91. SelectObject(plb->hdc, plb->hFont);
  92. // Set clipping area
  93. _GetClientRect(plb->spwnd, &rc);
  94. IntersectClipRect(plb->hdc, rc.left, rc.top, rc.right, rc.bottom);
  95. OffsetWindowOrgEx(plb->hdc, plb->xOrigin, 0, NULL);
  96. }
  97. /***************************************************************************\
  98. * LBGetDC
  99. *
  100. * Returns a DC which can be used by a list box even if parentDC is in effect
  101. *
  102. * History:
  103. \***************************************************************************/
  104. BOOL LBGetDC(
  105. PLBIV plb)
  106. {
  107. if (plb->hdc)
  108. return(FALSE);
  109. plb->hdc = NtUserGetDC(HWq(plb->spwnd));
  110. LBInitDC(plb);
  111. return TRUE;
  112. }
  113. /***************************************************************************\
  114. *
  115. * LBTermDC()
  116. *
  117. * Cleans up when done with listbox dc.
  118. *
  119. \***************************************************************************/
  120. void LBTermDC(PLBIV plb)
  121. {
  122. if (plb->hFont)
  123. SelectObject(plb->hdc, ghFontSys);
  124. }
  125. /***************************************************************************\
  126. * LBReleaseDC
  127. *
  128. * History:
  129. \***************************************************************************/
  130. void LBReleaseDC(
  131. PLBIV plb)
  132. {
  133. LBTermDC(plb);
  134. NtUserReleaseDC(HWq(plb->spwnd), plb->hdc);
  135. plb->hdc = NULL;
  136. }
  137. /***************************************************************************\
  138. * LBGetItemRect
  139. *
  140. * Return the rectangle that the item will be drawn in with respect to the
  141. * listbox window. Returns TRUE if any portion of the item's rectangle
  142. * is visible (ie. in the listbox client rect) else returns FALSE.
  143. *
  144. * History:
  145. \***************************************************************************/
  146. BOOL LBGetItemRect(
  147. PLBIV plb,
  148. INT sItem,
  149. LPRECT lprc)
  150. {
  151. INT sTmp;
  152. int clientbottom;
  153. /*
  154. * Always allow an item number of 0 so that we can draw the caret which
  155. * indicates the listbox has the focus even though it is empty.
  156. * FreeHand 3.1 passes in -1 as the itemNumber and expects
  157. * a non-null rectangle. So we check for -1 specifically.
  158. * BUGTAG: Fix for Bug #540 --Win95B-- SANKAR -- 2/20/95 --
  159. */
  160. if (sItem && (sItem != -1) && ((UINT)sItem >= (UINT)plb->cMac))
  161. {
  162. SetRectEmpty(lprc);
  163. RIPERR0(ERROR_INVALID_INDEX, RIP_VERBOSE, "");
  164. return (LB_ERR);
  165. }
  166. _GetClientRect(plb->spwnd, lprc);
  167. if (plb->fMultiColumn) {
  168. /*
  169. * itemHeight * sItem mod number ItemsPerColumn (itemsPerColumn)
  170. */
  171. lprc->top = plb->cyChar * (sItem % plb->itemsPerColumn);
  172. lprc->bottom = lprc->top + plb->cyChar /*+(plb->OwnerDraw ? 0 : 1)*/;
  173. UserAssert(plb->itemsPerColumn);
  174. if (plb->fRightAlign) {
  175. lprc->right = lprc->right - plb->cxColumn *
  176. ((sItem / plb->itemsPerColumn) - (plb->iTop / plb->itemsPerColumn));
  177. lprc->left = lprc->right - plb->cxColumn;
  178. } else {
  179. /*
  180. * Remember, this is integer division here...
  181. */
  182. lprc->left += plb->cxColumn *
  183. ((sItem / plb->itemsPerColumn) - (plb->iTop / plb->itemsPerColumn));
  184. lprc->right = lprc->left + plb->cxColumn;
  185. }
  186. } else if (plb->OwnerDraw == OWNERDRAWVAR) {
  187. /*
  188. * Var height owner draw
  189. */
  190. lprc->right += plb->xOrigin;
  191. clientbottom = lprc->bottom;
  192. if (sItem >= plb->iTop) {
  193. for (sTmp = plb->iTop; sTmp < sItem; sTmp++) {
  194. lprc->top = lprc->top + LBGetVariableHeightItemHeight(plb, sTmp);
  195. }
  196. /*
  197. * If item number is 0, it may be we are asking for the rect
  198. * associated with a nonexistant item so that we can draw a caret
  199. * indicating focus on an empty listbox.
  200. */
  201. lprc->bottom = lprc->top + (sItem < plb->cMac ? LBGetVariableHeightItemHeight(plb, sItem) : plb->cyChar);
  202. return (lprc->top < clientbottom);
  203. } else {
  204. /*
  205. * Item we want the rect of is before plb->iTop. Thus, negative
  206. * offsets for the rect and it is never visible.
  207. */
  208. for (sTmp = sItem; sTmp < plb->iTop; sTmp++) {
  209. lprc->top = lprc->top - LBGetVariableHeightItemHeight(plb, sTmp);
  210. }
  211. lprc->bottom = lprc->top + LBGetVariableHeightItemHeight(plb, sItem);
  212. return FALSE;
  213. }
  214. } else {
  215. /*
  216. * For fixed height listboxes
  217. */
  218. if (plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw) && plb->fHorzBar)
  219. lprc->right += plb->xOrigin + (plb->xRightOrigin - plb->xOrigin);
  220. else
  221. lprc->right += plb->xOrigin;
  222. lprc->top = (sItem - plb->iTop) * plb->cyChar;
  223. lprc->bottom = lprc->top + plb->cyChar;
  224. }
  225. return (sItem >= plb->iTop) &&
  226. (sItem < (plb->iTop + CItemInWindow(plb, TRUE)));
  227. }
  228. /***************************************************************************\
  229. *
  230. * LBPrintCallback
  231. *
  232. * Called back from DrawState()
  233. *
  234. \***************************************************************************/
  235. BOOL CALLBACK LBPrintCallback(
  236. HDC hdc,
  237. LPARAM lData,
  238. WPARAM wData,
  239. int cx,
  240. int cy)
  241. {
  242. LPWSTR lpstr = (LPWSTR)lData;
  243. PLBIV plb = (PLBIV)wData;
  244. int xStart;
  245. UINT cLen;
  246. RECT rc;
  247. UINT oldAlign;
  248. if (!lpstr) {
  249. return FALSE;
  250. }
  251. if (plb->fMultiColumn)
  252. xStart = 0;
  253. else
  254. xStart = 2;
  255. if (plb->fRightAlign) {
  256. oldAlign = SetTextAlign(hdc, TA_RIGHT | GetTextAlign(hdc));
  257. xStart = cx - xStart;
  258. }
  259. cLen = wcslen(lpstr);
  260. if (plb->fUseTabStops) {
  261. TabTextOut(hdc, xStart, 0, lpstr, cLen,
  262. (plb->iTabPixelPositions ? plb->iTabPixelPositions[0] : 0),
  263. (plb->iTabPixelPositions ? (LPINT)&plb->iTabPixelPositions[1] : NULL),
  264. plb->fRightAlign ? cx : 0, TRUE, GetTextCharset(plb->hdc));
  265. } else {
  266. rc.left = 0;
  267. rc.top = 0;
  268. rc.right = cx;
  269. rc.bottom = cy;
  270. if (plb->wMultiple)
  271. ExtTextOut(hdc, xStart, 0, ETO_OPAQUE, &rc, lpstr, cLen, NULL);
  272. else if (plb->fMultiColumn)
  273. ExtTextOut(hdc, xStart, 0, ETO_CLIPPED, &rc, lpstr, cLen, NULL);
  274. else {
  275. ExtTextOut(hdc, xStart, 0, 0, NULL, lpstr, cLen, NULL);
  276. /*
  277. * When the listbox is in the incremental search mode and the item
  278. * is highlighted (so we only draw in the current item), draw the
  279. * caret for search indication.
  280. */
  281. if ((plb->iTypeSearch != 0) && (plb->OwnerDraw == 0) &&
  282. (GetBkColor(hdc) == SYSRGB(HIGHLIGHT))) {
  283. SIZE size;
  284. GetTextExtentPointW(hdc, lpstr, plb->iTypeSearch, &size);
  285. PatBlt(hdc, xStart + size.cx - 1, 1, 1, cy - 2, DSTINVERT);
  286. }
  287. }
  288. }
  289. if (plb->fRightAlign)
  290. SetTextAlign(hdc, oldAlign);
  291. return(TRUE);
  292. }
  293. /***************************************************************************\
  294. * xxxLBDrawLBItem
  295. *
  296. * History:
  297. \***************************************************************************/
  298. void xxxLBDrawLBItem(
  299. PLBIV plb,
  300. INT sItem,
  301. LPRECT lprect,
  302. BOOL fHilite,
  303. HBRUSH hbr)
  304. {
  305. LPWSTR lpstr;
  306. DWORD rgbSave;
  307. DWORD rgbBkSave;
  308. UINT uFlags;
  309. HDC hdc = plb->hdc;
  310. UINT oldAlign;
  311. CheckLock(plb->spwnd);
  312. /*
  313. * If the item is selected, then fill with highlight color
  314. */
  315. if (fHilite) {
  316. FillRect(hdc, lprect, SYSHBR(HIGHLIGHT));
  317. rgbBkSave = SetBkColor(hdc, SYSRGB(HIGHLIGHT));
  318. rgbSave = SetTextColor(hdc, SYSRGB(HIGHLIGHTTEXT));
  319. } else {
  320. /*
  321. * If fUseTabStops, we must fill the background, because later we use
  322. * LBTabTheTextOutForWimps(), which fills the background only partially
  323. * Fix for Bug #1509 -- 01/25/91 -- SANKAR --
  324. */
  325. if ((hbr != NULL) && ((sItem == plb->iSelBase) || (plb->fUseTabStops))) {
  326. FillRect(hdc, lprect, hbr);
  327. }
  328. }
  329. uFlags = DST_COMPLEX;
  330. lpstr = GetLpszItem(plb, sItem);
  331. if (TestWF(plb->spwnd, WFDISABLED)) {
  332. if ((COLORREF)SYSRGB(GRAYTEXT) != GetBkColor(hdc))
  333. SetTextColor(hdc, SYSRGB(GRAYTEXT));
  334. else
  335. uFlags |= DSS_UNION;
  336. }
  337. if (plb->fRightAlign)
  338. uFlags |= DSS_RIGHT;
  339. if (plb->fRtoLReading)
  340. oldAlign = SetTextAlign(hdc, TA_RTLREADING | GetTextAlign(hdc));
  341. DrawState(hdc, SYSHBR(WINDOWTEXT),
  342. LBPrintCallback,
  343. (LPARAM)lpstr,
  344. (WPARAM)plb,
  345. lprect->left,
  346. lprect->top,
  347. lprect->right-lprect->left,
  348. lprect->bottom-lprect->top,
  349. uFlags);
  350. if (plb->fRtoLReading)
  351. SetTextAlign(hdc, oldAlign);
  352. if (fHilite) {
  353. SetTextColor(hdc, rgbSave);
  354. SetBkColor(hdc, rgbBkSave);
  355. }
  356. }
  357. /***************************************************************************\
  358. *
  359. * LBSetCaret()
  360. *
  361. \***************************************************************************/
  362. void xxxLBSetCaret(PLBIV plb, BOOL fSetCaret)
  363. {
  364. RECT rc;
  365. BOOL fNewDC;
  366. if (plb->fCaret && ((BOOL) plb->fCaretOn != !!fSetCaret)) {
  367. if (IsLBoxVisible(plb)) {
  368. /* Turn the caret (located at plb->iSelBase) on */
  369. fNewDC = LBGetDC(plb);
  370. LBGetItemRect(plb, plb->iSelBase, &rc);
  371. if (fNewDC) {
  372. SetBkColor(plb->hdc, SYSRGB(WINDOW));
  373. SetTextColor(plb->hdc, SYSRGB(WINDOWTEXT));
  374. }
  375. if (plb->OwnerDraw) {
  376. /* Fill in the drawitem struct */
  377. UINT itemState = (fSetCaret) ? ODS_FOCUS : 0;
  378. if (IsSelected(plb, plb->iSelBase, HILITEONLY))
  379. itemState |= ODS_SELECTED;
  380. xxxLBoxDrawItem(plb, plb->iSelBase, ODA_FOCUS, itemState, &rc);
  381. } else if (!TestWF(plb->spwnd, WEFPUIFOCUSHIDDEN)) {
  382. COLORREF crBk = SetBkColor(plb->hdc, SYSRGB(WINDOW));
  383. COLORREF crText = SetTextColor(plb->hdc, SYSRGB(WINDOWTEXT));
  384. DrawFocusRect(plb->hdc, &rc);
  385. SetBkColor(plb->hdc, crBk);
  386. SetTextColor(plb->hdc, crText);
  387. }
  388. if (fNewDC)
  389. LBReleaseDC(plb);
  390. }
  391. plb->fCaretOn = !!fSetCaret;
  392. }
  393. }
  394. /***************************************************************************\
  395. * IsSelected
  396. *
  397. * History:
  398. * 16-Apr-1992 beng The NODATA listbox case
  399. \***************************************************************************/
  400. BOOL IsSelected(
  401. PLBIV plb,
  402. INT sItem,
  403. UINT wOpFlags)
  404. {
  405. LPBYTE lp;
  406. if ((sItem >= plb->cMac) || (sItem < 0)) {
  407. RIPERR0(ERROR_INVALID_INDEX, RIP_VERBOSE, "");
  408. // return LB_ERR;
  409. return(FALSE);
  410. }
  411. if (plb->wMultiple == SINGLESEL) {
  412. return (sItem == plb->iSel);
  413. }
  414. lp = plb->rgpch + sItem +
  415. (plb->cMac * (plb->fHasStrings
  416. ? sizeof(LBItem)
  417. : (plb->fHasData
  418. ? sizeof(LBODItem)
  419. : 0)));
  420. sItem = *lp;
  421. if (wOpFlags == HILITEONLY) {
  422. sItem >>= 4;
  423. } else {
  424. sItem &= 0x0F; /* SELONLY */
  425. }
  426. return sItem;
  427. }
  428. /***************************************************************************\
  429. * CItemInWindow
  430. *
  431. * Returns the number of items which can fit in a list box. It
  432. * includes the partially visible one at the bottom if fPartial is TRUE. For
  433. * var height ownerdraw, return the number of items visible starting at iTop
  434. * and going to the bottom of the client rect.
  435. *
  436. * History:
  437. \***************************************************************************/
  438. INT CItemInWindow(
  439. PLBIV plb,
  440. BOOL fPartial)
  441. {
  442. RECT rect;
  443. if (plb->OwnerDraw == OWNERDRAWVAR) {
  444. return CItemInWindowVarOwnerDraw(plb, fPartial);
  445. }
  446. if (plb->fMultiColumn) {
  447. return plb->itemsPerColumn * (plb->numberOfColumns + (fPartial ? 1 : 0));
  448. }
  449. _GetClientRect(plb->spwnd, &rect);
  450. /*
  451. * fPartial must be considered only if the listbox height is not an
  452. * integral multiple of character height.
  453. * A part of the fix for Bug #3727 -- 01/14/91 -- SANKAR --
  454. */
  455. UserAssert(plb->cyChar);
  456. return (INT)((rect.bottom / plb->cyChar) +
  457. ((rect.bottom % plb->cyChar)? (fPartial ? 1 : 0) : 0));
  458. }
  459. /***************************************************************************\
  460. * xxxLBoxCtlScroll
  461. *
  462. * Handles vertical scrolling of the listbox
  463. *
  464. * History:
  465. \***************************************************************************/
  466. void xxxLBoxCtlScroll(
  467. PLBIV plb,
  468. INT cmd,
  469. int yAmt)
  470. {
  471. INT iTopNew;
  472. INT cItemPageScroll;
  473. DWORD dwTime = 0;
  474. CheckLock(plb->spwnd);
  475. if (plb->fMultiColumn) {
  476. /*
  477. * Don't allow vertical scrolling on a multicolumn list box. Needed
  478. * in case app sends WM_VSCROLL messages to the listbox.
  479. */
  480. return;
  481. }
  482. cItemPageScroll = plb->cItemFullMax;
  483. if (cItemPageScroll > 1)
  484. cItemPageScroll--;
  485. if (plb->cMac) {
  486. iTopNew = plb->iTop;
  487. switch (cmd) {
  488. case SB_LINEUP:
  489. dwTime = yAmt;
  490. iTopNew--;
  491. break;
  492. case SB_LINEDOWN:
  493. dwTime = yAmt;
  494. iTopNew++;
  495. break;
  496. case SB_PAGEUP:
  497. if (plb->OwnerDraw == OWNERDRAWVAR) {
  498. iTopNew = LBPage(plb, plb->iTop, FALSE);
  499. } else {
  500. iTopNew -= cItemPageScroll;
  501. }
  502. break;
  503. case SB_PAGEDOWN:
  504. if (plb->OwnerDraw == OWNERDRAWVAR) {
  505. iTopNew = LBPage(plb, plb->iTop, TRUE);
  506. } else {
  507. iTopNew += cItemPageScroll;
  508. }
  509. break;
  510. case SB_THUMBTRACK:
  511. case SB_THUMBPOSITION: {
  512. /*
  513. * If the listbox contains more than 0xFFFF items
  514. * it means that the scrolbar can return a position
  515. * that cannot fit in a WORD (16 bits), so use
  516. * GetScrollInfo (which is slower) in this case.
  517. */
  518. if (plb->cMac < 0xFFFF) {
  519. iTopNew = yAmt;
  520. } else {
  521. SCROLLINFO si;
  522. si.cbSize = sizeof(SCROLLINFO);
  523. si.fMask = SIF_TRACKPOS;
  524. GetScrollInfo( HWq(plb->spwnd), SB_VERT, &si);
  525. iTopNew = si.nTrackPos;
  526. }
  527. break;
  528. }
  529. case SB_TOP:
  530. iTopNew = 0;
  531. break;
  532. case SB_BOTTOM:
  533. iTopNew = plb->cMac - 1;
  534. break;
  535. case SB_ENDSCROLL:
  536. plb->fSmoothScroll = TRUE;
  537. xxxLBSetCaret(plb, FALSE);
  538. xxxLBShowHideScrollBars(plb);
  539. xxxLBSetCaret(plb, TRUE);
  540. return;
  541. }
  542. xxxNewITopEx(plb, iTopNew, dwTime);
  543. }
  544. }
  545. /***************************************************************************\
  546. * LBGetScrollFlags
  547. *
  548. \***************************************************************************/
  549. DWORD LBGetScrollFlags(PLBIV plb, DWORD dwTime)
  550. {
  551. DWORD dwFlags;
  552. if (GetAppCompatFlags(NULL) & GACF_NOSMOOTHSCROLLING)
  553. goto NoSmoothScrolling;
  554. if (dwTime != 0) {
  555. dwFlags = MAKELONG(SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN, dwTime);
  556. } else if (TEST_EffectPUSIF(PUSIF_LISTBOXSMOOTHSCROLLING) && plb->fSmoothScroll) {
  557. dwFlags = SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN;
  558. plb->fSmoothScroll = FALSE;
  559. } else {
  560. NoSmoothScrolling:
  561. dwFlags = SW_SCROLLWINDOW | SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN;
  562. }
  563. return dwFlags;
  564. }
  565. /***************************************************************************\
  566. * xxxLBoxCtlHScroll
  567. *
  568. * Supports horizontal scrolling of listboxes
  569. *
  570. * History:
  571. \***************************************************************************/
  572. void xxxLBoxCtlHScroll(
  573. PLBIV plb,
  574. INT cmd,
  575. int xAmt)
  576. {
  577. int newOrigin = plb->xOrigin;
  578. int oldOrigin = plb->xOrigin;
  579. int windowWidth;
  580. RECT rc;
  581. DWORD dwTime = 0;
  582. CheckLock(plb->spwnd);
  583. /*
  584. * Update the window so that we don't run into problems with invalid
  585. * regions during the horizontal scroll.
  586. */
  587. if (plb->fMultiColumn) {
  588. /*
  589. * Handle multicolumn scrolling in a separate segment
  590. */
  591. xxxLBoxCtlHScrollMultiColumn(plb, cmd, xAmt);
  592. return;
  593. }
  594. _GetClientRect(plb->spwnd, &rc);
  595. windowWidth = rc.right;
  596. if (plb->cMac) {
  597. switch (cmd) {
  598. case SB_LINEUP:
  599. dwTime = xAmt;
  600. newOrigin -= plb->cxChar;
  601. break;
  602. case SB_LINEDOWN:
  603. dwTime = xAmt;
  604. newOrigin += plb->cxChar;
  605. break;
  606. case SB_PAGEUP:
  607. newOrigin -= (windowWidth / 3) * 2;
  608. break;
  609. case SB_PAGEDOWN:
  610. newOrigin += (windowWidth / 3) * 2;
  611. break;
  612. case SB_THUMBTRACK:
  613. case SB_THUMBPOSITION:
  614. newOrigin = xAmt;
  615. break;
  616. case SB_TOP:
  617. newOrigin = 0;
  618. break;
  619. case SB_BOTTOM:
  620. newOrigin = plb->maxWidth;
  621. break;
  622. case SB_ENDSCROLL:
  623. plb->fSmoothScroll = TRUE;
  624. xxxLBSetCaret(plb, FALSE);
  625. xxxLBShowHideScrollBars(plb);
  626. xxxLBSetCaret(plb, TRUE);
  627. return;
  628. }
  629. xxxLBSetCaret(plb, FALSE);
  630. plb->xOrigin = newOrigin;
  631. plb->xOrigin = xxxSetLBScrollParms(plb, SB_HORZ);
  632. if ((cmd == SB_BOTTOM) && plb->fRightAlign) {
  633. /*
  634. * so we know where to draw from.
  635. */
  636. plb->xRightOrigin = plb->xOrigin;
  637. }
  638. if(oldOrigin != plb->xOrigin) {
  639. HWND hwnd = HWq(plb->spwnd);
  640. DWORD dwFlags;
  641. dwFlags = LBGetScrollFlags(plb, dwTime);
  642. ScrollWindowEx(hwnd, oldOrigin-plb->xOrigin,
  643. 0, NULL, &rc, NULL, NULL, dwFlags);
  644. UpdateWindow(hwnd);
  645. }
  646. xxxLBSetCaret(plb, TRUE);
  647. } else {
  648. // this is a less-than-ideal fix for ImageMind ScreenSaver (Win95
  649. // B#8252) but it works and it doesn't hurt anybody -- JEFFBOG 10/28/94
  650. xxxSetLBScrollParms(plb, SB_HORZ);
  651. }
  652. }
  653. /***************************************************************************\
  654. * xxxLBoxCtlPaint
  655. *
  656. * History:
  657. \***************************************************************************/
  658. void xxxLBPaint(
  659. PLBIV plb,
  660. HDC hdc,
  661. LPRECT lprcBounds)
  662. {
  663. INT i;
  664. RECT rect;
  665. RECT scratchRect;
  666. BOOL fHilite;
  667. INT iLastItem;
  668. HBRUSH hbrSave = NULL;
  669. HBRUSH hbrControl;
  670. BOOL fCaretOn;
  671. RECT rcBounds;
  672. HDC hdcSave;
  673. CheckLock(plb->spwnd);
  674. if (lprcBounds == NULL) {
  675. lprcBounds = &rcBounds;
  676. _GetClientRect(plb->spwnd, lprcBounds);
  677. }
  678. hdcSave = plb->hdc;
  679. plb->hdc = hdc;
  680. // Initialize dc.
  681. LBInitDC(plb);
  682. // Turn caret off
  683. if (fCaretOn = plb->fCaretOn)
  684. xxxLBSetCaret(plb, FALSE);
  685. hbrSave = NULL;
  686. hbrControl = xxxLBGetBrush(plb, &hbrSave);
  687. // Get listbox's client
  688. _GetClientRect(plb->spwnd, &rect);
  689. // Adjust width of client rect for scrolled amount
  690. // fix for #140, t-arthb
  691. if (plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw) && plb->fHorzBar)
  692. rect.right += plb->xOrigin + (plb->xRightOrigin - plb->xOrigin);
  693. else
  694. rect.right += plb->xOrigin;
  695. // Get the index of the last item visible on the screen. This is also
  696. // valid for var height ownerdraw.
  697. iLastItem = plb->iTop + CItemInWindow(plb,TRUE);
  698. iLastItem = min(iLastItem, plb->cMac - 1);
  699. // Fill in the background of the listbox if it's an empty listbox
  700. // or if we're doing a control print
  701. if (iLastItem == -1)
  702. FillRect(plb->hdc, &rect, hbrControl);
  703. // Allow AnimateWindow() catch the apps that do not use our DC when
  704. // drawing the list box
  705. SetBoundsRect(plb->hdc, NULL, DCB_RESET | DCB_ENABLE);
  706. for (i = plb->iTop; i <= iLastItem; i++) {
  707. /*
  708. * Note that rect contains the clientrect from when we did the
  709. * _GetClientRect so the width is correct. We just need to adjust
  710. * the top and bottom of the rectangle to the item of interest.
  711. */
  712. rect.bottom = rect.top + plb->cyChar;
  713. if ((UINT)i < (UINT)plb->cMac) {
  714. /*
  715. * If var height, get the rectangle for the item.
  716. */
  717. if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn) {
  718. LBGetItemRect(plb, i, &rect);
  719. }
  720. if (IntersectRect(&scratchRect, lprcBounds, &rect)) {
  721. fHilite = !plb->fNoSel && IsSelected(plb, i, HILITEONLY);
  722. if (plb->OwnerDraw) {
  723. /*
  724. * Fill in the drawitem struct
  725. */
  726. xxxLBoxDrawItem(plb, i, ODA_DRAWENTIRE,
  727. (UINT)(fHilite ? ODS_SELECTED : 0), &rect);
  728. } else {
  729. xxxLBDrawLBItem(plb, i, &rect, fHilite, hbrControl);
  730. }
  731. }
  732. }
  733. rect.top = rect.bottom;
  734. }
  735. if (hbrSave != NULL)
  736. SelectObject(hdc, hbrSave);
  737. if (fCaretOn)
  738. xxxLBSetCaret(plb, TRUE);
  739. LBTermDC(plb);
  740. plb->hdc = hdcSave;
  741. }
  742. /***************************************************************************\
  743. * ISelFromPt
  744. *
  745. * In the loword, returns the closest item number the pt is on. The high
  746. * word is 0 if the point is within bounds of the listbox client rect and is
  747. * 1 if it is outside the bounds. This will allow us to make the invertrect
  748. * disappear if the mouse is outside the listbox yet we can still show the
  749. * outline around the item that would be selected if the mouse is brought back
  750. * in bounds...
  751. *
  752. * History:
  753. \***************************************************************************/
  754. BOOL ISelFromPt(
  755. PLBIV plb,
  756. POINT pt,
  757. LPDWORD piItem)
  758. {
  759. RECT rect;
  760. int y;
  761. UINT mouseHighWord = 0;
  762. INT sItem;
  763. INT sTmp;
  764. _GetClientRect(plb->spwnd, &rect);
  765. if (pt.y < 0) {
  766. /*
  767. * Mouse is out of bounds above listbox
  768. */
  769. *piItem = plb->iTop;
  770. return TRUE;
  771. } else if ((y = pt.y) > rect.bottom) {
  772. y = rect.bottom;
  773. mouseHighWord = 1;
  774. }
  775. if (pt.x < 0 || pt.x > rect.right)
  776. mouseHighWord = 1;
  777. /*
  778. * Now just need to check if y mouse coordinate intersects item's rectangle
  779. */
  780. if (plb->OwnerDraw != OWNERDRAWVAR) {
  781. if (plb->fMultiColumn) {
  782. if (y < plb->itemsPerColumn * plb->cyChar) {
  783. if (plb->fRightAlign)
  784. sItem = plb->iTop + (INT)((y / plb->cyChar) +
  785. ((rect.right - pt.x) / plb->cxColumn) * plb->itemsPerColumn);
  786. else
  787. sItem = plb->iTop + (INT)((y / plb->cyChar) +
  788. (pt.x / plb->cxColumn) * plb->itemsPerColumn);
  789. } else {
  790. /*
  791. * User clicked in blank space at the bottom of a column.
  792. * Just select the last item in the column.
  793. */
  794. mouseHighWord = 1;
  795. sItem = plb->iTop + (plb->itemsPerColumn - 1) +
  796. (INT)((pt.x / plb->cxColumn) * plb->itemsPerColumn);
  797. }
  798. } else {
  799. sItem = plb->iTop + (INT)(y / plb->cyChar);
  800. }
  801. } else {
  802. /*
  803. * VarHeightOwnerdraw so we gotta do this the hardway... Set the x
  804. * coordinate of the mouse down point to be inside the listbox client
  805. * rectangle since we no longer care about it. This lets us use the
  806. * point in rect calls.
  807. */
  808. pt.x = 8;
  809. pt.y = y;
  810. for (sTmp = plb->iTop; sTmp < plb->cMac; sTmp++) {
  811. (void)LBGetItemRect(plb, sTmp, &rect);
  812. if (PtInRect(&rect, pt)) {
  813. *piItem = sTmp;
  814. return mouseHighWord;
  815. }
  816. }
  817. /*
  818. * Point was at the empty area at the bottom of a not full listbox
  819. */
  820. *piItem = plb->cMac - 1;
  821. return mouseHighWord;
  822. }
  823. /*
  824. * Check if user clicked on the blank area at the bottom of a not full list.
  825. * Assumes > 0 items in the listbox.
  826. */
  827. if (sItem > plb->cMac - 1) {
  828. mouseHighWord = 1;
  829. sItem = plb->cMac - 1;
  830. }
  831. *piItem = sItem;
  832. return mouseHighWord;
  833. }
  834. /***************************************************************************\
  835. * SetSelected
  836. *
  837. * This is used for button initiated changes of selection state.
  838. *
  839. * fSelected : TRUE if the item is to be set as selected, FALSE otherwise
  840. *
  841. * wOpFlags : HILITEONLY = Modify only the Display state (hi-nibble)
  842. * SELONLY = Modify only the Selection state (lo-nibble)
  843. * HILITEANDSEL = Modify both of them;
  844. *
  845. * History:
  846. * 16-Apr-1992 beng The NODATA listbox case
  847. \***************************************************************************/
  848. void SetSelected(
  849. PLBIV plb,
  850. INT iSel,
  851. BOOL fSelected,
  852. UINT wOpFlags)
  853. {
  854. LPSTR lp;
  855. BYTE cMask;
  856. BYTE cSelStatus;
  857. if (iSel < 0 || iSel >= plb->cMac)
  858. return;
  859. if (plb->wMultiple == SINGLESEL) {
  860. if (fSelected)
  861. plb->iSel = iSel;
  862. } else {
  863. cSelStatus = (BYTE)fSelected;
  864. switch (wOpFlags) {
  865. case HILITEONLY:
  866. /*
  867. * Mask out lo-nibble
  868. */
  869. cSelStatus = (BYTE)(cSelStatus << 4);
  870. cMask = 0x0F;
  871. break;
  872. case SELONLY:
  873. /*
  874. * Mask out hi-nibble
  875. */
  876. cMask = 0xF0;
  877. break;
  878. case HILITEANDSEL:
  879. /*
  880. * Mask the byte fully
  881. */
  882. cSelStatus |= (cSelStatus << 4);
  883. cMask = 0;
  884. break;
  885. }
  886. lp = (LPSTR)(plb->rgpch) + iSel +
  887. (plb->cMac * (plb->fHasStrings
  888. ? sizeof(LBItem)
  889. : (plb->fHasData ? sizeof(LBODItem) : 0)));
  890. *lp = (*lp & cMask) | cSelStatus;
  891. }
  892. }
  893. /***************************************************************************\
  894. * LastFullVisible
  895. *
  896. * Returns the last fully visible item in the listbox. This is valid
  897. * for ownerdraw var height and fixed height listboxes.
  898. *
  899. * History:
  900. \***************************************************************************/
  901. INT LastFullVisible(
  902. PLBIV plb)
  903. {
  904. INT iLastItem;
  905. if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn) {
  906. iLastItem = plb->iTop + CItemInWindow(plb, FALSE) - 1;
  907. iLastItem = max(iLastItem, plb->iTop);
  908. } else {
  909. iLastItem = min(plb->iTop + plb->cItemFullMax - 1, plb->cMac - 1);
  910. }
  911. return iLastItem;
  912. }
  913. /***************************************************************************\
  914. * xxxInvertLBItem
  915. *
  916. * History:
  917. \***************************************************************************/
  918. void xxxInvertLBItem(
  919. PLBIV plb,
  920. INT i,
  921. BOOL fHilite) /* The new selection state of the item */
  922. {
  923. RECT rect;
  924. BOOL fCaretOn;
  925. HBRUSH hbrControl;
  926. BOOL fNewDC;
  927. CheckLock(plb->spwnd);
  928. // Skip if item isn't showing.
  929. if (plb->fNoSel || (i < plb->iTop) || (i >= (plb->iTop + CItemInWindow(plb, TRUE))))
  930. return;
  931. if (IsLBoxVisible(plb)) {
  932. LBGetItemRect(plb, i, &rect);
  933. /*
  934. * Only turn off the caret if it is on. This avoids annoying caret
  935. * flicker when nesting xxxCaretOns and xxxCaretOffs.
  936. */
  937. if (fCaretOn = plb->fCaretOn) {
  938. xxxLBSetCaret(plb, FALSE);
  939. }
  940. fNewDC = LBGetDC(plb);
  941. hbrControl = xxxLBGetBrush(plb, NULL);
  942. if (!plb->OwnerDraw) {
  943. if (!fHilite) {
  944. FillRect(plb->hdc, &rect, hbrControl);
  945. hbrControl = NULL;
  946. }
  947. xxxLBDrawLBItem(plb, i, &rect, fHilite, hbrControl);
  948. } else {
  949. /*
  950. * We are ownerdraw so fill in the drawitem struct and send off
  951. * to the owner.
  952. */
  953. xxxLBoxDrawItem(plb, i, ODA_SELECT,
  954. (UINT)(fHilite ? ODS_SELECTED : 0), &rect);
  955. }
  956. if (fNewDC)
  957. LBReleaseDC(plb);
  958. /*
  959. * Turn the caret back on only if it was originally on.
  960. */
  961. if (fCaretOn) {
  962. xxxLBSetCaret(plb, TRUE);
  963. }
  964. }
  965. }
  966. /***************************************************************************\
  967. * xxxResetWorld
  968. *
  969. * Resets everyone's selection and hilite state except items in the
  970. * range sStItem to sEndItem (Both inclusive).
  971. *
  972. * History:
  973. \***************************************************************************/
  974. void xxxResetWorld(
  975. PLBIV plb,
  976. INT iStart,
  977. INT iEnd,
  978. BOOL fSelect)
  979. {
  980. INT i;
  981. INT iLastInWindow;
  982. BOOL fCaretOn;
  983. CheckLock(plb->spwnd);
  984. /*
  985. * If iStart and iEnd are not in correct order we swap them
  986. */
  987. if (iStart > iEnd) {
  988. i = iStart;
  989. iStart = iEnd;
  990. iEnd = i;
  991. }
  992. if (plb->wMultiple == SINGLESEL) {
  993. if (plb->iSel != -1 && ((plb->iSel < iStart) || (plb->iSel > iEnd))) {
  994. xxxInvertLBItem(plb, plb->iSel, fSelect);
  995. plb->iSel = -1;
  996. }
  997. return;
  998. }
  999. iLastInWindow = plb->iTop + CItemInWindow(plb, TRUE);
  1000. if (fCaretOn = plb->fCaretOn)
  1001. xxxLBSetCaret(plb, FALSE);
  1002. for (i = 0; i < plb->cMac; i++) {
  1003. if (i == iStart)
  1004. // skip range to be preserved
  1005. i = iEnd;
  1006. else {
  1007. if ((plb->iTop <= i) && (i <= iLastInWindow) &&
  1008. (fSelect != IsSelected(plb, i, HILITEONLY)))
  1009. // Only invert the item if it is visible and present Selection
  1010. // state is different from what is required.
  1011. xxxInvertLBItem(plb, i, fSelect);
  1012. // Set all items outside of preserved range to unselected
  1013. SetSelected(plb, i, fSelect, HILITEANDSEL);
  1014. }
  1015. }
  1016. if (fCaretOn)
  1017. xxxLBSetCaret(plb, TRUE);
  1018. }
  1019. /***************************************************************************\
  1020. * xxxNotifyOwner
  1021. *
  1022. * History:
  1023. \***************************************************************************/
  1024. void xxxNotifyOwner(
  1025. PLBIV plb,
  1026. INT sEvt)
  1027. {
  1028. TL tlpwndParent;
  1029. CheckLock(plb->spwnd);
  1030. ThreadLock(plb->spwndParent, &tlpwndParent);
  1031. SendMessage(HW(plb->spwndParent), WM_COMMAND,
  1032. MAKELONG(PTR_TO_ID(plb->spwnd->spmenu), sEvt), (LPARAM)HWq(plb->spwnd));
  1033. ThreadUnlock(&tlpwndParent);
  1034. }
  1035. /***************************************************************************\
  1036. * xxxSetISelBase
  1037. *
  1038. * History:
  1039. \***************************************************************************/
  1040. void xxxSetISelBase(
  1041. PLBIV plb,
  1042. INT sItem)
  1043. {
  1044. CheckLock(plb->spwnd);
  1045. xxxLBSetCaret(plb, FALSE);
  1046. plb->iSelBase = sItem;
  1047. xxxLBSetCaret(plb, TRUE);
  1048. xxxInsureVisible(plb, plb->iSelBase, FALSE);
  1049. /*
  1050. * We need to send this event even if the listbox isn't visible. See
  1051. * bug #88548. Also see 355612.
  1052. */
  1053. if (_IsWindowVisible(plb->spwnd) || (GetFocus() == HWq(plb->spwnd))) {
  1054. LBEvent(plb, EVENT_OBJECT_FOCUS, sItem);
  1055. }
  1056. }
  1057. /***************************************************************************\
  1058. * xxxTrackMouse
  1059. *
  1060. * History:
  1061. \***************************************************************************/
  1062. void xxxTrackMouse(
  1063. PLBIV plb,
  1064. UINT wMsg,
  1065. POINT pt)
  1066. {
  1067. INT iSelFromPt;
  1068. INT iSelTemp;
  1069. BOOL mousetemp;
  1070. BOOL fMouseInRect;
  1071. RECT rcClient;
  1072. UINT wModifiers = 0;
  1073. BOOL fSelected;
  1074. UINT uEvent = 0;
  1075. INT trackPtRetn;
  1076. HWND hwnd = HWq(plb->spwnd);
  1077. TL tlpwndEdit;
  1078. TL tlpwndParent;
  1079. CheckLock(plb->spwnd);
  1080. /*
  1081. * Optimization: do nothing if mouse not captured
  1082. */
  1083. if ((wMsg != WM_LBUTTONDOWN) && (wMsg != WM_LBUTTONDBLCLK)) {
  1084. if (!plb->fCaptured) {
  1085. return;
  1086. }
  1087. /*
  1088. * If we are processing a WM_MOUSEMOVE but the mouse has not moved
  1089. * from the previous point, then we may be dealing with a mouse
  1090. * "jiggle" sent from the kernel (see zzzInvalidateDCCache). If we
  1091. * process this, we will snap the listbox selection back to where
  1092. * the mouse cursor is pointing, even if the user has not touched
  1093. * the mouse. Windows NT Bug #220722.
  1094. *
  1095. * Some apps (like MSMoney98) rely on this, so we need to check the
  1096. * SRVIF_LASTRITWASKEYBOARD flag. Windows NT Bug #244450.
  1097. */
  1098. if (wMsg == WM_MOUSEMOVE && RtlEqualMemory(&pt, &(plb->ptPrev), sizeof(POINT))
  1099. && TEST_SRVIF(SRVIF_LASTRITWASKEYBOARD)) {
  1100. RIPMSG0(RIP_WARNING, "xxxTrackMouse ignoring WM_MOUSEMOVE with no mouse movement");
  1101. return;
  1102. }
  1103. }
  1104. mousetemp = ISelFromPt(plb, pt, &iSelFromPt);
  1105. /*
  1106. * If we allow the user to cancel his selection then fMouseInRect is true if
  1107. * the mouse is in the listbox client area otherwise it is false. If we
  1108. * don't allow the user to cancel his selection, then fMouseInRect will
  1109. * always be true. This allows us to implement cancelable selection
  1110. * listboxes ie. The selection reverts to the origional one if the user
  1111. * releases the mouse outside of the listbox.
  1112. */
  1113. fMouseInRect = !mousetemp || !plb->pcbox;
  1114. _GetClientRect(plb->spwnd, &rcClient);
  1115. switch (wMsg) {
  1116. case WM_LBUTTONDBLCLK:
  1117. case WM_LBUTTONDOWN:
  1118. /*
  1119. * We want to divert mouse clicks. If the user clicks outside
  1120. * of a dropped down listbox, we want to popup it up, using
  1121. * the current selection.
  1122. */
  1123. if (plb->fCaptured) {
  1124. /*
  1125. * If plb->pcbox is NULL, this is a listbox that
  1126. * received a WM_LBUTTONDOWN again w/o receiving
  1127. * a WM_LBUTTONUP for the previous WM_LBUTTONDOWN
  1128. * bug
  1129. */
  1130. if (plb->pcbox && mousetemp) {
  1131. _ClientToScreen(plb->spwnd, &pt);
  1132. if (!PtInRect(KPRECT_TO_PRECT(&plb->spwnd->rcWindow), pt)) {
  1133. /*
  1134. * Cancel selection if clicked outside of combo;
  1135. * Accept if clicked on combo button or item.
  1136. */
  1137. xxxCBHideListBoxWindow(plb->pcbox, TRUE, FALSE);
  1138. } else if (!PtInRect(KPRECT_TO_PRECT(&plb->spwnd->rcClient), pt)) {
  1139. /*
  1140. * Let it pass through. Save, restore capture in
  1141. * case user is clicking on scrollbar.
  1142. */
  1143. plb->fCaptured = FALSE;
  1144. NtUserReleaseCapture();
  1145. SendMessageWorker(plb->spwnd, WM_NCLBUTTONDOWN,
  1146. FindNCHit(plb->spwnd, POINTTOPOINTS(pt)),
  1147. MAKELONG(pt.x, pt.y), FALSE);
  1148. NtUserSetCapture(hwnd);
  1149. plb->fCaptured = TRUE;
  1150. }
  1151. break;
  1152. }
  1153. plb->fCaptured = FALSE;
  1154. NtUserReleaseCapture();
  1155. }
  1156. if (plb->pcbox) {
  1157. /*
  1158. * If this listbox is in a combo box, set the focus to the combo
  1159. * box window so that the edit control/static text is also
  1160. * activated
  1161. */
  1162. ThreadLock(plb->pcbox->spwndEdit, &tlpwndEdit);
  1163. NtUserSetFocus(HWq(plb->pcbox->spwndEdit));
  1164. ThreadUnlock(&tlpwndEdit);
  1165. } else {
  1166. /*
  1167. * Get the focus if the listbox is clicked in and we don't
  1168. * already have the focus. If we don't have the focus after
  1169. * this, run away...
  1170. */
  1171. NtUserSetFocus(hwnd);
  1172. if (!plb->fCaret)
  1173. return;
  1174. }
  1175. if (plb->fAddSelMode) {
  1176. /*
  1177. * If it is in "Add" mode, quit it using shift f8 key...
  1178. * However, since we can't send shift key state, we have to turn
  1179. * this off directly...
  1180. */
  1181. /*
  1182. *SendMessage(HW(plb->spwnd),WM_KEYDOWN, (UINT)VK_F8, 0L);
  1183. */
  1184. /*
  1185. * Switch off the Caret blinking
  1186. */
  1187. NtUserKillTimer(hwnd, IDSYS_CARET);
  1188. /*
  1189. * Make sure the caret does not vanish
  1190. */
  1191. xxxLBSetCaret(plb, TRUE);
  1192. plb->fAddSelMode = FALSE;
  1193. }
  1194. if (!plb->cMac) {
  1195. /*
  1196. * Don't even bother handling the mouse if no items in the
  1197. * listbox since the code below assumes >0 items in the
  1198. * listbox. We will just get the focus (the statement above) if
  1199. * we don't already have it.
  1200. */
  1201. break;
  1202. }
  1203. if (mousetemp) {
  1204. /*
  1205. * Mouse down occurred in a empty spot. Just ignore it.
  1206. */
  1207. break;
  1208. }
  1209. plb->fDoubleClick = (wMsg == WM_LBUTTONDBLCLK);
  1210. if (!plb->fDoubleClick) {
  1211. /*
  1212. * This hack put in for the shell. Tell the shell where in the
  1213. * listbox the user clicked and at what item number. The shell
  1214. * can return 0 to continue normal mouse tracking or TRUE to
  1215. * abort mouse tracking.
  1216. */
  1217. ThreadLock(plb->spwndParent, &tlpwndParent);
  1218. trackPtRetn = (INT)SendMessage(HW(plb->spwndParent), WM_LBTRACKPOINT,
  1219. (DWORD)iSelFromPt, MAKELONG(pt.x+plb->xOrigin, pt.y));
  1220. ThreadUnlock(&tlpwndParent);
  1221. if (trackPtRetn) {
  1222. if (trackPtRetn == 2) {
  1223. /*
  1224. * Ignore double clicks
  1225. */
  1226. NtUserCallNoParam(SFI__RESETDBLCLK);
  1227. }
  1228. return;
  1229. }
  1230. }
  1231. if (plb->pcbox) {
  1232. /*
  1233. * Save the last selection if this is a combo box. So that it
  1234. * can be restored if user decides to cancel the selection by up
  1235. * clicking outside the listbox.
  1236. */
  1237. plb->iLastSelection = plb->iSel;
  1238. }
  1239. /*
  1240. * Save for timer
  1241. */
  1242. plb->ptPrev = pt;
  1243. plb->fMouseDown = TRUE;
  1244. NtUserSetCapture(hwnd);
  1245. plb->fCaptured = TRUE;
  1246. if (plb->fDoubleClick) {
  1247. /*
  1248. * Double click. Fake a button up and exit
  1249. */
  1250. xxxTrackMouse(plb, WM_LBUTTONUP, pt);
  1251. return;
  1252. }
  1253. /*
  1254. * Set the system timer so that we can autoscroll if the mouse is
  1255. * outside the bounds of the listbox rectangle
  1256. */
  1257. NtUserSetTimer(hwnd, IDSYS_SCROLL, gpsi->dtScroll, NULL);
  1258. /*
  1259. * If extended multiselection listbox, are any modifier key pressed?
  1260. */
  1261. if (plb->wMultiple == EXTENDEDSEL) {
  1262. if (GetKeyState(VK_SHIFT) < 0)
  1263. wModifiers = SHIFTDOWN;
  1264. if (GetKeyState(VK_CONTROL) < 0)
  1265. wModifiers += CTLDOWN;
  1266. /*
  1267. * Please Note that (SHIFTDOWN + CTLDOWN) == (SHCTLDOWN)
  1268. */
  1269. }
  1270. switch (wModifiers) {
  1271. case NOMODIFIER:
  1272. MouseMoveHandler:
  1273. if (plb->iSelBase != iSelFromPt) {
  1274. xxxLBSetCaret(plb, FALSE);
  1275. }
  1276. /*
  1277. * We only look at the mouse if the point it is pointing to is
  1278. * not selected. Since we are not in ExtendedSelMode, anywhere
  1279. * the mouse points, we have to set the selection to that item.
  1280. * Hence, if the item isn't selected, it means the mouse never
  1281. * pointed to it before so we can select it. We ignore already
  1282. * selected items so that we avoid flashing the inverted
  1283. * selection rectangle. Also, we could get WM_SYSTIMER simulated
  1284. * mouse moves which would cause flashing otherwise...
  1285. */
  1286. iSelTemp = (fMouseInRect ? iSelFromPt : -1);
  1287. /*
  1288. * If the LB is either SingleSel or Extended multisel, clear all
  1289. * old selections except the new one being made.
  1290. */
  1291. if (plb->wMultiple != MULTIPLESEL) {
  1292. xxxResetWorld(plb, iSelTemp, iSelTemp, FALSE);
  1293. /*
  1294. * This will be TRUE if iSelTemp isn't -1 (like below)
  1295. * and also if it is but there is a current selection.
  1296. */
  1297. if ((iSelTemp == -1) && (plb->iSel != -1)) {
  1298. uEvent = EVENT_OBJECT_SELECTIONREMOVE;
  1299. }
  1300. }
  1301. fSelected = IsSelected(plb, iSelTemp, HILITEONLY);
  1302. if (iSelTemp != -1) {
  1303. /*
  1304. * If it is MULTIPLESEL, then toggle; For others, only if
  1305. * not selected already, select it.
  1306. */
  1307. if (((plb->wMultiple == MULTIPLESEL) && (wMsg != WM_LBUTTONDBLCLK)) || !fSelected) {
  1308. SetSelected(plb, iSelTemp, !fSelected, HILITEANDSEL);
  1309. /*
  1310. * And invert it
  1311. */
  1312. xxxInvertLBItem(plb, iSelTemp, !fSelected);
  1313. fSelected = !fSelected; /* Set the new state */
  1314. if (plb->wMultiple == MULTIPLESEL) {
  1315. uEvent = (fSelected ? EVENT_OBJECT_SELECTIONADD :
  1316. EVENT_OBJECT_SELECTIONREMOVE);
  1317. } else {
  1318. uEvent = EVENT_OBJECT_SELECTION;
  1319. }
  1320. }
  1321. }
  1322. /*
  1323. * We have to set iSel in case this is a multisel lb.
  1324. */
  1325. plb->iSel = iSelTemp;
  1326. /*
  1327. * Set the new anchor point
  1328. */
  1329. plb->iMouseDown = iSelFromPt;
  1330. plb->iLastMouseMove = iSelFromPt;
  1331. plb->fNewItemState = fSelected;
  1332. break;
  1333. case SHIFTDOWN:
  1334. /*
  1335. * This is so that we can handle click and drag for multisel
  1336. * listboxes using Shift modifier key .
  1337. */
  1338. plb->iLastMouseMove = plb->iSel = iSelFromPt;
  1339. /*
  1340. * Check if an anchor point already exists
  1341. */
  1342. if (plb->iMouseDown == -1) {
  1343. plb->iMouseDown = iSelFromPt;
  1344. /*
  1345. * Reset all the previous selections
  1346. */
  1347. xxxResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
  1348. /*
  1349. * Select the current position
  1350. */
  1351. SetSelected(plb, plb->iMouseDown, TRUE, HILITEANDSEL);
  1352. xxxInvertLBItem(plb, plb->iMouseDown, TRUE);
  1353. /*
  1354. * We are changing the selction to this item only
  1355. */
  1356. uEvent = EVENT_OBJECT_SELECTION;
  1357. } else {
  1358. /*
  1359. * Reset all the previous selections
  1360. */
  1361. xxxResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
  1362. /*
  1363. * Select all items from anchor point upto current click pt
  1364. */
  1365. xxxAlterHilite(plb, plb->iMouseDown, iSelFromPt, HILITE, HILITEONLY, FALSE);
  1366. uEvent = EVENT_OBJECT_SELECTIONWITHIN;
  1367. }
  1368. plb->fNewItemState = (UINT)TRUE;
  1369. break;
  1370. case CTLDOWN:
  1371. /*
  1372. * This is so that we can handle click and drag for multisel
  1373. * listboxes using Control modifier key.
  1374. */
  1375. /*
  1376. * Reset the anchor point to the current point
  1377. */
  1378. plb->iMouseDown = plb->iLastMouseMove = plb->iSel = iSelFromPt;
  1379. /*
  1380. * The state we will be setting items to
  1381. */
  1382. plb->fNewItemState = (UINT)!IsSelected(plb, iSelFromPt, (UINT)HILITEONLY);
  1383. /*
  1384. * Toggle the current point
  1385. */
  1386. SetSelected(plb, iSelFromPt, plb->fNewItemState, HILITEANDSEL);
  1387. xxxInvertLBItem(plb, iSelFromPt, plb->fNewItemState);
  1388. uEvent = (plb->fNewItemState ? EVENT_OBJECT_SELECTIONADD :
  1389. EVENT_OBJECT_SELECTIONREMOVE);
  1390. break;
  1391. case SHCTLDOWN:
  1392. /*
  1393. * This is so that we can handle click and drag for multisel
  1394. * listboxes using Shift and Control modifier keys.
  1395. */
  1396. /*
  1397. * Preserve all the previous selections
  1398. */
  1399. /*
  1400. * Deselect only the selection connected with the last
  1401. * anchor point; If the last anchor point is associated with a
  1402. * de-selection, then do not do it
  1403. */
  1404. if (plb->fNewItemState) {
  1405. xxxAlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE, HILITEANDSEL, FALSE);
  1406. }
  1407. plb->iLastMouseMove = plb->iSel = iSelFromPt;
  1408. /*
  1409. * Check if an anchor point already exists
  1410. */
  1411. if (plb->iMouseDown == -1) {
  1412. /*
  1413. * No existing anchor point; Make the current pt as anchor
  1414. */
  1415. plb->iMouseDown = iSelFromPt;
  1416. }
  1417. /*
  1418. * If one exists preserve the most recent anchor point
  1419. */
  1420. /*
  1421. * The state we will be setting items to
  1422. */
  1423. plb->fNewItemState = (UINT)IsSelected(plb, plb->iMouseDown, HILITEONLY);
  1424. /*
  1425. * Select all items from anchor point upto current click pt
  1426. */
  1427. xxxAlterHilite(plb, plb->iMouseDown, iSelFromPt, plb->fNewItemState, HILITEONLY, FALSE);
  1428. uEvent = EVENT_OBJECT_SELECTIONWITHIN;
  1429. break;
  1430. }
  1431. /*
  1432. * Set the new base point (the outline frame caret). We do the check
  1433. * first to avoid flashing the caret unnecessarly.
  1434. */
  1435. if (plb->iSelBase != iSelFromPt) {
  1436. /*
  1437. * Since xxxSetISelBase always turns on the caret, we don't need to
  1438. * do it here...
  1439. */
  1440. xxxSetISelBase(plb, iSelFromPt);
  1441. }
  1442. /*
  1443. * SetISelBase will change the focus and send a focus event.
  1444. * Then we send the selection event.
  1445. */
  1446. if (uEvent) {
  1447. LBEvent(plb, uEvent, iSelFromPt);
  1448. }
  1449. if (wMsg == WM_LBUTTONDOWN && TestWF(plb->spwnd, WEFDRAGOBJECT)) {
  1450. if (NtUserDragDetect(hwnd, pt)) {
  1451. /*
  1452. * User is trying to drag object...
  1453. */
  1454. /*
  1455. * Fake an up click so that the item is selected...
  1456. */
  1457. xxxTrackMouse(plb, WM_LBUTTONUP, pt);
  1458. /*
  1459. * Notify parent
  1460. * #ifndef WIN16 (32-bit Windows), plb->iSelBase gets
  1461. * zero-extended to LONG wParam automatically by the compiler.
  1462. */
  1463. ThreadLock(plb->spwndParent, &tlpwndParent);
  1464. SendMessage(HW(plb->spwndParent), WM_BEGINDRAG, plb->iSelBase,
  1465. (LPARAM)hwnd);
  1466. ThreadUnlock(&tlpwndParent);
  1467. } else {
  1468. xxxTrackMouse(plb, WM_LBUTTONUP, pt);
  1469. }
  1470. return;
  1471. }
  1472. break;
  1473. case WM_MOUSEMOVE: {
  1474. int dist;
  1475. int iTimer;
  1476. /*
  1477. * Save for timer.
  1478. */
  1479. plb->ptPrev = pt;
  1480. /*
  1481. * Autoscroll listbox if mouse button is held down and mouse is
  1482. * moved outside of the listbox
  1483. */
  1484. if (plb->fMouseDown) {
  1485. if (plb->fMultiColumn) {
  1486. if ((pt.x < 0) || (pt.x >= rcClient.right - 1)) {
  1487. /*
  1488. * Reset timer interval based on distance from listbox.
  1489. * use a longer default interval because each multicolumn
  1490. * scrolling increment is larger
  1491. */
  1492. dist = pt.x < 0 ? -pt.x : (pt.x - rcClient.right + 1);
  1493. iTimer = ((gpsi->dtScroll * 3) / 2) - ((WORD) dist << 4);
  1494. if (plb->fRightAlign)
  1495. xxxLBoxCtlHScrollMultiColumn(plb, (pt.x < 0 ? SB_LINEDOWN : SB_LINEUP), 0);
  1496. else
  1497. xxxLBoxCtlHScrollMultiColumn(plb, (pt.x < 0 ? SB_LINEUP : SB_LINEDOWN), 0);
  1498. goto SetTimerAndSel;
  1499. }
  1500. } else if ((pt.y < 0) || (pt.y >= rcClient.bottom - 1)) {
  1501. /*
  1502. * Reset timer interval based on distance from listbox.
  1503. */
  1504. dist = pt.y < 0 ? -pt.y : (pt.y - rcClient.bottom + 1);
  1505. iTimer = gpsi->dtScroll - ((WORD) dist << 4);
  1506. xxxLBoxCtlScroll(plb, (pt.y < 0 ? SB_LINEUP : SB_LINEDOWN), 0);
  1507. SetTimerAndSel:
  1508. NtUserSetTimer(hwnd, IDSYS_SCROLL, max(iTimer, 1), NULL);
  1509. ISelFromPt(plb, pt, &iSelFromPt);
  1510. }
  1511. } else {
  1512. /*
  1513. * Ignore if not in client since we don't autoscroll
  1514. */
  1515. if (!PtInRect(&rcClient, pt))
  1516. break;
  1517. }
  1518. switch (plb->wMultiple) {
  1519. case SINGLESEL:
  1520. /*
  1521. * If it is a single selection or plain multisel list box
  1522. */
  1523. goto MouseMoveHandler;
  1524. break;
  1525. case MULTIPLESEL:
  1526. case EXTENDEDSEL:
  1527. /*
  1528. * Handle mouse movement with extended selection of items
  1529. */
  1530. if (plb->iSelBase != iSelFromPt) {
  1531. xxxSetISelBase(plb, iSelFromPt);
  1532. /*
  1533. * If this is an extended Multi sel list box, then
  1534. * adjust the display of the range due to the mouse move
  1535. */
  1536. if (plb->wMultiple == EXTENDEDSEL) {
  1537. xxxLBBlockHilite(plb, iSelFromPt, FALSE);
  1538. LBEvent(plb, EVENT_OBJECT_SELECTIONWITHIN, iSelFromPt);
  1539. }
  1540. plb->iLastMouseMove = iSelFromPt;
  1541. }
  1542. break;
  1543. }
  1544. break;
  1545. }
  1546. case WM_LBUTTONUP:
  1547. if (plb->fMouseDown)
  1548. xxxLBButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY |
  1549. (mousetemp ? LBUP_RESETSELECTION : 0) |
  1550. (fMouseInRect ? LBUP_SUCCESS : 0));
  1551. }
  1552. }
  1553. /***************************************************************************\
  1554. *
  1555. * LBButtonUp()
  1556. *
  1557. * Called in response to both WM_CAPTURECHANGED and WM_LBUTTONUP.
  1558. *
  1559. \***************************************************************************/
  1560. void xxxLBButtonUp(PLBIV plb, UINT uFlags)
  1561. {
  1562. CheckLock(plb->spwnd);
  1563. /*
  1564. * If the list box is an Extended listbox, then change the select status
  1565. * of all items between the anchor and the last mouse position to the
  1566. * newItemState
  1567. */
  1568. if (plb->wMultiple == EXTENDEDSEL)
  1569. xxxAlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove,
  1570. plb->fNewItemState, SELONLY, FALSE);
  1571. /*
  1572. * This is a combo box and user upclicked outside the listbox
  1573. * so we want to restore the original selection.
  1574. */
  1575. if (plb->pcbox && (uFlags & LBUP_RESETSELECTION)) {
  1576. int iSelOld;
  1577. iSelOld = plb->iSel;
  1578. if (iSelOld >= 0)
  1579. xxxInvertLBItem(plb, plb->iSel, FALSE);
  1580. plb->iSel = plb->iLastSelection;
  1581. xxxInvertLBItem(plb, plb->iSel, TRUE);
  1582. /*
  1583. * Note that we always send selection events before we tell the
  1584. * app. This is on purpose--the app may turn around and select
  1585. * something else when notified. In which case our event would
  1586. * be out of order.
  1587. */
  1588. LBEvent(plb, EVENT_OBJECT_SELECTION, plb->iSel);
  1589. /*
  1590. * On win-95 and NT4 the check used to be !(uFlags & LBUP_NOTIFY) which
  1591. * is a bug because we would notify even when the lb is not LBUP_NOTIFY
  1592. */
  1593. if ((uFlags & LBUP_NOTIFY) && plb->fNotify && (iSelOld != plb->iSel))
  1594. xxxNotifyOwner(plb, LBN_SELCHANGE);
  1595. }
  1596. NtUserKillTimer(HWq(plb->spwnd), IDSYS_SCROLL);
  1597. plb->fMouseDown = FALSE;
  1598. if (plb->fCaptured) {
  1599. plb->fCaptured = FALSE;
  1600. if (uFlags & LBUP_RELEASECAPTURE)
  1601. NtUserReleaseCapture();
  1602. }
  1603. /*
  1604. * Don't scroll item as long as any part of it is visible
  1605. */
  1606. if (plb->iSelBase < plb->iTop ||
  1607. plb->iSelBase > plb->iTop + CItemInWindow(plb, TRUE))
  1608. xxxInsureVisible(plb, plb->iSelBase, FALSE);
  1609. if (plb->fNotify) {
  1610. if (uFlags & LBUP_NOTIFY) {
  1611. if (uFlags & LBUP_SUCCESS) {
  1612. /*
  1613. * ArtMaster needs this SELCHANGE notification now!
  1614. */
  1615. if ((plb->fDoubleClick) && !TestWF(plb->spwnd, WFWIN31COMPAT))
  1616. xxxNotifyOwner(plb, LBN_SELCHANGE);
  1617. /*
  1618. * Notify owner of click or double click on selection
  1619. */
  1620. xxxNotifyOwner(plb, (plb->fDoubleClick) ? LBN_DBLCLK : LBN_SELCHANGE);
  1621. } else {
  1622. /*
  1623. * Notify owner that the attempted selection was cancelled.
  1624. */
  1625. xxxNotifyOwner(plb, LBN_SELCANCEL);
  1626. }
  1627. } else if (uFlags & LBUP_SELCHANGE) {
  1628. /*
  1629. * Did we do some semi-selecting with mouse moves, then hit Enter?
  1630. * If so, we need to make sure the app knows that something was
  1631. * really truly selected.
  1632. */
  1633. UserAssert(TestWF(plb->spwnd, WFWIN40COMPAT));
  1634. if (plb->iLastSelection != plb->iSel)
  1635. xxxNotifyOwner(plb, LBN_SELCHANGE);
  1636. }
  1637. }
  1638. }
  1639. /***************************************************************************\
  1640. * IncrementISel
  1641. *
  1642. * History:
  1643. \***************************************************************************/
  1644. INT IncrementISel(
  1645. PLBIV plb,
  1646. INT iSel,
  1647. INT sInc)
  1648. {
  1649. /*
  1650. * Assumes cMac > 0, return iSel+sInc in range [0..cmac).
  1651. */
  1652. iSel += sInc;
  1653. if (iSel < 0) {
  1654. return 0;
  1655. } else if (iSel >= plb->cMac) {
  1656. return plb->cMac - 1;
  1657. }
  1658. return iSel;
  1659. }
  1660. /***************************************************************************\
  1661. * NewITop
  1662. *
  1663. \***************************************************************************/
  1664. void xxxNewITop(PLBIV plb, INT iTopNew)
  1665. {
  1666. xxxNewITopEx(plb, iTopNew, 0);
  1667. }
  1668. /***************************************************************************\
  1669. * xxxNewITopEx
  1670. *
  1671. * History:
  1672. \***************************************************************************/
  1673. void xxxNewITopEx(
  1674. PLBIV plb,
  1675. INT iTopNew,
  1676. DWORD dwTime)
  1677. {
  1678. int iTopOld;
  1679. BOOL fCaretOn;
  1680. BOOL fMulti = plb->fMultiColumn;
  1681. CheckLock(plb->spwnd);
  1682. // Always try to turn off caret whether or not redraw is on
  1683. if (fCaretOn = plb->fCaretOn)
  1684. xxxLBSetCaret(plb, FALSE);
  1685. iTopOld = (fMulti) ? (plb->iTop / plb->itemsPerColumn) : plb->iTop;
  1686. plb->iTop = iTopNew;
  1687. iTopNew = xxxSetLBScrollParms(plb, (fMulti) ? SB_HORZ : SB_VERT);
  1688. plb->iTop = (fMulti) ? (iTopNew * plb->itemsPerColumn) : iTopNew;
  1689. if (!IsLBoxVisible(plb)) {
  1690. return;
  1691. }
  1692. if (iTopNew != iTopOld) {
  1693. int xAmt, yAmt;
  1694. RECT rc;
  1695. DWORD dwFlags;
  1696. _GetClientRect(plb->spwnd, &rc);
  1697. if (fMulti) {
  1698. yAmt = 0;
  1699. if (abs(iTopNew - iTopOld) > plb->numberOfColumns)
  1700. // Handle scrolling a large number of columns properly so that
  1701. // we don't overflow the size of a rect.
  1702. xAmt = 32000;
  1703. else {
  1704. xAmt = (iTopOld - iTopNew) * plb->cxColumn;
  1705. if (plb->fRightAlign)
  1706. xAmt = -xAmt;
  1707. }
  1708. } else {
  1709. xAmt = 0;
  1710. if (plb->OwnerDraw == OWNERDRAWVAR) {
  1711. //
  1712. // Have to fake iTopOld for OWNERDRAWVAR listboxes so that
  1713. // the scrolling amount calculations work properly.
  1714. //
  1715. plb->iTop = iTopOld;
  1716. yAmt = LBCalcVarITopScrollAmt(plb, iTopOld, iTopNew);
  1717. plb->iTop = iTopNew;
  1718. } else if (abs(iTopNew - iTopOld) > plb->cItemFullMax)
  1719. yAmt = 32000;
  1720. else
  1721. yAmt = (iTopOld - iTopNew) * plb->cyChar;
  1722. }
  1723. dwFlags = LBGetScrollFlags(plb, dwTime);
  1724. ScrollWindowEx(HWq(plb->spwnd), xAmt, yAmt, NULL, &rc, NULL,
  1725. NULL, dwFlags);
  1726. UpdateWindow(HWq(plb->spwnd));
  1727. }
  1728. // Note that although we turn off the caret regardless of redraw, we
  1729. // only turn it on if redraw is true. Slimy thing to fixup many
  1730. // caret related bugs...
  1731. if (fCaretOn)
  1732. // Turn the caret back on only if we turned it off. This avoids
  1733. // annoying caret flicker.
  1734. xxxLBSetCaret(plb, TRUE);
  1735. }
  1736. /***************************************************************************\
  1737. * xxxInsureVisible
  1738. *
  1739. * History:
  1740. \***************************************************************************/
  1741. void xxxInsureVisible(
  1742. PLBIV plb,
  1743. INT iSel,
  1744. BOOL fPartial) /* It is ok for the item to be partially visible */
  1745. {
  1746. INT sLastVisibleItem;
  1747. CheckLock(plb->spwnd);
  1748. if (iSel < plb->iTop) {
  1749. xxxNewITop(plb, iSel);
  1750. } else {
  1751. if (fPartial) {
  1752. /*
  1753. * 1 must be subtracted to get the last visible item
  1754. * A part of the fix for Bug #3727 -- 01/14/91 -- SANKAR
  1755. */
  1756. sLastVisibleItem = plb->iTop + CItemInWindow(plb, TRUE) - (INT)1;
  1757. } else {
  1758. sLastVisibleItem = LastFullVisible(plb);
  1759. }
  1760. if (plb->OwnerDraw != OWNERDRAWVAR) {
  1761. if (iSel > sLastVisibleItem) {
  1762. if (plb->fMultiColumn) {
  1763. xxxNewITop(plb,
  1764. ((iSel / plb->itemsPerColumn) -
  1765. max(plb->numberOfColumns-1,0)) * plb->itemsPerColumn);
  1766. } else {
  1767. xxxNewITop(plb, (INT)max(0, iSel - sLastVisibleItem + plb->iTop));
  1768. }
  1769. }
  1770. } else if (iSel > sLastVisibleItem)
  1771. xxxNewITop(plb, LBPage(plb, iSel, FALSE));
  1772. }
  1773. }
  1774. /***************************************************************************\
  1775. * xxxLBoxCaretBlinker
  1776. *
  1777. * Timer callback function toggles Caret
  1778. * Since it is a callback, it is APIENTRY
  1779. *
  1780. * History:
  1781. \***************************************************************************/
  1782. VOID xxxLBoxCaretBlinker(
  1783. HWND hwnd,
  1784. UINT wMsg,
  1785. UINT_PTR nIDEvent,
  1786. DWORD dwTime)
  1787. {
  1788. PWND pwnd;
  1789. PLBIV plb;
  1790. /*
  1791. * Standard parameters for a timer callback function that aren't used.
  1792. * Mentioned here to avoid compiler warnings
  1793. */
  1794. UNREFERENCED_PARAMETER(wMsg);
  1795. UNREFERENCED_PARAMETER(nIDEvent);
  1796. UNREFERENCED_PARAMETER(dwTime);
  1797. pwnd = ValidateHwnd(hwnd);
  1798. plb = ((PLBWND)pwnd)->pLBIV;
  1799. /*
  1800. * leave caret on, don't blink it off (prevents rapid blinks?)
  1801. */
  1802. if (ISREMOTESESSION() && plb->fCaretOn) {
  1803. return;
  1804. }
  1805. /*
  1806. * Check if the Caret is ON, if so, switch it OFF
  1807. */
  1808. xxxLBSetCaret(plb, !plb->fCaretOn);
  1809. return;
  1810. }
  1811. /***************************************************************************\
  1812. * xxxLBoxCtlKeyInput
  1813. *
  1814. * If msg == LB_KEYDOWN, vKey is the number of the item to go to,
  1815. * otherwise it is the virtual key.
  1816. *
  1817. * History:
  1818. \***************************************************************************/
  1819. void xxxLBoxCtlKeyInput(
  1820. PLBIV plb,
  1821. UINT msg,
  1822. UINT vKey)
  1823. {
  1824. INT i;
  1825. INT iNewISel;
  1826. INT cItemPageScroll;
  1827. PCBOX pcbox;
  1828. BOOL fDropDownComboBox;
  1829. BOOL fExtendedUIComboBoxClosed;
  1830. BOOL hScrollBar = TestWF(plb->spwnd, WFHSCROLL);
  1831. UINT wModifiers = 0;
  1832. BOOL fSelectKey = FALSE; /* assume it is a navigation key */
  1833. UINT uEvent = 0;
  1834. HWND hwnd = HWq(plb->spwnd);
  1835. TL tlpwndParent;
  1836. TL tlpwnd;
  1837. CheckLock(plb->spwnd);
  1838. pcbox = plb->pcbox;
  1839. /*
  1840. * Is this a dropdown style combo box/listbox ?
  1841. */
  1842. fDropDownComboBox = pcbox && (pcbox->CBoxStyle & SDROPPABLE);
  1843. /*
  1844. *Is this an extended ui combo box which is closed?
  1845. */
  1846. fExtendedUIComboBoxClosed = fDropDownComboBox && pcbox->fExtendedUI &&
  1847. !pcbox->fLBoxVisible;
  1848. if (plb->fMouseDown || (!plb->cMac && vKey != VK_F4)) {
  1849. /*
  1850. * Ignore keyboard input if we are in the middle of a mouse down deal or
  1851. * if there are no items in the listbox. Note that we let F4's go
  1852. * through for combo boxes so that the use can pop up and down empty
  1853. * combo boxes.
  1854. */
  1855. return;
  1856. }
  1857. /*
  1858. * Modifiers are considered only in EXTENDED sel list boxes.
  1859. */
  1860. if (plb->wMultiple == EXTENDEDSEL) {
  1861. /*
  1862. * If multiselection listbox, are any modifiers used ?
  1863. */
  1864. if (GetKeyState(VK_SHIFT) < 0)
  1865. wModifiers = SHIFTDOWN;
  1866. if (GetKeyState(VK_CONTROL) < 0)
  1867. wModifiers += CTLDOWN;
  1868. /*
  1869. * Please Note that (SHIFTDOWN + CTLDOWN) == (SHCTLDOWN)
  1870. */
  1871. }
  1872. if (msg == LB_KEYDOWN) {
  1873. /*
  1874. * This is a listbox "go to specified item" message which means we want
  1875. * to go to a particular item number (given by vKey) directly. ie. the
  1876. * user has typed a character and we want to go to the item which
  1877. * starts with that character.
  1878. */
  1879. iNewISel = (INT)vKey;
  1880. goto TrackKeyDown;
  1881. }
  1882. cItemPageScroll = plb->cItemFullMax;
  1883. if (cItemPageScroll > 1)
  1884. cItemPageScroll--;
  1885. if (plb->fWantKeyboardInput) {
  1886. /*
  1887. * Note: msg must not be LB_KEYDOWN here or we'll be in trouble...
  1888. */
  1889. ThreadLock(plb->spwndParent, &tlpwndParent);
  1890. iNewISel = (INT)SendMessage(HW(plb->spwndParent), WM_VKEYTOITEM,
  1891. MAKELONG(vKey, plb->iSelBase), (LPARAM)hwnd);
  1892. ThreadUnlock(&tlpwndParent);
  1893. if (iNewISel == -2) {
  1894. /*
  1895. * Don't move the selection...
  1896. */
  1897. return;
  1898. }
  1899. if (iNewISel != -1) {
  1900. /*
  1901. * Jump directly to the item provided by the app
  1902. */
  1903. goto TrackKeyDown;
  1904. }
  1905. /*
  1906. * else do default processing of the character.
  1907. */
  1908. }
  1909. switch (vKey) {
  1910. // LATER IanJa: not language independent!!!
  1911. // We could use VkKeyScan() to find out which is the '\' key
  1912. // This is VK_OEM_5 '\|' for US English only.
  1913. // Germans, Italians etc. have to type CTRL+^ (etc) for this.
  1914. // This is documented as File Manager behaviour for 3.0, but apparently
  1915. // not for 3.1., although functionality remains. We should still fix it,
  1916. // although German (etc?) '\' is generated with AltGr (Ctrl-Alt) (???)
  1917. case VERKEY_BACKSLASH: /* '\' character for US English */
  1918. /*
  1919. * Check if this is CONTROL-\ ; If so Deselect all items
  1920. */
  1921. if ((wModifiers & CTLDOWN) && (plb->wMultiple != SINGLESEL)) {
  1922. xxxLBSetCaret(plb, FALSE);
  1923. xxxResetWorld(plb, plb->iSelBase, plb->iSelBase, FALSE);
  1924. /*
  1925. * And select the current item
  1926. */
  1927. SetSelected(plb, plb->iSelBase, TRUE, HILITEANDSEL);
  1928. xxxInvertLBItem(plb, plb->iSelBase, TRUE);
  1929. uEvent = EVENT_OBJECT_SELECTION;
  1930. goto CaretOnAndNotify;
  1931. }
  1932. return;
  1933. break;
  1934. case VK_DIVIDE: /* NumPad '/' character on enhanced keyboard */
  1935. // LATER IanJa: not language independent!!!
  1936. // We could use VkKeyScan() to find out which is the '/' key
  1937. // This is VK_OEM_2 '/?' for US English only.
  1938. // Germans, Italians etc. have to type CTRL+# (etc) for this.
  1939. case VERKEY_SLASH: /* '/' character */
  1940. /*
  1941. * Check if this is CONTROL-/ ; If so select all items
  1942. */
  1943. if ((wModifiers & CTLDOWN) && (plb->wMultiple != SINGLESEL)) {
  1944. xxxLBSetCaret(plb, FALSE);
  1945. xxxResetWorld(plb, -1, -1, TRUE);
  1946. uEvent = EVENT_OBJECT_SELECTIONWITHIN;
  1947. CaretOnAndNotify:
  1948. xxxLBSetCaret(plb, TRUE);
  1949. LBEvent(plb, uEvent, plb->iSelBase);
  1950. xxxNotifyOwner(plb, LBN_SELCHANGE);
  1951. }
  1952. return;
  1953. break;
  1954. case VK_F8:
  1955. /*
  1956. * The "Add" mode is possible only in Multiselection listboxes... Get
  1957. * into it via SHIFT-F8... (Yes, sometimes these UI people are sillier
  1958. * than your "typical dumb user"...)
  1959. */
  1960. if (plb->wMultiple != SINGLESEL && wModifiers == SHIFTDOWN) {
  1961. /*
  1962. * We have to make the caret blink! Do something...
  1963. */
  1964. if (plb->fAddSelMode) {
  1965. /*
  1966. * Switch off the Caret blinking
  1967. */
  1968. NtUserKillTimer(hwnd, IDSYS_CARET);
  1969. /*
  1970. * Make sure the caret does not vanish
  1971. */
  1972. xxxLBSetCaret(plb, TRUE);
  1973. } else {
  1974. /*
  1975. * Create a timer to make the caret blink
  1976. */
  1977. NtUserSetTimer(hwnd, IDSYS_CARET, gpsi->dtCaretBlink,
  1978. xxxLBoxCaretBlinker);
  1979. }
  1980. /*
  1981. * Toggle the Add mode flag
  1982. */
  1983. plb->fAddSelMode = (UINT)!plb->fAddSelMode;
  1984. }
  1985. return;
  1986. case VK_SPACE: /* Selection key is space */
  1987. i = 0;
  1988. fSelectKey = TRUE;
  1989. break;
  1990. case VK_PRIOR:
  1991. if (fExtendedUIComboBoxClosed) {
  1992. /*
  1993. * Disable movement keys for TandyT.
  1994. */
  1995. return;
  1996. }
  1997. if (plb->OwnerDraw == OWNERDRAWVAR) {
  1998. i = LBPage(plb, plb->iSelBase, FALSE) - plb->iSelBase;
  1999. } else {
  2000. i = -cItemPageScroll;
  2001. }
  2002. break;
  2003. case VK_NEXT:
  2004. if (fExtendedUIComboBoxClosed) {
  2005. /*
  2006. * Disable movement keys for TandyT.
  2007. */
  2008. return;
  2009. }
  2010. if (plb->OwnerDraw == OWNERDRAWVAR) {
  2011. i = LBPage(plb, plb->iSelBase, TRUE) - plb->iSelBase;
  2012. } else {
  2013. i = cItemPageScroll;
  2014. }
  2015. break;
  2016. case VK_HOME:
  2017. if (fExtendedUIComboBoxClosed) {
  2018. /*
  2019. * Disable movement keys for TandyT.
  2020. */
  2021. return;
  2022. }
  2023. i = (INT_MIN/2)+1; /* A very big negative number */
  2024. break;
  2025. case VK_END:
  2026. if (fExtendedUIComboBoxClosed) {
  2027. /*
  2028. * Disable movement keys for TandyT.
  2029. */
  2030. return;
  2031. }
  2032. i = (INT_MAX/2)-1; /* A very big positive number */
  2033. break;
  2034. case VK_LEFT:
  2035. if (plb->fMultiColumn) {
  2036. if (plb->fRightAlign ^ (!!TestWF(plb->spwnd, WEFLAYOUTRTL))) {
  2037. goto ReallyRight;
  2038. }
  2039. ReallyLeft:
  2040. if (plb->iSelBase / plb->itemsPerColumn == 0) {
  2041. i = 0;
  2042. } else {
  2043. i = -plb->itemsPerColumn;
  2044. }
  2045. break;
  2046. }
  2047. if (hScrollBar) {
  2048. goto HandleHScrolling;
  2049. } else {
  2050. /*
  2051. * Fall through and handle this as if the up arrow was pressed.
  2052. */
  2053. vKey = VK_UP;
  2054. }
  2055. /*
  2056. * Fall through
  2057. */
  2058. case VK_UP:
  2059. if (fExtendedUIComboBoxClosed)
  2060. // Disable movement keys for TandyT.
  2061. return;
  2062. i = -1;
  2063. break;
  2064. case VK_RIGHT:
  2065. if (plb->fMultiColumn) {
  2066. if (plb->fRightAlign ^ (!!TestWF(plb->spwnd, WEFLAYOUTRTL))) {
  2067. goto ReallyLeft;
  2068. }
  2069. ReallyRight:
  2070. if (plb->iSelBase / plb->itemsPerColumn == plb->cMac / plb->itemsPerColumn) {
  2071. i = 0;
  2072. } else {
  2073. i = plb->itemsPerColumn;
  2074. }
  2075. break;
  2076. }
  2077. if (hScrollBar) {
  2078. HandleHScrolling:
  2079. PostMessage(hwnd, WM_HSCROLL,
  2080. (vKey == VK_RIGHT ? SB_LINEDOWN : SB_LINEUP), 0L);
  2081. return;
  2082. } else {
  2083. /*
  2084. * Fall through and handle this as if the down arrow was
  2085. * pressed.
  2086. */
  2087. vKey = VK_DOWN;
  2088. }
  2089. /*
  2090. * Fall through
  2091. */
  2092. case VK_DOWN:
  2093. if (fExtendedUIComboBoxClosed) {
  2094. /*
  2095. * If the combo box is closed, down arrow should open it.
  2096. */
  2097. if (!pcbox->fLBoxVisible) {
  2098. /*
  2099. * If the listbox isn't visible, just show it
  2100. */
  2101. ThreadLock(pcbox->spwnd, &tlpwnd);
  2102. xxxCBShowListBoxWindow(pcbox, TRUE);
  2103. ThreadUnlock(&tlpwnd);
  2104. }
  2105. return;
  2106. }
  2107. i = 1;
  2108. break;
  2109. case VK_ESCAPE:
  2110. case VK_RETURN:
  2111. if (!fDropDownComboBox || !pcbox->fLBoxVisible)
  2112. return;
  2113. // | If this is a dropped listbox for a combobox and the ENTER |
  2114. // | key is pressed, close up the listbox, so FALLTHRU |
  2115. // V V
  2116. case VK_F4:
  2117. if (fDropDownComboBox && !pcbox->fExtendedUI) {
  2118. /*
  2119. * If we are a dropdown combo box/listbox we want to process
  2120. * this key. BUT for TandtT, we don't do anything on VK_F4 if we
  2121. * are in extended ui mode.
  2122. */
  2123. ThreadLock(pcbox->spwnd, &tlpwnd);
  2124. if (!pcbox->fLBoxVisible) {
  2125. /*
  2126. * If the listbox isn't visible, just show it
  2127. */
  2128. xxxCBShowListBoxWindow(pcbox, (vKey != VK_ESCAPE));
  2129. } else {
  2130. /*
  2131. * Ok, the listbox is visible. So hide the listbox window.
  2132. */
  2133. xxxCBHideListBoxWindow(pcbox, TRUE, (vKey != VK_ESCAPE));
  2134. }
  2135. ThreadUnlock(&tlpwnd);
  2136. }
  2137. /*
  2138. * Fall through to the return
  2139. */
  2140. default:
  2141. return;
  2142. }
  2143. /*
  2144. * Find out what the new selection should be
  2145. */
  2146. iNewISel = IncrementISel(plb, plb->iSelBase, i);
  2147. if (plb->wMultiple == SINGLESEL) {
  2148. if (plb->iSel == iNewISel) {
  2149. /*
  2150. * If we are single selection and the keystroke is moving us to an
  2151. * item which is already selected, we don't have to do anything...
  2152. */
  2153. return;
  2154. }
  2155. uEvent = EVENT_OBJECT_SELECTION;
  2156. plb->iTypeSearch = 0;
  2157. if ((vKey == VK_UP || vKey == VK_DOWN) &&
  2158. !IsSelected(plb, plb->iSelBase, HILITEONLY)) {
  2159. /*
  2160. * If the caret is on an unselected item and the user just hits the
  2161. * up or down arrow key (ie. with no shift or ctrl modifications),
  2162. * then we will just select the item the cursor is at. This is
  2163. * needed for proper behavior in combo boxes but do we always want
  2164. * to run this code??? Note that this is only used in single
  2165. * selection list boxes since it doesn't make sense in the
  2166. * multiselection case. Note that an LB_KEYDOWN message must not be
  2167. * checked here because the vKey will be an item number not a
  2168. * VK_and we will goof. Thus, trackkeydown label is below this to
  2169. * fix a bug caused by it being above this...
  2170. */
  2171. iNewISel = (plb->iSelBase == -1) ? 0 : plb->iSelBase;
  2172. }
  2173. }
  2174. TrackKeyDown:
  2175. xxxSetISelBase(plb, iNewISel);
  2176. xxxLBSetCaret(plb, FALSE);
  2177. if (wModifiers & SHIFTDOWN) {
  2178. // Check if iMouseDown is un-initialised
  2179. if (plb->iMouseDown == -1)
  2180. plb->iMouseDown = iNewISel;
  2181. if (plb->iLastMouseMove == -1)
  2182. plb->iLastMouseMove = iNewISel;
  2183. // Check if we are in ADD mode
  2184. if (plb->fAddSelMode) {
  2185. /* Preserve all the pre-existing selections except the
  2186. * ones connected with the last anchor point; If the last
  2187. * Preserve all the previous selections
  2188. */
  2189. /* Deselect only the selection connected with the last
  2190. * anchor point; If the last anchor point is associated
  2191. * with de-selection, then do not do it
  2192. */
  2193. if (!plb->fNewItemState)
  2194. plb->iLastMouseMove = plb->iMouseDown;
  2195. /* We haven't done anything here because, LBBlockHilite()
  2196. * will take care of wiping out the selection between
  2197. * Anchor point and iLastMouseMove and select the block
  2198. * between anchor point and current cursor location
  2199. */
  2200. } else {
  2201. /* We are not in ADD mode */
  2202. /* Remove all selections except between the anchor point
  2203. * and last mouse move because it will be taken care of in
  2204. * LBBlockHilite
  2205. */
  2206. xxxResetWorld(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE);
  2207. }
  2208. uEvent = EVENT_OBJECT_SELECTIONWITHIN;
  2209. /* LBBlockHilite takes care to deselect the block between
  2210. * the anchor point and iLastMouseMove and select the block
  2211. * between the anchor point and the current cursor location
  2212. */
  2213. /* Toggle all items to the same selection state as the item
  2214. * item at the anchor point) from the anchor point to the
  2215. * current cursor location.
  2216. */
  2217. plb->fNewItemState = IsSelected(plb, plb->iMouseDown, SELONLY);
  2218. xxxLBBlockHilite(plb, iNewISel, TRUE);
  2219. plb->iLastMouseMove = iNewISel;
  2220. /* Preserve the existing anchor point */
  2221. } else {
  2222. /* Check if this is in ADD mode */
  2223. if ((plb->fAddSelMode) || (plb->wMultiple == MULTIPLESEL)) {
  2224. /* Preserve all pre-exisiting selections */
  2225. if (fSelectKey) {
  2226. /* Toggle the selection state of the current item */
  2227. plb->fNewItemState = !IsSelected(plb, iNewISel, SELONLY);
  2228. SetSelected(plb, iNewISel, plb->fNewItemState, HILITEANDSEL);
  2229. xxxInvertLBItem(plb, iNewISel, plb->fNewItemState);
  2230. /* Set the anchor point at the current location */
  2231. plb->iLastMouseMove = plb->iMouseDown = iNewISel;
  2232. uEvent = (plb->fNewItemState ? EVENT_OBJECT_SELECTIONADD :
  2233. EVENT_OBJECT_SELECTIONREMOVE);
  2234. }
  2235. } else {
  2236. /* We are NOT in ADD mode */
  2237. /* Remove all existing selections except iNewISel, to
  2238. * avoid flickering.
  2239. */
  2240. xxxResetWorld(plb, iNewISel, iNewISel, FALSE);
  2241. /* Select the current item */
  2242. SetSelected(plb, iNewISel, TRUE, HILITEANDSEL);
  2243. xxxInvertLBItem(plb, iNewISel, TRUE);
  2244. /* Set the anchor point at the current location */
  2245. plb->iLastMouseMove = plb->iMouseDown = iNewISel;
  2246. uEvent = EVENT_OBJECT_SELECTION;
  2247. }
  2248. }
  2249. /*
  2250. * Move the cursor to the new location
  2251. */
  2252. xxxInsureVisible(plb, iNewISel, FALSE);
  2253. xxxLBShowHideScrollBars(plb);
  2254. xxxLBSetCaret(plb, TRUE);
  2255. if (uEvent) {
  2256. LBEvent(plb, uEvent, iNewISel);
  2257. }
  2258. /*
  2259. * Should we notify our parent?
  2260. */
  2261. if (plb->fNotify) {
  2262. if (fDropDownComboBox && pcbox->fLBoxVisible) {
  2263. /*
  2264. * If we are in a drop down combo box/listbox and the listbox is
  2265. * visible, we need to set the fKeyboardSelInListBox bit so that the
  2266. * combo box code knows not to hide the listbox since the selchange
  2267. * message is caused by the user keyboarding through...
  2268. */
  2269. pcbox->fKeyboardSelInListBox = TRUE;
  2270. plb->iLastSelection = iNewISel;
  2271. }
  2272. xxxNotifyOwner(plb, LBN_SELCHANGE);
  2273. }
  2274. }
  2275. /***************************************************************************\
  2276. * Compare
  2277. *
  2278. * Is lpstr1 equal/prefix/less-than/greater-than lsprst2 (case-insensitive) ?
  2279. *
  2280. * LATER IanJa: this assume a longer string is never a prefix of a longer one.
  2281. * Also assumes that removing 1 or more characters from the end of a string will
  2282. * give a string tahs sort before the original. These assumptions are not valid
  2283. * for all languages. We nedd better support from NLS. (Consider French
  2284. * accents, Spanish c/ch, ligatures, German sharp-s/SS, etc.)
  2285. *
  2286. * History:
  2287. \***************************************************************************/
  2288. INT Compare(
  2289. LPCWSTR pwsz1,
  2290. LPCWSTR pwsz2,
  2291. DWORD dwLocaleId)
  2292. {
  2293. UINT len1 = wcslen(pwsz1);
  2294. UINT len2 = wcslen(pwsz2);
  2295. INT result;
  2296. /*
  2297. * CompareStringW returns:
  2298. * 1 = pwsz1 < pwsz2
  2299. * 2 = pwsz1 == pwsz2
  2300. * 3 = pwsz1 > pwsz2
  2301. */
  2302. result = CompareStringW((LCID)dwLocaleId, NORM_IGNORECASE,
  2303. pwsz1, min(len1,len2), pwsz2, min(len1, len2));
  2304. if (result == CSTR_LESS_THAN) {
  2305. return LT;
  2306. } else if (result == CSTR_EQUAL) {
  2307. if (len1 == len2) {
  2308. return EQ;
  2309. } else if (len1 < len2) {
  2310. /*
  2311. * LATER IanJa: should not assume shorter string is a prefix
  2312. * Spanish "c" and "ch", ligatures, German sharp-s/SS etc.
  2313. */
  2314. return PREFIX;
  2315. }
  2316. }
  2317. return GT;
  2318. }
  2319. /***************************************************************************\
  2320. * xxxFindString
  2321. *
  2322. * Scans for a string in the listbox prefixed by or equal to lpstr.
  2323. * For OWNERDRAW listboxes without strings and without the sort style, we
  2324. * try to match the long app supplied values.
  2325. *
  2326. * History:
  2327. * 16-Apr-1992 beng The NODATA case
  2328. \***************************************************************************/
  2329. INT xxxFindString(
  2330. PLBIV plb,
  2331. LPWSTR lpstr,
  2332. INT sStart,
  2333. INT code,
  2334. BOOL fWrap)
  2335. {
  2336. /*
  2337. * Search for a prefix match (case-insensitive equal/prefix)
  2338. * sStart == -1 means start from beginning, else start looking at sStart+1
  2339. * assumes cMac > 0.
  2340. */
  2341. INT sInd; /* index of string */
  2342. INT sStop; /* index to stop searching at */
  2343. lpLBItem pRg;
  2344. TL tlpwndParent;
  2345. INT sortResult;
  2346. /*
  2347. * Owner-Draw version of pRg
  2348. */
  2349. #define pODRg ((lpLBODItem)pRg)
  2350. COMPAREITEMSTRUCT cis;
  2351. LPWSTR listboxString;
  2352. CheckLock(plb->spwnd);
  2353. if (plb->fHasStrings && (!lpstr || !*lpstr))
  2354. return LB_ERR;
  2355. if (!plb->fHasData) {
  2356. RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING, "FindString called on NODATA lb");
  2357. return LB_ERR;
  2358. }
  2359. if ((sInd = sStart + 1) >= plb->cMac)
  2360. sInd = (fWrap ? 0 : plb->cMac - 1);
  2361. sStop = (fWrap ? sInd : 0);
  2362. /*
  2363. * If at end and no wrap, stop right away
  2364. */
  2365. if (((sStart >= plb->cMac - 1) && !fWrap) || (plb->cMac < 1)) {
  2366. return LB_ERR;
  2367. }
  2368. /*
  2369. * Apps could pass in an invalid sStart like -2 and we would blow up.
  2370. * Win 3.1 would not so we need to fixup sInd to be zero
  2371. */
  2372. if (sInd < 0)
  2373. sInd = 0;
  2374. pRg = (lpLBItem)(plb->rgpch);
  2375. do {
  2376. if (plb->fHasStrings) {
  2377. /*
  2378. * Searching for string matches.
  2379. */
  2380. listboxString = (LPWSTR)((LPBYTE)plb->hStrings + pRg[sInd].offsz);
  2381. if (code == PREFIX &&
  2382. listboxString &&
  2383. *lpstr != TEXT('[') &&
  2384. *listboxString == TEXT('[')) {
  2385. /*
  2386. * If we are looking for a prefix string and the first items
  2387. * in this string are [- then we ignore them. This is so
  2388. * that in a directory listbox, the user can goto drives
  2389. * by selecting the drive letter.
  2390. */
  2391. listboxString++;
  2392. if (*listboxString == TEXT('-'))
  2393. listboxString++;
  2394. }
  2395. if (Compare(lpstr, listboxString, plb->dwLocaleId) <= code) {
  2396. goto FoundIt;
  2397. }
  2398. } else {
  2399. if (plb->fSort) {
  2400. /*
  2401. * Send compare item messages to the parent for sorting
  2402. */
  2403. cis.CtlType = ODT_LISTBOX;
  2404. cis.CtlID = PtrToUlong(plb->spwnd->spmenu);
  2405. cis.hwndItem = HWq(plb->spwnd);
  2406. cis.itemID1 = (UINT)-1;
  2407. cis.itemData1 = (ULONG_PTR)lpstr;
  2408. cis.itemID2 = (UINT)sInd;
  2409. cis.itemData2 = pODRg[sInd].itemData;
  2410. cis.dwLocaleId = plb->dwLocaleId;
  2411. ThreadLock(plb->spwndParent, &tlpwndParent);
  2412. sortResult = (INT)SendMessage(HW(plb->spwndParent), WM_COMPAREITEM,
  2413. cis.CtlID, (LPARAM)&cis);
  2414. ThreadUnlock(&tlpwndParent);
  2415. if (sortResult == -1) {
  2416. sortResult = LT;
  2417. } else if (sortResult == 1) {
  2418. sortResult = GT;
  2419. } else {
  2420. sortResult = EQ;
  2421. }
  2422. if (sortResult <= code) {
  2423. goto FoundIt;
  2424. }
  2425. } else {
  2426. /*
  2427. * Searching for app supplied long data matches.
  2428. */
  2429. if ((ULONG_PTR)lpstr == pODRg[sInd].itemData)
  2430. goto FoundIt;
  2431. }
  2432. }
  2433. /*
  2434. * Wrap round to beginning of list
  2435. */
  2436. if (++sInd == plb->cMac)
  2437. sInd = 0;
  2438. } while (sInd != sStop);
  2439. sInd = -1;
  2440. FoundIt:
  2441. return sInd;
  2442. }
  2443. /***************************************************************************\
  2444. * xxxLBoxCtlCharInput
  2445. *
  2446. * History:
  2447. \***************************************************************************/
  2448. void xxxLBoxCtlCharInput(
  2449. PLBIV plb,
  2450. UINT inputChar,
  2451. BOOL fAnsi)
  2452. {
  2453. INT iSel;
  2454. BOOL fControl;
  2455. TL tlpwndParent;
  2456. CheckLock(plb->spwnd);
  2457. if (plb->cMac == 0 || plb->fMouseDown) {
  2458. /*
  2459. * Get out if we are in the middle of mouse routines or if we have no
  2460. * items in the listbox, we just return without doing anything.
  2461. */
  2462. return;
  2463. }
  2464. fControl = (GetKeyState(VK_CONTROL) < 0);
  2465. switch (inputChar) {
  2466. case VK_ESCAPE:
  2467. plb->iTypeSearch = 0;
  2468. if (plb->pszTypeSearch)
  2469. plb->pszTypeSearch[0] = 0;
  2470. break;
  2471. case VK_BACK:
  2472. if (plb->iTypeSearch) {
  2473. plb->pszTypeSearch[plb->iTypeSearch--] = 0;
  2474. if (plb->fSort) {
  2475. iSel = -1;
  2476. goto TypeSearch;
  2477. }
  2478. }
  2479. break;
  2480. case VK_SPACE:
  2481. if (plb->fAddSelMode || plb->wMultiple == MULTIPLESEL)
  2482. break;
  2483. /* Otherwise, for single/extended selection listboxes not in add
  2484. * selection mode, let the space go thru as a type search character
  2485. * FALL THRU
  2486. */
  2487. default:
  2488. /*
  2489. * Move selection to first item beginning with the character the
  2490. * user typed. We don't want do this if we are using owner draw.
  2491. */
  2492. if (fAnsi && IS_DBCS_ENABLED() && IsDBCSLeadByteEx(THREAD_CODEPAGE(), (BYTE)inputChar)) {
  2493. WCHAR wch;
  2494. LPWSTR lpwstr = &wch;
  2495. inputChar = DbcsCombine(HWq(plb->spwnd), (BYTE)inputChar);
  2496. RIPMSG1(RIP_VERBOSE, "xxxLBoxCtlCharInput: combined DBCS. 0x%04x", inputChar);
  2497. if (inputChar == 0) {
  2498. RIPMSG1(RIP_WARNING, "xxxLBoxCtlCharInput: cannot combine two DBCS. LB=0x%02x",
  2499. inputChar);
  2500. break;
  2501. }
  2502. // If it is DBCS, let's ignore the ctrl status.
  2503. fControl = FALSE;
  2504. // Convert DBCS to UNICODE.
  2505. // Note: Leading byte is in the low byte, trailing byte is in high byte.
  2506. // Let's assume Little Endian CPUs only, so inputChar can directly be
  2507. // input for MBSToWCSEx as an ANSI string.
  2508. if (MBToWCSEx(THREAD_CODEPAGE(), (LPCSTR)&inputChar, 2, &lpwstr, 1, FALSE) == 0) {
  2509. RIPMSG1(RIP_WARNING, "xxxLBoxCtlCharInput: cannot convert 0x%04x to UNICODE.",
  2510. inputChar);
  2511. break;
  2512. }
  2513. inputChar = wch;
  2514. }
  2515. if (plb->fHasStrings) {
  2516. // Incremental Type Search processing
  2517. //
  2518. // update szTypeSearch string and then move to the first item from
  2519. // the current selection whose prefix matches szTypeSearch
  2520. //
  2521. // the szTypeSearch will continue to grow until a "long enough"
  2522. // gap between key entries is encountered -- at which point any
  2523. // more searching will start over
  2524. /*
  2525. * Undo CONTROL-char to char
  2526. */
  2527. if (fControl && inputChar < 0x20)
  2528. inputChar += 0x40;
  2529. if (plb->iTypeSearch == MAX_TYPESEARCH) {
  2530. NtUserMessageBeep(0);
  2531. break;
  2532. }
  2533. iSel = -1;
  2534. if (plb->pszTypeSearch == NULL)
  2535. plb->pszTypeSearch = (LPWSTR)UserLocalAlloc(HEAP_ZERO_MEMORY, sizeof(WCHAR) * (MAX_TYPESEARCH + 1));
  2536. if (plb->pszTypeSearch == NULL) {
  2537. NtUserMessageBeep(0);
  2538. break;
  2539. }
  2540. plb->pszTypeSearch[plb->iTypeSearch++] = (WCHAR) inputChar;
  2541. plb->pszTypeSearch[plb->iTypeSearch] = 0;
  2542. TypeSearch:
  2543. if (plb->fSort) {
  2544. // Set timer to determine when to kill incremental searching
  2545. NtUserSetTimer(HWq(plb->spwnd), IDSYS_LBSEARCH,
  2546. gpsi->dtLBSearch, NULL);
  2547. } else {
  2548. // If this is not a sorted listbox, no incremental search.
  2549. plb->iTypeSearch = 0;
  2550. iSel = plb->iSelBase;
  2551. }
  2552. /*
  2553. * Search for the item beginning with the given character starting
  2554. * at iSel+1. We will wrap the search to the beginning of the
  2555. * listbox if we don't find the item. If SHIFT is down and we are
  2556. * a multiselection lb, then the item's state will be set to
  2557. * plb->fNewItemState according to the current mode.
  2558. */
  2559. iSel = xxxFindString(plb, plb->pszTypeSearch, iSel, PREFIX, TRUE);
  2560. if (iSel == -1) {
  2561. // no match found -- check for prefix match
  2562. // (i.e. "p" find FIRST item that starts with 'p',
  2563. // "pp" find NEXT item that starts with 'p')
  2564. if(plb->iTypeSearch)
  2565. {
  2566. plb->iTypeSearch--;
  2567. if ((plb->iTypeSearch == 1) && (plb->pszTypeSearch[0] == plb->pszTypeSearch[1]))
  2568. {
  2569. plb->pszTypeSearch[1] = 0;
  2570. iSel = xxxFindString(plb, plb->pszTypeSearch, plb->iSelBase, PREFIX, TRUE);
  2571. }
  2572. }
  2573. }
  2574. // if match is found -- select it
  2575. if (iSel != -1)
  2576. {
  2577. CtlKeyInput:
  2578. xxxLBoxCtlKeyInput(plb, LB_KEYDOWN, iSel);
  2579. }
  2580. } else {
  2581. if (plb->spwndParent != NULL) {
  2582. ThreadLock(plb->spwndParent, &tlpwndParent);
  2583. iSel = (INT)SendMessageWorker(plb->spwndParent, WM_CHARTOITEM,
  2584. MAKELONG(inputChar, plb->iSelBase), (LPARAM)HWq(plb->spwnd), fAnsi);
  2585. ThreadUnlock(&tlpwndParent);
  2586. } else
  2587. iSel = -1;
  2588. if (iSel != -1 && iSel != -2)
  2589. goto CtlKeyInput;
  2590. }
  2591. break;
  2592. }
  2593. }
  2594. /***************************************************************************\
  2595. * LBoxGetSelItems
  2596. *
  2597. * effects: For multiselection listboxes, this returns the total number of
  2598. * selection items in the listbox if fCountOnly is true. or it fills an array
  2599. * (lParam) with the items numbers of the first wParam selected items.
  2600. *
  2601. * History:
  2602. \***************************************************************************/
  2603. int LBoxGetSelItems(
  2604. PLBIV plb,
  2605. BOOL fCountOnly,
  2606. int wParam,
  2607. LPINT lParam)
  2608. {
  2609. int i;
  2610. int itemsselected = 0;
  2611. if (plb->wMultiple == SINGLESEL)
  2612. return LB_ERR;
  2613. for (i = 0; i < plb->cMac; i++) {
  2614. if (IsSelected(plb, i, SELONLY)) {
  2615. if (!fCountOnly) {
  2616. if (itemsselected < wParam)
  2617. *lParam++ = i;
  2618. else {
  2619. /*
  2620. * That's all the items we can fit in the array.
  2621. */
  2622. return itemsselected;
  2623. }
  2624. }
  2625. itemsselected++;
  2626. }
  2627. }
  2628. return itemsselected;
  2629. }
  2630. /***************************************************************************\
  2631. * xxxLBSetRedraw
  2632. *
  2633. * Handle WM_SETREDRAW message
  2634. *
  2635. * History:
  2636. \***************************************************************************/
  2637. void xxxLBSetRedraw(
  2638. PLBIV plb,
  2639. BOOL fRedraw)
  2640. {
  2641. CheckLock(plb->spwnd);
  2642. if (fRedraw)
  2643. fRedraw = TRUE;
  2644. if (plb->fRedraw != (UINT)fRedraw) {
  2645. plb->fRedraw = !!fRedraw;
  2646. if (fRedraw) {
  2647. xxxLBSetCaret(plb, TRUE);
  2648. xxxLBShowHideScrollBars(plb);
  2649. if (plb->fDeferUpdate) {
  2650. plb->fDeferUpdate = FALSE;
  2651. RedrawWindow(HWq(plb->spwnd), NULL, NULL,
  2652. RDW_INVALIDATE | RDW_ERASE |
  2653. RDW_FRAME | RDW_ALLCHILDREN);
  2654. }
  2655. }
  2656. }
  2657. }
  2658. /***************************************************************************\
  2659. * xxxLBSelRange
  2660. *
  2661. * Selects the range of items between i and j, inclusive.
  2662. *
  2663. * History:
  2664. \***************************************************************************/
  2665. void xxxLBSelRange(
  2666. PLBIV plb,
  2667. int iStart,
  2668. int iEnd,
  2669. BOOL fnewstate)
  2670. {
  2671. DWORD temp;
  2672. RECT rc;
  2673. CheckLock(plb->spwnd);
  2674. if (iStart > iEnd) {
  2675. temp = iEnd;
  2676. iEnd = iStart;
  2677. iStart = temp;
  2678. }
  2679. /*
  2680. * We don't want to loop through items that don't exist.
  2681. */
  2682. iEnd = min(plb->cMac, iEnd);
  2683. iStart = max(iStart, 0);
  2684. if (iStart > iEnd)
  2685. return;
  2686. /*
  2687. * iEnd could be equal to MAXINT which is why we test temp and iEnd
  2688. * as DWORDs.
  2689. */
  2690. for (temp = iStart; temp <= (DWORD)iEnd; temp++) {
  2691. if (IsSelected(plb, temp, SELONLY) != fnewstate) {
  2692. SetSelected(plb, temp, fnewstate, HILITEANDSEL);
  2693. LBGetItemRect(plb, temp, &rc);
  2694. xxxLBInvalidateRect(plb, (LPRECT)&rc, FALSE);
  2695. }
  2696. }
  2697. UserAssert(plb->wMultiple);
  2698. LBEvent(plb, EVENT_OBJECT_SELECTIONWITHIN, iStart);
  2699. }
  2700. /***************************************************************************\
  2701. * xxxLBSetCurSel
  2702. *
  2703. * History:
  2704. \***************************************************************************/
  2705. int xxxLBSetCurSel(
  2706. PLBIV plb,
  2707. int iSel)
  2708. {
  2709. CheckLock(plb->spwnd);
  2710. if (!(plb->wMultiple || iSel < -1 || iSel >= plb->cMac)) {
  2711. xxxLBSetCaret(plb, FALSE);
  2712. if (plb->iSel != -1) {
  2713. /*
  2714. * This prevents scrolling when iSel == -1
  2715. */
  2716. if (iSel != -1)
  2717. xxxInsureVisible(plb, iSel, FALSE);
  2718. /*
  2719. * Turn off old selection
  2720. */
  2721. xxxInvertLBItem(plb, plb->iSel, FALSE);
  2722. }
  2723. if (iSel != -1) {
  2724. xxxInsureVisible(plb, iSel, FALSE);
  2725. plb->iSelBase = plb->iSel = iSel;
  2726. /*
  2727. * Highlight new selection
  2728. */
  2729. xxxInvertLBItem(plb, plb->iSel, TRUE);
  2730. } else {
  2731. plb->iSel = -1;
  2732. if (plb->cMac)
  2733. plb->iSelBase = min(plb->iSelBase, plb->cMac-1);
  2734. else
  2735. plb->iSelBase = 0;
  2736. }
  2737. /*
  2738. * Send both focus and selection events
  2739. *
  2740. * We need to send this event even if the listbox isn't visible. See
  2741. * bug #88548. Also see 355612.
  2742. */
  2743. if (_IsWindowVisible(plb->spwnd) || (GetFocus() == HWq(plb->spwnd))) {
  2744. LBEvent(plb, EVENT_OBJECT_FOCUS, plb->iSelBase);
  2745. LBEvent(plb, EVENT_OBJECT_SELECTION, plb->iSel);
  2746. }
  2747. xxxLBSetCaret(plb, TRUE);
  2748. return plb->iSel;
  2749. }
  2750. return LB_ERR;
  2751. }
  2752. /***************************************************************************\
  2753. * LBSetItemData
  2754. *
  2755. * Makes the item at index contain the data given.
  2756. *
  2757. * History:
  2758. * 16-Apr-1992 beng The NODATA listbox case
  2759. \***************************************************************************/
  2760. int LBSetItemData(
  2761. PLBIV plb,
  2762. int index,
  2763. LONG_PTR data)
  2764. {
  2765. LPSTR lpItemText;
  2766. /*
  2767. * v-ronaar: fix bug #25865, don't allow negative indices!
  2768. */
  2769. if ((index != -1) && ((UINT) index >= (UINT) plb->cMac)) {
  2770. RIPERR1(ERROR_INVALID_INDEX, RIP_WARNING, "LBSetItemData with invalid index %x", index);
  2771. return LB_ERR;
  2772. }
  2773. /*
  2774. * No-data listboxes just ignore all LB_SETITEMDATA calls
  2775. */
  2776. if (!plb->fHasData) {
  2777. return TRUE;
  2778. }
  2779. lpItemText = (LPSTR)plb->rgpch;
  2780. if (index == -1) {
  2781. /*
  2782. * index == -1 means set the data to all the items
  2783. */
  2784. if (plb->fHasStrings) {
  2785. for (index = 0; index < plb->cMac; index++) {
  2786. ((lpLBItem)lpItemText)->itemData = data;
  2787. lpItemText += sizeof(LBItem);
  2788. }
  2789. } else {
  2790. for (index = 0; index < plb->cMac; index++) {
  2791. ((lpLBODItem)lpItemText)->itemData = data;
  2792. lpItemText += sizeof(LBODItem);
  2793. }
  2794. }
  2795. return TRUE;
  2796. }
  2797. if (plb->fHasStrings) {
  2798. lpItemText = (LPSTR)(lpItemText + (index * sizeof(LBItem)));
  2799. ((lpLBItem)lpItemText)->itemData = data;
  2800. } else {
  2801. lpItemText = (LPSTR)(lpItemText + (index * sizeof(LBODItem)));
  2802. ((lpLBODItem)lpItemText)->itemData = data;
  2803. }
  2804. return TRUE;
  2805. }
  2806. /***************************************************************************\
  2807. * xxxCheckRedraw
  2808. *
  2809. * History:
  2810. \***************************************************************************/
  2811. void xxxCheckRedraw(
  2812. PLBIV plb,
  2813. BOOL fConditional,
  2814. INT sItem)
  2815. {
  2816. CheckLock(plb->spwnd);
  2817. if (fConditional && plb->cMac &&
  2818. (sItem > (plb->iTop + CItemInWindow(plb, TRUE))))
  2819. return;
  2820. /*
  2821. * Don't do anything if the parent is not visible.
  2822. */
  2823. xxxLBInvalidateRect(plb, (LPRECT)NULL, TRUE);
  2824. }
  2825. /***************************************************************************\
  2826. * xxxCaretDestroy
  2827. *
  2828. * History:
  2829. \***************************************************************************/
  2830. void xxxCaretDestroy(
  2831. PLBIV plb)
  2832. {
  2833. CheckLock(plb->spwnd);
  2834. /*
  2835. * We're losing the focus. Act like up clicks are happening so we release
  2836. * capture, set the current selection, notify the parent, etc.
  2837. */
  2838. if (plb->fCaptured)
  2839. /*
  2840. * If we have the capture and we lost the focus, that means we already
  2841. * changed the selection and we have to notify also the parent about
  2842. * this. So we need to add also the LBUP_SUCCESS flag in this case.
  2843. */
  2844. xxxLBButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY |
  2845. (plb->fMouseDown ? LBUP_SUCCESS : 0));
  2846. if (plb->fAddSelMode) {
  2847. /*
  2848. * Switch off the Caret blinking
  2849. */
  2850. NtUserKillTimer(HWq(plb->spwnd), IDSYS_CARET);
  2851. /*
  2852. * Make sure the caret goes away
  2853. */
  2854. xxxLBSetCaret(plb, FALSE);
  2855. plb->fAddSelMode = FALSE;
  2856. }
  2857. plb->fCaret = FALSE;
  2858. }
  2859. /***************************************************************************\
  2860. * xxxLbSetSel
  2861. *
  2862. * History:
  2863. \***************************************************************************/
  2864. LONG xxxLBSetSel(
  2865. PLBIV plb,
  2866. BOOL fSelect, /* New state to set selection to */
  2867. INT iSel)
  2868. {
  2869. INT sItem;
  2870. RECT rc;
  2871. UINT uEvent = 0;
  2872. CheckLock(plb->spwnd);
  2873. /*
  2874. * Bug 17656. WinZip's accelerator key for 'DeSelect All' sends a LB_SETSEL
  2875. * message with lparam = 0x0000ffff instead of 0xffffffff(-1). If iSel
  2876. * is equal to 0x0000ffff and there are less than 0xffff elements in the
  2877. * list we set iSel equal to 0xffffffff.
  2878. */
  2879. if ((iSel == (UINT)0xffff) && (iSel >= plb->cMac)) {
  2880. iSel = -1;
  2881. RIPMSG0(RIP_WARNING, "Sign extending iSel=0xffff to 0xffffffff");
  2882. }
  2883. if ((plb->wMultiple == SINGLESEL) || (iSel != -1 && iSel >= plb->cMac)) {
  2884. RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING,
  2885. "xxxLBSetSel:Invalid iSel or SINGLESEL listbox");
  2886. return LB_ERR;
  2887. }
  2888. xxxLBSetCaret(plb, FALSE);
  2889. if (iSel == -1/*(INT)0xffff*/) {
  2890. /*
  2891. * Set/clear selection from all items if -1
  2892. */
  2893. for (sItem = 0; sItem < plb->cMac; sItem++) {
  2894. if (IsSelected(plb, sItem, SELONLY) != fSelect) {
  2895. SetSelected(plb, sItem, fSelect, HILITEANDSEL);
  2896. if (LBGetItemRect(plb, sItem, &rc)) {
  2897. xxxLBInvalidateRect(plb, &rc, FALSE);
  2898. }
  2899. }
  2900. }
  2901. xxxLBSetCaret(plb, TRUE);
  2902. uEvent = EVENT_OBJECT_SELECTIONWITHIN;
  2903. } else {
  2904. if (fSelect) {
  2905. /*
  2906. * Check if the item if fully hidden and scroll it into view if it
  2907. * is. Note that we don't want to scroll partially visible items
  2908. * into full view because this breaks the shell...
  2909. */
  2910. xxxInsureVisible(plb, iSel, TRUE);
  2911. plb->iSelBase = plb->iSel = iSel;
  2912. plb->iMouseDown = plb->iLastMouseMove = iSel;
  2913. uEvent = EVENT_OBJECT_FOCUS;
  2914. } else {
  2915. uEvent = EVENT_OBJECT_SELECTIONREMOVE;
  2916. }
  2917. SetSelected(plb, iSel, fSelect, HILITEANDSEL);
  2918. /*
  2919. * Note that we set the caret on bit directly so that we avoid flicker
  2920. * when drawing this item. ie. We turn on the caret, redraw the item and
  2921. * turn it back on again.
  2922. */
  2923. if (!fSelect && plb->iSelBase != iSel) {
  2924. xxxLBSetCaret(plb, TRUE);
  2925. } else if (plb->fCaret) {
  2926. plb->fCaretOn = TRUE;
  2927. }
  2928. if (LBGetItemRect(plb, iSel, &rc)) {
  2929. xxxLBInvalidateRect(plb, &rc, FALSE);
  2930. }
  2931. }
  2932. /*
  2933. * We need to send this event even if the listbox isn't visible. See
  2934. * bug #88548. Also see 355612.
  2935. */
  2936. if (_IsWindowVisible(plb->spwnd) || (GetFocus() == HWq(plb->spwnd))) {
  2937. if (uEvent == EVENT_OBJECT_FOCUS) {
  2938. LBEvent(plb, uEvent, plb->iSelBase);
  2939. uEvent = EVENT_OBJECT_SELECTION;
  2940. }
  2941. LBEvent(plb, uEvent, iSel);
  2942. }
  2943. return 0;
  2944. }
  2945. /***************************************************************************\
  2946. * xxxLBoxDrawItem
  2947. *
  2948. * This fills the draw item struct with some constant data for the given
  2949. * item. The caller will only have to modify a small part of this data
  2950. * for specific needs.
  2951. *
  2952. * History:
  2953. * 16-Apr-1992 beng The NODATA case
  2954. \***************************************************************************/
  2955. void xxxLBoxDrawItem(
  2956. PLBIV plb,
  2957. INT item,
  2958. UINT itemAction,
  2959. UINT itemState,
  2960. LPRECT lprect)
  2961. {
  2962. DRAWITEMSTRUCT dis;
  2963. TL tlpwndParent;
  2964. CheckLock(plb->spwnd);
  2965. /*
  2966. * Fill the DRAWITEMSTRUCT with the unchanging constants
  2967. */
  2968. dis.CtlType = ODT_LISTBOX;
  2969. dis.CtlID = PtrToUlong(plb->spwnd->spmenu);
  2970. /*
  2971. * Use -1 if an invalid item number is being used. This is so that the app
  2972. * can detect if it should draw the caret (which indicates the lb has the
  2973. * focus) in an empty listbox
  2974. */
  2975. dis.itemID = (UINT)(item < plb->cMac ? item : -1);
  2976. dis.itemAction = itemAction;
  2977. dis.hwndItem = HWq(plb->spwnd);
  2978. dis.hDC = plb->hdc;
  2979. dis.itemState = itemState |
  2980. (UINT)(TestWF(plb->spwnd, WFDISABLED) ? ODS_DISABLED : 0);
  2981. if (TestWF(plb->spwnd, WEFPUIFOCUSHIDDEN)) {
  2982. dis.itemState |= ODS_NOFOCUSRECT;
  2983. }
  2984. if (TestWF(plb->spwnd, WEFPUIACCELHIDDEN)) {
  2985. dis.itemState |= ODS_NOACCEL;
  2986. }
  2987. /*
  2988. * Set the app supplied data
  2989. */
  2990. if (!plb->cMac || !plb->fHasData) {
  2991. /*
  2992. * If no strings or no items, just use 0 for data. This is so that we
  2993. * can display a caret when there are no items in the listbox.
  2994. *
  2995. * Lazy-eval listboxes of course have no data to pass - only itemID.
  2996. */
  2997. dis.itemData = 0L;
  2998. } else {
  2999. dis.itemData = LBGetItemData(plb, item);
  3000. }
  3001. CopyRect(&dis.rcItem, lprect);
  3002. /*
  3003. * Set the window origin to the horizontal scroll position. This is so that
  3004. * text can always be drawn at 0,0 and the view region will only start at
  3005. * the horizontal scroll offset. We pass this as wParam
  3006. */
  3007. /*
  3008. * Note: Only pass the itemID in wParam for 3.1 or newer apps. We break
  3009. * ccMail otherwise.
  3010. */
  3011. ThreadLock(plb->spwndParent, &tlpwndParent);
  3012. SendMessage(HW(plb->spwndParent), WM_DRAWITEM,
  3013. TestWF(plb->spwndParent, WFWIN31COMPAT) ? dis.CtlID : 0,
  3014. (LPARAM)&dis);
  3015. ThreadUnlock(&tlpwndParent);
  3016. }
  3017. /***************************************************************************\
  3018. * xxxLBBlockHilite
  3019. *
  3020. * In Extended selection mode for multiselection listboxes, when
  3021. * mouse is draged to a new position, the range being marked should be
  3022. * properly sized(parts of which will be highlighted/dehighlighted).
  3023. * NOTE: This routine assumes that iSelFromPt and LasMouseMove are not
  3024. * equal because only in that case this needs to be called;
  3025. * NOTE: This routine calculates the region whose display attribute is to
  3026. * be changed in an optimised way. Instead of de-highlighting the
  3027. * the old range completely and highlight the new range, it omits
  3028. * the regions that overlap and repaints only the non-pverlapping
  3029. * area.
  3030. * fKeyBoard = TRUE if this is called for Keyboard interface
  3031. * FALSE if called from Mouse interface routines
  3032. *
  3033. * History:
  3034. \***************************************************************************/
  3035. void xxxLBBlockHilite(
  3036. PLBIV plb,
  3037. INT iSelFromPt,
  3038. BOOL fKeyBoard)
  3039. {
  3040. INT sCurPosOffset;
  3041. INT sLastPosOffset;
  3042. INT sHiliteOrSel;
  3043. BOOL fUseSelStatus;
  3044. BOOL DeHiliteStatus;
  3045. CheckLock(plb->spwnd);
  3046. if (fKeyBoard) {
  3047. /*
  3048. * Set both Hilite and Selection states
  3049. */
  3050. sHiliteOrSel = HILITEANDSEL;
  3051. /*
  3052. * Do not use the Selection state while de-hiliting
  3053. */
  3054. fUseSelStatus = FALSE;
  3055. DeHiliteStatus = FALSE;
  3056. } else {
  3057. /*
  3058. * Set/Reset only the Hilite state
  3059. */
  3060. sHiliteOrSel = HILITEONLY;
  3061. /*
  3062. * Use the selection state for de-hilighting
  3063. */
  3064. fUseSelStatus = TRUE;
  3065. DeHiliteStatus = plb->fNewItemState;
  3066. }
  3067. /*
  3068. * The idea of the routine is to :
  3069. * 1. De-hilite the old range (iMouseDown to iLastMouseDown) and
  3070. * 2. Hilite the new range (iMouseDwon to iSelFromPt)
  3071. */
  3072. /*
  3073. * Offset of current mouse position from the anchor point
  3074. */
  3075. sCurPosOffset = plb->iMouseDown - iSelFromPt;
  3076. /*
  3077. * Offset of last mouse position from the anchor point
  3078. */
  3079. sLastPosOffset = plb->iMouseDown - plb->iLastMouseMove;
  3080. /*
  3081. * Check if both current position and last position lie on the same
  3082. * side of the anchor point.
  3083. */
  3084. if ((sCurPosOffset * sLastPosOffset) >= 0) {
  3085. /*
  3086. * Yes they are on the same side; So, highlight/dehighlight only
  3087. * the difference.
  3088. */
  3089. if (abs(sCurPosOffset) > abs(sLastPosOffset)) {
  3090. xxxAlterHilite(plb, plb->iLastMouseMove, iSelFromPt,
  3091. plb->fNewItemState, sHiliteOrSel, FALSE);
  3092. } else {
  3093. xxxAlterHilite(plb, iSelFromPt, plb->iLastMouseMove, DeHiliteStatus,
  3094. sHiliteOrSel, fUseSelStatus);
  3095. }
  3096. } else {
  3097. xxxAlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove,
  3098. DeHiliteStatus, sHiliteOrSel, fUseSelStatus);
  3099. xxxAlterHilite(plb, plb->iMouseDown, iSelFromPt,
  3100. plb->fNewItemState, sHiliteOrSel, FALSE);
  3101. }
  3102. }
  3103. /***************************************************************************\
  3104. * xxxAlterHilite
  3105. *
  3106. * Changes the hilite state of (i..j] (ie. excludes i, includes j in case
  3107. * you've forgotten this notation) to fHilite. It inverts this changes
  3108. * the hilite state.
  3109. *
  3110. * OpFlags: HILITEONLY Only change the display state of the items
  3111. * SELONLY Only Change the selection state of the items
  3112. * HILITEANDSELECT Do both.
  3113. * fHilite:
  3114. * HILITE/TRUE
  3115. * DEHILITE/FALSE
  3116. * fSelStatus:
  3117. * if TRUE, use the selection state of the item to hilite/dehilite
  3118. * if FALSE, use the fHilite parameter to hilite/dehilite
  3119. *
  3120. * History:
  3121. \***************************************************************************/
  3122. void xxxAlterHilite(
  3123. PLBIV plb,
  3124. INT i,
  3125. INT j,
  3126. BOOL fHilite,
  3127. INT OpFlags,
  3128. BOOL fSelStatus)
  3129. {
  3130. INT low;
  3131. INT high;
  3132. INT sLastInWindow;
  3133. BOOL fCaretOn;
  3134. BOOL fSelected;
  3135. CheckLock(plb->spwnd);
  3136. sLastInWindow = plb->iTop + CItemInWindow(plb, TRUE);
  3137. sLastInWindow = min(sLastInWindow, plb->cMac - 1);
  3138. high = max(i, j) + 1;
  3139. if (fCaretOn = plb->fCaretOn) {
  3140. xxxLBSetCaret(plb, FALSE);
  3141. }
  3142. for (low = min(i, j); low < high; low++) {
  3143. if (low != i) {
  3144. if (OpFlags & HILITEONLY) {
  3145. if (fSelStatus) {
  3146. fSelected = IsSelected(plb, low, SELONLY);
  3147. } else {
  3148. fSelected = fHilite;
  3149. }
  3150. if (IsSelected(plb, low, HILITEONLY) != fSelected) {
  3151. if (plb->iTop <= low && low <= sLastInWindow) {
  3152. /*
  3153. * Invert the item only if it is visible
  3154. */
  3155. xxxInvertLBItem(plb, low, fSelected);
  3156. }
  3157. SetSelected(plb, low, fSelected, HILITEONLY);
  3158. }
  3159. }
  3160. if (OpFlags & SELONLY) {
  3161. SetSelected(plb, low, fHilite, SELONLY);
  3162. }
  3163. }
  3164. }
  3165. if (fCaretOn) {
  3166. xxxLBSetCaret(plb, TRUE);
  3167. }
  3168. }