/*
 * IDROPTGT.CPP
 * Patron Chapter 15
 *
 * Implementation of a DropTarget object
 *
 * Copyright (c)1993-1994 Microsoft Corporation, All Rights Reserved
 *
 * Kraig Brockschmidt, Software Design Engineer
 * Microsoft Systems Developer Relations
 *
 * Internet  :  kraigb@microsoft.com
 * Compuserve:  >INTERNET:kraigb@microsoft.com
 */


#include "patron.h"



/*
 * CDropTarget::CDropTarget
 * CDropTarget::~CDropTarget
 *
 * Constructor Parameters:
 *  pDoc            PCPatronDoc of the window containing us.
 */

CDropTarget::CDropTarget(PCPatronDoc pDoc)
    {
    m_cRef=0;
    m_pDoc=pDoc;

    m_pIDataObject=NULL;
    return;
    }


CDropTarget::~CDropTarget(void)
    {
    return;
    }




/*
 * CDropTarget::QueryInterface
 * CDropTarget::AddRef
 * CDropTarget::Release
 *
 * Purpose:
 *  IUnknown members for CDropTarget object.
 */

STDMETHODIMP CDropTarget::QueryInterface(REFIID riid, PPVOID ppv)
    {
    *ppv=NULL;

    if (IID_IUnknown==riid || IID_IDropTarget==riid)
        *ppv=this;

    if (NULL!=*ppv)
        {
        ((LPUNKNOWN)*ppv)->AddRef();
        return NOERROR;
        }

    return ResultFromScode(E_NOINTERFACE);
    }


STDMETHODIMP_(ULONG) CDropTarget::AddRef(void)
    {
    return ++m_cRef;
    }

STDMETHODIMP_(ULONG) CDropTarget::Release(void)
    {
    ULONG           cRefT;

    cRefT=--m_cRef;

    if (0L==m_cRef)
        delete this;

    return cRefT;
    }





/*
 * CDropTarget::DragEnter
 *
 * Purpose:
 *  Indicates that data in a drag operation has been dragged over
 *  our window that's a potential target.  We are to decide if it's
 *  something in which we're interested.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT providing the source data.
 *  grfKeyState     DWORD flags: states of keys and mouse buttons.
 *  pt              POINTL coordinates in the client space of
 *                  the document.
 *  pdwEffect       LPDWORD into which we'll place the appropriate
 *                  effect flag for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragEnter(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    HWND            hWnd;
    FORMATETC       fe;
    STGMEDIUM       stm;
    UINT            uRet;

    m_fFeedback=FALSE;
    m_pIDataObject=NULL;

    if (!m_pDoc->FQueryPasteFromData(pIDataSource, &fe, NULL))
        {
        *pdwEffect=DROPEFFECT_NONE;
        return NOERROR;
        }

    //Check if we can link from this data object as well.
    ppg->m_fLinkAllowed
        =(NOERROR==OleQueryLinkFromData(pIDataSource));

    //We never allow it dragging in ourselves.
    ppg->m_fLinkAllowed &= !ppg->m_fDragSource;

    //Check if this is a valid drop point.
    uRet=ppg->UTestDroppablePoint(&pt);
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Default is move if we can, in fact drop here.
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        }

    m_pIDataObject=pIDataSource;
    m_pIDataObject->AddRef();

    /*
     * Determine the size of the data, if we can.  The default is
     * a small rectangle since we can't easily tell what size
     * something will be if we're pulling in a metafile or bitmap.
     * It's not a good idea to render it here with GetData just to
     * find that out. We only know the size if it's our own object
     * in which case a GetData will be fast.
     */

    if (fe.cfFormat==m_pDoc->m_cf)
        {
        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            PPATRONOBJECT   ppo;
            RECT            rc;

            ppo=(PPATRONOBJECT)GlobalLock(stm.hGlobal);

            SetRect(&rc, (int)ppo->szl.cx, -(int)ppo->szl.cy, 0, 0);
            RectConvertMappings(&rc, NULL, TRUE);
            SETSIZEL(m_szl, rc.left, rc.top);

            m_ptPick=ppo->ptlPick;
            m_fe=ppo->fe;

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }
    else
        {
        SETSIZEL(m_szl, 30, 30);
        m_ptPick.x=0;
        m_ptPick.y=0;
        m_fe.cfFormat=0;

        /*
         * Try to get CF_OBJECTDESCRIPTOR which might have a size
         * and a pick point.  If it exists, then always use the
         * point but still default to a 30*30 size if the sizes
         * are zero.
         */
        uRet=RegisterClipboardFormat(CF_OBJECTDESCRIPTOR);
        SETDefFormatEtc(fe, uRet, TYMED_HGLOBAL);

        if (SUCCEEDED(pIDataSource->GetData(&fe, &stm)))
            {
            LPOBJECTDESCRIPTOR  pOD;

            pOD=(LPOBJECTDESCRIPTOR)GlobalLock(stm.hGlobal);

            //Get the size, converting to LOMETRIC.
            if (0!=pOD->sizel.cx && 0!=pOD->sizel.cy)
                {
                XformSizeInHimetricToPixels(NULL, &pOD->sizel
                    , &m_szl);
                }

            //POINTL and SIZEL are interchangeable
            XformSizeInHimetricToPixels(NULL, (LPSIZEL)&pOD->pointl
                , (LPSIZEL)&m_ptPick);

            GlobalUnlock(stm.hGlobal);
            ReleaseStgMedium(&stm);
            }
        }


    //Bring the document window up front, show what a drop will do.
    hWnd=m_pDoc->Window();
    BringWindowToTop(hWnd);
    UpdateWindow(hWnd);

    ppg->m_uVScrollCode=NOVALUE;
    ppg->m_uHScrollCode=NOVALUE;
    m_fPendingRepaint=FALSE;

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }






/*
 * CDropTarget::DragOver
 *
 * Purpose:
 *  Indicates that the mouse was moved inside the window represented
 *  by this drop target.  This happens on every WM_MOUSEMOVE, so
 *  this function should be very efficient.
 *
 * Parameters:
 *  grfKeyState     DWORD providing the current keyboard and
 *                  mouse states
 *  pt              POINTL where the mouse currently is.
 *  pdwEffect       LPDWORD in which to store the effect flag
 *                  for this point.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragOver(DWORD grfKeyState, POINTL pt
    , LPDWORD pdwEffect)
    {
    PCPages     ppg=m_pDoc->m_pPG;
    UINT        uRet, uLast;
    UINT        xPos, yPos;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Check if this is still a valid point.  uRet used below as well
    uRet=ppg->UTestDroppablePoint(&pt);

    if (UDROP_NONE==uRet)
        *pdwEffect=DROPEFFECT_NONE;
    else
        {
        //Store these before possibly ORing in DROPEFFECT_SCROLL
        *pdwEffect=DROPEFFECT_MOVE;

        if (grfKeyState & MK_CONTROL)
            {
            if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
                *pdwEffect=DROPEFFECT_LINK;
            else
                *pdwEffect=DROPEFFECT_COPY;
            }
        }

    //If we haven't moved and we are not scrolling, then we're done.
    if ((pt.x-m_ptPick.x==m_ptLast.x)
        && (pt.y-m_ptPick.y==m_ptLast.y)
        && !((UDROP_INSETHORZ|UDROP_INSETVERT) & ppg->m_uLastTest))
        {
        return NOERROR;
        }

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    uLast=ppg->m_uLastTest;
    ppg->m_uLastTest=uRet;

    if (UDROP_NONE==uRet)
        {
        //If we're now an invalid point, better repaint as necessary
        if (m_fPendingRepaint)
            {
            UpdateWindow(ppg->m_hWnd);
            m_fPendingRepaint=FALSE;
            }

        ppg->m_uVScrollCode=NOVALUE;
        ppg->m_uHScrollCode=NOVALUE;
        m_fFeedback=FALSE;
        return NOERROR;
        }


    /*
     * Scrolling is a little tricky:  We get a DragOver pulse even
     * if we didn't move.  First we have to delay scrolling for
     * ppg->m_uScrollDelay clock ticks which we can determine using
     * GetTickCount.  Timers do not work here since we may not be
     * yielding to our message loop.
     *
     * Once we know we are scrolling then we determine if we
     * scroll again or if we reset the scrolling state.
     */

    if ((UDROP_INSETHORZ & uLast) && !(UDROP_INSETHORZ & uRet))
        ppg->m_uHScrollCode=NOVALUE;

    if (!(UDROP_INSETHORZ & uLast) && (UDROP_INSETHORZ & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uHScrollCode=(0!=(UDROP_INSETLEFT & uRet))
            ? SB_LINELEFT : SB_LINERIGHT; //Same as UP & DOWN codes.
        }

    if ((UDROP_INSETVERT & uLast) && !(UDROP_INSETVERT & uRet))
        ppg->m_uVScrollCode=NOVALUE;

    if (!(UDROP_INSETVERT & uLast) && (UDROP_INSETVERT & uRet))
        {
        ppg->m_dwTimeLast=GetTickCount();
        ppg->m_uVScrollCode=(0!=(UDROP_INSETTOP & uRet))
            ? SB_LINEUP : SB_LINEDOWN;
        }

    //Only change the last time if ALL scrolling stops.
    if (NOVALUE==ppg->m_uHScrollCode && NOVALUE==ppg->m_uVScrollCode)
        ppg->m_dwTimeLast=0L;

    //Set the scroll effect on any inset hit.
    if ((UDROP_INSETHORZ | UDROP_INSETVERT) & uRet)
        *pdwEffect |= DROPEFFECT_SCROLL;

    xPos=ppg->m_xPos;
    yPos=ppg->m_yPos;

    //Has the delay elapsed?  We can scroll if so
    if (ppg->m_dwTimeLast!=0
        && (GetTickCount()-ppg->m_dwTimeLast)
        > (DWORD)ppg->m_uScrollDelay)
        {
        if (NOVALUE!=ppg->m_uHScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_HSCROLL
                , ppg->m_uHScrollCode, 0L);
            }

        if (NOVALUE!=ppg->m_uVScrollCode)
            {
            m_fPendingRepaint=TRUE;
            SendMessage(ppg->m_hWnd, WM_VSCROLL
                , ppg->m_uVScrollCode, 0L);
            }
        }

    //If we didn't scroll but have a pending repaint, do it now.
    if (xPos==ppg->m_xPos && yPos==ppg->m_yPos && m_fPendingRepaint)
        {
        UpdateWindow(ppg->m_hWnd);
        m_fPendingRepaint=FALSE;
        }

    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    m_ptLast=pt;
    m_fFeedback=TRUE;
    ppg->DrawDropTargetRect(&pt, &m_szl);

    return NOERROR;
    }






/*
 * CDropTarget::DragLeave
 *
 * Purpose:
 *  Informs the drop target that the operation has left its window.
 *
 * Parameters:
 *  None
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::DragLeave(void)
    {
    PCPages         ppg=m_pDoc->m_pPG;

    if (NULL==m_pIDataObject)
        return NOERROR;

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //Remove the last feedback rectangle.
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_fFeedback=FALSE;
    m_pIDataObject->Release();
    return NOERROR;
    }





/*
 * CDropTarget::Drop
 *
 * Purpose:
 *  Instructs the drop target to paste the data that was just now
 *  dropped on it.
 *
 * Parameters:
 *  pIDataSource    LPDATAOBJECT from which we'll paste.
 *  grfKeyState     DWORD providing current keyboard/mouse state.
 *  pt              POINTL at which the drop occurred.
 *  pdwEffect       LPDWORD in which to store what you did.
 *
 * Return Value:
 *  HRESULT         NOERROR
 */

STDMETHODIMP CDropTarget::Drop(LPDATAOBJECT pIDataSource
    , DWORD grfKeyState, POINTL pt, LPDWORD pdwEffect)
    {
    PCPages         ppg=m_pDoc->m_pPG;
    BOOL            fRet=TRUE;
    FORMATETC       fe;
    TENANTTYPE      tType;
    PATRONOBJECT    po;
    POINT           ptS;

    *pdwEffect=DROPEFFECT_NONE;

    if (NULL==m_pIDataObject)
        return ResultFromScode(E_FAIL);

    if (UDROP_NONE==ppg->UTestDroppablePoint(&pt))
        return ResultFromScode(E_FAIL);

    //Stop scrolling
    ppg->m_uHScrollCode=NOVALUE;
    ppg->m_uVScrollCode=NOVALUE;

    if (m_fPendingRepaint)
        UpdateWindow(ppg->m_hWnd);

    //2.  Remove the UI feedback
    if (m_fFeedback)
        ppg->DrawDropTargetRect(&m_ptLast, &m_szl);

    m_pIDataObject->Release();


    /*
     * Check if we can do the paste, and if so, tell our pasting
     * mechanism exactly where to place us.
     */
    pt.x-=m_ptPick.x;
    pt.y-=m_ptPick.y;

    POINTFROMPOINTL(ptS, pt);
    ScreenToClient(ppg->Window(), &ptS);
    POINTLFROMPOINT(po.ptl, ptS);

    //This is true if we didn't see placement data in DragEnter
    if (0!=m_fe.cfFormat)
        {
        po.szl.cx=m_szl.cx;         //We stored these positive
        po.szl.cy=-m_szl.cy;
        }
    else
        SETSIZEL(po.szl, 0, 0); //Ask object for its size.

    //Adjust for scrolling and mapping mode.
    ppg->AdjustPosition(&po.ptl, &po.szl);


    /*
     * If we're in the same document and moving, then we can just
     * stuff the Pages' m_ptDrop which will move us and return.
     */
    if (ppg->m_fDragSource && !(grfKeyState & MK_CONTROL))
        {
        *pdwEffect=DROPEFFECT_MOVE;
        ppg->m_fMoveInPage=TRUE;
        ppg->m_ptDrop=po.ptl;
        return NOERROR;
        }

    /*
     * Otherwise, paste either from another document or from
     * the same document which will always be a copy to the new
     * point.
     */

    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        {
        if (ppg->m_fLinkAllowed && (grfKeyState & MK_SHIFT))
            *pdwEffect=DROPEFFECT_LINK;
        else
            *pdwEffect=DROPEFFECT_COPY;
        }

    ppg->m_fMoveInPage=FALSE;

    /*
     * We know linking is desired if effect flag is set, so this
     * function will get the FORMATETC for linking.  Otherwise
     * FQueryPasteFromData will get the other FORMATETC to use.
     */
    if (DROPEFFECT_LINK==*pdwEffect)
        {
        fRet=m_pDoc->FQueryPasteLinkFromData(pIDataSource, &fe
            , &tType);
        }
    else
        fRet=m_pDoc->FQueryPasteFromData(pIDataSource, &fe, &tType);

    if (fRet)
        {
        //Copy the real format if we have placement data.
        po.fe=(m_pDoc->m_cf==fe.cfFormat) ? m_fe : fe;

        //Flag FPasteFromData to use CF_OBJECTDESCRIPTOR
        fRet=m_pDoc->FPasteFromData(pIDataSource, &fe, tType
            , &po, 0, TRUE);
        }

    if (!fRet)
        return ResultFromScode(E_FAIL);


    *pdwEffect=DROPEFFECT_MOVE;

    if (grfKeyState & MK_CONTROL)
        *pdwEffect=DROPEFFECT_COPY;

    return NOERROR;
    }
