#include "ctlspriv.h" #pragma hdrstop #include "usrctl32.h" #include "listbox.h" //---------------------------------------------------------------------------// // // Number of list box items we allocated whenever we grow the list box // structures. // #define CITEMSALLOC 32 //---------------------------------------------------------------------------// // // Forwards // INT ListBox_BinarySearchString(PLBIV plb,LPWSTR lpstr); //---------------------------------------------------------------------------// // // Routine Description: // // This functions determines how many bytes would be needed to represent // the specified Unicode source string as an ANSI string (not counting the // null terminator) // BOOL UnicodeToMultiByteSize( OUT PULONG BytesInMultiByteString, IN PWCH UnicodeString, IN ULONG BytesInUnicodeString) { // //This should just tell us how much buffer is needed // ULONG cbSize = WideCharToMultiByte(CP_THREAD_ACP, WC_SEPCHARS, UnicodeString, -1, NULL, 0, NULL, NULL); if(cbSize) { *BytesInMultiByteString = cbSize; return TRUE; } return FALSE; } //---------------------------------------------------------------------------// // // ListBox_SetScrollParms() // // Sets the scroll range, page, and position // int ListBox_SetScrollParms(PLBIV plb, int nCtl) { int iPos; int cItems; UINT iPage; SCROLLINFO si; BOOL fNoScroll = FALSE; PSCROLLPOS psp; BOOL fCacheInitialized; int iReturn; if (nCtl == SB_VERT) { iPos = plb->iTop; cItems = plb->cMac; iPage = plb->cItemFullMax; if (!plb->fVertBar) { fNoScroll = TRUE; } psp = &plb->VPos; fCacheInitialized = plb->fVertInitialized; } else { if (plb->fMultiColumn) { iPos = plb->iTop / plb->itemsPerColumn; cItems = plb->cMac ? ((plb->cMac - 1) / plb->itemsPerColumn) + 1 : 0; iPage = plb->numberOfColumns; if (plb->fRightAlign && cItems) { iPos = cItems - iPos - 1; } } else { RECT r = {0}; GetClientRect(plb->hwnd, &r); iPos = plb->xOrigin; cItems = plb->maxWidth; iPage = RECTWIDTH(r); } if (!plb->fHorzBar) { fNoScroll = TRUE; } psp = &plb->HPos; fCacheInitialized = plb->fHorzInitialized; } if (cItems) { cItems--; } if (fNoScroll) { // // Limit page to 0, posMax + 1 // iPage = max(min((int)iPage, cItems + 1), 0); // // Limit pos to 0, posMax - (page - 1). // return max(min(iPos, cItems - ((iPage) ? (int)(iPage - 1) : 0)), 0); } else { si.fMask = SIF_ALL; if (plb->fDisableNoScroll) { si.fMask |= SIF_DISABLENOSCROLL; } // // If the scrollbar is already where we want it, do nothing. // if (fCacheInitialized) { if (psp->fMask == si.fMask && psp->cItems == cItems && psp->iPage == iPage && psp->iPos == iPos) { return psp->iReturn; } } else if (nCtl == SB_VERT) { plb->fVertInitialized = TRUE; } else { plb->fHorzInitialized = TRUE; } si.cbSize = sizeof(SCROLLINFO); si.nMin = 0; si.nMax = cItems; si.nPage = iPage; if (plb->fMultiColumn && plb->fRightAlign) { si.nPos = (iPos+1) > (int)iPage ? iPos - iPage + 1 : 0; } else { si.nPos = iPos; } iReturn = SetScrollInfo(plb->hwnd, nCtl, &si, plb->fRedraw); if (plb->fMultiColumn && plb->fRightAlign) { iReturn = cItems - (iReturn + iPage - 1); } // // Update the position cache // psp->fMask = si.fMask; psp->cItems = cItems; psp->iPage = iPage; psp->iPos = iPos; psp->iReturn = iReturn; return iReturn; } } //---------------------------------------------------------------------------// void ListBox_ShowHideScrollBars(PLBIV plb) { BOOL fVertDone = FALSE; BOOL fHorzDone = FALSE; // // Don't do anything if there are no scrollbars or if parents // are invisible. // if ((!plb->fHorzBar && !plb->fVertBar) || !plb->fRedraw) { return; } // // Adjust iTop if necessary but DO NOT REDRAW PERIOD. We never did // in 3.1. There's a potential bug: // If someone doesn't have redraw off and inserts an item in the // same position as the caret, we'll tell them to draw before they may // have called LB_SETITEMDATA for their item. This is because we turn // the caret off & on inside of ListBox_NewITop(), even if the item isn't // changing. // So we just want to reflect the position/scroll changes. // ListBox_CheckRedraw() will _really_ redraw the visual changes later if // redraw isn't off. // if (!plb->fFromInsert) { ListBox_NewITop(plb, plb->iTop); fVertDone = TRUE; } if (!plb->fMultiColumn) { if (!plb->fFromInsert) { fHorzDone = TRUE; ListBox_HScroll(plb, SB_THUMBPOSITION, plb->xOrigin); } if (!fVertDone) { ListBox_SetScrollParms(plb, SB_VERT); } } if (!fHorzDone) { ListBox_SetScrollParms(plb, SB_HORZ); } } //---------------------------------------------------------------------------// // // ListBox_GetItemDataHandler // // returns the long value associated with listbox items. -1 if error // LONG_PTR ListBox_GetItemDataHandler(PLBIV plb, INT sItem) { LONG_PTR buffer; LPBYTE lpItem; if (sItem < 0 || sItem >= plb->cMac) { TraceMsg(TF_STANDARD, "Invalid index"); return LB_ERR; } // // No-data listboxes always return 0L // if (!plb->fHasData) { return 0L; } lpItem = (plb->rgpch + (sItem * (plb->fHasStrings ? sizeof(LBItem) : sizeof(LBODItem)))); buffer = (plb->fHasStrings ? ((lpLBItem)lpItem)->itemData : ((lpLBODItem)lpItem)->itemData); return buffer; } //---------------------------------------------------------------------------// // // ListBox_GetTextHandler // // Copies the text associated with index to lpbuffer and returns its length. // If fLengthOnly, just return the length of the text without doing a copy. // // Waring: for size only querries lpbuffer is the count of ANSI characters // // Returns count of chars // INT ListBox_GetTextHandler(PLBIV plb, BOOL fLengthOnly, BOOL fAnsi, INT index, LPWSTR lpbuffer) { LPWSTR lpItemText; INT cchText; if (index < 0 || index >= plb->cMac) { TraceMsg(TF_STANDARD, "Invalid index"); return LB_ERR; } if (!plb->fHasStrings && plb->OwnerDraw) { // // Owner draw without strings so we must copy the app supplied DWORD // value. // cchText = sizeof(ULONG_PTR); if (!fLengthOnly) { LONG_PTR UNALIGNED *p = (LONG_PTR UNALIGNED *)lpbuffer; *p = ListBox_GetItemDataHandler(plb, index); } } else { lpItemText = GetLpszItem(plb, index); if (!lpItemText) { return LB_ERR; } // // These are strings so we are copying the text and we must include // the terminating 0 when doing the RtlMoveMemory. // cchText = wcslen(lpItemText); if (fLengthOnly) { if (fAnsi) { UnicodeToMultiByteSize(&cchText, lpItemText, cchText*sizeof(WCHAR)); } } else { if (fAnsi) { #ifdef FE_SB // ListBox_GetTextHandler() cchText = WCSToMB(lpItemText, cchText+1, &((LPSTR)lpbuffer), (cchText+1)*sizeof(WORD), FALSE); // // Here.. cchText contains null-terminate char, subtract it... Because, we pass cchText+1 to // above Unicode->Ansi convertsion to make sure the string is terminated with null. // cchText--; #else WCSToMB(lpItemText, cchText+1, &((LPSTR)lpbuffer), cchText+1, FALSE); #endif // FE_SB } else { CopyMemory(lpbuffer, lpItemText, (cchText+1)*sizeof(WCHAR)); } } } return cchText; } //---------------------------------------------------------------------------// BOOL ListBox_GromMem(PLBIV plb, INT numItems) { LONG cb; HANDLE hMem; // // Allocate memory for pointers to the strings. // cb = (plb->cMax + numItems) * (plb->fHasStrings ? sizeof(LBItem) : (plb->fHasData ? sizeof(LBODItem) : 0)); // // If multiple selection list box (MULTIPLESEL or EXTENDEDSEL), then // allocate an extra byte per item to keep track of it's selection state. // if (plb->wMultiple != SINGLESEL) { cb += (plb->cMax + numItems); } // // Extra bytes for each item so that we can store its height. // if (plb->OwnerDraw == OWNERDRAWVAR) { cb += (plb->cMax + numItems); } // // Don't allocate more than 2G of memory // if (cb > MAXLONG) { return FALSE; } if (plb->rgpch == NULL) { plb->rgpch = ControlAlloc(GetProcessHeap(), (LONG)cb); if ( plb->rgpch == NULL) { return FALSE; } } else { hMem = ControlReAlloc(GetProcessHeap(), plb->rgpch, (LONG)cb); if ( hMem == NULL) { return FALSE; } plb->rgpch = hMem; } plb->cMax += numItems; return TRUE; } //---------------------------------------------------------------------------// LONG ListBox_InitStorage(PLBIV plb, BOOL fAnsi, INT cItems, INT cb) { HANDLE hMem; INT cbChunk; // // if the app is talking ANSI, then adjust for the worst case in unicode // where each single ansi byte translates to one 16 bit unicode value // if (fAnsi) { cb *= sizeof(WCHAR); } // // Fail if either of the parameters look bad. // if ((cItems < 0) || (cb < 0)) { ListBox_NotifyOwner(plb, LBN_ERRSPACE); return LB_ERRSPACE; } // // try to grow the pointer array (if necessary) accounting for the free space // already available. // cItems -= plb->cMax - plb->cMac; if ((cItems > 0) && !ListBox_GromMem(plb, cItems)) { ListBox_NotifyOwner(plb, LBN_ERRSPACE); return LB_ERRSPACE; } // // now grow the string space if necessary // if (plb->fHasStrings) { cbChunk = (plb->ichAlloc + cb); if (cbChunk > plb->cchStrings) { // // Round up to the nearest 256 byte chunk. // cbChunk = (cbChunk & ~0xff) + 0x100; hMem = ControlReAlloc(GetProcessHeap(), plb->hStrings, (LONG)cbChunk); if (!hMem) { ListBox_NotifyOwner(plb, LBN_ERRSPACE); return LB_ERRSPACE; } plb->hStrings = hMem; plb->cchStrings = cbChunk; } } // // return the number of items that can be stored // return plb->cMax; } //---------------------------------------------------------------------------// // // ListBox_InsertItem // // Insert an item at a specified position. // // For owner draw listboxes without LBS_HASSTRINGS style, lpsz // is a 4 byte value we will store for the app. // // INT ListBox_InsertItem(PLBIV plb, LPWSTR lpsz, INT index, UINT wFlags) { INT cbString; INT cbChunk; PBYTE lp; PBYTE lpT; PBYTE lpHeightStart; LONG cbItem; // sizeof the Item in rgpch HANDLE hMem; HDC hdc; if (wFlags & LBI_ADD) { index = (plb->fSort) ? ListBox_BinarySearchString(plb, lpsz) : -1; } if (!plb->rgpch) { if (index != 0 && index != -1) { TraceMsg(TF_STANDARD, "Invalid index"); return LB_ERR; } plb->iSel = -1; plb->iSelBase = 0; plb->cMax = 0; plb->cMac = 0; plb->iTop = 0; plb->rgpch = ControlAlloc(GetProcessHeap(), 0L); if (!plb->rgpch) { return LB_ERR; } } if (index == -1) { index = plb->cMac; } if (index > plb->cMac || plb->cMac >= MAXLONG) { TraceMsg(TF_STANDARD, "Invalid index"); return LB_ERR; } if (plb->fHasStrings) { // // we must store the string in the hStrings memory block. // cbString = (wcslen(lpsz) + 1)*sizeof(WCHAR); cbChunk = (plb->ichAlloc + cbString); if ( cbChunk > plb->cchStrings) { // // Round up to the nearest 256 byte chunk. // cbChunk = (cbChunk & ~0xff) + 0x100; hMem = ControlReAlloc(GetProcessHeap(), plb->hStrings, (LONG)cbChunk); if (!hMem) { ListBox_NotifyOwner(plb, LBN_ERRSPACE); return LB_ERRSPACE; } plb->hStrings = hMem; plb->cchStrings = cbChunk; } // // Note difference between Win 95 code with placement of new string // if (wFlags & UPPERCASE) { CharUpperBuffW((LPWSTR)lpsz, cbString / sizeof(WCHAR)); } else if (wFlags & LOWERCASE) { CharLowerBuffW((LPWSTR)lpsz, cbString / sizeof(WCHAR)); } lp = (PBYTE)(plb->hStrings); MoveMemory(lp + plb->ichAlloc, lpsz, cbString); } // // Now expand the pointer array. // if (plb->cMac >= plb->cMax) { if (!ListBox_GromMem(plb, CITEMSALLOC)) { ListBox_NotifyOwner(plb, LBN_ERRSPACE); return LB_ERRSPACE; } } lpHeightStart = lpT = lp = plb->rgpch; // // Now calculate how much room we must make for the string pointer (lpsz). // If we are ownerdraw without LBS_HASSTRINGS, then a single DWORD // (LBODItem.itemData) stored for each item, but if we have strings with // each item then a LONG string offset (LBItem.offsz) is also stored. // cbItem = (plb->fHasStrings ? sizeof(LBItem) : (plb->fHasData ? sizeof(LBODItem):0)); cbChunk = (plb->cMac - index) * cbItem; if (plb->wMultiple != SINGLESEL) { // // Extra bytes were allocated for selection flag for each item // cbChunk += plb->cMac; } if (plb->OwnerDraw == OWNERDRAWVAR) { // // Extra bytes were allocated for each item's height // cbChunk += plb->cMac; } // // First, make room for the 2 byte pointer to the string or the 4 byte app // supplied value. // lpT += (index * cbItem); MoveMemory(lpT + cbItem, lpT, cbChunk); if (!plb->fHasStrings && plb->OwnerDraw) { if (plb->fHasData) { // // Ownerdraw so just save the DWORD value // lpLBODItem p = (lpLBODItem)lpT; p->itemData = (ULONG_PTR)lpsz; } } else { lpLBItem p = ((lpLBItem)lpT); // // Save the start of the string. Let the item data field be 0 // p->offsz = (LONG)(plb->ichAlloc); p->itemData = 0; plb->ichAlloc += cbString; } // // Now if Multiple Selection lbox, we have to insert a selection status // byte. If var height ownerdraw, then we also have to move up the height // bytes. // if (plb->wMultiple != SINGLESEL) { lpT = lp + ((plb->cMac + 1) * cbItem) + index; MoveMemory(lpT + 1, lpT, plb->cMac - index + (plb->OwnerDraw == OWNERDRAWVAR ? plb->cMac : 0)); *lpT = 0; // fSelected = FALSE } // // Increment count of items in the listbox now before we send a message to // the app. // plb->cMac++; // // If varheight ownerdraw, we much insert an extra byte for the item's // height. // if (plb->OwnerDraw == OWNERDRAWVAR) { MEASUREITEMSTRUCT measureItemStruct; // // Variable height owner draw so we need to get the height of each item. // lpHeightStart += (plb->cMac * cbItem) + index + (plb->wMultiple ? plb->cMac : 0); MoveMemory(lpHeightStart + 1, lpHeightStart, plb->cMac - 1 - index); // // Query for item height only if we are var height owner draw. // measureItemStruct.CtlType = ODT_LISTBOX; measureItemStruct.CtlID = GetDlgCtrlID(plb->hwnd); measureItemStruct.itemID = index; // // System font height is default height // measureItemStruct.itemHeight = SYSFONT_CYCHAR; hdc = GetDC(plb->hwnd); if (hdc) { SIZE size = {0}; GetCharDimensions(hdc, &size); ReleaseDC(plb->hwnd, hdc); if(size.cy) { measureItemStruct.itemHeight = (UINT)size.cy; } else { ASSERT(0);//GetCharDimensions } } measureItemStruct.itemData = (ULONG_PTR)lpsz; // // If "has strings" then add the special thunk bit so the client data // will be thunked to a client side address. LB_DIR sends a string // even if the listbox is not HASSTRINGS so we need to special // thunk this case. HP Dashboard for windows send LB_DIR to a non // HASSTRINGS listbox needs the server string converted to client. // WOW needs to know about this situation as well so we mark the // previously uninitialized itemWidth as FLAT. // SendMessage(plb->hwndParent, WM_MEASUREITEM, measureItemStruct.CtlID, (LPARAM)&measureItemStruct); *lpHeightStart = (BYTE)measureItemStruct.itemHeight; } // // If the item was inserted above the current selection then move // the selection down one as well. // if ((plb->wMultiple == SINGLESEL) && (plb->iSel >= index)) { plb->iSel++; } if (plb->OwnerDraw == OWNERDRAWVAR) { ListBox_SetCItemFullMax(plb); } // // Check if scroll bars need to be shown/hidden // plb->fFromInsert = TRUE; ListBox_ShowHideScrollBars(plb); if (plb->fHorzBar && plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw)) { // // origin to right // ListBox_HScroll(plb, SB_BOTTOM, 0); } plb->fFromInsert = FALSE; ListBox_CheckRedraw(plb, TRUE, index); ListBox_Event(plb, EVENT_OBJECT_CREATE, index); return index; } //---------------------------------------------------------------------------// // // ListBox_lstrcmpi // // This is a version of lstrcmpi() specifically used for listboxes // This gives more weight to '[' characters than alpha-numerics; // The US version of lstrcmpi() and lstrcmp() are similar as far as // non-alphanumerals are concerned; All non-alphanumerals get sorted // before alphanumerals; This means that subdirectory strings that start // with '[' will get sorted before; But we don't want that; So, this // function takes care of it; // INT ListBox_lstrcmpi(LPWSTR lpStr1, LPWSTR lpStr2, DWORD dwLocaleId) { // // NOTE: This function is written so as to reduce the number of calls // made to the costly IsCharAlphaNumeric() function because that might // load a language module; It 'traps' the most frequently occurring cases // like both strings starting with '[' or both strings NOT starting with '[' // first and only in abosolutely necessary cases calls IsCharAlphaNumeric(); // if (*lpStr1 == TEXT('[')) { if (*lpStr2 == TEXT('[')) { goto LBL_End; } if (IsCharAlphaNumeric(*lpStr2)) { return 1; } } if ((*lpStr2 == TEXT('[')) && IsCharAlphaNumeric(*lpStr1)) { return -1; } LBL_End: return (INT)CompareStringW((LCID)dwLocaleId, NORM_IGNORECASE, lpStr1, -1, lpStr2, -1 ) - 2; } //---------------------------------------------------------------------------// // // ListBox_BinarySearchString // // Does a binary search of the items in the SORTED listbox to find // out where this item should be inserted. Handles both HasStrings and item // long WM_COMPAREITEM cases. // INT ListBox_BinarySearchString(PLBIV plb, LPWSTR lpstr) { BYTE **lprgpch; INT sortResult; COMPAREITEMSTRUCT cis; LPWSTR pszLBBase; LPWSTR pszLB; INT itemhigh; INT itemnew = 0; INT itemlow = 0; if (!plb->cMac) { return 0; } lprgpch = (BYTE **)(plb->rgpch); if (plb->fHasStrings) { pszLBBase = plb->hStrings; } itemhigh = plb->cMac - 1; while (itemlow <= itemhigh) { itemnew = (itemhigh + itemlow) / 2; if (plb->fHasStrings) { // // Searching for string matches. // pszLB = (LPWSTR)((LPSTR)pszLBBase + ((lpLBItem)lprgpch)[itemnew].offsz); sortResult = ListBox_lstrcmpi(pszLB, lpstr, plb->dwLocaleId); } else { // // Send compare item messages to the parent for sorting // cis.CtlType = ODT_LISTBOX; cis.CtlID = GetDlgCtrlID(plb->hwnd); cis.hwndItem = plb->hwnd; cis.itemID1 = itemnew; cis.itemData1 = ((lpLBODItem)lprgpch)[itemnew].itemData; cis.itemID2 = (UINT)-1; cis.itemData2 = (ULONG_PTR)lpstr; cis.dwLocaleId = plb->dwLocaleId; sortResult = (INT)SendMessage(plb->hwndParent, WM_COMPAREITEM, cis.CtlID, (LPARAM)&cis); } if (sortResult < 0) { itemlow = itemnew + 1; } else if (sortResult > 0) { itemhigh = itemnew - 1; } else { itemlow = itemnew; goto FoundIt; } } FoundIt: return max(0, itemlow); } //---------------------------------------------------------------------------// BOOL ListBox_ResetContentHandler(PLBIV plb) { if (!plb->cMac) { return TRUE; } ListBox_DoDeleteItems(plb); if (plb->rgpch != NULL) { ControlFree(GetProcessHeap(), plb->rgpch); plb->rgpch = NULL; } if (plb->hStrings != NULL) { ControlFree(GetProcessHeap(), plb->hStrings); plb->hStrings = NULL; } ListBox_InitHStrings(plb); if (TESTFLAG(GET_STATE2(plb), WS_S2_WIN31COMPAT)) { ListBox_CheckRedraw(plb, FALSE, 0); } else if (IsWindowVisible(plb->hwnd)) { InvalidateRect(plb->hwnd, NULL, TRUE); } plb->iSelBase = 0; plb->iTop = 0; plb->cMac = 0; plb->cMax = 0; plb->xOrigin = 0; plb->iLastSelection = 0; plb->iSel = -1; ListBox_ShowHideScrollBars(plb); return TRUE; } //---------------------------------------------------------------------------// INT ListBox_DeleteStringHandler(PLBIV plb, INT sItem) { LONG cb; LPBYTE lp; LPBYTE lpT; RECT rc; int cbItem; LPWSTR lpString; PBYTE pbStrings; INT cbStringLen; LPBYTE itemNumbers; INT sTmp; if (sItem < 0 || sItem >= plb->cMac) { TraceMsg(TF_STANDARD, "Invalid index"); return LB_ERR; } ListBox_Event(plb, EVENT_OBJECT_DESTROY, sItem); if (plb->cMac == 1) { // // When the item count is 0, we send a resetcontent message so that we // can reclaim our string space this way. // SendMessageW(plb->hwnd, LB_RESETCONTENT, 0, 0); goto FinishUpDelete; } // // Get the rectangle associated with the last item in the listbox. If it is // visible, we need to invalidate it. When we delete an item, everything // scrolls up to replace the item deleted so we must make sure we erase the // old image of the last item in the listbox. // if (ListBox_GetItemRectHandler(plb, (INT)(plb->cMac - 1), &rc)) { ListBox_InvalidateRect(plb, &rc, TRUE); } // // 3.1 and earlier used to only send WM_DELETEITEMs if it was an ownerdraw // listbox. 4.0 and above will send WM_DELETEITEMs for every item that has // nonzero item data. // if (TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT) || (plb->OwnerDraw && plb->fHasData)) { ListBox_DeleteItem(plb, sItem); } plb->cMac--; cbItem = (plb->fHasStrings ? sizeof(LBItem) : (plb->fHasData ? sizeof(LBODItem): 0)); cb = ((plb->cMac - sItem) * cbItem); // // Byte for the selection status of the item. // if (plb->wMultiple != SINGLESEL) { cb += (plb->cMac + 1); } if (plb->OwnerDraw == OWNERDRAWVAR) { // // One byte for the height of the item. // cb += (plb->cMac + 1); } // // Might be nodata and singlesel, for instance. // but what out for the case where cItem == cMac (and cb == 0). // if ((cb != 0) || plb->fHasStrings) { lp = plb->rgpch; lpT = (lp + (sItem * cbItem)); if (plb->fHasStrings) { // // If we has strings with each item, then we want to compact the string // heap so that we can recover the space occupied by the string of the // deleted item. // // // Get the string which we will be deleting // pbStrings = (PBYTE)(plb->hStrings); lpString = (LPTSTR)(pbStrings + ((lpLBItem)lpT)->offsz); cbStringLen = (wcslen(lpString) + 1) * sizeof(WCHAR); // // Now compact the string array // plb->ichAlloc = plb->ichAlloc - cbStringLen; MoveMemory(lpString, (PBYTE)lpString + cbStringLen, plb->ichAlloc + (pbStrings - (LPBYTE)lpString)); // // We have to update the string pointers in plb->rgpch since all the // string after the deleted string have been moved down stringLength // bytes. Note that we have to explicitly check all items in the list // box if the string was allocated after the deleted item since the // LB_SORT style allows a lower item number to have a string allocated // at the end of the string heap for example. // itemNumbers = lp; for (sTmp = 0; sTmp <= plb->cMac; sTmp++) { lpLBItem p =(lpLBItem)itemNumbers; if ( (LPTSTR)(p->offsz + pbStrings) > lpString ) { p->offsz -= cbStringLen; } p++; itemNumbers=(LPBYTE)p; } } // // Now compact the pointers to the strings (or the long app supplied values // if ownerdraw without strings). // MoveMemory(lpT, lpT + cbItem, cb); // // Compress the multiselection bytes // if (plb->wMultiple != SINGLESEL) { lpT = (lp + (plb->cMac * cbItem) + sItem); MoveMemory(lpT, lpT + 1, plb->cMac - sItem + (plb->OwnerDraw == OWNERDRAWVAR ? plb->cMac + 1 : 0)); } if (plb->OwnerDraw == OWNERDRAWVAR) { // // Compress the height bytes // lpT = (lp + (plb->cMac * cbItem) + (plb->wMultiple ? plb->cMac : 0) + sItem); MoveMemory(lpT, lpT + 1, plb->cMac - sItem); } } if (plb->wMultiple == SINGLESEL) { if (plb->iSel == sItem) { plb->iSel = -1; if (plb->pcbox != NULL) { ComboBox_InternalUpdateEditWindow(plb->pcbox, NULL); } } else if (plb->iSel > sItem) { plb->iSel--; } } if ((plb->iMouseDown != -1) && (sItem <= plb->iMouseDown)) { plb->iMouseDown = -1; } if (plb->iSelBase && sItem == plb->iSelBase) { plb->iSelBase--; } if (plb->cMac) { plb->iSelBase = min(plb->iSelBase, plb->cMac - 1); } else { plb->iSelBase = 0; } if ((plb->wMultiple == EXTENDEDSEL) && (plb->iSel == -1)) { plb->iSel = plb->iSelBase; } if (plb->OwnerDraw == OWNERDRAWVAR) { ListBox_SetCItemFullMax(plb); } // // We always set a new iTop. The iTop won't change if it doesn't need to // but it will change if: 1. The iTop was deleted or 2. We need to change // the iTop so that we fill the listbox. // ListBox_InsureVisible(plb, plb->iTop, FALSE); FinishUpDelete: // // Check if scroll bars need to be shown/hidden // plb->fFromInsert = TRUE; ListBox_ShowHideScrollBars(plb); plb->fFromInsert = FALSE; ListBox_CheckRedraw(plb, TRUE, sItem); ListBox_InsureVisible(plb, plb->iSelBase, FALSE); return plb->cMac; } //---------------------------------------------------------------------------// // // ListBox_DeleteItem // // Sends a WM_DELETEITEM message to the owner of an ownerdraw listbox // void ListBox_DeleteItem(PLBIV plb, INT sItem) { DELETEITEMSTRUCT dis; HWND hwndParent; if (plb->hwnd == NULL) { return; } hwndParent = plb->hwndParent; // // No need to send message if no data! // if (!plb->fHasData) { return; } // // Fill the DELETEITEMSTRUCT // dis.CtlType = ODT_LISTBOX; dis.CtlID = GetDlgCtrlID(plb->hwnd); dis.itemID = sItem; dis.hwndItem = plb->hwnd; // // Bug 262122 - joejo // Fixed in 93 so that ItemData was passed. For some reason, not // merged in. // dis.itemData = ListBox_GetItemDataHandler(plb, sItem); if (hwndParent != NULL) { SendMessage(hwndParent, WM_DELETEITEM, dis.CtlID, (LPARAM)&dis); } } //---------------------------------------------------------------------------// // // ListBox_CalcAllocNeeded // // Calculate the number of bytes needed in rgpch to accommodate a given // number of items. // UINT ListBox_CalcAllocNeeded(PLBIV plb, INT cItems) { UINT cb; // // Allocate memory for pointers to the strings. // cb = cItems * (plb->fHasStrings ? sizeof(LBItem) : (plb->fHasData ? sizeof(LBODItem) : 0)); // // If multiple selection list box (MULTIPLESEL or EXTENDEDSEL), then // allocate an extra byte per item to keep track of it's selection state. // if (plb->wMultiple != SINGLESEL) { cb += cItems; } // // Extra bytes for each item so that we can store its height. // if (plb->OwnerDraw == OWNERDRAWVAR) { cb += cItems; } return cb; } //---------------------------------------------------------------------------// // // ListBox_SetCount // // Sets the number of items in a lazy-eval (fNoData) listbox. // // Calling SetCount scorches any existing selection state. To preserve // selection state, call Insert/DeleteItem instead. // INT ListBox_SetCount(PLBIV plb, INT cItems) { UINT cbRequired; BOOL fRedraw; // // SetCount is only valid on lazy-eval ("nodata") listboxes. // All other lboxen must add their items one at a time, although // they may SetCount(0) via RESETCONTENT. // if (plb->fHasStrings || plb->fHasData) { return LB_ERR; } if (cItems == 0) { SendMessage(plb->hwnd, LB_RESETCONTENT, 0, 0); return 0; } // // If redraw isn't turned off, turn it off now // if (fRedraw = plb->fRedraw) { ListBox_SetRedraw(plb, FALSE); } cbRequired = ListBox_CalcAllocNeeded(plb, cItems); // // Reset selection and position // plb->iSelBase = 0; plb->iTop = 0; plb->cMax = 0; plb->xOrigin = 0; plb->iLastSelection = 0; plb->iSel = -1; if (cbRequired != 0) { // // Only if record instance data required // // // If listbox was previously empty, prepare for the // realloc-based alloc strategy ahead. // if (plb->rgpch == NULL) { plb->rgpch = ControlAlloc(GetProcessHeap(), 0L); plb->cMax = 0; if (plb->rgpch == NULL) { ListBox_NotifyOwner(plb, LBN_ERRSPACE); return LB_ERRSPACE; } } // // rgpch might not have enough room for the new record instance // data, so check and realloc as necessary. // if (cItems >= plb->cMax) { INT cMaxNew; UINT cbNew; HANDLE hmemNew; // // Since ListBox_GromMem presumes a one-item-at-a-time add schema, // SetCount can't use it. Too bad. // cMaxNew = cItems+CITEMSALLOC; cbNew = ListBox_CalcAllocNeeded(plb, cMaxNew); hmemNew = ControlReAlloc(GetProcessHeap(), plb->rgpch, cbNew); if (hmemNew == NULL) { ListBox_NotifyOwner(plb, LBN_ERRSPACE); return LB_ERRSPACE; } plb->rgpch = hmemNew; plb->cMax = cMaxNew; } // // Reset the item instance data (multisel annotations) // ZeroMemory(plb->rgpch, cbRequired); } plb->cMac = cItems; // // Turn redraw back on // if (fRedraw) { ListBox_SetRedraw(plb, TRUE); } ListBox_InvalidateRect(plb, NULL, TRUE); ListBox_ShowHideScrollBars(plb); // takes care of fRedraw return 0; }