// History:
//  02-03-93 SatoNa     Removed obsolete DropFiles()
//

#include "shellprv.h"
#pragma hdrstop
// Function prototype
void _DestroyCachedCursors();

typedef struct _DAD_DRAGCONTEXT // dadc
{
    HWND        hwndFrom;
    BOOL        fImage;
    POINT       ptOffset;
    DWORD       _idProcess;
    DWORD       _idThread;

    // _ds struct is used between DAD_Enter and DAD_Leave
    struct
    {
            // Common part
            BOOL    bDragging;
            BOOL    bLocked;
            HWND    hwndLock;
            BOOL    bSingle;    // Single imagelist dragging.
            DWORD   idThreadEntered;

            // Multi-rect dragging specific part
            struct {
                BOOL bShown;
                LPRECT pRect;
                int nRects;
                POINT ptOffset;
                POINT ptNow;
            } mlt;
    } _ds;

    // following fields are used only when fImage==FALSE
    UINT        cItems;         //              
    RECT        arc[1];         // cItems
} DAD_DRAGCONTEXT, * LPDAD_DRAGCONTEXT;

int _MapCursorIDToImageListIndex(int idCur);

// move to commctrl\cutils.c

/*
 *  QueryDropObject() -
 *
 *  Determines where in the window heirarchy the "drop" takes place, and
 *  sends a message to the deepest child window first.  If that window does
 *  not respond, we go up the heirarchy (recursively, for the moment) until
 *  we either get a window that does respond or the parent doesn't respond.
 *
 *  in:
 *
 *  out:
 *      lpds->ptDrop    set to the point of the query (window coordinates)
 *      lpds->hwndSink  the window that answered the query
 *
 *  returns:
 *      value from WM_QUERYDROPOBJECT (0, 1, or hCursor)
 */

HCURSOR QueryDropObject(HWND hwnd, LPDROPSTRUCT lpds)
{
    HWND hwndT;
    HCURSOR hCurT = 0;
    POINT pt;
    BOOL fNC;
    RECT rc;

    pt = lpds->ptDrop;          /* pt is in screen coordinates */

    GetWindowRect(hwnd, &rc);

    /* reject points outside this window or if the window is disabled */
    if (!PtInRect(&rc, pt) || !IsWindowEnabled(hwnd))
        return NULL;

    /* are we dropping in the nonclient area of the window or on an iconic
     * window? */
    GetClientRect(hwnd, &rc);
    MapWindowPoints(hwnd, NULL, (LPPOINT)&rc, 2);
    if (IsMinimized(hwnd) || !PtInRect(&rc, pt)) {
        fNC = TRUE;
        ScreenToClient(hwnd, &lpds->ptDrop);
        goto SendQueryDrop;
    }

    fNC = FALSE;                /* dropping in client area */

    for (hwndT = GetWindow(hwnd, GW_CHILD); hwndT && !hCurT; hwndT = GetWindow(hwndT, GW_HWNDNEXT)) {

        if (!IsWindowVisible(hwndT))    /* Ignore invisible windows */
            continue;

        GetWindowRect(hwndT, &rc);
        if (!PtInRect(&rc, pt))         /* not in window? skip it*/
            continue;

        if (!IsWindowEnabled(hwndT))
            /* If point is in a disabled, visible window, get the heck out. No
             * need to check further since no drops allowed here. */
            break;

        /* recursively search child windows for the drop place */
        hCurT = QueryDropObject(hwndT, lpds);

        /* don't look at windows below this one in the zorder
         */
        break;
    }

    if (!hCurT) {
        /* there are no children who are in the right place or who want
         * drops... convert the point into client coordinates of the
         * current window.  Because of the recursion, this is already
         * done if a child window grabbed the drop. */
        ScreenToClient(hwnd, &lpds->ptDrop);

SendQueryDrop:
        lpds->hwndSink = hwnd;
        hCurT = (HCURSOR)SendMessage(hwnd, WM_QUERYDROPOBJECT, fNC, (LPARAM)lpds);

        /* restore drop point to screen coordinates if this window won't take
         * drops */
        if (!hCurT)
            lpds->ptDrop = pt;
    }

    return hCurT;
}

//=====================================================================
// Multile Drag show
//=====================================================================

void _MultipleDragShow(LPDAD_DRAGCONTEXT pdadc, BOOL bShow)
{
    HDC hDC;
    int nRect;
    RECT rc;
    int cxScreen = GetSystemMetrics(SM_CXSCREEN);
    int cyScreen = GetSystemMetrics(SM_CYSCREEN);

    if ((bShow && pdadc->_ds.mlt.bShown) || (!bShow && !pdadc->_ds.mlt.bShown))
    {
        return;
    }

    pdadc->_ds.mlt.bShown = bShow;

    hDC = GetDCEx(pdadc->_ds.hwndLock, NULL, DCX_WINDOW | DCX_CACHE |
        DCX_LOCKWINDOWUPDATE | DCX_CLIPSIBLINGS);

    for (nRect = pdadc->_ds.mlt.nRects - 1; nRect >= 0; --nRect)
    {
        rc = pdadc->_ds.mlt.pRect[nRect];
        OffsetRect(&rc, pdadc->_ds.mlt.ptNow.x-pdadc->_ds.mlt.ptOffset.x, pdadc->_ds.mlt.ptNow.y-pdadc->_ds.mlt.ptOffset.y);
        if (rc.left < cxScreen && rc.right > 0 &&
            rc.top < cyScreen && rc.bottom > 0)
        {
            DrawFocusRect(hDC, &rc);
        }
    }
    ReleaseDC(pdadc->_ds.hwndLock, hDC);
}


void _MultipleDragStart(LPDAD_DRAGCONTEXT pdadc, HWND hwndLock, LPRECT aRect, int nRects, POINT ptStart, POINT ptOffset)
{
    pdadc->_ds.mlt.bShown = FALSE;
    pdadc->_ds.mlt.pRect = aRect;
    pdadc->_ds.mlt.nRects = nRects;
    pdadc->_ds.mlt.ptOffset = ptOffset;
    pdadc->_ds.mlt.ptNow = ptStart;
}


void _MultipleDragMove(LPDAD_DRAGCONTEXT pdadc, POINT ptNew)
{
    BOOL bShown;

    // nothing has changed.  bail
    if (pdadc->_ds.mlt.bShown &&
        pdadc->_ds.mlt.ptNow.x == ptNew.x &&
        pdadc->_ds.mlt.ptNow.y == ptNew.y)
        return;

    bShown = pdadc->_ds.mlt.bShown;
    _MultipleDragShow(pdadc, FALSE);
    pdadc->_ds.mlt.ptNow = ptNew;

    /* Only show the drag if it was already shown.
    */
    if (bShown)
    {
        _MultipleDragShow(pdadc, TRUE);
    }
}

//=====================================================================
// DAD
//=====================================================================

//
// WARNING: s_pdadc MUST be in shared data section.
//
DAD_DRAGCONTEXT * s_pdadc = NULL;
UINT g_cRev = 0;

//
// Read 'Notes' in CDropSource_GiveFeedback for detail about this
// g_fDraggingOverSource flag, which is TRUE only if we are dragging
// over the source window itself with left mouse button
// (background and large/small icon mode only).
//
BOOL g_fDraggingOverSource = FALSE;     // shared

#pragma data_seg(DATASEG_PERINSTANCE)
struct {
    HIMAGELIST  _himl;
    UINT        _cRev;  // == g_cRev if it is still valid
    int         _aindex[DCID_MAX]; // will be initialized.
    HCURSOR     _ahcur[DCID_MAX];
    POINT       _aptHotSpot[DCID_MAX];
} s_cursors = { NULL, 0, /* { -1, -1, -1, -1, -1 } */ };
#pragma data_seg()

//
//  We don't want to destroy the cached cursors now. We simply increment
// g_cRef (global) to make it different from s_cursors._cRef.
//
void DAD_InvalidateCursors(void)
{
    g_cRev++;
}

void _SetDragContext(LPDAD_DRAGCONTEXT pdadc)
{
    if (pdadc)
    {
        s_pdadc = pdadc;
        pdadc->_idProcess = GetCurrentProcessId();
        pdadc->_idThread  = GetCurrentThreadId();

        //
        //  If the cached cursors are invalidated (by DAD_InvalidateCursors)
        // we need to destroy the old one and recreate the new one.
        //
        if (s_cursors._himl && (s_cursors._cRev!=g_cRev)) {
            _DestroyCachedCursors();
        }

        if (!s_cursors._himl)
        {
            UINT uFlags = ILC_MASK | ILC_SHARED;
            HDC hdc;

            //
            // if this is not a palette device, use a DDB for the imagelist
            // this is important when displaying high-color cursors
            //
            hdc = GetDC(NULL);
            if (!(GetDeviceCaps(hdc, RASTERCAPS) & RC_PALETTE))
            {
                uFlags |= ILC_COLORDDB;
            }
            ReleaseDC(NULL, hdc);

            s_cursors._himl = ImageList_Create(
                GetSystemMetrics(SM_CXCURSOR),
                GetSystemMetrics(SM_CYCURSOR),
                uFlags, 1, 0);

            s_cursors._cRev = g_cRev;

            // We need to initialize s_cursors._aindex[*]
            _MapCursorIDToImageListIndex(DCID_INVALID);
        }
    }
    else
    {
        if (s_pdadc)
        {
            if (s_pdadc->fImage)
            {
                ImageList_EndDrag();
            }

            Free(s_pdadc);
            s_pdadc=NULL;
        }
    }
}

// tell the drag source to hide or unhide the drag image to allow
// the destination to do drawing (unlock the screen)
//
// in:
//      bShow   FALSE   - hide the drag image, allow drawing
//              TRUE    - show the drag image, no drawing allowed after this

BOOL WINAPI DAD_ShowDragImage(BOOL bShow)
{
    BOOL fOld;

    if (!s_pdadc || !s_pdadc->_ds.bDragging)
    {
        return FALSE;
    }

    fOld = s_pdadc->_ds.bLocked;

    //
    // If we are going to show the drag image, lock the target window.
    //
    if (bShow && !s_pdadc->_ds.bLocked)
    {
        UpdateWindow(s_pdadc->_ds.hwndLock);
        LockWindowUpdate(s_pdadc->_ds.hwndLock);
        s_pdadc->_ds.bLocked = TRUE;
    }

    if (s_pdadc->_ds.bSingle)
    {
        ImageList_DragShowNolock(bShow);
    }
    else
    {
        _MultipleDragShow(s_pdadc, bShow);
    }

    //
    // If we have just hide the drag image, unlock the target window.
    //
    if (!bShow && s_pdadc->_ds.bLocked)
    {
        LockWindowUpdate(NULL);
        s_pdadc->_ds.bLocked = FALSE;
    }

    return fOld;
}


#define SCROLLDELAY 250         // 1/4 second


BOOL DAD_IsDragging()
{
    return (s_pdadc && s_pdadc->_ds.bDragging);
}

void _DestroyCachedCursors()
{
    int i;

    if (s_cursors._himl) {
        ImageList_Destroy(s_cursors._himl);
        s_cursors._himl = NULL;
    }

    for (i=0 ; i<DCID_MAX ; i++) {
        if (s_cursors._ahcur[i])
        {
            DestroyCursor(s_cursors._ahcur[i]);
            s_cursors._ahcur[i] = NULL;
        }
    }
}

void DAD_ProcessDetach(void)
{
    if (s_pdadc && s_pdadc->_idProcess==GetCurrentProcessId())
    {
        DAD_SetDragImage(NULL, NULL);
    }

    _DestroyCachedCursors();
}

void DAD_ThreadDetach(void)
{
    if (s_pdadc && s_pdadc->_idProcess==GetCurrentProcessId() && s_pdadc->_idThread==GetCurrentThreadId())
    {
        DAD_SetDragImage(NULL, NULL);
    }
}

BOOL _SetDragImage(HIMAGELIST himl, int index, POINT * pptOffset)
{
    if (himl)
    {
        DAD_DRAGCONTEXT * pdadc;
        // We are setting
        if (s_pdadc) {
            return FALSE;
        }

        pdadc = Alloc(SIZEOF(DAD_DRAGCONTEXT));
        if (pdadc)
        {
            pdadc->fImage = TRUE;
            if (pptOffset) {
                // Avoid the flicker by always pass even coords
                pdadc->ptOffset.x = (pptOffset->x & ~1);
                pdadc->ptOffset.y = (pptOffset->y & ~1);
            }

            ImageList_BeginDrag(himl, index, pdadc->ptOffset.x, pdadc->ptOffset.y);
        }
        _SetDragContext(pdadc);
    }
    else
    {
        // We are unsetting
        _SetDragContext(NULL);
    }
    return TRUE;
}

BOOL WINAPI DAD_SetDragImage(HIMAGELIST him, POINT * pptOffset)
{
    //
    // DAD_SetDragImage(-1, NULL) means "clear the drag image only
    //  if the image is set by this thread"
    //
    if (him == (HIMAGELIST)-1)
    {
        BOOL fThisThreadHasImage=FALSE;
        ENTERCRITICAL;
        if (s_pdadc && s_pdadc->_idThread == GetCurrentThreadId())
        {
            fThisThreadHasImage=TRUE;
        }
        LEAVECRITICAL;

        if (fThisThreadHasImage)
        {
            return _SetDragImage(NULL, 0, NULL);
        }
        return FALSE;
    }

    return _SetDragImage(him, 0, pptOffset);
}

#define ListView_IsIconView(hwndLV)    ((GetWindowLong(hwndLV, GWL_STYLE) & (UINT)LVS_TYPEMASK) == (UINT)LVS_ICON)

BOOL _SetMultiItemDragging(HWND hwndLV, int cItems, LPPOINT pptOffset)
{
    LPDAD_DRAGCONTEXT pdadc = NULL;     // assume error
    BOOL fRet = FALSE;

    if (s_pdadc) {
        return FALSE;
    }

    // Multiple item drag
    pdadc = Alloc(SIZEOF(DAD_DRAGCONTEXT)-SIZEOF(RECT) + 2*cItems*SIZEOF(RECT));
    if (pdadc)
    {
        POINT ptTemp;
        int iLast, iNext;
        int cxScreen, cyScreen;
        LPRECT prcNext;

        pdadc->cItems = 0;
        Assert(pdadc->fImage == FALSE);

        ptTemp.x = ptTemp.y = 0;
        ClientToScreen(hwndLV, &ptTemp);

        cxScreen = GetSystemMetrics(SM_CXSCREEN);
        cyScreen = GetSystemMetrics(SM_CYSCREEN);
        for (iNext=cItems-1, iLast=-1, prcNext=pdadc->arc; iNext>=0;
                --iNext)
        {
            iLast = ListView_GetNextItem(hwndLV, iLast, LVNI_SELECTED);
            if (iLast != -1) {

                ListView_GetItemRect(hwndLV, iLast, &prcNext[0], LVIR_ICON);
                OffsetRect(&prcNext[0], ptTemp.x, ptTemp.y);

                if (((prcNext[0].left - pptOffset->x) < cxScreen) &&
                    ((pptOffset->x - prcNext[0].right) < cxScreen) &&
                    ((prcNext[0].top - pptOffset->y) < cyScreen)) {

                    ListView_GetItemRect(hwndLV, iLast, &prcNext[1], LVIR_LABEL);
                    OffsetRect(&prcNext[1], ptTemp.x, ptTemp.y);
                    if ((pptOffset->y - prcNext[1].bottom) < cxScreen) {

                        //
                        // Fix 24857: Ask JoeB why we are drawing a bar instead of
                        //  a text rectangle.
                        //
                        prcNext[1].top = (prcNext[1].top + prcNext[1].bottom)/2;
                        prcNext[1].bottom = prcNext[1].top + 2;
                        prcNext += 2;
                        pdadc->cItems += 2;
                    }
                }
            }
        }

        // Avoid the flicker by always pass even coords
        pdadc->ptOffset.x = (pptOffset->x & ~1);
        pdadc->ptOffset.y = (pptOffset->y & ~1);
        pdadc->hwndFrom = hwndLV;
        _SetDragContext(pdadc);
        fRet = TRUE;
    }
    return fRet;
}

//
//  This function allocate a shared memory block which contains either
// a set of images (currently always one) or a set of rectangles.
//
// Notes: NEVER think about making this function public!
//
BOOL WINAPI DAD_SetDragImageFromListView(HWND hwndLV, POINT ptOffset)
{
    POINT ptTemp;
    HIMAGELIST himl;

    //
    // Count the number of selected items.
    //
    int cItems = ListView_GetSelectedCount(hwndLV);

    switch (cItems)
    {
    case 0:
        // There's nothing to drag
        break;

    case 1:
        if (NULL != (himl = ListView_CreateDragImage(hwndLV,
                ListView_GetNextItem(hwndLV, -1, LVNI_SELECTED), &ptTemp)))
        {
            ClientToScreen(hwndLV, &ptTemp);
            ptOffset.x -= ptTemp.x;
            ptOffset.y -= ptTemp.y;
            _SetDragImage(himl, 0, &ptOffset);
            ImageList_Destroy(himl);
            return TRUE;
        }
        break;

    default:
        return _SetMultiItemDragging(hwndLV, cItems, &ptOffset);
    }

    return FALSE;
}




BOOL WINAPI DAD_DragEnterEx(HWND hwndTarget, const POINT ptStart)
{
    if (s_pdadc)
    {
        DAD_SetDragCursor(DCID_INVALID);
        s_pdadc->_ds.bDragging = TRUE;
        s_pdadc->_ds.bSingle = s_pdadc->fImage;
        s_pdadc->_ds.hwndLock = hwndTarget ? hwndTarget : GetDesktopWindow();
        s_pdadc->_ds.bLocked = FALSE;
        s_pdadc->_ds.idThreadEntered = GetCurrentThreadId();

        if (s_pdadc->fImage)
        {

            // Avoid the flicker by always pass even coords
            ImageList_DragEnter(hwndTarget, ptStart.x & ~1, ptStart.y & ~1);
        }
        else
        {
            _MultipleDragStart(s_pdadc, hwndTarget, s_pdadc->arc, s_pdadc->cItems, ptStart, s_pdadc->ptOffset);
        }

        //
        // We should always show the image whenever this function is called.
        //
        DAD_ShowDragImage(TRUE);
    }
    return TRUE;
}

BOOL WINAPI DAD_DragEnter(HWND hwndTarget)
{
    POINT ptStart;

    GetCursorPos(&ptStart);
    if (hwndTarget) {
        ScreenToClient(hwndTarget, &ptStart);
    }

    return DAD_DragEnterEx(hwndTarget, ptStart);
}

BOOL WINAPI DAD_DragMove(POINT pt)
{
    if (s_pdadc)
    {
        // Avoid the flicker by always pass even coords
        pt.x &= ~1;
        pt.y &= ~1;

        if (s_pdadc->fImage)
        {
            ImageList_DragMove(pt.x, pt.y);
        }
        else
        {
            _MultipleDragMove(s_pdadc, pt);
        }
    }
    return TRUE;
}

BOOL WINAPI DAD_DragLeave()
{
    if ( s_pdadc && s_pdadc->_ds.bDragging &&
         s_pdadc->_ds.idThreadEntered == GetCurrentThreadId() )
    {
        DAD_ShowDragImage(FALSE);
        if (s_pdadc->fImage)
        {
            ImageList_DragLeave(s_pdadc->_ds.hwndLock);
        }
        s_pdadc->_ds.bDragging = FALSE;
    }
    return TRUE;
}

//
//  This function returns TRUE, if we are dragging an image. It means
// you have called either DAD_SetDragImage (with him != NULL) or
// DAD_SetDragImageFromListview.
//
BOOL DAD_IsDraggingImage(void)
{
    return (s_pdadc && s_pdadc->fImage && s_pdadc->_ds.bDragging);
}

HBITMAP CreateColorBitmap(int cx, int cy)
{
    HDC hdc;
    HBITMAP hbm;

    hdc = GetDC(NULL);
    hbm = CreateCompatibleBitmap(hdc, cx, cy);
    ReleaseDC(NULL, hdc);

    return hbm;
}

#define CreateMonoBitmap( cx,  cy) CreateBitmap(cx, cy, 1, 1, NULL)
typedef WORD CURMASK;
#define _BitSizeOf(x) (SIZEOF(x)*8)

void _GetCursorLowerRight(HCURSOR hcursor, int * px, int * py, POINT *pptHotSpot)
{
    ICONINFO iconinfo;
    CURMASK CurMask[16*8];
    BITMAP bm;
    int i;
    int xFine = 16;

    GetIconInfo(hcursor, &iconinfo);
    GetObject(iconinfo.hbmMask, SIZEOF(bm), (LPTSTR)&bm);
    GetBitmapBits(iconinfo.hbmMask, SIZEOF(CurMask), CurMask);
    pptHotSpot->x = iconinfo.xHotspot;
    pptHotSpot->y = iconinfo.yHotspot;
    if (iconinfo.hbmColor) {
        i = (int)(bm.bmWidth * bm.bmHeight / _BitSizeOf(CURMASK) - 1);
    } else {
        i = (int)(bm.bmWidth * (bm.bmHeight/2) / _BitSizeOf(CURMASK) - 1);
    }

    if ( i >= SIZEOF(CurMask)) i = SIZEOF(CurMask) -1;
    
    // BUGBUG: this assumes that the first pixel encountered on this bottom
    // up/right to left search will be reasonably close to the rightmost pixel
    // which for all of our cursors is correct, but it not necessarly correct.
    
    // also, it assumes the cursor has a good mask... not like the IBeam XOR only 
    // cursor
    for (; i >= 0; i--)   {
        if (CurMask[i] != 0xFFFF) {
            // this is only accurate to 16 pixels... which is a big gap..
            // so let's try to be a bit more accurate.
            int j;
            DWORD dwMask;
            
            for (j = 0; j < 16; j++, xFine--) {
                if (j < 8) {
                    dwMask = (1 << (8 + j));
                } else {
                    dwMask = (1 << (j - 8));
                }
                if (!(CurMask[i] & dwMask))
                    break;
            }
            Assert(j < 16);
            break;
        }
    }
    
    if (iconinfo.hbmColor) DeleteObject(iconinfo.hbmColor);
    if (iconinfo.hbmMask) DeleteObject(iconinfo.hbmMask);

    // Compute the pointer height
    // use width in both directions because the cursor is square, but the
    // height might be doubleheight if it's mono
    *py = ((i + 1) * _BitSizeOf(CURMASK)) / (int)bm.bmWidth;
    *px = ((i * _BitSizeOf(CURMASK)) % (int)bm.bmWidth) + xFine + 2; // hang it off a little
}

// this will draw iiMerge's image over iiMain on main's lower right.
BOOL _MergeIcons(HCURSOR hcursor, LPCTSTR idMerge, HBITMAP *phbmImage, HBITMAP *phbmMask, POINT* pptHotSpot)
{
    BITMAP bm;
    int xBitmap;
    int yBitmap;
    int xDraw;
    int yDraw;
    HDC hdcCursor, hdcBitmap;
    HBITMAP hbmTemp;
    HBITMAP hbmImage;
    HBITMAP hbmMask;
    int xCursor = GetSystemMetrics(SM_CXCURSOR);
    int yCursor = GetSystemMetrics(SM_CYCURSOR);
    HBITMAP hbmp;

    // find the lower corner of the cursor and put it there.
    // do this whether or not we have an idMerge because it will set the hotspot
    _GetCursorLowerRight(hcursor, &xDraw, &yDraw, pptHotSpot);
    if (idMerge != (LPCTSTR)-1) {
        hbmp = LoadImage(HINST_THISDLL, idMerge, IMAGE_BITMAP, 0, 0, 0);
        if (hbmp) {
            GetObject(hbmp, SIZEOF(bm), &bm);
            xBitmap = bm.bmWidth;
            yBitmap = bm.bmHeight/2;

            if (xDraw + xBitmap > xCursor)
                xDraw = xCursor - xBitmap;
            if (yDraw + yBitmap > yCursor)
                yDraw = yCursor - yBitmap;
        }
    } else
        hbmp = NULL;


    hdcCursor = CreateCompatibleDC(NULL);

    hbmMask = CreateMonoBitmap(xCursor, yCursor);
    hbmImage = CreateColorBitmap(xCursor, yCursor);

    if (hdcCursor && hbmMask && hbmImage) {

        hbmTemp = SelectObject(hdcCursor, hbmImage);
        DrawIconEx(hdcCursor, 0, 0, hcursor, 0, 0, 0, NULL, DI_NORMAL);

        if (hbmp) {
            hdcBitmap = CreateCompatibleDC(NULL);
            SelectObject(hdcBitmap, hbmp);

            //blt the two bitmaps onto the color and mask bitmaps for the cursor
            BitBlt(hdcCursor, xDraw, yDraw, xBitmap, yBitmap, hdcBitmap, 0, 0, SRCCOPY);
        }

        SelectObject(hdcCursor, hbmMask);
        DrawIconEx(hdcCursor, 0, 0, hcursor, 0, 0, 0, NULL, DI_MASK);

        if (hbmp) {
            BitBlt(hdcCursor, xDraw, yDraw, xBitmap, yBitmap, hdcBitmap, 0, yBitmap, SRCCOPY);

            // select back in the old bitmaps
            SelectObject(hdcBitmap, hbmTemp);
            DeleteDC(hdcBitmap);
            DeleteObject(hbmp);
        }

        // select back in the old bitmaps
        SelectObject(hdcCursor, hbmTemp);
    }

    if (hdcCursor)
        DeleteDC(hdcCursor);

    *phbmImage = hbmImage;
    *phbmMask = hbmMask;
    return (hbmImage && hbmMask);
}

// this will take a cursor index and load
int _AddCursorToImageList(HCURSOR hcur, LPCTSTR idMerge, POINT *pptHotSpot)
{
    int iIndex;
    HBITMAP hbmImage, hbmMask;

    // merge in the plus or link arrow if it's specified
    if (_MergeIcons(hcur, idMerge, &hbmImage, &hbmMask, pptHotSpot)) {
        iIndex = ImageList_Add(s_cursors._himl, hbmImage, hbmMask);
    } else {
        iIndex = -1;
    }

    if (hbmImage)
        DeleteObject(hbmImage);
    if (hbmMask)
        DeleteObject(hbmMask);

    return iIndex;
}

int _MapCursorIDToImageListIndex(int idCur)
{
#pragma data_seg(".text", "CODE")
    const static struct {
        BOOL   fSystem;
        LPCTSTR idRes;
        LPCTSTR idMerge;
    } c_acurmap[DCID_MAX] = {
        { FALSE, MAKEINTRESOURCE(IDC_NULL), (LPCTSTR)-1},
        { TRUE, IDC_NO, (LPCTSTR)-1 },
        { TRUE, IDC_ARROW, (LPCTSTR)-1 },
        { TRUE, IDC_ARROW, MAKEINTRESOURCE(IDB_PLUS_MERGE) },
        { TRUE, IDC_ARROW, MAKEINTRESOURCE(IDB_LINK_MERGE) },
    };
#pragma data_seg()

    Assert(idCur>=DCID_INVALID && idCur<DCID_MAX);

    //
    // idCur==DCID_INVALID means "Initialize the image list index array".
    //
    if (idCur==DCID_INVALID)
    {
    int i;
        for (i=0 ; i<DCID_MAX ; i++) {
            s_cursors._aindex[i] = -1;
            }
        return -1;
        }

    if (s_cursors._aindex[idCur] == -1)
    {
        HINSTANCE hinst = c_acurmap[idCur].fSystem ? NULL : HINST_THISDLL;
        HCURSOR   hcur = LoadCursor(hinst, c_acurmap[idCur].idRes);

        s_cursors._aindex[idCur] = _AddCursorToImageList(hcur, c_acurmap[idCur].idMerge,
                                                         &s_cursors._aptHotSpot[idCur]);
    }

    return s_cursors._aindex[idCur];
}

void DAD_SetDragCursor(int idCursor)
{
    static int s_idCursor = -1;
    //
    // Ignore if we are dragging over ourselves.
    //
    if (DAD_IsDraggingImage() && (s_idCursor!=idCursor) )
    {
        POINT ptHotSpot;

        if (s_cursors._himl && (idCursor!=DCID_INVALID))
        {
            int iIndex = _MapCursorIDToImageListIndex(idCursor);
            if (iIndex != -1) {
                ImageList_GetDragImage(NULL, &ptHotSpot);
                ptHotSpot.x -= s_cursors._aptHotSpot[idCursor].x;
                ptHotSpot.y -= s_cursors._aptHotSpot[idCursor].y;
                if (ptHotSpot.x < 0)
                    ptHotSpot.x = 0;
                if (ptHotSpot.y < 0)
                    ptHotSpot.y = 0;
                ImageList_SetDragCursorImage(s_cursors._himl, iIndex, ptHotSpot.x, ptHotSpot.y);
            } else {
                Assert(0);
            }
        }

        s_idCursor = idCursor;
    }
}

HCURSOR SetCursorHotspot(HCURSOR hcur, POINT *ptHot)
{
    ICONINFO iconinfo;
    HCURSOR hcurHotspot;
    
    GetIconInfo(hcur, &iconinfo);
    iconinfo.xHotspot = ptHot->x;
    iconinfo.yHotspot = ptHot->y;
    iconinfo.fIcon = FALSE;
    hcurHotspot = (HCURSOR)CreateIconIndirect(&iconinfo);
    if (iconinfo.hbmColor) DeleteObject(iconinfo.hbmColor);
    if (iconinfo.hbmMask) DeleteObject(iconinfo.hbmMask);
    return hcurHotspot;
}

void _SetDropEffectCursor(int idCur)
{
    if (s_cursors._himl && (idCur!=DCID_INVALID))
    {
        if (!s_cursors._ahcur[idCur])
        {
            int iIndex = _MapCursorIDToImageListIndex(idCur);
            if (iIndex != -1)
            {
                HCURSOR hcurColor = ImageList_GetIcon(s_cursors._himl, iIndex, 0);
                //
                // On non C1_COLORCURSOR displays, CopyImage() will enforce
                // monochrome.  So on color cursor displays, we'll get colored
                // dragdrop pix.
                //
                HCURSOR hcurScreen = CopyImage(hcurColor, IMAGE_CURSOR,
                    0, 0, LR_COPYRETURNORG | LR_DEFAULTSIZE);

                HCURSOR hcurFinal = SetCursorHotspot(hcurScreen, &s_cursors._aptHotSpot[idCur]);
                
                if (hcurScreen != hcurColor) {
                    DestroyCursor(hcurColor);
                }
                if (hcurFinal)
                    DestroyCursor(hcurScreen);
                else 
                    hcurFinal = hcurScreen;

                s_cursors._ahcur[idCur] = hcurFinal;
            }
        }

        if (s_cursors._ahcur[idCur]) {
            //
            // This code assumes that SetCursor is pretty quick if it is
            // already set.
            //
            SetCursor(s_cursors._ahcur[idCur]);
        }
    }
}

int _MapEffectToId(DWORD dwEffect)
{
    int idCursor;

    // DebugMsg(DM_TRACE, "sh TR - DAD_GiveFeedBack dwEffect=%x", dwEffect);

    switch (dwEffect & (DROPEFFECT_COPY|DROPEFFECT_LINK|DROPEFFECT_MOVE))
    {
    case 0:
        idCursor = DCID_NO;
        break;

    case DROPEFFECT_COPY:
        idCursor = DCID_COPY;
        break;

    case DROPEFFECT_LINK:
        idCursor = DCID_LINK;
        break;

    case DROPEFFECT_MOVE:
        idCursor = DCID_MOVE;
        break;

    default:
        // if it's a right drag, we can have any effect... we'll
        // default to the arrow without merging in anything
//
// REVIEW: Our Defview's DragEnter code is lazy and does not pick the default.
// We'll fix it only if it causes some problem with OLE-apps.
//
#if 0
        if (GetKeyState(VK_LBUTTON) < 0)
        {
            // if the left button is down we should always have
            // one of the above
            Assert(0);
        }
#endif
        idCursor = DCID_MOVE;
        break;
    }

    return idCursor;
}

//-----------------------------------------------------------------

typedef struct {
    IDropSource dsrc;
    UINT cRef;
    DWORD grfInitialKeyState;
} CDropSource;

extern IDropSourceVtbl c_CDropSourceVtbl;       // forward decl

//
// Create an instance of CDropSource
//
HRESULT CDropSource_CreateInstance(IDropSource **ppdsrc)
{
    CDropSource *this = (CDropSource *)LocalAlloc(LPTR, SIZEOF(CDropSource));
    if (this)
    {
        this->dsrc.lpVtbl = &c_CDropSourceVtbl;
        this->cRef = 1;
        *ppdsrc = &this->dsrc;

        return NOERROR;
    }
    else
    {
        *ppdsrc = NULL;
        return E_OUTOFMEMORY;
    }
}

HRESULT CDropSource_QueryInterface(IDropSource *pdsrc, REFIID riid, LPVOID *ppvObj)
{
    CDropSource *this = IToClass(CDropSource, dsrc, pdsrc);

    if (IsEqualIID(riid, &IID_IDropSource) || IsEqualIID(riid, &IID_IUnknown))
    {
        *ppvObj = this;
        this->cRef++;
        return NOERROR;
    }

    *ppvObj = NULL;
    return E_NOINTERFACE;
}

ULONG CDropSource_AddRef(IDropSource *pdsrc)
{
    CDropSource *this = IToClass(CDropSource, dsrc, pdsrc);

    this->cRef++;
    return this->cRef;
}

void DAD_ShowCursor(BOOL fShow)
{
    static BOOL s_fCursorHidden = FALSE;

    if (fShow) {
        if (s_fCursorHidden)
        {
            ShowCursor(TRUE);
            s_fCursorHidden = FALSE;
        }
    } else {
        if (!s_fCursorHidden)
        {
            ShowCursor(FALSE);
            s_fCursorHidden = TRUE;
        }
    }
}

ULONG CDropSource_Release(IDropSource *pdsrc)
{
    CDropSource *this = IToClass(CDropSource, dsrc, pdsrc);

    this->cRef--;
    if (this->cRef > 0)
        return this->cRef;

    DAD_ShowCursor(TRUE); // just in case

    LocalFree((HLOCAL)this);

    return 0;
}

HRESULT CDropSource_QueryContinueDrag(IDropSource *pdsrc, BOOL fEscapePressed, DWORD grfKeyState)
{
    CDropSource *this = IToClass(CDropSource, dsrc, pdsrc);
    HRESULT hres = S_OK;

    if (fEscapePressed)
    {
        hres = DRAGDROP_S_CANCEL;
    }
    else
    {
        // initialize ourself with the drag begin button
        if (this->grfInitialKeyState == 0)
            this->grfInitialKeyState = (grfKeyState & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON));

        Assert(this->grfInitialKeyState);

        if (!(grfKeyState & this->grfInitialKeyState))
        {
            //
            // A button is released.
            //
            hres = DRAGDROP_S_DROP;     
        }
        else if (this->grfInitialKeyState != (grfKeyState & (MK_LBUTTON | MK_RBUTTON | MK_MBUTTON)))
        {
            //
            //  If the button state is changed (except the drop case, which we handle
            // above, cancel the drag&drop.
            //
            hres = DRAGDROP_S_CANCEL;
        }
    }

    if (hres != S_OK)
    {
        SetCursor(LoadCursor(NULL, IDC_ARROW));
        DAD_ShowCursor(TRUE);
        DAD_SetDragCursor(DCID_NULL);
    }

    return hres;
}

HRESULT CDropSource_GiveFeedback(IDropSource *pdsrc, DWORD dwEffect)
{
    CDropSource *this = IToClass(CDropSource, dsrc, pdsrc);
    int idCursor = _MapEffectToId(dwEffect);

    //
    // Notes:
    //
    //  OLE does not give us DROPEFFECT_MOVE even though our IDT::DragOver
    // returns it, if we haven't set that bit when we have called DoDragDrop.
    // Instead of arguing whether or not this is a bug or by-design of OLE,
    // we work around it. It is important to note that this hack around
    // g_fDraggingOverSource is purely visual hack. It won't affect the
    // actual drag&drop operations at all (DV_AlterEffect does it all).
    //
    // - SatoNa
    //
    if (idCursor == DCID_NO && g_fDraggingOverSource)
    {
        idCursor = DCID_MOVE;
    }

    //
    //  No need to merge the cursor, if we are not dragging over to
    // one of shell windows.
    //
    if (DAD_IsDraggingImage())
    {
        // Feedback for single (image) dragging
        DAD_ShowCursor(FALSE);
        DAD_SetDragCursor(idCursor);
    }
    else if (DAD_IsDragging())
    {
        // Feedback for multiple (rectangles) dragging
        _SetDropEffectCursor(idCursor);
        DAD_ShowCursor(TRUE);
        return NOERROR;
    }
    else
    {
        DAD_ShowCursor(TRUE);
    }

    return DRAGDROP_S_USEDEFAULTCURSORS;
}

#pragma data_seg(".text", "CODE")
IDropSourceVtbl c_CDropSourceVtbl = {
    CDropSource_QueryInterface,
    CDropSource_AddRef,
    CDropSource_Release,
    CDropSource_QueryContinueDrag,
    CDropSource_GiveFeedback
};
#pragma data_seg()