Leaked source code of windows server 2003
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

/***************************************************************************\
*
* 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);
}
}