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.
 
 
 
 
 
 

4187 lines
113 KiB

#include "ctlspriv.h"
#pragma hdrstop
#include <limits.h>
#include "usrctl32.h"
#include "listbox.h"
//---------------------------------------------------------------------------//
//
// Defines and common macros
//
#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
//
// LATER IanJa: these vary by country! For US they are VK_OEM_2 VK_OEM_5.
// Change lboxctl2.c MapVirtualKey to character - and fix the spelling?
//
#define VERKEY_SLASH 0xBF // Vertual key for '/' character
#define VERKEY_BACKSLASH 0xDC // Vertual key for '\' character
//---------------------------------------------------------------------------//
//
// Forwards
//
VOID ListBox_NewITopEx(PLBIV, INT, DWORD);
VOID ListBox_FillDrawItem(PLBIV, INT, UINT, UINT, LPRECT);
VOID ListBox_BlockHilite(PLBIV, INT, BOOL);
VOID ListBox_AlterHilite(PLBIV, INT, INT, BOOL, INT, BOOL);
//---------------------------------------------------------------------------//
//
// ListBox_TermDC
//
// Cleans up when done with listbox dc.
//
__inline void ListBox_TermDC(PLBIV plb)
{
if (plb->hFont)
{
SelectObject(plb->hdc, GetStockObject(SYSTEM_FONT));
}
}
//---------------------------------------------------------------------------//
//
// ListBox_InitDC
//
// Initializes dc for listbox
//
void ListBox_InitDC(PLBIV plb)
{
RECT rc;
//
// Set font
//
if (plb->hFont)
{
SelectObject(plb->hdc, plb->hFont);
}
//
// Set clipping area
//
GetClientRect(plb->hwnd, &rc);
IntersectClipRect(plb->hdc, rc.left, rc.top, rc.right, rc.bottom);
OffsetWindowOrgEx(plb->hdc, plb->xOrigin, 0, NULL);
}
//---------------------------------------------------------------------------//
//
// ListBox_GetDC
//
// Returns a DC which can be used by a list box even if parentDC is in effect
//
BOOL ListBox_GetDC(PLBIV plb)
{
if (plb->hdc)
{
return FALSE;
}
plb->hdc = GetDC(plb->hwnd);
ListBox_InitDC(plb);
return TRUE;
}
//---------------------------------------------------------------------------//
void ListBox_ReleaseDC(PLBIV plb)
{
ListBox_TermDC(plb);
ReleaseDC(plb->hwnd, plb->hdc);
plb->hdc = NULL;
}
//---------------------------------------------------------------------------//
//
// ListBox_InvalidateRect()
//
// 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 ListBox_InvalidateRect(PLBIV plb, LPRECT lprc, BOOL fErase)
{
if (IsLBoxVisible(plb))
{
InvalidateRect(plb->hwnd, lprc, fErase);
return TRUE;
}
if (!plb->fRedraw)
{
plb->fDeferUpdate = TRUE;
}
return FALSE;
}
//---------------------------------------------------------------------------//
//
// ListBox_GetBrush
//
// Gets background brush & colors for listbox.
//
HBRUSH ListBox_GetBrush(PLBIV plb, HBRUSH *phbrOld)
{
HBRUSH hbr;
HBRUSH hbrOld;
HWND hwndParent = plb->hwndParent;
SetBkMode(plb->hdc, OPAQUE);
//
// Get brush & colors
//
// copied from windows\core\ntuser\kernel\random.c
if (hwndParent == NULL || hwndParent == GetDesktopWindow())
{
hbr = (HBRUSH)SendMessage(plb->hwnd, WM_CTLCOLORLISTBOX, (WPARAM)plb->hdc, (LPARAM)plb->hwnd);
}
else
{
hbr = (HBRUSH)SendMessage(hwndParent, WM_CTLCOLORLISTBOX, (WPARAM)plb->hdc, (LPARAM)plb->hwnd);
}
ASSERT(hbr != 0);
//
// Select brush into dc
//
if (hbr != NULL)
{
hbrOld = SelectObject(plb->hdc, hbr);
if (phbrOld)
{
*phbrOld = hbrOld;
}
}
return hbr;
}
//---------------------------------------------------------------------------//
//
// ListBox_GetItemRectHandler
//
// 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.
//
BOOL ListBox_GetItemRectHandler(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);
TraceMsg(TF_STANDARD, "Invalid index");
return LB_ERR;
}
GetClientRect(plb->hwnd, 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);
ASSERT(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 + ListBox_GetVarHeightItemHeight(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 ? ListBox_GetVarHeightItemHeight(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 - ListBox_GetVarHeightItemHeight(plb, sTmp);
}
lprc->bottom = lprc->top + ListBox_GetVarHeightItemHeight(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 + ListBox_CItemInWindow(plb, TRUE)));
}
//---------------------------------------------------------------------------//
//
// ListBox_PrintCallback
//
// Called back from DrawState
//
BOOL CALLBACK ListBox_PrintCallback(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;
}
xStart = plb->fMultiColumn ? 0 : 2;
if (plb->fRightAlign)
{
oldAlign = SetTextAlign(hdc, TA_RIGHT | GetTextAlign(hdc));
xStart = cx - xStart;
}
cLen = wcslen(lpstr);
if (plb->fUseTabStops)
{
TabbedTextOut(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;
}
//---------------------------------------------------------------------------//
void ListBox_DrawItem(PLBIV plb, INT sItem, LPRECT lprect, BOOL fHilite, HBRUSH hbr)
{
LPWSTR lpstr;
DWORD rgbSave;
DWORD rgbBkSave;
UINT uFlags;
HDC hdc = plb->hdc;
UINT oldAlign;
HBRUSH hNewBrush;
//
// If the item is selected, then fill with highlight color
//
if (fHilite)
{
FillRectClr(hdc, lprect, SYSRGB(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 (TESTFLAG(GET_STYLE(plb), WS_DISABLED))
{
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));
}
hNewBrush = CreateSolidBrush(SYSRGB(WINDOWTEXT));
DrawState(hdc, hNewBrush,
ListBox_PrintCallback,
(LPARAM)lpstr,
(WPARAM)plb,
lprect->left,
lprect->top,
lprect->right-lprect->left,
lprect->bottom-lprect->top,
uFlags);
if (hNewBrush)
{
DeleteObject(hNewBrush);
}
if (plb->fRtoLReading)
{
SetTextAlign(hdc, oldAlign);
}
if (fHilite)
{
SetTextColor(hdc, rgbSave);
SetBkColor(hdc, rgbBkSave);
}
}
//---------------------------------------------------------------------------//
void ListBox_SetCaret(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 = ListBox_GetDC(plb);
ListBox_GetItemRectHandler(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 (ListBox_IsSelected(plb, plb->iSelBase, HILITEONLY))
{
itemState |= ODS_SELECTED;
}
ListBox_FillDrawItem(plb, plb->iSelBase, ODA_FOCUS, itemState, &rc);
}
else if (!TESTFLAG(GET_EXSTYLE(plb), WS_EXP_UIFOCUSHIDDEN))
{
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)
{
ListBox_ReleaseDC(plb);
}
}
plb->fCaretOn = !!fSetCaret;
}
}
//---------------------------------------------------------------------------//
BOOL ListBox_IsSelected(PLBIV plb, INT sItem, UINT wOpFlags)
{
LPBYTE lp;
if ((sItem >= plb->cMac) || (sItem < 0))
{
TraceMsg(TF_STANDARD, "Invalid index");
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
{
//
// SELONLY
//
sItem &= 0x0F;
}
return sItem;
}
//---------------------------------------------------------------------------//
//
// ListBox_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.
//
INT ListBox_CItemInWindow(PLBIV plb, BOOL fPartial)
{
RECT rect;
if (plb->OwnerDraw == OWNERDRAWVAR)
{
return ListBox_VisibleItemsVarOwnerDraw(plb, fPartial);
}
if (plb->fMultiColumn)
{
return plb->itemsPerColumn * (plb->numberOfColumns + (fPartial ? 1 : 0));
}
GetClientRect(plb->hwnd, &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 --
//
ASSERT(plb->cyChar);
if (!plb->cyChar)
{
plb->cyChar = SYSFONT_CYCHAR;
}
return (INT)((rect.bottom / plb->cyChar) +
((rect.bottom % plb->cyChar)? (fPartial ? 1 : 0) : 0));
}
//---------------------------------------------------------------------------//
//
// ListBox_VScroll
//
// Handles vertical scrolling of the listbox
//
void ListBox_VScroll(PLBIV plb, INT cmd, int yAmt)
{
INT iTopNew;
INT cItemPageScroll;
DWORD dwTime = 0;
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 = ListBox_Page(plb, plb->iTop, FALSE);
}
else
{
iTopNew -= cItemPageScroll;
}
break;
case SB_PAGEDOWN:
if (plb->OwnerDraw == OWNERDRAWVAR)
{
iTopNew = ListBox_Page(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( plb->hwnd, 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;
ListBox_SetCaret(plb, FALSE);
ListBox_ShowHideScrollBars(plb);
ListBox_SetCaret(plb, TRUE);
return;
}
ListBox_NewITopEx(plb, iTopNew, dwTime);
}
}
//---------------------------------------------------------------------------//
DWORD ListBox_GetScrollFlags(PLBIV plb, DWORD dwTime)
{
DWORD dwFlags;
BOOL bUIEffects, bLBSmoothScroll;
SystemParametersInfo(SPI_GETUIEFFECTS, 0, &bUIEffects, 0);
SystemParametersInfo(SPI_GETLISTBOXSMOOTHSCROLLING, 0, &bLBSmoothScroll, 0);
if (dwTime != 0)
{
dwFlags = MAKELONG(SW_SCROLLWINDOW | SW_SMOOTHSCROLL | SW_SCROLLCHILDREN, dwTime);
}
else if (bUIEffects && bLBSmoothScroll && 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;
}
//---------------------------------------------------------------------------//
//
// ListBox_HScroll
//
// Supports horizontal scrolling of listboxes
//
void ListBox_HScroll(PLBIV plb, INT cmd, int xAmt)
{
int newOrigin = plb->xOrigin;
int oldOrigin = plb->xOrigin;
int windowWidth;
RECT rc;
DWORD dwTime = 0;
//
// 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
//
ListBox_HSrollMultiColumn(plb, cmd, xAmt);
return;
}
GetClientRect(plb->hwnd, &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;
ListBox_SetCaret(plb, FALSE);
ListBox_ShowHideScrollBars(plb);
ListBox_SetCaret(plb, TRUE);
return;
}
ListBox_SetCaret(plb, FALSE);
plb->xOrigin = newOrigin;
plb->xOrigin = ListBox_SetScrollParms(plb, SB_HORZ);
if ((cmd == SB_BOTTOM) && plb->fRightAlign)
{
//
// so we know where to draw from.
//
plb->xRightOrigin = plb->xOrigin;
}
if(oldOrigin != plb->xOrigin)
{
DWORD dwFlags;
dwFlags = ListBox_GetScrollFlags(plb, dwTime);
ScrollWindowEx(plb->hwnd, oldOrigin-plb->xOrigin,
0, NULL, &rc, NULL, NULL, dwFlags);
UpdateWindow(plb->hwnd);
}
ListBox_SetCaret(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
//
ListBox_SetScrollParms(plb, SB_HORZ);
}
}
//---------------------------------------------------------------------------//
void ListBox_Paint(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;
if (lprcBounds == NULL)
{
lprcBounds = &rcBounds;
GetClientRect(plb->hwnd, lprcBounds);
}
hdcSave = plb->hdc;
plb->hdc = hdc;
//
// Initialize dc.
//
ListBox_InitDC(plb);
//
// Turn caret off
//
fCaretOn = plb->fCaretOn;
if (fCaretOn)
{
ListBox_SetCaret(plb, FALSE);
}
hbrSave = NULL;
hbrControl = ListBox_GetBrush(plb, &hbrSave);
//
// Get listbox's client
//
GetClientRect(plb->hwnd, &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 + ListBox_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)
{
ListBox_GetItemRectHandler(plb, i, &rect);
}
if (IntersectRect(&scratchRect, lprcBounds, &rect))
{
fHilite = !plb->fNoSel && ListBox_IsSelected(plb, i, HILITEONLY);
if (plb->OwnerDraw)
{
//
// Fill in the drawitem struct
//
ListBox_FillDrawItem(plb, i, ODA_DRAWENTIRE,
(UINT)(fHilite ? ODS_SELECTED : 0), &rect);
}
else
{
ListBox_DrawItem(plb, i, &rect, fHilite, hbrControl);
}
}
}
rect.top = rect.bottom;
}
if (hbrSave != NULL)
{
SelectObject(hdc, hbrSave);
}
if (fCaretOn)
{
ListBox_SetCaret(plb, TRUE);
}
ListBox_TermDC(plb);
plb->hdc = hdcSave;
}
//---------------------------------------------------------------------------//
//
// ListBox_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...
BOOL ListBox_ISelFromPt(PLBIV plb, POINT pt, LPDWORD piItem)
{
RECT rect;
int y;
UINT mouseHighWord = 0;
INT sItem;
INT sTmp;
GetClientRect(plb->hwnd, &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++)
{
ListBox_GetItemRectHandler(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;
}
//---------------------------------------------------------------------------//
//
// ListBox_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;
//
void ListBox_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;
}
}
//---------------------------------------------------------------------------//
//
// ListBox_LastFullVisible
//
// Returns the last fully visible item in the listbox. This is valid
// for ownerdraw var height and fixed height listboxes.
//
INT ListBox_LastFullVisible(PLBIV plb)
{
INT iLastItem;
if (plb->OwnerDraw == OWNERDRAWVAR || plb->fMultiColumn)
{
iLastItem = plb->iTop + ListBox_CItemInWindow(plb, FALSE) - 1;
iLastItem = max(iLastItem, plb->iTop);
}
else
{
iLastItem = min(plb->iTop + plb->cItemFullMax - 1, plb->cMac - 1);
}
return iLastItem;
}
//---------------------------------------------------------------------------//
void ListBox_InvertItem( PLBIV plb, INT i, BOOL fHilite)
{
RECT rect;
BOOL fCaretOn;
HBRUSH hbrControl;
BOOL fNewDC;
//
// Skip if item isn't showing.
//
if (plb->fNoSel || (i < plb->iTop) || (i >= (plb->iTop + ListBox_CItemInWindow(plb, TRUE))))
{
return;
}
if (IsLBoxVisible(plb))
{
ListBox_GetItemRectHandler(plb, i, &rect);
//
// Only turn off the caret if it is on. This avoids annoying caret
// flicker when nesting CaretOns and CaretOffs.
//
fCaretOn = plb->fCaretOn;
if (fCaretOn)
{
ListBox_SetCaret(plb, FALSE);
}
fNewDC = ListBox_GetDC(plb);
hbrControl = ListBox_GetBrush(plb, NULL);
if (!plb->OwnerDraw)
{
if (!fHilite)
{
FillRect(plb->hdc, &rect, hbrControl);
hbrControl = NULL;
}
ListBox_DrawItem(plb, i, &rect, fHilite, hbrControl);
}
else
{
//
// We are ownerdraw so fill in the drawitem struct and send off
// to the owner.
//
ListBox_FillDrawItem(plb, i, ODA_SELECT,
(UINT)(fHilite ? ODS_SELECTED : 0), &rect);
}
if (fNewDC)
{
ListBox_ReleaseDC(plb);
}
//
// Turn the caret back on only if it was originally on.
//
if (fCaretOn)
{
ListBox_SetCaret(plb, TRUE);
}
}
}
//---------------------------------------------------------------------------//
//
// ListBox_ResetWorld
//
// Resets everyone's selection and hilite state except items in the
// range sStItem to sEndItem (Both inclusive).
void ListBox_ResetWorld(PLBIV plb, INT iStart, INT iEnd, BOOL fSelect)
{
INT i;
INT iLastInWindow;
BOOL fCaretOn;
//
// 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)))
{
ListBox_InvertItem(plb, plb->iSel, fSelect);
plb->iSel = -1;
}
return;
}
iLastInWindow = plb->iTop + ListBox_CItemInWindow(plb, TRUE);
fCaretOn = plb->fCaretOn;
if (fCaretOn)
{
ListBox_SetCaret(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 != ListBox_IsSelected(plb, i, HILITEONLY)))
{
//
// Only invert the item if it is visible and present Selection
// state is different from what is required.
//
ListBox_InvertItem(plb, i, fSelect);
}
//
// Set all items outside of preserved range to unselected
//
ListBox_SetSelected(plb, i, fSelect, HILITEANDSEL);
}
}
if (fCaretOn)
{
ListBox_SetCaret(plb, TRUE);
}
}
//---------------------------------------------------------------------------//
void ListBox_NotifyOwner(PLBIV plb, INT sEvt)
{
HWND hwndParent = plb->hwndParent;
if (hwndParent)
{
SendMessage(hwndParent, WM_COMMAND, MAKELONG(GetWindowID(plb->hwnd), sEvt), (LPARAM)(plb->hwnd));
}
}
//---------------------------------------------------------------------------//
void ListBox_SetISelBase(PLBIV plb, INT sItem)
{
ListBox_SetCaret(plb, FALSE);
plb->iSelBase = sItem;
ListBox_SetCaret(plb, TRUE);
ListBox_InsureVisible(plb, plb->iSelBase, FALSE);
if (IsWindowVisible(plb->hwnd) || (GetFocus() == plb->hwnd))
{
ListBox_Event(plb, EVENT_OBJECT_FOCUS, sItem);
}
}
//---------------------------------------------------------------------------//
void ListBox_TrackMouse(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 = plb->hwnd;
RECT rcWindow;
//
// 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. FritzS: NT5 bug 220722.
// Some apps (like MSMoney98) rely on this, so added the bLastRITWasKeyboard
// check. MCostea #244450
//
if ((wMsg == WM_MOUSEMOVE) && RtlEqualMemory(&pt, &(plb->ptPrev), sizeof(POINT)) )
{
TraceMsg(TF_STANDARD, "ListBox_TrackMouse ignoring WM_MOUSEMOVE with no mouse movement");
return;
}
}
mousetemp = ListBox_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->hwnd, &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)
{
// Translate pt and rcClient to screen rel coords
ClientToScreen(plb->hwnd, &pt);
ClientToScreen(plb->hwnd, (LPPOINT)&rcClient.left );
ClientToScreen(plb->hwnd, (LPPOINT)&rcClient.right );
GetWindowRect(plb->hwnd, &rcWindow);
if (!PtInRect(&rcWindow, pt))
{
//
// Cancel selection if clicked outside of combo;
// Accept if clicked on combo button or item.
//
ComboBox_HideListBoxWindow(plb->pcbox, TRUE, FALSE);
}
else if (!PtInRect(&rcClient, pt))
{
//
// Let it pass through. Save, restore capture in
// case user is clicking on scrollbar.
//
plb->fCaptured = FALSE;
ReleaseCapture();
SendMessageW(plb->hwnd, WM_NCLBUTTONDOWN,
(WPARAM)SendMessageW(plb->hwnd, WM_NCHITTEST, 0, POINTTOPOINTS(pt)), POINTTOPOINTS(pt));
SetCapture(hwnd);
plb->fCaptured = TRUE;
}
break;
}
plb->fCaptured = FALSE;
ReleaseCapture();
}
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
//
SetFocus(plb->pcbox->hwndEdit);
}
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...
//
SetFocus(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...
//
//
// Switch off the Caret blinking
//
KillTimer(hwnd, IDSYS_CARET);
//
// Make sure the caret does not vanish
//
ListBox_SetCaret(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 && plb->fCaptured)
{
//
// Mouse down occurred in a empty spot. And we're tracking the list.
// 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.
//
trackPtRetn = (INT)SendMessage(plb->hwndParent, WM_LBTRACKPOINT,
(DWORD)iSelFromPt, MAKELONG(pt.x+plb->xOrigin, pt.y));
if (trackPtRetn)
{
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;
SetCapture(hwnd);
plb->fCaptured = TRUE;
if (plb->fDoubleClick)
{
//
// Double click. Fake a button up and exit
//
ListBox_TrackMouse(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
//
SetTimer(hwnd, IDSYS_SCROLL, SCROLL_TIMEOUT(), 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)
{
ListBox_SetCaret(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...
//
if ( mousetemp || (plb->pcbox && plb->pcbox->fButtonPressed))
{
// We're outside the list but haven't begun tracking the list yet.
// Select the item that is already selected.
iSelTemp = plb->iSel;
}
else
{
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)
{
ListBox_ResetWorld(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 = ListBox_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)
{
ListBox_SetSelected(plb, iSelTemp, !fSelected, HILITEANDSEL);
//
// And invert it
//
ListBox_InvertItem(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
//
ListBox_ResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
//
// Select the current position
//
ListBox_SetSelected(plb, plb->iMouseDown, TRUE, HILITEANDSEL);
ListBox_InvertItem(plb, plb->iMouseDown, TRUE);
//
// We are changing the selction to this item only
//
uEvent = EVENT_OBJECT_SELECTION;
}
else
{
//
// Reset all the previous selections
//
ListBox_ResetWorld(plb, plb->iMouseDown, plb->iMouseDown, FALSE);
//
// Select all items from anchor point upto current click pt
//
ListBox_AlterHilite(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)!ListBox_IsSelected(plb, iSelFromPt, (UINT)HILITEONLY);
//
// Toggle the current point
//
ListBox_SetSelected(plb, iSelFromPt, plb->fNewItemState, HILITEANDSEL);
ListBox_InvertItem(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)
{
ListBox_AlterHilite(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)ListBox_IsSelected(plb, plb->iMouseDown, HILITEONLY);
//
// Select all items from anchor point upto current click pt
//
ListBox_AlterHilite(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 ListBox_SetISelBase always turns on the caret, we don't need to
// do it here...
//
ListBox_SetISelBase(plb, iSelFromPt);
}
//
// ListBox_SetISelBase will change the focus and send a focus event.
// Then we send the selection event.
//
if (uEvent)
{
ListBox_Event(plb, uEvent, iSelFromPt);
}
if (wMsg == WM_LBUTTONDOWN && (GET_EXSTYLE(plb) & WS_EX_DRAGOBJECT)!=0)
{
if (DragDetect(hwnd, pt))
{
//
// User is trying to drag object...
//
//
// Fake an up click so that the item is selected...
//
ListBox_TrackMouse(plb, WM_LBUTTONUP, pt);
//
// Notify parent
// #ifndef WIN16 (32-bit Windows), plb->iSelBase gets
// zero-extended to LONG wParam automatically by the compiler.
//
SendMessage(plb->hwndParent, WM_BEGINDRAG, plb->iSelBase, (LPARAM)hwnd);
}
else
{
ListBox_TrackMouse(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 = ((SCROLL_TIMEOUT() * 3) / 2) - ((WORD) dist << 4);
if (plb->fRightAlign)
{
ListBox_HSrollMultiColumn(plb, (pt.x < 0 ? SB_LINEDOWN : SB_LINEUP), 0);
}
else
{
ListBox_HSrollMultiColumn(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 = SCROLL_TIMEOUT() - ((WORD) dist << 4);
ListBox_VScroll(plb, (pt.y < 0 ? SB_LINEUP : SB_LINEDOWN), 0);
SetTimerAndSel:
SetTimer(hwnd, IDSYS_SCROLL, max(iTimer, 1), NULL);
ListBox_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;
case MULTIPLESEL:
case EXTENDEDSEL:
//
// Handle mouse movement with extended selection of items
//
if (plb->iSelBase != iSelFromPt)
{
ListBox_SetISelBase(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)
{
ListBox_BlockHilite(plb, iSelFromPt, FALSE);
ListBox_Event(plb, EVENT_OBJECT_SELECTIONWITHIN, iSelFromPt);
}
plb->iLastMouseMove = iSelFromPt;
}
break;
}
break;
}
case WM_LBUTTONUP:
if (plb->fMouseDown)
{
ListBox_ButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY |
(mousetemp ? LBUP_RESETSELECTION : 0) |
(fMouseInRect ? LBUP_SUCCESS : 0));
}
}
}
//---------------------------------------------------------------------------//
//
// ListBox_ButtonUp
//
// Called in response to both WM_CAPTURECHANGED and WM_LBUTTONUP.
//
void ListBox_ButtonUp(PLBIV plb, UINT uFlags)
{
//
// 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)
{
ListBox_AlterHilite(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)
{
ListBox_InvertItem(plb, plb->iSel, FALSE);
}
plb->iSel = plb->iLastSelection;
ListBox_InvertItem(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.
//
ListBox_Event(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))
{
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
}
}
KillTimer(plb->hwnd, IDSYS_SCROLL);
plb->fMouseDown = FALSE;
if ( plb->fCaptured || (GetCapture() == plb->hwndParent) )
{
plb->fCaptured = FALSE;
if (uFlags & LBUP_RELEASECAPTURE)
{
ReleaseCapture();
}
}
//
// Don't scroll item as long as any part of it is visible
//
if (plb->iSelBase < plb->iTop ||
plb->iSelBase > plb->iTop + ListBox_CItemInWindow(plb, TRUE))
{
ListBox_InsureVisible(plb, plb->iSelBase, FALSE);
}
if (plb->fNotify)
{
if (uFlags & LBUP_NOTIFY)
{
if (uFlags & LBUP_SUCCESS)
{
//
// ArtMaster needs this SELCHANGE notification now!
//
if ((plb->fDoubleClick) && !TESTFLAG(GET_STATE2(plb), WS_S2_WIN31COMPAT))
{
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
}
//
// Notify owner of click or double click on selection
//
ListBox_NotifyOwner(plb, (plb->fDoubleClick) ? LBN_DBLCLK : LBN_SELCHANGE);
}
else
{
//
// Notify owner that the attempted selection was cancelled.
//
ListBox_NotifyOwner(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.
//
ASSERT(TESTFLAG(GET_STATE2(plb), WS_S2_WIN40COMPAT));
if (plb->iLastSelection != plb->iSel)
{
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
}
}
}
}
//---------------------------------------------------------------------------//
INT ListBox_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;
}
//---------------------------------------------------------------------------//
void ListBox_NewITop(PLBIV plb, INT iTopNew)
{
ListBox_NewITopEx(plb, iTopNew, 0);
}
//---------------------------------------------------------------------------//
void ListBox_NewITopEx(PLBIV plb, INT iTopNew, DWORD dwTime)
{
int iTopOld;
BOOL fCaretOn;
BOOL fMulti = plb->fMultiColumn;
//
// Always try to turn off caret whether or not redraw is on
//
if (fCaretOn = plb->fCaretOn)
{
ListBox_SetCaret(plb, FALSE);
}
iTopOld = (fMulti) ? (plb->iTop / plb->itemsPerColumn) : plb->iTop;
plb->iTop = iTopNew;
iTopNew = ListBox_SetScrollParms(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->hwnd, &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 = ListBox_CalcVarITopScrollAmt(plb, iTopOld, iTopNew);
plb->iTop = iTopNew;
}
else if (abs(iTopNew - iTopOld) > plb->cItemFullMax)
{
yAmt = 32000;
}
else
{
yAmt = (iTopOld - iTopNew) * plb->cyChar;
}
}
dwFlags = ListBox_GetScrollFlags(plb, dwTime);
ScrollWindowEx(plb->hwnd, xAmt, yAmt, NULL, &rc, NULL, NULL, dwFlags);
UpdateWindow(plb->hwnd);
}
//
// 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.
ListBox_SetCaret(plb, TRUE);
}
}
//---------------------------------------------------------------------------//
void ListBox_InsureVisible( PLBIV plb, INT iSel, BOOL fPartial)
{
INT sLastVisibleItem;
if (iSel < plb->iTop)
{
ListBox_NewITop(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 + ListBox_CItemInWindow(plb, TRUE) - (INT)1;
}
else
{
sLastVisibleItem = ListBox_LastFullVisible(plb);
}
if (plb->OwnerDraw != OWNERDRAWVAR)
{
if (iSel > sLastVisibleItem)
{
if (plb->fMultiColumn)
{
ListBox_NewITop(plb,
((iSel / plb->itemsPerColumn) -
max(plb->numberOfColumns-1,0)) * plb->itemsPerColumn);
}
else
{
ListBox_NewITop(plb, (INT)max(0, iSel - sLastVisibleItem + plb->iTop));
}
}
}
else if (iSel > sLastVisibleItem)
{
ListBox_NewITop(plb, ListBox_Page(plb, iSel, FALSE));
}
}
}
//---------------------------------------------------------------------------//
//
// ListBox_CareBlinker
//
// Timer callback function toggles Caret
// Since it is a callback, it is APIENTRY
//
VOID ListBox_CareBlinker(HWND hwnd, UINT wMsg, UINT_PTR nIDEvent, DWORD dwTime)
{
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);
plb = ListBox_GetPtr(hwnd);
//
// 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
//
ListBox_SetCaret(plb, !plb->fCaretOn);
}
//---------------------------------------------------------------------------//
//
// ListBox_KeyInput
//
// If msg == LB_KEYDOWN, vKey is the number of the item to go to,
// otherwise it is the virtual key.
//
void ListBox_KeyInput(PLBIV plb, UINT msg, UINT vKey)
{
INT i;
INT iNewISel;
INT cItemPageScroll;
PCBOX pcbox;
BOOL fDropDownComboBox;
BOOL fExtendedUIComboBoxClosed;
UINT wModifiers = 0;
BOOL fSelectKey = FALSE; // assume it is a navigation key
UINT uEvent = 0;
HWND hwnd = plb->hwnd;
BOOL hScrollBar = (GET_STYLE(plb)&WS_HSCROLL)!=0;
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...
//
iNewISel = (INT)SendMessage(plb->hwndParent, WM_VKEYTOITEM,
MAKELONG(vKey, plb->iSelBase), (LPARAM)hwnd);
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))
{
ListBox_SetCaret(plb, FALSE);
ListBox_ResetWorld(plb, plb->iSelBase, plb->iSelBase, FALSE);
//
// And select the current item
//
ListBox_SetSelected(plb, plb->iSelBase, TRUE, HILITEANDSEL);
ListBox_InvertItem(plb, plb->iSelBase, TRUE);
uEvent = EVENT_OBJECT_SELECTION;
goto CaretOnAndNotify;
}
return;
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))
{
ListBox_SetCaret(plb, FALSE);
ListBox_ResetWorld(plb, -1, -1, TRUE);
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
CaretOnAndNotify:
ListBox_SetCaret(plb, TRUE);
ListBox_Event(plb, uEvent, plb->iSelBase);
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
}
return;
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
//
KillTimer(hwnd, IDSYS_CARET);
//
// Make sure the caret does not vanish
//
ListBox_SetCaret(plb, TRUE);
}
else
{
//
// Create a timer to make the caret blink
//
SetTimer(hwnd, IDSYS_CARET, GetCaretBlinkTime(),
ListBox_CareBlinker);
}
//
// 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 = ListBox_Page(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 = ListBox_Page(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
#ifdef USE_MIRRORING
^ (!!TESTFLAG(GET_EXSTYLE(plb), WS_EX_LAYOUTRTL))
#endif
)
{
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
#ifdef USE_MIRRORING
^ (!!TESTFLAG(GET_EXSTYLE(plb), WS_EX_LAYOUTRTL))
#endif
)
{
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
//
ComboBox_ShowListBoxWindow(pcbox, TRUE);
}
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.
//
if (!pcbox->fLBoxVisible)
{
//
// If the listbox isn't visible, just show it
//
ComboBox_ShowListBoxWindow(pcbox, (vKey != VK_ESCAPE));
}
else
{
//
// Ok, the listbox is visible. So hide the listbox window.
//
ComboBox_HideListBoxWindow(pcbox, TRUE, (vKey != VK_ESCAPE));
}
}
//
// Fall through to the return
//
default:
return;
}
//
// Find out what the new selection should be
//
iNewISel = ListBox_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) &&
!ListBox_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:
ListBox_SetISelBase(plb, iNewISel);
ListBox_SetCaret(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, ListBox_BlockHilite()
// 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
// ListBox_BlockHilite
//
ListBox_ResetWorld(plb, plb->iMouseDown, plb->iLastMouseMove, FALSE);
}
uEvent = EVENT_OBJECT_SELECTIONWITHIN;
//
// ListBox_BlockHilite 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 = ListBox_IsSelected(plb, plb->iMouseDown, SELONLY);
ListBox_BlockHilite(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 = !ListBox_IsSelected(plb, iNewISel, SELONLY);
ListBox_SetSelected(plb, iNewISel, plb->fNewItemState, HILITEANDSEL);
ListBox_InvertItem(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.
//
ListBox_ResetWorld(plb, iNewISel, iNewISel, FALSE);
//
// Select the current item
//
ListBox_SetSelected(plb, iNewISel, TRUE, HILITEANDSEL);
ListBox_InvertItem(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
//
ListBox_InsureVisible(plb, iNewISel, FALSE);
ListBox_ShowHideScrollBars(plb);
ListBox_SetCaret(plb, TRUE);
if (uEvent)
{
ListBox_Event(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;
}
ListBox_NotifyOwner(plb, LBN_SELCHANGE);
}
}
//---------------------------------------------------------------------------//
//
// ListBox_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.)
//
INT ListBox_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;
}
//---------------------------------------------------------------------------//
//
// Listbox_FindStringHandler
//
// 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.
//
INT Listbox_FindStringHandler(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;
INT sortResult;
//
// Owner-Draw version of pRg
//
#define pODRg ((lpLBODItem)pRg)
COMPAREITEMSTRUCT cis;
LPWSTR listboxString;
if (plb->fHasStrings && (!lpstr || !*lpstr))
{
return LB_ERR;
}
if (!plb->fHasData)
{
TraceMsg(TF_STANDARD, "Listbox_FindStringHandler 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 (ListBox_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 = GetDlgCtrlID(plb->hwnd);
cis.hwndItem = plb->hwnd;
cis.itemID1 = (UINT)-1;
cis.itemData1 = (ULONG_PTR)lpstr;
cis.itemID2 = (UINT)sInd;
cis.itemData2 = pODRg[sInd].itemData;
cis.dwLocaleId = plb->dwLocaleId;
sortResult = (INT)SendMessage(plb->hwndParent, WM_COMPAREITEM,
cis.CtlID, (LPARAM)&cis);
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;
}
//---------------------------------------------------------------------------//
void ListBox_CharHandler(PLBIV plb, UINT inputChar, BOOL fAnsi)
{
INT iSel;
BOOL fControl;
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 && IsDBCSLeadByteEx(CP_ACP, (BYTE)inputChar))
{
WCHAR wch;
LPWSTR lpwstr = &wch;
inputChar = DbcsCombine(plb->hwnd, (BYTE)inputChar);
if (inputChar == 0)
{
TraceMsg(TF_STANDARD, "ListBox_CharHandler: 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 (MultiByteToWideChar(CP_ACP, MB_PRECOMPOSED, (LPCSTR)&inputChar, 2, lpwstr, 1) == 0)
{
TraceMsg(TF_STANDARD, "ListBox_CharHandler: 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)
{
MessageBeep(0);
break;
}
iSel = -1;
if (plb->pszTypeSearch == NULL)
{
plb->pszTypeSearch = (LPWSTR)ControlAlloc(GetProcessHeap(), sizeof(WCHAR) * (MAX_TYPESEARCH + 1));
}
if (plb->pszTypeSearch == NULL)
{
MessageBeep(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
//
SetTimer(plb->hwnd, IDSYS_LBSEARCH,
GetDoubleClickTime()*4, 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 = Listbox_FindStringHandler(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 = Listbox_FindStringHandler(plb, plb->pszTypeSearch, plb->iSelBase, PREFIX, TRUE);
}
}
}
//
// if match is found -- select it
//
if (iSel != -1)
{
CtlKeyInput:
ListBox_KeyInput(plb, LB_KEYDOWN, iSel);
}
}
else
{
HWND hwndParent = plb->hwndParent;
if (hwndParent != NULL)
{
if(fAnsi)
{
iSel = (INT)SendMessageA(hwndParent, WM_CHARTOITEM,
MAKELONG(inputChar, plb->iSelBase), (LPARAM)plb->hwnd);
}
else
{
iSel = (INT)SendMessageW(hwndParent, WM_CHARTOITEM,
MAKELONG(inputChar, plb->iSelBase), (LPARAM)plb->hwnd);
}
}
else
{
iSel = -1;
}
if (iSel != -1 && iSel != -2)
{
goto CtlKeyInput;
}
}
break;
}
}
//---------------------------------------------------------------------------//
//
// ListBox_GetSelItemsHandler
//
// 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.
//
int ListBox_GetSelItemsHandler(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 (ListBox_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;
}
//---------------------------------------------------------------------------//
//
// ListBox_SetRedraw
//
// Handle WM_SETREDRAW message
//
void ListBox_SetRedraw(PLBIV plb, BOOL fRedraw)
{
if (fRedraw)
{
fRedraw = TRUE;
}
if (plb->fRedraw != (UINT)fRedraw)
{
plb->fRedraw = !!fRedraw;
if (fRedraw)
{
ListBox_SetCaret(plb, TRUE);
ListBox_ShowHideScrollBars(plb);
if (plb->fDeferUpdate)
{
plb->fDeferUpdate = FALSE;
RedrawWindow(plb->hwnd, NULL, NULL,
RDW_INVALIDATE | RDW_ERASE |
RDW_FRAME | RDW_ALLCHILDREN);
}
}
}
}
//---------------------------------------------------------------------------//
//
// ListBox_SetRange
//
// Selects the range of items between i and j, inclusive.
//
void ListBox_SetRange(PLBIV plb, int iStart, int iEnd, BOOL fnewstate)
{
DWORD temp;
RECT rc;
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 (ListBox_IsSelected(plb, temp, SELONLY) != fnewstate)
{
ListBox_SetSelected(plb, temp, fnewstate, HILITEANDSEL);
ListBox_GetItemRectHandler(plb, temp, &rc);
ListBox_InvalidateRect(plb, (LPRECT)&rc, FALSE);
}
}
ASSERT(plb->wMultiple);
ListBox_Event(plb, EVENT_OBJECT_SELECTIONWITHIN, iStart);
}
//---------------------------------------------------------------------------//
int ListBox_SetCurSelHandler(PLBIV plb, int iSel)
{
if (!(plb->wMultiple || iSel < -1 || iSel >= plb->cMac))
{
ListBox_SetCaret(plb, FALSE);
if (plb->iSel != -1)
{
//
// This prevents scrolling when iSel == -1
//
if (iSel != -1)
{
ListBox_InsureVisible(plb, iSel, FALSE);
}
//
// Turn off old selection
//
ListBox_InvertItem(plb, plb->iSel, FALSE);
}
if (iSel != -1)
{
ListBox_InsureVisible(plb, iSel, FALSE);
plb->iSelBase = plb->iSel = iSel;
//
// Highlight new selection
//
ListBox_InvertItem(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
//
if (IsWindowVisible(plb->hwnd) || (GetFocus() == plb->hwnd))
{
ListBox_Event(plb, EVENT_OBJECT_FOCUS, plb->iSelBase);
ListBox_Event(plb, EVENT_OBJECT_SELECTION, plb->iSel);
}
ListBox_SetCaret(plb, TRUE);
return plb->iSel;
}
return LB_ERR;
}
//---------------------------------------------------------------------------//
//
// ListBox_SetItemDataHandler
//
// Makes the item at index contain the data given.
//
int ListBox_SetItemDataHandler(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))
{
TraceMsg(TF_STANDARD, "ListBox_SetItemDataHandler 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;
}
//---------------------------------------------------------------------------//
void ListBox_CheckRedraw(PLBIV plb, BOOL fConditional, INT sItem)
{
if (fConditional && plb->cMac &&
(sItem > (plb->iTop + ListBox_CItemInWindow(plb, TRUE))))
{
return;
}
//
// Don't do anything if the parent is not visible.
//
ListBox_InvalidateRect(plb, (LPRECT)NULL, TRUE);
}
//---------------------------------------------------------------------------//
void ListBox_CaretDestroy(PLBIV plb)
{
//
// 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.
//
ListBox_ButtonUp(plb, LBUP_RELEASECAPTURE | LBUP_NOTIFY |
(plb->fMouseDown ? LBUP_SUCCESS : 0));
}
if (plb->fAddSelMode)
{
//
// Switch off the Caret blinking
//
KillTimer(plb->hwnd, IDSYS_CARET);
//
// Make sure the caret goes away
//
ListBox_SetCaret(plb, FALSE);
plb->fAddSelMode = FALSE;
}
plb->fCaret = FALSE;
}
//---------------------------------------------------------------------------//
LONG ListBox_SetSelHandler(PLBIV plb, BOOL fSelect, INT iSel)
{
INT sItem;
RECT rc;
UINT uEvent = 0;
//
// 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;
TraceMsg(TF_STANDARD, "Sign extending iSel=0xffff to 0xffffffff");
}
if ((plb->wMultiple == SINGLESEL) || (iSel != -1 && iSel >= plb->cMac))
{
TraceMsg(TF_STANDARD, "Invalid index");
return LB_ERR;
}
ListBox_SetCaret(plb, FALSE);
if (iSel == -1)
{
//
// Set/clear selection from all items if -1
//
for (sItem = 0; sItem < plb->cMac; sItem++)
{
if (ListBox_IsSelected(plb, sItem, SELONLY) != fSelect)
{
ListBox_SetSelected(plb, sItem, fSelect, HILITEANDSEL);
if (ListBox_GetItemRectHandler(plb, sItem, &rc))
{
ListBox_InvalidateRect(plb, &rc, FALSE);
}
}
}
ListBox_SetCaret(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...
//
ListBox_InsureVisible(plb, iSel, TRUE);
plb->iSelBase = plb->iSel = iSel;
plb->iMouseDown = plb->iLastMouseMove = iSel;
uEvent = EVENT_OBJECT_FOCUS;
}
else
{
uEvent = EVENT_OBJECT_SELECTIONREMOVE;
}
ListBox_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)
{
ListBox_SetCaret(plb, TRUE);
}
else if (plb->fCaret)
{
plb->fCaretOn = TRUE;
}
if (ListBox_GetItemRectHandler(plb, iSel, &rc))
{
ListBox_InvalidateRect(plb, &rc, FALSE);
}
}
if (IsWindowVisible(plb->hwnd) || (GetFocus() == plb->hwnd))
{
if (uEvent == EVENT_OBJECT_FOCUS)
{
ListBox_Event(plb, uEvent, plb->iSelBase);
uEvent = EVENT_OBJECT_SELECTION;
}
ListBox_Event(plb, uEvent, iSel);
}
return 0;
}
//---------------------------------------------------------------------------//
//
// ListBox_FillDrawItem
//
// 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.
//
void ListBox_FillDrawItem(PLBIV plb, INT item, UINT itemAction, UINT itemState, LPRECT lprect)
{
DRAWITEMSTRUCT dis;
//
// Fill the DRAWITEMSTRUCT with the unchanging constants
//
dis.CtlType = ODT_LISTBOX;
dis.CtlID = GetDlgCtrlID(plb->hwnd);
//
// 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 = plb->hwnd;
dis.hDC = plb->hdc;
dis.itemState = itemState |
(UINT)((GET_STYLE(plb)&WS_DISABLED) ? ODS_DISABLED : 0);
if (TESTFLAG(GET_EXSTYLE(plb), WS_EXP_UIFOCUSHIDDEN))
{
dis.itemState |= ODS_NOFOCUSRECT;
}
if (TESTFLAG(GET_EXSTYLE(plb), WS_EXP_UIACCELHIDDEN))
{
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 = ListBox_GetItemDataHandler(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
//
SendMessage(plb->hwndParent, WM_DRAWITEM, dis.CtlID, (LPARAM)&dis);
}
//---------------------------------------------------------------------------//
//
// ListBox_BlockHilite
//
// 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
//
void ListBox_BlockHilite(PLBIV plb, INT iSelFromPt, BOOL fKeyBoard)
{
INT sCurPosOffset;
INT sLastPosOffset;
INT sHiliteOrSel;
BOOL fUseSelStatus;
BOOL DeHiliteStatus;
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))
{
ListBox_AlterHilite(plb, plb->iLastMouseMove, iSelFromPt,
plb->fNewItemState, sHiliteOrSel, FALSE);
}
else
{
ListBox_AlterHilite(plb, iSelFromPt, plb->iLastMouseMove, DeHiliteStatus,
sHiliteOrSel, fUseSelStatus);
}
}
else
{
ListBox_AlterHilite(plb, plb->iMouseDown, plb->iLastMouseMove,
DeHiliteStatus, sHiliteOrSel, fUseSelStatus);
ListBox_AlterHilite(plb, plb->iMouseDown, iSelFromPt,
plb->fNewItemState, sHiliteOrSel, FALSE);
}
}
//---------------------------------------------------------------------------//
//
// ListBox_AlterHilite
//
// 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
//
void ListBox_AlterHilite(PLBIV plb, INT i, INT j, BOOL fHilite, INT OpFlags, BOOL fSelStatus)
{
INT low;
INT high;
INT sLastInWindow;
BOOL fCaretOn;
BOOL fSelected;
sLastInWindow = plb->iTop + ListBox_CItemInWindow(plb, TRUE);
sLastInWindow = min(sLastInWindow, plb->cMac - 1);
high = max(i, j) + 1;
if (fCaretOn = plb->fCaretOn)
{
ListBox_SetCaret(plb, FALSE);
}
for (low = min(i, j); low < high; low++)
{
if (low != i)
{
if (OpFlags & HILITEONLY)
{
if (fSelStatus)
{
fSelected = ListBox_IsSelected(plb, low, SELONLY);
}
else
{
fSelected = fHilite;
}
if (ListBox_IsSelected(plb, low, HILITEONLY) != fSelected)
{
if (plb->iTop <= low && low <= sLastInWindow)
{
//
// Invert the item only if it is visible
//
ListBox_InvertItem(plb, low, fSelected);
}
ListBox_SetSelected(plb, low, fSelected, HILITEONLY);
}
}
if (OpFlags & SELONLY)
{
ListBox_SetSelected(plb, low, fHilite, SELONLY);
}
}
}
if (fCaretOn)
{
ListBox_SetCaret(plb, TRUE);
}
}