// if you use precompiled headers, place these #include in stdafx.h
#include <windows.h>
#include <objidl.h>
#include <tchar.h>


/*
 * Sample FORMATETC enumerating object, which assumes that the data object only
 * exports a single CF_TEXT format with standard aspects/media/etc
 */
class CEnumFORMATETCImpl : public IEnumFORMATETC
{
public:
	// IUnknown methods
	STDMETHOD(QueryInterface)(REFIID iid, void** pp)
	{
		OutputDebugString(_T("IEnumFORMATETCImpl::QueryInterface\n"));
		return E_NOTIMPL;
		// nobody is ever likely to query us for something else
	}
	STDMETHOD_(ULONG, AddRef)()
	{
		TCHAR buf_[100];
		sprintf(buf_, _T("IEnumFORMATETCImpl::AddRef (cnt=%d)\n"), m_dwRef+1);
		OutputDebugString(buf_);
		return ++m_dwRef;
	}
	STDMETHOD_(ULONG, Release)()
	{
		TCHAR buf_[100];
		sprintf(buf_, _T("IEnumFORMATETCImpl::Release (cnt=%d)\n"), m_dwRef-1);
		OutputDebugString(buf_);
		return --m_dwRef;
	}

	// enum stuffen
	STDMETHOD(Next)(ULONG celt, FORMATETC* rgelt, ULONG* pceltFetched)
	{
		OutputDebugString(_T("IEnumFORMATETCImpl::Next\n"));
		if (rgelt == NULL || (celt != 1 && pceltFetched == NULL))
			return E_POINTER;

		if(!m_dwIndex) { // only one position we support
			m_dwIndex++;
			rgelt->cfFormat = CF_TEXT;
			rgelt->dwAspect = DVASPECT_CONTENT;
			rgelt->lindex = -1;
			rgelt->ptd = NULL; // nothing especial
			rgelt->tymed = TYMED_HGLOBAL;

			if(pceltFetched) *pceltFetched = 1; // alwayz
			return S_OK;
		}
		else m_dwIndex = 0; // quick fix to make object reentrant

		return S_FALSE; // indicates there's nothing more
	}
	STDMETHOD(Skip)(ULONG celt)
	{
		OutputDebugString(_T("IEnumFORMATETCImpl::Skip\n"));
		m_dwIndex += celt;
		if(m_dwIndex > 1) {
			m_dwIndex = 0;
			return S_FALSE;
		}
		return S_OK;
	}
	STDMETHOD(Reset)(void)
	{
		OutputDebugString(_T("IEnumFORMATETCImpl::Reset\n"));
		m_dwIndex = 0;
		return S_OK;
	}
	STDMETHOD(Clone)(IEnumFORMATETC** /*ppEnum*/)
	{
		OutputDebugString(_T("IEnumFORMATETCImpl::Clone\n"));
		return E_NOTIMPL;
	}

	// finally constructor
	CEnumFORMATETCImpl() {m_dwRef = m_dwIndex = 0;}
private:
	DWORD m_dwRef; // reference count
	DWORD m_dwIndex; // which of the various formats is next in line
};

/*
 * Accompanying data object, bearer of the fixed text to be placed in the clipboard.
 * Most functionality is missing (E_NOTIMPL) but it should be enough to experiment
 * with the OLE clipboard.
 */
class CDataObjectImpl : public IDataObject
{
public:
	// IUnknown methods
	STDMETHOD(QueryInterface)(REFIID iid, void** pp)
	{
		OutputDebugString(_T("IDataObjectImpl::QueryInterface\n"));
		return E_NOTIMPL;
	}
	STDMETHOD_(ULONG, AddRef)()
	{
		TCHAR buf_[100];
		sprintf(buf_, _T("IDataObjectImpl::AddRef (cnt=%d)\n"), m_dwRef+1);
		OutputDebugString(buf_);
		return ++m_dwRef;
	}
	STDMETHOD_(ULONG, Release)()
	{
		TCHAR buf_[100];
		sprintf(buf_, _T("IDataObjectImpl::Release (cnt=%d)\n"), m_dwRef-1);
		OutputDebugString(buf_);
		return --m_dwRef;
		// obviously we don't "delete" ourselves, we are always on the stack
	}

	// other DataObject stuff
	STDMETHOD(GetData)(FORMATETC *pformatetcIn, STGMEDIUM *pmedium)
	{
		OutputDebugString(_T("IDataObjectImpl::GetData (")); // in lieu of trace
		TCHAR buf_[100];
		sprintf(buf_, _T("Format = 0x%x, Medium = 0x%x)\n"), 
			pformatetcIn->cfFormat, pformatetcIn->tymed);
		OutputDebugString(buf_);

		if ( !(pformatetcIn && pmedium) ) return E_POINTER;
		ZeroMemory(pmedium, sizeof(pmedium));

		HRESULT hr = QueryGetData(pformatetcIn); // shared functionality
		if(hr != S_OK) return hr;

		LPCTSTR txt = _T("Hello dude!"); // same thing all the time
		HGLOBAL hMem = GlobalAlloc(GMEM_SHARE | GMEM_MOVEABLE, 
			(lstrlen(txt)+1)*sizeof(txt[0]) );
		if (NULL==hMem) return STG_E_MEDIUMFULL;

		LPTSTR pText =(LPTSTR)GlobalLock(hMem);
		lstrcpy(pText, txt);
		GlobalUnlock(hMem);

		// never mind what he asked in the formatEtc, pass what he's getting
		pmedium->hGlobal = hMem;
		pmedium->tymed = TYMED_HGLOBAL;
		pmedium->pUnkForRelease = NULL; // regular deletion with GlobalFree

		return S_OK;
	}
	STDMETHOD(GetDataHere)(FORMATETC* /* pformatetc */, STGMEDIUM* /* pmedium */)
	{
		OutputDebugString(_T("IDataObjectImpl::GetDataHere\n"));
		return E_NOTIMPL;
	}
	STDMETHOD(QueryGetData)(FORMATETC* pformatetcIn)
	{
		OutputDebugString(_T("IDataObjectImpl::QueryGetData\n"));
		if ( !pformatetcIn ) return E_POINTER;

		// supply rich error information for callers
		if ((pformatetcIn->tymed & TYMED_HGLOBAL) == 0)	return DV_E_TYMED;
		if(pformatetcIn->cfFormat != CF_TEXT) return DATA_E_FORMATETC;
		if(pformatetcIn->dwAspect != DVASPECT_CONTENT) return DV_E_DVASPECT;
		if(pformatetcIn->lindex != -1) return DV_E_LINDEX;
		if(pformatetcIn->ptd) return DATA_E_FORMATETC;

		return S_OK; // should be ok
	}
	STDMETHOD(GetCanonicalFormatEtc)(FORMATETC* /* pformatectIn */,FORMATETC* /* pformatetcOut */)
	{
		OutputDebugString(_T("IDataObjectImpl::GetCanonicalFormatEtc\n"));
		return E_NOTIMPL;
	}
	STDMETHOD(SetData)(FORMATETC* /* pformatetc */, STGMEDIUM* /* pmedium */, BOOL /* fRelease */)
	{
		OutputDebugString(_T("IDataObjectImpl::SetData\n"));
		return E_NOTIMPL;
	}
	STDMETHOD(EnumFormatEtc)(DWORD dwDirection, IEnumFORMATETC** ppenumFormatEtc)
	{
		// this is the first called when we OleSetClipboard
		OutputDebugString(_T("IDataObjectImpl::EnumFormatEtc\n"));

		if(dwDirection == DATADIR_GET) {
			*ppenumFormatEtc = &m_enumResident;
			m_enumResident.AddRef();
			return S_OK;
		}

		*ppenumFormatEtc = NULL;
		return E_NOTIMPL;
	}
	STDMETHOD(DAdvise)(FORMATETC* /*pformatetc*/, DWORD /*advf*/, 
		IAdviseSink* /*pAdvSink*/, DWORD* /*pdwConnection*/)
	{
		OutputDebugString(_T("IDataObjectImpl::DAdvise\n"));
		return E_NOTIMPL;
	}
	STDMETHOD(DUnadvise)(DWORD /*dwConnection*/)
	{
		OutputDebugString(_T("IDataObjectImpl::DUnadvise\n"));
		return E_NOTIMPL;
	}
	STDMETHOD(EnumDAdvise)(IEnumSTATDATA ** /*ppenumAdvise*/)
	{
		OutputDebugString(_T("IDataObjectImpl::EnumDAdvise\n"));
		return E_NOTIMPL;
	}

	// finally constructor
	CDataObjectImpl() {m_dwRef = 0;}
private:
	DWORD m_dwRef; // reference count
	CEnumFORMATETCImpl m_enumResident; // lokali
};

