windows-nt/Source/XPSP1/NT/shell/comctl32/v6/listbox.c
2020-09-26 16:20:57 +08:00

2310 lines
56 KiB
C

#include "ctlspriv.h"
#pragma hdrstop
#include "usrctl32.h"
#include "listbox.h"
//---------------------------------------------------------------------------//
//
// Forwards
//
VOID ListBox_CalcItemRowsAndColumns(PLBIV);
LONG ListBox_Create(PLBIV, HWND, LPCREATESTRUCT);
VOID ListBox_Destroy(PLBIV, HWND);
VOID ListBox_SetFont(PLBIV, HANDLE, BOOL);
VOID ListBox_Size(PLBIV, INT, INT, BOOL);
BOOL ListBox_SetTabStopsHandler(PLBIV, INT, LPINT);
VOID ListBox_DropObjectHandler(PLBIV, PDROPSTRUCT);
int ListBox_GetSetItemHeightHandler(PLBIV, UINT, int, UINT);
//---------------------------------------------------------------------------//
//
// InitListBoxClass() - Registers the control's window class
//
BOOL InitListBoxClass(HINSTANCE hinst)
{
WNDCLASS wc;
wc.lpfnWndProc = ListBox_WndProc;
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
wc.hIcon = NULL;
wc.lpszMenuName = NULL;
wc.hInstance = hinst;
wc.lpszClassName = WC_LISTBOX;
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1); // NULL;
wc.style = CS_GLOBALCLASS | CS_PARENTDC | CS_DBLCLKS;
wc.cbWndExtra = sizeof(PLBIV);
wc.cbClsExtra = 0;
if (!RegisterClass(&wc) && !GetClassInfo(hinst, WC_LISTBOX, &wc))
return FALSE;
return TRUE;
}
//---------------------------------------------------------------------------//
//
// ListBox_WndProc
//
// Window Procedure for ListBox AND ComboLBox controls.
//
LRESULT APIENTRY ListBox_WndProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
PLBIV plb;
UINT wFlags;
LRESULT lReturn = FALSE;
//
// Get the instance data for this listbox control
//
plb = ListBox_GetPtr(hwnd);
if (!plb && uMsg != WM_NCCREATE)
{
goto CallDWP;
}
switch (uMsg)
{
case LB_GETTOPINDEX:
//
// Return index of top item displayed.
//
return plb->iTop;
case LB_SETTOPINDEX:
if (wParam && ((INT)wParam < 0 || (INT)wParam >= plb->cMac))
{
TraceMsg(TF_STANDARD, "Invalid index");
return LB_ERR;
}
if (plb->cMac)
{
ListBox_NewITop(plb, (INT)wParam);
}
break;
case WM_STYLECHANGED:
plb->fRtoLReading = ((GET_EXSTYLE(plb) & WS_EX_RTLREADING) != 0);
plb->fRightAlign = ((GET_EXSTYLE(plb) & WS_EX_RIGHT) != 0);
ListBox_CheckRedraw(plb, FALSE, 0);
break;
case WM_WINDOWPOSCHANGED:
//
// If we are in the middle of creation, ignore this
// message because it will generate a WM_SIZE message.
// See ListBox_Create().
//
if (!plb->fIgnoreSizeMsg)
{
goto CallDWP;
}
break;
case WM_SIZE:
//
// If we are in the middle of creation, ignore size
// messages. See ListBox_Create().
//
if (!plb->fIgnoreSizeMsg)
{
ListBox_Size(plb, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam), FALSE);
}
break;
case WM_ERASEBKGND:
{
HDC hdcSave = plb->hdc;
HBRUSH hbr;
plb->hdc = (HDC)wParam;
hbr = ListBox_GetBrush(plb, NULL);
if (hbr)
{
RECT rcClient;
GetClientRect(hwnd, &rcClient);
FillRect(plb->hdc, &rcClient, hbr);
lReturn = TRUE;
}
plb->hdc = hdcSave;
break;
}
case LB_RESETCONTENT:
ListBox_ResetContentHandler(plb);
break;
case WM_TIMER:
if (wParam == IDSYS_LBSEARCH)
{
plb->iTypeSearch = 0;
KillTimer(hwnd, IDSYS_LBSEARCH);
ListBox_InvertItem(plb, plb->iSel, TRUE);
break;
}
uMsg = WM_MOUSEMOVE;
ListBox_TrackMouse(plb, uMsg, plb->ptPrev);
break;
case WM_LBUTTONUP:
//
// 295135: if the combobox dropdown button is pressed and the listbox
// covers the combobox, the ensuing buttonup message gets sent to
// list instead of the combobox, which causes the dropdown to be
// closed immediately.
//
//
// send this to the combo if it hasn't processed buttonup yet after
// dropping the list.
//
if (plb->pcbox && plb->pcbox->hwnd && plb->pcbox->fButtonPressed)
{
return SendMessage(plb->pcbox->hwnd, uMsg, wParam, lParam);
}
// fall through
case WM_MOUSEMOVE:
case WM_LBUTTONDOWN:
case WM_LBUTTONDBLCLK:
{
POINT pt;
POINTSTOPOINT(pt, lParam);
ListBox_TrackMouse(plb, uMsg, pt);
break;
}
case WM_MBUTTONDOWN:
EnterReaderMode(hwnd);
break;
case WM_CAPTURECHANGED:
//
// Note that this message should be handled only on unexpected
// capture changes currently.
//
ASSERT(TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT));
if (plb->fCaptured)
{
ListBox_ButtonUp(plb, LBUP_NOTIFY);
}
break;
case LBCB_STARTTRACK:
//
// Start tracking mouse moves in the listbox, setting capture
//
if (!plb->pcbox)
{
break;
}
plb->fCaptured = FALSE;
if (wParam)
{
POINT pt;
POINTSTOPOINT(pt, lParam);
ScreenToClient(hwnd, &pt);
ListBox_TrackMouse(plb, WM_LBUTTONDOWN, pt);
}
else
{
SetCapture(hwnd);
plb->fCaptured = TRUE;
plb->iLastSelection = plb->iSel;
}
break;
case LBCB_ENDTRACK:
//
// Kill capture, tracking, etc.
//
if ( plb->fCaptured || (GetCapture() == plb->hwndParent) )
{
ListBox_ButtonUp(plb, LBUP_RELEASECAPTURE | (wParam ? LBUP_SELCHANGE :
LBUP_RESETSELECTION));
}
break;
case WM_PRINTCLIENT:
ListBox_Paint(plb, (HDC)wParam, NULL);
break;
case WM_NCPAINT:
if (plb->hTheme && (GET_EXSTYLE(plb) & WS_EX_CLIENTEDGE))
{
HRGN hrgn = (wParam != 1) ? (HRGN)wParam : NULL;
HBRUSH hbr = (HBRUSH)GetClassLongPtr(hwnd, GCLP_HBRBACKGROUND);
if (CCDrawNonClientTheme(plb->hTheme, hwnd, hrgn, hbr, 0, 0))
{
break;
}
}
goto CallDWP;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc;
LPRECT lprc;
if (wParam)
{
hdc = (HDC) wParam;
lprc = NULL;
}
else
{
hdc = BeginPaint(hwnd, &ps);
lprc = &(ps.rcPaint);
}
if (IsLBoxVisible(plb))
{
ListBox_Paint(plb, hdc, lprc);
}
if (!wParam)
{
EndPaint(hwnd, &ps);
}
break;
}
case WM_NCDESTROY:
case WM_FINALDESTROY:
ListBox_Destroy(plb, hwnd);
break;
case WM_SETFOCUS:
CaretCreate(plb);
ListBox_SetCaret(plb, TRUE);
ListBox_NotifyOwner(plb, LBN_SETFOCUS);
ListBox_Event(plb, EVENT_OBJECT_FOCUS, plb->iSelBase);
break;
case WM_KILLFOCUS:
//
// Reset the wheel delta count.
//
gcWheelDelta = 0;
ListBox_SetCaret(plb, FALSE);
ListBox_CaretDestroy(plb);
ListBox_NotifyOwner(plb, LBN_KILLFOCUS);
if (plb->iTypeSearch)
{
plb->iTypeSearch = 0;
KillTimer(hwnd, IDSYS_LBSEARCH);
}
if (plb->pszTypeSearch)
{
ControlFree(GetProcessHeap(), plb->pszTypeSearch);
plb->pszTypeSearch = NULL;
}
break;
case WM_MOUSEWHEEL:
{
int cDetants;
int cPage;
int cLines;
RECT rc;
int windowWidth;
int cPos;
UINT ucWheelScrollLines;
//
// Don't handle zoom and datazoom.
//
if (wParam & (MK_SHIFT | MK_CONTROL))
{
goto CallDWP;
}
lReturn = 1;
gcWheelDelta -= (short) HIWORD(wParam);
cDetants = gcWheelDelta / WHEEL_DELTA;
SystemParametersInfo(SPI_GETWHEELSCROLLLINES, 0, &ucWheelScrollLines, 0);
if ( cDetants != 0 &&
ucWheelScrollLines > 0 &&
(GET_STYLE(plb) & (WS_VSCROLL | WS_HSCROLL)))
{
gcWheelDelta = gcWheelDelta % WHEEL_DELTA;
if (GET_STYLE(plb) & WS_VSCROLL)
{
cPage = max(1, (plb->cItemFullMax - 1));
cLines = cDetants *
(int) min((UINT) cPage, ucWheelScrollLines);
cPos = max(0, min(plb->iTop + cLines, plb->cMac - 1));
if (cPos != plb->iTop)
{
ListBox_VScroll(plb, SB_THUMBPOSITION, cPos);
ListBox_VScroll(plb, SB_ENDSCROLL, 0);
}
}
else if (plb->fMultiColumn)
{
cPage = max(1, plb->numberOfColumns);
cLines = cDetants * (int) min((UINT) cPage, ucWheelScrollLines);
cPos = max(
0,
min((plb->iTop / plb->itemsPerColumn) + cLines,
plb->cMac - 1 - ((plb->cMac - 1) % plb->itemsPerColumn)));
if (cPos != plb->iTop)
{
ListBox_HSrollMultiColumn(plb, SB_THUMBPOSITION, cPos);
ListBox_HSrollMultiColumn(plb, SB_ENDSCROLL, 0);
}
}
else
{
GetClientRect(plb->hwnd, &rc);
windowWidth = rc.right;
cPage = max(plb->cxChar, (windowWidth / 3) * 2) /
plb->cxChar;
cLines = cDetants *
(int) min((UINT) cPage, ucWheelScrollLines);
cPos = max(
0,
min(plb->xOrigin + (cLines * plb->cxChar),
plb->maxWidth));
if (cPos != plb->xOrigin) {
ListBox_HScroll(plb, SB_THUMBPOSITION, cPos);
ListBox_HScroll(plb, SB_ENDSCROLL, 0);
}
}
}
break;
}
case WM_VSCROLL:
ListBox_VScroll(plb, LOWORD(wParam), HIWORD(wParam));
break;
case WM_HSCROLL:
ListBox_HScroll(plb, LOWORD(wParam), HIWORD(wParam));
break;
case WM_GETDLGCODE:
return DLGC_WANTARROWS | DLGC_WANTCHARS;
case WM_CREATE:
return ListBox_Create(plb, hwnd, (LPCREATESTRUCT)lParam);
case WM_SETREDRAW:
//
// If wParam is nonzero, the redraw flag is set
// If wParam is zero, the flag is cleared
//
ListBox_SetRedraw(plb, (wParam != 0));
break;
case WM_ENABLE:
ListBox_InvalidateRect(plb, NULL, !plb->OwnerDraw);
break;
case WM_SETFONT:
ListBox_SetFont(plb, (HANDLE)wParam, LOWORD(lParam));
break;
case WM_GETFONT:
return (LRESULT)plb->hFont;
case WM_DRAGSELECT:
case WM_DRAGLOOP:
case WM_DRAGMOVE:
case WM_DROPFILES:
return SendMessage(plb->hwndParent, uMsg, wParam, lParam);
case WM_QUERYDROPOBJECT:
case WM_DROPOBJECT:
//
// fix up control data, then pass message to parent
//
ListBox_DropObjectHandler(plb, (PDROPSTRUCT)lParam);
return SendMessage(plb->hwndParent, uMsg, wParam, lParam);
case LB_GETITEMRECT:
return ListBox_GetItemRectHandler(plb, (INT)wParam, (LPRECT)lParam);
case LB_GETITEMDATA:
//
// wParam = item index
//
return ListBox_GetItemDataHandler(plb, (INT)wParam);
case LB_SETITEMDATA:
//
// wParam is item index
//
return ListBox_SetItemDataHandler(plb, (INT)wParam, lParam);
case LB_ADDSTRINGUPPER:
wFlags = UPPERCASE | LBI_ADD;
goto CallInsertItem;
case LB_ADDSTRINGLOWER:
wFlags = LOWERCASE | LBI_ADD;
goto CallInsertItem;
case LB_ADDSTRING:
wFlags = LBI_ADD;
goto CallInsertItem;
case LB_INSERTSTRINGUPPER:
wFlags = UPPERCASE;
goto CallInsertItem;
case LB_INSERTSTRINGLOWER:
wFlags = LOWERCASE;
goto CallInsertItem;
case LB_INSERTSTRING:
wFlags = 0;
CallInsertItem:
// Validate the lParam. If the listbox does not have HASSTRINGS,
// the lParam is a data value. Otherwise, it is a string
// pointer, fail if NULL.
if ( !TESTFLAG(GET_STYLE(plb), LBS_HASSTRINGS) || lParam )
{
lReturn = (LRESULT)ListBox_InsertItem(plb, (LPWSTR) lParam, (int) wParam, wFlags);
if (!plb->fNoIntegralHeight)
{
ListBox_Size(plb, 0, 0, TRUE);
}
}
else
{
lReturn = LB_ERR;
}
break;
case LB_INITSTORAGE:
return ListBox_InitStorage(plb, FALSE, (INT)wParam, (INT)lParam);
case LB_DELETESTRING:
return ListBox_DeleteStringHandler(plb, (INT)wParam);
case LB_DIR:
//
// wParam - Dos attribute value.
// lParam - Points to a file specification string
//
lReturn = ListBox_DirHandler(plb, (INT)wParam, (LPWSTR)lParam);
break;
case LB_ADDFILE:
lReturn = ListBox_InsertFile(plb, (LPWSTR)lParam);
break;
case LB_SETSEL:
return ListBox_SetSelHandler(plb, (wParam != 0), (INT)lParam);
case LB_SETCURSEL:
//
// If window obscured, update so invert will work correctly
//
return ListBox_SetCurSelHandler(plb, (INT)wParam);
case LB_GETSEL:
if (wParam >= (UINT)plb->cMac)
{
return (LRESULT)LB_ERR;
}
return ListBox_IsSelected(plb, (INT)wParam, SELONLY);
case LB_GETCURSEL:
if (plb->wMultiple == SINGLESEL)
{
return plb->iSel;
}
return plb->iSelBase;
case LB_SELITEMRANGE:
if (plb->wMultiple == SINGLESEL)
{
//
// Can't select a range if only single selections are enabled
//
TraceMsg(TF_STANDARD, "Invalid index passed to LB_SELITEMRANGE");
return LB_ERR;
}
ListBox_SetRange(plb, LOWORD(lParam), HIWORD(lParam), (wParam != 0));
break;
case LB_SELITEMRANGEEX:
if (plb->wMultiple == SINGLESEL)
{
//
// Can't select a range if only single selections are enabled
//
TraceMsg(TF_STANDARD, "LB_SELITEMRANGEEX:Can't select a range if only single selections are enabled");
return LB_ERR;
}
else
{
BOOL fHighlight = ((DWORD)lParam > (DWORD)wParam);
if (fHighlight == FALSE)
{
ULONG_PTR temp = lParam;
lParam = wParam;
wParam = temp;
}
ListBox_SetRange(plb, (INT)wParam, (INT)lParam, fHighlight);
}
break;
case LB_GETTEXTLEN:
if (lParam != 0)
{
TraceMsg(TF_WARNING, "LB_GETTEXTLEN with lParam = %lx\n", lParam);
}
lReturn = ListBox_GetTextHandler(plb, TRUE, FALSE, (INT)wParam, NULL);
break;
case LB_GETTEXT:
lReturn = ListBox_GetTextHandler(plb, FALSE, FALSE, (INT)wParam, (LPWSTR)lParam);
break;
case LB_GETCOUNT:
return (LRESULT)plb->cMac;
case LB_SETCOUNT:
return ListBox_SetCount(plb, (INT)wParam);
case LB_SELECTSTRING:
case LB_FINDSTRING:
{
int iSel = Listbox_FindStringHandler(plb, (LPWSTR)lParam, (INT)wParam, PREFIX, TRUE);
if (uMsg == LB_FINDSTRING || iSel == LB_ERR)
{
lReturn = iSel;
}
else
{
lReturn = ListBox_SetCurSelHandler(plb, iSel);
}
break;
}
case LB_GETLOCALE:
return plb->dwLocaleId;
case LB_SETLOCALE:
{
DWORD dwRet;
//
// Validate locale
//
wParam = ConvertDefaultLocale((LCID)wParam);
if (!IsValidLocale((LCID)wParam, LCID_INSTALLED))
{
return LB_ERR;
}
dwRet = plb->dwLocaleId;
plb->dwLocaleId = (DWORD)wParam;
return dwRet;
}
case LB_GETLISTBOXINFO:
//
// wParam - not used
// lParam - not used
//
if (plb->fMultiColumn)
{
lReturn = (LRESULT)plb->itemsPerColumn;
}
else
{
lReturn = (LRESULT)plb->cMac;
}
break;
case CB_GETCOMBOBOXINFO:
//
// wParam - not used
// lParam - pointer to COMBOBOXINFO struct
//
if (plb->pcbox && plb->pcbox->hwnd && IsWindow(plb->pcbox->hwnd))
{
lReturn = SendMessage(plb->pcbox->hwnd, uMsg, wParam, lParam);
}
break;
case CB_SETMINVISIBLE:
if (!plb->fNoIntegralHeight)
{
ListBox_Size(plb, 0, 0, TRUE);
}
break;
case WM_KEYDOWN:
//
// IanJa: Use LOWORD() to get low 16-bits of wParam - this should
// work for Win16 & Win32. The value obtained is the virtual key
//
ListBox_KeyInput(plb, uMsg, LOWORD(wParam));
break;
case WM_CHAR:
ListBox_CharHandler(plb, LOWORD(wParam), FALSE);
break;
case LB_GETSELITEMS:
case LB_GETSELCOUNT:
//
// IanJa/Win32 should this be LPWORD now?
//
return ListBox_GetSelItemsHandler(plb, (uMsg == LB_GETSELCOUNT), (INT)wParam, (LPINT)lParam);
case LB_SETTABSTOPS:
//
// IanJa/Win32: Tabs given by array of INT for backwards compatability
//
return ListBox_SetTabStopsHandler(plb, (INT)wParam, (LPINT)lParam);
case LB_GETHORIZONTALEXTENT:
//
// Return the max width of the listbox used for horizontal scrolling
//
return plb->maxWidth;
case LB_SETHORIZONTALEXTENT:
//
// Set the max width of the listbox used for horizontal scrolling
//
if (plb->maxWidth != (INT)wParam)
{
plb->maxWidth = (INT)wParam;
//
// When horizontal extent is set, Show/hide the scroll bars.
// NOTE: ListBox_ShowHideScrollBars() takes care if Redraw is OFF.
// Fix for Bug #2477 -- 01/14/91 -- SANKAR --
//
//
// Try to show or hide scroll bars
//
ListBox_ShowHideScrollBars(plb);
if (plb->fHorzBar && plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw))
{
//
// origin to right
//
ListBox_HScroll(plb, SB_BOTTOM, 0);
}
}
break;
case LB_SETCOLUMNWIDTH:
//
// Set the width of a column in a multicolumn listbox
//
plb->cxColumn = (INT)wParam;
ListBox_CalcItemRowsAndColumns(plb);
if (IsLBoxVisible(plb))
{
InvalidateRect(hwnd, NULL, TRUE);
}
ListBox_ShowHideScrollBars(plb);
break;
case LB_SETANCHORINDEX:
if ((INT)wParam >= plb->cMac)
{
TraceMsg(TF_ERROR, "Invalid index passed to LB_SETANCHORINDEX");
return LB_ERR;
}
plb->iMouseDown = (INT)wParam;
plb->iLastMouseMove = (INT)wParam;
ListBox_InsureVisible(plb, (int) wParam, (BOOL)(lParam != 0));
break;
case LB_GETANCHORINDEX:
return plb->iMouseDown;
case LB_SETCARETINDEX:
if ( (plb->iSel == -1) || ((plb->wMultiple != SINGLESEL) &&
(plb->cMac > (INT)wParam)))
{
//
// Set's the iSelBase to the wParam
// if lParam, then don't scroll if partially visible
// else scroll into view if not fully visible
//
ListBox_InsureVisible(plb, (INT)wParam, (BOOL)LOWORD(lParam));
ListBox_SetISelBase(plb, (INT)wParam);
break;
}
else
{
if ((INT)wParam >= plb->cMac)
{
TraceMsg(TF_ERROR, "Invalid index passed to LB_SETCARETINDEX");
}
return LB_ERR;
}
break;
case LB_GETCARETINDEX:
return plb->iSelBase;
case LB_SETITEMHEIGHT:
case LB_GETITEMHEIGHT:
return ListBox_GetSetItemHeightHandler(plb, uMsg, (INT)wParam, LOWORD(lParam));
case LB_FINDSTRINGEXACT:
return Listbox_FindStringHandler(plb, (LPWSTR)lParam, (INT)wParam, EQ, TRUE);
case LB_ITEMFROMPOINT:
{
POINT pt;
BOOL bOutside;
DWORD dwItem;
POINTSTOPOINT(pt, lParam);
bOutside = ListBox_ISelFromPt(plb, pt, &dwItem);
ASSERT(bOutside == 1 || bOutside == 0);
return (LRESULT)MAKELONG(dwItem, bOutside);
}
case LBCB_CARETON:
//
// Internal message for combo box support
//
CaretCreate(plb);
//
// Set up the caret in the proper location for drop downs.
//
plb->iSelBase = plb->iSel;
ListBox_SetCaret(plb, TRUE);
if (IsWindowVisible(hwnd) || (GetFocus() == hwnd))
{
ListBox_Event(plb, EVENT_OBJECT_FOCUS, plb->iSelBase);
}
return plb->iSel;
case LBCB_CARETOFF:
//
// Internal message for combo box support
//
ListBox_SetCaret(plb, FALSE);
ListBox_CaretDestroy(plb);
break;
case WM_NCCREATE:
//
// Allocate the listbox instance stucture
//
plb = (PLBIV)UserLocalAlloc(HEAP_ZERO_MEMORY, sizeof(LBIV));
if(plb)
{
ULONG ulStyle;
//
// Success... store the instance pointer.
//
TraceMsg(TF_STANDARD, "LISTBOX: Setting listbox instance pointer.");
ListBox_SetPtr(hwnd, plb);
plb->hwnd = hwnd;
plb->pww = (PWW)GetWindowLongPtr(hwnd, GWLP_WOWWORDS);
ulStyle = GET_STYLE(plb);
if ( (ulStyle & LBS_MULTICOLUMN) &&
(ulStyle & WS_VSCROLL))
{
DWORD dwMask = WS_VSCROLL;
DWORD dwFlags = 0;
if (!TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT))
{
dwMask |= WS_HSCROLL;
dwFlags = WS_HSCROLL;
}
AlterWindowStyle(hwnd, dwMask, dwFlags);
}
goto CallDWP;
}
else
{
//
// Failed... return FALSE.
//
// From a WM_NCCREATE msg, this will cause the
// CreateWindow call to fail.
//
TraceMsg(TF_STANDARD, "LISTBOX: Unable to allocate listbox instance structure.");
lReturn = FALSE;
}
break;
case WM_GETOBJECT:
if(lParam == OBJID_QUERYCLASSNAMEIDX)
{
lReturn = MSAA_CLASSNAMEIDX_LISTBOX;
}
else
{
lReturn = FALSE;
}
break;
case WM_THEMECHANGED:
if ( plb->hTheme )
{
CloseThemeData(plb->hTheme);
}
plb->hTheme = OpenThemeData(plb->hwnd, L"Listbox");
InvalidateRect(plb->hwnd, NULL, TRUE);
lReturn = TRUE;
break;
default:
CallDWP:
lReturn = DefWindowProcW(hwnd, uMsg, wParam, lParam);
}
return lReturn;
}
//---------------------------------------------------------------------------//
//
// Function: GetWindowBorders
//
// Synopsis: Calculates # of borders around window
//
// Algorithm: Calculate # of window borders and # of client borders
//
int GetWindowBorders(LONG lStyle, DWORD dwExStyle, BOOL fWindow, BOOL fClient)
{
int cBorders = 0;
DWORD dwTemp;
if (fWindow)
{
//
// Is there a 3D border around the window?
//
if (dwExStyle & WS_EX_WINDOWEDGE)
{
cBorders += 2;
}
else if (dwExStyle & WS_EX_STATICEDGE)
{
++cBorders;
}
//
// Is there a single flat border around the window? This is true for
// WS_BORDER, WS_DLGFRAME, and WS_EX_DLGMODALFRAME windows.
//
if ( (lStyle & WS_CAPTION) || (dwExStyle & WS_EX_DLGMODALFRAME) )
{
++cBorders;
}
//
// Is there a sizing flat border around the window?
//
if (lStyle & WS_SIZEBOX)
{
if(SystemParametersInfo(SPI_GETBORDER, 0, &dwTemp, 0))
{
cBorders += dwTemp;
}
else
{
ASSERT(0);
}
}
}
if (fClient)
{
//
// Is there a 3D border around the client?
//
if (dwExStyle & WS_EX_CLIENTEDGE)
{
cBorders += 2;
}
}
return cBorders;
}
//---------------------------------------------------------------------------//
//
// GetLpszItem
//
// Returns a far pointer to the string belonging to item sItem
// ONLY for Listboxes maintaining their own strings (pLBIV->fHasStrings == TRUE)
//
LPWSTR GetLpszItem(PLBIV pLBIV, INT sItem)
{
LONG offsz;
lpLBItem plbi;
if (sItem < 0 || sItem >= pLBIV->cMac)
{
TraceMsg(TF_ERROR, "Invalid parameter \"sItem\" (%ld) to GetLpszItem", sItem);
return NULL;
}
//
// get pointer to item index array
// NOTE: NOT OWNERDRAW
//
plbi = (lpLBItem)(pLBIV->rgpch);
offsz = plbi[sItem].offsz;
return (LPWSTR)((PBYTE)(pLBIV->hStrings) + offsz);
}
//---------------------------------------------------------------------------//
//
// Multi column Listbox functions
//
//---------------------------------------------------------------------------//
//
// ListBox_CalcItemRowsAndColumns
//
// Calculates the number of columns (including partially visible)
// in the listbox and calculates the number of items per column
//
void ListBox_CalcItemRowsAndColumns(PLBIV plb)
{
RECT rc;
GetClientRect(plb->hwnd, &rc);
//
// B#4155
// We need to check if plb->cyChar has been initialized. This is because
// we remove WS_BORDER from old listboxes and add on WS_EX_CLIENTEDGE.
// Since listboxes are always inflated by CXBORDER and CYBORDER, a
// listbox that was created empty always ends up 2 x 2. Since this isn't
// big enough to fit the entire client border, we don't mark it as
// present. Thus the client isn't empty in VER40, although it was in
// VER31 and before. It is possible to get to this spot without
// plb->cyChar having been initialized yet if the listbox is
// multicolumn && ownerdraw variable.
//
if (rc.bottom && rc.right && plb->cyChar)
{
//
// Only make these calculations if the width & height are positive
//
plb->itemsPerColumn = (INT)max(rc.bottom / plb->cyChar, 1);
plb->numberOfColumns = (INT)max(rc.right / plb->cxColumn, 1);
plb->cItemFullMax = plb->itemsPerColumn * plb->numberOfColumns;
//
// Adjust iTop so it's at the top of a column
//
ListBox_NewITop(plb, plb->iTop);
}
}
//---------------------------------------------------------------------------//
//
// ListBox_HSrollMultiColumn
//
// Supports horizontal scrolling of multicolumn listboxes
//
void ListBox_HSrollMultiColumn(PLBIV plb, INT cmd, INT xAmt)
{
INT iTop = plb->iTop;
if (!plb->cMac)
{
return;
}
switch (cmd)
{
case SB_LINEUP:
if (plb->fRightAlign)
{
goto ReallyLineDown;
}
ReallyLineUp:
iTop -= plb->itemsPerColumn;
break;
case SB_LINEDOWN:
if (plb->fRightAlign)
{
goto ReallyLineUp;
}
ReallyLineDown:
iTop += plb->itemsPerColumn;
break;
case SB_PAGEUP:
if (plb->fRightAlign)
{
goto ReallyPageDown;
}
ReallyPageUp:
iTop -= plb->itemsPerColumn * plb->numberOfColumns;
break;
case SB_PAGEDOWN:
if (plb->fRightAlign)
{
goto ReallyPageUp;
}
ReallyPageDown:
iTop += plb->itemsPerColumn * plb->numberOfColumns;
break;
case SB_THUMBTRACK:
case SB_THUMBPOSITION:
if (plb->fRightAlign)
{
int iCols = plb->cMac ? ((plb->cMac-1) / plb->itemsPerColumn) + 1 : 0;
xAmt = iCols - (xAmt + plb->numberOfColumns);
if (xAmt < 0)
{
xAmt=0;
}
}
iTop = xAmt * plb->itemsPerColumn;
break;
case SB_TOP:
if (plb->fRightAlign)
{
goto ReallyBottom;
}
ReallyTop:
iTop = 0;
break;
case SB_BOTTOM:
if (plb->fRightAlign)
{
goto ReallyTop;
}
ReallyBottom:
iTop = plb->cMac - 1 - ((plb->cMac - 1) % plb->itemsPerColumn);
break;
case SB_ENDSCROLL:
plb->fSmoothScroll = TRUE;
ListBox_ShowHideScrollBars(plb);
break;
}
ListBox_NewITop(plb, iTop);
}
//---------------------------------------------------------------------------//
//
// ListBox variable height owner draw functions
//
//---------------------------------------------------------------------------//
//
// ListBox_GetVarHeightItemHeight
//
// Returns the height of the given item number. Assumes variable
// height owner draw.
//
INT ListBox_GetVarHeightItemHeight(PLBIV plb, INT itemNumber)
{
BYTE itemHeight;
UINT offsetHeight;
if (plb->cMac)
{
if (plb->fHasStrings)
{
offsetHeight = plb->cMac * sizeof(LBItem);
}
else
{
offsetHeight = plb->cMac * sizeof(LBODItem);
}
if (plb->wMultiple)
{
offsetHeight += plb->cMac;
}
offsetHeight += itemNumber;
itemHeight = *(plb->rgpch+(UINT)offsetHeight);
return (INT)itemHeight;
}
//
// Default, we return the height of the system font. This is so we can draw
// the focus rect even though there are no items in the listbox.
//
return SYSFONT_CYCHAR;
}
//---------------------------------------------------------------------------//
//
// ListBox_SetVarHeightItemHeight
//
// Sets the height of the given item number. Assumes variable height
// owner draw, a valid item number and valid height.
//
void ListBox_SetVarHeightItemHeight(PLBIV plb, INT itemNumber, INT itemHeight)
{
int offsetHeight;
if (plb->fHasStrings)
offsetHeight = plb->cMac * sizeof(LBItem);
else
offsetHeight = plb->cMac * sizeof(LBODItem);
if (plb->wMultiple)
offsetHeight += plb->cMac;
offsetHeight += itemNumber;
*(plb->rgpch + (UINT)offsetHeight) = (BYTE)itemHeight;
}
//---------------------------------------------------------------------------//
//
// ListBox_VisibleItemsVarOwnerDraw
//
// Returns the number of items which can fit in a variable height OWNERDRAW
// list box. If fDirection, then we return the number of items which
// fit starting at sTop and going forward (for page down), otherwise, we are
// going backwards (for page up). (Assumes var height ownerdraw) If fPartial,
// then include the partially visible item at the bottom of the listbox.
//
INT ListBox_VisibleItemsVarOwnerDraw(PLBIV plb, BOOL fPartial)
{
RECT rect;
INT sItem;
INT clientbottom;
GetClientRect(plb->hwnd, (LPRECT)&rect);
clientbottom = rect.bottom;
//
// Find the number of var height ownerdraw items which are visible starting
// from plb->iTop.
//
for (sItem = plb->iTop; sItem < plb->cMac; sItem++)
{
//
// Find out if the item is visible or not
//
if (!ListBox_GetItemRectHandler(plb, sItem, (LPRECT)&rect))
{
//
// This is the first item which is completely invisible, so return
// how many items are visible.
//
return (sItem - plb->iTop);
}
if (!fPartial && rect.bottom > clientbottom)
{
//
// If we only want fully visible items, then if this item is
// visible, we check if the bottom of the item is below the client
// rect, so we return how many are fully visible.
//
return (sItem - plb->iTop - 1);
}
}
//
// All the items are visible
//
return (plb->cMac - plb->iTop);
}
//---------------------------------------------------------------------------//
//
// ListBox_Page
//
// For variable height ownerdraw listboxes, calaculates the new iTop we must
// move to when paging (page up/down) through variable height listboxes.
//
INT ListBox_Page(PLBIV plb, INT startItem, BOOL fPageForwardDirection)
{
INT i;
INT height;
RECT rc;
if (plb->cMac == 1)
{
return 0;
}
GetClientRect(plb->hwnd, &rc);
height = rc.bottom;
i = startItem;
if (fPageForwardDirection)
{
while ((height >= 0) && (i < plb->cMac))
{
height -= ListBox_GetVarHeightItemHeight(plb, i++);
}
return (height >= 0) ? (plb->cMac - 1) : max(i - 2, startItem + 1);
}
else
{
while ((height >= 0) && (i >= 0))
{
height -= ListBox_GetVarHeightItemHeight(plb, i--);
}
return (height >= 0) ? 0 : min(i + 2, startItem - 1);
}
}
//---------------------------------------------------------------------------//
//
// ListBox_CalcVarITopScrollAmt
//
// Changing the top most item in the listbox from iTopOld to iTopNew we
// want to calculate the number of pixels to scroll so that we minimize the
// number of items we will redraw.
//
INT ListBox_CalcVarITopScrollAmt(PLBIV plb, INT iTopOld, INT iTopNew)
{
RECT rc;
RECT rcClient;
GetClientRect(plb->hwnd, (LPRECT)&rcClient);
//
// Just optimize redrawing when move +/- 1 item. We will redraw all items
// if moving more than 1 item ahead or back. This is good enough for now.
//
if (iTopOld + 1 == iTopNew)
{
//
// We are scrolling the current iTop up off the top off the listbox so
// return a negative number.
//
ListBox_GetItemRectHandler(plb, iTopOld, (LPRECT)&rc);
return (rcClient.top - rc.bottom);
}
if (iTopOld - 1 == iTopNew)
{
//
// We are scrolling the current iTop down and the previous item is
// becoming the new iTop so return a positive number.
//
ListBox_GetItemRectHandler(plb, iTopNew, (LPRECT)&rc);
return -rc.top;
}
return rcClient.bottom - rcClient.top;
}
//---------------------------------------------------------------------------//
//
// (supposedly) Rarely called Listbox functions
//
//---------------------------------------------------------------------------//
void ListBox_SetCItemFullMax(PLBIV plb)
{
if (plb->OwnerDraw != OWNERDRAWVAR)
{
plb->cItemFullMax = ListBox_CItemInWindow(plb, FALSE);
}
else if (plb->cMac < 2)
{
plb->cItemFullMax = 1;
}
else
{
int height;
RECT rect;
int i;
int j = 0;
GetClientRect(plb->hwnd, &rect);
height = rect.bottom;
plb->cItemFullMax = 0;
for (i = plb->cMac - 1; i >= 0; i--, j++)
{
height -= ListBox_GetVarHeightItemHeight(plb, i);
if (height < 0)
{
plb->cItemFullMax = j;
break;
}
}
if (!plb->cItemFullMax)
{
plb->cItemFullMax = j;
}
}
}
//---------------------------------------------------------------------------//
LONG ListBox_Create(PLBIV plb, HWND hwnd, LPCREATESTRUCT lpcs)
{
UINT style;
DWORD ExStyle;
MEASUREITEMSTRUCT measureItemStruct;
HDC hdc;
HWND hwndParent;
SIZE size;
//
// Once we make it here, nobody can change the ownerdraw style bits
// by calling SetWindowLong. The window style must match the flags in plb
//
plb->fInitialized = TRUE;
style = lpcs->style;
ExStyle = lpcs->dwExStyle;
hwndParent = lpcs->hwndParent;
plb->hwndParent = hwndParent;
plb->hTheme = OpenThemeData(plb->hwnd, L"Listbox");
//
// Break out the style bits
//
plb->fRedraw = ((style & LBS_NOREDRAW) == 0);
plb->fDeferUpdate = FALSE;
plb->fNotify = (UINT)((style & LBS_NOTIFY) != 0);
plb->fVertBar = ((style & WS_VSCROLL) != 0);
plb->fHorzBar = ((style & WS_HSCROLL) != 0);
if (!TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT))
{
//
// for 3.x apps, if either scroll bar was specified, the app got BOTH
//
if (plb->fVertBar || plb->fHorzBar)
{
plb->fVertBar = plb->fHorzBar = TRUE;
}
}
plb->fRtoLReading = (ExStyle & WS_EX_RTLREADING)!= 0;
plb->fRightAlign = (ExStyle & WS_EX_RIGHT) != 0;
plb->fDisableNoScroll = ((style & LBS_DISABLENOSCROLL) != 0);
plb->fSmoothScroll = TRUE;
//
// LBS_NOSEL gets priority over any other selection style. Next highest
// priority goes to LBS_EXTENDEDSEL. Then LBS_MULTIPLESEL.
//
if (TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT) && (style & LBS_NOSEL))
{
plb->wMultiple = SINGLESEL;
plb->fNoSel = TRUE;
}
else if (style & LBS_EXTENDEDSEL)
{
plb->wMultiple = EXTENDEDSEL;
}
else
{
plb->wMultiple = (UINT)((style & LBS_MULTIPLESEL) ? MULTIPLESEL : SINGLESEL);
}
plb->fNoIntegralHeight = ((style & LBS_NOINTEGRALHEIGHT) != 0);
plb->fWantKeyboardInput = ((style & LBS_WANTKEYBOARDINPUT) != 0);
plb->fUseTabStops = ((style & LBS_USETABSTOPS) != 0);
if (plb->fUseTabStops)
{
//
// Set tab stops every <default> dialog units.
//
ListBox_SetTabStopsHandler(plb, 0, NULL);
}
plb->fMultiColumn = ((style & LBS_MULTICOLUMN) != 0);
plb->fHasStrings = TRUE;
plb->iLastSelection = -1;
//
// Anchor point for multi selection
//
plb->iMouseDown = -1;
plb->iLastMouseMove = -1;
//
// Get ownerdraw style bits
//
if ((style & LBS_OWNERDRAWFIXED))
{
plb->OwnerDraw = OWNERDRAWFIXED;
}
else if ((style & LBS_OWNERDRAWVARIABLE) && !plb->fMultiColumn)
{
plb->OwnerDraw = OWNERDRAWVAR;
//
// Integral height makes no sense with var height owner draw
//
plb->fNoIntegralHeight = TRUE;
}
if (plb->OwnerDraw && !(style & LBS_HASSTRINGS))
{
//
// If owner draw, do they want the listbox to maintain strings?
//
plb->fHasStrings = FALSE;
}
//
// If user specifies sort and not hasstrings, then we will send
// WM_COMPAREITEM messages to the parent.
//
plb->fSort = ((style & LBS_SORT) != 0);
//
// "No data" lazy-eval listbox mandates certain other style settings
//
plb->fHasData = TRUE;
if (style & LBS_NODATA)
{
if (plb->OwnerDraw != OWNERDRAWFIXED || plb->fSort || plb->fHasStrings)
{
TraceMsg(TF_STANDARD, "NODATA listbox must be OWNERDRAWFIXED, w/o SORT or HASSTRINGS");
}
else
{
plb->fHasData = FALSE;
}
}
plb->dwLocaleId = GetThreadLocale();
//
// Check if this is part of a combo box
//
if ((style & LBS_COMBOBOX) != 0)
{
//
// Get the pcbox structure contained in the parent window's extra data
// pointer. Check cbwndExtra to ensure compatibility with SQL windows.
//
plb->pcbox = ComboBox_GetPtr(hwndParent);
}
plb->iSel = -1;
plb->hdc = NULL;
//
// Set the keyboard state so that when the user keyboard clicks he selects
// an item.
//
plb->fNewItemState = TRUE;
ListBox_InitHStrings(plb);
if (plb->fHasStrings && plb->hStrings == NULL)
{
return -1L;
}
hdc = GetDC(hwnd);
GetCharDimensions(hdc, &size);
plb->cxChar = size.cx;
plb->cyChar = size.cy;
ReleaseDC(hwnd, hdc);
if ((plb->cxChar == 0) || (plb->cyChar == 0))
{
TraceMsg(TF_STANDARD, "LISTBOX: GetCharDimensions failed.");
plb->cxChar = SYSFONT_CXCHAR;
plb->cyChar = SYSFONT_CYCHAR;
}
if (plb->OwnerDraw == OWNERDRAWFIXED)
{
//
// Query for item height only if we are fixed height owner draw. Note
// that we don't care about an item's width for listboxes.
//
measureItemStruct.CtlType = ODT_LISTBOX;
measureItemStruct.CtlID = GetDlgCtrlID(hwnd);
//
// System font height is default height
//
measureItemStruct.itemHeight = plb->cyChar;
measureItemStruct.itemWidth = 0;
measureItemStruct.itemData = 0;
//
// IanJa: #ifndef WIN16 (32-bit Windows), plb->id gets extended
// to LONG wParam automatically by the compiler
//
SendMessage(plb->hwndParent, WM_MEASUREITEM,
measureItemStruct.CtlID,
(LPARAM)&measureItemStruct);
//
// Use default height if given 0. This prevents any possible future
// div-by-zero errors.
//
if (measureItemStruct.itemHeight)
{
plb->cyChar = measureItemStruct.itemHeight;
}
if (plb->fMultiColumn)
{
//
// Get default column width from measure items struct if we are a
// multicolumn listbox.
//
plb->cxColumn = measureItemStruct.itemWidth;
}
}
else if (plb->OwnerDraw == OWNERDRAWVAR)
{
plb->cyChar = 0;
}
if (plb->fMultiColumn)
{
//
// Set these default values till we get the WM_SIZE message and we
// calculate them properly. This is because some people create a
// 0 width/height listbox and size it later. We don't want to have
// problems with invalid values in these fields
//
if (plb->cxColumn <= 0)
{
plb->cxColumn = 15 * plb->cxChar;
}
plb->numberOfColumns = plb->itemsPerColumn = 1;
}
ListBox_SetCItemFullMax(plb);
//
// Don't do this for 4.0 apps. It'll make everyone's lives easier and
// fix the anomaly that a combo & list created the same width end up
// different when all is done.
// B#1520
//
if (!TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT))
{
plb->fIgnoreSizeMsg = TRUE;
MoveWindow(hwnd,
lpcs->x - SYSMET(CXBORDER),
lpcs->y - SYSMET(CYBORDER),
lpcs->cx + SYSMET(CXEDGE),
lpcs->cy + SYSMET(CYEDGE),
FALSE);
plb->fIgnoreSizeMsg = FALSE;
}
if (!plb->fNoIntegralHeight)
{
//
// Send a message to ourselves to resize the listbox to an integral
// height. We need to do it this way because at create time we are all
// mucked up with window rects etc...
// IanJa: #ifndef WIN16 (32-bit Windows), wParam 0 gets extended
// to wParam 0L automatically by the compiler.
//
PostMessage(hwnd, WM_SIZE, 0, 0L);
}
return 1L;
}
//---------------------------------------------------------------------------//
//
// ListBox_DoDeleteItems
//
// Send DELETEITEM message for all the items in the ownerdraw listbox.
//
void ListBox_DoDeleteItems(PLBIV plb)
{
INT sItem;
//
// Send WM_DELETEITEM message for ownerdraw listboxes which are
// being deleted. (NODATA listboxes don't send such, though.)
//
if (plb->OwnerDraw && plb->cMac && plb->fHasData)
{
for (sItem = plb->cMac - 1; sItem >= 0; sItem--)
{
ListBox_DeleteItem(plb, sItem);
}
}
}
//---------------------------------------------------------------------------//
VOID ListBox_Destroy(PLBIV plv, HWND hwnd)
{
if (plv != NULL)
{
//
// If ownerdraw, send deleteitem messages to parent
//
ListBox_DoDeleteItems(plv);
if (plv->rgpch != NULL)
{
ControlFree(GetProcessHeap(), plv->rgpch);
plv->rgpch = NULL;
}
if (plv->hStrings != NULL)
{
ControlFree(GetProcessHeap(), plv->hStrings);
plv->hStrings = NULL;
}
if (plv->iTabPixelPositions != NULL)
{
ControlFree(GetProcessHeap(), (HANDLE)plv->iTabPixelPositions);
plv->iTabPixelPositions = NULL;
}
if (plv->pszTypeSearch)
{
ControlFree(GetProcessHeap(), plv->pszTypeSearch);
}
if (plv->hTheme != NULL)
{
CloseThemeData(plv->hTheme);
}
//
// If we're part of a combo box, let it know we're gone
//
if (plv->hwndParent && plv->pcbox)
{
ComboBox_WndProc(plv->hwndParent, WM_PARENTNOTIFY,
MAKEWPARAM(WM_DESTROY, GetWindowID(hwnd)), (LPARAM)hwnd);
}
UserLocalFree(plv);
}
TraceMsg(TF_STANDARD, "LISTBOX: Clearing listbox instance pointer.");
ListBox_SetPtr(hwnd, NULL);
}
//---------------------------------------------------------------------------//
void ListBox_SetFont(PLBIV plb, HANDLE hFont, BOOL fRedraw)
{
HDC hdc;
HANDLE hOldFont = NULL;
SIZE size;
plb->hFont = hFont;
hdc = GetDC(plb->hwnd);
if (hFont)
{
hOldFont = SelectObject(hdc, hFont);
if (!hOldFont)
{
plb->hFont = NULL;
}
}
GetCharDimensions(hdc, &size);
if ((size.cx == 0) || (size.cy == 0))
{
TraceMsg(TF_STANDARD, "LISTBOX: GetCharDimensions failed.");
size.cx = SYSFONT_CXCHAR;
size.cy = SYSFONT_CYCHAR;
}
plb->cxChar = size.cx;
if (!plb->OwnerDraw && (plb->cyChar != size.cy))
{
//
// We don't want to mess up the cyChar height for owner draw listboxes
// so don't do this.
//
plb->cyChar = size.cy;
//
// Only resize the listbox for 4.0 dudes, or combo dropdowns.
// Macromedia Director 4.0 GP-faults otherwise.
//
if (!plb->fNoIntegralHeight &&
(plb->pcbox || TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT)))
{
RECT rcClient;
GetClientRect(plb->hwnd, &rcClient);
ListBox_Size(plb, rcClient.right - rcClient.left, rcClient.bottom - rcClient.top, FALSE);
}
}
if (hOldFont)
{
SelectObject(hdc, hOldFont);
}
ReleaseDC(plb->hwnd, hdc);
if (plb->fMultiColumn)
{
ListBox_CalcItemRowsAndColumns(plb);
}
ListBox_SetCItemFullMax(plb);
if (fRedraw)
{
ListBox_CheckRedraw(plb, FALSE, 0);
}
}
//---------------------------------------------------------------------------//
void ListBox_Size(PLBIV plb, INT cx, INT cy, BOOL fSizeMinVisible)
{
RECT rc, rcWindow;
int iTopOld;
int cBorder;
BOOL fSizedSave;
if (!plb->fNoIntegralHeight)
{
int cBdrs = GetWindowBorders(GET_STYLE(plb), GET_EXSTYLE(plb), TRUE, TRUE);
GetWindowRect(plb->hwnd, &rcWindow);
cBorder = SYSMET(CYBORDER);
CopyRect(&rc, &rcWindow);
InflateRect(&rc, 0, -cBdrs * cBorder);
//
// Size the listbox to fit an integral # of items in its client
//
if ((plb->cyChar && ((rc.bottom - rc.top) % plb->cyChar)) || fSizeMinVisible)
{
int iItems = (rc.bottom - rc.top);
//
// B#2285 - If its a 3.1 app its SetWindowPos needs
// to be window based dimensions not Client !
// this crunches Money into using a scroll bar
//
if (!TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT))
{
//
// so add it back in
//
iItems += (cBdrs * SYSMET(CYEDGE));
}
iItems /= plb->cyChar;
//
// If we're in a dropdown list, size the listbox to accomodate
// a minimum number of items before needing to show scrolls.
//
if (plb->pcbox &&
(plb->pcbox->CBoxStyle & SDROPPABLE) &&
(((iItems < plb->pcbox->iMinVisible) &&
(iItems < plb->cMac)) || fSizeMinVisible))
{
iItems = min(plb->pcbox->iMinVisible, plb->cMac);
}
SetWindowPos(plb->hwnd, HWND_TOP, 0, 0, rc.right - rc.left,
iItems * plb->cyChar + (SYSMET(CYEDGE) * cBdrs),
SWP_NOACTIVATE | SWP_NOMOVE | SWP_NOZORDER);
//
// Changing the size causes us to recurse. Upon return
// the state is where it should be and nothing further
// needs to be done.
//
return;
}
}
if (plb->fMultiColumn)
{
//
// Compute the number of DISPLAYABLE rows and columns in the listbox
//
ListBox_CalcItemRowsAndColumns(plb);
}
else
{
//
// Adjust the current horizontal position to eliminate as much
// empty space as possible from the right side of the items.
//
GetClientRect(plb->hwnd, &rc);
if ((plb->maxWidth - plb->xOrigin) < (rc.right - rc.left))
{
plb->xOrigin = max(0, plb->maxWidth - (rc.right - rc.left));
}
}
ListBox_SetCItemFullMax(plb);
//
// Adjust the top item in the listbox to eliminate as much empty space
// after the last item as possible
// (fix for bugs #8490 & #3836)
//
iTopOld = plb->iTop;
fSizedSave = plb->fSized;
plb->fSized = FALSE;
ListBox_NewITop(plb, plb->iTop);
//
// If changing the top item index caused a resize, there is no
// more work to be done here.
//
if (plb->fSized)
{
return;
}
plb->fSized = fSizedSave;
if (IsLBoxVisible(plb))
{
//
// This code no longer blows because it's fixed right!!! We could
// optimize the fMultiColumn case with some more code to figure out
// if we really need to invalidate the whole thing but note that some
// 3.0 apps depend on this extra invalidation (AMIPRO 2.0, bug 14620)
//
// For 3.1 apps, we blow off the invalidaterect in the case where
// cx and cy are 0 because this happens during the processing of
// the posted WM_SIZE message when we are created which would otherwise
// cause us to flash.
//
if ((plb->fMultiColumn && !(cx == 0 && cy == 0)) || plb->iTop != iTopOld)
{
InvalidateRect(plb->hwnd, NULL, TRUE);
}
else if (plb->iSelBase >= 0)
{
//
// Invalidate the item with the caret so that if the listbox
// grows horizontally, we redraw it properly.
//
ListBox_GetItemRectHandler(plb, plb->iSelBase, &rc);
InvalidateRect(plb->hwnd, &rc, FALSE);
}
}
else if (!plb->fRedraw)
{
plb->fDeferUpdate = TRUE;
}
//
// Send "fake" scroll bar messages to update the scroll positions since we
// changed size.
//
if (TESTFLAG(GET_STYLE(plb), WS_VSCROLL))
{
ListBox_VScroll(plb, SB_ENDSCROLL, 0);
}
//
// We count on this to call ListBox_ShowHideScrollBars except when plb->cMac == 0!
//
ListBox_HScroll(plb, SB_ENDSCROLL, 0);
//
// Show/hide scroll bars depending on how much stuff is visible...
//
// Note: Now we only call this guy when cMac == 0, because it is
// called inside the ListBox_HScroll with SB_ENDSCROLL otherwise.
//
if (plb->cMac == 0)
{
ListBox_ShowHideScrollBars(plb);
}
}
//---------------------------------------------------------------------------//
//
// ListBox_SetTabStopsHandler
//
// Sets the tab stops for this listbox. Returns TRUE if successful else FALSE.
//
BOOL ListBox_SetTabStopsHandler(PLBIV plb, INT count, LPINT lptabstops)
{
PINT ptabs;
if (!plb->fUseTabStops)
{
TraceMsg(TF_STANDARD, "Calling SetTabStops without the LBS_TABSTOPS style set");
return FALSE;
}
if (count)
{
//
// Allocate memory for the tab stops. The first byte in the
// plb->iTabPixelPositions array will contain a count of the number
// of tab stop positions we have.
//
ptabs = (LPINT)ControlAlloc(GetProcessHeap(), (count + 1) * sizeof(int));
if (ptabs == NULL)
{
return FALSE;
}
if (plb->iTabPixelPositions != NULL)
{
ControlFree(GetProcessHeap(), plb->iTabPixelPositions);
}
plb->iTabPixelPositions = ptabs;
//
// Set the count of tab stops
//
*ptabs++ = count;
for (; count > 0; count--)
{
//
// Convert the dialog unit tabstops into pixel position tab stops.
//
*ptabs++ = MultDiv(*lptabstops, plb->cxChar, 4);
lptabstops++;
}
}
else
{
//
// Set default 8 system font ave char width tabs. So free the memory
// associated with the tab stop list.
//
if (plb->iTabPixelPositions != NULL)
{
ControlFree(GetProcessHeap(), (HANDLE)plb->iTabPixelPositions);
plb->iTabPixelPositions = NULL;
}
}
return TRUE;
}
//---------------------------------------------------------------------------//
void ListBox_InitHStrings(PLBIV plb)
{
if (plb->fHasStrings)
{
plb->ichAlloc = 0;
plb->cchStrings = 0;
plb->hStrings = ControlAlloc(GetProcessHeap(), 0);
}
}
//---------------------------------------------------------------------------//
//
// ListBox_DropObjectHandler
//
// Handles a WM_DROPITEM message on this listbox
//
void ListBox_DropObjectHandler(PLBIV plb, PDROPSTRUCT pds)
{
LONG mouseSel;
if (ListBox_ISelFromPt(plb, pds->ptDrop, &mouseSel))
{
//
// User dropped in empty space at bottom of listbox
//
pds->dwControlData = (DWORD)-1L;
}
else
{
pds->dwControlData = mouseSel;
}
}
//---------------------------------------------------------------------------//
//
// ListBox_GetSetItemHeightHandler()
//
// Sets/Gets the height associated with each item. For non ownerdraw
// and fixed height ownerdraw, the item number is ignored.
//
int ListBox_GetSetItemHeightHandler(PLBIV plb, UINT message, int item, UINT height)
{
if (message == LB_GETITEMHEIGHT)
{
//
// All items are same height for non ownerdraw and for fixed height
// ownerdraw.
//
if (plb->OwnerDraw != OWNERDRAWVAR)
{
return plb->cyChar;
}
if (plb->cMac && item >= plb->cMac)
{
TraceMsg(TF_STANDARD,
"Invalid parameter \"item\" (%ld) to ListBox_GetSetItemHeightHandler", item);
return LB_ERR;
}
return (int)ListBox_GetVarHeightItemHeight(plb, (INT)item);
}
if (!height || height > 255)
{
TraceMsg(TF_STANDARD,
"Invalid parameter \"height\" (%ld) to ListBox_GetSetItemHeightHandler", height);
return LB_ERR;
}
if (plb->OwnerDraw != OWNERDRAWVAR)
{
plb->cyChar = height;
}
else
{
if (item < 0 || item >= plb->cMac)
{
TraceMsg(TF_STANDARD,
"Invalid parameter \"item\" (%ld) to ListBox_GetSetItemHeightHandler", item);
return LB_ERR;
}
ListBox_SetVarHeightItemHeight(plb, (INT)item, (INT)height);
}
if (plb->fMultiColumn)
{
ListBox_CalcItemRowsAndColumns(plb);
}
ListBox_SetCItemFullMax(plb);
return 0;
}
//---------------------------------------------------------------------------//
//
// ListBox_Event()
//
// This is for item focus & selection events in listboxes.
//
void ListBox_Event(PLBIV plb, UINT uEvent, int iItem)
{
switch (uEvent)
{
case EVENT_OBJECT_SELECTIONREMOVE:
if (plb->wMultiple != SINGLESEL)
{
break;
}
iItem = -1;
//
// FALL THRU
//
case EVENT_OBJECT_SELECTIONADD:
if (plb->wMultiple == MULTIPLESEL)
{
uEvent = EVENT_OBJECT_SELECTION;
}
break;
case EVENT_OBJECT_SELECTIONWITHIN:
iItem = -1;
break;
}
NotifyWinEvent(uEvent, plb->hwnd, OBJID_CLIENT, iItem+1);
}