/**************************** Module Header ********************************\ * Module Name: lboxrare.c * * Copyright (c) 1985 - 1999, Microsoft Corporation * * Infrequently Used List Box Routines * * History: * ??-???-???? ianja Ported from Win 3.0 sources * 14-Feb-1991 mikeke Added Revalidation code \***************************************************************************/ #include "precomp.h" #pragma hdrstop extern LOOKASIDE ListboxLookaside; /***************************************************************************\ * LBSetCItemFullMax * * History: * 03-04-92 JimA Ported from Win 3.1 sources. \***************************************************************************/ void LBSetCItemFullMax( PLBIV plb) { if (plb->OwnerDraw != OWNERDRAWVAR) { plb->cItemFullMax = CItemInWindow(plb, FALSE); } else if (plb->cMac < 2) { plb->cItemFullMax = 1; } else { int height; RECT rect; int i; int j = 0; _GetClientRect(plb->spwnd, &rect); height = rect.bottom; plb->cItemFullMax = 0; for (i = plb->cMac - 1; i >= 0; i--, j++) { height -= LBGetVariableHeightItemHeight(plb, i); if (height < 0) { plb->cItemFullMax = j; break; } } if (!plb->cItemFullMax) plb->cItemFullMax = j; } } /***************************************************************************\ * xxxCreateLBox * * History: * 16-Apr-1992 beng Added LBS_NODATA \***************************************************************************/ LONG xxxLBCreate( PLBIV plb, PWND pwnd, LPCREATESTRUCT lpcs) { UINT style; MEASUREITEMSTRUCT measureItemStruct; TL tlpwndParent; HDC hdc; /* * 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 = pwnd->style; /* * Compatibility hack. */ if (pwnd->spwndParent == NULL) Lock(&(plb->spwndParent), _GetDesktopWindow()); else Lock(&(plb->spwndParent), REBASEPWND(pwnd, spwndParent)); /* * 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 (!TestWF(pwnd, WFWIN40COMPAT)) { // 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 = (TestWF(pwnd, WEFRTLREADING) != 0); plb->fRightAlign = (TestWF(pwnd, WEFRIGHT) != 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 (TestWF(pwnd, WFWIN40COMPAT) && (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 dialog units. */ LBSetTabStops(plb, 0, NULL); } plb->fMultiColumn = ((style & LBS_MULTICOLUMN) != 0); plb->fHasStrings = TRUE; plb->iLastSelection = -1; plb->iMouseDown = -1; /* Anchor point for multi selection */ 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) { RIPERR0(ERROR_INVALID_FLAGS, RIP_WARNING, "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. */ if (plb->spwndParent->cbwndExtra != 0) plb->pcbox = ((PCOMBOWND)(plb->spwndParent))->pcbox; } /* * No need to set these to 0 since that was done for us when we Alloced * the PLBIV. */ /* * plb->rgpch = (PBYTE)0; */ /* * plb->iSelBase = plb->iTop = 0; */ /* * plb->fMouseDown = FALSE; */ /* * plb->fCaret = FALSE; */ /* * plb->fCaretOn = FALSE; */ /* * plb->maxWidth = 0; */ plb->iSel = -1; plb->hdc = NULL; /* * Set the keyboard state so that when the user keyboard clicks he selects * an item. */ plb->fNewItemState = TRUE; InitHStrings(plb); if (plb->fHasStrings && plb->hStrings == NULL) { return -1L; } hdc = NtUserGetDC(HWq(pwnd)); plb->cxChar = GdiGetCharDimensions(hdc, NULL, &plb->cyChar); NtUserReleaseDC(HWq(pwnd), hdc); if (plb->cxChar == 0) { RIPMSG0(RIP_WARNING, "xxxLBCreate: GdiGetCharDimensions failed"); plb->cxChar = gpsi->cxSysFontChar; plb->cyChar = gpsi->cySysFontChar; } 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 = PtrToUlong(pwnd->spmenu); /* * 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 */ ThreadLock(plb->spwndParent, &tlpwndParent); SendMessage(HW(plb->spwndParent), WM_MEASUREITEM, measureItemStruct.CtlID, (LPARAM)&measureItemStruct); ThreadUnlock(&tlpwndParent); /* * 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; } LBSetCItemFullMax(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 (!TestWF(pwnd, WFWIN40COMPAT)) { plb->fIgnoreSizeMsg = TRUE; NtUserMoveWindow(HWq(pwnd), 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(HWq(pwnd), WM_SIZE, 0, 0L); } return 1L; } /***************************************************************************\ * xxxLBoxDoDeleteItems * * Send DELETEITEM message for all the items in the ownerdraw listbox. * * History: * 16-Apr-1992 beng Nodata case \***************************************************************************/ void xxxLBoxDoDeleteItems( PLBIV plb) { INT sItem; CheckLock(plb->spwnd); /* * 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--) { xxxLBoxDeleteItem(plb, sItem); } } } /***************************************************************************\ * xxxDestroyLBox * * History: \***************************************************************************/ void xxxDestroyLBox( PLBIV pLBIV, PWND pwnd) { PWND pwndParent; CheckLock(pwnd); if (pLBIV != NULL) { CheckLock(pLBIV->spwnd); /* * If ownerdraw, send deleteitem messages to parent */ xxxLBoxDoDeleteItems(pLBIV); if (pLBIV->rgpch != NULL) { UserLocalFree(pLBIV->rgpch); pLBIV->rgpch = NULL; } if (pLBIV->hStrings != NULL) { UserLocalFree(pLBIV->hStrings); pLBIV->hStrings = NULL; } if (pLBIV->iTabPixelPositions != NULL) { UserLocalFree((HANDLE)pLBIV->iTabPixelPositions); pLBIV->iTabPixelPositions = NULL; } Unlock(&pLBIV->spwnd); Unlock(&pLBIV->spwndParent); if (pLBIV->pszTypeSearch) { UserLocalFree(pLBIV->pszTypeSearch); } FreeLookasideEntry(&ListboxLookaside, pLBIV); } /* * Set the window's fnid status so that we can ignore rogue messages */ NtUserSetWindowFNID(HWq(pwnd), FNID_CLEANEDUP_BIT); /* * If we're part of a combo box, let it know we're gone */ pwndParent = REBASEPWND(pwnd, spwndParent); if (pwndParent && GETFNID(pwndParent) == FNID_COMBOBOX) { ComboBoxWndProcWorker(pwndParent, WM_PARENTNOTIFY, MAKELONG(WM_DESTROY, PTR_TO_ID(pwnd->spmenu)), (LPARAM)HWq(pwnd), FALSE); } } /***************************************************************************\ * xxxLBSetFont * * History: \***************************************************************************/ void xxxLBSetFont( PLBIV plb, HANDLE hFont, BOOL fRedraw) { HDC hdc; HANDLE hOldFont = NULL; int iHeight; CheckLock(plb->spwnd); plb->hFont = hFont; hdc = NtUserGetDC(HWq(plb->spwnd)); if (hFont) { hOldFont = SelectObject(hdc, hFont); if (!hOldFont) { plb->hFont = NULL; } } plb->cxChar = GdiGetCharDimensions(hdc, NULL, &iHeight); if (plb->cxChar == 0) { RIPMSG0(RIP_WARNING, "xxxLBSetFont: GdiGetCharDimensions failed"); plb->cxChar = gpsi->cxSysFontChar; iHeight = gpsi->cySysFontChar; } if (!plb->OwnerDraw && (plb->cyChar != iHeight)) { /* * We don't want to mess up the cyChar height for owner draw listboxes * so don't do this. */ plb->cyChar = iHeight; /* * Only resize the listbox for 4.0 dudes, or combo dropdowns. * Macromedia Director 4.0 GP-faults otherwise. */ if (!plb->fNoIntegralHeight && (plb->pcbox || TestWF(plb->spwnd, WFWIN40COMPAT))) { xxxLBSize(plb, plb->spwnd->rcClient.right - plb->spwnd->rcClient.left, plb->spwnd->rcClient.bottom - plb->spwnd->rcClient.top); } } if (hOldFont) { SelectObject(hdc, hOldFont); } /* * IanJa: was ReleaseDC(hwnd, hdc); */ NtUserReleaseDC(HWq(plb->spwnd), hdc); if (plb->fMultiColumn) { LBCalcItemRowsAndColumns(plb); } LBSetCItemFullMax(plb); if (fRedraw) xxxCheckRedraw(plb, FALSE, 0); } /***************************************************************************\ * xxxLBSize * * History: \***************************************************************************/ void xxxLBSize( PLBIV plb, INT cx, INT cy) { RECT rc; int iTopOld; BOOL fSizedSave; CheckLock(plb->spwnd); if (!plb->fNoIntegralHeight) { int cBdrs = GetWindowBorders(plb->spwnd->style, plb->spwnd->ExStyle, TRUE, TRUE); CopyInflateRect(&rc, KPRECT_TO_PRECT(&plb->spwnd->rcWindow), 0, -cBdrs * SYSMET(CYBORDER)); // Size the listbox to fit an integral # of items in its client if ((rc.bottom - rc.top) % plb->cyChar) { 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 ( ! TestWF( plb->spwnd, WFWIN40COMPAT ) ) iItems += (cBdrs * SYSMET(CYEDGE)); // so add it back in iItems /= plb->cyChar; NtUserSetWindowPos(HWq(plb->spwnd), 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 */ LBCalcItemRowsAndColumns(plb); } else { /* * Adjust the current horizontal position to eliminate as much * empty space as possible from the right side of the items. */ _GetClientRect(plb->spwnd, &rc); if ((plb->maxWidth - plb->xOrigin) < (rc.right - rc.left)) plb->xOrigin = max(0, plb->maxWidth - (rc.right - rc.left)); } LBSetCItemFullMax(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; xxxNewITop(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) NtUserInvalidateRect(HWq(plb->spwnd), NULL, TRUE); else if (plb->iSelBase >= 0) { /* * Invalidate the item with the caret so that if the listbox * grows horizontally, we redraw it properly. */ LBGetItemRect(plb, plb->iSelBase, &rc); NtUserInvalidateRect(HWq(plb->spwnd), &rc, FALSE); } } else if (!plb->fRedraw) plb->fDeferUpdate = TRUE; /* * Send "fake" scroll bar messages to update the scroll positions since we * changed size. */ if (TestWF(plb->spwnd, WFVSCROLL)) { xxxLBoxCtlScroll(plb, SB_ENDSCROLL, 0); } /* * We count on this to call LBShowHideScrollBars except when plb->cMac == 0! */ xxxLBoxCtlHScroll(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 LBoxCtlHScroll with SB_ENDSCROLL otherwise. */ if (plb->cMac == 0) xxxLBShowHideScrollBars(plb); } /***************************************************************************\ * LBSetTabStops * * Sets the tab stops for this listbox. Returns TRUE if successful else FALSE. * * History: \***************************************************************************/ BOOL LBSetTabStops( PLBIV plb, INT count, LPINT lptabstops) { PINT ptabs; if (!plb->fUseTabStops) { RIPERR0(ERROR_LB_WITHOUT_TABSTOPS, RIP_VERBOSE, ""); 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)UserLocalAlloc(HEAP_ZERO_MEMORY, (count + 1) * sizeof(int)); if (ptabs == NULL) return FALSE; if (plb->iTabPixelPositions != NULL) UserLocalFree(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) { UserLocalFree((HANDLE)plb->iTabPixelPositions); plb->iTabPixelPositions = NULL; } } return TRUE; } /***************************************************************************\ * InitHStrings * * History: \***************************************************************************/ void InitHStrings( PLBIV plb) { if (plb->fHasStrings) { plb->ichAlloc = 0; plb->cchStrings = 0; plb->hStrings = UserLocalAlloc(0, 0L); } } /***************************************************************************\ * LBDropObjectHandler * * Handles a WM_DROPITEM message on this listbox * * History: \***************************************************************************/ void LBDropObjectHandler( PLBIV plb, PDROPSTRUCT pds) { LONG mouseSel; if (ISelFromPt(plb, pds->ptDrop, &mouseSel)) { /* * User dropped in empty space at bottom of listbox */ pds->dwControlData = (DWORD)-1L; } else { pds->dwControlData = mouseSel; } } /***************************************************************************\ * LBGetSetItemHeightHandler() * * Sets/Gets the height associated with each item. For non ownerdraw * and fixed height ownerdraw, the item number is ignored. * * History: \***************************************************************************/ int LBGetSetItemHeightHandler( 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) { RIPERR0(ERROR_INVALID_INDEX, RIP_VERBOSE, ""); return LB_ERR; } return (int)LBGetVariableHeightItemHeight(plb, (INT)item); } if (!height || height > 255) { RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING, "Invalid parameter \"height\" (%ld) to LBGetSetItemHeightHandler", height); return LB_ERR; } if (plb->OwnerDraw != OWNERDRAWVAR) plb->cyChar = height; else { if (item < 0 || item >= plb->cMac) { RIPERR1(ERROR_INVALID_PARAMETER, RIP_WARNING, "Invalid parameter \"item\" (%ld) to LBGetSetItemHeightHandler", item); return LB_ERR; } LBSetVariableHeightItemHeight(plb, (INT)item, (INT)height); } if (plb->fMultiColumn) LBCalcItemRowsAndColumns(plb); LBSetCItemFullMax(plb); return(0); } /*****************************************************************************\ * * LBEvent() * * This is for item focus & selection events in listboxes. * \*****************************************************************************/ void LBEvent(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, HW(plb->spwnd), OBJID_CLIENT, iItem+1); }