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.
3810 lines
112 KiB
3810 lines
112 KiB
/***************************************************************************\
|
|
*
|
|
* LBOXCTL2.C -
|
|
*
|
|
* Copyright (c) 1985 - 1999, Microsoft Corporation
|
|
*
|
|
* List box handling routines
|
|
*
|
|
* 18-Dec-1990 ianja Ported from Win 3.0 sources
|
|
* 14-Feb-1991 mikeke Added Revalidation code
|
|
\***************************************************************************/
|
|
|
|
#include "precomp.h"
|
|
#pragma hdrstop
|
|
|
|
#define LB_KEYDOWN WM_USER+1
|
|
#define NOMODIFIER 0 /* No modifier is down */
|
|
#define SHIFTDOWN 1 /* Shift alone */
|
|
#define CTLDOWN 2 /* Ctl alone */
|
|
#define SHCTLDOWN (SHIFTDOWN + CTLDOWN) /* Ctrl + Shift */
|
|
|
|
/*
|
|
* Variables for incremental type search support
|
|
*/
|
|
#define MAX_TYPESEARCH 256
|
|
|
|
BOOL LBGetDC(PLBIV plb);
|
|
void LBReleaseDC(PLBIV plb);
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* LBInvalidateRect()
|
|
*
|
|
* If the listbox is visible, invalidates a rectangle in the listbox.
|
|
* If the listbox is not visible, sets the defer update flag for the listbox
|
|
*
|
|
\***************************************************************************/
|
|
BOOL xxxLBInvalidateRect(PLBIV plb, LPRECT lprc, BOOL fErase)
|
|
{
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (IsLBoxVisible(plb)) {
|
|
NtUserInvalidateRect(HWq(plb->spwnd), lprc, fErase);
|
|
return(TRUE);
|
|
}
|
|
|
|
if (!plb->fRedraw)
|
|
plb->fDeferUpdate = TRUE;
|
|
|
|
return(FALSE);
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* LBGetBrush()
|
|
*
|
|
* Gets background brush & colors for listbox.
|
|
*
|
|
\***************************************************************************/
|
|
HBRUSH xxxLBGetBrush(PLBIV plb, HBRUSH *phbrOld)
|
|
{
|
|
HBRUSH hbr;
|
|
HBRUSH hbrOld;
|
|
TL tlpwndParent;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
SetBkMode(plb->hdc, OPAQUE);
|
|
|
|
//
|
|
// Get brush & colors
|
|
//
|
|
if ((plb->spwnd->spwndParent == NULL) ||
|
|
(REBASEPWND(plb->spwnd, spwndParent) == _GetDesktopWindow())) {
|
|
ThreadLock(plb->spwndParent, &tlpwndParent);
|
|
hbr = GetControlColor(HW(plb->spwndParent), HWq(plb->spwnd),
|
|
plb->hdc, WM_CTLCOLORLISTBOX);
|
|
ThreadUnlock(&tlpwndParent);
|
|
} else
|
|
hbr = GetControlBrush(HWq(plb->spwnd), plb->hdc, WM_CTLCOLORLISTBOX);
|
|
|
|
//
|
|
// Select brush into dc
|
|
//
|
|
if (hbr != NULL) {
|
|
hbrOld = SelectObject(plb->hdc, hbr);
|
|
if (phbrOld)
|
|
*phbrOld = hbrOld;
|
|
}
|
|
|
|
return(hbr);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* LBInitDC()
|
|
*
|
|
* Initializes dc for listbox
|
|
*
|
|
\***************************************************************************/
|
|
void LBInitDC(PLBIV plb)
|
|
{
|
|
RECT rc;
|
|
|
|
// Set font
|
|
if (plb->hFont)
|
|
SelectObject(plb->hdc, plb->hFont);
|
|
|
|
// Set clipping area
|
|
_GetClientRect(plb->spwnd, &rc);
|
|
IntersectClipRect(plb->hdc, rc.left, rc.top, rc.right, rc.bottom);
|
|
|
|
OffsetWindowOrgEx(plb->hdc, plb->xOrigin, 0, NULL);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* LBGetDC
|
|
*
|
|
* Returns a DC which can be used by a list box even if parentDC is in effect
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
BOOL LBGetDC(
|
|
PLBIV plb)
|
|
{
|
|
if (plb->hdc)
|
|
return(FALSE);
|
|
|
|
plb->hdc = NtUserGetDC(HWq(plb->spwnd));
|
|
|
|
LBInitDC(plb);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* LBTermDC()
|
|
*
|
|
* Cleans up when done with listbox dc.
|
|
*
|
|
\***************************************************************************/
|
|
void LBTermDC(PLBIV plb)
|
|
{
|
|
if (plb->hFont)
|
|
SelectObject(plb->hdc, ghFontSys);
|
|
}
|
|
|
|
|
|
|
|
/***************************************************************************\
|
|
* LBReleaseDC
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void LBReleaseDC(
|
|
PLBIV plb)
|
|
{
|
|
LBTermDC(plb);
|
|
NtUserReleaseDC(HWq(plb->spwnd), plb->hdc);
|
|
plb->hdc = NULL;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* LBGetItemRect
|
|
*
|
|
* Return the rectangle that the item will be drawn in with respect to the
|
|
* listbox window. Returns TRUE if any portion of the item's rectangle
|
|
* is visible (ie. in the listbox client rect) else returns FALSE.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
BOOL LBGetItemRect(
|
|
PLBIV plb,
|
|
INT sItem,
|
|
LPRECT lprc)
|
|
{
|
|
INT sTmp;
|
|
int clientbottom;
|
|
|
|
/*
|
|
* Always allow an item number of 0 so that we can draw the caret which
|
|
* indicates the listbox has the focus even though it is empty.
|
|
|
|
* FreeHand 3.1 passes in -1 as the itemNumber and expects
|
|
* a non-null rectangle. So we check for -1 specifically.
|
|
* BUGTAG: Fix for Bug #540 --Win95B-- SANKAR -- 2/20/95 --
|
|
*/
|
|
|
|
if (sItem && (sItem != -1) && ((UINT)sItem >= (UINT)plb->cMac))
|
|
{
|
|
SetRectEmpty(lprc);
|
|
RIPERR0(ERROR_INVALID_INDEX, RIP_VERBOSE, "");
|
|
return (LB_ERR);
|
|
}
|
|
|
|
_GetClientRect(plb->spwnd, lprc);
|
|
|
|
if (plb->fMultiColumn) {
|
|
|
|
/*
|
|
* itemHeight * sItem mod number ItemsPerColumn (itemsPerColumn)
|
|
*/
|
|
lprc->top = plb->cyChar * (sItem % plb->itemsPerColumn);
|
|
lprc->bottom = lprc->top + plb->cyChar /*+(plb->OwnerDraw ? 0 : 1)*/;
|
|
|
|
UserAssert(plb->itemsPerColumn);
|
|
|
|
if (plb->fRightAlign) {
|
|
lprc->right = lprc->right - plb->cxColumn *
|
|
((sItem / plb->itemsPerColumn) - (plb->iTop / plb->itemsPerColumn));
|
|
|
|
lprc->left = lprc->right - plb->cxColumn;
|
|
} else {
|
|
/*
|
|
* Remember, this is integer division here...
|
|
*/
|
|
lprc->left += plb->cxColumn *
|
|
((sItem / plb->itemsPerColumn) - (plb->iTop / plb->itemsPerColumn));
|
|
|
|
lprc->right = lprc->left + plb->cxColumn;
|
|
}
|
|
} else if (plb->OwnerDraw == OWNERDRAWVAR) {
|
|
|
|
/*
|
|
* Var height owner draw
|
|
*/
|
|
lprc->right += plb->xOrigin;
|
|
clientbottom = lprc->bottom;
|
|
|
|
if (sItem >= plb->iTop) {
|
|
for (sTmp = plb->iTop; sTmp < sItem; sTmp++) {
|
|
lprc->top = lprc->top + LBGetVariableHeightItemHeight(plb, sTmp);
|
|
}
|
|
|
|
/*
|
|
* If item number is 0, it may be we are asking for the rect
|
|
* associated with a nonexistant item so that we can draw a caret
|
|
* indicating focus on an empty listbox.
|
|
*/
|
|
lprc->bottom = lprc->top + (sItem < plb->cMac ? LBGetVariableHeightItemHeight(plb, sItem) : plb->cyChar);
|
|
return (lprc->top < clientbottom);
|
|
} else {
|
|
|
|
/*
|
|
* Item we want the rect of is before plb->iTop. Thus, negative
|
|
* offsets for the rect and it is never visible.
|
|
*/
|
|
for (sTmp = sItem; sTmp < plb->iTop; sTmp++) {
|
|
lprc->top = lprc->top - LBGetVariableHeightItemHeight(plb, sTmp);
|
|
}
|
|
lprc->bottom = lprc->top + LBGetVariableHeightItemHeight(plb, sItem);
|
|
return FALSE;
|
|
}
|
|
} else {
|
|
|
|
/*
|
|
* For fixed height listboxes
|
|
*/
|
|
if (plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw) && plb->fHorzBar)
|
|
lprc->right += plb->xOrigin + (plb->xRightOrigin - plb->xOrigin);
|
|
else
|
|
lprc->right += plb->xOrigin;
|
|
lprc->top = (sItem - plb->iTop) * plb->cyChar;
|
|
lprc->bottom = lprc->top + plb->cyChar;
|
|
}
|
|
|
|
return (sItem >= plb->iTop) &&
|
|
(sItem < (plb->iTop + CItemInWindow(plb, TRUE)));
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* LBPrintCallback
|
|
*
|
|
* Called back from DrawState()
|
|
*
|
|
\***************************************************************************/
|
|
BOOL CALLBACK LBPrintCallback(
|
|
HDC hdc,
|
|
LPARAM lData,
|
|
WPARAM wData,
|
|
int cx,
|
|
int cy)
|
|
{
|
|
LPWSTR lpstr = (LPWSTR)lData;
|
|
PLBIV plb = (PLBIV)wData;
|
|
int xStart;
|
|
UINT cLen;
|
|
RECT rc;
|
|
UINT oldAlign;
|
|
|
|
if (!lpstr) {
|
|
return FALSE;
|
|
}
|
|
|
|
if (plb->fMultiColumn)
|
|
xStart = 0;
|
|
else
|
|
xStart = 2;
|
|
|
|
if (plb->fRightAlign) {
|
|
oldAlign = SetTextAlign(hdc, TA_RIGHT | GetTextAlign(hdc));
|
|
xStart = cx - xStart;
|
|
}
|
|
|
|
cLen = wcslen(lpstr);
|
|
|
|
if (plb->fUseTabStops) {
|
|
TabTextOut(hdc, xStart, 0, lpstr, cLen,
|
|
(plb->iTabPixelPositions ? plb->iTabPixelPositions[0] : 0),
|
|
(plb->iTabPixelPositions ? (LPINT)&plb->iTabPixelPositions[1] : NULL),
|
|
plb->fRightAlign ? cx : 0, TRUE, GetTextCharset(plb->hdc));
|
|
} else {
|
|
rc.left = 0;
|
|
rc.top = 0;
|
|
rc.right = cx;
|
|
rc.bottom = cy;
|
|
|
|
if (plb->wMultiple)
|
|
ExtTextOut(hdc, xStart, 0, ETO_OPAQUE, &rc, lpstr, cLen, NULL);
|
|
else if (plb->fMultiColumn)
|
|
ExtTextOut(hdc, xStart, 0, ETO_CLIPPED, &rc, lpstr, cLen, NULL);
|
|
else {
|
|
ExtTextOut(hdc, xStart, 0, 0, NULL, lpstr, cLen, NULL);
|
|
|
|
/*
|
|
* When the listbox is in the incremental search mode and the item
|
|
* is highlighted (so we only draw in the current item), draw the
|
|
* caret for search indication.
|
|
*/
|
|
if ((plb->iTypeSearch != 0) && (plb->OwnerDraw == 0) &&
|
|
(GetBkColor(hdc) == SYSRGB(HIGHLIGHT))) {
|
|
SIZE size;
|
|
GetTextExtentPointW(hdc, lpstr, plb->iTypeSearch, &size);
|
|
PatBlt(hdc, xStart + size.cx - 1, 1, 1, cy - 2, DSTINVERT);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (plb->fRightAlign)
|
|
SetTextAlign(hdc, oldAlign);
|
|
|
|
return(TRUE);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLBDrawLBItem
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxLBDrawLBItem(
|
|
PLBIV plb,
|
|
INT sItem,
|
|
LPRECT lprect,
|
|
BOOL fHilite,
|
|
HBRUSH hbr)
|
|
{
|
|
LPWSTR lpstr;
|
|
DWORD rgbSave;
|
|
DWORD rgbBkSave;
|
|
UINT uFlags;
|
|
HDC hdc = plb->hdc;
|
|
UINT oldAlign;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
/*
|
|
* If the item is selected, then fill with highlight color
|
|
*/
|
|
if (fHilite) {
|
|
FillRect(hdc, lprect, SYSHBR(HIGHLIGHT));
|
|
rgbBkSave = SetBkColor(hdc, SYSRGB(HIGHLIGHT));
|
|
rgbSave = SetTextColor(hdc, SYSRGB(HIGHLIGHTTEXT));
|
|
} else {
|
|
|
|
/*
|
|
* If fUseTabStops, we must fill the background, because later we use
|
|
* LBTabTheTextOutForWimps(), which fills the background only partially
|
|
* Fix for Bug #1509 -- 01/25/91 -- SANKAR --
|
|
*/
|
|
if ((hbr != NULL) && ((sItem == plb->iSelBase) || (plb->fUseTabStops))) {
|
|
FillRect(hdc, lprect, hbr);
|
|
}
|
|
}
|
|
|
|
uFlags = DST_COMPLEX;
|
|
lpstr = GetLpszItem(plb, sItem);
|
|
|
|
if (TestWF(plb->spwnd, WFDISABLED)) {
|
|
if ((COLORREF)SYSRGB(GRAYTEXT) != GetBkColor(hdc))
|
|
SetTextColor(hdc, SYSRGB(GRAYTEXT));
|
|
else
|
|
uFlags |= DSS_UNION;
|
|
}
|
|
|
|
if (plb->fRightAlign)
|
|
uFlags |= DSS_RIGHT;
|
|
|
|
if (plb->fRtoLReading)
|
|
oldAlign = SetTextAlign(hdc, TA_RTLREADING | GetTextAlign(hdc));
|
|
|
|
DrawState(hdc, SYSHBR(WINDOWTEXT),
|
|
LBPrintCallback,
|
|
(LPARAM)lpstr,
|
|
(WPARAM)plb,
|
|
lprect->left,
|
|
lprect->top,
|
|
lprect->right-lprect->left,
|
|
lprect->bottom-lprect->top,
|
|
uFlags);
|
|
|
|
if (plb->fRtoLReading)
|
|
SetTextAlign(hdc, oldAlign);
|
|
|
|
if (fHilite) {
|
|
SetTextColor(hdc, rgbSave);
|
|
SetBkColor(hdc, rgbBkSave);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* LBSetCaret()
|
|
*
|
|
\***************************************************************************/
|
|
void xxxLBSetCaret(PLBIV plb, BOOL fSetCaret)
|
|
{
|
|
RECT rc;
|
|
BOOL fNewDC;
|
|
|
|
if (plb->fCaret && ((BOOL) plb->fCaretOn != !!fSetCaret)) {
|
|
if (IsLBoxVisible(plb)) {
|
|
/* Turn the caret (located at plb->iSelBase) on */
|
|
fNewDC = LBGetDC(plb);
|
|
|
|
LBGetItemRect(plb, plb->iSelBase, &rc);
|
|
|
|
if (fNewDC) {
|
|
SetBkColor(plb->hdc, SYSRGB(WINDOW));
|
|
SetTextColor(plb->hdc, SYSRGB(WINDOWTEXT));
|
|
}
|
|
|
|
if (plb->OwnerDraw) {
|
|
/* Fill in the drawitem struct */
|
|
UINT itemState = (fSetCaret) ? ODS_FOCUS : 0;
|
|
|
|
if (IsSelected(plb, plb->iSelBase, HILITEONLY))
|
|
itemState |= ODS_SELECTED;
|
|
|
|
xxxLBoxDrawItem(plb, plb->iSelBase, ODA_FOCUS, itemState, &rc);
|
|
} else if (!TestWF(plb->spwnd, WEFPUIFOCUSHIDDEN)) {
|
|
COLORREF crBk = SetBkColor(plb->hdc, SYSRGB(WINDOW));
|
|
COLORREF crText = SetTextColor(plb->hdc, SYSRGB(WINDOWTEXT));
|
|
|
|
DrawFocusRect(plb->hdc, &rc);
|
|
|
|
SetBkColor(plb->hdc, crBk);
|
|
SetTextColor(plb->hdc, crText);
|
|
}
|
|
|
|
if (fNewDC)
|
|
LBReleaseDC(plb);
|
|
}
|
|
plb->fCaretOn = !!fSetCaret;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* IsSelected
|
|
*
|
|
* History:
|
|
* 16-Apr-1992 beng The NODATA listbox case
|
|
\***************************************************************************/
|
|
|
|
BOOL IsSelected(
|
|
PLBIV plb,
|
|
INT sItem,
|
|
UINT wOpFlags)
|
|
{
|
|
LPBYTE lp;
|
|
|
|
if ((sItem >= plb->cMac) || (sItem < 0)) {
|
|
RIPERR0(ERROR_INVALID_INDEX, RIP_VERBOSE, "");
|
|
// return LB_ERR;
|
|
return(FALSE);
|
|
}
|
|
|
|
if (plb->wMultiple == SINGLESEL) {
|
|
return (sItem == plb->iSel);
|
|
}
|
|
|
|
lp = plb->rgpch + sItem +
|
|
(plb->cMac * (plb->fHasStrings
|
|
? sizeof(LBItem)
|
|
: (plb->fHasData
|
|
? sizeof(LBODItem)
|
|
: 0)));
|
|
sItem = *lp;
|
|
|
|
if (wOpFlags == HILITEONLY) {
|
|
sItem >>= 4;
|
|
} else {
|
|
sItem &= 0x0F; /* SELONLY */
|
|
}
|
|
|
|
return sItem;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* CItemInWindow
|
|
*
|
|
* Returns the number of items which can fit in a list box. It
|
|
* includes the partially visible one at the bottom if fPartial is TRUE. For
|
|
* var height ownerdraw, return the number of items visible starting at iTop
|
|
* and going to the bottom of the client rect.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
INT CItemInWindow(
|
|
PLBIV plb,
|
|
BOOL fPartial)
|
|
{
|
|
RECT rect;
|
|
|
|
if (plb->OwnerDraw == OWNERDRAWVAR) {
|
|
return CItemInWindowVarOwnerDraw(plb, fPartial);
|
|
}
|
|
|
|
if (plb->fMultiColumn) {
|
|
return plb->itemsPerColumn * (plb->numberOfColumns + (fPartial ? 1 : 0));
|
|
}
|
|
|
|
_GetClientRect(plb->spwnd, &rect);
|
|
|
|
/*
|
|
* fPartial must be considered only if the listbox height is not an
|
|
* integral multiple of character height.
|
|
* A part of the fix for Bug #3727 -- 01/14/91 -- SANKAR --
|
|
*/
|
|
UserAssert(plb->cyChar);
|
|
return (INT)((rect.bottom / plb->cyChar) +
|
|
((rect.bottom % plb->cyChar)? (fPartial ? 1 : 0) : 0));
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLBoxCtlScroll
|
|
*
|
|
* Handles vertical scrolling of the listbox
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxLBoxCtlScroll(
|
|
PLBIV plb,
|
|
INT cmd,
|
|
int yAmt)
|
|
{
|
|
INT iTopNew;
|
|
INT cItemPageScroll;
|
|
DWORD dwTime = 0;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (plb->fMultiColumn) {
|
|
|
|
/*
|
|
* Don't allow vertical scrolling on a multicolumn list box. Needed
|
|
* in case app sends WM_VSCROLL messages to the listbox.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
cItemPageScroll = plb->cItemFullMax;
|
|
|
|
if (cItemPageScroll > 1)
|
|
cItemPageScroll--;
|
|
|
|
if (plb->cMac) {
|
|
iTopNew = plb->iTop;
|
|
switch (cmd) {
|
|
case SB_LINEUP:
|
|
dwTime = yAmt;
|
|
iTopNew--;
|
|
break;
|
|
|
|
case SB_LINEDOWN:
|
|
dwTime = yAmt;
|
|
iTopNew++;
|
|
break;
|
|
|
|
case SB_PAGEUP:
|
|
if (plb->OwnerDraw == OWNERDRAWVAR) {
|
|
iTopNew = LBPage(plb, plb->iTop, FALSE);
|
|
} else {
|
|
iTopNew -= cItemPageScroll;
|
|
}
|
|
break;
|
|
|
|
case SB_PAGEDOWN:
|
|
if (plb->OwnerDraw == OWNERDRAWVAR) {
|
|
iTopNew = LBPage(plb, plb->iTop, TRUE);
|
|
} else {
|
|
iTopNew += cItemPageScroll;
|
|
}
|
|
break;
|
|
|
|
case SB_THUMBTRACK:
|
|
case SB_THUMBPOSITION: {
|
|
|
|
/*
|
|
* If the listbox contains more than 0xFFFF items
|
|
* it means that the scrolbar can return a position
|
|
* that cannot fit in a WORD (16 bits), so use
|
|
* GetScrollInfo (which is slower) in this case.
|
|
*/
|
|
if (plb->cMac < 0xFFFF) {
|
|
iTopNew = yAmt;
|
|
} else {
|
|
SCROLLINFO si;
|
|
|
|
si.cbSize = sizeof(SCROLLINFO);
|
|
si.fMask = SIF_TRACKPOS;
|
|
|
|
GetScrollInfo( HWq(plb->spwnd), SB_VERT, &si);
|
|
|
|
iTopNew = si.nTrackPos;
|
|
}
|
|
break;
|
|
}
|
|
case SB_TOP:
|
|
iTopNew = 0;
|
|
break;
|
|
|
|
case SB_BOTTOM:
|
|
iTopNew = plb->cMac - 1;
|
|
break;
|
|
|
|
case SB_ENDSCROLL:
|
|
plb->fSmoothScroll = TRUE;
|
|
xxxLBSetCaret(plb, FALSE);
|
|
xxxLBShowHideScrollBars(plb);
|
|
xxxLBSetCaret(plb, TRUE);
|
|
return;
|
|
}
|
|
|
|
xxxNewITopEx(plb, iTopNew, dwTime);
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* LBGetScrollFlags
|
|
*
|
|
\***************************************************************************/
|
|
|
|
DWORD LBGetScrollFlags(PLBIV plb, DWORD dwTime)
|
|
{
|
|
DWORD dwFlags;
|
|
|
|
if (GetAppCompatFlags(NULL) & GACF_NOSMOOTHSCROLLING)
|
|
goto NoSmoothScrolling;
|
|
|
|
if (dwTime != 0) {
|
|
dwFlags = MAKELONG(SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN, dwTime);
|
|
} else if (TEST_EffectPUSIF(PUSIF_LISTBOXSMOOTHSCROLLING) && plb->fSmoothScroll) {
|
|
dwFlags = SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN;
|
|
plb->fSmoothScroll = FALSE;
|
|
} else {
|
|
NoSmoothScrolling:
|
|
dwFlags = SW_SCROLLWINDOW | SW_INVALIDATE | SW_ERASE | SW_SCROLLCHILDREN;
|
|
}
|
|
|
|
return dwFlags;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxLBoxCtlHScroll
|
|
*
|
|
* Supports horizontal scrolling of listboxes
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxLBoxCtlHScroll(
|
|
PLBIV plb,
|
|
INT cmd,
|
|
int xAmt)
|
|
{
|
|
int newOrigin = plb->xOrigin;
|
|
int oldOrigin = plb->xOrigin;
|
|
int windowWidth;
|
|
RECT rc;
|
|
DWORD dwTime = 0;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
/*
|
|
* Update the window so that we don't run into problems with invalid
|
|
* regions during the horizontal scroll.
|
|
*/
|
|
if (plb->fMultiColumn) {
|
|
|
|
/*
|
|
* Handle multicolumn scrolling in a separate segment
|
|
*/
|
|
xxxLBoxCtlHScrollMultiColumn(plb, cmd, xAmt);
|
|
return;
|
|
}
|
|
|
|
_GetClientRect(plb->spwnd, &rc);
|
|
windowWidth = rc.right;
|
|
|
|
if (plb->cMac) {
|
|
|
|
switch (cmd) {
|
|
case SB_LINEUP:
|
|
dwTime = xAmt;
|
|
newOrigin -= plb->cxChar;
|
|
break;
|
|
|
|
case SB_LINEDOWN:
|
|
dwTime = xAmt;
|
|
newOrigin += plb->cxChar;
|
|
break;
|
|
|
|
case SB_PAGEUP:
|
|
newOrigin -= (windowWidth / 3) * 2;
|
|
break;
|
|
|
|
case SB_PAGEDOWN:
|
|
newOrigin += (windowWidth / 3) * 2;
|
|
break;
|
|
|
|
case SB_THUMBTRACK:
|
|
case SB_THUMBPOSITION:
|
|
newOrigin = xAmt;
|
|
break;
|
|
|
|
case SB_TOP:
|
|
newOrigin = 0;
|
|
break;
|
|
|
|
case SB_BOTTOM:
|
|
newOrigin = plb->maxWidth;
|
|
break;
|
|
|
|
case SB_ENDSCROLL:
|
|
plb->fSmoothScroll = TRUE;
|
|
xxxLBSetCaret(plb, FALSE);
|
|
xxxLBShowHideScrollBars(plb);
|
|
xxxLBSetCaret(plb, TRUE);
|
|
return;
|
|
}
|
|
|
|
xxxLBSetCaret(plb, FALSE);
|
|
plb->xOrigin = newOrigin;
|
|
plb->xOrigin = xxxSetLBScrollParms(plb, SB_HORZ);
|
|
|
|
if ((cmd == SB_BOTTOM) && plb->fRightAlign) {
|
|
/*
|
|
* so we know where to draw from.
|
|
*/
|
|
plb->xRightOrigin = plb->xOrigin;
|
|
}
|
|
|
|
if(oldOrigin != plb->xOrigin) {
|
|
HWND hwnd = HWq(plb->spwnd);
|
|
DWORD dwFlags;
|
|
|
|
dwFlags = LBGetScrollFlags(plb, dwTime);
|
|
ScrollWindowEx(hwnd, oldOrigin-plb->xOrigin,
|
|
0, NULL, &rc, NULL, NULL, dwFlags);
|
|
UpdateWindow(hwnd);
|
|
}
|
|
|
|
xxxLBSetCaret(plb, TRUE);
|
|
} else {
|
|
// this is a less-than-ideal fix for ImageMind ScreenSaver (Win95
|
|
// B#8252) but it works and it doesn't hurt anybody -- JEFFBOG 10/28/94
|
|
xxxSetLBScrollParms(plb, SB_HORZ);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLBoxCtlPaint
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxLBPaint(
|
|
PLBIV plb,
|
|
HDC hdc,
|
|
LPRECT lprcBounds)
|
|
{
|
|
INT i;
|
|
RECT rect;
|
|
RECT scratchRect;
|
|
BOOL fHilite;
|
|
INT iLastItem;
|
|
HBRUSH hbrSave = NULL;
|
|
HBRUSH hbrControl;
|
|
BOOL fCaretOn;
|
|
RECT rcBounds;
|
|
HDC hdcSave;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (lprcBounds == NULL) {
|
|
lprcBounds = &rcBounds;
|
|
_GetClientRect(plb->spwnd, lprcBounds);
|
|
}
|
|
|
|
hdcSave = plb->hdc;
|
|
plb->hdc = hdc;
|
|
|
|
// Initialize dc.
|
|
LBInitDC(plb);
|
|
|
|
// Turn caret off
|
|
if (fCaretOn = plb->fCaretOn)
|
|
xxxLBSetCaret(plb, FALSE);
|
|
|
|
hbrSave = NULL;
|
|
hbrControl = xxxLBGetBrush(plb, &hbrSave);
|
|
|
|
// Get listbox's client
|
|
_GetClientRect(plb->spwnd, &rect);
|
|
|
|
// Adjust width of client rect for scrolled amount
|
|
// fix for #140, t-arthb
|
|
if (plb->fRightAlign && !(plb->fMultiColumn || plb->OwnerDraw) && plb->fHorzBar)
|
|
rect.right += plb->xOrigin + (plb->xRightOrigin - plb->xOrigin);
|
|
else
|
|
rect.right += plb->xOrigin;
|
|
|
|
// Get the index of the last item visible on the screen. This is also
|
|
// valid for var height ownerdraw.
|
|
iLastItem = plb->iTop + CItemInWindow(plb,TRUE);
|
|
iLastItem = min(iLastItem, plb->cMac - 1);
|
|
|
|
// Fill in the background of the listbox if it's an empty listbox
|
|
// or if we're doing a control print
|
|
if (iLastItem == -1)
|
|
FillRect(plb->hdc, &rect, hbrControl);
|
|
|
|
|
|
// Allow AnimateWindow() catch the apps that do not use our DC when
|
|
// drawing the list box
|
|
SetBoundsRect(plb->hdc, NULL, DCB_RESET | DCB_ENABLE);
|
|
|
|
for (i = plb->iTop; i <= iLastItem; i++) {
|
|
|
|
/*
|
|
* Note that rect contains the clientrect from when we did the
|
|
* _GetClientRect so the width is correct. We just need to adjust
|
|
* the top and bottom of the rectangle to the item of interest.
|
|
*/
|
|
rect.bottom = rect.top + plb->cyChar;
|
|
|
|
if ((UINT)i < (UINT)plb->cMac) {
|
|
|
|
/*
|
|
* If var height, get the rectangle for the item.
|
|
*/
|
|
if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn) {
|
|
LBGetItemRect(plb, i, &rect);
|
|
}
|
|
|
|
if (IntersectRect(&scratchRect, lprcBounds, &rect)) {
|
|
fHilite = !plb->fNoSel && IsSelected(plb, i, HILITEONLY);
|
|
|
|
if (plb->OwnerDraw) {
|
|
|
|
/*
|
|
* Fill in the drawitem struct
|
|
*/
|
|
xxxLBoxDrawItem(plb, i, ODA_DRAWENTIRE,
|
|
(UINT)(fHilite ? ODS_SELECTED : 0), &rect);
|
|
} else {
|
|
xxxLBDrawLBItem(plb, i, &rect, fHilite, hbrControl);
|
|
}
|
|
}
|
|
}
|
|
rect.top = rect.bottom;
|
|
}
|
|
|
|
if (hbrSave != NULL)
|
|
SelectObject(hdc, hbrSave);
|
|
|
|
if (fCaretOn)
|
|
xxxLBSetCaret(plb, TRUE);
|
|
|
|
LBTermDC(plb);
|
|
|
|
plb->hdc = hdcSave;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* ISelFromPt
|
|
*
|
|
* In the loword, returns the closest item number the pt is on. The high
|
|
* word is 0 if the point is within bounds of the listbox client rect and is
|
|
* 1 if it is outside the bounds. This will allow us to make the invertrect
|
|
* disappear if the mouse is outside the listbox yet we can still show the
|
|
* outline around the item that would be selected if the mouse is brought back
|
|
* in bounds...
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
BOOL ISelFromPt(
|
|
PLBIV plb,
|
|
POINT pt,
|
|
LPDWORD piItem)
|
|
{
|
|
RECT rect;
|
|
int y;
|
|
UINT mouseHighWord = 0;
|
|
INT sItem;
|
|
INT sTmp;
|
|
|
|
_GetClientRect(plb->spwnd, &rect);
|
|
|
|
if (pt.y < 0) {
|
|
|
|
/*
|
|
* Mouse is out of bounds above listbox
|
|
*/
|
|
*piItem = plb->iTop;
|
|
return TRUE;
|
|
} else if ((y = pt.y) > rect.bottom) {
|
|
y = rect.bottom;
|
|
mouseHighWord = 1;
|
|
}
|
|
|
|
if (pt.x < 0 || pt.x > rect.right)
|
|
mouseHighWord = 1;
|
|
|
|
/*
|
|
* Now just need to check if y mouse coordinate intersects item's rectangle
|
|
*/
|
|
if (plb->OwnerDraw != OWNERDRAWVAR) {
|
|
if (plb->fMultiColumn) {
|
|
if (y < plb->itemsPerColumn * plb->cyChar) {
|
|
if (plb->fRightAlign)
|
|
sItem = plb->iTop + (INT)((y / plb->cyChar) +
|
|
((rect.right - pt.x) / plb->cxColumn) * plb->itemsPerColumn);
|
|
else
|
|
sItem = plb->iTop + (INT)((y / plb->cyChar) +
|
|
(pt.x / plb->cxColumn) * plb->itemsPerColumn);
|
|
|
|
} else {
|
|
|
|
/*
|
|
* User clicked in blank space at the bottom of a column.
|
|
* Just select the last item in the column.
|
|
*/
|
|
mouseHighWord = 1;
|
|
sItem = plb->iTop + (plb->itemsPerColumn - 1) +
|
|
(INT)((pt.x / plb->cxColumn) * plb->itemsPerColumn);
|
|
}
|
|
} else {
|
|
sItem = plb->iTop + (INT)(y / plb->cyChar);
|
|
}
|
|
} else {
|
|
|
|
/*
|
|
* VarHeightOwnerdraw so we gotta do this the hardway... Set the x
|
|
* coordinate of the mouse down point to be inside the listbox client
|
|
* rectangle since we no longer care about it. This lets us use the
|
|
* point in rect calls.
|
|
*/
|
|
pt.x = 8;
|
|
pt.y = y;
|
|
for (sTmp = plb->iTop; sTmp < plb->cMac; sTmp++) {
|
|
(void)LBGetItemRect(plb, sTmp, &rect);
|
|
if (PtInRect(&rect, pt)) {
|
|
*piItem = sTmp;
|
|
return mouseHighWord;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Point was at the empty area at the bottom of a not full listbox
|
|
*/
|
|
*piItem = plb->cMac - 1;
|
|
return mouseHighWord;
|
|
}
|
|
|
|
/*
|
|
* Check if user clicked on the blank area at the bottom of a not full list.
|
|
* Assumes > 0 items in the listbox.
|
|
*/
|
|
if (sItem > plb->cMac - 1) {
|
|
mouseHighWord = 1;
|
|
sItem = plb->cMac - 1;
|
|
}
|
|
|
|
*piItem = sItem;
|
|
return mouseHighWord;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* SetSelected
|
|
*
|
|
* This is used for button initiated changes of selection state.
|
|
*
|
|
* fSelected : TRUE if the item is to be set as selected, FALSE otherwise
|
|
*
|
|
* wOpFlags : HILITEONLY = Modify only the Display state (hi-nibble)
|
|
* SELONLY = Modify only the Selection state (lo-nibble)
|
|
* HILITEANDSEL = Modify both of them;
|
|
*
|
|
* History:
|
|
* 16-Apr-1992 beng The NODATA listbox case
|
|
\***************************************************************************/
|
|
|
|
void SetSelected(
|
|
PLBIV plb,
|
|
INT iSel,
|
|
BOOL fSelected,
|
|
UINT wOpFlags)
|
|
{
|
|
LPSTR lp;
|
|
BYTE cMask;
|
|
BYTE cSelStatus;
|
|
|
|
if (iSel < 0 || iSel >= plb->cMac)
|
|
return;
|
|
|
|
if (plb->wMultiple == SINGLESEL) {
|
|
if (fSelected)
|
|
plb->iSel = iSel;
|
|
} else {
|
|
cSelStatus = (BYTE)fSelected;
|
|
switch (wOpFlags) {
|
|
case HILITEONLY:
|
|
|
|
/*
|
|
* Mask out lo-nibble
|
|
*/
|
|
cSelStatus = (BYTE)(cSelStatus << 4);
|
|
cMask = 0x0F;
|
|
break;
|
|
case SELONLY:
|
|
|
|
/*
|
|
* Mask out hi-nibble
|
|
*/
|
|
cMask = 0xF0;
|
|
break;
|
|
case HILITEANDSEL:
|
|
|
|
/*
|
|
* Mask the byte fully
|
|
*/
|
|
cSelStatus |= (cSelStatus << 4);
|
|
cMask = 0;
|
|
break;
|
|
}
|
|
lp = (LPSTR)(plb->rgpch) + iSel +
|
|
(plb->cMac * (plb->fHasStrings
|
|
? sizeof(LBItem)
|
|
: (plb->fHasData ? sizeof(LBODItem) : 0)));
|
|
|
|
*lp = (*lp & cMask) | cSelStatus;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* LastFullVisible
|
|
*
|
|
* Returns the last fully visible item in the listbox. This is valid
|
|
* for ownerdraw var height and fixed height listboxes.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
INT LastFullVisible(
|
|
PLBIV plb)
|
|
{
|
|
INT iLastItem;
|
|
|
|
if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn) {
|
|
iLastItem = plb->iTop + CItemInWindow(plb, FALSE) - 1;
|
|
iLastItem = max(iLastItem, plb->iTop);
|
|
} else {
|
|
iLastItem = min(plb->iTop + plb->cItemFullMax - 1, plb->cMac - 1);
|
|
}
|
|
return iLastItem;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxInvertLBItem
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxInvertLBItem(
|
|
PLBIV plb,
|
|
INT i,
|
|
BOOL fHilite) /* The new selection state of the item */
|
|
{
|
|
RECT rect;
|
|
BOOL fCaretOn;
|
|
HBRUSH hbrControl;
|
|
BOOL fNewDC;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
// Skip if item isn't showing.
|
|
if (plb->fNoSel || (i < plb->iTop) || (i >= (plb->iTop + CItemInWindow(plb, TRUE))))
|
|
return;
|
|
|
|
if (IsLBoxVisible(plb)) {
|
|
LBGetItemRect(plb, i, &rect);
|
|
|
|
/*
|
|
* Only turn off the caret if it is on. This avoids annoying caret
|
|
* flicker when nesting xxxCaretOns and xxxCaretOffs.
|
|
*/
|
|
if (fCaretOn = plb->fCaretOn) {
|
|
xxxLBSetCaret(plb, FALSE);
|
|
}
|
|
|
|
fNewDC = LBGetDC(plb);
|
|
|
|
hbrControl = xxxLBGetBrush(plb, NULL);
|
|
|
|
if (!plb->OwnerDraw) {
|
|
if (!fHilite) {
|
|
FillRect(plb->hdc, &rect, hbrControl);
|
|
hbrControl = NULL;
|
|
}
|
|
|
|
xxxLBDrawLBItem(plb, i, &rect, fHilite, hbrControl);
|
|
} else {
|
|
|
|
/*
|
|
* We are ownerdraw so fill in the drawitem struct and send off
|
|
* to the owner.
|
|
*/
|
|
xxxLBoxDrawItem(plb, i, ODA_SELECT,
|
|
(UINT)(fHilite ? ODS_SELECTED : 0), &rect);
|
|
}
|
|
|
|
if (fNewDC)
|
|
LBReleaseDC(plb);
|
|
|
|
/*
|
|
* Turn the caret back on only if it was originally on.
|
|
*/
|
|
if (fCaretOn) {
|
|
xxxLBSetCaret(plb, TRUE);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxResetWorld
|
|
*
|
|
* Resets everyone's selection and hilite state except items in the
|
|
* range sStItem to sEndItem (Both inclusive).
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxResetWorld(
|
|
PLBIV plb,
|
|
INT iStart,
|
|
INT iEnd,
|
|
BOOL fSelect)
|
|
{
|
|
INT i;
|
|
INT iLastInWindow;
|
|
BOOL fCaretOn;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
/*
|
|
* If iStart and iEnd are not in correct order we swap them
|
|
*/
|
|
|
|
if (iStart > iEnd) {
|
|
i = iStart;
|
|
iStart = iEnd;
|
|
iEnd = i;
|
|
}
|
|
|
|
if (plb->wMultiple == SINGLESEL) {
|
|
if (plb->iSel != -1 && ((plb->iSel < iStart) || (plb->iSel > iEnd))) {
|
|
xxxInvertLBItem(plb, plb->iSel, fSelect);
|
|
plb->iSel = -1;
|
|
}
|
|
return;
|
|
}
|
|
|
|
iLastInWindow = plb->iTop + CItemInWindow(plb, TRUE);
|
|
|
|
if (fCaretOn = plb->fCaretOn)
|
|
xxxLBSetCaret(plb, FALSE);
|
|
|
|
for (i = 0; i < plb->cMac; i++) {
|
|
if (i == iStart)
|
|
// skip range to be preserved
|
|
i = iEnd;
|
|
else {
|
|
if ((plb->iTop <= i) && (i <= iLastInWindow) &&
|
|
(fSelect != IsSelected(plb, i, HILITEONLY)))
|
|
// Only invert the item if it is visible and present Selection
|
|
// state is different from what is required.
|
|
xxxInvertLBItem(plb, i, fSelect);
|
|
|
|
// Set all items outside of preserved range to unselected
|
|
SetSelected(plb, i, fSelect, HILITEANDSEL);
|
|
}
|
|
}
|
|
|
|
if (fCaretOn)
|
|
xxxLBSetCaret(plb, TRUE);
|
|
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxNotifyOwner
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxNotifyOwner(
|
|
PLBIV plb,
|
|
INT sEvt)
|
|
{
|
|
TL tlpwndParent;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
ThreadLock(plb->spwndParent, &tlpwndParent);
|
|
SendMessage(HW(plb->spwndParent), WM_COMMAND,
|
|
MAKELONG(PTR_TO_ID(plb->spwnd->spmenu), sEvt), (LPARAM)HWq(plb->spwnd));
|
|
ThreadUnlock(&tlpwndParent);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxSetISelBase
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxSetISelBase(
|
|
PLBIV plb,
|
|
INT sItem)
|
|
{
|
|
CheckLock(plb->spwnd);
|
|
|
|
xxxLBSetCaret(plb, FALSE);
|
|
plb->iSelBase = sItem;
|
|
xxxLBSetCaret(plb, TRUE);
|
|
|
|
xxxInsureVisible(plb, plb->iSelBase, FALSE);
|
|
|
|
/*
|
|
* We need to send this event even if the listbox isn't visible. See
|
|
* bug #88548. Also see 355612.
|
|
*/
|
|
if (_IsWindowVisible(plb->spwnd) || (GetFocus() == HWq(plb->spwnd))) {
|
|
LBEvent(plb, EVENT_OBJECT_FOCUS, sItem);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxTrackMouse
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxTrackMouse(
|
|
PLBIV plb,
|
|
UINT wMsg,
|
|
POINT pt)
|
|
{
|
|
INT iSelFromPt;
|
|
INT iSelTemp;
|
|
BOOL mousetemp;
|
|
BOOL fMouseInRect;
|
|
RECT rcClient;
|
|
UINT wModifiers = 0;
|
|
BOOL fSelected;
|
|
UINT uEvent = 0;
|
|
INT trackPtRetn;
|
|
HWND hwnd = HWq(plb->spwnd);
|
|
TL tlpwndEdit;
|
|
TL tlpwndParent;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
/*
|
|
* Optimization: do nothing if mouse not captured
|
|
*/
|
|
if ((wMsg != WM_LBUTTONDOWN) && (wMsg != WM_LBUTTONDBLCLK)) {
|
|
if (!plb->fCaptured) {
|
|
return;
|
|
}
|
|
/*
|
|
* If we are processing a WM_MOUSEMOVE but the mouse has not moved
|
|
* from the previous point, then we may be dealing with a mouse
|
|
* "jiggle" sent from the kernel (see zzzInvalidateDCCache). If we
|
|
* process this, we will snap the listbox selection back to where
|
|
* the mouse cursor is pointing, even if the user has not touched
|
|
* the mouse. Windows NT Bug #220722.
|
|
*
|
|
* Some apps (like MSMoney98) rely on this, so we need to check the
|
|
* SRVIF_LASTRITWASKEYBOARD flag. Windows NT Bug #244450.
|
|
*/
|
|
if (wMsg == WM_MOUSEMOVE && RtlEqualMemory(&pt, &(plb->ptPrev), sizeof(POINT))
|
|
&& TEST_SRVIF(SRVIF_LASTRITWASKEYBOARD)) {
|
|
RIPMSG0(RIP_WARNING, "xxxTrackMouse ignoring WM_MOUSEMOVE with no mouse movement");
|
|
return;
|
|
}
|
|
}
|
|
|
|
mousetemp = ISelFromPt(plb, pt, &iSelFromPt);
|
|
|
|
/*
|
|
* If we allow the user to cancel his selection then fMouseInRect is true if
|
|
* the mouse is in the listbox client area otherwise it is false. If we
|
|
* don't allow the user to cancel his selection, then fMouseInRect will
|
|
* always be true. This allows us to implement cancelable selection
|
|
* listboxes ie. The selection reverts to the origional one if the user
|
|
* releases the mouse outside of the listbox.
|
|
*/
|
|
fMouseInRect = !mousetemp || !plb->pcbox;
|
|
|
|
_GetClientRect(plb->spwnd, &rcClient);
|
|
|
|
switch (wMsg) {
|
|
case WM_LBUTTONDBLCLK:
|
|
case WM_LBUTTONDOWN:
|
|
/*
|
|
* We want to divert mouse clicks. If the user clicks outside
|
|
* of a dropped down listbox, we want to popup it up, using
|
|
* the current selection.
|
|
*/
|
|
if (plb->fCaptured) {
|
|
/*
|
|
* If plb->pcbox is NULL, this is a listbox that
|
|
* received a WM_LBUTTONDOWN again w/o receiving
|
|
* a WM_LBUTTONUP for the previous WM_LBUTTONDOWN
|
|
* bug
|
|
*/
|
|
if (plb->pcbox && mousetemp) {
|
|
_ClientToScreen(plb->spwnd, &pt);
|
|
|
|
if (!PtInRect(KPRECT_TO_PRECT(&plb->spwnd->rcWindow), pt)) {
|
|
/*
|
|
* Cancel selection if clicked outside of combo;
|
|
* Accept if clicked on combo button or item.
|
|
*/
|
|
xxxCBHideListBoxWindow(plb->pcbox, TRUE, FALSE);
|
|
} else if (!PtInRect(KPRECT_TO_PRECT(&plb->spwnd->rcClient), pt)) {
|
|
/*
|
|
* Let it pass through. Save, restore capture in
|
|
* case user is clicking on scrollbar.
|
|
*/
|
|
plb->fCaptured = FALSE;
|
|
NtUserReleaseCapture();
|
|
|
|
SendMessageWorker(plb->spwnd, WM_NCLBUTTONDOWN,
|
|
FindNCHit(plb->spwnd, POINTTOPOINTS(pt)),
|
|
MAKELONG(pt.x, pt.y), FALSE);
|
|
|
|
NtUserSetCapture(hwnd);
|
|
plb->fCaptured = TRUE;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
plb->fCaptured = FALSE;
|
|
NtUserReleaseCapture();
|
|
}
|
|
|
|
if (plb->pcbox) {
|
|
|
|
/*
|
|
* If this listbox is in a combo box, set the focus to the combo
|
|
* box window so that the edit control/static text is also
|
|
* activated
|
|
*/
|
|
ThreadLock(plb->pcbox->spwndEdit, &tlpwndEdit);
|
|
NtUserSetFocus(HWq(plb->pcbox->spwndEdit));
|
|
ThreadUnlock(&tlpwndEdit);
|
|
} else {
|
|
|
|
/*
|
|
* Get the focus if the listbox is clicked in and we don't
|
|
* already have the focus. If we don't have the focus after
|
|
* this, run away...
|
|
*/
|
|
NtUserSetFocus(hwnd);
|
|
if (!plb->fCaret)
|
|
return;
|
|
}
|
|
|
|
if (plb->fAddSelMode) {
|
|
|
|
/*
|
|
* If it is in "Add" mode, quit it using shift f8 key...
|
|
* However, since we can't send shift key state, we have to turn
|
|
* this off directly...
|
|
*/
|
|
|
|
/*
|
|
*SendMessage(HW(plb->spwnd),WM_KEYDOWN, (UINT)VK_F8, 0L);
|
|
*/
|
|
|
|
/*
|
|
* Switch off the Caret blinking
|
|
*/
|
|
NtUserKillTimer(hwnd, IDSYS_CARET);
|
|
|
|
/*
|
|
* Make sure the caret does not vanish
|
|
*/
|
|
xxxLBSetCaret(plb, TRUE);
|
|
plb->fAddSelMode = FALSE;
|
|
}
|
|
|
|
if (!plb->cMac) {
|
|
|
|
/*
|
|
* Don't even bother handling the mouse if no items in the
|
|
* listbox since the code below assumes >0 items in the
|
|
* listbox. We will just get the focus (the statement above) if
|
|
* we don't already have it.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
if (mousetemp) {
|
|
|
|
/*
|
|
* Mouse down occurred in a empty spot. Just ignore it.
|
|
*/
|
|
break;
|
|
}
|
|
|
|
plb->fDoubleClick = (wMsg == WM_LBUTTONDBLCLK);
|
|
|
|
if (!plb->fDoubleClick) {
|
|
|
|
/*
|
|
* This hack put in for the shell. Tell the shell where in the
|
|
* listbox the user clicked and at what item number. The shell
|
|
* can return 0 to continue normal mouse tracking or TRUE to
|
|
* abort mouse tracking.
|
|
*/
|
|
ThreadLock(plb->spwndParent, &tlpwndParent);
|
|
trackPtRetn = (INT)SendMessage(HW(plb->spwndParent), WM_LBTRACKPOINT,
|
|
(DWORD)iSelFromPt, MAKELONG(pt.x+plb->xOrigin, pt.y));
|
|
ThreadUnlock(&tlpwndParent);
|
|
if (trackPtRetn) {
|
|
if (trackPtRetn == 2) {
|
|
|
|
/*
|
|
* Ignore double clicks
|
|
*/
|
|
NtUserCallNoParam(SFI__RESETDBLCLK);
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
|
|
if (plb->pcbox) {
|
|
|
|
/*
|
|
* Save the last selection if this is a combo box. So that it
|
|
* can be restored if user decides to cancel the selection by up
|
|
* clicking outside the listbox.
|
|
*/
|
|
plb->iLastSelection = plb->iSel;
|
|
}
|
|
|
|
/*
|
|
* Save for timer
|
|
*/
|
|
plb->ptPrev = pt;
|
|
|
|
plb->fMouseDown = TRUE;
|
|
NtUserSetCapture(hwnd);
|
|
plb->fCaptured = TRUE;
|
|
|
|
if (plb->fDoubleClick) {
|
|
|
|
/*
|
|
* Double click. Fake a button up and exit
|
|
*/
|
|
xxxTrackMouse(plb, WM_LBUTTONUP, pt);
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Set the system timer so that we can autoscroll if the mouse is
|
|
* outside the bounds of the listbox rectangle
|
|
*/
|
|
NtUserSetTimer(hwnd, IDSYS_SCROLL, gpsi->dtScroll, NULL);
|
|
|
|
|
|
|
|
/*
|
|
* If extended multiselection listbox, are any modifier key pressed?
|
|
*/
|
|
if (plb->wMultiple == EXTENDEDSEL) {
|
|
if (GetKeyState(VK_SHIFT) < 0)
|
|
wModifiers = SHIFTDOWN;
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
wModifiers += CTLDOWN;
|
|
|
|
/*
|
|
* Please Note that (SHIFTDOWN + CTLDOWN) == (SHCTLDOWN)
|
|
*/
|
|
}
|
|
|
|
|
|
switch (wModifiers) {
|
|
case NOMODIFIER:
|
|
MouseMoveHandler:
|
|
if (plb->iSelBase != iSelFromPt) {
|
|
xxxLBSetCaret(plb, FALSE);
|
|
}
|
|
|
|
/*
|
|
* We only look at the mouse if the point it is pointing to is
|
|
* not selected. Since we are not in ExtendedSelMode, anywhere
|
|
* the mouse points, we have to set the selection to that item.
|
|
* Hence, if the item isn't selected, it means the mouse never
|
|
* pointed to it before so we can select it. We ignore already
|
|
* selected items so that we avoid flashing the inverted
|
|
* selection rectangle. Also, we could get WM_SYSTIMER simulated
|
|
* mouse moves which would cause flashing otherwise...
|
|
*/
|
|
|
|
iSelTemp = (fMouseInRect ? iSelFromPt : -1);
|
|
|
|
/*
|
|
* If the LB is either SingleSel or Extended multisel, clear all
|
|
* old selections except the new one being made.
|
|
*/
|
|
if (plb->wMultiple != MULTIPLESEL) {
|
|
xxxResetWorld(plb, iSelTemp, iSelTemp, FALSE);
|
|
/*
|
|
* This will be TRUE if iSelTemp isn't -1 (like below)
|
|
* and also if it is but there is a current selection.
|
|
*/
|
|
if ((iSelTemp == -1) && (plb->iSel != -1)) {
|
|
uEvent = EVENT_OBJECT_SELECTIONREMOVE;
|
|
}
|
|
}
|
|
|
|
fSelected = IsSelected(plb, iSelTemp, HILITEONLY);
|
|
if (iSelTemp != -1) {
|
|
|
|
/*
|
|
* If it is MULTIPLESEL, then toggle; For others, only if
|
|
* not selected already, select it.
|
|
*/
|
|
if (((plb->wMultiple == MULTIPLESEL) && (wMsg != WM_LBUTTONDBLCLK)) || !fSelected) {
|
|
SetSelected(plb, iSelTemp, !fSelected, HILITEANDSEL);
|
|
|
|
/*
|
|
* And invert it
|
|
*/
|
|
xxxInvertLBItem(plb, iSelTemp, !fSelected);
|
|
fSelected = !fSelected; /* Set the new state */
|
|
if (plb->wMultiple == MULTIPLESEL) {
|
|
uEvent = (fSelected ? EVENT_OBJECT_SELECTIONADD :
|
|
EVENT_OBJECT_SELECTIONREMOVE);
|
|
} else {
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We have to set iSel in case this is a multisel lb.
|
|
*/
|
|
plb->iSel = iSelTemp;
|
|
|
|
/*
|
|
* Set the new anchor point
|
|
*/
|
|
plb->iMouseDown = iSelFromPt;
|
|
plb->iLastMouseMove = iSelFromPt;
|
|
plb->fNewItemState = fSelected;
|
|
|
|
break;
|
|
case SHIFTDOWN:
|
|
|
|
/*
|
|
* This is so that we can handle click and drag for multisel
|
|
* listboxes using Shift modifier key .
|
|
*/
|
|
plb->iLastMouseMove = plb->iSel = iSelFromPt;
|
|
|
|
|
|
|
|
/*
|
|
* Check if an anchor point already exists
|
|
*/
|
|
if (plb->iMouseDown == -1) {
|
|
plb->iMouseDown = iSelFromPt;
|
|
|
|
/*
|
|
* Reset all the previous selections
|
|
*/
|
|
xxxResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
|
|
|
|
/*
|
|
* Select the current position
|
|
*/
|
|
SetSelected(plb, plb->iMouseDown, TRUE, HILITEANDSEL);
|
|
xxxInvertLBItem(plb, plb->iMouseDown, TRUE);
|
|
/*
|
|
* We are changing the selction to this item only
|
|
*/
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
} else {
|
|
|
|
/*
|
|
* Reset all the previous selections
|
|
*/
|
|
xxxResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
|
|
|
|
/*
|
|
* Select all items from anchor point upto current click pt
|
|
*/
|
|
xxxAlterHilite(plb, plb->iMouseDown, iSelFromPt, HILITE, HILITEONLY, FALSE);
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
}
|
|
plb->fNewItemState = (UINT)TRUE;
|
|
break;
|
|
|
|
case CTLDOWN:
|
|
|
|
/*
|
|
* This is so that we can handle click and drag for multisel
|
|
* listboxes using Control modifier key.
|
|
*/
|
|
|
|
/*
|
|
* Reset the anchor point to the current point
|
|
*/
|
|
plb->iMouseDown = plb->iLastMouseMove = plb->iSel = iSelFromPt;
|
|
|
|
/*
|
|
* The state we will be setting items to
|
|
*/
|
|
plb->fNewItemState = (UINT)!IsSelected(plb, iSelFromPt, (UINT)HILITEONLY);
|
|
|
|
/*
|
|
* Toggle the current point
|
|
*/
|
|
SetSelected(plb, iSelFromPt, plb->fNewItemState, HILITEANDSEL);
|
|
xxxInvertLBItem(plb, iSelFromPt, plb->fNewItemState);
|
|
uEvent = (plb->fNewItemState ? EVENT_OBJECT_SELECTIONADD :
|
|
EVENT_OBJECT_SELECTIONREMOVE);
|
|
break;
|
|
|
|
case SHCTLDOWN:
|
|
|
|
/*
|
|
* This is so that we can handle click and drag for multisel
|
|
* listboxes using Shift and Control modifier keys.
|
|
*/
|
|
|
|
/*
|
|
* Preserve all the previous selections
|
|
*/
|
|
|
|
/*
|
|
* Deselect only the selection connected with the last
|
|
* anchor point; If the last anchor point is associated with a
|
|
* de-selection, then do not do it
|
|
*/
|
|
if (plb->fNewItemState) {
|
|
xxxAlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE, HILITEANDSEL, FALSE);
|
|
}
|
|
plb->iLastMouseMove = plb->iSel = iSelFromPt;
|
|
|
|
/*
|
|
* Check if an anchor point already exists
|
|
*/
|
|
if (plb->iMouseDown == -1) {
|
|
|
|
/*
|
|
* No existing anchor point; Make the current pt as anchor
|
|
*/
|
|
plb->iMouseDown = iSelFromPt;
|
|
}
|
|
|
|
/*
|
|
* If one exists preserve the most recent anchor point
|
|
*/
|
|
|
|
/*
|
|
* The state we will be setting items to
|
|
*/
|
|
plb->fNewItemState = (UINT)IsSelected(plb, plb->iMouseDown, HILITEONLY);
|
|
|
|
/*
|
|
* Select all items from anchor point upto current click pt
|
|
*/
|
|
xxxAlterHilite(plb, plb->iMouseDown, iSelFromPt, plb->fNewItemState, HILITEONLY, FALSE);
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
break;
|
|
}
|
|
|
|
/*
|
|
* Set the new base point (the outline frame caret). We do the check
|
|
* first to avoid flashing the caret unnecessarly.
|
|
*/
|
|
if (plb->iSelBase != iSelFromPt) {
|
|
|
|
/*
|
|
* Since xxxSetISelBase always turns on the caret, we don't need to
|
|
* do it here...
|
|
*/
|
|
xxxSetISelBase(plb, iSelFromPt);
|
|
}
|
|
|
|
/*
|
|
* SetISelBase will change the focus and send a focus event.
|
|
* Then we send the selection event.
|
|
*/
|
|
if (uEvent) {
|
|
LBEvent(plb, uEvent, iSelFromPt);
|
|
}
|
|
if (wMsg == WM_LBUTTONDOWN && TestWF(plb->spwnd, WEFDRAGOBJECT)) {
|
|
if (NtUserDragDetect(hwnd, pt)) {
|
|
|
|
/*
|
|
* User is trying to drag object...
|
|
*/
|
|
|
|
/*
|
|
* Fake an up click so that the item is selected...
|
|
*/
|
|
xxxTrackMouse(plb, WM_LBUTTONUP, pt);
|
|
|
|
/*
|
|
* Notify parent
|
|
* #ifndef WIN16 (32-bit Windows), plb->iSelBase gets
|
|
* zero-extended to LONG wParam automatically by the compiler.
|
|
*/
|
|
ThreadLock(plb->spwndParent, &tlpwndParent);
|
|
SendMessage(HW(plb->spwndParent), WM_BEGINDRAG, plb->iSelBase,
|
|
(LPARAM)hwnd);
|
|
ThreadUnlock(&tlpwndParent);
|
|
} else {
|
|
xxxTrackMouse(plb, WM_LBUTTONUP, pt);
|
|
}
|
|
return;
|
|
}
|
|
break;
|
|
|
|
case WM_MOUSEMOVE: {
|
|
int dist;
|
|
int iTimer;
|
|
|
|
/*
|
|
* Save for timer.
|
|
*/
|
|
plb->ptPrev = pt;
|
|
/*
|
|
* Autoscroll listbox if mouse button is held down and mouse is
|
|
* moved outside of the listbox
|
|
*/
|
|
if (plb->fMouseDown) {
|
|
if (plb->fMultiColumn) {
|
|
if ((pt.x < 0) || (pt.x >= rcClient.right - 1)) {
|
|
/*
|
|
* Reset timer interval based on distance from listbox.
|
|
* use a longer default interval because each multicolumn
|
|
* scrolling increment is larger
|
|
*/
|
|
dist = pt.x < 0 ? -pt.x : (pt.x - rcClient.right + 1);
|
|
iTimer = ((gpsi->dtScroll * 3) / 2) - ((WORD) dist << 4);
|
|
|
|
if (plb->fRightAlign)
|
|
xxxLBoxCtlHScrollMultiColumn(plb, (pt.x < 0 ? SB_LINEDOWN : SB_LINEUP), 0);
|
|
else
|
|
xxxLBoxCtlHScrollMultiColumn(plb, (pt.x < 0 ? SB_LINEUP : SB_LINEDOWN), 0);
|
|
|
|
goto SetTimerAndSel;
|
|
}
|
|
} else if ((pt.y < 0) || (pt.y >= rcClient.bottom - 1)) {
|
|
/*
|
|
* Reset timer interval based on distance from listbox.
|
|
*/
|
|
dist = pt.y < 0 ? -pt.y : (pt.y - rcClient.bottom + 1);
|
|
iTimer = gpsi->dtScroll - ((WORD) dist << 4);
|
|
|
|
xxxLBoxCtlScroll(plb, (pt.y < 0 ? SB_LINEUP : SB_LINEDOWN), 0);
|
|
SetTimerAndSel:
|
|
NtUserSetTimer(hwnd, IDSYS_SCROLL, max(iTimer, 1), NULL);
|
|
ISelFromPt(plb, pt, &iSelFromPt);
|
|
}
|
|
} else {
|
|
/*
|
|
* Ignore if not in client since we don't autoscroll
|
|
*/
|
|
if (!PtInRect(&rcClient, pt))
|
|
break;
|
|
}
|
|
|
|
switch (plb->wMultiple) {
|
|
case SINGLESEL:
|
|
|
|
/*
|
|
* If it is a single selection or plain multisel list box
|
|
*/
|
|
goto MouseMoveHandler;
|
|
break;
|
|
|
|
case MULTIPLESEL:
|
|
case EXTENDEDSEL:
|
|
|
|
/*
|
|
* Handle mouse movement with extended selection of items
|
|
*/
|
|
if (plb->iSelBase != iSelFromPt) {
|
|
xxxSetISelBase(plb, iSelFromPt);
|
|
|
|
/*
|
|
* If this is an extended Multi sel list box, then
|
|
* adjust the display of the range due to the mouse move
|
|
*/
|
|
if (plb->wMultiple == EXTENDEDSEL) {
|
|
xxxLBBlockHilite(plb, iSelFromPt, FALSE);
|
|
LBEvent(plb, EVENT_OBJECT_SELECTIONWITHIN, iSelFromPt);
|
|
}
|
|
plb->iLastMouseMove = iSelFromPt;
|
|
}
|
|
break;
|
|
}
|
|
break;
|
|
}
|
|
case WM_LBUTTONUP:
|
|
if (plb->fMouseDown)
|
|
xxxLBButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY |
|
|
(mousetemp ? LBUP_RESETSELECTION : 0) |
|
|
(fMouseInRect ? LBUP_SUCCESS : 0));
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
*
|
|
* LBButtonUp()
|
|
*
|
|
* Called in response to both WM_CAPTURECHANGED and WM_LBUTTONUP.
|
|
*
|
|
\***************************************************************************/
|
|
void xxxLBButtonUp(PLBIV plb, UINT uFlags)
|
|
{
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
/*
|
|
* If the list box is an Extended listbox, then change the select status
|
|
* of all items between the anchor and the last mouse position to the
|
|
* newItemState
|
|
*/
|
|
if (plb->wMultiple == EXTENDEDSEL)
|
|
xxxAlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove,
|
|
plb->fNewItemState, SELONLY, FALSE);
|
|
|
|
/*
|
|
* This is a combo box and user upclicked outside the listbox
|
|
* so we want to restore the original selection.
|
|
*/
|
|
if (plb->pcbox && (uFlags & LBUP_RESETSELECTION)) {
|
|
int iSelOld;
|
|
|
|
iSelOld = plb->iSel;
|
|
|
|
if (iSelOld >= 0)
|
|
xxxInvertLBItem(plb, plb->iSel, FALSE);
|
|
|
|
plb->iSel = plb->iLastSelection;
|
|
xxxInvertLBItem(plb, plb->iSel, TRUE);
|
|
|
|
/*
|
|
* Note that we always send selection events before we tell the
|
|
* app. This is on purpose--the app may turn around and select
|
|
* something else when notified. In which case our event would
|
|
* be out of order.
|
|
*/
|
|
LBEvent(plb, EVENT_OBJECT_SELECTION, plb->iSel);
|
|
|
|
/*
|
|
* On win-95 and NT4 the check used to be !(uFlags & LBUP_NOTIFY) which
|
|
* is a bug because we would notify even when the lb is not LBUP_NOTIFY
|
|
*/
|
|
if ((uFlags & LBUP_NOTIFY) && plb->fNotify && (iSelOld != plb->iSel))
|
|
xxxNotifyOwner(plb, LBN_SELCHANGE);
|
|
}
|
|
|
|
NtUserKillTimer(HWq(plb->spwnd), IDSYS_SCROLL);
|
|
plb->fMouseDown = FALSE;
|
|
if (plb->fCaptured) {
|
|
plb->fCaptured = FALSE;
|
|
if (uFlags & LBUP_RELEASECAPTURE)
|
|
NtUserReleaseCapture();
|
|
}
|
|
/*
|
|
* Don't scroll item as long as any part of it is visible
|
|
*/
|
|
if (plb->iSelBase < plb->iTop ||
|
|
plb->iSelBase > plb->iTop + CItemInWindow(plb, TRUE))
|
|
xxxInsureVisible(plb, plb->iSelBase, FALSE);
|
|
|
|
if (plb->fNotify) {
|
|
if (uFlags & LBUP_NOTIFY) {
|
|
if (uFlags & LBUP_SUCCESS) {
|
|
/*
|
|
* ArtMaster needs this SELCHANGE notification now!
|
|
*/
|
|
if ((plb->fDoubleClick) && !TestWF(plb->spwnd, WFWIN31COMPAT))
|
|
xxxNotifyOwner(plb, LBN_SELCHANGE);
|
|
|
|
/*
|
|
* Notify owner of click or double click on selection
|
|
*/
|
|
xxxNotifyOwner(plb, (plb->fDoubleClick) ? LBN_DBLCLK : LBN_SELCHANGE);
|
|
} else {
|
|
/*
|
|
* Notify owner that the attempted selection was cancelled.
|
|
*/
|
|
xxxNotifyOwner(plb, LBN_SELCANCEL);
|
|
}
|
|
} else if (uFlags & LBUP_SELCHANGE) {
|
|
/*
|
|
* Did we do some semi-selecting with mouse moves, then hit Enter?
|
|
* If so, we need to make sure the app knows that something was
|
|
* really truly selected.
|
|
*/
|
|
UserAssert(TestWF(plb->spwnd, WFWIN40COMPAT));
|
|
if (plb->iLastSelection != plb->iSel)
|
|
xxxNotifyOwner(plb, LBN_SELCHANGE);
|
|
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* IncrementISel
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
INT IncrementISel(
|
|
PLBIV plb,
|
|
INT iSel,
|
|
INT sInc)
|
|
{
|
|
|
|
/*
|
|
* Assumes cMac > 0, return iSel+sInc in range [0..cmac).
|
|
*/
|
|
iSel += sInc;
|
|
if (iSel < 0) {
|
|
return 0;
|
|
} else if (iSel >= plb->cMac) {
|
|
return plb->cMac - 1;
|
|
}
|
|
return iSel;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* NewITop
|
|
*
|
|
\***************************************************************************/
|
|
|
|
void xxxNewITop(PLBIV plb, INT iTopNew)
|
|
{
|
|
xxxNewITopEx(plb, iTopNew, 0);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxNewITopEx
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxNewITopEx(
|
|
PLBIV plb,
|
|
INT iTopNew,
|
|
DWORD dwTime)
|
|
{
|
|
int iTopOld;
|
|
BOOL fCaretOn;
|
|
BOOL fMulti = plb->fMultiColumn;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
// Always try to turn off caret whether or not redraw is on
|
|
if (fCaretOn = plb->fCaretOn)
|
|
xxxLBSetCaret(plb, FALSE);
|
|
|
|
iTopOld = (fMulti) ? (plb->iTop / plb->itemsPerColumn) : plb->iTop;
|
|
plb->iTop = iTopNew;
|
|
iTopNew = xxxSetLBScrollParms(plb, (fMulti) ? SB_HORZ : SB_VERT);
|
|
plb->iTop = (fMulti) ? (iTopNew * plb->itemsPerColumn) : iTopNew;
|
|
|
|
if (!IsLBoxVisible(plb)) {
|
|
return;
|
|
}
|
|
|
|
if (iTopNew != iTopOld) {
|
|
int xAmt, yAmt;
|
|
RECT rc;
|
|
DWORD dwFlags;
|
|
|
|
_GetClientRect(plb->spwnd, &rc);
|
|
|
|
if (fMulti) {
|
|
yAmt = 0;
|
|
if (abs(iTopNew - iTopOld) > plb->numberOfColumns)
|
|
// Handle scrolling a large number of columns properly so that
|
|
// we don't overflow the size of a rect.
|
|
xAmt = 32000;
|
|
else {
|
|
xAmt = (iTopOld - iTopNew) * plb->cxColumn;
|
|
if (plb->fRightAlign)
|
|
xAmt = -xAmt;
|
|
}
|
|
} else {
|
|
xAmt = 0;
|
|
if (plb->OwnerDraw == OWNERDRAWVAR) {
|
|
//
|
|
// Have to fake iTopOld for OWNERDRAWVAR listboxes so that
|
|
// the scrolling amount calculations work properly.
|
|
//
|
|
plb->iTop = iTopOld;
|
|
yAmt = LBCalcVarITopScrollAmt(plb, iTopOld, iTopNew);
|
|
plb->iTop = iTopNew;
|
|
} else if (abs(iTopNew - iTopOld) > plb->cItemFullMax)
|
|
yAmt = 32000;
|
|
else
|
|
yAmt = (iTopOld - iTopNew) * plb->cyChar;
|
|
}
|
|
|
|
dwFlags = LBGetScrollFlags(plb, dwTime);
|
|
ScrollWindowEx(HWq(plb->spwnd), xAmt, yAmt, NULL, &rc, NULL,
|
|
NULL, dwFlags);
|
|
UpdateWindow(HWq(plb->spwnd));
|
|
}
|
|
|
|
// Note that although we turn off the caret regardless of redraw, we
|
|
// only turn it on if redraw is true. Slimy thing to fixup many
|
|
// caret related bugs...
|
|
if (fCaretOn)
|
|
// Turn the caret back on only if we turned it off. This avoids
|
|
// annoying caret flicker.
|
|
xxxLBSetCaret(plb, TRUE);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxInsureVisible
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxInsureVisible(
|
|
PLBIV plb,
|
|
INT iSel,
|
|
BOOL fPartial) /* It is ok for the item to be partially visible */
|
|
{
|
|
INT sLastVisibleItem;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (iSel < plb->iTop) {
|
|
xxxNewITop(plb, iSel);
|
|
} else {
|
|
if (fPartial) {
|
|
|
|
/*
|
|
* 1 must be subtracted to get the last visible item
|
|
* A part of the fix for Bug #3727 -- 01/14/91 -- SANKAR
|
|
*/
|
|
sLastVisibleItem = plb->iTop + CItemInWindow(plb, TRUE) - (INT)1;
|
|
} else {
|
|
sLastVisibleItem = LastFullVisible(plb);
|
|
}
|
|
|
|
if (plb->OwnerDraw != OWNERDRAWVAR) {
|
|
if (iSel > sLastVisibleItem) {
|
|
if (plb->fMultiColumn) {
|
|
xxxNewITop(plb,
|
|
((iSel / plb->itemsPerColumn) -
|
|
max(plb->numberOfColumns-1,0)) * plb->itemsPerColumn);
|
|
} else {
|
|
xxxNewITop(plb, (INT)max(0, iSel - sLastVisibleItem + plb->iTop));
|
|
}
|
|
}
|
|
} else if (iSel > sLastVisibleItem)
|
|
xxxNewITop(plb, LBPage(plb, iSel, FALSE));
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxLBoxCaretBlinker
|
|
*
|
|
* Timer callback function toggles Caret
|
|
* Since it is a callback, it is APIENTRY
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
VOID xxxLBoxCaretBlinker(
|
|
HWND hwnd,
|
|
UINT wMsg,
|
|
UINT_PTR nIDEvent,
|
|
DWORD dwTime)
|
|
{
|
|
PWND pwnd;
|
|
PLBIV plb;
|
|
|
|
/*
|
|
* Standard parameters for a timer callback function that aren't used.
|
|
* Mentioned here to avoid compiler warnings
|
|
*/
|
|
UNREFERENCED_PARAMETER(wMsg);
|
|
UNREFERENCED_PARAMETER(nIDEvent);
|
|
UNREFERENCED_PARAMETER(dwTime);
|
|
|
|
pwnd = ValidateHwnd(hwnd);
|
|
plb = ((PLBWND)pwnd)->pLBIV;
|
|
|
|
/*
|
|
* leave caret on, don't blink it off (prevents rapid blinks?)
|
|
*/
|
|
if (ISREMOTESESSION() && plb->fCaretOn) {
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Check if the Caret is ON, if so, switch it OFF
|
|
*/
|
|
xxxLBSetCaret(plb, !plb->fCaretOn);
|
|
return;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLBoxCtlKeyInput
|
|
*
|
|
* If msg == LB_KEYDOWN, vKey is the number of the item to go to,
|
|
* otherwise it is the virtual key.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxLBoxCtlKeyInput(
|
|
PLBIV plb,
|
|
UINT msg,
|
|
UINT vKey)
|
|
{
|
|
INT i;
|
|
INT iNewISel;
|
|
INT cItemPageScroll;
|
|
PCBOX pcbox;
|
|
BOOL fDropDownComboBox;
|
|
BOOL fExtendedUIComboBoxClosed;
|
|
BOOL hScrollBar = TestWF(plb->spwnd, WFHSCROLL);
|
|
UINT wModifiers = 0;
|
|
BOOL fSelectKey = FALSE; /* assume it is a navigation key */
|
|
UINT uEvent = 0;
|
|
HWND hwnd = HWq(plb->spwnd);
|
|
TL tlpwndParent;
|
|
TL tlpwnd;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
pcbox = plb->pcbox;
|
|
|
|
/*
|
|
* Is this a dropdown style combo box/listbox ?
|
|
*/
|
|
fDropDownComboBox = pcbox && (pcbox->CBoxStyle & SDROPPABLE);
|
|
|
|
/*
|
|
*Is this an extended ui combo box which is closed?
|
|
*/
|
|
fExtendedUIComboBoxClosed = fDropDownComboBox && pcbox->fExtendedUI &&
|
|
!pcbox->fLBoxVisible;
|
|
|
|
if (plb->fMouseDown || (!plb->cMac && vKey != VK_F4)) {
|
|
|
|
/*
|
|
* Ignore keyboard input if we are in the middle of a mouse down deal or
|
|
* if there are no items in the listbox. Note that we let F4's go
|
|
* through for combo boxes so that the use can pop up and down empty
|
|
* combo boxes.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Modifiers are considered only in EXTENDED sel list boxes.
|
|
*/
|
|
if (plb->wMultiple == EXTENDEDSEL) {
|
|
|
|
/*
|
|
* If multiselection listbox, are any modifiers used ?
|
|
*/
|
|
if (GetKeyState(VK_SHIFT) < 0)
|
|
wModifiers = SHIFTDOWN;
|
|
if (GetKeyState(VK_CONTROL) < 0)
|
|
wModifiers += CTLDOWN;
|
|
|
|
/*
|
|
* Please Note that (SHIFTDOWN + CTLDOWN) == (SHCTLDOWN)
|
|
*/
|
|
}
|
|
|
|
if (msg == LB_KEYDOWN) {
|
|
|
|
/*
|
|
* This is a listbox "go to specified item" message which means we want
|
|
* to go to a particular item number (given by vKey) directly. ie. the
|
|
* user has typed a character and we want to go to the item which
|
|
* starts with that character.
|
|
*/
|
|
iNewISel = (INT)vKey;
|
|
goto TrackKeyDown;
|
|
}
|
|
|
|
cItemPageScroll = plb->cItemFullMax;
|
|
|
|
if (cItemPageScroll > 1)
|
|
cItemPageScroll--;
|
|
|
|
if (plb->fWantKeyboardInput) {
|
|
|
|
/*
|
|
* Note: msg must not be LB_KEYDOWN here or we'll be in trouble...
|
|
*/
|
|
ThreadLock(plb->spwndParent, &tlpwndParent);
|
|
iNewISel = (INT)SendMessage(HW(plb->spwndParent), WM_VKEYTOITEM,
|
|
MAKELONG(vKey, plb->iSelBase), (LPARAM)hwnd);
|
|
ThreadUnlock(&tlpwndParent);
|
|
|
|
if (iNewISel == -2) {
|
|
|
|
/*
|
|
* Don't move the selection...
|
|
*/
|
|
return;
|
|
}
|
|
if (iNewISel != -1) {
|
|
|
|
/*
|
|
* Jump directly to the item provided by the app
|
|
*/
|
|
goto TrackKeyDown;
|
|
}
|
|
|
|
/*
|
|
* else do default processing of the character.
|
|
*/
|
|
}
|
|
|
|
switch (vKey) {
|
|
// LATER IanJa: not language independent!!!
|
|
// We could use VkKeyScan() to find out which is the '\' key
|
|
// This is VK_OEM_5 '\|' for US English only.
|
|
// Germans, Italians etc. have to type CTRL+^ (etc) for this.
|
|
// This is documented as File Manager behaviour for 3.0, but apparently
|
|
// not for 3.1., although functionality remains. We should still fix it,
|
|
// although German (etc?) '\' is generated with AltGr (Ctrl-Alt) (???)
|
|
case VERKEY_BACKSLASH: /* '\' character for US English */
|
|
|
|
/*
|
|
* Check if this is CONTROL-\ ; If so Deselect all items
|
|
*/
|
|
if ((wModifiers & CTLDOWN) && (plb->wMultiple != SINGLESEL)) {
|
|
xxxLBSetCaret(plb, FALSE);
|
|
xxxResetWorld(plb, plb->iSelBase, plb->iSelBase, FALSE);
|
|
|
|
/*
|
|
* And select the current item
|
|
*/
|
|
SetSelected(plb, plb->iSelBase, TRUE, HILITEANDSEL);
|
|
xxxInvertLBItem(plb, plb->iSelBase, TRUE);
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
goto CaretOnAndNotify;
|
|
}
|
|
return;
|
|
break;
|
|
|
|
case VK_DIVIDE: /* NumPad '/' character on enhanced keyboard */
|
|
// LATER IanJa: not language independent!!!
|
|
// We could use VkKeyScan() to find out which is the '/' key
|
|
// This is VK_OEM_2 '/?' for US English only.
|
|
// Germans, Italians etc. have to type CTRL+# (etc) for this.
|
|
case VERKEY_SLASH: /* '/' character */
|
|
|
|
/*
|
|
* Check if this is CONTROL-/ ; If so select all items
|
|
*/
|
|
if ((wModifiers & CTLDOWN) && (plb->wMultiple != SINGLESEL)) {
|
|
xxxLBSetCaret(plb, FALSE);
|
|
xxxResetWorld(plb, -1, -1, TRUE);
|
|
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
|
|
CaretOnAndNotify:
|
|
xxxLBSetCaret(plb, TRUE);
|
|
LBEvent(plb, uEvent, plb->iSelBase);
|
|
xxxNotifyOwner(plb, LBN_SELCHANGE);
|
|
}
|
|
return;
|
|
break;
|
|
|
|
case VK_F8:
|
|
|
|
/*
|
|
* The "Add" mode is possible only in Multiselection listboxes... Get
|
|
* into it via SHIFT-F8... (Yes, sometimes these UI people are sillier
|
|
* than your "typical dumb user"...)
|
|
*/
|
|
if (plb->wMultiple != SINGLESEL && wModifiers == SHIFTDOWN) {
|
|
|
|
/*
|
|
* We have to make the caret blink! Do something...
|
|
*/
|
|
if (plb->fAddSelMode) {
|
|
|
|
/*
|
|
* Switch off the Caret blinking
|
|
*/
|
|
NtUserKillTimer(hwnd, IDSYS_CARET);
|
|
|
|
/*
|
|
* Make sure the caret does not vanish
|
|
*/
|
|
xxxLBSetCaret(plb, TRUE);
|
|
} else {
|
|
|
|
/*
|
|
* Create a timer to make the caret blink
|
|
*/
|
|
NtUserSetTimer(hwnd, IDSYS_CARET, gpsi->dtCaretBlink,
|
|
xxxLBoxCaretBlinker);
|
|
}
|
|
|
|
/*
|
|
* Toggle the Add mode flag
|
|
*/
|
|
plb->fAddSelMode = (UINT)!plb->fAddSelMode;
|
|
}
|
|
return;
|
|
case VK_SPACE: /* Selection key is space */
|
|
i = 0;
|
|
fSelectKey = TRUE;
|
|
break;
|
|
|
|
case VK_PRIOR:
|
|
if (fExtendedUIComboBoxClosed) {
|
|
|
|
/*
|
|
* Disable movement keys for TandyT.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (plb->OwnerDraw == OWNERDRAWVAR) {
|
|
i = LBPage(plb, plb->iSelBase, FALSE) - plb->iSelBase;
|
|
} else {
|
|
i = -cItemPageScroll;
|
|
}
|
|
break;
|
|
|
|
case VK_NEXT:
|
|
if (fExtendedUIComboBoxClosed) {
|
|
|
|
/*
|
|
* Disable movement keys for TandyT.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
if (plb->OwnerDraw == OWNERDRAWVAR) {
|
|
i = LBPage(plb, plb->iSelBase, TRUE) - plb->iSelBase;
|
|
} else {
|
|
i = cItemPageScroll;
|
|
}
|
|
break;
|
|
|
|
case VK_HOME:
|
|
if (fExtendedUIComboBoxClosed) {
|
|
|
|
/*
|
|
* Disable movement keys for TandyT.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
i = (INT_MIN/2)+1; /* A very big negative number */
|
|
break;
|
|
|
|
case VK_END:
|
|
if (fExtendedUIComboBoxClosed) {
|
|
|
|
/*
|
|
* Disable movement keys for TandyT.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
i = (INT_MAX/2)-1; /* A very big positive number */
|
|
break;
|
|
|
|
case VK_LEFT:
|
|
if (plb->fMultiColumn) {
|
|
if (plb->fRightAlign ^ (!!TestWF(plb->spwnd, WEFLAYOUTRTL))) {
|
|
goto ReallyRight;
|
|
}
|
|
ReallyLeft:
|
|
if (plb->iSelBase / plb->itemsPerColumn == 0) {
|
|
i = 0;
|
|
} else {
|
|
i = -plb->itemsPerColumn;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (hScrollBar) {
|
|
goto HandleHScrolling;
|
|
} else {
|
|
|
|
/*
|
|
* Fall through and handle this as if the up arrow was pressed.
|
|
*/
|
|
|
|
vKey = VK_UP;
|
|
}
|
|
|
|
/*
|
|
* Fall through
|
|
*/
|
|
|
|
case VK_UP:
|
|
if (fExtendedUIComboBoxClosed)
|
|
// Disable movement keys for TandyT.
|
|
return;
|
|
|
|
i = -1;
|
|
break;
|
|
|
|
case VK_RIGHT:
|
|
if (plb->fMultiColumn) {
|
|
if (plb->fRightAlign ^ (!!TestWF(plb->spwnd, WEFLAYOUTRTL))) {
|
|
goto ReallyLeft;
|
|
}
|
|
ReallyRight:
|
|
if (plb->iSelBase / plb->itemsPerColumn == plb->cMac / plb->itemsPerColumn) {
|
|
i = 0;
|
|
} else {
|
|
i = plb->itemsPerColumn;
|
|
}
|
|
break;
|
|
}
|
|
if (hScrollBar) {
|
|
HandleHScrolling:
|
|
PostMessage(hwnd, WM_HSCROLL,
|
|
(vKey == VK_RIGHT ? SB_LINEDOWN : SB_LINEUP), 0L);
|
|
return;
|
|
} else {
|
|
|
|
/*
|
|
* Fall through and handle this as if the down arrow was
|
|
* pressed.
|
|
*/
|
|
vKey = VK_DOWN;
|
|
}
|
|
|
|
/*
|
|
* Fall through
|
|
*/
|
|
|
|
case VK_DOWN:
|
|
if (fExtendedUIComboBoxClosed) {
|
|
|
|
/*
|
|
* If the combo box is closed, down arrow should open it.
|
|
*/
|
|
if (!pcbox->fLBoxVisible) {
|
|
|
|
/*
|
|
* If the listbox isn't visible, just show it
|
|
*/
|
|
ThreadLock(pcbox->spwnd, &tlpwnd);
|
|
xxxCBShowListBoxWindow(pcbox, TRUE);
|
|
ThreadUnlock(&tlpwnd);
|
|
}
|
|
return;
|
|
}
|
|
i = 1;
|
|
break;
|
|
|
|
case VK_ESCAPE:
|
|
case VK_RETURN:
|
|
if (!fDropDownComboBox || !pcbox->fLBoxVisible)
|
|
return;
|
|
|
|
// | If this is a dropped listbox for a combobox and the ENTER |
|
|
// | key is pressed, close up the listbox, so FALLTHRU |
|
|
// V V
|
|
|
|
case VK_F4:
|
|
if (fDropDownComboBox && !pcbox->fExtendedUI) {
|
|
|
|
/*
|
|
* If we are a dropdown combo box/listbox we want to process
|
|
* this key. BUT for TandtT, we don't do anything on VK_F4 if we
|
|
* are in extended ui mode.
|
|
*/
|
|
ThreadLock(pcbox->spwnd, &tlpwnd);
|
|
if (!pcbox->fLBoxVisible) {
|
|
|
|
/*
|
|
* If the listbox isn't visible, just show it
|
|
*/
|
|
xxxCBShowListBoxWindow(pcbox, (vKey != VK_ESCAPE));
|
|
} else {
|
|
|
|
/*
|
|
* Ok, the listbox is visible. So hide the listbox window.
|
|
*/
|
|
xxxCBHideListBoxWindow(pcbox, TRUE, (vKey != VK_ESCAPE));
|
|
}
|
|
ThreadUnlock(&tlpwnd);
|
|
}
|
|
|
|
/*
|
|
* Fall through to the return
|
|
*/
|
|
|
|
default:
|
|
return;
|
|
}
|
|
|
|
/*
|
|
* Find out what the new selection should be
|
|
*/
|
|
iNewISel = IncrementISel(plb, plb->iSelBase, i);
|
|
|
|
|
|
if (plb->wMultiple == SINGLESEL) {
|
|
if (plb->iSel == iNewISel) {
|
|
|
|
/*
|
|
* If we are single selection and the keystroke is moving us to an
|
|
* item which is already selected, we don't have to do anything...
|
|
*/
|
|
return;
|
|
}
|
|
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
|
|
plb->iTypeSearch = 0;
|
|
if ((vKey == VK_UP || vKey == VK_DOWN) &&
|
|
!IsSelected(plb, plb->iSelBase, HILITEONLY)) {
|
|
|
|
/*
|
|
* If the caret is on an unselected item and the user just hits the
|
|
* up or down arrow key (ie. with no shift or ctrl modifications),
|
|
* then we will just select the item the cursor is at. This is
|
|
* needed for proper behavior in combo boxes but do we always want
|
|
* to run this code??? Note that this is only used in single
|
|
* selection list boxes since it doesn't make sense in the
|
|
* multiselection case. Note that an LB_KEYDOWN message must not be
|
|
* checked here because the vKey will be an item number not a
|
|
* VK_and we will goof. Thus, trackkeydown label is below this to
|
|
* fix a bug caused by it being above this...
|
|
*/
|
|
iNewISel = (plb->iSelBase == -1) ? 0 : plb->iSelBase;
|
|
}
|
|
}
|
|
|
|
TrackKeyDown:
|
|
|
|
xxxSetISelBase(plb, iNewISel);
|
|
|
|
xxxLBSetCaret(plb, FALSE);
|
|
|
|
if (wModifiers & SHIFTDOWN) {
|
|
// Check if iMouseDown is un-initialised
|
|
if (plb->iMouseDown == -1)
|
|
plb->iMouseDown = iNewISel;
|
|
if (plb->iLastMouseMove == -1)
|
|
plb->iLastMouseMove = iNewISel;
|
|
|
|
// Check if we are in ADD mode
|
|
if (plb->fAddSelMode) {
|
|
/* Preserve all the pre-existing selections except the
|
|
* ones connected with the last anchor point; If the last
|
|
* Preserve all the previous selections
|
|
*/
|
|
/* Deselect only the selection connected with the last
|
|
* anchor point; If the last anchor point is associated
|
|
* with de-selection, then do not do it
|
|
*/
|
|
|
|
if (!plb->fNewItemState)
|
|
plb->iLastMouseMove = plb->iMouseDown;
|
|
|
|
/* We haven't done anything here because, LBBlockHilite()
|
|
* will take care of wiping out the selection between
|
|
* Anchor point and iLastMouseMove and select the block
|
|
* between anchor point and current cursor location
|
|
*/
|
|
} else {
|
|
/* We are not in ADD mode */
|
|
/* Remove all selections except between the anchor point
|
|
* and last mouse move because it will be taken care of in
|
|
* LBBlockHilite
|
|
*/
|
|
xxxResetWorld(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE);
|
|
}
|
|
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
|
|
/* LBBlockHilite takes care to deselect the block between
|
|
* the anchor point and iLastMouseMove and select the block
|
|
* between the anchor point and the current cursor location
|
|
*/
|
|
/* Toggle all items to the same selection state as the item
|
|
* item at the anchor point) from the anchor point to the
|
|
* current cursor location.
|
|
*/
|
|
plb->fNewItemState = IsSelected(plb, plb->iMouseDown, SELONLY);
|
|
xxxLBBlockHilite(plb, iNewISel, TRUE);
|
|
|
|
plb->iLastMouseMove = iNewISel;
|
|
/* Preserve the existing anchor point */
|
|
} else {
|
|
/* Check if this is in ADD mode */
|
|
if ((plb->fAddSelMode) || (plb->wMultiple == MULTIPLESEL)) {
|
|
/* Preserve all pre-exisiting selections */
|
|
if (fSelectKey) {
|
|
/* Toggle the selection state of the current item */
|
|
plb->fNewItemState = !IsSelected(plb, iNewISel, SELONLY);
|
|
SetSelected(plb, iNewISel, plb->fNewItemState, HILITEANDSEL);
|
|
|
|
xxxInvertLBItem(plb, iNewISel, plb->fNewItemState);
|
|
|
|
/* Set the anchor point at the current location */
|
|
plb->iLastMouseMove = plb->iMouseDown = iNewISel;
|
|
uEvent = (plb->fNewItemState ? EVENT_OBJECT_SELECTIONADD :
|
|
EVENT_OBJECT_SELECTIONREMOVE);
|
|
}
|
|
} else {
|
|
/* We are NOT in ADD mode */
|
|
/* Remove all existing selections except iNewISel, to
|
|
* avoid flickering.
|
|
*/
|
|
xxxResetWorld(plb, iNewISel, iNewISel, FALSE);
|
|
|
|
/* Select the current item */
|
|
SetSelected(plb, iNewISel, TRUE, HILITEANDSEL);
|
|
xxxInvertLBItem(plb, iNewISel, TRUE);
|
|
|
|
/* Set the anchor point at the current location */
|
|
plb->iLastMouseMove = plb->iMouseDown = iNewISel;
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Move the cursor to the new location
|
|
*/
|
|
xxxInsureVisible(plb, iNewISel, FALSE);
|
|
xxxLBShowHideScrollBars(plb);
|
|
|
|
xxxLBSetCaret(plb, TRUE);
|
|
|
|
if (uEvent) {
|
|
LBEvent(plb, uEvent, iNewISel);
|
|
}
|
|
|
|
/*
|
|
* Should we notify our parent?
|
|
*/
|
|
if (plb->fNotify) {
|
|
if (fDropDownComboBox && pcbox->fLBoxVisible) {
|
|
|
|
/*
|
|
* If we are in a drop down combo box/listbox and the listbox is
|
|
* visible, we need to set the fKeyboardSelInListBox bit so that the
|
|
* combo box code knows not to hide the listbox since the selchange
|
|
* message is caused by the user keyboarding through...
|
|
*/
|
|
pcbox->fKeyboardSelInListBox = TRUE;
|
|
plb->iLastSelection = iNewISel;
|
|
}
|
|
xxxNotifyOwner(plb, LBN_SELCHANGE);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* Compare
|
|
*
|
|
* Is lpstr1 equal/prefix/less-than/greater-than lsprst2 (case-insensitive) ?
|
|
*
|
|
* LATER IanJa: this assume a longer string is never a prefix of a longer one.
|
|
* Also assumes that removing 1 or more characters from the end of a string will
|
|
* give a string tahs sort before the original. These assumptions are not valid
|
|
* for all languages. We nedd better support from NLS. (Consider French
|
|
* accents, Spanish c/ch, ligatures, German sharp-s/SS, etc.)
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
INT Compare(
|
|
LPCWSTR pwsz1,
|
|
LPCWSTR pwsz2,
|
|
DWORD dwLocaleId)
|
|
{
|
|
UINT len1 = wcslen(pwsz1);
|
|
UINT len2 = wcslen(pwsz2);
|
|
INT result;
|
|
|
|
/*
|
|
* CompareStringW returns:
|
|
* 1 = pwsz1 < pwsz2
|
|
* 2 = pwsz1 == pwsz2
|
|
* 3 = pwsz1 > pwsz2
|
|
*/
|
|
result = CompareStringW((LCID)dwLocaleId, NORM_IGNORECASE,
|
|
pwsz1, min(len1,len2), pwsz2, min(len1, len2));
|
|
|
|
if (result == CSTR_LESS_THAN) {
|
|
return LT;
|
|
} else if (result == CSTR_EQUAL) {
|
|
if (len1 == len2) {
|
|
return EQ;
|
|
} else if (len1 < len2) {
|
|
/*
|
|
* LATER IanJa: should not assume shorter string is a prefix
|
|
* Spanish "c" and "ch", ligatures, German sharp-s/SS etc.
|
|
*/
|
|
return PREFIX;
|
|
}
|
|
}
|
|
return GT;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxFindString
|
|
*
|
|
* Scans for a string in the listbox prefixed by or equal to lpstr.
|
|
* For OWNERDRAW listboxes without strings and without the sort style, we
|
|
* try to match the long app supplied values.
|
|
*
|
|
* History:
|
|
* 16-Apr-1992 beng The NODATA case
|
|
\***************************************************************************/
|
|
|
|
INT xxxFindString(
|
|
PLBIV plb,
|
|
LPWSTR lpstr,
|
|
INT sStart,
|
|
INT code,
|
|
BOOL fWrap)
|
|
{
|
|
/*
|
|
* Search for a prefix match (case-insensitive equal/prefix)
|
|
* sStart == -1 means start from beginning, else start looking at sStart+1
|
|
* assumes cMac > 0.
|
|
*/
|
|
INT sInd; /* index of string */
|
|
INT sStop; /* index to stop searching at */
|
|
lpLBItem pRg;
|
|
TL tlpwndParent;
|
|
INT sortResult;
|
|
|
|
/*
|
|
* Owner-Draw version of pRg
|
|
*/
|
|
#define pODRg ((lpLBODItem)pRg)
|
|
COMPAREITEMSTRUCT cis;
|
|
LPWSTR listboxString;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (plb->fHasStrings && (!lpstr || !*lpstr))
|
|
return LB_ERR;
|
|
|
|
if (!plb->fHasData) {
|
|
RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING, "FindString called on NODATA lb");
|
|
return LB_ERR;
|
|
}
|
|
|
|
if ((sInd = sStart + 1) >= plb->cMac)
|
|
sInd = (fWrap ? 0 : plb->cMac - 1);
|
|
|
|
sStop = (fWrap ? sInd : 0);
|
|
|
|
/*
|
|
* If at end and no wrap, stop right away
|
|
*/
|
|
if (((sStart >= plb->cMac - 1) && !fWrap) || (plb->cMac < 1)) {
|
|
return LB_ERR;
|
|
}
|
|
|
|
/*
|
|
* Apps could pass in an invalid sStart like -2 and we would blow up.
|
|
* Win 3.1 would not so we need to fixup sInd to be zero
|
|
*/
|
|
if (sInd < 0)
|
|
sInd = 0;
|
|
|
|
pRg = (lpLBItem)(plb->rgpch);
|
|
|
|
do {
|
|
if (plb->fHasStrings) {
|
|
|
|
/*
|
|
* Searching for string matches.
|
|
*/
|
|
listboxString = (LPWSTR)((LPBYTE)plb->hStrings + pRg[sInd].offsz);
|
|
|
|
if (code == PREFIX &&
|
|
listboxString &&
|
|
*lpstr != TEXT('[') &&
|
|
*listboxString == TEXT('[')) {
|
|
|
|
/*
|
|
* If we are looking for a prefix string and the first items
|
|
* in this string are [- then we ignore them. This is so
|
|
* that in a directory listbox, the user can goto drives
|
|
* by selecting the drive letter.
|
|
*/
|
|
listboxString++;
|
|
if (*listboxString == TEXT('-'))
|
|
listboxString++;
|
|
}
|
|
|
|
if (Compare(lpstr, listboxString, plb->dwLocaleId) <= code) {
|
|
goto FoundIt;
|
|
}
|
|
|
|
} else {
|
|
if (plb->fSort) {
|
|
|
|
/*
|
|
* Send compare item messages to the parent for sorting
|
|
*/
|
|
cis.CtlType = ODT_LISTBOX;
|
|
cis.CtlID = PtrToUlong(plb->spwnd->spmenu);
|
|
cis.hwndItem = HWq(plb->spwnd);
|
|
cis.itemID1 = (UINT)-1;
|
|
cis.itemData1 = (ULONG_PTR)lpstr;
|
|
cis.itemID2 = (UINT)sInd;
|
|
cis.itemData2 = pODRg[sInd].itemData;
|
|
cis.dwLocaleId = plb->dwLocaleId;
|
|
|
|
ThreadLock(plb->spwndParent, &tlpwndParent);
|
|
sortResult = (INT)SendMessage(HW(plb->spwndParent), WM_COMPAREITEM,
|
|
cis.CtlID, (LPARAM)&cis);
|
|
ThreadUnlock(&tlpwndParent);
|
|
|
|
|
|
if (sortResult == -1) {
|
|
sortResult = LT;
|
|
} else if (sortResult == 1) {
|
|
sortResult = GT;
|
|
} else {
|
|
sortResult = EQ;
|
|
}
|
|
|
|
if (sortResult <= code) {
|
|
goto FoundIt;
|
|
}
|
|
} else {
|
|
|
|
/*
|
|
* Searching for app supplied long data matches.
|
|
*/
|
|
if ((ULONG_PTR)lpstr == pODRg[sInd].itemData)
|
|
goto FoundIt;
|
|
}
|
|
}
|
|
|
|
/*
|
|
* Wrap round to beginning of list
|
|
*/
|
|
if (++sInd == plb->cMac)
|
|
sInd = 0;
|
|
} while (sInd != sStop);
|
|
|
|
sInd = -1;
|
|
|
|
FoundIt:
|
|
return sInd;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLBoxCtlCharInput
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxLBoxCtlCharInput(
|
|
PLBIV plb,
|
|
UINT inputChar,
|
|
BOOL fAnsi)
|
|
{
|
|
INT iSel;
|
|
BOOL fControl;
|
|
TL tlpwndParent;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (plb->cMac == 0 || plb->fMouseDown) {
|
|
|
|
/*
|
|
* Get out if we are in the middle of mouse routines or if we have no
|
|
* items in the listbox, we just return without doing anything.
|
|
*/
|
|
return;
|
|
}
|
|
|
|
fControl = (GetKeyState(VK_CONTROL) < 0);
|
|
|
|
switch (inputChar) {
|
|
case VK_ESCAPE:
|
|
plb->iTypeSearch = 0;
|
|
if (plb->pszTypeSearch)
|
|
plb->pszTypeSearch[0] = 0;
|
|
break;
|
|
|
|
case VK_BACK:
|
|
if (plb->iTypeSearch) {
|
|
plb->pszTypeSearch[plb->iTypeSearch--] = 0;
|
|
if (plb->fSort) {
|
|
iSel = -1;
|
|
goto TypeSearch;
|
|
}
|
|
}
|
|
break;
|
|
|
|
case VK_SPACE:
|
|
if (plb->fAddSelMode || plb->wMultiple == MULTIPLESEL)
|
|
break;
|
|
/* Otherwise, for single/extended selection listboxes not in add
|
|
* selection mode, let the space go thru as a type search character
|
|
* FALL THRU
|
|
*/
|
|
|
|
default:
|
|
|
|
/*
|
|
* Move selection to first item beginning with the character the
|
|
* user typed. We don't want do this if we are using owner draw.
|
|
*/
|
|
|
|
if (fAnsi && IS_DBCS_ENABLED() && IsDBCSLeadByteEx(THREAD_CODEPAGE(), (BYTE)inputChar)) {
|
|
WCHAR wch;
|
|
LPWSTR lpwstr = &wch;
|
|
|
|
inputChar = DbcsCombine(HWq(plb->spwnd), (BYTE)inputChar);
|
|
RIPMSG1(RIP_VERBOSE, "xxxLBoxCtlCharInput: combined DBCS. 0x%04x", inputChar);
|
|
|
|
if (inputChar == 0) {
|
|
RIPMSG1(RIP_WARNING, "xxxLBoxCtlCharInput: cannot combine two DBCS. LB=0x%02x",
|
|
inputChar);
|
|
break;
|
|
}
|
|
// If it is DBCS, let's ignore the ctrl status.
|
|
fControl = FALSE;
|
|
|
|
// Convert DBCS to UNICODE.
|
|
// Note: Leading byte is in the low byte, trailing byte is in high byte.
|
|
// Let's assume Little Endian CPUs only, so inputChar can directly be
|
|
// input for MBSToWCSEx as an ANSI string.
|
|
if (MBToWCSEx(THREAD_CODEPAGE(), (LPCSTR)&inputChar, 2, &lpwstr, 1, FALSE) == 0) {
|
|
RIPMSG1(RIP_WARNING, "xxxLBoxCtlCharInput: cannot convert 0x%04x to UNICODE.",
|
|
inputChar);
|
|
break;
|
|
}
|
|
inputChar = wch;
|
|
}
|
|
|
|
if (plb->fHasStrings) {
|
|
// Incremental Type Search processing
|
|
//
|
|
// update szTypeSearch string and then move to the first item from
|
|
// the current selection whose prefix matches szTypeSearch
|
|
//
|
|
// the szTypeSearch will continue to grow until a "long enough"
|
|
// gap between key entries is encountered -- at which point any
|
|
// more searching will start over
|
|
|
|
/*
|
|
* Undo CONTROL-char to char
|
|
*/
|
|
if (fControl && inputChar < 0x20)
|
|
inputChar += 0x40;
|
|
|
|
if (plb->iTypeSearch == MAX_TYPESEARCH) {
|
|
NtUserMessageBeep(0);
|
|
break;
|
|
}
|
|
iSel = -1;
|
|
|
|
if (plb->pszTypeSearch == NULL)
|
|
plb->pszTypeSearch = (LPWSTR)UserLocalAlloc(HEAP_ZERO_MEMORY, sizeof(WCHAR) * (MAX_TYPESEARCH + 1));
|
|
|
|
if (plb->pszTypeSearch == NULL) {
|
|
NtUserMessageBeep(0);
|
|
break;
|
|
}
|
|
|
|
plb->pszTypeSearch[plb->iTypeSearch++] = (WCHAR) inputChar;
|
|
plb->pszTypeSearch[plb->iTypeSearch] = 0;
|
|
|
|
TypeSearch:
|
|
if (plb->fSort) {
|
|
// Set timer to determine when to kill incremental searching
|
|
NtUserSetTimer(HWq(plb->spwnd), IDSYS_LBSEARCH,
|
|
gpsi->dtLBSearch, NULL);
|
|
} else {
|
|
// If this is not a sorted listbox, no incremental search.
|
|
plb->iTypeSearch = 0;
|
|
iSel = plb->iSelBase;
|
|
}
|
|
|
|
|
|
/*
|
|
* Search for the item beginning with the given character starting
|
|
* at iSel+1. We will wrap the search to the beginning of the
|
|
* listbox if we don't find the item. If SHIFT is down and we are
|
|
* a multiselection lb, then the item's state will be set to
|
|
* plb->fNewItemState according to the current mode.
|
|
*/
|
|
iSel = xxxFindString(plb, plb->pszTypeSearch, iSel, PREFIX, TRUE);
|
|
if (iSel == -1) {
|
|
// no match found -- check for prefix match
|
|
// (i.e. "p" find FIRST item that starts with 'p',
|
|
// "pp" find NEXT item that starts with 'p')
|
|
if(plb->iTypeSearch)
|
|
{
|
|
plb->iTypeSearch--;
|
|
if ((plb->iTypeSearch == 1) && (plb->pszTypeSearch[0] == plb->pszTypeSearch[1]))
|
|
{
|
|
plb->pszTypeSearch[1] = 0;
|
|
iSel = xxxFindString(plb, plb->pszTypeSearch, plb->iSelBase, PREFIX, TRUE);
|
|
}
|
|
}
|
|
}
|
|
// if match is found -- select it
|
|
if (iSel != -1)
|
|
{
|
|
CtlKeyInput:
|
|
xxxLBoxCtlKeyInput(plb, LB_KEYDOWN, iSel);
|
|
|
|
}
|
|
} else {
|
|
if (plb->spwndParent != NULL) {
|
|
ThreadLock(plb->spwndParent, &tlpwndParent);
|
|
iSel = (INT)SendMessageWorker(plb->spwndParent, WM_CHARTOITEM,
|
|
MAKELONG(inputChar, plb->iSelBase), (LPARAM)HWq(plb->spwnd), fAnsi);
|
|
ThreadUnlock(&tlpwndParent);
|
|
} else
|
|
iSel = -1;
|
|
|
|
if (iSel != -1 && iSel != -2)
|
|
goto CtlKeyInput;
|
|
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* LBoxGetSelItems
|
|
*
|
|
* effects: For multiselection listboxes, this returns the total number of
|
|
* selection items in the listbox if fCountOnly is true. or it fills an array
|
|
* (lParam) with the items numbers of the first wParam selected items.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
int LBoxGetSelItems(
|
|
PLBIV plb,
|
|
BOOL fCountOnly,
|
|
int wParam,
|
|
LPINT lParam)
|
|
{
|
|
int i;
|
|
int itemsselected = 0;
|
|
|
|
if (plb->wMultiple == SINGLESEL)
|
|
return LB_ERR;
|
|
|
|
for (i = 0; i < plb->cMac; i++) {
|
|
if (IsSelected(plb, i, SELONLY)) {
|
|
if (!fCountOnly) {
|
|
if (itemsselected < wParam)
|
|
*lParam++ = i;
|
|
else {
|
|
|
|
/*
|
|
* That's all the items we can fit in the array.
|
|
*/
|
|
return itemsselected;
|
|
}
|
|
}
|
|
itemsselected++;
|
|
}
|
|
}
|
|
|
|
return itemsselected;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLBSetRedraw
|
|
*
|
|
* Handle WM_SETREDRAW message
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxLBSetRedraw(
|
|
PLBIV plb,
|
|
BOOL fRedraw)
|
|
{
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (fRedraw)
|
|
fRedraw = TRUE;
|
|
|
|
if (plb->fRedraw != (UINT)fRedraw) {
|
|
plb->fRedraw = !!fRedraw;
|
|
|
|
if (fRedraw) {
|
|
xxxLBSetCaret(plb, TRUE);
|
|
xxxLBShowHideScrollBars(plb);
|
|
|
|
if (plb->fDeferUpdate) {
|
|
plb->fDeferUpdate = FALSE;
|
|
RedrawWindow(HWq(plb->spwnd), NULL, NULL,
|
|
RDW_INVALIDATE | RDW_ERASE |
|
|
RDW_FRAME | RDW_ALLCHILDREN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxLBSelRange
|
|
*
|
|
* Selects the range of items between i and j, inclusive.
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxLBSelRange(
|
|
PLBIV plb,
|
|
int iStart,
|
|
int iEnd,
|
|
BOOL fnewstate)
|
|
{
|
|
DWORD temp;
|
|
RECT rc;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (iStart > iEnd) {
|
|
temp = iEnd;
|
|
iEnd = iStart;
|
|
iStart = temp;
|
|
}
|
|
|
|
/*
|
|
* We don't want to loop through items that don't exist.
|
|
*/
|
|
iEnd = min(plb->cMac, iEnd);
|
|
iStart = max(iStart, 0);
|
|
if (iStart > iEnd)
|
|
return;
|
|
|
|
|
|
/*
|
|
* iEnd could be equal to MAXINT which is why we test temp and iEnd
|
|
* as DWORDs.
|
|
*/
|
|
for (temp = iStart; temp <= (DWORD)iEnd; temp++) {
|
|
|
|
if (IsSelected(plb, temp, SELONLY) != fnewstate) {
|
|
SetSelected(plb, temp, fnewstate, HILITEANDSEL);
|
|
LBGetItemRect(plb, temp, &rc);
|
|
|
|
xxxLBInvalidateRect(plb, (LPRECT)&rc, FALSE);
|
|
}
|
|
|
|
}
|
|
UserAssert(plb->wMultiple);
|
|
LBEvent(plb, EVENT_OBJECT_SELECTIONWITHIN, iStart);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLBSetCurSel
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
int xxxLBSetCurSel(
|
|
PLBIV plb,
|
|
int iSel)
|
|
{
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (!(plb->wMultiple || iSel < -1 || iSel >= plb->cMac)) {
|
|
xxxLBSetCaret(plb, FALSE);
|
|
if (plb->iSel != -1) {
|
|
|
|
/*
|
|
* This prevents scrolling when iSel == -1
|
|
*/
|
|
if (iSel != -1)
|
|
xxxInsureVisible(plb, iSel, FALSE);
|
|
|
|
/*
|
|
* Turn off old selection
|
|
*/
|
|
xxxInvertLBItem(plb, plb->iSel, FALSE);
|
|
}
|
|
|
|
if (iSel != -1) {
|
|
xxxInsureVisible(plb, iSel, FALSE);
|
|
plb->iSelBase = plb->iSel = iSel;
|
|
|
|
/*
|
|
* Highlight new selection
|
|
*/
|
|
xxxInvertLBItem(plb, plb->iSel, TRUE);
|
|
} else {
|
|
plb->iSel = -1;
|
|
if (plb->cMac)
|
|
plb->iSelBase = min(plb->iSelBase, plb->cMac-1);
|
|
else
|
|
plb->iSelBase = 0;
|
|
}
|
|
|
|
/*
|
|
* Send both focus and selection events
|
|
*
|
|
* We need to send this event even if the listbox isn't visible. See
|
|
* bug #88548. Also see 355612.
|
|
*/
|
|
if (_IsWindowVisible(plb->spwnd) || (GetFocus() == HWq(plb->spwnd))) {
|
|
LBEvent(plb, EVENT_OBJECT_FOCUS, plb->iSelBase);
|
|
LBEvent(plb, EVENT_OBJECT_SELECTION, plb->iSel);
|
|
}
|
|
|
|
xxxLBSetCaret(plb, TRUE);
|
|
return plb->iSel;
|
|
}
|
|
|
|
return LB_ERR;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* LBSetItemData
|
|
*
|
|
* Makes the item at index contain the data given.
|
|
*
|
|
* History:
|
|
* 16-Apr-1992 beng The NODATA listbox case
|
|
\***************************************************************************/
|
|
|
|
int LBSetItemData(
|
|
PLBIV plb,
|
|
int index,
|
|
LONG_PTR data)
|
|
{
|
|
LPSTR lpItemText;
|
|
|
|
/*
|
|
* v-ronaar: fix bug #25865, don't allow negative indices!
|
|
*/
|
|
if ((index != -1) && ((UINT) index >= (UINT) plb->cMac)) {
|
|
RIPERR1(ERROR_INVALID_INDEX, RIP_WARNING, "LBSetItemData with invalid index %x", index);
|
|
return LB_ERR;
|
|
}
|
|
|
|
/*
|
|
* No-data listboxes just ignore all LB_SETITEMDATA calls
|
|
*/
|
|
if (!plb->fHasData) {
|
|
return TRUE;
|
|
}
|
|
|
|
lpItemText = (LPSTR)plb->rgpch;
|
|
|
|
if (index == -1) {
|
|
|
|
/*
|
|
* index == -1 means set the data to all the items
|
|
*/
|
|
if (plb->fHasStrings) {
|
|
for (index = 0; index < plb->cMac; index++) {
|
|
|
|
((lpLBItem)lpItemText)->itemData = data;
|
|
lpItemText += sizeof(LBItem);
|
|
}
|
|
} else {
|
|
for (index = 0; index < plb->cMac; index++) {
|
|
|
|
((lpLBODItem)lpItemText)->itemData = data;
|
|
lpItemText += sizeof(LBODItem);
|
|
}
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
if (plb->fHasStrings) {
|
|
|
|
lpItemText = (LPSTR)(lpItemText + (index * sizeof(LBItem)));
|
|
((lpLBItem)lpItemText)->itemData = data;
|
|
} else {
|
|
|
|
lpItemText = (LPSTR)(lpItemText + (index * sizeof(LBODItem)));
|
|
((lpLBODItem)lpItemText)->itemData = data;
|
|
}
|
|
return TRUE;
|
|
}
|
|
|
|
/***************************************************************************\
|
|
* xxxCheckRedraw
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxCheckRedraw(
|
|
PLBIV plb,
|
|
BOOL fConditional,
|
|
INT sItem)
|
|
{
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (fConditional && plb->cMac &&
|
|
(sItem > (plb->iTop + CItemInWindow(plb, TRUE))))
|
|
return;
|
|
|
|
/*
|
|
* Don't do anything if the parent is not visible.
|
|
*/
|
|
xxxLBInvalidateRect(plb, (LPRECT)NULL, TRUE);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxCaretDestroy
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxCaretDestroy(
|
|
PLBIV plb)
|
|
{
|
|
CheckLock(plb->spwnd);
|
|
|
|
/*
|
|
* We're losing the focus. Act like up clicks are happening so we release
|
|
* capture, set the current selection, notify the parent, etc.
|
|
*/
|
|
if (plb->fCaptured)
|
|
|
|
/*
|
|
* If we have the capture and we lost the focus, that means we already
|
|
* changed the selection and we have to notify also the parent about
|
|
* this. So we need to add also the LBUP_SUCCESS flag in this case.
|
|
*/
|
|
|
|
xxxLBButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY |
|
|
(plb->fMouseDown ? LBUP_SUCCESS : 0));
|
|
|
|
if (plb->fAddSelMode) {
|
|
|
|
/*
|
|
* Switch off the Caret blinking
|
|
*/
|
|
NtUserKillTimer(HWq(plb->spwnd), IDSYS_CARET);
|
|
|
|
/*
|
|
* Make sure the caret goes away
|
|
*/
|
|
xxxLBSetCaret(plb, FALSE);
|
|
plb->fAddSelMode = FALSE;
|
|
}
|
|
|
|
plb->fCaret = FALSE;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLbSetSel
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
LONG xxxLBSetSel(
|
|
PLBIV plb,
|
|
BOOL fSelect, /* New state to set selection to */
|
|
INT iSel)
|
|
{
|
|
INT sItem;
|
|
RECT rc;
|
|
UINT uEvent = 0;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
/*
|
|
* Bug 17656. WinZip's accelerator key for 'DeSelect All' sends a LB_SETSEL
|
|
* message with lparam = 0x0000ffff instead of 0xffffffff(-1). If iSel
|
|
* is equal to 0x0000ffff and there are less than 0xffff elements in the
|
|
* list we set iSel equal to 0xffffffff.
|
|
*/
|
|
if ((iSel == (UINT)0xffff) && (iSel >= plb->cMac)) {
|
|
iSel = -1;
|
|
RIPMSG0(RIP_WARNING, "Sign extending iSel=0xffff to 0xffffffff");
|
|
}
|
|
|
|
|
|
if ((plb->wMultiple == SINGLESEL) || (iSel != -1 && iSel >= plb->cMac)) {
|
|
RIPERR0(ERROR_INVALID_PARAMETER, RIP_WARNING,
|
|
"xxxLBSetSel:Invalid iSel or SINGLESEL listbox");
|
|
return LB_ERR;
|
|
}
|
|
|
|
xxxLBSetCaret(plb, FALSE);
|
|
|
|
if (iSel == -1/*(INT)0xffff*/) {
|
|
|
|
/*
|
|
* Set/clear selection from all items if -1
|
|
*/
|
|
for (sItem = 0; sItem < plb->cMac; sItem++) {
|
|
if (IsSelected(plb, sItem, SELONLY) != fSelect) {
|
|
SetSelected(plb, sItem, fSelect, HILITEANDSEL);
|
|
if (LBGetItemRect(plb, sItem, &rc)) {
|
|
xxxLBInvalidateRect(plb, &rc, FALSE);
|
|
}
|
|
}
|
|
}
|
|
xxxLBSetCaret(plb, TRUE);
|
|
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
|
|
} else {
|
|
if (fSelect) {
|
|
|
|
/*
|
|
* Check if the item if fully hidden and scroll it into view if it
|
|
* is. Note that we don't want to scroll partially visible items
|
|
* into full view because this breaks the shell...
|
|
*/
|
|
xxxInsureVisible(plb, iSel, TRUE);
|
|
plb->iSelBase = plb->iSel = iSel;
|
|
|
|
plb->iMouseDown = plb->iLastMouseMove = iSel;
|
|
uEvent = EVENT_OBJECT_FOCUS;
|
|
} else {
|
|
uEvent = EVENT_OBJECT_SELECTIONREMOVE;
|
|
}
|
|
SetSelected(plb, iSel, fSelect, HILITEANDSEL);
|
|
|
|
/*
|
|
* Note that we set the caret on bit directly so that we avoid flicker
|
|
* when drawing this item. ie. We turn on the caret, redraw the item and
|
|
* turn it back on again.
|
|
*/
|
|
if (!fSelect && plb->iSelBase != iSel) {
|
|
xxxLBSetCaret(plb, TRUE);
|
|
} else if (plb->fCaret) {
|
|
plb->fCaretOn = TRUE;
|
|
}
|
|
|
|
if (LBGetItemRect(plb, iSel, &rc)) {
|
|
xxxLBInvalidateRect(plb, &rc, FALSE);
|
|
}
|
|
}
|
|
|
|
/*
|
|
* We need to send this event even if the listbox isn't visible. See
|
|
* bug #88548. Also see 355612.
|
|
*/
|
|
if (_IsWindowVisible(plb->spwnd) || (GetFocus() == HWq(plb->spwnd))) {
|
|
if (uEvent == EVENT_OBJECT_FOCUS) {
|
|
LBEvent(plb, uEvent, plb->iSelBase);
|
|
uEvent = EVENT_OBJECT_SELECTION;
|
|
}
|
|
LBEvent(plb, uEvent, iSel);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLBoxDrawItem
|
|
*
|
|
* This fills the draw item struct with some constant data for the given
|
|
* item. The caller will only have to modify a small part of this data
|
|
* for specific needs.
|
|
*
|
|
* History:
|
|
* 16-Apr-1992 beng The NODATA case
|
|
\***************************************************************************/
|
|
|
|
void xxxLBoxDrawItem(
|
|
PLBIV plb,
|
|
INT item,
|
|
UINT itemAction,
|
|
UINT itemState,
|
|
LPRECT lprect)
|
|
{
|
|
DRAWITEMSTRUCT dis;
|
|
TL tlpwndParent;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
/*
|
|
* Fill the DRAWITEMSTRUCT with the unchanging constants
|
|
*/
|
|
|
|
dis.CtlType = ODT_LISTBOX;
|
|
dis.CtlID = PtrToUlong(plb->spwnd->spmenu);
|
|
|
|
/*
|
|
* Use -1 if an invalid item number is being used. This is so that the app
|
|
* can detect if it should draw the caret (which indicates the lb has the
|
|
* focus) in an empty listbox
|
|
*/
|
|
dis.itemID = (UINT)(item < plb->cMac ? item : -1);
|
|
dis.itemAction = itemAction;
|
|
dis.hwndItem = HWq(plb->spwnd);
|
|
dis.hDC = plb->hdc;
|
|
dis.itemState = itemState |
|
|
(UINT)(TestWF(plb->spwnd, WFDISABLED) ? ODS_DISABLED : 0);
|
|
|
|
if (TestWF(plb->spwnd, WEFPUIFOCUSHIDDEN)) {
|
|
dis.itemState |= ODS_NOFOCUSRECT;
|
|
}
|
|
if (TestWF(plb->spwnd, WEFPUIACCELHIDDEN)) {
|
|
dis.itemState |= ODS_NOACCEL;
|
|
}
|
|
|
|
/*
|
|
* Set the app supplied data
|
|
*/
|
|
if (!plb->cMac || !plb->fHasData) {
|
|
|
|
/*
|
|
* If no strings or no items, just use 0 for data. This is so that we
|
|
* can display a caret when there are no items in the listbox.
|
|
*
|
|
* Lazy-eval listboxes of course have no data to pass - only itemID.
|
|
*/
|
|
dis.itemData = 0L;
|
|
} else {
|
|
dis.itemData = LBGetItemData(plb, item);
|
|
}
|
|
|
|
CopyRect(&dis.rcItem, lprect);
|
|
|
|
/*
|
|
* Set the window origin to the horizontal scroll position. This is so that
|
|
* text can always be drawn at 0,0 and the view region will only start at
|
|
* the horizontal scroll offset. We pass this as wParam
|
|
*/
|
|
/*
|
|
* Note: Only pass the itemID in wParam for 3.1 or newer apps. We break
|
|
* ccMail otherwise.
|
|
*/
|
|
|
|
ThreadLock(plb->spwndParent, &tlpwndParent);
|
|
SendMessage(HW(plb->spwndParent), WM_DRAWITEM,
|
|
TestWF(plb->spwndParent, WFWIN31COMPAT) ? dis.CtlID : 0,
|
|
(LPARAM)&dis);
|
|
ThreadUnlock(&tlpwndParent);
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxLBBlockHilite
|
|
*
|
|
* In Extended selection mode for multiselection listboxes, when
|
|
* mouse is draged to a new position, the range being marked should be
|
|
* properly sized(parts of which will be highlighted/dehighlighted).
|
|
* NOTE: This routine assumes that iSelFromPt and LasMouseMove are not
|
|
* equal because only in that case this needs to be called;
|
|
* NOTE: This routine calculates the region whose display attribute is to
|
|
* be changed in an optimised way. Instead of de-highlighting the
|
|
* the old range completely and highlight the new range, it omits
|
|
* the regions that overlap and repaints only the non-pverlapping
|
|
* area.
|
|
* fKeyBoard = TRUE if this is called for Keyboard interface
|
|
* FALSE if called from Mouse interface routines
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxLBBlockHilite(
|
|
PLBIV plb,
|
|
INT iSelFromPt,
|
|
BOOL fKeyBoard)
|
|
{
|
|
INT sCurPosOffset;
|
|
INT sLastPosOffset;
|
|
INT sHiliteOrSel;
|
|
BOOL fUseSelStatus;
|
|
BOOL DeHiliteStatus;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
if (fKeyBoard) {
|
|
|
|
/*
|
|
* Set both Hilite and Selection states
|
|
*/
|
|
sHiliteOrSel = HILITEANDSEL;
|
|
|
|
/*
|
|
* Do not use the Selection state while de-hiliting
|
|
*/
|
|
fUseSelStatus = FALSE;
|
|
DeHiliteStatus = FALSE;
|
|
} else {
|
|
|
|
/*
|
|
* Set/Reset only the Hilite state
|
|
*/
|
|
sHiliteOrSel = HILITEONLY;
|
|
|
|
/*
|
|
* Use the selection state for de-hilighting
|
|
*/
|
|
fUseSelStatus = TRUE;
|
|
DeHiliteStatus = plb->fNewItemState;
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
* The idea of the routine is to :
|
|
* 1. De-hilite the old range (iMouseDown to iLastMouseDown) and
|
|
* 2. Hilite the new range (iMouseDwon to iSelFromPt)
|
|
*/
|
|
|
|
/*
|
|
* Offset of current mouse position from the anchor point
|
|
*/
|
|
sCurPosOffset = plb->iMouseDown - iSelFromPt;
|
|
|
|
/*
|
|
* Offset of last mouse position from the anchor point
|
|
*/
|
|
sLastPosOffset = plb->iMouseDown - plb->iLastMouseMove;
|
|
|
|
/*
|
|
* Check if both current position and last position lie on the same
|
|
* side of the anchor point.
|
|
*/
|
|
if ((sCurPosOffset * sLastPosOffset) >= 0) {
|
|
|
|
/*
|
|
* Yes they are on the same side; So, highlight/dehighlight only
|
|
* the difference.
|
|
*/
|
|
if (abs(sCurPosOffset) > abs(sLastPosOffset)) {
|
|
xxxAlterHilite(plb, plb->iLastMouseMove, iSelFromPt,
|
|
plb->fNewItemState, sHiliteOrSel, FALSE);
|
|
} else {
|
|
xxxAlterHilite(plb, iSelFromPt, plb->iLastMouseMove, DeHiliteStatus,
|
|
sHiliteOrSel, fUseSelStatus);
|
|
}
|
|
} else {
|
|
xxxAlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove,
|
|
DeHiliteStatus, sHiliteOrSel, fUseSelStatus);
|
|
xxxAlterHilite(plb, plb->iMouseDown, iSelFromPt,
|
|
plb->fNewItemState, sHiliteOrSel, FALSE);
|
|
}
|
|
}
|
|
|
|
|
|
/***************************************************************************\
|
|
* xxxAlterHilite
|
|
*
|
|
* Changes the hilite state of (i..j] (ie. excludes i, includes j in case
|
|
* you've forgotten this notation) to fHilite. It inverts this changes
|
|
* the hilite state.
|
|
*
|
|
* OpFlags: HILITEONLY Only change the display state of the items
|
|
* SELONLY Only Change the selection state of the items
|
|
* HILITEANDSELECT Do both.
|
|
* fHilite:
|
|
* HILITE/TRUE
|
|
* DEHILITE/FALSE
|
|
* fSelStatus:
|
|
* if TRUE, use the selection state of the item to hilite/dehilite
|
|
* if FALSE, use the fHilite parameter to hilite/dehilite
|
|
*
|
|
* History:
|
|
\***************************************************************************/
|
|
|
|
void xxxAlterHilite(
|
|
PLBIV plb,
|
|
INT i,
|
|
INT j,
|
|
BOOL fHilite,
|
|
INT OpFlags,
|
|
BOOL fSelStatus)
|
|
{
|
|
INT low;
|
|
INT high;
|
|
INT sLastInWindow;
|
|
BOOL fCaretOn;
|
|
BOOL fSelected;
|
|
|
|
CheckLock(plb->spwnd);
|
|
|
|
sLastInWindow = plb->iTop + CItemInWindow(plb, TRUE);
|
|
sLastInWindow = min(sLastInWindow, plb->cMac - 1);
|
|
high = max(i, j) + 1;
|
|
|
|
if (fCaretOn = plb->fCaretOn) {
|
|
xxxLBSetCaret(plb, FALSE);
|
|
}
|
|
|
|
for (low = min(i, j); low < high; low++) {
|
|
if (low != i) {
|
|
if (OpFlags & HILITEONLY) {
|
|
if (fSelStatus) {
|
|
fSelected = IsSelected(plb, low, SELONLY);
|
|
} else {
|
|
fSelected = fHilite;
|
|
}
|
|
if (IsSelected(plb, low, HILITEONLY) != fSelected) {
|
|
if (plb->iTop <= low && low <= sLastInWindow) {
|
|
|
|
/*
|
|
* Invert the item only if it is visible
|
|
*/
|
|
xxxInvertLBItem(plb, low, fSelected);
|
|
}
|
|
SetSelected(plb, low, fSelected, HILITEONLY);
|
|
}
|
|
}
|
|
|
|
if (OpFlags & SELONLY) {
|
|
SetSelected(plb, low, fHilite, SELONLY);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (fCaretOn) {
|
|
xxxLBSetCaret(plb, TRUE);
|
|
}
|
|
}
|