2233 lines
56 KiB
C++
2233 lines
56 KiB
C++
|
//+-------------------------------------------------------------------------
|
||
|
//
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
|