// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1998 Microsoft Corporation
// All rights reserved.
//
// This source code is only intended as a supplement to the
// Microsoft Foundation Classes Reference and related
// electronic documentation provided with the library.
// See these sources for detailed information regarding the
// Microsoft Foundation Classes product.

#include "stdafx.h"

#ifdef AFX_OLE4_SEG
#pragma code_seg(AFX_OLE4_SEG)
#endif

#ifdef _DEBUG
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// COleDropTarget implementation

AFX_DATADEF int COleDropTarget::nScrollInset;
AFX_DATADEF UINT COleDropTarget::nScrollDelay;
AFX_DATADEF UINT COleDropTarget::nScrollInterval;

COleDropTarget::COleDropTarget()
{
	// initialize local state
	m_hWnd = NULL;
	m_lpDataObject = NULL;
	m_nTimerID = MAKEWORD(-1, -1);

	AfxLockGlobals(CRIT_DROPTARGET);
	static BOOL bInitialized;
	if (!bInitialized)
	{
		// get scroll metrics from win.ini
		static const TCHAR szWindows[] = _T("windows");
		static const TCHAR szScrollDelay[] = _T("DragScrollDelay");
		static const TCHAR szScrollInset[] = _T("DragScrollInset");
		static const TCHAR szScrollInterval[] = _T("DragScrollInterval");

		nScrollInset = GetProfileInt(szWindows, szScrollInset, DD_DEFSCROLLINSET);
		nScrollDelay = GetProfileInt(szWindows, szScrollDelay, DD_DEFSCROLLDELAY);
		nScrollInterval = GetProfileInt(szWindows, szScrollInterval,
			DD_DEFSCROLLINTERVAL);

		// now initialized, no need to call Initialize any more
		bInitialized = TRUE;
	}
	AfxUnlockGlobals(CRIT_DROPTARGET);

	ASSERT_VALID(this);
}

COleDropTarget::~COleDropTarget()
{
	ASSERT_VALID(this);

	if (m_hWnd != NULL)
	{
		TRACE0("COleDropTarget::Revoke not called before destructor --\n");
		TRACE0("\tmay cause RIPs under debug Windows.\n");
		Revoke();
	}
}

BOOL COleDropTarget::Register(CWnd* pWnd)
{
	ASSERT_VALID(this);
	ASSERT(m_hWnd == NULL);     // registering drop target twice?
	ASSERT_VALID(pWnd);

	LPUNKNOWN lpUnknown = (LPUNKNOWN)GetInterface(&IID_IUnknown);
	ASSERT(lpUnknown != NULL);

	// the object must be locked externally to keep LRPC connections alive
	if (CoLockObjectExternal(lpUnknown, TRUE, FALSE) != S_OK)
		return FALSE;

	// connect the HWND to the IDropTarget implementation
	if (RegisterDragDrop(pWnd->m_hWnd,
		(LPDROPTARGET)GetInterface(&IID_IDropTarget)) != S_OK)
	{
		CoLockObjectExternal(lpUnknown, FALSE, FALSE);
		return FALSE;
	}

	// connect internal data
	m_hWnd = pWnd->m_hWnd;
	ASSERT(pWnd->m_pDropTarget == NULL);
	pWnd->m_pDropTarget = this;

	return TRUE;
}

void COleDropTarget::Revoke()
{
	ASSERT_VALID(this);
	ASSERT(m_lpDataObject == NULL);

	if (m_hWnd == NULL)
	{
		ASSERT(m_nTimerID == MAKEWORD(-1, -1));
		return;
	}

	// disconnect from OLE
	RevokeDragDrop(m_hWnd);
	CoLockObjectExternal((LPUNKNOWN)GetInterface(&IID_IUnknown), FALSE, TRUE);

	// disconnect internal data
	CWnd::FromHandle(m_hWnd)->m_pDropTarget = NULL;
	m_hWnd = NULL;
}

/////////////////////////////////////////////////////////////////////////////
// default implementation of drag/drop scrolling

DROPEFFECT COleDropTarget::OnDragScroll(CWnd* pWnd, DWORD dwKeyState,
	CPoint point)
{
	ASSERT_VALID(this);
	ASSERT_VALID(pWnd);

	// CWnds are allowed, but don't support autoscrolling
	if (!pWnd->IsKindOf(RUNTIME_CLASS(CView)))
		return DROPEFFECT_NONE;
	CView* pView = (CView*)pWnd;
	DROPEFFECT dropEffect = pView->OnDragScroll(dwKeyState, point);

	// DROPEFFECT_SCROLL means do the default
	if (dropEffect != DROPEFFECT_SCROLL)
		return dropEffect;

	// get client rectangle of destination window
	CRect rectClient;
	pWnd->GetClientRect(&rectClient);
	CRect rect = rectClient;

	// hit-test against inset region
	UINT nTimerID = MAKEWORD(-1, -1);
	rect.InflateRect(-nScrollInset, -nScrollInset);
	CSplitterWnd* pSplitter = NULL;
	if (rectClient.PtInRect(point) && !rect.PtInRect(point))
	{
		// determine which way to scroll along both X & Y axis
		if (point.x < rect.left)
			nTimerID = MAKEWORD(SB_LINEUP, HIBYTE(nTimerID));
		else if (point.x >= rect.right)
			nTimerID = MAKEWORD(SB_LINEDOWN, HIBYTE(nTimerID));
		if (point.y < rect.top)
			nTimerID = MAKEWORD(LOBYTE(nTimerID), SB_LINEUP);
		else if (point.y >= rect.bottom)
			nTimerID = MAKEWORD(LOBYTE(nTimerID), SB_LINEDOWN);
		ASSERT(nTimerID != MAKEWORD(-1, -1));

		// check for valid scroll first
		pSplitter = CView::GetParentSplitter(pView, FALSE);
		BOOL bEnableScroll = FALSE;
		if (pSplitter != NULL)
			bEnableScroll = pSplitter->DoScroll(pView, nTimerID, FALSE);
		else
			bEnableScroll = pView->OnScroll(nTimerID, 0, FALSE);
		if (!bEnableScroll)
			nTimerID = MAKEWORD(-1, -1);
	}

	if (nTimerID == MAKEWORD(-1, -1))
	{
		if (m_nTimerID != MAKEWORD(-1, -1))
		{
			// send fake OnDragEnter when transition from scroll->normal
			COleDataObject dataObject;
			dataObject.Attach(m_lpDataObject, FALSE);
			OnDragEnter(pWnd, &dataObject, dwKeyState, point);
			m_nTimerID = MAKEWORD(-1, -1);
		}
		return DROPEFFECT_NONE;
	}

	// save tick count when timer ID changes
	DWORD dwTick = GetTickCount();
	if (nTimerID != m_nTimerID)
	{
		m_dwLastTick = dwTick;
		m_nScrollDelay = nScrollDelay;
	}

	// scroll if necessary
	if (dwTick - m_dwLastTick > m_nScrollDelay)
	{
		if (pSplitter != NULL)
			pSplitter->DoScroll(pView, nTimerID, TRUE);
		else
			pView->OnScroll(nTimerID, 0, TRUE);
		m_dwLastTick = dwTick;
		m_nScrollDelay = nScrollInterval;
	}
	if (m_nTimerID == MAKEWORD(-1, -1))
	{
		// send fake OnDragLeave when transitioning from normal->scroll
		OnDragLeave(pWnd);
	}

	m_nTimerID = nTimerID;
	// check for force link
	if ((dwKeyState & (MK_CONTROL|MK_SHIFT)) == (MK_CONTROL|MK_SHIFT))
		dropEffect = DROPEFFECT_SCROLL|DROPEFFECT_LINK;
	// check for force copy
	else if ((dwKeyState & MK_CONTROL) == MK_CONTROL)
		dropEffect = DROPEFFECT_SCROLL|DROPEFFECT_COPY;
	// check for force move
	else if ((dwKeyState & MK_ALT) == MK_ALT ||
		(dwKeyState & MK_SHIFT) == MK_SHIFT)
		dropEffect = DROPEFFECT_SCROLL|DROPEFFECT_MOVE;
	// default -- recommended action is move
	else
		dropEffect = DROPEFFECT_SCROLL|DROPEFFECT_MOVE;
	return dropEffect;
}

/////////////////////////////////////////////////////////////////////////////
// COleDropTarget drop/ drop query handling

DROPEFFECT COleDropTarget::OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject,
	DWORD dwKeyState, CPoint point)
{
	ASSERT_VALID(this);

	if (!pWnd->IsKindOf(RUNTIME_CLASS(CView)))
		return DROPEFFECT_NONE;

	// default delegates to view
	CView* pView = (CView*)pWnd;
	ASSERT_VALID(pView);
	return pView->OnDragEnter(pDataObject, dwKeyState, point);
}

DROPEFFECT COleDropTarget::OnDragOver(CWnd* pWnd, COleDataObject* pDataObject,
	DWORD dwKeyState, CPoint point)
{
	ASSERT_VALID(this);

	if (!pWnd->IsKindOf(RUNTIME_CLASS(CView)))
		return DROPEFFECT_NONE;

	// default delegates to view
	CView* pView = (CView*)pWnd;
	ASSERT_VALID(pView);
	return pView->OnDragOver(pDataObject, dwKeyState, point);
}

BOOL COleDropTarget::OnDrop(CWnd* pWnd, COleDataObject* pDataObject,
	DROPEFFECT dropEffect, CPoint point)
{
	ASSERT_VALID(this);

	if (!pWnd->IsKindOf(RUNTIME_CLASS(CView)))
		return DROPEFFECT_NONE;

	// default delegates to view
	CView* pView = (CView*)pWnd;
	ASSERT_VALID(pView);
	return pView->OnDrop(pDataObject, dropEffect, point);
}

DROPEFFECT COleDropTarget::OnDropEx(CWnd* pWnd, COleDataObject* pDataObject,
	DROPEFFECT dropEffect, DROPEFFECT dropEffectList, CPoint point)
{
	ASSERT_VALID(this);

	if (!pWnd->IsKindOf(RUNTIME_CLASS(CView)))
		return (DROPEFFECT)-1;  // not implemented

	// default delegates to view
	CView* pView = (CView*)pWnd;
	ASSERT_VALID(pView);
	return pView->OnDropEx(pDataObject, dropEffect, dropEffectList, point);
}

void COleDropTarget::OnDragLeave(CWnd* pWnd)
{
	ASSERT_VALID(this);

	if (!pWnd->IsKindOf(RUNTIME_CLASS(CView)))
		return;

	// default delegates to view
	CView* pView = (CView*)pWnd;
	ASSERT_VALID(pView);
	pView->OnDragLeave();
	return;
}

/////////////////////////////////////////////////////////////////////////////
// COleDropTarget::COleDropTarget implementation

BEGIN_INTERFACE_MAP(COleDropTarget, CCmdTarget)
	INTERFACE_PART(COleDropTarget, IID_IDropTarget, DropTarget)
END_INTERFACE_MAP()

STDMETHODIMP_(ULONG) COleDropTarget::XDropTarget::AddRef()
{
	METHOD_PROLOGUE_EX_(COleDropTarget, DropTarget)
	return pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) COleDropTarget::XDropTarget::Release()
{
	METHOD_PROLOGUE_EX_(COleDropTarget, DropTarget)
	return pThis->ExternalRelease();
}

STDMETHODIMP COleDropTarget::XDropTarget::QueryInterface(
	REFIID iid, LPVOID* ppvObj)
{
	METHOD_PROLOGUE_EX_(COleDropTarget, DropTarget)
	return pThis->ExternalQueryInterface(&iid, ppvObj);
}

// helper to filter out invalid DROPEFFECTs
AFX_STATIC DROPEFFECT AFXAPI
_AfxFilterDropEffect(DROPEFFECT dropEffect, DROPEFFECT dwEffects)
{
	// return allowed dropEffect and DROPEFFECT_NONE
	if ((dropEffect & dwEffects) != 0)
		return dropEffect;

	// map common operations (copy/move) to alternates, but give negative
	//  feedback for DROPEFFECT_LINK.
	switch (dropEffect)
	{
	case DROPEFFECT_COPY:
		if (dwEffects & DROPEFFECT_MOVE)
			return DROPEFFECT_MOVE;
		else if (dwEffects & DROPEFFECT_LINK)
			return DROPEFFECT_LINK;
		break;
	case DROPEFFECT_MOVE:
		if (dwEffects & DROPEFFECT_COPY)
			return DROPEFFECT_COPY;
		else if (dwEffects & DROPEFFECT_LINK)
			return DROPEFFECT_LINK;
		break;
	case DROPEFFECT_LINK:
		break;
	}

	return DROPEFFECT_NONE;
}

STDMETHODIMP COleDropTarget::XDropTarget::DragEnter(THIS_ LPDATAOBJECT lpDataObject,
	DWORD dwKeyState, POINTL pt, LPDWORD pdwEffect)
{
	METHOD_PROLOGUE_EX(COleDropTarget, DropTarget)
	ASSERT_VALID(pThis);

	ASSERT(pdwEffect != NULL);
	ASSERT(lpDataObject != NULL);

	SCODE sc = E_UNEXPECTED;
	TRY
	{
		// cache lpDataObject
		lpDataObject->AddRef();
		RELEASE(pThis->m_lpDataObject);
		pThis->m_lpDataObject = lpDataObject;

		CWnd* pWnd = CWnd::FromHandle(pThis->m_hWnd);
		ASSERT_VALID(pWnd);
		CPoint point((int)pt.x, (int)pt.y);
		pWnd->ScreenToClient(&point);

		// check first for entering scroll area
		DROPEFFECT dropEffect = pThis->OnDragScroll(pWnd, dwKeyState, point);
		if ((dropEffect & DROPEFFECT_SCROLL) == 0)
		{
			// funnel through OnDragEnter since not in scroll region
			COleDataObject dataObject;
			dataObject.Attach(lpDataObject, FALSE);
			dropEffect = pThis->OnDragEnter(pWnd, &dataObject, dwKeyState,
				point);
		}
		*pdwEffect = _AfxFilterDropEffect(dropEffect, *pdwEffect);
		sc = S_OK;
	}
	END_TRY

	return sc;
}

STDMETHODIMP COleDropTarget::XDropTarget::DragOver(THIS_ DWORD dwKeyState,
	POINTL pt, LPDWORD pdwEffect)
{
	METHOD_PROLOGUE_EX(COleDropTarget, DropTarget)
	ASSERT_VALID(pThis);

	ASSERT(pdwEffect != NULL);
	ASSERT(pThis->m_lpDataObject != NULL);

	SCODE sc = E_UNEXPECTED;
	TRY
	{
		CWnd* pWnd = CWnd::FromHandle(pThis->m_hWnd);
		ASSERT_VALID(pWnd);
		CPoint point((int)pt.x, (int)pt.y);
		pWnd->ScreenToClient(&point);

		// check first for entering scroll area
		DROPEFFECT dropEffect = pThis->OnDragScroll(pWnd, dwKeyState, point);
		if ((dropEffect & DROPEFFECT_SCROLL) == 0)
		{
			// funnel through OnDragOver
			COleDataObject dataObject;
			dataObject.Attach(pThis->m_lpDataObject, FALSE);
			dropEffect = pThis->OnDragOver(pWnd, &dataObject, dwKeyState,
				point);
		}
		*pdwEffect = _AfxFilterDropEffect(dropEffect, *pdwEffect);
		sc = S_OK;
	}
	END_TRY

	return sc;
}

STDMETHODIMP COleDropTarget::XDropTarget::DragLeave(THIS)
{
	METHOD_PROLOGUE_EX(COleDropTarget, DropTarget)
	ASSERT_VALID(pThis);

	CWnd* pWnd = CWnd::FromHandle(pThis->m_hWnd);
	ASSERT_VALID(pWnd);

	// cancel drag scrolling
	pThis->m_nTimerID = MAKEWORD(-1, -1);

	// allow derivative to do own cleanup
	COleDataObject dataObject;
	dataObject.Attach(pThis->m_lpDataObject, FALSE);
	pThis->OnDragLeave(pWnd);

	// release cached data object
	RELEASE(pThis->m_lpDataObject);

	return S_OK;
}

STDMETHODIMP COleDropTarget::XDropTarget::Drop(THIS_ LPDATAOBJECT lpDataObject,
	DWORD dwKeyState, POINTL pt, LPDWORD pdwEffect)
{
	METHOD_PROLOGUE_EX(COleDropTarget, DropTarget)
	ASSERT_VALID(pThis);

	ASSERT(pdwEffect != NULL);
	ASSERT(lpDataObject != NULL);

	SCODE sc = E_UNEXPECTED;
	TRY
	{
		// cancel drag scrolling
		pThis->m_nTimerID = MAKEWORD(-1, -1);

		// prepare for call to OnDragOver
		CWnd* pWnd = CWnd::FromHandle(pThis->m_hWnd);
		ASSERT_VALID(pWnd);
		COleDataObject dataObject;
		dataObject.Attach(lpDataObject, FALSE);
		CPoint point((int)pt.x, (int)pt.y);
		pWnd->ScreenToClient(&point);

		// verify that drop is legal
		DROPEFFECT dropEffect = _AfxFilterDropEffect(pThis->OnDragOver(pWnd,
			&dataObject, dwKeyState, point), *pdwEffect);

		// execute the drop (try OnDropEx then OnDrop for backward compatibility)
		DROPEFFECT temp = pThis->OnDropEx(pWnd, &dataObject, dropEffect, *pdwEffect, point);
		if (temp != -1)
		{
			// OnDropEx was implemented, return its drop effect
			dropEffect = temp;
		}
		else if (dropEffect != DROPEFFECT_NONE)
		{
			// OnDropEx not implemented
			if (!pThis->OnDrop(pWnd, &dataObject, dropEffect, point))
				dropEffect = DROPEFFECT_NONE;
		}
		else
		{
			// drop not accepted, allow cleanup
			pThis->OnDragLeave(pWnd);
		}

		// release potentially cached data object
		RELEASE(pThis->m_lpDataObject);
		*pdwEffect = dropEffect;
		sc = S_OK;
	}
	END_TRY

	return sc;
}

/////////////////////////////////////////////////////////////////////////////
// COleDropTarget diagnostics

#ifdef _DEBUG
void COleDropTarget::AssertValid() const
{
	CCmdTarget::AssertValid();
	if (m_lpDataObject != NULL)
		CWnd::FromHandle(m_hWnd)->AssertValid();
}

void COleDropTarget::Dump(CDumpContext& dc) const
{
	CCmdTarget::Dump(dc);

	dc << "m_hWnd = " << m_hWnd;
	dc << "\nm_lpDataObject = " << m_lpDataObject;
	dc << "\nm_nTimerID = " << m_nTimerID;
	dc << "\nm_dwLastTick = " << m_dwLastTick;

	dc << "\n";
}
#endif

/////////////////////////////////////////////////////////////////////////////
