#include "ctlspriv.h" #pragma hdrstop #include #include "usrctl32.h" #include "listbox.h" //---------------------------------------------------------------------------// // // Defines and common macros // #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 // // LATER IanJa: these vary by country! For US they are VK_OEM_2 VK_OEM_5. // Change lboxctl2.c MapVirtualKey to character - and fix the spelling? // #define VERKEY_SLASH 0xBF // Vertual key for '/' character #define VERKEY_BACKSLASH 0xDC // Vertual key for '\' character //---------------------------------------------------------------------------// // // Forwards // VOID ListBox_NewITopEx(PLBIV, INT, DWORD); VOID ListBox_FillDrawItem(PLBIV, INT, UINT, UINT, LPRECT); VOID ListBox_BlockHilite(PLBIV, INT, BOOL); VOID ListBox_AlterHilite(PLBIV, INT, INT, BOOL, INT, BOOL); //---------------------------------------------------------------------------// // // ListBox_TermDC // // Cleans up when done with listbox dc. // __inline void ListBox_TermDC(PLBIV plb) { if (plb->hFont) { SelectObject(plb->hdc, GetStockObject(SYSTEM_FONT)); } } //---------------------------------------------------------------------------// // // ListBox_InitDC // // Initializes dc for listbox // void ListBox_InitDC(PLBIV plb) { RECT rc; // // Set font // if (plb->hFont) { SelectObject(plb->hdc, plb->hFont); } // // Set clipping area // GetClientRect(plb->hwnd, &rc); IntersectClipRect(plb->hdc, rc.left, rc.top, rc.right, rc.bottom); OffsetWindowOrgEx(plb->hdc, plb->xOrigin, 0, NULL); } //---------------------------------------------------------------------------// // // ListBox_GetDC // // Returns a DC which can be used by a list box even if parentDC is in effect // BOOL ListBox_GetDC(PLBIV plb) { if (plb->hdc) { return FALSE; } plb->hdc = GetDC(plb->hwnd); ListBox_InitDC(plb); return TRUE; } //---------------------------------------------------------------------------// void ListBox_ReleaseDC(PLBIV plb) { ListBox_TermDC(plb); ReleaseDC(plb->hwnd, plb->hdc); plb->hdc = NULL; } //---------------------------------------------------------------------------// // // ListBox_InvalidateRect() // // 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 ListBox_InvalidateRect(PLBIV plb, LPRECT lprc, BOOL fErase) { if (IsLBoxVisible(plb)) { InvalidateRect(plb->hwnd, lprc, fErase); return TRUE; } if (!plb->fRedraw) { plb->fDeferUpdate = TRUE; } return FALSE; } //---------------------------------------------------------------------------// // // ListBox_GetBrush // // Gets background brush & colors for listbox. // HBRUSH ListBox_GetBrush(PLBIV plb, HBRUSH *phbrOld) { HBRUSH hbr; HBRUSH hbrOld; HWND hwndParent = plb->hwndParent; SetBkMode(plb->hdc, OPAQUE); // // Get brush & colors // // copied from windows\core\ntuser\kernel\random.c if (hwndParent == NULL || hwndParent == GetDesktopWindow()) { hbr = (HBRUSH)SendMessage(plb->hwnd, WM_CTLCOLORLISTBOX, (WPARAM)plb->hdc, (LPARAM)plb->hwnd); } else { hbr = (HBRUSH)SendMessage(hwndParent, WM_CTLCOLORLISTBOX, (WPARAM)plb->hdc, (LPARAM)plb->hwnd); } ASSERT(hbr != 0); // // Select brush into dc // if (hbr != NULL) { hbrOld = SelectObject(plb->hdc, hbr); if (phbrOld) { *phbrOld = hbrOld; } } return hbr; } //---------------------------------------------------------------------------// // // ListBox_GetItemRectHandler // // 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. // BOOL ListBox_GetItemRectHandler(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); TraceMsg(TF_STANDARD, "Invalid index"); return LB_ERR; } GetClientRect(plb->hwnd, 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); ASSERT(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 + ListBox_GetVarHeightItemHeight(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 ? ListBox_GetVarHeightItemHeight(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 - ListBox_GetVarHeightItemHeight(plb, sTmp); } lprc->bottom = lprc->top + ListBox_GetVarHeightItemHeight(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 + ListBox_CItemInWindow(plb, TRUE))); } //---------------------------------------------------------------------------// // // ListBox_PrintCallback // // Called back from DrawState // BOOL CALLBACK ListBox_PrintCallback(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; } xStart = plb->fMultiColumn ? 0 : 2; if (plb->fRightAlign) { oldAlign = SetTextAlign(hdc, TA_RIGHT | GetTextAlign(hdc)); xStart = cx - xStart; } cLen = wcslen(lpstr); if (plb->fUseTabStops) { TabbedTextOut(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; } //---------------------------------------------------------------------------// void ListBox_DrawItem(PLBIV plb, INT sItem, LPRECT lprect, BOOL fHilite, HBRUSH hbr) { LPWSTR lpstr; DWORD rgbSave; DWORD rgbBkSave; UINT uFlags; HDC hdc = plb->hdc; UINT oldAlign; HBRUSH hNewBrush; // // If the item is selected, then fill with highlight color // if (fHilite) { FillRectClr(hdc, lprect, SYSRGB(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 (TESTFLAG(GET_STYLE(plb), WS_DISABLED)) { 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)); } hNewBrush = CreateSolidBrush(SYSRGB(WINDOWTEXT)); DrawState(hdc, hNewBrush, ListBox_PrintCallback, (LPARAM)lpstr, (WPARAM)plb, lprect->left, lprect->top, lprect->right-lprect->left, lprect->bottom-lprect->top, uFlags); if (hNewBrush) { DeleteObject(hNewBrush); } if (plb->fRtoLReading) { SetTextAlign(hdc, oldAlign); } if (fHilite) { SetTextColor(hdc, rgbSave); SetBkColor(hdc, rgbBkSave); } } //---------------------------------------------------------------------------// void ListBox_SetCaret(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 = ListBox_GetDC(plb); ListBox_GetItemRectHandler(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 (ListBox_IsSelected(plb, plb->iSelBase, HILITEONLY)) { itemState |= ODS_SELECTED; } ListBox_FillDrawItem(plb, plb->iSelBase, ODA_FOCUS, itemState, &rc); } else if (!TESTFLAG(GET_EXSTYLE(plb), WS_EXP_UIFOCUSHIDDEN)) { 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) { ListBox_ReleaseDC(plb); } } plb->fCaretOn = !!fSetCaret; } } //---------------------------------------------------------------------------// BOOL ListBox_IsSelected(PLBIV plb, INT sItem, UINT wOpFlags) { LPBYTE lp; if ((sItem >= plb->cMac) || (sItem < 0)) { TraceMsg(TF_STANDARD, "Invalid index"); 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 { // // SELONLY // sItem &= 0x0F; } return sItem; } //---------------------------------------------------------------------------// // // ListBox_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. // INT ListBox_CItemInWindow(PLBIV plb, BOOL fPartial) { RECT rect; if (plb->OwnerDraw == OWNERDRAWVAR) { return ListBox_VisibleItemsVarOwnerDraw(plb, fPartial); } if (plb->fMultiColumn) { return plb->itemsPerColumn * (plb->numberOfColumns + (fPartial ? 1 : 0)); } GetClientRect(plb->hwnd, &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 -- // ASSERT(plb->cyChar); if (!plb->cyChar) { plb->cyChar = SYSFONT_CYCHAR; } return (INT)((rect.bottom / plb->cyChar) + ((rect.bottom % plb->cyChar)? (fPartial ? 1 : 0) : 0)); } //---------------------------------------------------------------------------// // // ListBox_VScroll // // Handles vertical scrolling of the listbox // void ListBox_VScroll(PLBIV plb, INT cmd, int yAmt) { INT iTopNew; INT cItemPageScroll; DWORD dwTime = 0; 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 = ListBox_Page(plb, plb->iTop, FALSE); } else { iTopNew -= cItemPageScroll; } break; case SB_PAGEDOWN: if (plb->OwnerDraw == OWNERDRAWVAR) { iTopNew = ListBox_Page(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( plb->hwnd, 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; ListBox_SetCaret(plb, FALSE); ListBox_ShowHideScrollBars(plb); ListBox_SetCaret(plb, TRUE); return; } ListBox_NewITopEx(plb, iTopNew, dwTime); } } //---------------------------------------------------------------------------// DWORD ListBox_GetScrollFlags(PLBIV plb, DWORD dwTime) { DWORD dwFlags; BOOL bUIEffects, bLBSmoothScroll; SystemParametersInfo(SPI_GETUIEFFECTS, 0, &bUIEffects, 0); SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &bLBSmoothScroll, 0); if (dwTime != 0) { dwFlags = MAKELONG(SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN, dwTime); } else if (bUIEffects && bLBSmoothScroll && 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; } //---------------------------------------------------------------------------// // // ListBox_HScroll // // Supports horizontal scrolling of listboxes // void ListBox_HScroll(PLBIV plb, INT cmd, int xAmt) { int newOrigin = plb->xOrigin; int oldOrigin = plb->xOrigin; int windowWidth; RECT rc; DWORD dwTime = 0; // // 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 // ListBox_HSrollMultiColumn(plb, cmd, xAmt); return; } GetClientRect(plb->hwnd, &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; ListBox_SetCaret(plb, FALSE); ListBox_ShowHideScrollBars(plb); ListBox_SetCaret(plb, TRUE); return; } ListBox_SetCaret(plb, FALSE); plb->xOrigin = newOrigin; plb->xOrigin = ListBox_SetScrollParms(plb, SB_HORZ); if ((cmd == SB_BOTTOM) && plb->fRightAlign) { // // so we know where to draw from. // plb->xRightOrigin = plb->xOrigin; } if(oldOrigin != plb->xOrigin) { DWORD dwFlags; dwFlags = ListBox_GetScrollFlags(plb, dwTime); ScrollWindowEx(plb->hwnd, oldOrigin-plb->xOrigin, 0, NULL, &rc, NULL, NULL, dwFlags); UpdateWindow(plb->hwnd); } ListBox_SetCaret(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 // ListBox_SetScrollParms(plb, SB_HORZ); } } //---------------------------------------------------------------------------// void ListBox_Paint(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; if (lprcBounds == NULL) { lprcBounds = &rcBounds; GetClientRect(plb->hwnd, lprcBounds); } hdcSave = plb->hdc; plb->hdc = hdc; // // Initialize dc. // ListBox_InitDC(plb); // // Turn caret off // fCaretOn = plb->fCaretOn; if (fCaretOn) { ListBox_SetCaret(plb, FALSE); } hbrSave = NULL; hbrControl = ListBox_GetBrush(plb, &hbrSave); // // Get listbox's client // GetClientRect(plb->hwnd, &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 + ListBox_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) { ListBox_GetItemRectHandler(plb, i, &rect); } if (IntersectRect(&scratchRect, lprcBounds, &rect)) { fHilite = !plb->fNoSel && ListBox_IsSelected(plb, i, HILITEONLY); if (plb->OwnerDraw) { // // Fill in the drawitem struct // ListBox_FillDrawItem(plb, i, ODA_DRAWENTIRE, (UINT)(fHilite ? ODS_SELECTED : 0), &rect); } else { ListBox_DrawItem(plb, i, &rect, fHilite, hbrControl); } } } rect.top = rect.bottom; } if (hbrSave != NULL) { SelectObject(hdc, hbrSave); } if (fCaretOn) { ListBox_SetCaret(plb, TRUE); } ListBox_TermDC(plb); plb->hdc = hdcSave; } //---------------------------------------------------------------------------// // // ListBox_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... BOOL ListBox_ISelFromPt(PLBIV plb, POINT pt, LPDWORD piItem) { RECT rect; int y; UINT mouseHighWord = 0; INT sItem; INT sTmp; GetClientRect(plb->hwnd, &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++) { ListBox_GetItemRectHandler(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; } //---------------------------------------------------------------------------// // // ListBox_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; // void ListBox_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; } } //---------------------------------------------------------------------------// // // ListBox_LastFullVisible // // Returns the last fully visible item in the listbox. This is valid // for ownerdraw var height and fixed height listboxes. // INT ListBox_LastFullVisible(PLBIV plb) { INT iLastItem; if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn) { iLastItem = plb->iTop + ListBox_CItemInWindow(plb, FALSE) - 1; iLastItem = max(iLastItem, plb->iTop); } else { iLastItem = min(plb->iTop + plb->cItemFullMax - 1, plb->cMac - 1); } return iLastItem; } //---------------------------------------------------------------------------// void ListBox_InvertItem( PLBIV plb, INT i, BOOL fHilite) { RECT rect; BOOL fCaretOn; HBRUSH hbrControl; BOOL fNewDC; // // Skip if item isn't showing. // if (plb->fNoSel || (i < plb->iTop) || (i >= (plb->iTop + ListBox_CItemInWindow(plb, TRUE)))) { return; } if (IsLBoxVisible(plb)) { ListBox_GetItemRectHandler(plb, i, &rect); // // Only turn off the caret if it is on. This avoids annoying caret // flicker when nesting CaretOns and CaretOffs. // fCaretOn = plb->fCaretOn; if (fCaretOn) { ListBox_SetCaret(plb, FALSE); } fNewDC = ListBox_GetDC(plb); hbrControl = ListBox_GetBrush(plb, NULL); if (!plb->OwnerDraw) { if (!fHilite) { FillRect(plb->hdc, &rect, hbrControl); hbrControl = NULL; } ListBox_DrawItem(plb, i, &rect, fHilite, hbrControl); } else { // // We are ownerdraw so fill in the drawitem struct and send off // to the owner. // ListBox_FillDrawItem(plb, i, ODA_SELECT, (UINT)(fHilite ? ODS_SELECTED : 0), &rect); } if (fNewDC) { ListBox_ReleaseDC(plb); } // // Turn the caret back on only if it was originally on. // if (fCaretOn) { ListBox_SetCaret(plb, TRUE); } } } //---------------------------------------------------------------------------// // // ListBox_ResetWorld // // Resets everyone's selection and hilite state except items in the // range sStItem to sEndItem (Both inclusive). void ListBox_ResetWorld(PLBIV plb, INT iStart, INT iEnd, BOOL fSelect) { INT i; INT iLastInWindow; BOOL fCaretOn; // // 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))) { ListBox_InvertItem(plb, plb->iSel, fSelect); plb->iSel = -1; } return; } iLastInWindow = plb->iTop + ListBox_CItemInWindow(plb, TRUE); fCaretOn = plb->fCaretOn; if (fCaretOn) { ListBox_SetCaret(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 != ListBox_IsSelected(plb, i, HILITEONLY))) { // // Only invert the item if it is visible and present Selection // state is different from what is required. // ListBox_InvertItem(plb, i, fSelect); } // // Set all items outside of preserved range to unselected // ListBox_SetSelected(plb, i, fSelect, HILITEANDSEL); } } if (fCaretOn) { ListBox_SetCaret(plb, TRUE); } } //---------------------------------------------------------------------------// void ListBox_NotifyOwner(PLBIV plb, INT sEvt) { HWND hwndParent = plb->hwndParent; if (hwndParent) { SendMessage(hwndParent, WM_COMMAND, MAKELONG(GetWindowID(plb->hwnd), sEvt), (LPARAM)(plb->hwnd)); } } //---------------------------------------------------------------------------// void ListBox_SetISelBase(PLBIV plb, INT sItem) { ListBox_SetCaret(plb, FALSE); plb->iSelBase = sItem; ListBox_SetCaret(plb, TRUE); ListBox_InsureVisible(plb, plb->iSelBase, FALSE); if (IsWindowVisible(plb->hwnd) || (GetFocus() == plb->hwnd)) { ListBox_Event(plb, EVENT_OBJECT_FOCUS, sItem); } } //---------------------------------------------------------------------------// void ListBox_TrackMouse(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 = plb->hwnd; RECT rcWindow; // // 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. FritzS: NT5 bug 220722. // Some apps (like MSMoney98) rely on this, so added the bLastRITWasKeyboard // check. MCostea #244450 // if ((wMsg == WM_MOUSEMOVE) && RtlEqualMemory(&pt, &(plb->ptPrev), sizeof(POINT)) ) { TraceMsg(TF_STANDARD, "ListBox_TrackMouse ignoring WM_MOUSEMOVE with no mouse movement"); return; } } mousetemp = ListBox_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->hwnd, &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) { // Translate pt and rcClient to screen rel coords ClientToScreen(plb->hwnd, &pt); ClientToScreen(plb->hwnd, (LPPOINT)&rcClient.left ); ClientToScreen(plb->hwnd, (LPPOINT)&rcClient.right ); GetWindowRect(plb->hwnd, &rcWindow); if (!PtInRect(&rcWindow, pt)) { // // Cancel selection if clicked outside of combo; // Accept if clicked on combo button or item. // ComboBox_HideListBoxWindow(plb->pcbox, TRUE, FALSE); } else if (!PtInRect(&rcClient, pt)) { // // Let it pass through. Save, restore capture in // case user is clicking on scrollbar. // plb->fCaptured = FALSE; ReleaseCapture(); SendMessageW(plb->hwnd, WM_NCLBUTTONDOWN, (WPARAM)SendMessageW(plb->hwnd, WM_NCHITTEST, 0, POINTTOPOINTS(pt)), POINTTOPOINTS(pt)); SetCapture(hwnd); plb->fCaptured = TRUE; } break; } plb->fCaptured = FALSE; ReleaseCapture(); } 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 // SetFocus(plb->pcbox->hwndEdit); } 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... // SetFocus(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... // // // Switch off the Caret blinking // KillTimer(hwnd, IDSYS_CARET); // // Make sure the caret does not vanish // ListBox_SetCaret(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 && plb->fCaptured) { // // Mouse down occurred in a empty spot. And we're tracking the list. // 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. // trackPtRetn = (INT)SendMessage(plb->hwndParent, WM_LBTRACKPOINT, (DWORD)iSelFromPt, MAKELONG(pt.x+plb->xOrigin, pt.y)); if (trackPtRetn) { 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; SetCapture(hwnd); plb->fCaptured = TRUE; if (plb->fDoubleClick) { // // Double click. Fake a button up and exit // ListBox_TrackMouse(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 // SetTimer(hwnd, IDSYS_SCROLL, SCROLL_TIMEOUT(), 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) { ListBox_SetCaret(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... // if ( mousetemp || (plb->pcbox && plb->pcbox->fButtonPressed)) { // We're outside the list but haven't begun tracking the list yet. // Select the item that is already selected. iSelTemp = plb->iSel; } else { 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) { ListBox_ResetWorld(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 = ListBox_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) { ListBox_SetSelected(plb, iSelTemp, !fSelected, HILITEANDSEL); // // And invert it // ListBox_InvertItem(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 // ListBox_ResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE); // // Select the current position // ListBox_SetSelected(plb, plb->iMouseDown, TRUE, HILITEANDSEL); ListBox_InvertItem(plb, plb->iMouseDown, TRUE); // // We are changing the selction to this item only // uEvent = EVENT_OBJECT_SELECTION; } else { // // Reset all the previous selections // ListBox_ResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE); // // Select all items from anchor point upto current click pt // ListBox_AlterHilite(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)!ListBox_IsSelected(plb, iSelFromPt, (UINT)HILITEONLY); // // Toggle the current point // ListBox_SetSelected(plb, iSelFromPt, plb->fNewItemState, HILITEANDSEL); ListBox_InvertItem(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) { ListBox_AlterHilite(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)ListBox_IsSelected(plb, plb->iMouseDown, HILITEONLY); // // Select all items from anchor point upto current click pt // ListBox_AlterHilite(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 ListBox_SetISelBase always turns on the caret, we don't need to // do it here... // ListBox_SetISelBase(plb, iSelFromPt); } // // ListBox_SetISelBase will change the focus and send a focus event. // Then we send the selection event. // if (uEvent) { ListBox_Event(plb, uEvent, iSelFromPt); } if (wMsg == WM_LBUTTONDOWN && (GET_EXSTYLE(plb) & WS_EX_DRAGOBJECT)!=0) { if (DragDetect(hwnd, pt)) { // // User is trying to drag object... // // // Fake an up click so that the item is selected... // ListBox_TrackMouse(plb, WM_LBUTTONUP, pt); // // Notify parent // #ifndef WIN16 (32-bit Windows), plb->iSelBase gets // zero-extended to LONG wParam automatically by the compiler. // SendMessage(plb->hwndParent, WM_BEGINDRAG, plb->iSelBase, (LPARAM)hwnd); } else { ListBox_TrackMouse(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 = ((SCROLL_TIMEOUT() * 3) / 2) - ((WORD) dist << 4); if (plb->fRightAlign) { ListBox_HSrollMultiColumn(plb, (pt.x < 0 ? SB_LINEDOWN : SB_LINEUP), 0); } else { ListBox_HSrollMultiColumn(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 = SCROLL_TIMEOUT() - ((WORD) dist << 4); ListBox_VScroll(plb, (pt.y < 0 ? SB_LINEUP : SB_LINEDOWN), 0); SetTimerAndSel: SetTimer(hwnd, IDSYS_SCROLL, max(iTimer, 1), NULL); ListBox_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; case MULTIPLESEL: case EXTENDEDSEL: // // Handle mouse movement with extended selection of items // if (plb->iSelBase != iSelFromPt) { ListBox_SetISelBase(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) { ListBox_BlockHilite(plb, iSelFromPt, FALSE); ListBox_Event(plb, EVENT_OBJECT_SELECTIONWITHIN, iSelFromPt); } plb->iLastMouseMove = iSelFromPt; } break; } break; } case WM_LBUTTONUP: if (plb->fMouseDown) { ListBox_ButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY | (mousetemp ? LBUP_RESETSELECTION : 0) | (fMouseInRect ? LBUP_SUCCESS : 0)); } } } //---------------------------------------------------------------------------// // // ListBox_ButtonUp // // Called in response to both WM_CAPTURECHANGED and WM_LBUTTONUP. // void ListBox_ButtonUp(PLBIV plb, UINT uFlags) { // // 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) { ListBox_AlterHilite(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) { ListBox_InvertItem(plb, plb->iSel, FALSE); } plb->iSel = plb->iLastSelection; ListBox_InvertItem(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. // ListBox_Event(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)) { ListBox_NotifyOwner(plb, LBN_SELCHANGE); } } KillTimer(plb->hwnd, IDSYS_SCROLL); plb->fMouseDown = FALSE; if ( plb->fCaptured || (GetCapture() == plb->hwndParent) ) { plb->fCaptured = FALSE; if (uFlags & LBUP_RELEASECAPTURE) { ReleaseCapture(); } } // // Don't scroll item as long as any part of it is visible // if (plb->iSelBase < plb->iTop || plb->iSelBase > plb->iTop + ListBox_CItemInWindow(plb, TRUE)) { ListBox_InsureVisible(plb, plb->iSelBase, FALSE); } if (plb->fNotify) { if (uFlags & LBUP_NOTIFY) { if (uFlags & LBUP_SUCCESS) { // // ArtMaster needs this SELCHANGE notification now! // if ((plb->fDoubleClick) && !TESTFLAG(GET_STATE2(plb), WS_S2_WIN31COMPAT)) { ListBox_NotifyOwner(plb, LBN_SELCHANGE); } // // Notify owner of click or double click on selection // ListBox_NotifyOwner(plb, (plb->fDoubleClick) ? LBN_DBLCLK : LBN_SELCHANGE); } else { // // Notify owner that the attempted selection was cancelled. // ListBox_NotifyOwner(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. // ASSERT(TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT)); if (plb->iLastSelection != plb->iSel) { ListBox_NotifyOwner(plb, LBN_SELCHANGE); } } } } //---------------------------------------------------------------------------// INT ListBox_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; } //---------------------------------------------------------------------------// void ListBox_NewITop(PLBIV plb, INT iTopNew) { ListBox_NewITopEx(plb, iTopNew, 0); } //---------------------------------------------------------------------------// void ListBox_NewITopEx(PLBIV plb, INT iTopNew, DWORD dwTime) { int iTopOld; BOOL fCaretOn; BOOL fMulti = plb->fMultiColumn; // // Always try to turn off caret whether or not redraw is on // if (fCaretOn = plb->fCaretOn) { ListBox_SetCaret(plb, FALSE); } iTopOld = (fMulti) ? (plb->iTop / plb->itemsPerColumn) : plb->iTop; plb->iTop = iTopNew; iTopNew = ListBox_SetScrollParms(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->hwnd, &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 = ListBox_CalcVarITopScrollAmt(plb, iTopOld, iTopNew); plb->iTop = iTopNew; } else if (abs(iTopNew - iTopOld) > plb->cItemFullMax) { yAmt = 32000; } else { yAmt = (iTopOld - iTopNew) * plb->cyChar; } } dwFlags = ListBox_GetScrollFlags(plb, dwTime); ScrollWindowEx(plb->hwnd, xAmt, yAmt, NULL, &rc, NULL, NULL, dwFlags); UpdateWindow(plb->hwnd); } // // 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. ListBox_SetCaret(plb, TRUE); } } //---------------------------------------------------------------------------// void ListBox_InsureVisible( PLBIV plb, INT iSel, BOOL fPartial) { INT sLastVisibleItem; if (iSel < plb->iTop) { ListBox_NewITop(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 + ListBox_CItemInWindow(plb, TRUE) - (INT)1; } else { sLastVisibleItem = ListBox_LastFullVisible(plb); } if (plb->OwnerDraw != OWNERDRAWVAR) { if (iSel > sLastVisibleItem) { if (plb->fMultiColumn) { ListBox_NewITop(plb, ((iSel / plb->itemsPerColumn) - max(plb->numberOfColumns-1,0)) * plb->itemsPerColumn); } else { ListBox_NewITop(plb, (INT)max(0, iSel - sLastVisibleItem + plb->iTop)); } } } else if (iSel > sLastVisibleItem) { ListBox_NewITop(plb, ListBox_Page(plb, iSel, FALSE)); } } } //---------------------------------------------------------------------------// // // ListBox_CareBlinker // // Timer callback function toggles Caret // Since it is a callback, it is APIENTRY // VOID ListBox_CareBlinker(HWND hwnd, UINT wMsg, UINT_PTR nIDEvent, DWORD dwTime) { 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); plb = ListBox_GetPtr(hwnd); // // 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 // ListBox_SetCaret(plb, !plb->fCaretOn); } //---------------------------------------------------------------------------// // // ListBox_KeyInput // // If msg == LB_KEYDOWN, vKey is the number of the item to go to, // otherwise it is the virtual key. // void ListBox_KeyInput(PLBIV plb, UINT msg, UINT vKey) { INT i; INT iNewISel; INT cItemPageScroll; PCBOX pcbox; BOOL fDropDownComboBox; BOOL fExtendedUIComboBoxClosed; UINT wModifiers = 0; BOOL fSelectKey = FALSE; // assume it is a navigation key UINT uEvent = 0; HWND hwnd = plb->hwnd; BOOL hScrollBar = (GET_STYLE(plb)&WS_HSCROLL)!=0; 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... // iNewISel = (INT)SendMessage(plb->hwndParent, WM_VKEYTOITEM, MAKELONG(vKey, plb->iSelBase), (LPARAM)hwnd); 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)) { ListBox_SetCaret(plb, FALSE); ListBox_ResetWorld(plb, plb->iSelBase, plb->iSelBase, FALSE); // // And select the current item // ListBox_SetSelected(plb, plb->iSelBase, TRUE, HILITEANDSEL); ListBox_InvertItem(plb, plb->iSelBase, TRUE); uEvent = EVENT_OBJECT_SELECTION; goto CaretOnAndNotify; } return; 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)) { ListBox_SetCaret(plb, FALSE); ListBox_ResetWorld(plb, -1, -1, TRUE); uEvent = EVENT_OBJECT_SELECTIONWITHIN; CaretOnAndNotify: ListBox_SetCaret(plb, TRUE); ListBox_Event(plb, uEvent, plb->iSelBase); ListBox_NotifyOwner(plb, LBN_SELCHANGE); } return; 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 // KillTimer(hwnd, IDSYS_CARET); // // Make sure the caret does not vanish // ListBox_SetCaret(plb, TRUE); } else { // // Create a timer to make the caret blink // SetTimer(hwnd, IDSYS_CARET, GetCaretBlinkTime(), ListBox_CareBlinker); } // // 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 = ListBox_Page(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 = ListBox_Page(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 #ifdef USE_MIRRORING ^ (!!TESTFLAG(GET_EXSTYLE(plb), WS_EX_LAYOUTRTL)) #endif ) { 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 #ifdef USE_MIRRORING ^ (!!TESTFLAG(GET_EXSTYLE(plb), WS_EX_LAYOUTRTL)) #endif ) { 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 // ComboBox_ShowListBoxWindow(pcbox, TRUE); } 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. // if (!pcbox->fLBoxVisible) { // // If the listbox isn't visible, just show it // ComboBox_ShowListBoxWindow(pcbox, (vKey != VK_ESCAPE)); } else { // // Ok, the listbox is visible. So hide the listbox window. // ComboBox_HideListBoxWindow(pcbox, TRUE, (vKey != VK_ESCAPE)); } } // // Fall through to the return // default: return; } // // Find out what the new selection should be // iNewISel = ListBox_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) && !ListBox_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: ListBox_SetISelBase(plb, iNewISel); ListBox_SetCaret(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, ListBox_BlockHilite() // 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 // ListBox_BlockHilite // ListBox_ResetWorld(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE); } uEvent = EVENT_OBJECT_SELECTIONWITHIN; // // ListBox_BlockHilite 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 = ListBox_IsSelected(plb, plb->iMouseDown, SELONLY); ListBox_BlockHilite(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 = !ListBox_IsSelected(plb, iNewISel, SELONLY); ListBox_SetSelected(plb, iNewISel, plb->fNewItemState, HILITEANDSEL); ListBox_InvertItem(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. // ListBox_ResetWorld(plb, iNewISel, iNewISel, FALSE); // // Select the current item // ListBox_SetSelected(plb, iNewISel, TRUE, HILITEANDSEL); ListBox_InvertItem(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 // ListBox_InsureVisible(plb, iNewISel, FALSE); ListBox_ShowHideScrollBars(plb); ListBox_SetCaret(plb, TRUE); if (uEvent) { ListBox_Event(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; } ListBox_NotifyOwner(plb, LBN_SELCHANGE); } } //---------------------------------------------------------------------------// // // ListBox_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.) // INT ListBox_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; } //---------------------------------------------------------------------------// // // Listbox_FindStringHandler // // 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. // INT Listbox_FindStringHandler(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; INT sortResult; // // Owner-Draw version of pRg // #define pODRg ((lpLBODItem)pRg) COMPAREITEMSTRUCT cis; LPWSTR listboxString; if (plb->fHasStrings && (!lpstr || !*lpstr)) { return LB_ERR; } if (!plb->fHasData) { TraceMsg(TF_STANDARD, "Listbox_FindStringHandler 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 (ListBox_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 = GetDlgCtrlID(plb->hwnd); cis.hwndItem = plb->hwnd; cis.itemID1 = (UINT)-1; cis.itemData1 = (ULONG_PTR)lpstr; cis.itemID2 = (UINT)sInd; cis.itemData2 = pODRg[sInd].itemData; cis.dwLocaleId = plb->dwLocaleId; sortResult = (INT)SendMessage(plb->hwndParent, WM_COMPAREITEM, cis.CtlID, (LPARAM)&cis); 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; } //---------------------------------------------------------------------------// void ListBox_CharHandler(PLBIV plb, UINT inputChar, BOOL fAnsi) { INT iSel; BOOL fControl; 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 && IsDBCSLeadByteEx(CP_ACP, (BYTE)inputChar)) { WCHAR wch; LPWSTR lpwstr = &wch; inputChar = DbcsCombine(plb->hwnd, (BYTE)inputChar); if (inputChar == 0) { TraceMsg(TF_STANDARD, "ListBox_CharHandler: 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 (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)&inputChar, 2, lpwstr, 1) == 0) { TraceMsg(TF_STANDARD, "ListBox_CharHandler: 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) { MessageBeep(0); break; } iSel = -1; if (plb->pszTypeSearch == NULL) { plb->pszTypeSearch = (LPWSTR)ControlAlloc(GetProcessHeap(), sizeof(WCHAR) * (MAX_TYPESEARCH + 1)); } if (plb->pszTypeSearch == NULL) { MessageBeep(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 // SetTimer(plb->hwnd, IDSYS_LBSEARCH, GetDoubleClickTime()*4, 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 = Listbox_FindStringHandler(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 = Listbox_FindStringHandler(plb, plb->pszTypeSearch, plb->iSelBase, PREFIX, TRUE); } } } // // if match is found -- select it // if (iSel != -1) { CtlKeyInput: ListBox_KeyInput(plb, LB_KEYDOWN, iSel); } } else { HWND hwndParent = plb->hwndParent; if (hwndParent != NULL) { if(fAnsi) { iSel = (INT)SendMessageA(hwndParent, WM_CHARTOITEM, MAKELONG(inputChar, plb->iSelBase), (LPARAM)plb->hwnd); } else { iSel = (INT)SendMessageW(hwndParent, WM_CHARTOITEM, MAKELONG(inputChar, plb->iSelBase), (LPARAM)plb->hwnd); } } else { iSel = -1; } if (iSel != -1 && iSel != -2) { goto CtlKeyInput; } } break; } } //---------------------------------------------------------------------------// // // ListBox_GetSelItemsHandler // // 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. // int ListBox_GetSelItemsHandler(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 (ListBox_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; } //---------------------------------------------------------------------------// // // ListBox_SetRedraw // // Handle WM_SETREDRAW message // void ListBox_SetRedraw(PLBIV plb, BOOL fRedraw) { if (fRedraw) { fRedraw = TRUE; } if (plb->fRedraw != (UINT)fRedraw) { plb->fRedraw = !!fRedraw; if (fRedraw) { ListBox_SetCaret(plb, TRUE); ListBox_ShowHideScrollBars(plb); if (plb->fDeferUpdate) { plb->fDeferUpdate = FALSE; RedrawWindow(plb->hwnd, NULL, NULL, RDW_INVALIDATE | RDW_ERASE | RDW_FRAME | RDW_ALLCHILDREN); } } } } //---------------------------------------------------------------------------// // // ListBox_SetRange // // Selects the range of items between i and j, inclusive. // void ListBox_SetRange(PLBIV plb, int iStart, int iEnd, BOOL fnewstate) { DWORD temp; RECT rc; 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 (ListBox_IsSelected(plb, temp, SELONLY) != fnewstate) { ListBox_SetSelected(plb, temp, fnewstate, HILITEANDSEL); ListBox_GetItemRectHandler(plb, temp, &rc); ListBox_InvalidateRect(plb, (LPRECT)&rc, FALSE); } } ASSERT(plb->wMultiple); ListBox_Event(plb, EVENT_OBJECT_SELECTIONWITHIN, iStart); } //---------------------------------------------------------------------------// int ListBox_SetCurSelHandler(PLBIV plb, int iSel) { if (!(plb->wMultiple || iSel < -1 || iSel >= plb->cMac)) { ListBox_SetCaret(plb, FALSE); if (plb->iSel != -1) { // // This prevents scrolling when iSel == -1 // if (iSel != -1) { ListBox_InsureVisible(plb, iSel, FALSE); } // // Turn off old selection // ListBox_InvertItem(plb, plb->iSel, FALSE); } if (iSel != -1) { ListBox_InsureVisible(plb, iSel, FALSE); plb->iSelBase = plb->iSel = iSel; // // Highlight new selection // ListBox_InvertItem(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 // if (IsWindowVisible(plb->hwnd) || (GetFocus() == plb->hwnd)) { ListBox_Event(plb, EVENT_OBJECT_FOCUS, plb->iSelBase); ListBox_Event(plb, EVENT_OBJECT_SELECTION, plb->iSel); } ListBox_SetCaret(plb, TRUE); return plb->iSel; } return LB_ERR; } //---------------------------------------------------------------------------// // // ListBox_SetItemDataHandler // // Makes the item at index contain the data given. // int ListBox_SetItemDataHandler(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)) { TraceMsg(TF_STANDARD, "ListBox_SetItemDataHandler 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; } //---------------------------------------------------------------------------// void ListBox_CheckRedraw(PLBIV plb, BOOL fConditional, INT sItem) { if (fConditional && plb->cMac && (sItem > (plb->iTop + ListBox_CItemInWindow(plb, TRUE)))) { return; } // // Don't do anything if the parent is not visible. // ListBox_InvalidateRect(plb, (LPRECT)NULL, TRUE); } //---------------------------------------------------------------------------// void ListBox_CaretDestroy(PLBIV plb) { // // 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. // ListBox_ButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY | (plb->fMouseDown ? LBUP_SUCCESS : 0)); } if (plb->fAddSelMode) { // // Switch off the Caret blinking // KillTimer(plb->hwnd, IDSYS_CARET); // // Make sure the caret goes away // ListBox_SetCaret(plb, FALSE); plb->fAddSelMode = FALSE; } plb->fCaret = FALSE; } //---------------------------------------------------------------------------// LONG ListBox_SetSelHandler(PLBIV plb, BOOL fSelect, INT iSel) { INT sItem; RECT rc; UINT uEvent = 0; // // 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; TraceMsg(TF_STANDARD, "Sign extending iSel=0xffff to 0xffffffff"); } if ((plb->wMultiple == SINGLESEL) || (iSel != -1 && iSel >= plb->cMac)) { TraceMsg(TF_STANDARD, "Invalid index"); return LB_ERR; } ListBox_SetCaret(plb, FALSE); if (iSel == -1) { // // Set/clear selection from all items if -1 // for (sItem = 0; sItem < plb->cMac; sItem++) { if (ListBox_IsSelected(plb, sItem, SELONLY) != fSelect) { ListBox_SetSelected(plb, sItem, fSelect, HILITEANDSEL); if (ListBox_GetItemRectHandler(plb, sItem, &rc)) { ListBox_InvalidateRect(plb, &rc, FALSE); } } } ListBox_SetCaret(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... // ListBox_InsureVisible(plb, iSel, TRUE); plb->iSelBase = plb->iSel = iSel; plb->iMouseDown = plb->iLastMouseMove = iSel; uEvent = EVENT_OBJECT_FOCUS; } else { uEvent = EVENT_OBJECT_SELECTIONREMOVE; } ListBox_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) { ListBox_SetCaret(plb, TRUE); } else if (plb->fCaret) { plb->fCaretOn = TRUE; } if (ListBox_GetItemRectHandler(plb, iSel, &rc)) { ListBox_InvalidateRect(plb, &rc, FALSE); } } if (IsWindowVisible(plb->hwnd) || (GetFocus() == plb->hwnd)) { if (uEvent == EVENT_OBJECT_FOCUS) { ListBox_Event(plb, uEvent, plb->iSelBase); uEvent = EVENT_OBJECT_SELECTION; } ListBox_Event(plb, uEvent, iSel); } return 0; } //---------------------------------------------------------------------------// // // ListBox_FillDrawItem // // 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. // void ListBox_FillDrawItem(PLBIV plb, INT item, UINT itemAction, UINT itemState, LPRECT lprect) { DRAWITEMSTRUCT dis; // // Fill the DRAWITEMSTRUCT with the unchanging constants // dis.CtlType = ODT_LISTBOX; dis.CtlID = GetDlgCtrlID(plb->hwnd); // // 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 = plb->hwnd; dis.hDC = plb->hdc; dis.itemState = itemState | (UINT)((GET_STYLE(plb)&WS_DISABLED) ? ODS_DISABLED : 0); if (TESTFLAG(GET_EXSTYLE(plb), WS_EXP_UIFOCUSHIDDEN)) { dis.itemState |= ODS_NOFOCUSRECT; } if (TESTFLAG(GET_EXSTYLE(plb), WS_EXP_UIACCELHIDDEN)) { 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 = ListBox_GetItemDataHandler(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 // SendMessage(plb->hwndParent, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis); } //---------------------------------------------------------------------------// // // ListBox_BlockHilite // // 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 // void ListBox_BlockHilite(PLBIV plb, INT iSelFromPt, BOOL fKeyBoard) { INT sCurPosOffset; INT sLastPosOffset; INT sHiliteOrSel; BOOL fUseSelStatus; BOOL DeHiliteStatus; 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)) { ListBox_AlterHilite(plb, plb->iLastMouseMove, iSelFromPt, plb->fNewItemState, sHiliteOrSel, FALSE); } else { ListBox_AlterHilite(plb, iSelFromPt, plb->iLastMouseMove, DeHiliteStatus, sHiliteOrSel, fUseSelStatus); } } else { ListBox_AlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove, DeHiliteStatus, sHiliteOrSel, fUseSelStatus); ListBox_AlterHilite(plb, plb->iMouseDown, iSelFromPt, plb->fNewItemState, sHiliteOrSel, FALSE); } } //---------------------------------------------------------------------------// // // ListBox_AlterHilite // // 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 // void ListBox_AlterHilite(PLBIV plb, INT i, INT j, BOOL fHilite, INT OpFlags, BOOL fSelStatus) { INT low; INT high; INT sLastInWindow; BOOL fCaretOn; BOOL fSelected; sLastInWindow = plb->iTop + ListBox_CItemInWindow(plb, TRUE); sLastInWindow = min(sLastInWindow, plb->cMac - 1); high = max(i, j) + 1; if (fCaretOn = plb->fCaretOn) { ListBox_SetCaret(plb, FALSE); } for (low = min(i, j); low < high; low++) { if (low != i) { if (OpFlags & HILITEONLY) { if (fSelStatus) { fSelected = ListBox_IsSelected(plb, low, SELONLY); } else { fSelected = fHilite; } if (ListBox_IsSelected(plb, low, HILITEONLY) != fSelected) { if (plb->iTop <= low && low <= sLastInWindow) { // // Invert the item only if it is visible // ListBox_InvertItem(plb, low, fSelected); } ListBox_SetSelected(plb, low, fSelected, HILITEONLY); } } if (OpFlags & SELONLY) { ListBox_SetSelected(plb, low, fHilite, SELONLY); } } } if (fCaretOn) { ListBox_SetCaret(plb, TRUE); } }