// This is a part of the Microsoft Foundation Classes C++ library.
// Copyright (C) 1992-1995 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_OLE3_SEG
#pragma code_seg(AFX_OLE3_SEG)
#endif

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

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// COleDataObject constructors

COleDataObject::COleDataObject()
{
	m_lpEnumerator = NULL;
	m_lpDataObject = NULL;
	m_bAutoRelease = TRUE;
	m_bClipboard = FALSE;
}

void COleDataObject::Attach(LPDATAOBJECT lpDataObject, BOOL bAutoRelease)
{
	ASSERT(lpDataObject != NULL);

	Release();  // detach previous
	m_lpDataObject = lpDataObject;
	m_bAutoRelease = bAutoRelease;
}

void COleDataObject::Release()
{
	RELEASE(m_lpEnumerator);

	if (m_lpDataObject != NULL)
	{
		if (m_bAutoRelease)
			m_lpDataObject->Release();
		m_lpDataObject = NULL;
	}
	m_bClipboard = FALSE;
}

LPDATAOBJECT COleDataObject::Detach()
{
	EnsureClipboardObject();

	LPDATAOBJECT lpDataObject = m_lpDataObject;
	m_lpDataObject = NULL;  // detach without Release
	m_bClipboard = FALSE;

	return lpDataObject;
}

LPDATAOBJECT COleDataObject::GetIDataObject(BOOL bAddRef)
{
	EnsureClipboardObject();

	LPDATAOBJECT lpDataObject = m_lpDataObject;
	if (bAddRef && lpDataObject != NULL)
		lpDataObject->AddRef();

	return lpDataObject;
}

/////////////////////////////////////////////////////////////////////////////
// COleDataObject attributes

void COleDataObject::BeginEnumFormats()
{
	EnsureClipboardObject();
	ASSERT(m_bClipboard || m_lpDataObject != NULL);

	// release old enumerator
	RELEASE(m_lpEnumerator);
	if (m_lpDataObject == NULL)
		return;

	// get the new enumerator
	SCODE sc = m_lpDataObject->EnumFormatEtc(DATADIR_GET, &m_lpEnumerator);
	ASSERT(sc != S_OK || m_lpEnumerator != NULL);
}

BOOL COleDataObject::GetNextFormat(LPFORMATETC lpFormatEtc)
{
	ASSERT(m_bClipboard || m_lpDataObject != NULL);
	ASSERT(AfxIsValidAddress(lpFormatEtc, sizeof(FORMATETC), FALSE));

	// return false if enumerator is already NULL
	if (m_lpEnumerator == NULL)
		return FALSE;

	// attempt to retrieve the next format with the enumerator
	SCODE sc = m_lpEnumerator->Next(1, lpFormatEtc, NULL);

	// if enumerator fails, stop the enumeration
	if (sc != S_OK)
	{
		RELEASE(m_lpEnumerator);
		return FALSE;   // enumeration has ended
	}
	// otherwise, continue
	return TRUE;
}

CFile* COleDataObject::GetFileData(CLIPFORMAT cfFormat, LPFORMATETC lpFormatEtc)
{
	EnsureClipboardObject();
	ASSERT(m_bClipboard || m_lpDataObject != NULL);
	if (m_lpDataObject == NULL)
		return NULL;

	ASSERT(lpFormatEtc == NULL ||
		AfxIsValidAddress(lpFormatEtc, sizeof(FORMATETC), FALSE));

	// fill in FORMATETC struct
	FORMATETC formatEtc;
	lpFormatEtc = _AfxFillFormatEtc(lpFormatEtc, cfFormat, &formatEtc);
	formatEtc.tymed = TYMED_FILE|TYMED_MFPICT|TYMED_HGLOBAL|TYMED_ISTREAM;

	// attempt to get the data
	STGMEDIUM stgMedium;
	SCODE sc = m_lpDataObject->GetData(lpFormatEtc, &stgMedium);
	if (FAILED(sc))
		return FALSE;

	// STGMEDIUMs with pUnkForRelease need to be copied first
	if (stgMedium.pUnkForRelease != NULL)
	{
		STGMEDIUM stgMediumDest;
		stgMediumDest.tymed = TYMED_NULL;
		stgMediumDest.pUnkForRelease = NULL;
		if (!_AfxCopyStgMedium(lpFormatEtc->cfFormat, &stgMediumDest, &stgMedium))
		{
			::ReleaseStgMedium(&stgMedium);
			return FALSE;
		}
		// release original and replace with new
		::ReleaseStgMedium(&stgMedium);
		stgMedium = stgMediumDest;
	}

	// convert it to a file, depending on data
	CString strFileName;
	CFile* pFile = NULL;
	TRY
	{
		switch (stgMedium.tymed)
		{
		case TYMED_FILE:
			strFileName = stgMedium.lpszFileName;
			pFile = new CFile;
			if (!pFile->Open(strFileName,
				CFile::modeReadWrite|CFile::shareExclusive))
			{
				delete pFile;
				pFile = NULL;
				break;
			}
			// caller is responsible for deleting the actual file,
			//  but we free the file name.
			CoTaskMemFree(stgMedium.lpszFileName);
			break;

		case TYMED_MFPICT:
		case TYMED_HGLOBAL:
			pFile = new CSharedFile;
			((CSharedFile*)pFile)->SetHandle(stgMedium.hGlobal);
			break;

		case TYMED_ISTREAM:
			pFile = new COleStreamFile(stgMedium.pstm);
			break;

		default:
			// type not supported, so return error
			::ReleaseStgMedium(&stgMedium);
			break;
		}
	}
	CATCH_ALL(e)
	{
		delete pFile;
		pFile = NULL;
		DELETE_EXCEPTION(e);
	}
	END_CATCH_ALL

	// store newly created CFile* and return
	return pFile;
}

HGLOBAL COleDataObject::GetGlobalData(CLIPFORMAT cfFormat, LPFORMATETC lpFormatEtc)
{
	EnsureClipboardObject();
	ASSERT(m_bClipboard || m_lpDataObject != NULL);
	if (m_lpDataObject == NULL)
		return NULL;

	ASSERT(lpFormatEtc == NULL ||
		AfxIsValidAddress(lpFormatEtc, sizeof(FORMATETC), FALSE));

	// fill in FORMATETC struct
	FORMATETC formatEtc;
	BOOL bFillFormatEtc = (lpFormatEtc == NULL);
	lpFormatEtc = _AfxFillFormatEtc(lpFormatEtc, cfFormat, &formatEtc);
	if (bFillFormatEtc)
		lpFormatEtc->tymed = TYMED_HGLOBAL|TYMED_MFPICT;
	ASSERT((lpFormatEtc->tymed & (TYMED_HGLOBAL|TYMED_MFPICT)) != 0);

	// attempt to get the data
	STGMEDIUM stgMedium;
	SCODE sc = m_lpDataObject->GetData(lpFormatEtc, &stgMedium);
	if (FAILED(sc))
		return FALSE;

	// handle just hGlobal types
	switch (stgMedium.tymed)
	{
	case TYMED_MFPICT:
	case TYMED_HGLOBAL:
		if (stgMedium.pUnkForRelease == NULL)
			return stgMedium.hGlobal;

		STGMEDIUM stgMediumDest;
		stgMediumDest.tymed = TYMED_NULL;
		stgMediumDest.pUnkForRelease = NULL;
		if (!_AfxCopyStgMedium(lpFormatEtc->cfFormat, &stgMediumDest, &stgMedium))
		{
			::ReleaseStgMedium(&stgMedium);
			return NULL;
		}
		::ReleaseStgMedium(&stgMedium);
		return stgMediumDest.hGlobal;

	// default -- falls through to error condition...
	}

	::ReleaseStgMedium(&stgMedium);
	return NULL;
}

BOOL COleDataObject::GetData(CLIPFORMAT cfFormat, LPSTGMEDIUM lpStgMedium,
	LPFORMATETC lpFormatEtc)
{
	EnsureClipboardObject();
	ASSERT(m_bClipboard || m_lpDataObject != NULL);
	if (m_lpDataObject == NULL)
		return FALSE;

	ASSERT(AfxIsValidAddress(lpStgMedium, sizeof(STGMEDIUM), FALSE));

	// fill in FORMATETC struct
	FORMATETC formatEtc;
	lpFormatEtc = _AfxFillFormatEtc(lpFormatEtc, cfFormat, &formatEtc);

	// attempt to get the data
	SCODE sc = m_lpDataObject->GetData(lpFormatEtc, lpStgMedium);
	if (FAILED(sc))
		return FALSE;

	return TRUE;
}

BOOL COleDataObject::IsDataAvailable(CLIPFORMAT cfFormat, LPFORMATETC lpFormatEtc)
{
	if (m_bClipboard)
	{
		// it is faster and more reliable to ask the real Win32 clipboard
		//  instead of the OLE clipboard.
		return ::IsClipboardFormatAvailable(cfFormat);
	}
	else
	{
		ASSERT(m_lpDataObject != NULL);
		ASSERT(lpFormatEtc == NULL ||
			AfxIsValidAddress(lpFormatEtc, sizeof(FORMATETC), FALSE));

		// fill in FORMATETC struct
		FORMATETC formatEtc;
		lpFormatEtc = _AfxFillFormatEtc(lpFormatEtc, cfFormat, &formatEtc);

		// attempt to get the data
		return m_lpDataObject->QueryGetData(lpFormatEtc) == S_OK;
	}
}

/////////////////////////////////////////////////////////////////////////////
// clipboard API wrappers

BOOL COleDataObject::AttachClipboard()
{
	ASSERT(AfxIsValidAddress(this, sizeof(COleDataObject)));
	ASSERT(m_lpDataObject == NULL); // need to call release?
	ASSERT(!m_bClipboard); // already attached to clipboard?

	// set special "clipboard" flag for optimizations
	m_bClipboard = TRUE;
	return TRUE;
}

void COleDataObject::EnsureClipboardObject()
{
	ASSERT(AfxIsValidAddress(this, sizeof(COleDataObject)));

	if (m_bClipboard && m_lpDataObject == NULL)
	{
		// get clipboard using OLE API
		LPDATAOBJECT lpDataObject;
		SCODE sc = ::OleGetClipboard(&lpDataObject);

		// attach COleDataObject wrapper to IDataObject from clipboard
		if (sc == S_OK)
			Attach(lpDataObject, TRUE);
	}
}

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