|
|
/***************************************************************************\
* * LBOXCTL2.C - * * Copyright (c) 1985 - 1999, Microsoft Corporation * * List box handling routines * * 18-Dec-1990 ianja Ported from Win 3.0 sources * 14-Feb-1991 mikeke Added Revalidation code \***************************************************************************/
#include "precomp.h"
#pragma hdrstop
#define LB_KEYDOWN WM_USER+1
#define NOMODIFIER 0 /* No modifier is down */
#define SHIFTDOWN 1 /* Shift alone */
#define CTLDOWN 2 /* Ctl alone */
#define SHCTLDOWN (SHIFTDOWN + CTLDOWN) /* Ctrl + Shift */
/*
* Variables for incremental type search support */ #define MAX_TYPESEARCH 256
BOOL LBGetDC(PLBIV plb); void LBReleaseDC(PLBIV plb);
/***************************************************************************\
* * LBInvalidateRect() * * If the listbox is visible, invalidates a rectangle in the listbox. * If the listbox is not visible, sets the defer update flag for the listbox * \***************************************************************************/ BOOL xxxLBInvalidateRect(PLBIV plb, LPRECT lprc, BOOL fErase) { CheckLock(plb->spwnd);
if (IsLBoxVisible(plb)) { NtUserInvalidateRect(HWq(plb->spwnd), lprc, fErase); return(TRUE); }
if (!plb->fRedraw) plb->fDeferUpdate = TRUE;
return(FALSE); }
/***************************************************************************\
* * LBGetBrush() * * Gets background brush & colors for listbox. * \***************************************************************************/ HBRUSH xxxLBGetBrush(PLBIV plb, HBRUSH *phbrOld) { HBRUSH hbr; HBRUSH hbrOld; TL tlpwndParent;
CheckLock(plb->spwnd);
SetBkMode(plb->hdc, OPAQUE);
//
// Get brush & colors
//
if ((plb->spwnd->spwndParent == NULL) || (REBASEPWND(plb->spwnd, spwndParent) == _GetDesktopWindow())) { ThreadLock(plb->spwndParent, &tlpwndParent); hbr = GetControlColor(HW(plb->spwndParent), HWq(plb->spwnd), plb->hdc, WM_CTLCOLORLISTBOX); ThreadUnlock(&tlpwndParent); } else hbr = GetControlBrush(HWq(plb->spwnd), plb->hdc, WM_CTLCOLORLISTBOX);
//
// Select brush into dc
//
if (hbr != NULL) { hbrOld = SelectObject(plb->hdc, hbr); if (phbrOld) *phbrOld = hbrOld; }
return(hbr); }
/***************************************************************************\
* * LBInitDC() * * Initializes dc for listbox * \***************************************************************************/ void LBInitDC(PLBIV plb) { RECT rc;
// Set font
if (plb->hFont) SelectObject(plb->hdc, plb->hFont);
// Set clipping area
_GetClientRect(plb->spwnd, &rc); IntersectClipRect(plb->hdc, rc.left, rc.top, rc.right, rc.bottom);
OffsetWindowOrgEx(plb->hdc, plb->xOrigin, 0, NULL); }
/***************************************************************************\
* LBGetDC * * Returns a DC which can be used by a list box even if parentDC is in effect * * History: \***************************************************************************/
BOOL LBGetDC( PLBIV plb) { if (plb->hdc) return(FALSE);
plb->hdc = NtUserGetDC(HWq(plb->spwnd));
LBInitDC(plb);
return TRUE; }
/***************************************************************************\
* * LBTermDC() * * Cleans up when done with listbox dc. * \***************************************************************************/ void LBTermDC(PLBIV plb) { if (plb->hFont) SelectObject(plb->hdc, ghFontSys); }
/***************************************************************************\
* LBReleaseDC * * History: \***************************************************************************/
void LBReleaseDC( PLBIV plb) { LBTermDC(plb); NtUserReleaseDC(HWq(plb->spwnd), plb->hdc); plb->hdc = NULL; }
/***************************************************************************\
* LBGetItemRect * * Return the rectangle that the item will be drawn in with respect to the * listbox window. Returns TRUE if any portion of the item's rectangle * is visible (ie. in the listbox client rect) else returns FALSE. * * History: \***************************************************************************/
BOOL LBGetItemRect( PLBIV plb, INT sItem, LPRECT lprc) { INT sTmp; int clientbottom;
/*
* Always allow an item number of 0 so that we can draw the caret which * indicates the listbox has the focus even though it is empty.
* FreeHand 3.1 passes in -1 as the itemNumber and expects * a non-null rectangle. So we check for -1 specifically. * BUGTAG: Fix for Bug #540 --Win95B-- SANKAR -- 2/20/95 -- */
if (sItem && (sItem != -1) && ((UINT)sItem >= (UINT)plb->cMac)) { SetRectEmpty(lprc); RIPERR0(ERROR_INVALID_INDEX, RIP_VERBOSE, ""); return (LB_ERR); }
_GetClientRect(plb->spwnd, lprc);
if (plb->fMultiColumn) {
/*
* itemHeight * sItem mod number ItemsPerColumn (itemsPerColumn) */ lprc->top = plb->cyChar * (sItem % plb->itemsPerColumn); lprc->bottom = lprc->top + plb->cyChar /*+(plb->OwnerDraw ? 0 : 1)*/;
UserAssert(plb->itemsPerColumn);
if (plb->fRightAlign) { lprc->right = lprc->right - plb->cxColumn * ((sItem / plb->itemsPerColumn) - (plb->iTop / plb->itemsPerColumn));
lprc->left = lprc->right - plb->cxColumn; } else { /*
* Remember, this is integer division here... */ lprc->left += plb->cxColumn * ((sItem / plb->itemsPerColumn) - (plb->iTop / plb->itemsPerColumn));
lprc->right = lprc->left + plb->cxColumn; } } else if (plb->OwnerDraw == OWNERDRAWVAR) {
/*
* Var height owner draw */ lprc->right += plb->xOrigin; clientbottom = lprc->bottom;
if (sItem >= plb->iTop) { for (sTmp = plb->iTop; sTmp < sItem; sTmp++) { lprc->top = lprc->top + LBGetVariableHeightItemHeight(plb, sTmp); }
/*
* If item number is 0, it may be we are asking for the rect * associated with a nonexistant item so that we can draw a caret * indicating focus on an empty listbox. */ lprc->bottom = lprc->top + (sItem < plb->cMac ? LBGetVariableHeightItemHeight(plb, sItem) : plb->cyChar); return (lprc->top < clientbottom); } else {
/*
* Item we want the rect of is before plb->iTop. Thus, negative * offsets for the rect and it is never visible. */ for (sTmp = sItem; sTmp < plb->iTop; sTmp++) { lprc->top = lprc->top - LBGetVariableHeightItemHeight(plb, sTmp); } lprc->bottom = lprc->top + LBGetVariableHeightItemHeight(plb, sItem); return FALSE; } } else {
/*
* For fixed height listboxes */ if (plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw) && plb->fHorzBar) lprc->right += plb->xOrigin + (plb->xRightOrigin - plb->xOrigin); else lprc->right += plb->xOrigin; lprc->top = (sItem - plb->iTop) * plb->cyChar; lprc->bottom = lprc->top + plb->cyChar; }
return (sItem >= plb->iTop) && (sItem < (plb->iTop + CItemInWindow(plb, TRUE))); }
/***************************************************************************\
* * LBPrintCallback * * Called back from DrawState() * \***************************************************************************/ BOOL CALLBACK LBPrintCallback( HDC hdc, LPARAM lData, WPARAM wData, int cx, int cy) { LPWSTR lpstr = (LPWSTR)lData; PLBIV plb = (PLBIV)wData; int xStart; UINT cLen; RECT rc; UINT oldAlign;
if (!lpstr) { return FALSE; }
if (plb->fMultiColumn) xStart = 0; else xStart = 2;
if (plb->fRightAlign) { oldAlign = SetTextAlign(hdc, TA_RIGHT | GetTextAlign(hdc)); xStart = cx - xStart; }
cLen = wcslen(lpstr);
if (plb->fUseTabStops) { TabTextOut(hdc, xStart, 0, lpstr, cLen, (plb->iTabPixelPositions ? plb->iTabPixelPositions[0] : 0), (plb->iTabPixelPositions ? (LPINT)&plb->iTabPixelPositions[1] : NULL), plb->fRightAlign ? cx : 0, TRUE, GetTextCharset(plb->hdc)); } else { rc.left = 0; rc.top = 0; rc.right = cx; rc.bottom = cy;
if (plb->wMultiple) ExtTextOut(hdc, xStart, 0, ETO_OPAQUE, &rc, lpstr, cLen, NULL); else if (plb->fMultiColumn) ExtTextOut(hdc, xStart, 0, ETO_CLIPPED, &rc, lpstr, cLen, NULL); else { ExtTextOut(hdc, xStart, 0, 0, NULL, lpstr, cLen, NULL);
/*
* When the listbox is in the incremental search mode and the item * is highlighted (so we only draw in the current item), draw the * caret for search indication. */ if ((plb->iTypeSearch != 0) && (plb->OwnerDraw == 0) && (GetBkColor(hdc) == SYSRGB(HIGHLIGHT))) { SIZE size; GetTextExtentPointW(hdc, lpstr, plb->iTypeSearch, &size); PatBlt(hdc, xStart + size.cx - 1, 1, 1, cy - 2, DSTINVERT); } } }
if (plb->fRightAlign) SetTextAlign(hdc, oldAlign);
return(TRUE); }
/***************************************************************************\
* xxxLBDrawLBItem * * History: \***************************************************************************/
void xxxLBDrawLBItem( PLBIV plb, INT sItem, LPRECT lprect, BOOL fHilite, HBRUSH hbr) { LPWSTR lpstr; DWORD rgbSave; DWORD rgbBkSave; UINT uFlags; HDC hdc = plb->hdc; UINT oldAlign;
CheckLock(plb->spwnd);
/*
* If the item is selected, then fill with highlight color */ if (fHilite) { FillRect(hdc, lprect, SYSHBR(HIGHLIGHT)); rgbBkSave = SetBkColor(hdc, SYSRGB(HIGHLIGHT)); rgbSave = SetTextColor(hdc, SYSRGB(HIGHLIGHTTEXT)); } else {
/*
* If fUseTabStops, we must fill the background, because later we use * LBTabTheTextOutForWimps(), which fills the background only partially * Fix for Bug #1509 -- 01/25/91 -- SANKAR -- */ if ((hbr != NULL) && ((sItem == plb->iSelBase) || (plb->fUseTabStops))) { FillRect(hdc, lprect, hbr); } }
uFlags = DST_COMPLEX; lpstr = GetLpszItem(plb, sItem);
if (TestWF(plb->spwnd, WFDISABLED)) { if ((COLORREF)SYSRGB(GRAYTEXT) != GetBkColor(hdc)) SetTextColor(hdc, SYSRGB(GRAYTEXT)); else uFlags |= DSS_UNION; }
if (plb->fRightAlign) uFlags |= DSS_RIGHT;
if (plb->fRtoLReading) oldAlign = SetTextAlign(hdc, TA_RTLREADING | GetTextAlign(hdc));
DrawState(hdc, SYSHBR(WINDOWTEXT), LBPrintCallback, (LPARAM)lpstr, (WPARAM)plb, lprect->left, lprect->top, lprect->right-lprect->left, lprect->bottom-lprect->top, uFlags);
if (plb->fRtoLReading) SetTextAlign(hdc, oldAlign);
if (fHilite) { SetTextColor(hdc, rgbSave); SetBkColor(hdc, rgbBkSave); } }
/***************************************************************************\
* * LBSetCaret() * \***************************************************************************/ void xxxLBSetCaret(PLBIV plb, BOOL fSetCaret) { RECT rc; BOOL fNewDC;
if (plb->fCaret && ((BOOL) plb->fCaretOn != !!fSetCaret)) { if (IsLBoxVisible(plb)) { /* Turn the caret (located at plb->iSelBase) on */ fNewDC = LBGetDC(plb);
LBGetItemRect(plb, plb->iSelBase, &rc);
if (fNewDC) { SetBkColor(plb->hdc, SYSRGB(WINDOW)); SetTextColor(plb->hdc, SYSRGB(WINDOWTEXT)); }
if (plb->OwnerDraw) { /* Fill in the drawitem struct */ UINT itemState = (fSetCaret) ? ODS_FOCUS : 0;
if (IsSelected(plb, plb->iSelBase, HILITEONLY)) itemState |= ODS_SELECTED;
xxxLBoxDrawItem(plb, plb->iSelBase, ODA_FOCUS, itemState, &rc); } else if (!TestWF(plb->spwnd, WEFPUIFOCUSHIDDEN)) { COLORREF crBk = SetBkColor(plb->hdc, SYSRGB(WINDOW)); COLORREF crText = SetTextColor(plb->hdc, SYSRGB(WINDOWTEXT));
DrawFocusRect(plb->hdc, &rc);
SetBkColor(plb->hdc, crBk); SetTextColor(plb->hdc, crText); }
if (fNewDC) LBReleaseDC(plb); } plb->fCaretOn = !!fSetCaret; } }
/***************************************************************************\
* IsSelected * * History: * 16-Apr-1992 beng The NODATA listbox case \***************************************************************************/
BOOL IsSelected( PLBIV plb, INT sItem, UINT wOpFlags) { LPBYTE lp;
if ((sItem >= plb->cMac) || (sItem < 0)) { RIPERR0(ERROR_INVALID_INDEX, RIP_VERBOSE, ""); // return LB_ERR;
return(FALSE); }
if (plb->wMultiple == SINGLESEL) { return (sItem == plb->iSel); }
lp = plb->rgpch + sItem + (plb->cMac * (plb->fHasStrings ? sizeof(LBItem) : (plb->fHasData ? sizeof(LBODItem) : 0))); sItem = *lp;
if (wOpFlags == HILITEONLY) { sItem >>= 4; } else { sItem &= 0x0F; /* SELONLY */ }
return sItem; }
/***************************************************************************\
* CItemInWindow * * Returns the number of items which can fit in a list box. It * includes the partially visible one at the bottom if fPartial is TRUE. For * var height ownerdraw, return the number of items visible starting at iTop * and going to the bottom of the client rect. * * History: \***************************************************************************/
INT CItemInWindow( PLBIV plb, BOOL fPartial) { RECT rect;
if (plb->OwnerDraw == OWNERDRAWVAR) { return CItemInWindowVarOwnerDraw(plb, fPartial); }
if (plb->fMultiColumn) { return plb->itemsPerColumn * (plb->numberOfColumns + (fPartial ? 1 : 0)); }
_GetClientRect(plb->spwnd, &rect);
/*
* fPartial must be considered only if the listbox height is not an * integral multiple of character height. * A part of the fix for Bug #3727 -- 01/14/91 -- SANKAR -- */ UserAssert(plb->cyChar); return (INT)((rect.bottom / plb->cyChar) + ((rect.bottom % plb->cyChar)? (fPartial ? 1 : 0) : 0)); }
/***************************************************************************\
* xxxLBoxCtlScroll * * Handles vertical scrolling of the listbox * * History: \***************************************************************************/
void xxxLBoxCtlScroll( PLBIV plb, INT cmd, int yAmt) { INT iTopNew; INT cItemPageScroll; DWORD dwTime = 0;
CheckLock(plb->spwnd);
if (plb->fMultiColumn) {
/*
* Don't allow vertical scrolling on a multicolumn list box. Needed * in case app sends WM_VSCROLL messages to the listbox. */ return; }
cItemPageScroll = plb->cItemFullMax;
if (cItemPageScroll > 1) cItemPageScroll--;
if (plb->cMac) { iTopNew = plb->iTop; switch (cmd) { case SB_LINEUP: dwTime = yAmt; iTopNew--; break;
case SB_LINEDOWN: dwTime = yAmt; iTopNew++; break;
case SB_PAGEUP: if (plb->OwnerDraw == OWNERDRAWVAR) { iTopNew = LBPage(plb, plb->iTop, FALSE); } else { iTopNew -= cItemPageScroll; } break;
case SB_PAGEDOWN: if (plb->OwnerDraw == OWNERDRAWVAR) { iTopNew = LBPage(plb, plb->iTop, TRUE); } else { iTopNew += cItemPageScroll; } break;
case SB_THUMBTRACK: case SB_THUMBPOSITION: {
/*
* If the listbox contains more than 0xFFFF items * it means that the scrolbar can return a position * that cannot fit in a WORD (16 bits), so use * GetScrollInfo (which is slower) in this case. */ if (plb->cMac < 0xFFFF) { iTopNew = yAmt; } else { SCROLLINFO si;
si.cbSize = sizeof(SCROLLINFO); si.fMask = SIF_TRACKPOS;
GetScrollInfo( HWq(plb->spwnd), SB_VERT, &si);
iTopNew = si.nTrackPos; } break; } case SB_TOP: iTopNew = 0; break;
case SB_BOTTOM: iTopNew = plb->cMac - 1; break;
case SB_ENDSCROLL: plb->fSmoothScroll = TRUE; xxxLBSetCaret(plb, FALSE); xxxLBShowHideScrollBars(plb); xxxLBSetCaret(plb, TRUE); return; }
xxxNewITopEx(plb, iTopNew, dwTime); } }
/***************************************************************************\
* LBGetScrollFlags * \***************************************************************************/
DWORD LBGetScrollFlags(PLBIV plb, DWORD dwTime) { DWORD dwFlags;
if (GetAppCompatFlags(NULL) & GACF_NOSMOOTHSCROLLING) goto NoSmoothScrolling;
if (dwTime != 0) { dwFlags = MAKELONG(SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN, dwTime); } else if (TEST_EffectPUSIF(PUSIF_LISTBOXSMOOTHSCROLLING) && plb->fSmoothScroll) { dwFlags = SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN; plb->fSmoothScroll = FALSE; } else { NoSmoothScrolling: dwFlags = SW_SCROLLWINDOW | SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN; }
return dwFlags; }
/***************************************************************************\
* xxxLBoxCtlHScroll * * Supports horizontal scrolling of listboxes * * History: \***************************************************************************/
void xxxLBoxCtlHScroll( PLBIV plb, INT cmd, int xAmt) { int newOrigin = plb->xOrigin; int oldOrigin = plb->xOrigin; int windowWidth; RECT rc; DWORD dwTime = 0;
CheckLock(plb->spwnd);
/*
* Update the window so that we don't run into problems with invalid * regions during the horizontal scroll. */ if (plb->fMultiColumn) {
/*
* Handle multicolumn scrolling in a separate segment */ xxxLBoxCtlHScrollMultiColumn(plb, cmd, xAmt); return; }
_GetClientRect(plb->spwnd, &rc); windowWidth = rc.right;
if (plb->cMac) {
switch (cmd) { case SB_LINEUP: dwTime = xAmt; newOrigin -= plb->cxChar; break;
case SB_LINEDOWN: dwTime = xAmt; newOrigin += plb->cxChar; break;
case SB_PAGEUP: newOrigin -= (windowWidth / 3) * 2; break;
case SB_PAGEDOWN: newOrigin += (windowWidth / 3) * 2; break;
case SB_THUMBTRACK: case SB_THUMBPOSITION: newOrigin = xAmt; break;
case SB_TOP: newOrigin = 0; break;
case SB_BOTTOM: newOrigin = plb->maxWidth; break;
case SB_ENDSCROLL: plb->fSmoothScroll = TRUE; xxxLBSetCaret(plb, FALSE); xxxLBShowHideScrollBars(plb); xxxLBSetCaret(plb, TRUE); return; }
xxxLBSetCaret(plb, FALSE); plb->xOrigin = newOrigin; plb->xOrigin = xxxSetLBScrollParms(plb, SB_HORZ);
if ((cmd == SB_BOTTOM) && plb->fRightAlign) { /*
* so we know where to draw from. */ plb->xRightOrigin = plb->xOrigin; }
if(oldOrigin != plb->xOrigin) { HWND hwnd = HWq(plb->spwnd); DWORD dwFlags;
dwFlags = LBGetScrollFlags(plb, dwTime); ScrollWindowEx(hwnd, oldOrigin-plb->xOrigin, 0, NULL, &rc, NULL, NULL, dwFlags); UpdateWindow(hwnd); }
xxxLBSetCaret(plb, TRUE); } else { // this is a less-than-ideal fix for ImageMind ScreenSaver (Win95
// B#8252) but it works and it doesn't hurt anybody -- JEFFBOG 10/28/94
xxxSetLBScrollParms(plb, SB_HORZ); } }
/***************************************************************************\
* xxxLBoxCtlPaint * * History: \***************************************************************************/
void xxxLBPaint( PLBIV plb, HDC hdc, LPRECT lprcBounds) { INT i; RECT rect; RECT scratchRect; BOOL fHilite; INT iLastItem; HBRUSH hbrSave = NULL; HBRUSH hbrControl; BOOL fCaretOn; RECT rcBounds; HDC hdcSave;
CheckLock(plb->spwnd);
if (lprcBounds == NULL) { lprcBounds = &rcBounds; _GetClientRect(plb->spwnd, lprcBounds); }
hdcSave = plb->hdc; plb->hdc = hdc;
// Initialize dc.
LBInitDC(plb);
// Turn caret off
if (fCaretOn = plb->fCaretOn) xxxLBSetCaret(plb, FALSE);
hbrSave = NULL; hbrControl = xxxLBGetBrush(plb, &hbrSave);
// Get listbox's client
_GetClientRect(plb->spwnd, &rect);
// Adjust width of client rect for scrolled amount
// fix for #140, t-arthb
if (plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw) && plb->fHorzBar) rect.right += plb->xOrigin + (plb->xRightOrigin - plb->xOrigin); else rect.right += plb->xOrigin;
// Get the index of the last item visible on the screen. This is also
// valid for var height ownerdraw.
iLastItem = plb->iTop + CItemInWindow(plb,TRUE); iLastItem = min(iLastItem, plb->cMac - 1);
// Fill in the background of the listbox if it's an empty listbox
// or if we're doing a control print
if (iLastItem == -1) FillRect(plb->hdc, &rect, hbrControl);
// Allow AnimateWindow() catch the apps that do not use our DC when
// drawing the list box
SetBoundsRect(plb->hdc, NULL, DCB_RESET | DCB_ENABLE);
for (i = plb->iTop; i <= iLastItem; i++) {
/*
* Note that rect contains the clientrect from when we did the * _GetClientRect so the width is correct. We just need to adjust * the top and bottom of the rectangle to the item of interest. */ rect.bottom = rect.top + plb->cyChar;
if ((UINT)i < (UINT)plb->cMac) {
/*
* If var height, get the rectangle for the item. */ if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn) { LBGetItemRect(plb, i, &rect); }
if (IntersectRect(&scratchRect, lprcBounds, &rect)) { fHilite = !plb->fNoSel && IsSelected(plb, i, HILITEONLY);
if (plb->OwnerDraw) {
/*
* Fill in the drawitem struct */ xxxLBoxDrawItem(plb, i, ODA_DRAWENTIRE, (UINT)(fHilite ? ODS_SELECTED : 0), &rect); } else { xxxLBDrawLBItem(plb, i, &rect, fHilite, hbrControl); } } } rect.top = rect.bottom; }
if (hbrSave != NULL) SelectObject(hdc, hbrSave);
if (fCaretOn) xxxLBSetCaret(plb, TRUE);
LBTermDC(plb);
plb->hdc = hdcSave; }
/***************************************************************************\
* ISelFromPt * * In the loword, returns the closest item number the pt is on. The high * word is 0 if the point is within bounds of the listbox client rect and is * 1 if it is outside the bounds. This will allow us to make the invertrect * disappear if the mouse is outside the listbox yet we can still show the * outline around the item that would be selected if the mouse is brought back * in bounds... * * History: \***************************************************************************/
BOOL ISelFromPt( PLBIV plb, POINT pt, LPDWORD piItem) { RECT rect; int y; UINT mouseHighWord = 0; INT sItem; INT sTmp;
_GetClientRect(plb->spwnd, &rect);
if (pt.y < 0) {
/*
* Mouse is out of bounds above listbox */ *piItem = plb->iTop; return TRUE; } else if ((y = pt.y) > rect.bottom) { y = rect.bottom; mouseHighWord = 1; }
if (pt.x < 0 || pt.x > rect.right) mouseHighWord = 1;
/*
* Now just need to check if y mouse coordinate intersects item's rectangle */ if (plb->OwnerDraw != OWNERDRAWVAR) { if (plb->fMultiColumn) { if (y < plb->itemsPerColumn * plb->cyChar) { if (plb->fRightAlign) sItem = plb->iTop + (INT)((y / plb->cyChar) + ((rect.right - pt.x) / plb->cxColumn) * plb->itemsPerColumn); else sItem = plb->iTop + (INT)((y / plb->cyChar) + (pt.x / plb->cxColumn) * plb->itemsPerColumn);
} else {
/*
* User clicked in blank space at the bottom of a column. * Just select the last item in the column. */ mouseHighWord = 1; sItem = plb->iTop + (plb->itemsPerColumn - 1) + (INT)((pt.x / plb->cxColumn) * plb->itemsPerColumn); } } else { sItem = plb->iTop + (INT)(y / plb->cyChar); } } else {
/*
* VarHeightOwnerdraw so we gotta do this the hardway... Set the x * coordinate of the mouse down point to be inside the listbox client * rectangle since we no longer care about it. This lets us use the * point in rect calls. */ pt.x = 8; pt.y = y; for (sTmp = plb->iTop; sTmp < plb->cMac; sTmp++) { (void)LBGetItemRect(plb, sTmp, &rect); if (PtInRect(&rect, pt)) { *piItem = sTmp; return mouseHighWord; } }
/*
* Point was at the empty area at the bottom of a not full listbox */ *piItem = plb->cMac - 1; return mouseHighWord; }
/*
* Check if user clicked on the blank area at the bottom of a not full list. * Assumes > 0 items in the listbox. */ if (sItem > plb->cMac - 1) { mouseHighWord = 1; sItem = plb->cMac - 1; }
*piItem = sItem; return mouseHighWord; }
/***************************************************************************\
* SetSelected * * This is used for button initiated changes of selection state. * * fSelected : TRUE if the item is to be set as selected, FALSE otherwise * * wOpFlags : HILITEONLY = Modify only the Display state (hi-nibble) * SELONLY = Modify only the Selection state (lo-nibble) * HILITEANDSEL = Modify both of them; * * History: * 16-Apr-1992 beng The NODATA listbox case \***************************************************************************/
void SetSelected( PLBIV plb, INT iSel, BOOL fSelected, UINT wOpFlags) { LPSTR lp; BYTE cMask; BYTE cSelStatus;
if (iSel < 0 || iSel >= plb->cMac) return;
if (plb->wMultiple == SINGLESEL) { if (fSelected) plb->iSel = iSel; } else { cSelStatus = (BYTE)fSelected; switch (wOpFlags) { case HILITEONLY:
/*
* Mask out lo-nibble */ cSelStatus = (BYTE)(cSelStatus << 4); cMask = 0x0F; break; case SELONLY:
/*
* Mask out hi-nibble */ cMask = 0xF0; break; case HILITEANDSEL:
/*
* Mask the byte fully */ cSelStatus |= (cSelStatus << 4); cMask = 0; break; } lp = (LPSTR)(plb->rgpch) + iSel + (plb->cMac * (plb->fHasStrings ? sizeof(LBItem) : (plb->fHasData ? sizeof(LBODItem) : 0)));
*lp = (*lp & cMask) | cSelStatus; } }
/***************************************************************************\
* LastFullVisible * * Returns the last fully visible item in the listbox. This is valid * for ownerdraw var height and fixed height listboxes. * * History: \***************************************************************************/
INT LastFullVisible( PLBIV plb) { INT iLastItem;
if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn) { iLastItem = plb->iTop + CItemInWindow(plb, FALSE) - 1; iLastItem = max(iLastItem, plb->iTop); } else { iLastItem = min(plb->iTop + plb->cItemFullMax - 1, plb->cMac - 1); } return iLastItem; }
/***************************************************************************\
* xxxInvertLBItem * * History: \***************************************************************************/
void xxxInvertLBItem( PLBIV plb, INT i, BOOL fHilite) /* The new selection state of the item */ { RECT rect; BOOL fCaretOn; HBRUSH hbrControl; BOOL fNewDC;
CheckLock(plb->spwnd);
// Skip if item isn't showing.
if (plb->fNoSel || (i < plb->iTop) || (i >= (plb->iTop + CItemInWindow(plb, TRUE)))) return;
if (IsLBoxVisible(plb)) { LBGetItemRect(plb, i, &rect);
/*
* Only turn off the caret if it is on. This avoids annoying caret * flicker when nesting xxxCaretOns and xxxCaretOffs. */ if (fCaretOn = plb->fCaretOn) { xxxLBSetCaret(plb, FALSE); }
fNewDC = LBGetDC(plb);
hbrControl = xxxLBGetBrush(plb, NULL);
if (!plb->OwnerDraw) { if (!fHilite) { FillRect(plb->hdc, &rect, hbrControl); hbrControl = NULL; }
xxxLBDrawLBItem(plb, i, &rect, fHilite, hbrControl); } else {
/*
* We are ownerdraw so fill in the drawitem struct and send off * to the owner. */ xxxLBoxDrawItem(plb, i, ODA_SELECT, (UINT)(fHilite ? ODS_SELECTED : 0), &rect); }
if (fNewDC) LBReleaseDC(plb);
/*
* Turn the caret back on only if it was originally on. */ if (fCaretOn) { xxxLBSetCaret(plb, TRUE); } } }
/***************************************************************************\
* xxxResetWorld * * Resets everyone's selection and hilite state except items in the * range sStItem to sEndItem (Both inclusive). * * History: \***************************************************************************/
void xxxResetWorld( PLBIV plb, INT iStart, INT iEnd, BOOL fSelect) { INT i; INT iLastInWindow; BOOL fCaretOn;
CheckLock(plb->spwnd);
/*
* If iStart and iEnd are not in correct order we swap them */
if (iStart > iEnd) { i = iStart; iStart = iEnd; iEnd = i; }
if (plb->wMultiple == SINGLESEL) { if (plb->iSel != -1 && ((plb->iSel < iStart) || (plb->iSel > iEnd))) { xxxInvertLBItem(plb, plb->iSel, fSelect); plb->iSel = -1; } return; }
iLastInWindow = plb->iTop + CItemInWindow(plb, TRUE);
if (fCaretOn = plb->fCaretOn) xxxLBSetCaret(plb, FALSE);
for (i = 0; i < plb->cMac; i++) { if (i == iStart) // skip range to be preserved
i = iEnd; else { if ((plb->iTop <= i) && (i <= iLastInWindow) && (fSelect != IsSelected(plb, i, HILITEONLY))) // Only invert the item if it is visible and present Selection
// state is different from what is required.
xxxInvertLBItem(plb, i, fSelect);
// Set all items outside of preserved range to unselected
SetSelected(plb, i, fSelect, HILITEANDSEL); } }
if (fCaretOn) xxxLBSetCaret(plb, TRUE);
}
/***************************************************************************\
* xxxNotifyOwner * * History: \***************************************************************************/
void xxxNotifyOwner( PLBIV plb, INT sEvt) { TL tlpwndParent;
CheckLock(plb->spwnd);
ThreadLock(plb->spwndParent, &tlpwndParent); SendMessage(HW(plb->spwndParent), WM_COMMAND, MAKELONG(PTR_TO_ID(plb->spwnd->spmenu), sEvt), (LPARAM)HWq(plb->spwnd)); ThreadUnlock(&tlpwndParent); }
/***************************************************************************\
* xxxSetISelBase * * History: \***************************************************************************/
void xxxSetISelBase( PLBIV plb, INT sItem) { CheckLock(plb->spwnd);
xxxLBSetCaret(plb, FALSE); plb->iSelBase = sItem; xxxLBSetCaret(plb, TRUE);
xxxInsureVisible(plb, plb->iSelBase, FALSE); /*
* We need to send this event even if the listbox isn't visible. See * bug #88548. Also see 355612. */ if (_IsWindowVisible(plb->spwnd) || (GetFocus() == HWq(plb->spwnd))) { LBEvent(plb, EVENT_OBJECT_FOCUS, sItem); } }
/***************************************************************************\
* xxxTrackMouse * * History: \***************************************************************************/
void xxxTrackMouse( PLBIV plb, UINT wMsg, POINT pt) { INT iSelFromPt; INT iSelTemp; BOOL mousetemp; BOOL fMouseInRect; RECT rcClient; UINT wModifiers = 0; BOOL fSelected; UINT uEvent = 0; INT trackPtRetn; HWND hwnd = HWq(plb->spwnd); TL tlpwndEdit; TL tlpwndParent;
CheckLock(plb->spwnd);
/*
* Optimization: do nothing if mouse not captured */ if ((wMsg != WM_LBUTTONDOWN) && (wMsg != WM_LBUTTONDBLCLK)) { if (!plb->fCaptured) { return; } /*
* If we are processing a WM_MOUSEMOVE but the mouse has not moved * from the previous point, then we may be dealing with a mouse * "jiggle" sent from the kernel (see zzzInvalidateDCCache). If we * process this, we will snap the listbox selection back to where * the mouse cursor is pointing, even if the user has not touched * the mouse. Windows NT Bug #220722. * * Some apps (like MSMoney98) rely on this, so we need to check the * SRVIF_LASTRITWASKEYBOARD flag. Windows NT Bug #244450. */ if (wMsg == WM_MOUSEMOVE && RtlEqualMemory(&pt, &(plb->ptPrev), sizeof(POINT)) && TEST_SRVIF(SRVIF_LASTRITWASKEYBOARD)) { RIPMSG0(RIP_WARNING, "xxxTrackMouse ignoring WM_MOUSEMOVE with no mouse movement"); return; } }
mousetemp = ISelFromPt(plb, pt, &iSelFromPt);
/*
* If we allow the user to cancel his selection then fMouseInRect is true if * the mouse is in the listbox client area otherwise it is false. If we * don't allow the user to cancel his selection, then fMouseInRect will * always be true. This allows us to implement cancelable selection * listboxes ie. The selection reverts to the origional one if the user * releases the mouse outside of the listbox. */ fMouseInRect = !mousetemp || !plb->pcbox;
_GetClientRect(plb->spwnd, &rcClient);
switch (wMsg) { case WM_LBUTTONDBLCLK: case WM_LBUTTONDOWN: /*
* We want to divert mouse clicks. If the user clicks outside * of a dropped down listbox, we want to popup it up, using * the current selection. */ if (plb->fCaptured) { /*
* If plb->pcbox is NULL, this is a listbox that * received a WM_LBUTTONDOWN again w/o receiving * a WM_LBUTTONUP for the previous WM_LBUTTONDOWN * bug */ if (plb->pcbox && mousetemp) { _ClientToScreen(plb->spwnd, &pt);
if (!PtInRect(KPRECT_TO_PRECT(&plb->spwnd->rcWindow), pt)) { /*
* Cancel selection if clicked outside of combo; * Accept if clicked on combo button or item. */ xxxCBHideListBoxWindow(plb->pcbox, TRUE, FALSE); } else if (!PtInRect(KPRECT_TO_PRECT(&plb->spwnd->rcClient), pt)) { /*
* Let it pass through. Save, restore capture in * case user is clicking on scrollbar. */ plb->fCaptured = FALSE; NtUserReleaseCapture();
SendMessageWorker(plb->spwnd, WM_NCLBUTTONDOWN, FindNCHit(plb->spwnd, POINTTOPOINTS(pt)), MAKELONG(pt.x, pt.y), FALSE);
NtUserSetCapture(hwnd); plb->fCaptured = TRUE; }
break; }
plb->fCaptured = FALSE; NtUserReleaseCapture(); }
if (plb->pcbox) {
/*
* If this listbox is in a combo box, set the focus to the combo * box window so that the edit control/static text is also * activated */ ThreadLock(plb->pcbox->spwndEdit, &tlpwndEdit); NtUserSetFocus(HWq(plb->pcbox->spwndEdit)); ThreadUnlock(&tlpwndEdit); } else {
/*
* Get the focus if the listbox is clicked in and we don't * already have the focus. If we don't have the focus after * this, run away... */ NtUserSetFocus(hwnd); if (!plb->fCaret) return; }
if (plb->fAddSelMode) {
/*
* If it is in "Add" mode, quit it using shift f8 key... * However, since we can't send shift key state, we have to turn * this off directly... */
/*
*SendMessage(HW(plb->spwnd),WM_KEYDOWN, (UINT)VK_F8, 0L); */
/*
* Switch off the Caret blinking */ NtUserKillTimer(hwnd, IDSYS_CARET);
/*
* Make sure the caret does not vanish */ xxxLBSetCaret(plb, TRUE); plb->fAddSelMode = FALSE; }
if (!plb->cMac) {
/*
* Don't even bother handling the mouse if no items in the * listbox since the code below assumes >0 items in the * listbox. We will just get the focus (the statement above) if * we don't already have it. */ break; }
if (mousetemp) {
/*
* Mouse down occurred in a empty spot. Just ignore it. */ break; }
plb->fDoubleClick = (wMsg == WM_LBUTTONDBLCLK);
if (!plb->fDoubleClick) {
/*
* This hack put in for the shell. Tell the shell where in the * listbox the user clicked and at what item number. The shell * can return 0 to continue normal mouse tracking or TRUE to * abort mouse tracking. */ ThreadLock(plb->spwndParent, &tlpwndParent); trackPtRetn = (INT)SendMessage(HW(plb->spwndParent), WM_LBTRACKPOINT, (DWORD)iSelFromPt, MAKELONG(pt.x+plb->xOrigin, pt.y)); ThreadUnlock(&tlpwndParent); if (trackPtRetn) { if (trackPtRetn == 2) {
/*
* Ignore double clicks */ NtUserCallNoParam(SFI__RESETDBLCLK); } return; } }
if (plb->pcbox) {
/*
* Save the last selection if this is a combo box. So that it * can be restored if user decides to cancel the selection by up * clicking outside the listbox. */ plb->iLastSelection = plb->iSel; }
/*
* Save for timer */ plb->ptPrev = pt;
plb->fMouseDown = TRUE; NtUserSetCapture(hwnd); plb->fCaptured = TRUE;
if (plb->fDoubleClick) {
/*
* Double click. Fake a button up and exit */ xxxTrackMouse(plb, WM_LBUTTONUP, pt); return; }
/*
* Set the system timer so that we can autoscroll if the mouse is * outside the bounds of the listbox rectangle */ NtUserSetTimer(hwnd, IDSYS_SCROLL, gpsi->dtScroll, NULL);
/*
* If extended multiselection listbox, are any modifier key pressed? */ if (plb->wMultiple == EXTENDEDSEL) { if (GetKeyState(VK_SHIFT) < 0) wModifiers = SHIFTDOWN; if (GetKeyState(VK_CONTROL) < 0) wModifiers += CTLDOWN;
/*
* Please Note that (SHIFTDOWN + CTLDOWN) == (SHCTLDOWN) */ }
switch (wModifiers) { case NOMODIFIER: MouseMoveHandler: if (plb->iSelBase != iSelFromPt) { xxxLBSetCaret(plb, FALSE); }
/*
* We only look at the mouse if the point it is pointing to is * not selected. Since we are not in ExtendedSelMode, anywhere * the mouse points, we have to set the selection to that item. * Hence, if the item isn't selected, it means the mouse never * pointed to it before so we can select it. We ignore already * selected items so that we avoid flashing the inverted * selection rectangle. Also, we could get WM_SYSTIMER simulated * mouse moves which would cause flashing otherwise... */
iSelTemp = (fMouseInRect ? iSelFromPt : -1);
/*
* If the LB is either SingleSel or Extended multisel, clear all * old selections except the new one being made. */ if (plb->wMultiple != MULTIPLESEL) { xxxResetWorld(plb, iSelTemp, iSelTemp, FALSE); /*
* This will be TRUE if iSelTemp isn't -1 (like below) * and also if it is but there is a current selection. */ if ((iSelTemp == -1) && (plb->iSel != -1)) { uEvent = EVENT_OBJECT_SELECTIONREMOVE; } }
fSelected = IsSelected(plb, iSelTemp, HILITEONLY); if (iSelTemp != -1) {
/*
* If it is MULTIPLESEL, then toggle; For others, only if * not selected already, select it. */ if (((plb->wMultiple == MULTIPLESEL) && (wMsg != WM_LBUTTONDBLCLK)) || !fSelected) { SetSelected(plb, iSelTemp, !fSelected, HILITEANDSEL);
/*
* And invert it */ xxxInvertLBItem(plb, iSelTemp, !fSelected); fSelected = !fSelected; /* Set the new state */ if (plb->wMultiple == MULTIPLESEL) { uEvent = (fSelected ? EVENT_OBJECT_SELECTIONADD : EVENT_OBJECT_SELECTIONREMOVE); } else { uEvent = EVENT_OBJECT_SELECTION; } } }
/*
* We have to set iSel in case this is a multisel lb. */ plb->iSel = iSelTemp;
/*
* Set the new anchor point */ plb->iMouseDown = iSelFromPt; plb->iLastMouseMove = iSelFromPt; plb->fNewItemState = fSelected;
break; case SHIFTDOWN:
/*
* This is so that we can handle click and drag for multisel * listboxes using Shift modifier key . */ plb->iLastMouseMove = plb->iSel = iSelFromPt;
/*
* Check if an anchor point already exists */ if (plb->iMouseDown == -1) { plb->iMouseDown = iSelFromPt;
/*
* Reset all the previous selections */ xxxResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
/*
* Select the current position */ SetSelected(plb, plb->iMouseDown, TRUE, HILITEANDSEL); xxxInvertLBItem(plb, plb->iMouseDown, TRUE); /*
* We are changing the selction to this item only */ uEvent = EVENT_OBJECT_SELECTION; } else {
/*
* Reset all the previous selections */ xxxResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
/*
* Select all items from anchor point upto current click pt */ xxxAlterHilite(plb, plb->iMouseDown, iSelFromPt, HILITE, HILITEONLY, FALSE); uEvent = EVENT_OBJECT_SELECTIONWITHIN; } plb->fNewItemState = (UINT)TRUE; break;
case CTLDOWN:
/*
* This is so that we can handle click and drag for multisel * listboxes using Control modifier key. */
/*
* Reset the anchor point to the current point */ plb->iMouseDown = plb->iLastMouseMove = plb->iSel = iSelFromPt;
/*
* The state we will be setting items to */ plb->fNewItemState = (UINT)!IsSelected(plb, iSelFromPt, (UINT)HILITEONLY);
/*
* Toggle the current point */ SetSelected(plb, iSelFromPt, plb->fNewItemState, HILITEANDSEL); xxxInvertLBItem(plb, iSelFromPt, plb->fNewItemState); uEvent = (plb->fNewItemState ? EVENT_OBJECT_SELECTIONADD : EVENT_OBJECT_SELECTIONREMOVE); break;
case SHCTLDOWN:
/*
* This is so that we can handle click and drag for multisel * listboxes using Shift and Control modifier keys. */
/*
* Preserve all the previous selections */
/*
* Deselect only the selection connected with the last * anchor point; If the last anchor point is associated with a * de-selection, then do not do it */ if (plb->fNewItemState) { xxxAlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE, HILITEANDSEL, FALSE); } plb->iLastMouseMove = plb->iSel = iSelFromPt;
/*
* Check if an anchor point already exists */ if (plb->iMouseDown == -1) {
/*
* No existing anchor point; Make the current pt as anchor */ plb->iMouseDown = iSelFromPt; }
/*
* If one exists preserve the most recent anchor point */
/*
* The state we will be setting items to */ plb->fNewItemState = (UINT)IsSelected(plb, plb->iMouseDown, HILITEONLY);
/*
* Select all items from anchor point upto current click pt */ xxxAlterHilite(plb, plb->iMouseDown, iSelFromPt, plb->fNewItemState, HILITEONLY, FALSE); uEvent = EVENT_OBJECT_SELECTIONWITHIN; break; }
/*
* Set the new base point (the outline frame caret). We do the check * first to avoid flashing the caret unnecessarly. */ if (plb->iSelBase != iSelFromPt) {
/*
* Since xxxSetISelBase always turns on the caret, we don't need to * do it here... */ xxxSetISelBase(plb, iSelFromPt); }
/*
* SetISelBase will change the focus and send a focus event. * Then we send the selection event. */ if (uEvent) { LBEvent(plb, uEvent, iSelFromPt); } if (wMsg == WM_LBUTTONDOWN && TestWF(plb->spwnd, WEFDRAGOBJECT)) { if (NtUserDragDetect(hwnd, pt)) {
/*
* User is trying to drag object... */
/*
* Fake an up click so that the item is selected... */ xxxTrackMouse(plb, WM_LBUTTONUP, pt);
/*
* Notify parent * #ifndef WIN16 (32-bit Windows), plb->iSelBase gets * zero-extended to LONG wParam automatically by the compiler. */ ThreadLock(plb->spwndParent, &tlpwndParent); SendMessage(HW(plb->spwndParent), WM_BEGINDRAG, plb->iSelBase, (LPARAM)hwnd); ThreadUnlock(&tlpwndParent); } else { xxxTrackMouse(plb, WM_LBUTTONUP, pt); } return; } break;
case WM_MOUSEMOVE: { int dist; int iTimer;
/*
* Save for timer. */ plb->ptPrev = pt; /*
* Autoscroll listbox if mouse button is held down and mouse is * moved outside of the listbox */ if (plb->fMouseDown) { if (plb->fMultiColumn) { if ((pt.x < 0) || (pt.x >= rcClient.right - 1)) { /*
* Reset timer interval based on distance from listbox. * use a longer default interval because each multicolumn * scrolling increment is larger */ dist = pt.x < 0 ? -pt.x : (pt.x - rcClient.right + 1); iTimer = ((gpsi->dtScroll * 3) / 2) - ((WORD) dist << 4);
if (plb->fRightAlign) xxxLBoxCtlHScrollMultiColumn(plb, (pt.x < 0 ? SB_LINEDOWN : SB_LINEUP), 0); else xxxLBoxCtlHScrollMultiColumn(plb, (pt.x < 0 ? SB_LINEUP : SB_LINEDOWN), 0);
goto SetTimerAndSel; } } else if ((pt.y < 0) || (pt.y >= rcClient.bottom - 1)) { /*
* Reset timer interval based on distance from listbox. */ dist = pt.y < 0 ? -pt.y : (pt.y - rcClient.bottom + 1); iTimer = gpsi->dtScroll - ((WORD) dist << 4);
xxxLBoxCtlScroll(plb, (pt.y < 0 ? SB_LINEUP : SB_LINEDOWN), 0); SetTimerAndSel: NtUserSetTimer(hwnd, IDSYS_SCROLL, max(iTimer, 1), NULL); ISelFromPt(plb, pt, &iSelFromPt); } } else { /*
* Ignore if not in client since we don't autoscroll */ if (!PtInRect(&rcClient, pt)) break; }
switch (plb->wMultiple) { case SINGLESEL:
/*
* If it is a single selection or plain multisel list box */ goto MouseMoveHandler; break;
case MULTIPLESEL: case EXTENDEDSEL:
/*
* Handle mouse movement with extended selection of items */ if (plb->iSelBase != iSelFromPt) { xxxSetISelBase(plb, iSelFromPt);
/*
* If this is an extended Multi sel list box, then * adjust the display of the range due to the mouse move */ if (plb->wMultiple == EXTENDEDSEL) { xxxLBBlockHilite(plb, iSelFromPt, FALSE); LBEvent(plb, EVENT_OBJECT_SELECTIONWITHIN, iSelFromPt); } plb->iLastMouseMove = iSelFromPt; } break; } break; } case WM_LBUTTONUP: if (plb->fMouseDown) xxxLBButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY | (mousetemp ? LBUP_RESETSELECTION : 0) | (fMouseInRect ? LBUP_SUCCESS : 0)); } }
/***************************************************************************\
* * LBButtonUp() * * Called in response to both WM_CAPTURECHANGED and WM_LBUTTONUP. * \***************************************************************************/ void xxxLBButtonUp(PLBIV plb, UINT uFlags) {
CheckLock(plb->spwnd);
/*
* If the list box is an Extended listbox, then change the select status * of all items between the anchor and the last mouse position to the * newItemState */ if (plb->wMultiple == EXTENDEDSEL) xxxAlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove, plb->fNewItemState, SELONLY, FALSE);
/*
* This is a combo box and user upclicked outside the listbox * so we want to restore the original selection. */ if (plb->pcbox && (uFlags & LBUP_RESETSELECTION)) { int iSelOld;
iSelOld = plb->iSel;
if (iSelOld >= 0) xxxInvertLBItem(plb, plb->iSel, FALSE);
plb->iSel = plb->iLastSelection; xxxInvertLBItem(plb, plb->iSel, TRUE);
/*
* Note that we always send selection events before we tell the * app. This is on purpose--the app may turn around and select * something else when notified. In which case our event would * be out of order. */ LBEvent(plb, EVENT_OBJECT_SELECTION, plb->iSel);
/*
* On win-95 and NT4 the check used to be !(uFlags & LBUP_NOTIFY) which * is a bug because we would notify even when the lb is not LBUP_NOTIFY */ if ((uFlags & LBUP_NOTIFY) && plb->fNotify && (iSelOld != plb->iSel)) xxxNotifyOwner(plb, LBN_SELCHANGE); }
NtUserKillTimer(HWq(plb->spwnd), IDSYS_SCROLL); plb->fMouseDown = FALSE; if (plb->fCaptured) { plb->fCaptured = FALSE; if (uFlags & LBUP_RELEASECAPTURE) NtUserReleaseCapture(); } /*
* Don't scroll item as long as any part of it is visible */ if (plb->iSelBase < plb->iTop || plb->iSelBase > plb->iTop + CItemInWindow(plb, TRUE)) xxxInsureVisible(plb, plb->iSelBase, FALSE);
if (plb->fNotify) { if (uFlags & LBUP_NOTIFY) { if (uFlags & LBUP_SUCCESS) { /*
* ArtMaster needs this SELCHANGE notification now! */ if ((plb->fDoubleClick) && !TestWF(plb->spwnd, WFWIN31COMPAT)) xxxNotifyOwner(plb, LBN_SELCHANGE);
/*
* Notify owner of click or double click on selection */ xxxNotifyOwner(plb, (plb->fDoubleClick) ? LBN_DBLCLK : LBN_SELCHANGE); } else { /*
* Notify owner that the attempted selection was cancelled. */ xxxNotifyOwner(plb, LBN_SELCANCEL); } } else if (uFlags & LBUP_SELCHANGE) { /*
* Did we do some semi-selecting with mouse moves, then hit Enter? * If so, we need to make sure the app knows that something was * really truly selected. */ UserAssert(TestWF(plb->spwnd, WFWIN40COMPAT)); if (plb->iLastSelection != plb->iSel) xxxNotifyOwner(plb, LBN_SELCHANGE);
} }
}
/***************************************************************************\
* IncrementISel * * History: \***************************************************************************/
INT IncrementISel( PLBIV plb, INT iSel, INT sInc) {
/*
* Assumes cMac > 0, return iSel+sInc in range [0..cmac). */ iSel += sInc; if (iSel < 0) { return 0; } else if (iSel >= plb->cMac) { return plb->cMac - 1; } return iSel; }
/***************************************************************************\
* NewITop * \***************************************************************************/
void xxxNewITop(PLBIV plb, INT iTopNew) { xxxNewITopEx(plb, iTopNew, 0); }
/***************************************************************************\
* xxxNewITopEx * * History: \***************************************************************************/
void xxxNewITopEx( PLBIV plb, INT iTopNew, DWORD dwTime) { int iTopOld; BOOL fCaretOn; BOOL fMulti = plb->fMultiColumn;
CheckLock(plb->spwnd);
// Always try to turn off caret whether or not redraw is on
if (fCaretOn = plb->fCaretOn) xxxLBSetCaret(plb, FALSE);
iTopOld = (fMulti) ? (plb->iTop / plb->itemsPerColumn) : plb->iTop; plb->iTop = iTopNew; iTopNew = xxxSetLBScrollParms(plb, (fMulti) ? SB_HORZ : SB_VERT); plb->iTop = (fMulti) ? (iTopNew * plb->itemsPerColumn) : iTopNew;
if (!IsLBoxVisible(plb)) { return; }
if (iTopNew != iTopOld) { int xAmt, yAmt; RECT rc; DWORD dwFlags;
_GetClientRect(plb->spwnd, &rc);
if (fMulti) { yAmt = 0; if (abs(iTopNew - iTopOld) > plb->numberOfColumns) // Handle scrolling a large number of columns properly so that
// we don't overflow the size of a rect.
xAmt = 32000; else { xAmt = (iTopOld - iTopNew) * plb->cxColumn; if (plb->fRightAlign) xAmt = -xAmt; } } else { xAmt = 0; if (plb->OwnerDraw == OWNERDRAWVAR) { //
// Have to fake iTopOld for OWNERDRAWVAR listboxes so that
// the scrolling amount calculations work properly.
//
plb->iTop = iTopOld; yAmt = LBCalcVarITopScrollAmt(plb, iTopOld, iTopNew); plb->iTop = iTopNew; } else if (abs(iTopNew - iTopOld) > plb->cItemFullMax) yAmt = 32000; else yAmt = (iTopOld - iTopNew) * plb->cyChar; }
dwFlags = LBGetScrollFlags(plb, dwTime); ScrollWindowEx(HWq(plb->spwnd), xAmt, yAmt, NULL, &rc, NULL, NULL, dwFlags); UpdateWindow(HWq(plb->spwnd)); }
// Note that although we turn off the caret regardless of redraw, we
// only turn it on if redraw is true. Slimy thing to fixup many
// caret related bugs...
if (fCaretOn) // Turn the caret back on only if we turned it off. This avoids
// annoying caret flicker.
xxxLBSetCaret(plb, TRUE); }
/***************************************************************************\
* xxxInsureVisible * * History: \***************************************************************************/
void xxxInsureVisible( PLBIV plb, INT iSel, BOOL fPartial) /* It is ok for the item to be partially visible */ { INT sLastVisibleItem;
CheckLock(plb->spwnd);
if (iSel < plb->iTop) { xxxNewITop(plb, iSel); } else { if (fPartial) {
/*
* 1 must be subtracted to get the last visible item * A part of the fix for Bug #3727 -- 01/14/91 -- SANKAR */ sLastVisibleItem = plb->iTop + CItemInWindow(plb, TRUE) - (INT)1; } else { sLastVisibleItem = LastFullVisible(plb); }
if (plb->OwnerDraw != OWNERDRAWVAR) { if (iSel > sLastVisibleItem) { if (plb->fMultiColumn) { xxxNewITop(plb, ((iSel / plb->itemsPerColumn) - max(plb->numberOfColumns-1,0)) * plb->itemsPerColumn); } else { xxxNewITop(plb, (INT)max(0, iSel - sLastVisibleItem + plb->iTop)); } } } else if (iSel > sLastVisibleItem) xxxNewITop(plb, LBPage(plb, iSel, FALSE)); } }
/***************************************************************************\
* xxxLBoxCaretBlinker * * Timer callback function toggles Caret * Since it is a callback, it is APIENTRY * * History: \***************************************************************************/
VOID xxxLBoxCaretBlinker( HWND hwnd, UINT wMsg, UINT_PTR nIDEvent, DWORD dwTime) { PWND pwnd; PLBIV plb;
/*
* Standard parameters for a timer callback function that aren't used. * Mentioned here to avoid compiler warnings */ UNREFERENCED_PARAMETER(wMsg); UNREFERENCED_PARAMETER(nIDEvent); UNREFERENCED_PARAMETER(dwTime);
pwnd = ValidateHwnd(hwnd); plb = ((PLBWND)pwnd)->pLBIV;
/*
* leave caret on, don't blink it off (prevents rapid blinks?) */ if (ISREMOTESESSION() && plb->fCaretOn) { return; }
/*
* Check if the Caret is ON, if so, switch it OFF */ xxxLBSetCaret(plb, !plb->fCaretOn); return; }
/***************************************************************************\
* xxxLBoxCtlKeyInput * * If msg == LB_KEYDOWN, vKey is the number of the item to go to, * otherwise it is the virtual key. * * History: \***************************************************************************/
void xxxLBoxCtlKeyInput( PLBIV plb, UINT msg, UINT vKey) { INT i; INT iNewISel; INT cItemPageScroll; PCBOX pcbox; BOOL fDropDownComboBox; BOOL fExtendedUIComboBoxClosed; BOOL hScrollBar = TestWF(plb->spwnd, WFHSCROLL); UINT wModifiers = 0; BOOL fSelectKey = FALSE; /* assume it is a navigation key */ UINT uEvent = 0; HWND hwnd = HWq(plb->spwnd); TL tlpwndParent; TL tlpwnd;
CheckLock(plb->spwnd);
pcbox = plb->pcbox;
/*
* Is this a dropdown style combo box/listbox ? */ fDropDownComboBox = pcbox && (pcbox->CBoxStyle & SDROPPABLE);
/*
*Is this an extended ui combo box which is closed? */ fExtendedUIComboBoxClosed = fDropDownComboBox && pcbox->fExtendedUI && !pcbox->fLBoxVisible;
if (plb->fMouseDown || (!plb->cMac && vKey != VK_F4)) {
/*
* Ignore keyboard input if we are in the middle of a mouse down deal or * if there are no items in the listbox. Note that we let F4's go * through for combo boxes so that the use can pop up and down empty * combo boxes. */ return; }
/*
* Modifiers are considered only in EXTENDED sel list boxes. */ if (plb->wMultiple == EXTENDEDSEL) {
/*
* If multiselection listbox, are any modifiers used ? */ if (GetKeyState(VK_SHIFT) < 0) wModifiers = SHIFTDOWN; if (GetKeyState(VK_CONTROL) < 0) wModifiers += CTLDOWN;
/*
* Please Note that (SHIFTDOWN + CTLDOWN) == (SHCTLDOWN) */ }
if (msg == LB_KEYDOWN) {
/*
* This is a listbox "go to specified item" message which means we want * to go to a particular item number (given by vKey) directly. ie. the * user has typed a character and we want to go to the item which * starts with that character. */ iNewISel = (INT)vKey; goto TrackKeyDown; }
cItemPageScroll = plb->cItemFullMax;
if (cItemPageScroll > 1) cItemPageScroll--;
if (plb->fWantKeyboardInput) {
/*
* Note: msg must not be LB_KEYDOWN here or we'll be in trouble... */ ThreadLock(plb->spwndParent, &tlpwndParent); iNewISel = (INT)SendMessage(HW(plb->spwndParent), WM_VKEYTOITEM, MAKELONG(vKey, plb->iSelBase), (LPARAM)hwnd); ThreadUnlock(&tlpwndParent);
if (iNewISel == -2) {
/*
* Don't move the selection... */ return; } if (iNewISel != -1) {
/*
* Jump directly to the item provided by the app */ goto TrackKeyDown; }
/*
* else do default processing of the character. */ }
switch (vKey) { // LATER IanJa: not language independent!!!
// We could use VkKeyScan() to find out which is the '\' key
// This is VK_OEM_5 '\|' for US English only.
// Germans, Italians etc. have to type CTRL+^ (etc) for this.
// This is documented as File Manager behaviour for 3.0, but apparently
// not for 3.1., although functionality remains. We should still fix it,
// although German (etc?) '\' is generated with AltGr (Ctrl-Alt) (???)
case VERKEY_BACKSLASH: /* '\' character for US English */
/*
* Check if this is CONTROL-\ ; If so Deselect all items */ if ((wModifiers & CTLDOWN) && (plb->wMultiple != SINGLESEL)) { xxxLBSetCaret(plb, FALSE); xxxResetWorld(plb, plb->iSelBase, plb->iSelBase, FALSE);
/*
* And select the current item */ SetSelected(plb, plb->iSelBase, TRUE, HILITEANDSEL); xxxInvertLBItem(plb, plb->iSelBase, TRUE); uEvent = EVENT_OBJECT_SELECTION; goto CaretOnAndNotify; } return; break;
case VK_DIVIDE: /* NumPad '/' character on enhanced keyboard */ // LATER IanJa: not language independent!!!
// We could use VkKeyScan() to find out which is the '/' key
// This is VK_OEM_2 '/?' for US English only.
// Germans, Italians etc. have to type CTRL+# (etc) for this.
case VERKEY_SLASH: /* '/' character */
/*
* Check if this is CONTROL-/ ; If so select all items */ if ((wModifiers & CTLDOWN) && (plb->wMultiple != SINGLESEL)) { xxxLBSetCaret(plb, FALSE); xxxResetWorld(plb, -1, -1, TRUE);
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
CaretOnAndNotify: xxxLBSetCaret(plb, TRUE); LBEvent(plb, uEvent, plb->iSelBase); xxxNotifyOwner(plb, LBN_SELCHANGE); } return; break;
case VK_F8:
/*
* The "Add" mode is possible only in Multiselection listboxes... Get * into it via SHIFT-F8... (Yes, sometimes these UI people are sillier * than your "typical dumb user"...) */ if (plb->wMultiple != SINGLESEL && wModifiers == SHIFTDOWN) {
/*
* We have to make the caret blink! Do something... */ if (plb->fAddSelMode) {
/*
* Switch off the Caret blinking */ NtUserKillTimer(hwnd, IDSYS_CARET);
/*
* Make sure the caret does not vanish */ xxxLBSetCaret(plb, TRUE); } else {
/*
* Create a timer to make the caret blink */ NtUserSetTimer(hwnd, IDSYS_CARET, gpsi->dtCaretBlink, xxxLBoxCaretBlinker); }
/*
* Toggle the Add mode flag */ plb->fAddSelMode = (UINT)!plb->fAddSelMode; } return; case VK_SPACE: /* Selection key is space */ i = 0; fSelectKey = TRUE; break;
case VK_PRIOR: if (fExtendedUIComboBoxClosed) {
/*
* Disable movement keys for TandyT. */ return; }
if (plb->OwnerDraw == OWNERDRAWVAR) { i = LBPage(plb, plb->iSelBase, FALSE) - plb->iSelBase; } else { i = -cItemPageScroll; } break;
case VK_NEXT: if (fExtendedUIComboBoxClosed) {
/*
* Disable movement keys for TandyT. */ return; }
if (plb->OwnerDraw == OWNERDRAWVAR) { i = LBPage(plb, plb->iSelBase, TRUE) - plb->iSelBase; } else { i = cItemPageScroll; } break;
case VK_HOME: if (fExtendedUIComboBoxClosed) {
/*
* Disable movement keys for TandyT. */ return; }
i = (INT_MIN/2)+1; /* A very big negative number */ break;
case VK_END: if (fExtendedUIComboBoxClosed) {
/*
* Disable movement keys for TandyT. */ return; }
i = (INT_MAX/2)-1; /* A very big positive number */ break;
case VK_LEFT: if (plb->fMultiColumn) { if (plb->fRightAlign ^ (!!TestWF(plb->spwnd, WEFLAYOUTRTL))) { goto ReallyRight; } ReallyLeft: if (plb->iSelBase / plb->itemsPerColumn == 0) { i = 0; } else { i = -plb->itemsPerColumn; } break; }
if (hScrollBar) { goto HandleHScrolling; } else {
/*
* Fall through and handle this as if the up arrow was pressed. */
vKey = VK_UP; }
/*
* Fall through */
case VK_UP: if (fExtendedUIComboBoxClosed) // Disable movement keys for TandyT.
return;
i = -1; break;
case VK_RIGHT: if (plb->fMultiColumn) { if (plb->fRightAlign ^ (!!TestWF(plb->spwnd, WEFLAYOUTRTL))) { goto ReallyLeft; } ReallyRight: if (plb->iSelBase / plb->itemsPerColumn == plb->cMac / plb->itemsPerColumn) { i = 0; } else { i = plb->itemsPerColumn; } break; } if (hScrollBar) { HandleHScrolling: PostMessage(hwnd, WM_HSCROLL, (vKey == VK_RIGHT ? SB_LINEDOWN : SB_LINEUP), 0L); return; } else {
/*
* Fall through and handle this as if the down arrow was * pressed. */ vKey = VK_DOWN; }
/*
* Fall through */
case VK_DOWN: if (fExtendedUIComboBoxClosed) {
/*
* If the combo box is closed, down arrow should open it. */ if (!pcbox->fLBoxVisible) {
/*
* If the listbox isn't visible, just show it */ ThreadLock(pcbox->spwnd, &tlpwnd); xxxCBShowListBoxWindow(pcbox, TRUE); ThreadUnlock(&tlpwnd); } return; } i = 1; break;
case VK_ESCAPE: case VK_RETURN: if (!fDropDownComboBox || !pcbox->fLBoxVisible) return;
// | If this is a dropped listbox for a combobox and the ENTER |
// | key is pressed, close up the listbox, so FALLTHRU |
// V V
case VK_F4: if (fDropDownComboBox && !pcbox->fExtendedUI) {
/*
* If we are a dropdown combo box/listbox we want to process * this key. BUT for TandtT, we don't do anything on VK_F4 if we * are in extended ui mode. */ ThreadLock(pcbox->spwnd, &tlpwnd); if (!pcbox->fLBoxVisible) {
/*
* If the listbox isn't visible, just show it */ xxxCBShowListBoxWindow(pcbox, (vKey != VK_ESCAPE)); } else {
/*
* Ok, the listbox is visible. So hide the listbox window. */ xxxCBHideListBoxWindow(pcbox, TRUE, (vKey != VK_ESCAPE)); } ThreadUnlock(&tlpwnd); }
/*
* Fall through to the return */
default: return; }
/*
* Find out what the new selection should be */ iNewISel = IncrementISel(plb, plb->iSelBase, i);
if (plb->wMultiple == SINGLESEL) { if (plb->iSel == iNewISel) {
/*
* If we are single selection and the keystroke is moving us to an * item which is already selected, we don't have to do anything... */ return; }
uEvent = EVENT_OBJECT_SELECTION;
plb->iTypeSearch = 0; if ((vKey == VK_UP || vKey == VK_DOWN) && !IsSelected(plb, plb->iSelBase, HILITEONLY)) {
/*
* If the caret is on an unselected item and the user just hits the * up or down arrow key (ie. with no shift or ctrl modifications), * then we will just select the item the cursor is at. This is * needed for proper behavior in combo boxes but do we always want * to run this code??? Note that this is only used in single * selection list boxes since it doesn't make sense in the * multiselection case. Note that an LB_KEYDOWN message must not be * checked here because the vKey will be an item number not a * VK_and we will goof. Thus, trackkeydown label is below this to * fix a bug caused by it being above this... */ iNewISel = (plb->iSelBase == -1) ? 0 : plb->iSelBase; } }
TrackKeyDown:
xxxSetISelBase(plb, iNewISel);
xxxLBSetCaret(plb, FALSE);
if (wModifiers & SHIFTDOWN) { // Check if iMouseDown is un-initialised
if (plb->iMouseDown == -1) plb->iMouseDown = iNewISel; if (plb->iLastMouseMove == -1) plb->iLastMouseMove = iNewISel;
// Check if we are in ADD mode
if (plb->fAddSelMode) { /* Preserve all the pre-existing selections except the
* ones connected with the last anchor point; If the last * Preserve all the previous selections */ /* Deselect only the selection connected with the last
* anchor point; If the last anchor point is associated * with de-selection, then do not do it */
if (!plb->fNewItemState) plb->iLastMouseMove = plb->iMouseDown;
/* We haven't done anything here because, LBBlockHilite()
* will take care of wiping out the selection between * Anchor point and iLastMouseMove and select the block * between anchor point and current cursor location */ } else { /* We are not in ADD mode */ /* Remove all selections except between the anchor point
* and last mouse move because it will be taken care of in * LBBlockHilite */ xxxResetWorld(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE); }
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
/* LBBlockHilite takes care to deselect the block between
* the anchor point and iLastMouseMove and select the block * between the anchor point and the current cursor location */ /* Toggle all items to the same selection state as the item
* item at the anchor point) from the anchor point to the * current cursor location. */ plb->fNewItemState = IsSelected(plb, plb->iMouseDown, SELONLY); xxxLBBlockHilite(plb, iNewISel, TRUE);
plb->iLastMouseMove = iNewISel; /* Preserve the existing anchor point */ } else { /* Check if this is in ADD mode */ if ((plb->fAddSelMode) || (plb->wMultiple == MULTIPLESEL)) { /* Preserve all pre-exisiting selections */ if (fSelectKey) { /* Toggle the selection state of the current item */ plb->fNewItemState = !IsSelected(plb, iNewISel, SELONLY); SetSelected(plb, iNewISel, plb->fNewItemState, HILITEANDSEL);
xxxInvertLBItem(plb, iNewISel, plb->fNewItemState);
/* Set the anchor point at the current location */ plb->iLastMouseMove = plb->iMouseDown = iNewISel; uEvent = (plb->fNewItemState ? EVENT_OBJECT_SELECTIONADD : EVENT_OBJECT_SELECTIONREMOVE); } } else { /* We are NOT in ADD mode */ /* Remove all existing selections except iNewISel, to
* avoid flickering. */ xxxResetWorld(plb, iNewISel, iNewISel, FALSE);
/* Select the current item */ SetSelected(plb, iNewISel, TRUE, HILITEANDSEL); xxxInvertLBItem(plb, iNewISel, TRUE);
/* Set the anchor point at the current location */ plb->iLastMouseMove = plb->iMouseDown = iNewISel; uEvent = EVENT_OBJECT_SELECTION; } }
/*
* Move the cursor to the new location */ xxxInsureVisible(plb, iNewISel, FALSE); xxxLBShowHideScrollBars(plb);
xxxLBSetCaret(plb, TRUE);
if (uEvent) { LBEvent(plb, uEvent, iNewISel); }
/*
* Should we notify our parent? */ if (plb->fNotify) { if (fDropDownComboBox && pcbox->fLBoxVisible) {
/*
* If we are in a drop down combo box/listbox and the listbox is * visible, we need to set the fKeyboardSelInListBox bit so that the * combo box code knows not to hide the listbox since the selchange * message is caused by the user keyboarding through... */ pcbox->fKeyboardSelInListBox = TRUE; plb->iLastSelection = iNewISel; } xxxNotifyOwner(plb, LBN_SELCHANGE); } }
/***************************************************************************\
* Compare * * Is lpstr1 equal/prefix/less-than/greater-than lsprst2 (case-insensitive) ? * * LATER IanJa: this assume a longer string is never a prefix of a longer one. * Also assumes that removing 1 or more characters from the end of a string will * give a string tahs sort before the original. These assumptions are not valid * for all languages. We nedd better support from NLS. (Consider French * accents, Spanish c/ch, ligatures, German sharp-s/SS, etc.) * * History: \***************************************************************************/
INT Compare( LPCWSTR pwsz1, LPCWSTR pwsz2, DWORD dwLocaleId) { UINT len1 = wcslen(pwsz1); UINT len2 = wcslen(pwsz2); INT result;
/*
* CompareStringW returns: * 1 = pwsz1 < pwsz2 * 2 = pwsz1 == pwsz2 * 3 = pwsz1 > pwsz2 */ result = CompareStringW((LCID)dwLocaleId, NORM_IGNORECASE, pwsz1, min(len1,len2), pwsz2, min(len1, len2));
if (result == CSTR_LESS_THAN) { return LT; } else if (result == CSTR_EQUAL) { if (len1 == len2) { return EQ; } else if (len1 < len2) { /*
* LATER IanJa: should not assume shorter string is a prefix * Spanish "c" and "ch", ligatures, German sharp-s/SS etc. */ return PREFIX; } } return GT; }
/***************************************************************************\
* xxxFindString * * Scans for a string in the listbox prefixed by or equal to lpstr. * For OWNERDRAW listboxes without strings and without the sort style, we * try to match the long app supplied values. * * History: * 16-Apr-1992 beng The NODATA case \***************************************************************************/
INT xxxFindString( PLBIV plb, LPWSTR lpstr, INT sStart, INT code, BOOL fWrap) { /*
* Search for a prefix match (case-insensitive equal/prefix) * sStart == -1 means start from beginning, else start looking at sStart+1 * assumes cMac > 0. */ INT sInd; /* index of string */ INT sStop; /* index to stop searching at */ lpLBItem pRg; TL tlpwndParent; INT sortResult;
/*
* Owner-Draw version of pRg */ #define pODRg ((lpLBODItem)pRg)
COMPAREITEMSTRUCT cis; LPWSTR listboxString;
CheckLock(plb->spwnd);
if (plb->fHasStrings && (!lpstr || !*lpstr)) return LB_ERR;
if (!plb->fHasData) { RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING, "FindString called on NODATA lb"); return LB_ERR; }
if ((sInd = sStart + 1) >= plb->cMac) sInd = (fWrap ? 0 : plb->cMac - 1);
sStop = (fWrap ? sInd : 0);
/*
* If at end and no wrap, stop right away */ if (((sStart >= plb->cMac - 1) && !fWrap) || (plb->cMac < 1)) { return LB_ERR; }
/*
* Apps could pass in an invalid sStart like -2 and we would blow up. * Win 3.1 would not so we need to fixup sInd to be zero */ if (sInd < 0) sInd = 0;
pRg = (lpLBItem)(plb->rgpch);
do { if (plb->fHasStrings) {
/*
* Searching for string matches. */ listboxString = (LPWSTR)((LPBYTE)plb->hStrings + pRg[sInd].offsz);
if (code == PREFIX && listboxString && *lpstr != TEXT('[') && *listboxString == TEXT('[')) {
/*
* If we are looking for a prefix string and the first items * in this string are [- then we ignore them. This is so * that in a directory listbox, the user can goto drives * by selecting the drive letter. */ listboxString++; if (*listboxString == TEXT('-')) listboxString++; }
if (Compare(lpstr, listboxString, plb->dwLocaleId) <= code) { goto FoundIt; }
} else { if (plb->fSort) {
/*
* Send compare item messages to the parent for sorting */ cis.CtlType = ODT_LISTBOX; cis.CtlID = PtrToUlong(plb->spwnd->spmenu); cis.hwndItem = HWq(plb->spwnd); cis.itemID1 = (UINT)-1; cis.itemData1 = (ULONG_PTR)lpstr; cis.itemID2 = (UINT)sInd; cis.itemData2 = pODRg[sInd].itemData; cis.dwLocaleId = plb->dwLocaleId;
ThreadLock(plb->spwndParent, &tlpwndParent); sortResult = (INT)SendMessage(HW(plb->spwndParent), WM_COMPAREITEM, cis.CtlID, (LPARAM)&cis); ThreadUnlock(&tlpwndParent);
if (sortResult == -1) { sortResult = LT; } else if (sortResult == 1) { sortResult = GT; } else { sortResult = EQ; }
if (sortResult <= code) { goto FoundIt; } } else {
/*
* Searching for app supplied long data matches. */ if ((ULONG_PTR)lpstr == pODRg[sInd].itemData) goto FoundIt; } }
/*
* Wrap round to beginning of list */ if (++sInd == plb->cMac) sInd = 0; } while (sInd != sStop);
sInd = -1;
FoundIt: return sInd; }
/***************************************************************************\
* xxxLBoxCtlCharInput * * History: \***************************************************************************/
void xxxLBoxCtlCharInput( PLBIV plb, UINT inputChar, BOOL fAnsi) { INT iSel; BOOL fControl; TL tlpwndParent;
CheckLock(plb->spwnd);
if (plb->cMac == 0 || plb->fMouseDown) {
/*
* Get out if we are in the middle of mouse routines or if we have no * items in the listbox, we just return without doing anything. */ return; }
fControl = (GetKeyState(VK_CONTROL) < 0);
switch (inputChar) { case VK_ESCAPE: plb->iTypeSearch = 0; if (plb->pszTypeSearch) plb->pszTypeSearch[0] = 0; break;
case VK_BACK: if (plb->iTypeSearch) { plb->pszTypeSearch[plb->iTypeSearch--] = 0; if (plb->fSort) { iSel = -1; goto TypeSearch; } } break;
case VK_SPACE: if (plb->fAddSelMode || plb->wMultiple == MULTIPLESEL) break; /* Otherwise, for single/extended selection listboxes not in add
* selection mode, let the space go thru as a type search character * FALL THRU */
default:
/*
* Move selection to first item beginning with the character the * user typed. We don't want do this if we are using owner draw. */
if (fAnsi && IS_DBCS_ENABLED() && IsDBCSLeadByteEx(THREAD_CODEPAGE(), (BYTE)inputChar)) { WCHAR wch; LPWSTR lpwstr = &wch;
inputChar = DbcsCombine(HWq(plb->spwnd), (BYTE)inputChar); RIPMSG1(RIP_VERBOSE, "xxxLBoxCtlCharInput: combined DBCS. 0x%04x", inputChar);
if (inputChar == 0) { RIPMSG1(RIP_WARNING, "xxxLBoxCtlCharInput: cannot combine two DBCS. LB=0x%02x", inputChar); break; } // If it is DBCS, let's ignore the ctrl status.
fControl = FALSE;
// Convert DBCS to UNICODE.
// Note: Leading byte is in the low byte, trailing byte is in high byte.
// Let's assume Little Endian CPUs only, so inputChar can directly be
// input for MBSToWCSEx as an ANSI string.
if (MBToWCSEx(THREAD_CODEPAGE(), (LPCSTR)&inputChar, 2, &lpwstr, 1, FALSE) == 0) { RIPMSG1(RIP_WARNING, "xxxLBoxCtlCharInput: cannot convert 0x%04x to UNICODE.", inputChar); break; } inputChar = wch; }
if (plb->fHasStrings) { // Incremental Type Search processing
//
// update szTypeSearch string and then move to the first item from
// the current selection whose prefix matches szTypeSearch
//
// the szTypeSearch will continue to grow until a "long enough"
// gap between key entries is encountered -- at which point any
// more searching will start over
/*
* Undo CONTROL-char to char */ if (fControl && inputChar < 0x20) inputChar += 0x40;
if (plb->iTypeSearch == MAX_TYPESEARCH) { NtUserMessageBeep(0); break; } iSel = -1;
if (plb->pszTypeSearch == NULL) plb->pszTypeSearch = (LPWSTR)UserLocalAlloc(HEAP_ZERO_MEMORY, sizeof(WCHAR) * (MAX_TYPESEARCH + 1));
if (plb->pszTypeSearch == NULL) { NtUserMessageBeep(0); break; }
plb->pszTypeSearch[plb->iTypeSearch++] = (WCHAR) inputChar; plb->pszTypeSearch[plb->iTypeSearch] = 0;
TypeSearch: if (plb->fSort) { // Set timer to determine when to kill incremental searching
NtUserSetTimer(HWq(plb->spwnd), IDSYS_LBSEARCH, gpsi->dtLBSearch, NULL); } else { // If this is not a sorted listbox, no incremental search.
plb->iTypeSearch = 0; iSel = plb->iSelBase; }
/*
* Search for the item beginning with the given character starting * at iSel+1. We will wrap the search to the beginning of the * listbox if we don't find the item. If SHIFT is down and we are * a multiselection lb, then the item's state will be set to * plb->fNewItemState according to the current mode. */ iSel = xxxFindString(plb, plb->pszTypeSearch, iSel, PREFIX, TRUE); if (iSel == -1) { // no match found -- check for prefix match
// (i.e. "p" find FIRST item that starts with 'p',
// "pp" find NEXT item that starts with 'p')
if(plb->iTypeSearch) { plb->iTypeSearch--; if ((plb->iTypeSearch == 1) && (plb->pszTypeSearch[0] == plb->pszTypeSearch[1])) { plb->pszTypeSearch[1] = 0; iSel = xxxFindString(plb, plb->pszTypeSearch, plb->iSelBase, PREFIX, TRUE); } } } // if match is found -- select it
if (iSel != -1) { CtlKeyInput: xxxLBoxCtlKeyInput(plb, LB_KEYDOWN, iSel);
} } else { if (plb->spwndParent != NULL) { ThreadLock(plb->spwndParent, &tlpwndParent); iSel = (INT)SendMessageWorker(plb->spwndParent, WM_CHARTOITEM, MAKELONG(inputChar, plb->iSelBase), (LPARAM)HWq(plb->spwnd), fAnsi); ThreadUnlock(&tlpwndParent); } else iSel = -1;
if (iSel != -1 && iSel != -2) goto CtlKeyInput;
} break; } }
/***************************************************************************\
* LBoxGetSelItems * * effects: For multiselection listboxes, this returns the total number of * selection items in the listbox if fCountOnly is true. or it fills an array * (lParam) with the items numbers of the first wParam selected items. * * History: \***************************************************************************/
int LBoxGetSelItems( PLBIV plb, BOOL fCountOnly, int wParam, LPINT lParam) { int i; int itemsselected = 0;
if (plb->wMultiple == SINGLESEL) return LB_ERR;
for (i = 0; i < plb->cMac; i++) { if (IsSelected(plb, i, SELONLY)) { if (!fCountOnly) { if (itemsselected < wParam) *lParam++ = i; else {
/*
* That's all the items we can fit in the array. */ return itemsselected; } } itemsselected++; } }
return itemsselected; }
/***************************************************************************\
* xxxLBSetRedraw * * Handle WM_SETREDRAW message * * History: \***************************************************************************/
void xxxLBSetRedraw( PLBIV plb, BOOL fRedraw) { CheckLock(plb->spwnd);
if (fRedraw) fRedraw = TRUE;
if (plb->fRedraw != (UINT)fRedraw) { plb->fRedraw = !!fRedraw;
if (fRedraw) { xxxLBSetCaret(plb, TRUE); xxxLBShowHideScrollBars(plb);
if (plb->fDeferUpdate) { plb->fDeferUpdate = FALSE; RedrawWindow(HWq(plb->spwnd), NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN); } } } }
/***************************************************************************\
* xxxLBSelRange * * Selects the range of items between i and j, inclusive. * * History: \***************************************************************************/
void xxxLBSelRange( PLBIV plb, int iStart, int iEnd, BOOL fnewstate) { DWORD temp; RECT rc;
CheckLock(plb->spwnd);
if (iStart > iEnd) { temp = iEnd; iEnd = iStart; iStart = temp; }
/*
* We don't want to loop through items that don't exist. */ iEnd = min(plb->cMac, iEnd); iStart = max(iStart, 0); if (iStart > iEnd) return;
/*
* iEnd could be equal to MAXINT which is why we test temp and iEnd * as DWORDs. */ for (temp = iStart; temp <= (DWORD)iEnd; temp++) {
if (IsSelected(plb, temp, SELONLY) != fnewstate) { SetSelected(plb, temp, fnewstate, HILITEANDSEL); LBGetItemRect(plb, temp, &rc);
xxxLBInvalidateRect(plb, (LPRECT)&rc, FALSE); }
} UserAssert(plb->wMultiple); LBEvent(plb, EVENT_OBJECT_SELECTIONWITHIN, iStart); }
/***************************************************************************\
* xxxLBSetCurSel * * History: \***************************************************************************/
int xxxLBSetCurSel( PLBIV plb, int iSel) { CheckLock(plb->spwnd);
if (!(plb->wMultiple || iSel < -1 || iSel >= plb->cMac)) { xxxLBSetCaret(plb, FALSE); if (plb->iSel != -1) {
/*
* This prevents scrolling when iSel == -1 */ if (iSel != -1) xxxInsureVisible(plb, iSel, FALSE);
/*
* Turn off old selection */ xxxInvertLBItem(plb, plb->iSel, FALSE); }
if (iSel != -1) { xxxInsureVisible(plb, iSel, FALSE); plb->iSelBase = plb->iSel = iSel;
/*
* Highlight new selection */ xxxInvertLBItem(plb, plb->iSel, TRUE); } else { plb->iSel = -1; if (plb->cMac) plb->iSelBase = min(plb->iSelBase, plb->cMac-1); else plb->iSelBase = 0; }
/*
* Send both focus and selection events * * We need to send this event even if the listbox isn't visible. See * bug #88548. Also see 355612. */ if (_IsWindowVisible(plb->spwnd) || (GetFocus() == HWq(plb->spwnd))) { LBEvent(plb, EVENT_OBJECT_FOCUS, plb->iSelBase); LBEvent(plb, EVENT_OBJECT_SELECTION, plb->iSel); }
xxxLBSetCaret(plb, TRUE); return plb->iSel; }
return LB_ERR; }
/***************************************************************************\
* LBSetItemData * * Makes the item at index contain the data given. * * History: * 16-Apr-1992 beng The NODATA listbox case \***************************************************************************/
int LBSetItemData( PLBIV plb, int index, LONG_PTR data) { LPSTR lpItemText;
/*
* v-ronaar: fix bug #25865, don't allow negative indices! */ if ((index != -1) && ((UINT) index >= (UINT) plb->cMac)) { RIPERR1(ERROR_INVALID_INDEX, RIP_WARNING, "LBSetItemData with invalid index %x", index); return LB_ERR; }
/*
* No-data listboxes just ignore all LB_SETITEMDATA calls */ if (!plb->fHasData) { return TRUE; }
lpItemText = (LPSTR)plb->rgpch;
if (index == -1) {
/*
* index == -1 means set the data to all the items */ if (plb->fHasStrings) { for (index = 0; index < plb->cMac; index++) {
((lpLBItem)lpItemText)->itemData = data; lpItemText += sizeof(LBItem); } } else { for (index = 0; index < plb->cMac; index++) {
((lpLBODItem)lpItemText)->itemData = data; lpItemText += sizeof(LBODItem); } } return TRUE; }
if (plb->fHasStrings) {
lpItemText = (LPSTR)(lpItemText + (index * sizeof(LBItem))); ((lpLBItem)lpItemText)->itemData = data; } else {
lpItemText = (LPSTR)(lpItemText + (index * sizeof(LBODItem))); ((lpLBODItem)lpItemText)->itemData = data; } return TRUE; }
/***************************************************************************\
* xxxCheckRedraw * * History: \***************************************************************************/
void xxxCheckRedraw( PLBIV plb, BOOL fConditional, INT sItem) { CheckLock(plb->spwnd);
if (fConditional && plb->cMac && (sItem > (plb->iTop + CItemInWindow(plb, TRUE)))) return;
/*
* Don't do anything if the parent is not visible. */ xxxLBInvalidateRect(plb, (LPRECT)NULL, TRUE); }
/***************************************************************************\
* xxxCaretDestroy * * History: \***************************************************************************/
void xxxCaretDestroy( PLBIV plb) { CheckLock(plb->spwnd);
/*
* We're losing the focus. Act like up clicks are happening so we release * capture, set the current selection, notify the parent, etc. */ if (plb->fCaptured)
/*
* If we have the capture and we lost the focus, that means we already * changed the selection and we have to notify also the parent about * this. So we need to add also the LBUP_SUCCESS flag in this case. */
xxxLBButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY | (plb->fMouseDown ? LBUP_SUCCESS : 0));
if (plb->fAddSelMode) {
/*
* Switch off the Caret blinking */ NtUserKillTimer(HWq(plb->spwnd), IDSYS_CARET);
/*
* Make sure the caret goes away */ xxxLBSetCaret(plb, FALSE); plb->fAddSelMode = FALSE; }
plb->fCaret = FALSE; }
/***************************************************************************\
* xxxLbSetSel * * History: \***************************************************************************/
LONG xxxLBSetSel( PLBIV plb, BOOL fSelect, /* New state to set selection to */ INT iSel) { INT sItem; RECT rc; UINT uEvent = 0;
CheckLock(plb->spwnd);
/*
* Bug 17656. WinZip's accelerator key for 'DeSelect All' sends a LB_SETSEL * message with lparam = 0x0000ffff instead of 0xffffffff(-1). If iSel * is equal to 0x0000ffff and there are less than 0xffff elements in the * list we set iSel equal to 0xffffffff. */ if ((iSel == (UINT)0xffff) && (iSel >= plb->cMac)) { iSel = -1; RIPMSG0(RIP_WARNING, "Sign extending iSel=0xffff to 0xffffffff"); }
if ((plb->wMultiple == SINGLESEL) || (iSel != -1 && iSel >= plb->cMac)) { RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING, "xxxLBSetSel:Invalid iSel or SINGLESEL listbox"); return LB_ERR; }
xxxLBSetCaret(plb, FALSE);
if (iSel == -1/*(INT)0xffff*/) {
/*
* Set/clear selection from all items if -1 */ for (sItem = 0; sItem < plb->cMac; sItem++) { if (IsSelected(plb, sItem, SELONLY) != fSelect) { SetSelected(plb, sItem, fSelect, HILITEANDSEL); if (LBGetItemRect(plb, sItem, &rc)) { xxxLBInvalidateRect(plb, &rc, FALSE); } } } xxxLBSetCaret(plb, TRUE); uEvent = EVENT_OBJECT_SELECTIONWITHIN; } else { if (fSelect) {
/*
* Check if the item if fully hidden and scroll it into view if it * is. Note that we don't want to scroll partially visible items * into full view because this breaks the shell... */ xxxInsureVisible(plb, iSel, TRUE); plb->iSelBase = plb->iSel = iSel;
plb->iMouseDown = plb->iLastMouseMove = iSel; uEvent = EVENT_OBJECT_FOCUS; } else { uEvent = EVENT_OBJECT_SELECTIONREMOVE; } SetSelected(plb, iSel, fSelect, HILITEANDSEL);
/*
* Note that we set the caret on bit directly so that we avoid flicker * when drawing this item. ie. We turn on the caret, redraw the item and * turn it back on again. */ if (!fSelect && plb->iSelBase != iSel) { xxxLBSetCaret(plb, TRUE); } else if (plb->fCaret) { plb->fCaretOn = TRUE; }
if (LBGetItemRect(plb, iSel, &rc)) { xxxLBInvalidateRect(plb, &rc, FALSE); } }
/*
* We need to send this event even if the listbox isn't visible. See * bug #88548. Also see 355612. */ if (_IsWindowVisible(plb->spwnd) || (GetFocus() == HWq(plb->spwnd))) { if (uEvent == EVENT_OBJECT_FOCUS) { LBEvent(plb, uEvent, plb->iSelBase); uEvent = EVENT_OBJECT_SELECTION; } LBEvent(plb, uEvent, iSel); }
return 0; }
/***************************************************************************\
* xxxLBoxDrawItem * * This fills the draw item struct with some constant data for the given * item. The caller will only have to modify a small part of this data * for specific needs. * * History: * 16-Apr-1992 beng The NODATA case \***************************************************************************/
void xxxLBoxDrawItem( PLBIV plb, INT item, UINT itemAction, UINT itemState, LPRECT lprect) { DRAWITEMSTRUCT dis; TL tlpwndParent;
CheckLock(plb->spwnd);
/*
* Fill the DRAWITEMSTRUCT with the unchanging constants */
dis.CtlType = ODT_LISTBOX; dis.CtlID = PtrToUlong(plb->spwnd->spmenu);
/*
* Use -1 if an invalid item number is being used. This is so that the app * can detect if it should draw the caret (which indicates the lb has the * focus) in an empty listbox */ dis.itemID = (UINT)(item < plb->cMac ? item : -1); dis.itemAction = itemAction; dis.hwndItem = HWq(plb->spwnd); dis.hDC = plb->hdc; dis.itemState = itemState | (UINT)(TestWF(plb->spwnd, WFDISABLED) ? ODS_DISABLED : 0);
if (TestWF(plb->spwnd, WEFPUIFOCUSHIDDEN)) { dis.itemState |= ODS_NOFOCUSRECT; } if (TestWF(plb->spwnd, WEFPUIACCELHIDDEN)) { dis.itemState |= ODS_NOACCEL; }
/*
* Set the app supplied data */ if (!plb->cMac || !plb->fHasData) {
/*
* If no strings or no items, just use 0 for data. This is so that we * can display a caret when there are no items in the listbox. * * Lazy-eval listboxes of course have no data to pass - only itemID. */ dis.itemData = 0L; } else { dis.itemData = LBGetItemData(plb, item); }
CopyRect(&dis.rcItem, lprect);
/*
* Set the window origin to the horizontal scroll position. This is so that * text can always be drawn at 0,0 and the view region will only start at * the horizontal scroll offset. We pass this as wParam */ /*
* Note: Only pass the itemID in wParam for 3.1 or newer apps. We break * ccMail otherwise. */
ThreadLock(plb->spwndParent, &tlpwndParent); SendMessage(HW(plb->spwndParent), WM_DRAWITEM, TestWF(plb->spwndParent, WFWIN31COMPAT) ? dis.CtlID : 0, (LPARAM)&dis); ThreadUnlock(&tlpwndParent); }
/***************************************************************************\
* xxxLBBlockHilite * * In Extended selection mode for multiselection listboxes, when * mouse is draged to a new position, the range being marked should be * properly sized(parts of which will be highlighted/dehighlighted). * NOTE: This routine assumes that iSelFromPt and LasMouseMove are not * equal because only in that case this needs to be called; * NOTE: This routine calculates the region whose display attribute is to * be changed in an optimised way. Instead of de-highlighting the * the old range completely and highlight the new range, it omits * the regions that overlap and repaints only the non-pverlapping * area. * fKeyBoard = TRUE if this is called for Keyboard interface * FALSE if called from Mouse interface routines * * History: \***************************************************************************/
void xxxLBBlockHilite( PLBIV plb, INT iSelFromPt, BOOL fKeyBoard) { INT sCurPosOffset; INT sLastPosOffset; INT sHiliteOrSel; BOOL fUseSelStatus; BOOL DeHiliteStatus;
CheckLock(plb->spwnd);
if (fKeyBoard) {
/*
* Set both Hilite and Selection states */ sHiliteOrSel = HILITEANDSEL;
/*
* Do not use the Selection state while de-hiliting */ fUseSelStatus = FALSE; DeHiliteStatus = FALSE; } else {
/*
* Set/Reset only the Hilite state */ sHiliteOrSel = HILITEONLY;
/*
* Use the selection state for de-hilighting */ fUseSelStatus = TRUE; DeHiliteStatus = plb->fNewItemState; }
/*
* The idea of the routine is to : * 1. De-hilite the old range (iMouseDown to iLastMouseDown) and * 2. Hilite the new range (iMouseDwon to iSelFromPt) */
/*
* Offset of current mouse position from the anchor point */ sCurPosOffset = plb->iMouseDown - iSelFromPt;
/*
* Offset of last mouse position from the anchor point */ sLastPosOffset = plb->iMouseDown - plb->iLastMouseMove;
/*
* Check if both current position and last position lie on the same * side of the anchor point. */ if ((sCurPosOffset * sLastPosOffset) >= 0) {
/*
* Yes they are on the same side; So, highlight/dehighlight only * the difference. */ if (abs(sCurPosOffset) > abs(sLastPosOffset)) { xxxAlterHilite(plb, plb->iLastMouseMove, iSelFromPt, plb->fNewItemState, sHiliteOrSel, FALSE); } else { xxxAlterHilite(plb, iSelFromPt, plb->iLastMouseMove, DeHiliteStatus, sHiliteOrSel, fUseSelStatus); } } else { xxxAlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove, DeHiliteStatus, sHiliteOrSel, fUseSelStatus); xxxAlterHilite(plb, plb->iMouseDown, iSelFromPt, plb->fNewItemState, sHiliteOrSel, FALSE); } }
/***************************************************************************\
* xxxAlterHilite * * Changes the hilite state of (i..j] (ie. excludes i, includes j in case * you've forgotten this notation) to fHilite. It inverts this changes * the hilite state. * * OpFlags: HILITEONLY Only change the display state of the items * SELONLY Only Change the selection state of the items * HILITEANDSELECT Do both. * fHilite: * HILITE/TRUE * DEHILITE/FALSE * fSelStatus: * if TRUE, use the selection state of the item to hilite/dehilite * if FALSE, use the fHilite parameter to hilite/dehilite * * History: \***************************************************************************/
void xxxAlterHilite( PLBIV plb, INT i, INT j, BOOL fHilite, INT OpFlags, BOOL fSelStatus) { INT low; INT high; INT sLastInWindow; BOOL fCaretOn; BOOL fSelected;
CheckLock(plb->spwnd);
sLastInWindow = plb->iTop + CItemInWindow(plb, TRUE); sLastInWindow = min(sLastInWindow, plb->cMac - 1); high = max(i, j) + 1;
if (fCaretOn = plb->fCaretOn) { xxxLBSetCaret(plb, FALSE); }
for (low = min(i, j); low < high; low++) { if (low != i) { if (OpFlags & HILITEONLY) { if (fSelStatus) { fSelected = IsSelected(plb, low, SELONLY); } else { fSelected = fHilite; } if (IsSelected(plb, low, HILITEONLY) != fSelected) { if (plb->iTop <= low && low <= sLastInWindow) {
/*
* Invert the item only if it is visible */ xxxInvertLBItem(plb, low, fSelected); } SetSelected(plb, low, fSelected, HILITEONLY); } }
if (OpFlags & SELONLY) { SetSelected(plb, low, fHilite, SELONLY); } } }
if (fCaretOn) { xxxLBSetCaret(plb, TRUE); } }
|