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.

4187 lines
113 KiB

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