windows-nt/Source/XPSP1/NT/shell/ext/taskui/propbag.cpp

2233 lines
56 KiB
C++
Raw Normal View History

2020-09-26 03:20:57 -05:00
//+-------------------------------------------------------------------------
//
// Microsoft Windows
//
// Copyright (C) Microsoft Corporation, 2000
//
// File: propbag.cpp
//
// This property bag implementation supports the standard COM interface
// IPropertyBag as well as the specialized interface ITaskSheetPropertyBag.
//
// The following facilities are available through ITaskSheetPropertyBag.
//
// 1. Property change notifications.
// 2. Constants
// 3. Property groups (namespace scoping)
// 4. Use IDs or names for property identification
//
//
// Here's the general structure of the implementation.
//
// The implementation uses 8 C++ classes.
//
// CPropertyBag - The property bag.
//
// CPropertyGroup - A group of properties. All properties in a group
// exist in their own namespace. A default 'global' group is always
// present. A property bag can have an unlimited number of groups.
// Groups can be added and removed dynamically by the application.
//
// CPropertyBucket - A set of properties who's identifiers (ID or name)
// hash to the same value. It's just a simple container to manage
// hash table collisions. Each property group has HASH_TABLE_SIZE - 1
// buckets. Buckets are created on-demand so an unused hash value
// has a minimal cost.
//
// CProperty - A property in the property bag. A property has an
// identifier (ID or name), a value and a set of flags. Currently,
// only the "CONSTANT" flag is used. A property may be a read-write
// or a constant value. Each property bucket contains one or more
// CProperty objects. CProperty objects may not be removed individually.
// They are removed only through destruction of the property bag or
// removal of the parent property group.
//
// CPropertyName - The identifier of a property. Abstracts the use
// of both a property ID and property name from the rest of the
// implementation. Each CProperty object has one CPropertyName
// member. Handles identity comparison of two properties.
//
// CNotifyMap - A simple bit map used to represent notification clients
// interested in changes to one or more properties in the property
// bag. Each bit corresponds directly to an entry in the "notify
// client table" in the CNotifier object. The table is merely an
// array of pointers to ITaskSheetPropertyNotifySink. Therefore, if
// bit 5 is set, that means the the client who's notify sink pointer
// is stored in element 5 of the notify table wants to be notified.
//
// Each of the following objects has a notify map:
//
// 1. CPropertyBag - This is the 'global' notify map. A bit set
// in this map indicates the corresponding client is
// interested in changes to ANY property in the bag.
//
// 2. CPropertyGroup - A bit set in a group's notify map indicates
// the corresponding client is interested in changes to
// any property in the group.
//
// 3. CProperty - A bit set in a property's notify map indicates
// the corresponding client is interested in changes
// to this property.
//
// 4. CNotifier - This is the 'active' notify map and is used
// to indicate which clients are to receive notification
// of the change to the property currently being changed
// by a 'set' operation. See below for a description of
// the CNotifier object.
//
// CNotifier - Handles registration of notification clients and performs
// client notifications when properties change. The property bag
// has a single CNotifier member. When a client registers for change
// notifications, the client's ITaskSheetPropertyNotifySink pointer
// is stored in the notifier's client table (array of ptrs). The
// index of the pointer is the "cookie" returned to the client.
// When a "set" operation begins, the notifier is initialized and
// a reference to it is passed down the function call chain
// (bag->group->bucket->property). At the 'bag', 'group' and 'property'
// points in the call stack each respective object's "notify map" is
// merged into the notifier's 'active' notify map. Once the call reaches
// the point of setting the property value, the map contains bits set to
// represent each of the notification clients interested in changes to
// this property. The notifier is then called to notify all interested
// clients. Clients may be dynamically added or removed at any time.
//
//
//
// The implementation of IPropertyBag automatically uses the 'global'
// property group. Therefore, if you were to write some properties
// through IPropertyBag::Write, those properties would be avaialble through
// the 'global' property group (PROPGROUP_GLOBAL) when using
// ITaskSheetPropertyBag.
//
//
// [brianau - 06/20/00]
//
//--------------------------------------------------------------------------
#include "stdafx.h"
#include "taskui.h"
#include "cdpa.h"
//
// Size of each property group hash table. This should be a prime
// number for best hashing distribution.
//
const int HASH_TABLE_SIZE = 13;
//
// The type of data used by the change notify map.
// A ULONGLONG provides 64 bits which means we can have 64 registered
// notification clients. Use a DWORD and this drops to 32.
//
typedef ULONGLONG NOTIFYMAPTYPE;
//
// The maximum number of unique change notify connections. It's just the
// number of bits available in a notify map.
//
const int MAX_NOTIFY_CNX = sizeof(NOTIFYMAPTYPE) * 8;
//
// ----------------------------------------------------------------------------
// CASE SENSITIVITY
// ----------------------------------------------------------------------------
// The following macro controls the case sensitivity of the property bag.
// By default the macro is undefined and the property bag is case-insensitive
// with respect to property names. To make the bag case sensitive, uncomment
// this macro and recompile.
//
// #define TASKUI_PROPBAG_CASE_SENSITIVE
//
inline int ComparePropertyNames(LPCWSTR s1, LPCWSTR s2)
{
#ifdef TASKUI_PROPBAG_CASE_SENSITIVE
return lstrcmpW(s1, s2);
#else
return lstrcmpiW(s1, s2);
#endif
}
inline int CharValue(WCHAR c)
{
#ifdef TASKUI_PROPBAG_CASE_SENSITIVE
return c;
#else
return towlower(c);
#endif
}
//
// Validate the writeability of an "out" pointer. This just saves
// us from entering the sizeof() part, potentially reducing bugs.
//
template <typename T>
inline bool IsValidWritePtr(T *p)
{
return (!IsBadWritePtr(p, sizeof(*p)));
}
//-----------------------------------------------------------------------------
// CNotifyMap
//
// Represents a 64-bit bitmap for the purpose of representing notification
// registrations in the property bag. There are 3 levels of notify maps
// in the system.
//
// 1. Global - owned by the CPropertyBag object.
// 2. Group - one owned by each CPropertyGroup object.
// 3. Property - one owned by each CProperty object.
//
// Whenever a client registers for notification at one of these levels,
// the appropriate map is located and the bit corresponding to that client's
// connection cookie is set. When a property is set, the union of all three
// maps represents the clients requesting notification.
//
//-----------------------------------------------------------------------------
class CNotifyMap
{
public:
CNotifyMap(void)
: m_bits(0) { }
bool IsSet(int iBit) const;
HRESULT Set(int iBit, bool bSet);
bool AnySet(void) const
{ return NOTIFYMAPTYPE(0) != m_bits; }
void Clear(void)
{ m_bits = NOTIFYMAPTYPE(0); }
void Union(const CNotifyMap& rhs)
{ m_bits |= rhs.m_bits; }
private:
NOTIFYMAPTYPE m_bits;
bool _IsValidBitIndex(int iBit) const
{ return (iBit < (sizeof(m_bits) * 8)); }
NOTIFYMAPTYPE _MaskFromBit(int iBit) const
{ return NOTIFYMAPTYPE(1) << iBit; }
};
//
// Set or clear a bit corresponding to a client
//
// Returns:
// S_OK - Bit was set or cleared.
// TSPB_E_NOTIFYCOOKIE - Passed an invalid bit index.
//
HRESULT
CNotifyMap::Set(
int iBit,
bool bSet // true == set bit, false == clear
)
{
HRESULT hr = TSPB_E_NOTIFYCOOKIE;
ASSERT(_IsValidBitIndex(iBit));
if (_IsValidBitIndex(iBit))
{
if (bSet)
{
m_bits |= _MaskFromBit(iBit);
}
else
{
m_bits &= ~(_MaskFromBit(iBit));
}
hr = S_OK;
}
return hr;
}
//
// Determines if a given bit is set in the map.
// On 'free' builds, returns false if an invalid bit number
// is specified. Asserts on 'check' builds.
//
bool
CNotifyMap::IsSet(
int iBit
) const
{
ASSERT(_IsValidBitIndex(iBit));
return (_IsValidBitIndex(iBit) && (0 != (_MaskFromBit(iBit) & m_bits)));
}
//-----------------------------------------------------------------------------
// CNotifier
//
// This class handles all of the duties of client notification including
// client registration, client unregistration and client notification.
//
// A property bag has a notifier as a member. When a client wishes to
// register for a change notification, the property bag calls CNotifier::Register
// to create an entry in the client table. The cookie returned is merely the
// index into the client table.
//
// When a "set prop" operation begins, the notifier's notification map is set
// equal to the bag's global notify map and the prop group handle is set to that
// of the group specified in the "set" operation. A reference to the
// notifier is then passed down the chain of "set" calls
// (bag -> group -> property). At each point the notify maps are merged
// (union) so that the map in the notifier represents all requested
// notifications. Finally, the CProperty::SetValue method sets the property
// then calls CNotifier::Notify. The notifier then scans it's current notify
// map and notifies each client corresponding to bits set.
//
// That sounds like a lot but it really is quite simple and happens very fast.
// The best thing is that is cleanly handles dynamic registration and
// unregstration of clients as well as addition and removal of properties
// and property categories.
//
//-----------------------------------------------------------------------------
class CNotifier
{
public:
explicit CNotifier(ITaskSheetPropertyBag *pBag);
~CNotifier(void);
HRESULT Register(ITaskSheetPropertyNotifySink *pClient, DWORD *pdwCookie);
HRESULT Unregister(DWORD dwCookie);
void Notify(LPCWSTR pszPropertyName);
void MergeWithActiveNotifyMap(const CNotifyMap& map)
{ m_NotifyMapActive.Union(map); }
void Reset(void)
{ m_NotifyMapActive.Clear(); m_hGroup = PROPGROUP_INVALID; }
void SetPropertyGroup(HPROPGROUP hGroup)
{ ASSERT(PROPGROUP_INVALID != hGroup); m_hGroup = hGroup; }
private:
//
// A single entry in the connection table.
// Why use an array of structures rather than simply an array
// of client pointers? Good question! While developing this property
// bag I've twice stored something along with the client pointer in each
// client table slot. Once a ref count, the other time a private data
// ptr. As the final design solidified, neither were required. However,
// I've left the structure just in case we need to again store something
// with the client notification pointer. [brianau - 6/20/00].
//
struct Entry
{
ITaskSheetPropertyNotifySink *pClient; // Client's notification interface ptr.
};
ITaskSheetPropertyBag *m_pBag; // Don't addref. Will create circular reference
Entry m_rgCnx[MAX_NOTIFY_CNX]; // The connection table.
CNotifyMap m_NotifyMapActive; // The "active" notify map.
HPROPGROUP m_hGroup; // Handle of group associated with prop.
// with the property bag.
HRESULT _FindEntry(ITaskSheetPropertyNotifySink *pClient, Entry **ppEntry, DWORD *pdwIndex);
HRESULT _FindFirstUnusedEntry(Entry **ppEntry, DWORD *pdwIndex);
//
// Prevent copy.
//
CNotifier(const CNotifier& rhs);
CNotifier& operator = (const CNotifier& rhs);
};
//-----------------------------------------------------------------------------
// CNotifier
//-----------------------------------------------------------------------------
CNotifier::CNotifier(
ITaskSheetPropertyBag *pBag
) : m_hGroup(PROPGROUP_INVALID),
m_pBag(pBag)
{
ASSERT(NULL != m_pBag);
ZeroMemory(m_rgCnx, sizeof(m_rgCnx));
//
// Note that we DON'T addref the property bag interface.
// Since the CNotifier object is a member of CPropertyBag,
// that would create a circular reference with the property bag.
// We can be assured that the notifier's lifetime is contained
// within the property bag's lifetime.
//
}
CNotifier::~CNotifier(
void
)
{
for (int i = 0; i < ARRAYSIZE(m_rgCnx); i++)
{
if (NULL != m_rgCnx[i].pClient)
{
m_rgCnx[i].pClient->Release();
}
}
//
// DON'T Release m_pBag. See ctor above for details.
//
}
//
// Register a notifiation client. Returns Cookie in pdwCookie to be
// used in call to Unregister if unregistration is desired.
//
// Returns:
// S_OK - Registration successful.
// S_FALSE - Already registered.
//
HRESULT
CNotifier::Register(
ITaskSheetPropertyNotifySink *pClient,
DWORD *pdwCookie // Optional. May be NULL.
)
{
ASSERT(NULL != pClient);
Entry *pEntry = NULL;
DWORD dwIndex = DWORD(-1);
HRESULT hr = _FindEntry(pClient, &pEntry, &dwIndex);
if (S_OK == hr)
{
//
// Found existing entry for this client.
//
hr = S_FALSE;
}
else if (S_FALSE == hr)
{
//
// Need to create a new entry for this client.
// Find a slot for it.
//
hr = _FindFirstUnusedEntry(&pEntry, &dwIndex);
if (SUCCEEDED(hr))
{
ASSERT(NULL == pEntry->pClient);
pClient->AddRef();
pEntry->pClient = pClient;
hr = S_OK;
}
}
if (SUCCEEDED(hr) && NULL != pdwCookie)
{
ASSERT(IsValidWritePtr(pdwCookie));
ASSERT(DWORD(-1) != dwIndex);
*pdwCookie = dwIndex;
}
return hr;
}
//
// Unregister a client to cancel it's reception of all notifications.
// The dwCookie parameter is the one received via the Register method.
//
// Returns:
// S_OK - Connection unregistered.
// TSPB_E_NOTIFYCOOKIE - Cookie references an invalid connection.
//
HRESULT
CNotifier::Unregister(
DWORD dwCookie
)
{
HRESULT hr = TSPB_E_NOTIFYCOOKIE;
if (int(dwCookie) >= 0 && int(dwCookie) < ARRAYSIZE(m_rgCnx))
{
Entry& entry = m_rgCnx[dwCookie];
if (NULL != entry.pClient)
{
entry.pClient->Release();
entry.pClient = NULL;
hr = S_OK;
}
}
return hr;
}
//
// Called by CProperty::SetValue when a value has been altered.
// This function scans the active notify map and notifies each
// registered client specified by a bit set in the map.
//
void
CNotifier::Notify(
LPCWSTR pszPropertyName
)
{
ASSERT(NULL != m_pBag);
ASSERT(NULL != pszPropertyName);
if (m_NotifyMapActive.AnySet())
{
//
// We pass a pointer to the property bag in the notification
// so we AddRef it to ensure it remains alive across the
// notification.
//
m_pBag->AddRef();
//
// As we notify clients we'll clear the corresponding bit in
// notify map. This way we examine the map only as long as
// necessary.
//
for (int i = 0; i < ARRAYSIZE(m_rgCnx) && m_NotifyMapActive.AnySet(); i++)
{
if (m_NotifyMapActive.IsSet(i))
{
ITaskSheetPropertyNotifySink *pClient = m_rgCnx[i].pClient;
ASSERT(NULL != pClient);
if (NULL != pClient)
{
pClient->OnPropChanged(m_pBag, m_hGroup, pszPropertyName);
m_NotifyMapActive.Set(i, false);
}
}
}
m_pBag->Release();
}
//
// Reset our internal state in preparation for the next
// property notify. The map should be clear at this point.
//
ASSERT(!m_NotifyMapActive.AnySet());
Reset();
}
//
// Locates an entry in the client table.
// The pClient argument is the key.
// Using COM identity rules, we QI for IUnknown on both the key
// interface and each of the table entries. If IUnknown ptrs
// match then we found the entry.
//
// Returns:
// S_OK - Entry found.
// S_FALSE - Entry not found.
//
//
HRESULT
CNotifier::_FindEntry(
ITaskSheetPropertyNotifySink *pClient, // Looking for this client.
Entry **ppEntry,
DWORD *pdwIndexOut // Optional. Can be NULL.
)
{
ASSERT(NULL != pClient);
ASSERT(NULL != ppEntry);
ASSERT(IsValidWritePtr(ppEntry));
Entry *pEntry = NULL;
DWORD dwIndex = DWORD(-1);
IUnknown *pUnkClient;
//
// Get the IUnknown interface pointer from both the key and
// each entry. When we find a match we've found the entry.
//
HRESULT hr = pClient->QueryInterface(IID_IUnknown, (void **)&pUnkClient);
if (SUCCEEDED(hr))
{
for (int i = 0; i < ARRAYSIZE(m_rgCnx) && NULL == pEntry; i++)
{
if (NULL != m_rgCnx[i].pClient)
{
IUnknown *pUnkEntry;
hr = m_rgCnx[i].pClient->QueryInterface(IID_IUnknown, (void **)&pUnkEntry);
if (SUCCEEDED(hr))
{
if (pUnkEntry == pUnkClient)
{
pEntry = &m_rgCnx[i];
dwIndex = i;
}
pUnkEntry->Release();
}
}
}
pUnkClient->Release();
}
*ppEntry = pEntry;
if (NULL != pEntry)
{
//
// Entry found.
//
ASSERT(DWORD(-1) != dwIndex);
if (NULL != pdwIndexOut)
{
ASSERT(IsValidWritePtr(pdwIndexOut));
*pdwIndexOut = dwIndex;
}
hr = S_OK;
}
else if (hr == S_OK)
{
//
// Entry not found.
//
ASSERT(NULL == pEntry);
ASSERT(DWORD(-1) == dwIndex);
hr = S_FALSE;
}
return hr;
}
//
// Search the table for the first unused entry.
// Returns:
// S_OK - Unused entry found.
// TSPB_E_MAXNOTIFYCNX - Notify client table is full.
//
HRESULT
CNotifier::_FindFirstUnusedEntry(
Entry **ppEntry,
DWORD *pdwIndexOut // Optional. May be NULL.
)
{
ASSERT(NULL != ppEntry);
ASSERT(IsValidWritePtr(ppEntry));
HRESULT hr = TSPB_E_MAXNOTIFYCNX;
Entry *pEntry = NULL;
DWORD dwIndex = DWORD(-1);
for (int i = 0; i < ARRAYSIZE(m_rgCnx); i++)
{
if (NULL == m_rgCnx[i].pClient)
{
pEntry = &m_rgCnx[i];
dwIndex = i;
hr = S_OK;
break;
}
}
*ppEntry = pEntry;
if (SUCCEEDED(hr))
{
if (NULL != pdwIndexOut)
{
ASSERT(DWORD(-1) != dwIndex);
ASSERT(IsValidWritePtr(pdwIndexOut));
*pdwIndexOut = dwIndex;
}
}
ASSERT(FAILED(hr) || DWORD(-1) != dwIndex);
ASSERT(FAILED(hr) || NULL != pEntry);
return hr;
}
//-----------------------------------------------------------------------------
// CPropertyName
//
// This class handles the dual-nature of property names. A name can either be
// a text string or a property ID created wtih the MAKEPROPID macro. Prop
// IDs are used the same was as Windows Resource IDs thorugh the
// MAKEINTRESOURCE macro.
//-----------------------------------------------------------------------------
class CPropertyName
{
public:
CPropertyName(LPCWSTR pszName);
~CPropertyName(void);
bool IsValid(void) const
{ return NULL != m_pszName; }
bool CompareEqual(LPCWSTR pszName) const;
operator LPCWSTR() const
{ return m_pszName; }
private:
LPWSTR m_pszName;
//
// Prevent copy.
//
CPropertyName(const CPropertyName& rhs);
CPropertyName& operator = (const CPropertyName& rhs);
static bool IsPropID(LPCWSTR pszName)
{ return IS_PROPID(pszName); }
};
CPropertyName::CPropertyName(
LPCWSTR pszName
) : m_pszName(NULL)
{
ASSERT(NULL != pszName);
if (!IsPropID(pszName))
{
m_pszName = StrDupW(pszName);
}
else
{
m_pszName = (LPWSTR)pszName;
}
}
CPropertyName::~CPropertyName(
void
)
{
if (!IsPropID(m_pszName))
{
if (NULL != m_pszName)
{
LocalFree(m_pszName);
}
}
}
bool
CPropertyName::CompareEqual (
LPCWSTR pszName
) const
{
bool bEqual = false;
//
// The pszName (and m_pszName) values can be either a pointer to
// a name string or a property ID.
//
const bool bLhsIsID = IsPropID(m_pszName);
const bool bRhsIsID = IsPropID(pszName);
if (bLhsIsID == bRhsIsID)
{
if (bLhsIsID)
{
bEqual = (m_pszName == pszName);
}
else
{
bEqual = (0 == ComparePropertyNames(m_pszName, pszName));
}
}
return bEqual;
}
//-----------------------------------------------------------------------------
// CProperty
//
// Represents a single property in the property bag.
//-----------------------------------------------------------------------------
class CProperty
{
public:
static HRESULT CreateInstance(LPCWSTR pszName,
const VARIANT *pVar,
bool bConstant,
CProperty **ppPropOut);
~CProperty(void);
HRESULT SetValue(const VARIANT *pVar, CNotifier& notifier);
HRESULT GetValue(VARIANT *pVarOut) const;
LPCWSTR GetName(void) const
{ return m_Name; }
bool CompareNamesEqual(LPCWSTR pszName) const
{ return m_Name.CompareEqual(pszName); }
HRESULT Advise(int iClient)
{ m_NotifyMapProperty.Set(iClient, true); return S_OK; }
HRESULT Unadvise(int iClient)
{ m_NotifyMapProperty.Set(iClient, false); return S_OK; }
private:
CProperty(LPCWSTR pszName, const VARIANT *pVar, bool bConstant);
enum { CONSTANT = 0x00000001 };
CPropertyName m_Name; // The property name or prop ID.
CComVariant m_Value; // The value.
DWORD m_dwFlags; // Flags like 'CONSTANT'.
CNotifyMap m_NotifyMapProperty; // Clients who want notification.
//
// Prevent copy.
//
CProperty(const CProperty& rhs);
CProperty& operator = (const CProperty& rhs);
};
//
// Create a property.
//
HRESULT
CProperty::CreateInstance( // [static]
LPCWSTR pszName,
const VARIANT *pVar,
bool bConstant,
CProperty **ppPropOut
)
{
ASSERT(NULL != pszName);
ASSERT(NULL != ppPropOut);
ASSERT(IsValidWritePtr(ppPropOut));
HRESULT hr = E_OUTOFMEMORY;
*ppPropOut = NULL;
CProperty *pProp = new CProperty(pszName, pVar, bConstant);
if (NULL != pProp)
{
if (pProp->m_Name.IsValid())
{
*ppPropOut = pProp;
hr = S_OK;
}
else
{
delete pProp;
}
}
return hr;
}
CProperty::CProperty(
LPCWSTR pszName,
const VARIANT *pVar,
bool bConstant
) : m_dwFlags(0),
m_Name(pszName)
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVar);
if (bConstant)
{
m_dwFlags |= CONSTANT;
}
m_Value.Copy(pVar);
}
CProperty::~CProperty(
void
)
{
}
//
// Set the property's value.
//
// Returns:
// S_OK - Value set.
// S_FALSE - New value same as old. Not changed.
// TSPB_E_MODIFYCONST - Attempt to modify a const property.
//
HRESULT
CProperty::SetValue(
const VARIANT *pVar,
CNotifier& notifier
)
{
ASSERT(NULL != pVar);
HRESULT hr = TSPB_E_MODIFYCONST;
if (0 == (CONSTANT & m_dwFlags))
{
hr = S_FALSE; // Assume no change.
//
// Only set the value and notify clients if the value is
// going to change.
//
if (m_Value != *pVar)
{
//
// Copy the value. Must first clear the value to release
// any VARIANT memory we might be holding.
//
m_Value.Clear();
hr = m_Value.Copy(pVar);
if (SUCCEEDED(hr))
{
//
// Merge the property's notify map with the 'active'
// notify map. The notifier's 'active' map is now the
// union of the 'global' map, the 'group' map and the
// 'property' map. It contains bits representing
// all of the clients interested in this property change.
//
notifier.MergeWithActiveNotifyMap(m_NotifyMapProperty);
//
// Notify the clients whos bits are set in the 'active' notify
// map.
//
notifier.Notify(m_Name);
}
}
}
return hr;
}
//
// Retrieve the property's value. The value is copied
// the output variant.
//
HRESULT
CProperty::GetValue(
VARIANT *pVarOut
) const
{
ASSERT(NULL != pVarOut);
ASSERT(IsValidWritePtr(pVarOut));
//
// Must first initialize the output variant before copying.
//
::VariantInit(pVarOut);
return ::VariantCopy(pVarOut, (VARIANT *)&m_Value);
}
//-----------------------------------------------------------------------------
// CPropertyBucket
//
// This is a simple container representing the chain of collisions in
// a conventional 'linear-chaining' hash table. The bucket is just a
// DPA of CProperty object ptrs.
//-----------------------------------------------------------------------------
class CPropertyBucket
{
public:
static HRESULT CreateInstance(CPropertyBucket **ppBucketOut);
~CPropertyBucket(void);
HRESULT SetProperty(LPCWSTR pszName, const VARIANT *pVar, CNotifier& notifier);
HRESULT SetConstProperty(LPCWSTR pszName, const VARIANT *pVAr);
HRESULT GetProperty(LPCWSTR pszName, VARIANT *pVarOut) const;
HRESULT Advise(LPCWSTR pszPropertyName, int iClient);
HRESULT Unadvise(int iClient);
private:
CPropertyBucket(void);
CDpa<CProperty> m_dpaProp; // DPA of (CProperty *)
CProperty *_FindProperty(LPCWSTR pszName) const;
HRESULT _InsertProperty(CProperty *pProp);
//
// Prevent copy.
//
CPropertyBucket(const CPropertyBucket& rhs);
CPropertyBucket& operator = (const CPropertyBucket& rhs);
};
//
// Create a bucket.
//
HRESULT
CPropertyBucket::CreateInstance( // [static]
CPropertyBucket **ppBucketOut
)
{
HRESULT hr = E_OUTOFMEMORY;
CPropertyBucket *pBucket = new CPropertyBucket();
if (NULL != pBucket)
{
if (pBucket->m_dpaProp.IsValid())
{
ASSERT(IsValidWritePtr(ppBucketOut));
*ppBucketOut = pBucket;
hr = S_OK;
}
else
{
delete pBucket;
}
}
return hr;
}
CPropertyBucket::CPropertyBucket(
void
) : m_dpaProp(4)
{
}
CPropertyBucket::~CPropertyBucket(
void
)
{
//
// Note that the m_dpaProp member is self-destructive.
//
}
//
// Set a named property in the bucket to a new value.
// If the property doesn't exist it is created.
//
// Returns:
// S_OK - Property value set.
// S_FALSE - Property value unchanged (dup value).
// E_OUTOFMEMORY - Insuffient memory to add property.
//
HRESULT
CPropertyBucket::SetProperty(
LPCWSTR pszName,
const VARIANT *pVar,
CNotifier& notifier
)
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVar);
HRESULT hr;
CProperty *pProp = _FindProperty(pszName);
if (NULL != pProp)
{
//
// Found the property in the bucket. Set it's value.
//
hr = pProp->SetValue(pVar, notifier);
}
else
{
//
// Must be a new property. Create a property object and
// insert it into the bucket.
//
hr = CProperty::CreateInstance(pszName, pVar, false, &pProp);
if (SUCCEEDED(hr))
{
hr = _InsertProperty(pProp);
if (FAILED(hr))
{
delete pProp;
}
}
}
return hr;
}
//
// Create a named constant property in the bucket and intialize
// it with the specified value. Note that this property is now
// a constant and cannot be modified. It will however be deleted
// when it's parent property group is removed. If a property of the
// specified name already exists, this method fails.
//
// Returns:
// S_OK - Constant created.
// TSPB_E_MODIFYCONST - Property of this name already exists.
// E_OUTOFMEMORY - Insuffient memory to add property.
//
HRESULT
CPropertyBucket::SetConstProperty(
LPCWSTR pszName,
const VARIANT *pVar
)
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVar);
HRESULT hr = TSPB_E_MODIFYCONST;
CProperty *pProp = _FindProperty(pszName);
if (NULL == pProp)
{
hr = CProperty::CreateInstance(pszName, pVar, true, &pProp);
if (SUCCEEDED(hr))
{
hr = _InsertProperty(pProp);
if (FAILED(hr))
{
delete pProp;
}
}
}
return hr;
}
//
// Retrieve the value of a named property in the bucket.
// Returns:
// S_OK - Property retrieved.
// TSPB_E_PROPNOTFOUND - Property not found.
//
HRESULT
CPropertyBucket::GetProperty(
LPCWSTR pszName,
VARIANT *pVarOut
) const
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVarOut);
ASSERT(IsValidWritePtr(pVarOut));
HRESULT hr = TSPB_E_PROPNOTFOUND;
CProperty *pProp = _FindProperty(pszName);
if (NULL != pProp)
{
hr = pProp->GetValue(pVarOut);
}
return hr;
}
//
// Register a client for a change notification on a property.
// Returns:
// S_OK - Change notify registered on the property.
// TSPB_E_PROPNOTFOUND - Property not found.
//
HRESULT
CPropertyBucket::Advise(
LPCWSTR pszPropertyName,
int iClient
)
{
ASSERT(NULL != pszPropertyName);
HRESULT hr = TSPB_E_PROPNOTFOUND;
CProperty *pProp = _FindProperty(pszPropertyName);
if (NULL != pProp)
{
hr = pProp->Advise(iClient);
}
return hr;
}
//
// Remove the change notification registration for a particular
// client. This cancels the registration on all properties in the
// bucket.
// Returns:
// Always returns S_OK.
//
HRESULT
CPropertyBucket::Unadvise(
int iClient
)
{
const int cProps = m_dpaProp.Count();
for (int i = 0; i < cProps; i++)
{
CProperty *pProp = m_dpaProp.Get(i);
if (NULL != pProp)
{
pProp->Unadvise(iClient);
}
}
return S_OK;
}
//
// Find a property (by name) in a bucket. This is simply
// a linear search. We don't do any sorting of items in the
// bucket.
//
// REVIEW: Sorting items could yield a slight performance
// improvement if collision chains become long.
//
// Returns NULL if the property is not found.
// If found, the pointer returned is for reference only and
// is not to be deleted by the caller.
//
CProperty *
CPropertyBucket::_FindProperty(
LPCWSTR pszName
) const
{
ASSERT(NULL != pszName);
ASSERT(m_dpaProp.IsValid());
const int cProps = m_dpaProp.Count();
for (int iProp = 0; iProp < cProps; iProp++)
{
const CProperty *pProp = m_dpaProp.Get(iProp);
ASSERT(NULL != pProp);
if (pProp->CompareNamesEqual(pszName))
{
//
// This function is const because it doesn't modify
// the DPA. However, it returns a non-const ptr
// to a CProperty object because the caller may
// need to change the object.
//
return const_cast<CProperty *>(pProp);
}
}
return NULL;
}
//
// Insert a new property object into the bucket.
// This simply appends the pointer onto the DPA.
// It is assumed that the pProp argument is a heap address.
//
HRESULT
CPropertyBucket::_InsertProperty(
CProperty *pProp
)
{
ASSERT(NULL != pProp);
HRESULT hr = E_OUTOFMEMORY;
if (-1 != m_dpaProp.Append(pProp))
{
hr = S_OK;
}
return hr;
}
//-----------------------------------------------------------------------------
// CPropertyGroup
//
// This class represents a group of properties in the property bag.
// The point is to simulate "scopes" of properties so that clients can create
// separate namespaces within the property bag. This should help greatly with
// reducing bugs related to 'unexpected' changes in property values resulting
// in name collisions. Each group is identified in the application
// namespace by a GUID. GUID_NULL always represents the 'global' namespace.
// When a group is first created, we hand back a 'handle' that is used in
// all other accesses. The handle is merely an index into our group table
// providing direct access to a group. Helper functions are provided to
// return a handle from a property group ID (guid) if necessary.
//
//-----------------------------------------------------------------------------
class CPropertyGroup
{
public:
explicit CPropertyGroup(REFGUID id);
~CPropertyGroup(void);
REFGUID GetID(void) const
{ return m_id; }
HRESULT SetProperty(LPCWSTR pszName, const VARIANT *pVar, CNotifier& notifier);
HRESULT SetConstProperty(LPCWSTR pszName, const VARIANT *pVar);
HRESULT GetProperty(LPCWSTR pszName, VARIANT *pVarOut) const;
HRESULT Advise(LPCWSTR pszPropertyName, int iClient);
HRESULT Unadvise(int iClient);
private:
const GUID m_id; // Property group ID.
CNotifyMap m_NotifyMapGroup;
CPropertyBucket *m_rgpBuckets[HASH_TABLE_SIZE]; // Hash table buckets.
DWORD _HashName(LPCWSTR pszName) const;
CPropertyBucket *_GetBucket(LPCWSTR pszName) const;
//
// Prevent copy.
//
CPropertyGroup(const CPropertyGroup& rhs);
CPropertyGroup& operator = (const CPropertyGroup& rhs);
};
CPropertyGroup::CPropertyGroup(
REFGUID id
) : m_id(id)
{
ZeroMemory(m_rgpBuckets, sizeof(m_rgpBuckets));
}
CPropertyGroup::~CPropertyGroup(
void
)
{
for (int iBucket = 0; iBucket < ARRAYSIZE(m_rgpBuckets); iBucket++)
{
delete m_rgpBuckets[iBucket];
}
}
//
// Set the value of a property in the group.
// Will fail if the named property is a constant.
//
// Returns:
// S_OK - Property value changed.
// S_FALSE - Property value unchanged (dup value).
// E_OUTOFMEMORY
// TSPB_E_PROPNOTFOUND - Property name not found.
// TSPB_E_MODIFYCONST - Attempt to modify a const property.
//
HRESULT
CPropertyGroup::SetProperty(
LPCWSTR pszName,
const VARIANT *pVar,
CNotifier& notifier
)
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVar);
HRESULT hr = E_OUTOFMEMORY;
CPropertyBucket *pBucket = _GetBucket(pszName);
if (NULL != pBucket)
{
//
// Merging the group's notify map with the 'active'
// map adds those notifications registered at the
// group level for this group.
//
notifier.MergeWithActiveNotifyMap(m_NotifyMapGroup);
hr = pBucket->SetProperty(pszName, pVar, notifier);
}
return hr;
}
//
// Creates a new constant property and sets its value.
// Since constants are only initialized and cannot be changed,
// this operation does not generate a notification event.
//
// Returns:
// S_OK
// E_OUTOFMEMORY
// TSPB_E_PROPNOTFOUND - Property name not found.
// TSPB_E_MODIFYCONST - Attempt to modify a const property.
//
HRESULT
CPropertyGroup::SetConstProperty(
LPCWSTR pszName,
const VARIANT *pVar
)
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVar);
HRESULT hr = E_OUTOFMEMORY;
CPropertyBucket *pBucket = _GetBucket(pszName);
if (NULL != pBucket)
{
hr = pBucket->SetConstProperty(pszName, pVar);
}
return hr;
}
//
// Retrieve the value of a property in the property group.
//
// Returns:
// S_OK
// E_OUTOFMEMORY
// TSPB_E_PROPNOTFOUND - Property name not found.
//
HRESULT
CPropertyGroup::GetProperty(
LPCWSTR pszName,
VARIANT *pVarOut
) const
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVarOut);
ASSERT(IsValidWritePtr(pVarOut));
HRESULT hr = E_OUTOFMEMORY;
CPropertyBucket *pBucket = _GetBucket(pszName);
if (NULL != pBucket)
{
hr = pBucket->GetProperty(pszName, pVarOut);
}
return hr;
}
//
// Register a client for change notifications on a single property
// or all properties in a group.
//
// Returns:
// S_OK
// E_OUTOFMEMORY
// TSPB_E_PROPNOTFOUND - Property name not found.
// TSPB_E_NOTIFYCOOKIE - iClient is invalid.
//
HRESULT
CPropertyGroup::Advise(
LPCWSTR pszPropertyName, // NULL == All properties in group.
int iClient
)
{
HRESULT hr = E_OUTOFMEMORY;
if (NULL == pszPropertyName || L'\0' == *pszPropertyName)
{
//
// Register for a change on any property in this group.
//
hr = m_NotifyMapGroup.Set(iClient, true);
}
else
{
//
// Register for a change on a specific property.
//
CPropertyBucket *pBucket = _GetBucket(pszPropertyName);
if (NULL != pBucket)
{
hr = pBucket->Advise(pszPropertyName, iClient);
}
}
return hr;
}
//
// Cancel change notifications for a given client on all the property
// group and all properties in the group.
// Always returns S_OK.
//
HRESULT
CPropertyGroup::Unadvise(
int iClient
)
{
m_NotifyMapGroup.Set(iClient, false);
for (int i = 0; i < ARRAYSIZE(m_rgpBuckets); i++)
{
CPropertyBucket *pBucket = m_rgpBuckets[i];
if (NULL != pBucket)
{
pBucket->Unadvise(iClient);
}
}
return S_OK;
}
//
// Retrieve the address of a bucket object containing a named property.
// The bucket number is calculated by a hash on the property name.
// If a bucket does not yet exist for the property, one is created.
// Returns NULL if a bucket doesn't exist and can't be created.
//
CPropertyBucket *
CPropertyGroup::_GetBucket(
LPCWSTR pszName
) const
{
ASSERT(NULL != pszName);
const DWORD iBucket = _HashName(pszName);
if (NULL == m_rgpBuckets[iBucket])
{
//
// Create a new bucket.
//
CPropertyBucket *pBucket;
HRESULT hr = CPropertyBucket::CreateInstance(&pBucket);
if (SUCCEEDED(hr))
{
CPropertyGroup *pNonConstThis = const_cast<CPropertyGroup *>(this);
pNonConstThis->m_rgpBuckets[iBucket] = pBucket;
}
}
return m_rgpBuckets[iBucket];
}
//
// Generate a hash value from a property name.
// The number generated will be between 0 and HASH_TABLE_SIZE.
//
DWORD
CPropertyGroup::_HashName(
LPCWSTR pszText
) const
{
LPCWSTR p = NULL;
DWORD dwCode = 0;
if (IS_INTRESOURCE(pszText))
{
//
// Property name is really an ID. In that case just
// use the (ID % tablesize) as the hash code.
//
dwCode = (DWORD)((ULONG_PTR)(pszText));
}
else
{
DWORD dwTemp = 0;
for (p = pszText; TEXT('\0') != *p; p++)
{
dwCode = (dwCode << 4) + CharValue(*p);
if (0 != (dwTemp = dwCode & 0xF0000000))
{
dwCode = dwCode ^ (dwTemp >> 24);
dwCode = dwCode ^ dwTemp;
}
}
}
dwCode %= HASH_TABLE_SIZE;
ASSERT(dwCode < HASH_TABLE_SIZE);
return dwCode;
}
//-----------------------------------------------------------------------------
// CPropertyBag
//-----------------------------------------------------------------------------
class CPropertyBag : public IPropertyBag,
public ITaskSheetPropertyBag
{
public:
~CPropertyBag(void);
//
// IUnknown
//
STDMETHOD(QueryInterface)(REFIID riid, void **ppv);
STDMETHOD_(ULONG, AddRef)();
STDMETHOD_(ULONG, Release)();
//
// IPropertyBag
//
STDMETHOD(Read)(LPCOLESTR pszPropName, VARIANT *pVar, IErrorLog *pErrorLog);
STDMETHOD(Write)(LPCOLESTR pszPropName, VARIANT *pVar);
//
// ITaskSheetPropertyBag
//
STDMETHOD(CreatePropertyGroup)(REFGUID idGroup, HPROPGROUP *phGroupOut);
STDMETHOD(RemovePropertyGroup)(HPROPGROUP hGroup);
STDMETHOD(Set)(HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVar);
STDMETHOD(SetConst)(HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVar);
STDMETHOD(Get)(HPROPGROUP hGroup, LPCWSTR pszName, VARIANT *pVarOut);
STDMETHOD(PropertyGroupIdToHandle)(REFGUID id, HPROPGROUP *phGroupOut);
STDMETHOD(PropertyGroupHandleToId)(HPROPGROUP hGroup, GUID *pidCatOut);
STDMETHOD(RegisterNotify)(ITaskSheetPropertyNotifySink *pSink, DWORD *pdwCookie);
STDMETHOD(UnregisterNotify)(DWORD dwCookie);
STDMETHOD(Advise)(DWORD dwCookie, HPROPGROUP hGroup, LPCWSTR pszPropName);
private:
CPropertyBag(void);
LONG m_cRef;
CNotifyMap m_NotifyMapGlobal; // Global notify map.
CNotifier m_Notifier; // Handles notifying clients on chg.
CDpa<CPropertyGroup> m_dpaGroups; // DPA of (CPropertyGroup *)
//
// Prevent copy.
//
CPropertyBag(const CPropertyBag& rhs);
CPropertyBag& operator = (const CPropertyBag& rhs);
HRESULT _FindPropertyGroupByID(REFGUID idGroup, HPROPGROUP *phGroup) const;
HRESULT _GetPropertyGroup(HPROPGROUP hGroup, CPropertyGroup **ppGroup);
bool _IsValidPropertyGroupHandle(HPROPGROUP hGroup) const;
//
// Friendship with instance generator is typical.
//
friend HRESULT TaskUiPropertyBag_CreateInstance(REFIID riid, void **ppv);
};
//
// Create a property bag.
//
HRESULT
TaskUiPropertyBag_CreateInstance(
REFIID riid,
void **ppv
)
{
HRESULT hr = E_OUTOFMEMORY;
CPropertyBag *pBag = new CPropertyBag();
if (NULL != pBag)
{
if (pBag->m_dpaGroups.IsValid())
{
//
// Create the 'global' property group.
//
HPROPGROUP hGroup;
hr = pBag->CreatePropertyGroup(PGID_GLOBAL, &hGroup);
if (SUCCEEDED(hr))
{
ASSERT(PROPGROUP_GLOBAL == hGroup);
hr = pBag->QueryInterface(riid, ppv);
pBag->Release();
}
}
if (FAILED(hr))
{
delete pBag;
}
}
return hr;
}
CPropertyBag::CPropertyBag(
void
) : m_cRef(1),
m_dpaGroups(4),
m_Notifier(this)
{
}
CPropertyBag::~CPropertyBag(
void
)
{
//
// Note that the m_dpaGroups member is self-destructive.
//
}
STDMETHODIMP
CPropertyBag::QueryInterface(
REFIID riid,
void **ppv
)
{
static const QITAB qit[] = {
QITABENT(CPropertyBag, IPropertyBag),
QITABENT(CPropertyBag, ITaskSheetPropertyBag),
{ 0 },
};
return QISearch(this, qit, riid, ppv);
}
STDMETHODIMP_ (ULONG)
CPropertyBag::AddRef(
void
)
{
return InterlockedIncrement(&m_cRef);
}
STDMETHODIMP_ (ULONG)
CPropertyBag::Release(
void
)
{
if (InterlockedDecrement(&m_cRef))
return m_cRef;
delete this;
return 0;
}
//
// Returns:
// S_OK - Group created.
// E_OUTOFMEMORY.
// TSPB_E_GROUPEXISTS
//
STDMETHODIMP
CPropertyBag::CreatePropertyGroup(
REFGUID idGroup,
HPROPGROUP *phGroupOut
)
{
ASSERT(NULL != phGroupOut);
ASSERT(IsValidWritePtr(phGroupOut));
if (NULL == phGroupOut || !IsValidWritePtr(phGroupOut))
{
//
// Parameter validation for public API.
//
return E_INVALIDARG;
}
HRESULT hr = _FindPropertyGroupByID(idGroup, phGroupOut);
if (S_OK == hr)
{
hr = TSPB_E_GROUPEXISTS;
}
else if (S_FALSE == hr)
{
hr = E_OUTOFMEMORY;
CPropertyGroup *pGroup = new CPropertyGroup(idGroup);
if (NULL != pGroup)
{
//
// First try to find an empty slot in the DPA.
//
int cSlots = m_dpaGroups.Count();
for (int i = 0; i < cSlots; i++)
{
if (NULL == m_dpaGroups.Get(i))
{
m_dpaGroups.Set(i, pGroup);
*phGroupOut = i;
pGroup = NULL;
break;
}
}
if (NULL != pGroup)
{
//
// DPA is full, extend it.
//
const int iGroup = m_dpaGroups.Append(pGroup);
if (-1 != iGroup)
{
*phGroupOut = iGroup;
hr = S_OK;
}
else
{
delete pGroup;
}
}
}
}
return hr;
}
//
// Removes a property group from the bag. This includes removing
// all of the properties associated with the group.
// Note that the 'global' group cannot be
// removed. It's scope is the lifetime of the property bag and
// is destroyed when the bag is destroyed.
//
STDMETHODIMP
CPropertyBag::RemovePropertyGroup(
HPROPGROUP hGroup
)
{
CPropertyGroup *pGroup;
HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
if (SUCCEEDED(hr) && PROPGROUP_GLOBAL != hGroup)
{
delete pGroup;
m_dpaGroups.Set(hGroup, NULL);
}
return hr;
}
//
// Converts a property group ID (guid) to a group handle.
// The handle returned is the same value that was returned
// in CreatePropertyGroup.
//
STDMETHODIMP
CPropertyBag::PropertyGroupIdToHandle(
REFGUID idGroup,
HPROPGROUP *phGroupOut
)
{
ASSERT(NULL != phGroupOut);
ASSERT(IsValidWritePtr(phGroupOut));
if (NULL == phGroupOut || !IsValidWritePtr(phGroupOut))
{
//
// Parameter validation for public API.
//
return E_INVALIDARG;
}
HRESULT hr = TSPB_E_GROUPNOTFOUND;
const int cGroups = m_dpaGroups.Count();
for (int i = 0; i < cGroups; i++)
{
CPropertyGroup *pGroup = m_dpaGroups.Get(i);
if (NULL != pGroup && IsEqualGUID(pGroup->GetID(), idGroup))
{
*phGroupOut = i;
hr = S_OK;
}
}
return hr;
}
//
// Converts a property group handle to a group ID (guid).
// Returns TSPB_E_GROUPNOTFOUND if the group handle is invalid.
// Cannot retrieve a handle to the pseudo-group handles
// PROPGROUP_ANY or PROPGROUP_INVALID.
//
STDMETHODIMP
CPropertyBag::PropertyGroupHandleToId(
HPROPGROUP hGroup,
GUID *pidGroupOut
)
{
ASSERT(NULL != pidGroupOut);
ASSERT(IsValidWritePtr(pidGroupOut));
if (NULL == pidGroupOut || !IsValidWritePtr(pidGroupOut))
{
//
// Parameter validation for public API.
//
return E_INVALIDARG;
}
CPropertyGroup *pGroup;
HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
if (SUCCEEDED(hr))
{
ASSERT(NULL != pGroup);
*pidGroupOut = pGroup->GetID();
}
return hr;
}
//
// Set a property value in the bag.
//
// Returns:
// S_OK - Property value changed.
// S_FALSE - Property value unchanged (dup value).
// E_INVALIDARG
// E_OUTOFMEMORY
// TSPB_E_GROUPNOTFOUND - Invalid group handle.
// TSPB_E_MODIFYCONST - Attempt to modify a const property.
//
STDMETHODIMP
CPropertyBag::Set(
HPROPGROUP hGroup,
LPCWSTR pszName,
VARIANT *pVar
)
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVar);
if (NULL == pszName || NULL == pVar)
{
//
// Parameter validation for public API.
//
return E_INVALIDARG;
}
CPropertyGroup *pGroup;
HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
if (SUCCEEDED(hr))
{
ASSERT(NULL != pGroup);
//
// Initialize the active notify map to the content of the
// global notify map.
//
m_Notifier.Reset();
m_Notifier.MergeWithActiveNotifyMap(m_NotifyMapGlobal);
m_Notifier.SetPropertyGroup(hGroup);
hr = pGroup->SetProperty(pszName, pVar, m_Notifier);
}
return hr;
}
//
// Create a constant property value in the bag.
//
//
// Returns:
// S_OK
// E_INVALIDARG
// E_OUTOFMEMORY
// TSPB_E_GROUPNOTFOUND - Invalid group handle.
// TSPB_E_MODIFYCONST - Attempt to modify a const property.
//
STDMETHODIMP
CPropertyBag::SetConst(
HPROPGROUP hGroup,
LPCWSTR pszName,
VARIANT *pVar
)
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVar);
if (NULL == pszName || NULL == pVar)
{
//
// Parameter validation for public API.
//
return E_INVALIDARG;
}
CPropertyGroup *pGroup;
HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
if (SUCCEEDED(hr))
{
ASSERT(NULL != pGroup);
hr = pGroup->SetConstProperty(pszName, pVar);
}
return hr;
}
//
// Retrieve property value from the bag.
//
// Returns:
// S_OK
// E_INVALIDARG
// E_OUTOFMEMORY
// TSPB_E_GROUPNOTFOUND - Invalid group handle.
// TSPB_E_MODIFYCONST - Attempt to modify a const property.
//
STDMETHODIMP
CPropertyBag::Get(
HPROPGROUP hGroup,
LPCWSTR pszName,
VARIANT *pVarOut
)
{
ASSERT(NULL != pszName);
ASSERT(NULL != pVarOut);
ASSERT(IsValidWritePtr(pVarOut));
if (NULL == pszName || NULL == pVarOut || !IsValidWritePtr(pVarOut))
{
//
// Parameter validation for public API.
//
return E_INVALIDARG;
}
CPropertyGroup *pGroup;
HRESULT hr = _GetPropertyGroup(hGroup, &pGroup);
if (SUCCEEDED(hr))
{
ASSERT(NULL != pGroup);
hr = pGroup->GetProperty(pszName, pVarOut);
}
return hr;
}
//
// Register an interface pointer for property change notifications.
//
STDMETHODIMP
CPropertyBag::RegisterNotify(
ITaskSheetPropertyNotifySink *pSink,
DWORD *pdwCookie
)
{
ASSERT(NULL != pSink);
ASSERT(NULL != pdwCookie);
if (NULL == pSink || NULL == pdwCookie || !IsValidWritePtr(pdwCookie))
{
//
// Parameter validation for public API.
//
return E_INVALIDARG;
}
return m_Notifier.Register(pSink, pdwCookie);
}
//
// Cancel all notifications associated with a given connection cookie.
//
// Returns:
// S_OK
// TSPB_E_GROUPNOTFOUND - Invalid group handle.
//
STDMETHODIMP
CPropertyBag::UnregisterNotify(
DWORD dwCookie
)
{
//
// Unegister the sink with the notifier.
//
HRESULT hr = m_Notifier.Unregister(dwCookie);
if (SUCCEEDED(hr))
{
//
// Unregister from the global notify map.
//
m_NotifyMapGlobal.Set(dwCookie, false);
//
// Unregister each property group.
//
const cGroups = m_dpaGroups.Count();
for (int i = 0; i < cGroups; i++)
{
CPropertyGroup *pGroup = m_dpaGroups.Get(i);
if (NULL != pGroup)
{
pGroup->Unadvise(dwCookie);
}
}
}
return hr;
}
//
// Request that a notification interface be notified when certain
// properties change. The interface is identified by the 'cookie' that
// was returned in RegisterNotify.
// The property(s) of interested are identified through the hGroup
// and pszPropertyName arguments as follows:
//
// hGroup pszPropName(*) Notify triggered on change
// ------------ ---------------- ------------------------------------
// ANYPROPGROUP <ignored> Any property in the bag.
// PROPGROUP_GLOBAL NULL Any property in the global group
// PROPGROUP_GLOBAL Non-NULL Named property in the global group
// Other NULL Any property in the group
// Other Non-NULL Named property in the group
//
// (*) For the prop name, NULL and L"" are equivalent.
//
// Returns:
// S_OK
// E_INVALIDARG
// E_OUTOFMEMORY
// TSPB_E_GROUPNOTFOUND - Invalid group handle.
//
STDMETHODIMP
CPropertyBag::Advise(
DWORD dwCookie,
HPROPGROUP hGroup,
LPCWSTR pszPropertyName
)
{
HRESULT hr;
if (PROPGROUP_ANY == hGroup)
{
//
// Register for changes in any property.
//
hr = m_NotifyMapGlobal.Set(dwCookie, true);
}
else
{
CPropertyGroup *pGroup;
hr = _GetPropertyGroup(hGroup, &pGroup);
if (SUCCEEDED(hr))
{
ASSERT(NULL != pGroup);
//
// Register for changes in either a group or a specific
// property.
//
hr = pGroup->Advise(pszPropertyName, dwCookie);
}
}
return hr;
}
//
// IPropertyBag implementation.
//
// Read a property from the 'global' namespace.
//
// BUGBUG: Do we need to do anything with pErrorLog?
//
STDMETHODIMP
CPropertyBag::Read(
LPCOLESTR pszPropName,
VARIANT *pVar,
IErrorLog * /* unused */
)
{
ASSERT(NULL != pszPropName);
ASSERT(NULL != pVar);
ASSERT(IsValidWritePtr(pVar));
if (NULL == pszPropName || NULL == pVar || !IsValidWritePtr(pVar))
{
//
// Parameter validation for public API.
//
return E_INVALIDARG;
}
return Get(PROPGROUP_GLOBAL, pszPropName, pVar);
}
//
// Write a property to the 'global' namespace.
//
STDMETHODIMP
CPropertyBag::Write(
LPCOLESTR pszPropName,
VARIANT *pVar
)
{
ASSERT(NULL != pszPropName);
ASSERT(NULL != pVar);
if (NULL == pszPropName || NULL == pVar)
{
//
// Parameter validation for public API.
//
return E_INVALIDARG;
}
return Set(PROPGROUP_GLOBAL, pszPropName, pVar);
}
//
// Locate a property group by it's ID (guid), returning it's handle.
// Returns:
// S_OK - Found group.
// S_FALSE - Didn't find group.
//
HRESULT
CPropertyBag::_FindPropertyGroupByID(
REFGUID idGroup,
HPROPGROUP *phGroup
) const
{
ASSERT(NULL != phGroup);
ASSERT(IsValidWritePtr(phGroup));
const int cCategories = m_dpaGroups.Count();
for (int i = 0; i < cCategories; i++)
{
const CPropertyGroup *pGroup = m_dpaGroups.Get(i);
if (NULL != pGroup && IsEqualGUID(pGroup->GetID(), idGroup))
{
*phGroup = i;
return S_OK;
}
}
return S_FALSE;
}
//
// Determine if a group handle is valid.
// Note that the pseudo handle PROPGROUP_GLOBAL is valid.
//
bool
CPropertyBag::_IsValidPropertyGroupHandle(
HPROPGROUP hGroup
) const
{
return (PROPGROUP_ANY != hGroup &&
PROPGROUP_INVALID != hGroup &&
hGroup < m_dpaGroups.Count());
}
//
// Retrieve the address of the CPropertyGroup object associated
// with a particular group handle. The function performs handle
// validation.
//
HRESULT
CPropertyBag::_GetPropertyGroup(
HPROPGROUP hGroup,
CPropertyGroup **ppGroup
)
{
ASSERT(NULL != ppGroup);
ASSERT(IsValidWritePtr(ppGroup));
HRESULT hr = TSPB_E_GROUPNOTFOUND;
if (_IsValidPropertyGroupHandle(hGroup))
{
*ppGroup = m_dpaGroups.Get(hGroup);
hr = S_OK;
}
return hr;
}