#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);
    }
}