//
//**************************************************************************
//
// BOleData.cpp --   Implements a data cache object which is used for delayed
//                   rendering.
//
//                   Bolero customers aren't required to use this object (or
//                   delayed rendering at all) but when we cut/copy an
//                   embedded object (i.e. embed from embedding) we need the
//                   data cache to avoid getting changes to the original
//                   object when the user expects a snapshot (the usual
//                   delayed rendering problem).
//
// Copyright (c) 1993,94 by Borland International, Inc. All rights reserved
//
//**************************************************************************

#include <windows.h>
#include "BOleData.h"
#include "BOleCman.h"
#include "BOleDoc.h"

#ifdef	_DEBUG

//**************************************************************************
//
// StgWatcher
//
// StgWatcher is a debugging tool used to watch when temporary storages are
// created and deleted, if they ever are.
//
//**************************************************************************

class StgWatcher : public IUnknown, public STATSTG {
 public:
	StgWatcher( IUnknown *Data, IStorage *Stg );
	~StgWatcher();
	// IUnknown
	virtual HRESULT _IFUNC QueryInterface(REFIID iid, void FAR* FAR* pif);
	virtual ULONG _IFUNC AddRef();
	virtual ULONG _IFUNC Release();
 protected:
	IStorage *Stg;
	IUnknown *Data;
	ULONG	nRef;
};

StgWatcher::StgWatcher( IUnknown *data, IStorage *stg ) : nRef( 1 ), Data( data ), Stg( stg ){
	stg->Stat( this, 0 );
	OLECHAR tmp[80];
	wsprintf( tmp, TEXT("Stg \"%s\" created\n\r" ), pwcsName );
	OutputDebugString( tmp );
}
StgWatcher::~StgWatcher(){
	LPMALLOC pMalloc;
	CoGetMalloc( MEMCTX_TASK, &pMalloc );
	pMalloc->Free( pwcsName );
	pMalloc->Release();
}

HRESULT _IFUNC StgWatcher::QueryInterface(REFIID iid, void FAR* FAR* pif){
	*pif = ( iid == IID_IUnknown ) ? ( void FAR * )( IUnknown * )this : 0;
	return ( iid == IID_IUnknown ) ? ResultFromScode(S_OK) : ResultFromScode(E_NOINTERFACE);
}
ULONG _IFUNC StgWatcher::AddRef(){
	nRef++;
	OLECHAR tmp[80];
	wsprintf( tmp, TEXT("Stg \"%s\" AddRef to %d\n\r" ), pwcsName, nRef );
	OutputDebugString( tmp );
	return nRef;
}
ULONG _IFUNC StgWatcher::Release(){
	nRef--;
	OLECHAR tmp[80];
	wsprintf( tmp, TEXT("Stg \"%s\" Release to %d\n\r" ), pwcsName, nRef );
	OutputDebugString( tmp );
	if( nRef == 0 ){
		wsprintf( tmp, TEXT("Stg \"%s\" deleted at %d\n\r" ), pwcsName, nRef );
		OutputDebugString( tmp );
		delete this;
		return 0;
	}
	return nRef;
}
#endif

//**************************************************************************
//
// BOleData Implementation
//
//**************************************************************************

BOleData::BOleData (BOleClassManager *pCM, PIBUnknownMain pOuter) :
	BOleComponent (pCM, pOuter), pFirstItem (NULL), pLastItem (NULL)
{
	#ifdef _DEBUG
	{
		OLECHAR b[ 80 ];
		wsprintf( b, TEXT("BOleData %04x:%04x created\n\r"),
			HIWORD( ( DWORD )this ), LOWORD( ( DWORD )this ) );
		OutputDebugString( b );
	}
	#endif
}

BOleData::~BOleData ()
{
	#ifdef _DEBUG
	{
		OLECHAR b[ 80 ];
		wsprintf( b, TEXT("BOleData %04x:%04x deleted\n\r"),
			HIWORD( ( DWORD )this ), LOWORD( ( DWORD )this ) );
		OutputDebugString( b );
	}
	#endif
	FreeItems();

	if (NOERROR == OleIsCurrentClipboard (this))
		pFactory->GetService()->NotifyClipboardEmptied();
}

void BOleData::FreeItems()
{
	// Release the mediums we might have created for deferred rendering
	//
	LPMALLOC pMalloc;
	CoGetMalloc( MEMCTX_TASK, &pMalloc );
	
	BOleDataItem FAR *pWalk = pFirstItem;
	while (pWalk) {
		BOleDataItem *pTmp = pWalk->pNext;
		ReleaseStgMedium (&(pWalk->tymed));
		pMalloc->Free (pWalk);
		pWalk = pTmp;
	}
	pFirstItem = NULL;
	pMalloc->Release();
}

HRESULT _IFUNC BOleData::QueryInterfaceMain (REFIID iid, LPVOID FAR *ppv)
{
	HRESULT hr = ResultFromScode(E_NOINTERFACE);
	*ppv = NULL;

	// interfaces

	SUCCEEDED(hr = IDataObject_QueryInterface(this, iid, ppv))

	// base classes

	|| SUCCEEDED(hr = BOleComponent::QueryInterfaceMain(iid, ppv))

	// helpers

	;

	return hr;
};

//**************************************************************************
//
// IDataObject Implementation
//
//**************************************************************************

HRESULT _IFUNC BOleData::GetData (LPFORMATETC pformatetcIn, 
											 LPSTGMEDIUM pmedium)
{
	pmedium->tymed = TYMED_NULL;
	pmedium->pstg = NULL;

	if (!pFirstItem)
		return ResultFromScode (E_FAIL);

#ifdef _DEBUG
	OLECHAR name[32];
	GetClipboardFormatName (pformatetcIn->cfFormat, name, sizeof(name));
#endif

	BOleDataItem *pWalk = pFirstItem;
	while (pWalk) {
		if (pformatetcIn->cfFormat == pWalk->fmtEtc.cfFormat &&
			 pformatetcIn->tymed == pWalk->fmtEtc.tymed &&
			 pformatetcIn->dwAspect == pWalk->fmtEtc.dwAspect) {
			*pmedium = pWalk->tymed;
			if( pWalk->fmtEtc.tymed == TYMED_ISTREAM )
				pWalk->tymed.pstm->AddRef();
			if( pWalk->fmtEtc.tymed == TYMED_ISTORAGE ) 
				pWalk->tymed.pstg->AddRef();
			// so we go away when they release the storage
			pmedium->pUnkForRelease = this;
			pmedium->pUnkForRelease->AddRef();
			// old pUnkForRelease is still in the table
			return NOERROR;
		}
		pWalk = pWalk->pNext;
	}

	return ResultFromScode (DATA_E_FORMATETC);
}

HRESULT _IFUNC BOleData::GetDataHere (LPFORMATETC pformatetc, 
                                      LPSTGMEDIUM pmedium)
{
	pmedium->tymed = TYMED_NULL;
	
	if (!pFirstItem)
		return ResultFromScode (E_FAIL);
	if (!pformatetc || !(pformatetc->tymed & TYMED_ISTORAGE))
		return ResultFromScode (DV_E_FORMATETC);
	if (!pmedium)
		return ResultFromScode (DV_E_STGMEDIUM);

	BOleDataItem *pWalk = pFirstItem;
	while (pWalk) {
		if (pformatetc->cfFormat == pWalk->fmtEtc.cfFormat &&
			pformatetc->tymed == pWalk->fmtEtc.tymed &&
			 pformatetc->dwAspect == pWalk->fmtEtc.dwAspect) {
			pmedium->tymed = TYMED_ISTORAGE;
			return pWalk->tymed.pstg->CopyTo (NULL, NULL, NULL, pmedium->pstg);
		}
		pWalk = pWalk->pNext;
	}

	return ResultFromScode (DV_E_FORMATETC);
}

HRESULT _IFUNC BOleData::QueryGetData (LPFORMATETC pformatetc)
{
	if (!pFirstItem)
		return ResultFromScode (E_FAIL);
	if (!pformatetc)
		return ResultFromScode (DV_E_FORMATETC);

	BOleDataItem *pWalk = pFirstItem;
	while (pWalk) {
		if (pformatetc->cfFormat == pWalk->fmtEtc.cfFormat &&
		    pformatetc->tymed == pWalk->fmtEtc.tymed &&
			 pformatetc->dwAspect == pWalk->fmtEtc.dwAspect) {
			return NOERROR;
		}
		pWalk = pWalk->pNext;
	}

	return ResultFromScode (DV_E_FORMATETC);
}

HRESULT _IFUNC BOleData::GetCanonicalFormatEtc (LPFORMATETC pformatetc,
												LPFORMATETC pformatetcOut)
{
	return ResultFromScode (DATA_S_SAMEFORMATETC);
}

HRESULT _IFUNC BOleData::SetData (LPFORMATETC pformatetc,
								  LPSTGMEDIUM pmedium,
								  BOOL fRelease)
{
	// We own the data
	//
#ifdef _DEBUG
	assert(fRelease);
#endif
	if (!fRelease)
		return ResultFromScode (E_FAIL);

	// If the input pointers are NULL, we're supposed to delete the list
	//
	if (!pformatetc || !pmedium) {
		FreeItems();
		return NOERROR;
	}

	// Allocate a new item to hold data
	//
	LPMALLOC pMalloc = NULL;
	HRESULT hr = ::CoGetMalloc (MEMCTX_TASK, &pMalloc);
	if (!SUCCEEDED(hr))
		return hr;

	BOleDataItem FAR *pTmp = NULL;
	pTmp = (BOleDataItem FAR*) pMalloc->Alloc (sizeof(BOleDataItem));
	pMalloc->Release ();
	if (!pTmp)
		return ResultFromScode (E_OUTOFMEMORY);
	else
		_fmemset (pTmp, 0, sizeof(BOleDataItem));


	// Insert formats onto the back of the list. Minimizes list traversal and
	// keeps the priority order of the formats intact
	//
	if (pLastItem) {
		pLastItem->pNext = pTmp;
		pLastItem = pTmp;
	} 
	else {
		pLastItem = pFirstItem = pTmp;
	}

	pTmp->fmtEtc = *pformatetc;
	pTmp->tymed = *pmedium;

	// When copying an embedding displayed as icon, it seems that the only
	// format which can have DVASPECT_ICON is the metafile. It doesn't work
	// if all formats have DVASPECT_ICON. Weird.
	//
	if (pTmp->fmtEtc.dwAspect == DVASPECT_ICON)
		if (pTmp->fmtEtc.cfFormat != CF_METAFILEPICT)
			pTmp->fmtEtc.dwAspect = DVASPECT_CONTENT;

	return NOERROR;
}

HRESULT _IFUNC BOleData::EnumFormatEtc (DWORD dwDirection,
										LPENUMFORMATETC FAR* ppenumFormatEtc)
{
	*ppenumFormatEtc = NULL;

	if (dwDirection == DATADIR_SET) {
		*ppenumFormatEtc = NULL;
		return ResultFromScode (E_FAIL);
	}
	*ppenumFormatEtc = new BOleEnumFormatEtc (this);
	return *ppenumFormatEtc != NULL ? NOERROR : ResultFromScode (E_OUTOFMEMORY);
}

HRESULT _IFUNC BOleData::DAdvise (FORMATETC FAR* pFormatetc,
                                  DWORD advf,
                                  LPADVISESINK pAdvSink, 
                                  DWORD FAR* pdwConnection)
{
	*pdwConnection = 0;
	return ResultFromScode (E_FAIL);
}

HRESULT _IFUNC BOleData::DUnadvise (DWORD dwConnection)
{
	return ResultFromScode (E_FAIL);
}

HRESULT _IFUNC BOleData::EnumDAdvise (LPENUMSTATDATA FAR* ppenumAdvise)
{
	*ppenumAdvise = NULL;
	return ResultFromScode (E_FAIL);
}


//**************************************************************************
//
// IEnumFORMATETC Implementation
//
//**************************************************************************

BOleEnumFormatEtc::BOleEnumFormatEtc (BOleData *data) : pData (data), nRef (1)
{
#ifdef _DEBUG
	OutputDebugString( TEXT("EnumFormatEtc Created\n\r") );
#endif

	pCurItem = pFirstItem = pLastItem = NULL;
	Reset();
	// Hold the data object
	pData->AddRef();
}

HRESULT _IFUNC BOleEnumFormatEtc::QueryInterface(REFIID iid, LPVOID FAR *ppv)
{
	HRESULT hr = ResultFromScode(E_NOINTERFACE);
	*ppv = NULL;

	// interfaces
	hr = IEnumFORMATETC_QueryInterface(this, iid, ppv);

	return hr;
}

ULONG _IFUNC BOleEnumFormatEtc::AddRef()
{
	return ++nRef;
}

ULONG _IFUNC BOleEnumFormatEtc::Release()
{
	if (--nRef == 0) {
#ifdef _DEBUG
		OutputDebugString( TEXT("EnumFormatEtc Deleted\n\r") );
#endif
		pData->Release();
		delete this;
		return 0;
	} 
	else
		return nRef;
}

HRESULT _IFUNC BOleEnumFormatEtc::Next (ULONG celt,
							   FORMATETC FAR * rgelt,
							   ULONG FAR* pceltFetched)
{
	// In case the format list has changed since the last call, resynchronize
	Resync();

	// Initialize output parameters
	//
	if (pceltFetched)
		*pceltFetched = 0;
	if (rgelt)
		_fmemset (rgelt, 0, sizeof(FORMATETC) * celt);

	// Error on output parameter
	//
	if (!rgelt)
		return ResultFromScode (DV_E_FORMATETC);

	// Either no data in the cache, or we've already enumerated them all
	//
	if (!pFirstItem || !pCurItem)
		return ResultFromScode (S_FALSE);

	// Loop over formats, copying as many as we have, or as many as they
	// asked for, whichever comes first
	//
	short nCopied = 0;
	while (pCurItem && (celt > 0)) {

		*rgelt = pCurItem->fmtEtc;		// Copy formats

		rgelt++;								// Increment pointers
		pCurItem = pCurItem->pNext;

		nCopied++;							// Increment counters
		celt--;
	}

	if (pceltFetched)
		*pceltFetched = nCopied;

	return NOERROR;
}

HRESULT _IFUNC BOleEnumFormatEtc::Skip (ULONG celt)
{
	// In case the format list has changed since the last call, resynchronize
	Resync();

	// Save off the current item pointer in case Skip fails
	//
	BOleDataItem FAR *pTmp = pCurItem;

	// Skip as many items as we can
	//
	ULONG count = 0;
	while (pCurItem && (count < celt)) {
		pCurItem = pCurItem->pNext;
		count++;
	}

	// Ran off the edge of the list before skipping the requested number
	// of items. Restore current item pointer
	//
	if (count < celt) {
		pCurItem = pTmp;
		return ResultFromScode (S_FALSE);
	}

	return NOERROR;
}

HRESULT _IFUNC BOleEnumFormatEtc::Reset ()
{
	// In case the format list has changed since the last call, resynchronize
	Resync();

	// Reset the cursor to the head of the list.
	//
	pCurItem = pFirstItem;
	return NOERROR;
}

HRESULT _IFUNC BOleEnumFormatEtc::Clone (LPENUMFORMATETC FAR* ppEnum)
{
	// In case the format list has changed since the last call, resynchronize
	Resync();

	*ppEnum = NULL;

	// Get a memory allocator from COM
	//
	LPMALLOC pMalloc = NULL;
	HRESULT hr = ::CoGetMalloc (MEMCTX_TASK, &pMalloc);
	if (!SUCCEEDED(hr))
		return hr;

	// Allocate a new BOleDataObject
	//
	BOleEnumFormatEtc FAR *pClone = (BOleEnumFormatEtc FAR*) pMalloc->Alloc (sizeof(BOleEnumFormatEtc));
	pMalloc->Release ();
	if (!pClone)
		return ResultFromScode (E_OUTOFMEMORY);

	// Copy the pointers from this BOleData object into the new one.
	// Note that the data cache is not copied, but I think that's ok since
	// cloning the enumerator is supposed to provide a snapshot of the state,
	// not necessarily the whole data cache
	//

	_fmemcpy (pClone, this, sizeof(BOleEnumFormatEtc));
	*ppEnum = pClone;

	return NOERROR;
}


void BOleEnumFormatEtc::Resync(){
	// Now that the enumerator is seperated from the data object, we have
	// to make sure the format list is still the same. The cheap way is just
	// to check that the two ends of the list are still the same, otherwise
	// we decide to restart
	//
	if( pFirstItem != pData->pFirstItem || pLastItem != pData->pLastItem ){
		pFirstItem = pData->pFirstItem;
		pLastItem = pData->pLastItem;
		pCurItem = pData->pFirstItem;
	}
}

//**************************************************************************
//
// BOleShadowData --	Prevent EmptyClipboard from deleting our real object
//                   when it calls CoDisconnectObject. (see class defn)
//
//**************************************************************************

BOleShadowData::BOleShadowData (BOleClassManager *pCM, 
                                LPDATAOBJECT pDeleg) 	: 
                                pDelegate (pDeleg), 
                                BOleComponent(pCM, NULL)
{
	pDelegate->AddRef();
}

BOleShadowData::~BOleShadowData () 
{
	if (NOERROR == OleIsCurrentClipboard (this))
		pFactory->GetService()->NotifyClipboardEmptied();

	pDelegate->Release();
}

HRESULT _IFUNC BOleShadowData::QueryInterfaceMain(REFIID iid, void FAR* FAR* pif)
{
	HRESULT hr = ResultFromScode(E_NOINTERFACE);
	*pif = NULL;

	// interfaces
	
	if (iid == IID_BOleShadowData) {
		(BOleShadowData*) *pif = this;
		AddRef ();
		hr = NOERROR;
	}
	else if (SUCCEEDED(hr = IDataObject_QueryInterface(this, iid, pif))) {
	}
	else if SUCCEEDED(hr = BOleComponent::	QueryInterfaceMain(iid, pif)) {
	}
	return hr;
}

HRESULT _IFUNC BOleShadowData::GetData (LPFORMATETC pformatetcIn, LPSTGMEDIUM pmedium)
{
	return pDelegate->GetData (pformatetcIn, pmedium);
}

HRESULT _IFUNC BOleShadowData::GetDataHere (LPFORMATETC pformatetc, LPSTGMEDIUM pmedium)
{
	return pDelegate->GetDataHere (pformatetc, pmedium);
}

HRESULT _IFUNC BOleShadowData::QueryGetData (LPFORMATETC pformatetc)
{
	return pDelegate->QueryGetData (pformatetc);
}

HRESULT _IFUNC BOleShadowData::GetCanonicalFormatEtc (LPFORMATETC pformatetc, LPFORMATETC pformatetcOut)
{
	return pDelegate->GetCanonicalFormatEtc (pformatetc, pformatetcOut);
}

HRESULT _IFUNC BOleShadowData::SetData (LPFORMATETC pformatetc, STGMEDIUM FAR * pmedium, BOOL fRelease)
{
	return pDelegate->SetData (pformatetc, pmedium, fRelease);
}

HRESULT _IFUNC BOleShadowData::EnumFormatEtc (DWORD dwDirection, LPENUMFORMATETC FAR* ppenumFormatEtc)
{
	return pDelegate->EnumFormatEtc (dwDirection, ppenumFormatEtc);
}

HRESULT _IFUNC BOleShadowData::DAdvise (FORMATETC FAR* pFormatetc, DWORD advf, LPADVISESINK pAdvSink, DWORD FAR* pdwConnection)
{
	return pDelegate->DAdvise (pFormatetc, advf, pAdvSink, pdwConnection);
}

HRESULT _IFUNC BOleShadowData::DUnadvise (DWORD dwConnection)
{
	return pDelegate->DUnadvise (dwConnection);
}

HRESULT _IFUNC BOleShadowData::EnumDAdvise (LPENUMSTATDATA FAR* ppenumAdvise)
{
	return pDelegate->EnumDAdvise (ppenumAdvise);
}


