
#pragma once
#include <vector>
#include <shlobj.h>

using namespace std;

class CEnumFormatEtc : public IEnumFORMATETC
{
public:
	CEnumFormatEtc( const vector < FORMATETC >& vFormatEtc )
	{
		m_nRefCount = 0;
		m_nIndex = 0;
		m_vFormatEtc = vFormatEtc;
	}
	
protected:
	vector < FORMATETC > m_vFormatEtc;
	int m_nRefCount;
	int m_nIndex;

public:
	// IUnknown members
	STDMETHOD(QueryInterface)( REFIID refiid, void FAR* FAR* ppvObject )
	{
		*ppvObject = ( refiid == IID_IUnknown || refiid == IID_IEnumFORMATETC ) ? this : NULL;
		
		if ( *ppvObject != NULL )
			( (LPUNKNOWN)*ppvObject )->AddRef();
		
		return *ppvObject == NULL ? E_NOINTERFACE : S_OK;
	}
	
	STDMETHOD_(ULONG, AddRef)( void )
	{
		return ++m_nRefCount;
	}
	
	STDMETHOD_(ULONG, Release)( void )
	{
		int nRefCount = --m_nRefCount;
		if ( nRefCount == 0 )
			delete this;
		return nRefCount;
	}
	
	// IEnumFORMATETC members
	STDMETHOD(Next)( ULONG celt, LPFORMATETC lpFormatEtc, ULONG FAR *pceltFetched )
	{
		if ( pceltFetched != NULL )
			*pceltFetched=0;

		ULONG cReturn = celt;

		if ( celt <= 0 || lpFormatEtc == NULL || m_nIndex >= (int)m_vFormatEtc.size() )
			return S_FALSE;

		if ( pceltFetched == NULL && celt != 1 ) // pceltFetched can be NULL only for 1 item request
			return S_FALSE;

		while ( m_nIndex < (int)m_vFormatEtc.size() && cReturn > 0 )
		{
			*lpFormatEtc++ = m_vFormatEtc[ m_nIndex++ ];
			cReturn--;
		}
		
		if ( pceltFetched != NULL )
			*pceltFetched = celt - cReturn;

		return cReturn == 0 ? S_OK : S_FALSE;
	}
	
	STDMETHOD(Skip)( ULONG celt )
	{
		if ( ( m_nIndex + (int)celt ) >= (int)m_vFormatEtc.size() )
			return S_FALSE;
		m_nIndex += celt;
		return S_OK;
	}
	
	STDMETHOD(Reset)( void )
	{
		m_nIndex = 0;
		return S_OK;
	}
	
	STDMETHOD(Clone)( IEnumFORMATETC FAR * FAR* ppCloneEnumFormatEtc )
	{
		if ( ppCloneEnumFormatEtc == NULL )
			return E_POINTER;

		*ppCloneEnumFormatEtc = new CEnumFormatEtc( m_vFormatEtc );
		( (CEnumFormatEtc*)*ppCloneEnumFormatEtc )->AddRef();
		( (CEnumFormatEtc*)*ppCloneEnumFormatEtc )->m_nIndex = m_nIndex;
		
		return S_OK;
	}
};

class CDropSource : public IDropSource
{
public:
	CDropSource()
	{
		m_nRefCount = 0;
	}

protected:
	int m_nRefCount;
	
public:
	// IUnknown members
	STDMETHOD(QueryInterface)( REFIID refiid, void FAR* FAR* ppvObject )
	{
		*ppvObject = ( refiid == IID_IUnknown || refiid == IID_IDropSource ) ? this : NULL;
		
		if ( *ppvObject != NULL )
			( (LPUNKNOWN)*ppvObject )->AddRef();
		
		return *ppvObject == NULL ? E_NOINTERFACE : S_OK;
	}
	
	STDMETHOD_(ULONG, AddRef)( void )
	{
		return ++m_nRefCount;
	}
	
	STDMETHOD_(ULONG, Release)( void )
	{
		int nRefCount = --m_nRefCount;
		if ( nRefCount == 0 )
			delete this;
		return nRefCount;
	}
	
	// IDropSource members
	STDMETHOD(QueryContinueDrag)( BOOL bEscapePressed, DWORD dwKeyState )
	{
		if ( bEscapePressed )
			return DRAGDROP_S_CANCEL;
			
		if ( !( dwKeyState & ( MK_LBUTTON | MK_RBUTTON ) ) )
			return DRAGDROP_S_DROP;
		
		return S_OK;
	}
	
    STDMETHOD(GiveFeedback)( DWORD /*dwEffect*/ )
    {
		return DRAGDROP_S_USEDEFAULTCURSORS;
    }
};

class CDataObject : public IDataObject
{
public:
	CDataObject( CDropSource *pDropSource )
	{
		m_nRefCount = 0;
		m_pDropSource = pDropSource;
		m_bSwappedButtons = GetSystemMetrics( SM_SWAPBUTTON );
	}
	
	virtual ~CDataObject()
	{
		for ( vector < STGMEDIUM >::iterator posStgMedium = m_vStgMedium.begin(); posStgMedium != m_vStgMedium.end(); posStgMedium++ )
			ReleaseStgMedium( &( *posStgMedium ) );
	}

protected:
	CDropSource *m_pDropSource;
	int m_nRefCount;
	BOOL m_bSwappedButtons;
	
	vector < FORMATETC > m_vFormatEtc;
	vector < STGMEDIUM > m_vStgMedium;

public:
	// IUnknown members
	STDMETHOD(QueryInterface)( REFIID refiid, void FAR* FAR* ppvObject )
	{
		*ppvObject = ( refiid == IID_IUnknown || refiid == IID_IDataObject ) ? this : NULL;
		
		if ( *ppvObject != NULL )
			( (LPUNKNOWN)*ppvObject )->AddRef();
		
		return *ppvObject == NULL ? E_NOINTERFACE : S_OK;
	}
	
	STDMETHOD_(ULONG, AddRef)( void )
	{
		return ++m_nRefCount;
	}
	
	STDMETHOD_(ULONG, Release)( void )
	{
		int nRefCount = --m_nRefCount;
		if ( nRefCount == 0 )
			delete this;
		return nRefCount;
	}
	
	// IDataObject members
	STDMETHOD(GetData)( FORMATETC __RPC_FAR *pformatetcIn, STGMEDIUM __RPC_FAR *pmedium )
	{
		if ( pformatetcIn == NULL || pmedium == NULL )
			return E_INVALIDARG;

		ZeroMemory( pmedium, sizeof( STGMEDIUM ) );
		
		for ( int nFormatEtc = 0; nFormatEtc < (int)m_vFormatEtc.size(); nFormatEtc++ )
		{
			if ( pformatetcIn->tymed & m_vFormatEtc[ nFormatEtc ].tymed &&
				 pformatetcIn->dwAspect == m_vFormatEtc[ nFormatEtc ].dwAspect &&
				 pformatetcIn->cfFormat == m_vFormatEtc[ nFormatEtc ].cfFormat )
			{
				if ( m_vStgMedium[ nFormatEtc ].tymed == TYMED_NULL )
					return OnRenderData( m_vFormatEtc[ nFormatEtc ], pmedium, ( GetAsyncKeyState( m_bSwappedButtons ? VK_RBUTTON : VK_LBUTTON ) >= 0 ) ) ? S_OK : DV_E_FORMATETC;
				
				CopyMedium( pmedium, m_vStgMedium[ nFormatEtc ], m_vFormatEtc[ nFormatEtc ] );
				return S_OK;
			}
		}
		
		return DV_E_FORMATETC;
	}
	
	STDMETHOD(GetDataHere)( FORMATETC __RPC_FAR * /*pformatetc*/, STGMEDIUM __RPC_FAR * /*pmedium*/ )
	{
		return E_NOTIMPL;	
	}
	
	STDMETHOD(QueryGetData)( FORMATETC __RPC_FAR *pformatetc )
	{
		if ( pformatetc == NULL )
			return E_INVALIDARG;

		if ( !( pformatetc->dwAspect & DVASPECT_CONTENT ) )
			return DV_E_DVASPECT;
		
		HRESULT hResult = DV_E_TYMED;
		for ( int nFormatEtc = 0; nFormatEtc < (int)m_vFormatEtc.size(); nFormatEtc++ )
		{
			if ( !( pformatetc->tymed & m_vFormatEtc[ nFormatEtc ].tymed ) )
			{
				hResult = DV_E_TYMED;
				continue;
			}
			
			if ( pformatetc->cfFormat == m_vFormatEtc[ nFormatEtc ].cfFormat )
				return S_OK;
			
			hResult = DV_E_CLIPFORMAT;
		}

		return hResult;
	}
	
	STDMETHOD(GetCanonicalFormatEtc)( FORMATETC __RPC_FAR * /*pformatectIn*/, FORMATETC __RPC_FAR * pformatetcOut )
	{
		return pformatetcOut == NULL ? E_INVALIDARG : DATA_S_SAMEFORMATETC;
	}
    
    STDMETHOD(SetData)( FORMATETC __RPC_FAR *pformatetc, STGMEDIUM __RPC_FAR *pmedium, BOOL bRelease )
    {
		if ( pformatetc == NULL || pmedium == NULL )
			return E_INVALIDARG;

		m_vFormatEtc.push_back( *pformatetc );
		
		STGMEDIUM StgMedium = *pmedium;
		 
		if ( !bRelease )
			CopyMedium( &StgMedium, *pmedium, *pformatetc );

		m_vStgMedium.push_back( StgMedium );

		return S_OK;
    }
    
    STDMETHOD(EnumFormatEtc)( DWORD dwDirection, IEnumFORMATETC __RPC_FAR *__RPC_FAR *ppenumFormatEtc )
    {
		if ( ppenumFormatEtc == NULL )
			return E_POINTER;

		switch ( dwDirection )
		{
			case DATADIR_GET:	*ppenumFormatEtc = new CEnumFormatEtc( m_vFormatEtc );
								( (CEnumFormatEtc*)*ppenumFormatEtc )->AddRef();
								return S_OK;
			default:			*ppenumFormatEtc = NULL;
								return E_NOTIMPL;
		}
    }
    
    STDMETHOD(DAdvise)( FORMATETC __RPC_FAR * /*pformatetc*/, DWORD /*advf*/, IAdviseSink __RPC_FAR * /*pAdvSink*/, DWORD __RPC_FAR * /*pdwConnection*/ )
    {
		return OLE_E_ADVISENOTSUPPORTED;
    }
    
    STDMETHOD(DUnadvise)( DWORD /*dwConnection*/ )
    {
		return E_NOTIMPL;
    }
    
    STDMETHOD(EnumDAdvise)( IEnumSTATDATA __RPC_FAR *__RPC_FAR * /*ppenumAdvise*/ )
    {
		return OLE_E_ADVISENOTSUPPORTED;
    }
       
	void CopyMedium( STGMEDIUM *pMedDest, STGMEDIUM& MedSrc, FORMATETC& FmtSrc )
	{
		switch( MedSrc.tymed )
		{
			case TYMED_HGLOBAL:		pMedDest->hGlobal = (HGLOBAL)OleDuplicateData( MedSrc.hGlobal, FmtSrc.cfFormat, NULL );
									break;
			case TYMED_GDI:			pMedDest->hBitmap = (HBITMAP)OleDuplicateData( MedSrc.hBitmap, FmtSrc.cfFormat, NULL );
									break;
			case TYMED_MFPICT:		pMedDest->hMetaFilePict = (HMETAFILEPICT)OleDuplicateData( MedSrc.hMetaFilePict, FmtSrc.cfFormat, NULL );
									break;
			case TYMED_ENHMF:		pMedDest->hEnhMetaFile = (HENHMETAFILE)OleDuplicateData( MedSrc.hEnhMetaFile, FmtSrc.cfFormat, NULL );
									break;
			case TYMED_FILE:		pMedDest->lpszFileName = (LPOLESTR)OleDuplicateData( MedSrc.lpszFileName, FmtSrc.cfFormat, NULL );
									break;
			case TYMED_ISTREAM:		pMedDest->pstm = MedSrc.pstm;
									MedSrc.pstm->AddRef();
									break;
			case TYMED_ISTORAGE:	pMedDest->pstg = MedSrc.pstg;
									MedSrc.pstg->AddRef();
									break;
		}
		
		pMedDest->tymed = MedSrc.tymed;
		pMedDest->pUnkForRelease = NULL;
		
		if ( MedSrc.pUnkForRelease != NULL )
		{
			pMedDest->pUnkForRelease = MedSrc.pUnkForRelease;
			MedSrc.pUnkForRelease->AddRef();
		}	
	}
	
	virtual BOOL OnRenderData( FORMATETC& /*FormatEtc*/, STGMEDIUM * /*pStgMedium*/, BOOL /*bDropComplete*/ )
	{
		return FALSE;
	}
};

class CDropTarget : public IDropTarget
{
public:	
	CDropTarget( HWND hTargetWnd )
	{
		m_hTargetWnd = hTargetWnd;
		m_nRefCount = 0;
		m_bAllowDrop = FALSE;
		m_pDropTargetHelper = NULL;
		ZeroMemory( &m_FormatEtc, sizeof( FORMATETC ) );
		ZeroMemory( &m_StgMedium, sizeof( STGMEDIUM ) );
		
		if ( FAILED( CoCreateInstance( CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER, IID_IDropTargetHelper, (LPVOID*)&m_pDropTargetHelper ) ) )
			m_pDropTargetHelper = NULL;
	}
	
	virtual ~CDropTarget()
	{
		if ( m_pDropTargetHelper != NULL )
		{
			m_pDropTargetHelper->Release();
			m_pDropTargetHelper = NULL;
		}
	}

protected:
	HWND m_hTargetWnd;	
	int m_nRefCount;
	struct IDropTargetHelper *m_pDropTargetHelper;
	vector < FORMATETC > m_vFormatEtc;
	BOOL m_bAllowDrop;
	FORMATETC m_FormatEtc;
	STGMEDIUM m_StgMedium;
	
public:
	// IUnknown members
	STDMETHOD(QueryInterface)( REFIID refiid, void FAR* FAR* ppvObject )
	{
		*ppvObject = ( refiid == IID_IUnknown || refiid == IID_IDropTarget ) ? this : NULL;
		
		if ( *ppvObject != NULL )
			( (LPUNKNOWN)*ppvObject )->AddRef();
		
		return *ppvObject == NULL ? E_NOINTERFACE : S_OK;
	}
	
	STDMETHOD_(ULONG, AddRef)( void )
	{
		return ++m_nRefCount;
	}
	
	STDMETHOD_(ULONG, Release)( void )
	{
		int nRefCount = --m_nRefCount;
		if ( nRefCount == 0 )
			delete this;
		return nRefCount;
	}

	STDMETHOD(DragEnter)( IDataObject __RPC_FAR *pDataObject, DWORD dwKeyState, POINTL pt, DWORD __RPC_FAR *pdwEffect )
	{
		if ( pDataObject == NULL )
			return E_INVALIDARG;

		if ( m_pDropTargetHelper != NULL )
			m_pDropTargetHelper->DragEnter( m_hTargetWnd, pDataObject, (LPPOINT)&pt, *pdwEffect );
		
		ZeroMemory( &m_FormatEtc, sizeof( FORMATETC ) );
		if ( m_StgMedium.tymed != TYMED_NULL )
			ReleaseStgMedium( &m_StgMedium );
		ZeroMemory( &m_StgMedium, sizeof( STGMEDIUM ) );
		
		for ( int nFormatEtc = 0; nFormatEtc < (int)m_vFormatEtc.size(); nFormatEtc++ )
		{
			STGMEDIUM StgMedium;
			m_bAllowDrop = ( pDataObject->GetData( &m_vFormatEtc[ nFormatEtc ], &StgMedium ) == S_OK );
		
			if ( m_bAllowDrop )
			{
				// store drag data for later use in DragOver
				m_FormatEtc = m_vFormatEtc[ nFormatEtc ];
				m_StgMedium = StgMedium;
				
				// get client cursor position
				CWindow hWnd( m_hTargetWnd );
				CPoint point( pt.x, pt.y );
				hWnd.ScreenToClient( &point );
					
				*pdwEffect = OnDragEnter( m_FormatEtc, m_StgMedium, dwKeyState, point );
				
				break;
			}
		}
		
		QueryDrop( dwKeyState, pdwEffect );
		
		return S_OK;	
	}
	
	STDMETHOD(DragOver)( DWORD dwKeyState, POINTL pt, DWORD __RPC_FAR *pdwEffect )
	{
		if ( m_pDropTargetHelper )
			m_pDropTargetHelper->DragOver( (LPPOINT)&pt, *pdwEffect );
		
		if ( m_bAllowDrop && m_FormatEtc.cfFormat != CF_NULL && m_StgMedium.tymed != TYMED_NULL )
		{
			// get client cursor position
			CWindow hWnd( m_hTargetWnd );
			CPoint point( pt.x, pt.y );
			hWnd.ScreenToClient( &point );
				
			*pdwEffect = OnDragOver( m_FormatEtc, m_StgMedium, dwKeyState, point );
		}
		
		QueryDrop( dwKeyState, pdwEffect );
		
		return S_OK;
	}
	
	STDMETHOD(DragLeave)( void )
	{
		if ( m_pDropTargetHelper )
			m_pDropTargetHelper->DragLeave();
		
		OnDragLeave();

		m_bAllowDrop = FALSE;
		
		ZeroMemory( &m_FormatEtc, sizeof( FORMATETC ) );
		if ( m_StgMedium.tymed != TYMED_NULL )
			ReleaseStgMedium( &m_StgMedium );
		ZeroMemory( &m_StgMedium, sizeof( STGMEDIUM ) );
		
		return S_OK;
	}
	
	STDMETHOD(Drop)( IDataObject __RPC_FAR *pDataObject, DWORD dwKeyState, POINTL pt, DWORD __RPC_FAR *pdwEffect )
    {
		if ( pDataObject == NULL )
			return E_INVALIDARG;	

		if ( m_pDropTargetHelper )
			m_pDropTargetHelper->Drop( pDataObject, (LPPOINT)&pt, *pdwEffect );

		if ( m_bAllowDrop && m_FormatEtc.cfFormat != CF_NULL && QueryDrop( dwKeyState, pdwEffect ) )
		{
			STGMEDIUM StgMedium;
			if ( pDataObject->GetData( &m_FormatEtc, &StgMedium ) == S_OK )
			{
				// get client cursor position
				CWindow hWnd( m_hTargetWnd );
				CPoint point( pt.x, pt.y );
				hWnd.ScreenToClient( &point );
				
				if ( !OnDrop( m_FormatEtc, StgMedium, *pdwEffect, point ) )
					*pdwEffect = DROPEFFECT_NONE;
				
				ReleaseStgMedium( &StgMedium );
			}
		}
		
		m_bAllowDrop = FALSE;
		
		ZeroMemory( &m_FormatEtc, sizeof( FORMATETC ) );
		if ( m_StgMedium.tymed != TYMED_NULL )
			ReleaseStgMedium( &m_StgMedium );
		ZeroMemory( &m_StgMedium, sizeof( STGMEDIUM ) );
		
		return S_OK;
    }    
    
    void AddSupportedFormat( FORMATETC& FormatEtc )
	{
		m_vFormatEtc.push_back( FormatEtc );
	}
	
	void AddSupportedFormat( CLIPFORMAT cfFormat )
	{
		FORMATETC FormatEtc;
		ZeroMemory( &FormatEtc, sizeof( FORMATETC ) );
		
		FormatEtc.cfFormat = cfFormat;
		FormatEtc.dwAspect = DVASPECT_CONTENT;
		FormatEtc.lindex = -1;
		FormatEtc.tymed = TYMED_HGLOBAL;
		
		AddSupportedFormat( FormatEtc );
	}
	
	BOOL QueryDrop( DWORD dwKeyState, LPDWORD pdwEffect )
	{
		DWORD dwEffects = *pdwEffect; 

		if ( !m_bAllowDrop )
		{
			*pdwEffect = DROPEFFECT_NONE;
			return FALSE;
		}
		
		*pdwEffect = ( dwKeyState & MK_CONTROL ) ? ( ( dwKeyState & MK_SHIFT ) ? DROPEFFECT_LINK : DROPEFFECT_COPY ) : ( ( dwKeyState & MK_SHIFT ) ? DROPEFFECT_MOVE : 0 );
		if ( *pdwEffect == 0 ) 
		{
			if ( dwEffects & DROPEFFECT_COPY )
				*pdwEffect = DROPEFFECT_COPY;
			else if ( dwEffects & DROPEFFECT_MOVE )
				*pdwEffect = DROPEFFECT_MOVE; 
			else if (dwEffects & DROPEFFECT_LINK )
				*pdwEffect = DROPEFFECT_LINK; 
			else 
				*pdwEffect = DROPEFFECT_NONE;
		} 
		else if ( !( *pdwEffect & dwEffects ) )
			*pdwEffect = DROPEFFECT_NONE;

		return ( *pdwEffect != DROPEFFECT_NONE );
	}

	virtual DWORD OnDragEnter( FORMATETC& /*FormatEtc*/, STGMEDIUM& /*StgMedium*/, DWORD /*dwKeyState*/, CPoint /*point*/ )
	{
		return FALSE;
	}
	
	virtual DWORD OnDragOver( FORMATETC& /*FormatEtc*/, STGMEDIUM& /*StgMedium*/, DWORD /*dwKeyState*/, CPoint /*point*/ )
	{
		return FALSE;
	}
	
	virtual BOOL OnDrop( FORMATETC& /*FormatEtc*/, STGMEDIUM& /*StgMedium*/, DWORD /*dwEffect*/, CPoint /*point*/ )
	{
		return FALSE;
	}
	
	virtual void OnDragLeave()
	{
	}
};

template < class T >
class CDropTargetT : public CDropTarget
{
public:
	CDropTargetT( HWND hTargetWnd ) : CDropTarget( hTargetWnd )
	{
		m_pDelegate = NULL;
	}

protected:
	T *m_pDelegate;

public:	
	BOOL Register( T *pDelegate )
	{
		m_pDelegate = pDelegate;
		return TRUE;
	}
	
	virtual DWORD OnDragEnter( FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwKeyState, CPoint point )
	{
		return m_pDelegate == NULL ? DROPEFFECT_NONE : m_pDelegate->OnDragEnter( FormatEtc, StgMedium, dwKeyState, point );
	}
	
	virtual DWORD OnDragOver( FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwKeyState, CPoint point )
	{
		return m_pDelegate == NULL ? DROPEFFECT_NONE : m_pDelegate->OnDragOver( FormatEtc, StgMedium, dwKeyState, point );
	}
	
	virtual BOOL OnDrop( FORMATETC& FormatEtc, STGMEDIUM& StgMedium, DWORD dwEffect, CPoint point )
	{
		return m_pDelegate == NULL ? FALSE : m_pDelegate->OnDrop( FormatEtc, StgMedium, dwEffect, point );
	}
	
	virtual void OnDragLeave()
	{
		if ( m_pDelegate != NULL )
			m_pDelegate->OnDragLeave();
	}
};

template < class T >
class CDataObjectT : public CDataObject
{
public:
	CDataObjectT( CDropSource *pDropSource ) : CDataObject( pDropSource )
	{
		m_pDelegate = FALSE;
	}

protected:
	T *m_pDelegate;

public:	
	BOOL Register( T *pDelegate )
	{
		m_pDelegate = pDelegate;
		return TRUE;
	}
	
	virtual BOOL OnRenderData( FORMATETC& FormatEtc, STGMEDIUM *pStgMedium, BOOL bDropComplete )
	{
		return m_pDelegate == NULL ? FALSE : m_pDelegate->OnRenderData( FormatEtc, pStgMedium, bDropComplete );
	}
};

template < class T >
class CDragDrop
{
public:
	CDragDrop()
	{
		m_pDropSource = NULL;
		m_pDataObject = NULL;
		m_pDropTarget = NULL;
		m_hTargetWnd = NULL;
	}
		
	virtual ~CDragDrop()
	{
		if ( m_pDropSource != NULL )
			m_pDropSource->Release();
		if ( m_pDataObject != NULL )
			m_pDataObject->Release();
	}

protected:
	CDropSource *m_pDropSource;
	CDataObjectT< T > *m_pDataObject;
	CDropTargetT< T > *m_pDropTarget;
	HWND m_hTargetWnd;

public:
	BOOL Register( T *pDelegate, BOOL bDropSource = TRUE )
	{
		m_hTargetWnd = pDelegate->m_hWnd;
		
		// instantiate new drop target object
		m_pDropTarget = new CDropTargetT< T >( m_hTargetWnd );
		m_pDropTarget->Register( pDelegate );
		
		// register drop target
		if ( FAILED( RegisterDragDrop( m_hTargetWnd, m_pDropTarget ) ) )
		{
			delete m_pDropTarget;
			m_pDropTarget = NULL;
			return FALSE;
		}
		
		// is this a drop target only?
		if ( !bDropSource )
			return TRUE;
		
		// instantiate new drop source object
		m_pDropSource = new CDropSource;
		m_pDropSource->AddRef();
		
		m_pDataObject = new CDataObjectT< T >( m_pDropSource );
		m_pDataObject->AddRef();
		
		// register drop source delegate for data render
		return m_pDataObject->Register( pDelegate );
	}
	
	BOOL Revoke()
	{
		m_pDropTarget = NULL;
		return ( RevokeDragDrop( m_hTargetWnd ) == S_OK );
	}
	
	BOOL AddTargetFormat( CLIPFORMAT cfFormat )
	{
		if ( m_pDropTarget == NULL )
			return FALSE;
		m_pDropTarget->AddSupportedFormat( cfFormat );
		return TRUE;
	}	
	
	BOOL AddSourceFormat( CLIPFORMAT cfFormat )
	{
		if ( m_pDataObject == NULL )
			return FALSE;
			
		FORMATETC FormatEtc;
		ZeroMemory( &FormatEtc, sizeof( FORMATETC ) );
		
		FormatEtc.cfFormat = cfFormat;
		FormatEtc.dwAspect = DVASPECT_CONTENT;
		FormatEtc.lindex = -1;
		FormatEtc.tymed = TYMED_HGLOBAL;
		
		STGMEDIUM StgMedium;
		ZeroMemory( &StgMedium, sizeof( STGMEDIUM ) );
		
		return SUCCEEDED( m_pDataObject->SetData( &FormatEtc, &StgMedium, TRUE ) );
	}
	
	BOOL SetClipboard( FORMATETC& FormatEtc, STGMEDIUM& StgMedium )
	{
		if ( m_pDataObject == NULL )
			return DROPEFFECT_NONE;
		
		if ( FAILED( m_pDataObject->SetData( &FormatEtc, &StgMedium, TRUE ) ) )
			return DROPEFFECT_NONE;
		
		return ( OleSetClipboard( m_pDataObject ) == S_OK );
	}
	
	BOOL FlushClipboard()
	{
		return ( OleFlushClipboard() == S_OK );
	}
	
	DWORD DoDragDrop( SHDRAGIMAGE *pDragImage = NULL, DWORD dwValidEffects = DROPEFFECT_COPY | DROPEFFECT_MOVE | DROPEFFECT_LINK )
	{
		if ( m_pDataObject == NULL )
			return DROPEFFECT_NONE;
			
		IDragSourceHelper *pDragSourceHelper = NULL;
		
		// instantiate drag source helper object
		if ( pDragImage != NULL )
		{
			if ( FAILED( CoCreateInstance( CLSID_DragDropHelper, NULL, CLSCTX_INPROC_SERVER, IID_IDragSourceHelper, (LPVOID*)&pDragSourceHelper ) ) )
				pDragSourceHelper = NULL;
			
			if ( pDragSourceHelper != NULL )
				pDragSourceHelper->InitializeFromBitmap( pDragImage, m_pDataObject );
		}
		
		DWORD dwEffects = DROPEFFECT_NONE;
		dwEffects = ::DoDragDrop( m_pDataObject, m_pDropSource, dwValidEffects, &dwEffects ) == DRAGDROP_S_DROP ? DROPEFFECT_NONE : dwEffects;
		
		// destroy drag source helper object
		if ( pDragSourceHelper != NULL )
			pDragSourceHelper->Release();
		
		return dwEffects;
	}	
};
