// 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 AFXCTL_PROP_SEG
#pragma code_seg(AFXCTL_PROP_SEG)
#endif

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

#define new DEBUG_NEW

/////////////////////////////////////////////////////////////////////////////
// CBlobProperty

class CBlobProperty : public IPersistStream
{
public:
	CBlobProperty(HGLOBAL pBlob = NULL);
	HGLOBAL GetBlob();

	STDMETHOD_(ULONG, AddRef)();
	STDMETHOD_(ULONG, Release)();
	STDMETHOD(QueryInterface)(REFIID, LPVOID*);

	STDMETHOD(GetClassID)(LPCLSID);
	STDMETHOD(IsDirty)();
	STDMETHOD(Load)(LPSTREAM);
	STDMETHOD(Save)(LPSTREAM, BOOL);
	STDMETHOD(GetSizeMax)(ULARGE_INTEGER*);

protected:
	long m_dwRef;
	HGLOBAL m_hBlob;
};

CBlobProperty::CBlobProperty(HGLOBAL hBlob) :
	m_hBlob(hBlob),
	m_dwRef(1)
{
}

HGLOBAL CBlobProperty::GetBlob()
{
	return m_hBlob;
}

STDMETHODIMP_(ULONG) CBlobProperty::AddRef()
{
	return InterlockedIncrement(&m_dwRef);
}

STDMETHODIMP_(ULONG) CBlobProperty::Release()
{
	if (InterlockedDecrement(&m_dwRef) > 0)
		return m_dwRef;

	delete this;
	return 0;
}

STDMETHODIMP CBlobProperty::QueryInterface(REFIID riid, LPVOID* ppvObj)
{
	if (IsEqualIID(riid, IID_IUnknown) ||
		IsEqualIID(riid, IID_IPersist) ||
		IsEqualIID(riid, IID_IPersistStream))
	{
		AddRef();
		*ppvObj = this;
		return S_OK;
	}

	*ppvObj = NULL;
	return E_NOINTERFACE;
}

static const CLSID _clsidBlobProperty =
{ 0xf6f07540, 0x42ec, 0x11ce, { 0x81, 0x35, 0x0, 0xaa, 0x0, 0x4b, 0xb8, 0x51 } };

STDMETHODIMP CBlobProperty::GetClassID(LPCLSID pClsid)
{
	*pClsid = _clsidBlobProperty;
	return S_OK;
}

STDMETHODIMP CBlobProperty::IsDirty()
{
	return S_OK;
}

STDMETHODIMP CBlobProperty::Load(LPSTREAM pStream)
{
	ULONG cb;
	ULONG cbRead;
	HRESULT hr = pStream->Read(&cb, sizeof(ULONG), &cbRead);

	if (FAILED(hr))
		return hr;

	if (sizeof(ULONG) != cbRead)
		return E_FAIL;

	HGLOBAL hBlobNew = GlobalAlloc(GMEM_MOVEABLE, sizeof(ULONG)+cb);
	if (hBlobNew == NULL)
		return E_OUTOFMEMORY;

	void* pBlobNew = GlobalLock(hBlobNew);
	*(ULONG*)pBlobNew = cb;
	hr = pStream->Read(((ULONG*)pBlobNew)+1, cb, &cbRead);
	GlobalUnlock(hBlobNew);

	if (FAILED(hr))
	{
		GlobalFree(hBlobNew);
		return hr;
	}
	if (cb != cbRead)
	{
		GlobalFree(hBlobNew);
		return E_FAIL;
	}

	GlobalFree(m_hBlob);
	m_hBlob = hBlobNew;
	return S_OK;
}

STDMETHODIMP CBlobProperty::Save(LPSTREAM pStream, BOOL)
{
	void* pBlob = GlobalLock(m_hBlob);
	if (pBlob == NULL)
		return E_OUTOFMEMORY;

	ULONG cb = sizeof(ULONG) + *(ULONG*)pBlob;
	ULONG cbWritten;
	HRESULT hr = pStream->Write(pBlob, cb, &cbWritten);

	GlobalUnlock(m_hBlob);

	if (FAILED(hr))
		return hr;

	if (cb != cbWritten)
		return E_FAIL;

	return S_OK;
}

STDMETHODIMP CBlobProperty::GetSizeMax(ULARGE_INTEGER* pcbSize)
{
	void* pBlob = GlobalLock(m_hBlob);
	if (pBlob == NULL)
		return E_OUTOFMEMORY;

	pcbSize->HighPart = 0;
	pcbSize->LowPart = sizeof(ULONG) + *(ULONG*)pBlob;

	GlobalUnlock(m_hBlob);
	return S_OK;
}

/////////////////////////////////////////////////////////////////////////////
// CPropbagPropExchange

class CPropbagPropExchange : public CPropExchange
{
public:
	CPropbagPropExchange(LPPROPERTYBAG pPropBag, LPERRORLOG pErrorLog,
		BOOL bLoading, BOOL bSaveAllProperties=FALSE);
	~CPropbagPropExchange();

// Operations
	virtual BOOL ExchangeProp(LPCTSTR pszPropName, VARTYPE vtProp,
				void* pvProp, const void* pvDefault = NULL);
	virtual BOOL ExchangeBlobProp(LPCTSTR pszPropName, HGLOBAL* phBlob,
				HGLOBAL hBlobDefault = NULL);
	virtual BOOL ExchangeFontProp(LPCTSTR pszPropName, CFontHolder& font,
				const FONTDESC* pFontDesc, LPFONTDISP pFontDispAmbient);
	virtual BOOL ExchangePersistentProp(LPCTSTR pszPropName,
				LPUNKNOWN* ppUnk, REFIID iid, LPUNKNOWN pUnkDefault);

// Implementation
	LPPROPERTYBAG m_pPropBag;
	LPERRORLOG m_pErrorLog;
	BOOL m_bSaveAllProperties;
};

CPropbagPropExchange::CPropbagPropExchange(LPPROPERTYBAG pPropBag,
	LPERRORLOG pErrorLog, BOOL bLoading, BOOL bSaveAllProperties) :
	m_pPropBag(pPropBag),
	m_pErrorLog(pErrorLog),
	m_bSaveAllProperties(bSaveAllProperties)
{
	m_bLoading = bLoading;
	if (pPropBag != NULL)
		pPropBag->AddRef();
	if (pErrorLog != NULL)
		pErrorLog->AddRef();
}

CPropbagPropExchange::~CPropbagPropExchange()
{
	RELEASE(m_pPropBag);
	RELEASE(m_pErrorLog);
}

BOOL CPropbagPropExchange::ExchangeProp(LPCTSTR pszPropName, VARTYPE vtProp,
	void* pvProp, const void* pvDefault)
{
	USES_CONVERSION;

	ASSERT_POINTER(m_pPropBag, IPropertyBag);
	ASSERT(AfxIsValidString(pszPropName));
	ASSERT(AfxIsValidAddress(pvProp, 1, FALSE));
	ASSERT((pvDefault == NULL) || AfxIsValidAddress(pvDefault, 1, FALSE));

	if (m_pPropBag == NULL)
		return FALSE;

	BOOL bSuccess = TRUE;
	VARIANT var;
	AfxVariantInit(&var);
	V_VT(&var) = vtProp;

	if (m_bLoading)
	{
		if (FAILED(m_pPropBag->Read(T2COLE(pszPropName), &var, m_pErrorLog)))
			return _AfxCopyPropValue(vtProp, pvProp, pvDefault);

		switch (vtProp)
		{
		case VT_I2:
			*(short*)pvProp = V_I2(&var);
			break;

		case VT_I4:
			*(long*)pvProp = V_I4(&var);
			break;

		case VT_BOOL:
			*(BOOL*)pvProp = (BOOL)V_BOOL(&var);
			break;

		case VT_BSTR:
			*(CString*)pvProp = OLE2CT(V_BSTR(&var));
			break;

		case VT_CY:
			*(CY*)pvProp = V_CY(&var);
			break;

		case VT_R4:
			memcpy(pvProp, &V_R4(&var), sizeof(float));
			break;

		case VT_R8:
			memcpy(pvProp, &V_R8(&var), sizeof(double));
			break;

		default:
			bSuccess = FALSE;
		}
	}
	else
	{
		if (m_bSaveAllProperties ||
			!_AfxIsSamePropValue(vtProp, pvProp, pvDefault))
		{
			switch (vtProp)
			{
			case VT_I2:
				V_I2(&var) = *(short*)pvProp;
				break;

			case VT_I4:
				V_I4(&var) = *(long*)pvProp;
				break;

			case VT_BOOL:
				V_BOOL(&var) = (VARIANT_BOOL)*(BOOL*)pvProp;
				break;

			case VT_BSTR:
				V_BSTR(&var) = SysAllocString(T2COLE(*(CString*)pvProp));
				break;

			case VT_CY:
				V_CY(&var) = *(CY*)pvProp;
				break;

			case VT_R4:
				memcpy(&V_R4(&var), pvProp, sizeof(float));
				break;

			case VT_R8:
				memcpy(&V_R8(&var), pvProp, sizeof(double));
				break;

			default:
				return FALSE;
			}
			bSuccess = SUCCEEDED(m_pPropBag->Write(T2COLE(pszPropName), &var));
		}
	}

	VariantClear(&var);
	return bSuccess;
}

BOOL CPropbagPropExchange::ExchangeBlobProp(LPCTSTR pszPropName,
	HGLOBAL* phBlob, HGLOBAL hBlobDefault)
{
	USES_CONVERSION;

	ASSERT_POINTER(m_pPropBag, IPropertyBag);
	ASSERT(AfxIsValidString(pszPropName));
	ASSERT_POINTER(phBlob, HGLOBAL);

	BOOL bSuccess = FALSE;
	VARIANT var;
	AfxVariantInit(&var);
	V_VT(&var) = VT_UNKNOWN;

	if (m_bLoading)
	{
		if (*phBlob != NULL)
		{
			GlobalFree(*phBlob);
			*phBlob = NULL;
		}

		CBlobProperty* pBlobProp = new CBlobProperty;
		V_UNKNOWN(&var) = pBlobProp;

		if (SUCCEEDED(m_pPropBag->Read(T2COLE(pszPropName), &var, m_pErrorLog)))
		{
			*phBlob = pBlobProp->GetBlob();
			bSuccess = TRUE;
		}
		else
		{
			if (hBlobDefault != NULL)
				bSuccess = _AfxCopyBlob(phBlob, hBlobDefault);
		}

		pBlobProp->Release();
	}
	else
	{
		CBlobProperty* pBlobProp = new CBlobProperty(*phBlob);
		V_UNKNOWN(&var) = pBlobProp;
		bSuccess = SUCCEEDED(m_pPropBag->Write(T2COLE(pszPropName), &var));
		pBlobProp->Release();
	}
	return bSuccess;
}

BOOL CPropbagPropExchange::ExchangeFontProp(LPCTSTR pszPropName,
	CFontHolder& font, const FONTDESC* pFontDesc,
	LPFONTDISP pFontDispAmbient)
{
	USES_CONVERSION;

	ASSERT_POINTER(m_pPropBag, IPropertyBag);
	ASSERT(AfxIsValidString(pszPropName));
	ASSERT_POINTER(&font, CFontHolder);
	ASSERT_NULL_OR_POINTER(pFontDesc, FONTDESC);
	ASSERT_NULL_OR_POINTER(pFontDispAmbient, IFontDisp);

	BOOL bSuccess = FALSE;
	VARIANT var;
	AfxVariantInit(&var);
	V_VT(&var) = VT_UNKNOWN;

	if (m_bLoading)
	{
		LPFONT pFont = NULL;

		bSuccess =
			SUCCEEDED(m_pPropBag->Read(T2COLE(pszPropName), &var,
				m_pErrorLog)) &&
			SUCCEEDED(V_UNKNOWN(&var)->QueryInterface(IID_IFont,
				(LPVOID*)&pFont));

		if (bSuccess)
		{
			ASSERT_POINTER(pFont, IFont);
			font.SetFont(pFont);
		}
		else
		{
			// Initialize font to its default state
			font.InitializeFont(pFontDesc, pFontDispAmbient);
		}
		VariantClear(&var);
	}
	else
	{
		if ((font.m_pFont == NULL) ||
			(_AfxIsSameFont(font, pFontDesc, pFontDispAmbient) &&
				!m_bSaveAllProperties))
		{
			bSuccess = TRUE;
		}
		else
		{
			V_UNKNOWN(&var) = font.m_pFont;
			bSuccess = SUCCEEDED(m_pPropBag->Write(T2COLE(pszPropName), &var));
		}
	}

	return bSuccess;
}

BOOL CPropbagPropExchange::ExchangePersistentProp(LPCTSTR pszPropName,
	LPUNKNOWN* ppUnk, REFIID iid, LPUNKNOWN pUnkDefault)
{
	USES_CONVERSION;

	ASSERT_POINTER(m_pPropBag, IPropertyBag);
	ASSERT(AfxIsValidString(pszPropName));
	ASSERT_POINTER(ppUnk, LPUNKNOWN);
	ASSERT_NULL_OR_POINTER(pUnkDefault, IUnknown);

	BOOL bSuccess = FALSE;
	VARIANT var;
	AfxVariantInit(&var);
	V_VT(&var) = VT_UNKNOWN;

	if (m_bLoading)
	{
		RELEASE(*ppUnk);
		*ppUnk = NULL;

		bSuccess =
			SUCCEEDED(m_pPropBag->Read(T2COLE(pszPropName), &var,
				m_pErrorLog)) &&
			SUCCEEDED(V_UNKNOWN(&var)->QueryInterface(iid, (LPVOID*)ppUnk));

		if (!bSuccess)
		{
			// Use default value.
			if (pUnkDefault != NULL)
			{
				bSuccess = SUCCEEDED(pUnkDefault->QueryInterface(iid,
					(LPVOID*)ppUnk));
			}
			else
			{
				bSuccess = TRUE;
			}
		}
		VariantClear(&var);
	}
	else
	{
		if ((*ppUnk == NULL) ||
			(_AfxIsSameUnknownObject(iid, *ppUnk, pUnkDefault) &&
				!m_bSaveAllProperties))
		{
			bSuccess = TRUE;
		}
		else
		{
			V_UNKNOWN(&var) = *ppUnk;
			bSuccess = SUCCEEDED(m_pPropBag->Write(T2COLE(pszPropName), &var));
		}
	}

	return bSuccess;
}

/////////////////////////////////////////////////////////////////////////////
// COleControl::XPersistPropertyBag

STDMETHODIMP_(ULONG) COleControl::XPersistPropertyBag::AddRef()
{
	METHOD_PROLOGUE_EX_(COleControl, PersistPropertyBag)
	return (ULONG)pThis->ExternalAddRef();
}

STDMETHODIMP_(ULONG) COleControl::XPersistPropertyBag::Release()
{
	METHOD_PROLOGUE_EX_(COleControl, PersistPropertyBag)
	return (ULONG)pThis->ExternalRelease();
}

STDMETHODIMP COleControl::XPersistPropertyBag::QueryInterface(
	REFIID iid, LPVOID* ppvObj)
{
	METHOD_PROLOGUE_EX_(COleControl, PersistPropertyBag)
	return (HRESULT)pThis->ExternalQueryInterface(&iid, ppvObj);
}

STDMETHODIMP COleControl::XPersistPropertyBag::GetClassID(LPCLSID lpClassID)
{
	METHOD_PROLOGUE_EX_(COleControl, PersistPropertyBag)
	return pThis->GetClassID(lpClassID);
}

STDMETHODIMP COleControl::XPersistPropertyBag::InitNew()
{
	METHOD_PROLOGUE_EX(COleControl, PersistPropertyBag)

	// Delegate to OnResetState.
	pThis->OnResetState();

	// Unless IOleObject::SetClientSite is called after this, we can
	// count on ambient properties being available while loading.
	pThis->m_bCountOnAmbients = TRUE;

	// Properties have been initialized
	pThis->m_bInitialized = TRUE;

	return S_OK;
}

STDMETHODIMP COleControl::XPersistPropertyBag::Load(LPPROPERTYBAG pPropBag,
	LPERRORLOG pErrorLog)
{
	METHOD_PROLOGUE_EX(COleControl, PersistPropertyBag)

	HRESULT hr;

	TRY
	{
		CPropbagPropExchange px(pPropBag, pErrorLog, TRUE);
		pThis->DoPropExchange(&px);
		hr = S_OK;
	}
	CATCH_ALL(e)
	{
		hr = E_FAIL;
		DELETE_EXCEPTION(e);
	}
	END_CATCH_ALL

	// Clear the modified flag.
	pThis->m_bModified = FALSE;

	// Properties have been initialized
	pThis->m_bInitialized = TRUE;

	return hr;
}

STDMETHODIMP COleControl::XPersistPropertyBag::Save(LPPROPERTYBAG pPropBag,
	BOOL fClearDirty, BOOL fSaveAllProperties)
{
	METHOD_PROLOGUE_EX(COleControl, PersistPropertyBag)

	HRESULT hr;

	TRY
	{
		CPropbagPropExchange px(pPropBag, NULL, FALSE, fSaveAllProperties);
		pThis->DoPropExchange(&px);
		hr = S_OK;
	}
	CATCH_ALL(e)
	{
		hr = E_FAIL;
		DELETE_EXCEPTION(e);
	}
	END_CATCH_ALL

	// Bookkeeping:  Clear the dirty flag, if requested.
	if (fClearDirty)
		pThis->m_bModified = FALSE;

	return hr;
}
