You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1416 lines
36 KiB
1416 lines
36 KiB
#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;
|
|
}
|