/////////////////////////////////////////////////////////////////////////////
//
//  Copyright (c ) 1996-2000 Microsoft Corporation
//
//  Module Name:
//      ClusItem.cpp
//
//  Description:
//      Implementation of the CClusterItem class.
//
//  Maintained By:
//      David Potter (davidp )   May 6, 1996
//
//  Revision History:
//
//  Modified to fix bugs associated with open/close state of m_hkey.
//  m_hkey will be closed upon destruction of CClusterItem.
//  Roderick Sharper March 23, 1997.
//
//  Notes:
//
/////////////////////////////////////////////////////////////////////////////

#include "stdafx.h"
#include "CluAdmin.h"
#include "ConstDef.h"
#include "ClusItem.h"
#include "ClusDoc.h"
#include "ExcOper.h"
#include "TraceTag.h"
#include "TreeItem.inl"
#include "PropList.h"

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

/////////////////////////////////////////////////////////////////////////////
// Global Variables
/////////////////////////////////////////////////////////////////////////////

#ifdef _DEBUG
CTraceTag g_tagClusItemCreate( _T("Create"), _T("CLUSTER ITEM CREATE"), 0 );
CTraceTag g_tagClusItemDelete( _T("Delete"), _T("CLUSTER ITEM DELETE"), 0 );
CTraceTag g_tagClusItemNotify( _T("Notify"), _T("CLUSTER ITEM NOTIFY"), 0 );
#endif

/////////////////////////////////////////////////////////////////////////////
// CClusterItemList
/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItemList::PciFromName
//
//  Routine Description:
//      Find a cluster item in the list by its name.
//
//  Arguments:
//      pszName     [IN] Name of item to look for.
//      ppos        [OUT] Position of the item in the list.
//
//  Return Value:
//      pci         Cluster item corresponding the the specified name.
//
//--
/////////////////////////////////////////////////////////////////////////////
CClusterItem * CClusterItemList::PciFromName(
    IN LPCTSTR      pszName,
    OUT POSITION *  ppos    // = NULL
   )
{
    POSITION        posPci;
    POSITION        posCurPci;
    CClusterItem *  pci = NULL;

    ASSERT( pszName != NULL );

    posPci = GetHeadPosition( );
    while ( posPci != NULL )
    {
        posCurPci = posPci;
        pci = GetNext( posPci );
        ASSERT_VALID( pci );

        if ( pci->StrName( ).CompareNoCase( pszName ) == 0 )
        {
            if ( ppos != NULL )
            {
                *ppos = posCurPci;
            } // if
            break;
        }  // if:  found a match

        pci = NULL;
    }  // while:  more resources in the list

    return pci;

}  //*** CClusterItemList::PciFromName( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItemList::RemoveAll
//
//  Routine Description:
//      Remove all items from the list, decrementing the reference count
//      on each one first.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//  Note:
//      This routine is not virtual, so calls to the base class will
//      not go through this routine.  Also, it does not call the base
//      class method.
//
//--
/////////////////////////////////////////////////////////////////////////////
#ifdef NEVER
void CClusterItemList::RemoveAll( void )
{
    ASSERT_VALID( this );

    // destroy elements
    CNode * pNode;
    for ( pNode = m_pNodeHead ; pNode != NULL ; pNode = pNode->pNext )
    {
//      ((CClusterItem *) pNode->data)->Release( );
        DestructElements( (CClusterItem**) &pNode->data, 1 );
    }  // for:  each node in the list

    // Call the base class method.
    CObList::RemoveAll( );

}  //*** CClusterItemList::RemoveAll( )
#endif


//***************************************************************************


/////////////////////////////////////////////////////////////////////////////
// CClusterItem
/////////////////////////////////////////////////////////////////////////////

IMPLEMENT_DYNCREATE( CClusterItem, CBaseCmdTarget )

/////////////////////////////////////////////////////////////////////////////
// Message Maps

BEGIN_MESSAGE_MAP( CClusterItem, CBaseCmdTarget )
    //{{AFX_MSG_MAP(CClusterItem)
    ON_UPDATE_COMMAND_UI(ID_FILE_RENAME, OnUpdateRename)
    ON_UPDATE_COMMAND_UI(ID_FILE_PROPERTIES, OnUpdateProperties)
    ON_COMMAND(ID_FILE_PROPERTIES, OnCmdProperties)
    //}}AFX_MSG_MAP
END_MESSAGE_MAP( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::CClusterItem
//
//  Routine Description:
//      Default constructor.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
CClusterItem::CClusterItem( void )
{
    CommonConstruct( );

}  //*** CClusterItem::CClusterItem( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::CClusterItem
//
//  Routine Description:
//      Constructor.
//
//  Arguments:
//      pstrName        [IN] Name of the item.
//      idsType         [IN] Type ID of the item.
//      pstrDescription [IN] Description of the item.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
CClusterItem::CClusterItem(
    IN const CString *  pstrName,
    IN IDS              idsType,
    IN const CString *  pstrDescription
    )
{
    CommonConstruct( );

    if ( pstrName != NULL )
    {
        m_strName = *pstrName;
    } // if

    if ( idsType == 0 )
    {
        idsType = IDS_ITEMTYPE_CONTAINER;
    } // if
    m_idsType = idsType;
    m_strType.LoadString( IdsType( ) );

    if ( pstrDescription != NULL )
    {
        m_strDescription = *pstrDescription;
    } // if

    Trace( g_tagClusItemCreate, _T("CClusterItem( ) - Creating '%s' (%s )"), m_strName, m_strType );

}  //*** CClusterItem::CClusterItem( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::CommonConstruct
//
//  Routine Description:
//      Common construction.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::CommonConstruct( void )
{
    m_hkey = NULL;
    m_idsType = IDS_ITEMTYPE_CONTAINER;
    m_strType.LoadString( IDS_ITEMTYPE_CONTAINER );
    m_iimgObjectType = 0;
    m_iimgState = GetClusterAdminApp( )->Iimg( IMGLI_FOLDER );
    m_pdoc = NULL;
    m_idmPopupMenu = 0;
    m_bDocObj = TRUE;
    m_bChanged = FALSE;
    m_bReadOnly = FALSE;
    m_pcnk = NULL;

}  //*** CClusterItem::CommonConstruct( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::~CClusterItem
//
//  Routine Description:
//      Destructor.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
CClusterItem::~CClusterItem( void )
{
    Trace( g_tagClusItemDelete, _T("~CClusterItem( ) - Deleting cluster item '%s'"), StrName( ) );

    // Empty the lists.
    DeleteAllItemData( LptiBackPointers( ) );
    DeleteAllItemData( LpliBackPointers( ) );
    LptiBackPointers( ).RemoveAll( );
    LpliBackPointers( ).RemoveAll( );

    // Close the registry key.
    if ( Hkey( ) != NULL )
    {
        ClusterRegCloseKey( Hkey( ) );
        m_hkey = NULL;
    } // if

    // Remove the notification key and delete it.
    if ( BDocObj( ) )
    {
        POSITION    pos;

        pos = GetClusterAdminApp( )->Cnkl( ).Find( m_pcnk );
        if ( pos != NULL )
        {
            GetClusterAdminApp( )->Cnkl( ).RemoveAt( pos );
        } // if
        Trace( g_tagClusItemNotify, _T("~CClusterItem( ) - Deleting notification key (%08.8x ) for '%s'"), m_pcnk, StrName( ) );
        delete m_pcnk;
        m_pcnk = NULL;
    }  // if:  object resides in the document

    Trace( g_tagClusItemDelete, _T("~CClusterItem( ) - Done deleting cluster item '%s'"), StrName( ) );

}  //*** CClusterItem::~CClusterItem( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::Delete
//
//  Routine Description:
//      Delete the item.  If the item still has references, add it to the
//      document's pending delete list.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::Delete( void )
{
    // Add a reference so that we don't delete ourselves while
    // still doing cleanup.
    AddRef( );

    // Cleanup this object.
    Cleanup( );

    // Remove the item from all lists and views.
    CClusterItem::RemoveItem( );

    // If there are still references to this object, add it to the delete
    // pending list.  Check for greater than 1 because we added a reference
    // at the beginning of this method.
    if ( ( Pdoc( ) != NULL ) && ( NReferenceCount( ) > 1 ) )
    {
        if ( Pdoc( )->LpciToBeDeleted( ).Find( this ) == NULL )
        {
            Pdoc( )->LpciToBeDeleted( ).AddTail( this );
        } // if
    }  // if:  object still has references to it

    // Release the reference we added at the beginning.  This will
    // cause the object to be deleted if we were the last reference.
    Release( );

}  //*** CClusterItem::Delete( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::Init
//
//  Routine Description:
//      Initialize the item.
//
//  Arguments:
//      pdoc        [IN OUT] Document to which this item belongs.
//      lpszName    [IN] Name of the item.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      Any exceptions thrown by CNotifyKey::new( ) or
//      CNotifyKeyList::AddTail( ).
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::Init( IN OUT CClusterDoc * pdoc, IN LPCTSTR lpszName )
{
    ASSERT_VALID( pdoc );
    ASSERT( lpszName != NULL );

    // Save parameters.
    m_pdoc = pdoc;
    m_strName = lpszName;

    Trace( g_tagClusItemCreate, _T("Init( ) - Initializing '%s' (%s )"), m_strName, m_strType );

    // Find the notification key for this item in the document's list.
    // If one is not found, allocate one.
    if ( BDocObj( ) )
    {
        POSITION            pos;
        CClusterNotifyKey * pcnk    = NULL;

        pos = GetClusterAdminApp( )->Cnkl( ).GetHeadPosition( );
        while ( pos != NULL )
        {
            pcnk = GetClusterAdminApp( )->Cnkl( ).GetNext( pos );
            if ( ( pcnk->m_cnkt == cnktClusterItem )
              && ( pcnk->m_pci == this )
               )
                break;
            pcnk = NULL;
        }  // while:  more items in the list

        // If a key was not found, allocate a new one.
        if ( pcnk == NULL )
        {
            pcnk = new CClusterNotifyKey( this, lpszName );
            if ( pcnk == NULL )
            {
                ThrowStaticException( GetLastError( ) );
            } // if: error allocating the notify key
            try
            {
                GetClusterAdminApp( )->Cnkl( ).AddTail( pcnk );
                Trace( g_tagClusItemNotify, _T("Init( ) - Creating notification key (%08.8x ) for '%s'"), pcnk, StrName( ) );
            }  // try
            catch ( ... )
            {
                delete pcnk;
                throw;
            }  // catch:  anything
        }  // if:  key wasn't found

        m_pcnk = pcnk;
    }  // if:  object resides in the document

}  //*** CClusterItem::Init( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::PlstrExtensions
//
//  Routine Description:
//      Return the list of admin extensions.
//
//  Arguments:
//      None.
//
//  Return Value:
//      plstr       List of extensions.
//      NULL        No extension associated with this object.
//
//  Exceptions Thrown:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
const CStringList * CClusterItem::PlstrExtensions( void ) const
{
    return NULL;

}  //*** CClusterItem::PlstrExtensions( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::RemoveItem
//
//  Routine Description:
//      Remove the item from all lists and views.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::RemoveItem( void )
{
    // Remove the item from each tree item.
    {
        POSITION    posPti;
        CTreeItem * pti;

        posPti = LptiBackPointers( ).GetHeadPosition( );
        while ( posPti != NULL )
        {
            pti = LptiBackPointers( ).GetNext( posPti );
            ASSERT_VALID( pti );
            ASSERT_VALID( pti->PtiParent( ) );
            Trace( g_tagClusItemDelete, _T("RemoveItem( ) - Deleting tree item backptr from '%s' in '%s' - %d left"), StrName( ), pti->PtiParent( )->StrName( ), LptiBackPointers( ).GetCount( ) - 1 );
            pti->RemoveItem( );
        }  // while:  more items in the list
    }  // Remove the item from each tree item

    // Remove the item from each list item.
    {
        POSITION    posPli;
        CListItem * pli;

        posPli = LpliBackPointers( ).GetHeadPosition( );
        while ( posPli != NULL )
        {
            pli = LpliBackPointers( ).GetNext( posPli );
            ASSERT_VALID( pli );
            ASSERT_VALID( pli->PtiParent( ) );
            Trace( g_tagClusItemDelete, _T("RemoveItem( ) - Deleting list item backptr from '%s' in '%s' - %d left"), StrName( ), pli->PtiParent( )->StrName( ), LpliBackPointers( ).GetCount( ) - 1 );
            pli->PtiParent( )->RemoveChild( pli->Pci( ) );
        }  // while:  more items in the list
    }  // Remove the item from each tree item

}  //*** CClusterItem::RemoveItem( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::WriteItem
//
//  Routine Description:
//      Write the item parameters to the cluster database.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      Any exceptions thrown by WriteItem( ).
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::WriteItem( void )
{
}  //*** CClusterItem::WriteItem( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::DwParseProperties
//
//  Routine Description:
//      Parse the properties of the resource.  This is in a separate function
//      from BInit so that the optimizer can do a better job.
//
//  Arguments:
//      rcpl            [IN] Cluster property list to parse.
//
//  Return Value:
//      ERROR_SUCCESS   Properties were parsed successfully.
//
//  Exceptions Thrown:
//      Any exceptions from CString::operator=( ).
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CClusterItem::DwParseProperties( IN const CClusPropList & rcpl )
{
    DWORD                           cProps;
    DWORD                           cprop;
    DWORD                           cbProps;
    const CObjectProperty *         pprop;
    CLUSPROP_BUFFER_HELPER          props;
    CLUSPROP_PROPERTY_NAME const *  pName;

    ASSERT( rcpl.PbPropList( ) != NULL );

    props.pb = rcpl.PbPropList( );
    cbProps = rcpl.CbPropList( );

    // Loop through each property.
    for ( cProps = *(props.pdw++ ) ; cProps > 0 ; cProps-- )
    {
        pName = props.pName;
        ASSERT( pName->Syntax.dw == CLUSPROP_SYNTAX_NAME );
        props.pb += sizeof( *pName ) + ALIGN_CLUSPROP( pName->cbLength );

        // Decrement the counter by the size of the name.
        ASSERT( cbProps > sizeof( *pName ) + ALIGN_CLUSPROP( pName->cbLength ) );
        cbProps -= sizeof( *pName ) + ALIGN_CLUSPROP( pName->cbLength );

        ASSERT( cbProps > sizeof( *props.pValue ) + ALIGN_CLUSPROP( props.pValue->cbLength ) );

        // Parse known properties.
        for ( pprop = Pprops( ), cprop = Cprops( ) ; cprop > 0 ; pprop++, cprop-- )
        {
            if ( lstrcmpiW( pName->sz, pprop->m_pwszName ) == 0 )
            {
                ASSERT( props.pSyntax->wFormat == pprop->m_propFormat );
                switch ( pprop->m_propFormat )
                {
                    case CLUSPROP_FORMAT_SZ:
                        ASSERT( ( props.pValue->cbLength == ( lstrlenW( props.pStringValue->sz ) + 1 ) * sizeof( WCHAR ) )
                             || ( (props.pValue->cbLength == 0 ) && ( props.pStringValue->sz[ 0 ] == L'\0' ) ) );
                        *pprop->m_valuePrev.pstr = props.pStringValue->sz;
                        break;
                    case CLUSPROP_FORMAT_DWORD:
                    case CLUSPROP_FORMAT_LONG:
                        ASSERT( props.pValue->cbLength == sizeof( DWORD ) );
                        *pprop->m_valuePrev.pdw = props.pDwordValue->dw;
                        break;
                    case CLUSPROP_FORMAT_BINARY:
                    case CLUSPROP_FORMAT_MULTI_SZ:
                        *pprop->m_valuePrev.ppb = props.pBinaryValue->rgb;
                        *pprop->m_valuePrev.pcb = props.pBinaryValue->cbLength;
                        break;
                    default:
                        ASSERT( 0 );  // don't know how to deal with this type
                }  // switch:  property format

                // Exit the loop since we found the parameter.
                break;
            }  // if:  found a match
        }  // for:  each property

        // If the property wasn't known, ask the derived class to parse it.
        if ( cprop == 0 )
        {
            DWORD       dwStatus;

            dwStatus = DwParseUnknownProperty( pName->sz, props, cbProps );
            if ( dwStatus != ERROR_SUCCESS )
            {
                return dwStatus;
            } // if
        }  // if:  property not parsed

        // Advance the buffer pointer past the value in the value list.
        while ( ( props.pSyntax->dw != CLUSPROP_SYNTAX_ENDMARK )
             && ( cbProps > 0 ) )
        {
            ASSERT( cbProps > sizeof( *props.pValue ) + ALIGN_CLUSPROP( props.pValue->cbLength ) );
            cbProps -= sizeof( *props.pValue ) + ALIGN_CLUSPROP( props.pValue->cbLength );
            props.pb += sizeof( *props.pValue ) + ALIGN_CLUSPROP( props.pValue->cbLength );
        }  // while:  more values in the list

        // Advance the buffer pointer past the value list endmark.
        ASSERT( cbProps >= sizeof( *props.pSyntax ) );
        cbProps -= sizeof( *props.pSyntax );
        props.pb += sizeof( *props.pSyntax ); // endmark
    }  // for:  each property

    return ERROR_SUCCESS;

}  //*** CClusterItem::DwParseProperties( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::SetCommonProperties
//
//  Routine Description:
//      Set the common properties for this object in the cluster database.
//
//  Arguments:
//      bValidateOnly   [IN] Only validate the data.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      Any exceptions thrown by WriteItem( ).
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::SetCommonProperties( IN BOOL bValidateOnly )
{
    DWORD           dwStatus    = ERROR_SUCCESS;
    CClusPropList   cpl;
    CWaitCursor     wc;

    // Save data.
    {
        // Build the property list and set the data.
        try
        {
            BuildPropList( cpl );
            dwStatus = DwSetCommonProperties( cpl, bValidateOnly );
        }  // try
        catch ( CMemoryException * pme )
        {
            pme->Delete( );
            dwStatus = ERROR_NOT_ENOUGH_MEMORY;
        }  // catch:  CMemoryException

        // Handle errors.
        if ( dwStatus != ERROR_SUCCESS )
        {
            if ( dwStatus != ERROR_RESOURCE_PROPERTIES_STORED )
            {
                ThrowStaticException( dwStatus, IDS_APPLY_PARAM_CHANGES_ERROR );
            } // if
        }  // if:  error setting properties

        if ( ! bValidateOnly && ( dwStatus == ERROR_SUCCESS ) )
        {
            DWORD                   cprop;
            const CObjectProperty * pprop;

            // Save new values as previous values.

            for ( pprop = Pprops( ), cprop = Cprops( ) ; cprop > 0 ; pprop++, cprop-- )
            {
                switch ( pprop->m_propFormat )
                {
                    case CLUSPROP_FORMAT_SZ:
                        ASSERT( pprop->m_value.pstr != NULL );
                        ASSERT( pprop->m_valuePrev.pstr != NULL );
                        *pprop->m_valuePrev.pstr = *pprop->m_value.pstr;
                        break;
                    case CLUSPROP_FORMAT_DWORD:
                        ASSERT( pprop->m_value.pdw != NULL );
                        ASSERT( pprop->m_valuePrev.pdw != NULL );
                        *pprop->m_valuePrev.pdw = *pprop->m_value.pdw;
                        break;
                    case CLUSPROP_FORMAT_BINARY:
                    case CLUSPROP_FORMAT_MULTI_SZ:
                        ASSERT( pprop->m_value.ppb != NULL );
                        ASSERT( *pprop->m_value.ppb != NULL );
                        ASSERT( pprop->m_value.pcb != NULL );
                        ASSERT( pprop->m_valuePrev.ppb != NULL );
                        ASSERT( *pprop->m_valuePrev.ppb != NULL );
                        ASSERT( pprop->m_valuePrev.pcb != NULL );
                        delete [] *pprop->m_valuePrev.ppb;
                        *pprop->m_valuePrev.ppb = new BYTE[ *pprop->m_value.pcb ];
                        if ( *pprop->m_valuePrev.ppb == NULL )
                        {
                            ThrowStaticException( GetLastError( ) );
                        } // if: error allocating data buffer
                        CopyMemory( *pprop->m_valuePrev.ppb, *pprop->m_value.ppb, *pprop->m_value.pcb );
                        *pprop->m_valuePrev.pcb = *pprop->m_value.pcb;
                        break;
                    default:
                        ASSERT( 0 );  // don't know how to deal with this type
                }  // switch:  property format
            }  // for:  each property
        }  // if:  not just validating and properties set successfully

        if ( dwStatus == ERROR_RESOURCE_PROPERTIES_STORED )
        {
            ThrowStaticException( dwStatus );
        } // if
    }  // Save data

}  //*** CClusterItem::SetCommonProperties( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::BuildPropList
//
//  Routine Description:
//      Build the property list.
//
//  Arguments:
//      rcpl        [IN OUT] Cluster property list.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      Any exceptions thrown by CClusPropList::ScAddProp( ).
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::BuildPropList(
    IN OUT CClusPropList & rcpl
    )
{
    DWORD                   cprop;
    const CObjectProperty * pprop;

    for ( pprop = Pprops( ), cprop = Cprops( ) ; cprop > 0 ; pprop++, cprop-- )
    {
        switch ( pprop->m_propFormat )
        {
            case CLUSPROP_FORMAT_SZ:
                rcpl.ScAddProp(
                        pprop->m_pwszName,
                        *pprop->m_value.pstr,
                        *pprop->m_valuePrev.pstr
                        );
                break;
            case CLUSPROP_FORMAT_DWORD:
                rcpl.ScAddProp(
                        pprop->m_pwszName,
                        *pprop->m_value.pdw,
                        *pprop->m_valuePrev.pdw
                        );
                break;
            case CLUSPROP_FORMAT_BINARY:
            case CLUSPROP_FORMAT_MULTI_SZ:
                rcpl.ScAddProp(
                        pprop->m_pwszName,
                        *pprop->m_value.ppb,
                        *pprop->m_value.pcb,
                        *pprop->m_valuePrev.ppb,
                        *pprop->m_valuePrev.pcb
                        );
                break;
            default:
                ASSERT( 0 );  // don't know how to deal with this type
                return;
        }  // switch:  property format
    }  // for:  each property

}  //*** CClusterItem::BuildPropList( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::UpdateState
//
//  Routine Description:
//      Update the current state of the item.
//      Default implementation.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::UpdateState( void )
{
    // Update the state of all the tree items pointing to us.
    {
        POSITION    pos;
        CTreeItem * pti;

        pos = LptiBackPointers( ).GetHeadPosition( );
        while ( pos != NULL )
        {
            pti = LptiBackPointers( ).GetNext( pos );
            ASSERT_VALID( pti );
            pti->UpdateUIState( );
        }  // while:  more items in the list
    }  // Update the state of all the tree items pointing to us

    // Update the state of all the list items pointing to us.
    {
        POSITION    pos;
        CListItem * pli;

        pos = LpliBackPointers( ).GetHeadPosition( );
        while ( pos != NULL )
        {
            pli = LpliBackPointers( ).GetNext( pos );
            ASSERT_VALID( pli );
            pli->UpdateUIState( );
        }  // while:  more items in the list
    }  // Update the state of all the tree items pointing to us

}  //*** CClusterItem::UpdateState( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::DwReadValue
//
//  Routine Description:
//      Read a REG_SZ value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to read.
//      pszKeyName      [IN] Name of the key where the value resides.
//      rstrValue       [OUT] String in which to return the value.
//
//  Return Value:
//      dwStatus    ERROR_SUCCESS = success, !0 = failure
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CClusterItem::DwReadValue(
    IN LPCTSTR      pszValueName,
    IN LPCTSTR      pszKeyName,
    OUT CString &   rstrValue
    )
{
    DWORD       dwStatus;
    LPWSTR      pwszValue   = NULL;
    DWORD       dwValueLen;
    DWORD       dwValueType;
    HKEY        hkey        = NULL;
    CWaitCursor wc;

    ASSERT( pszValueName != NULL );
    ASSERT( Hkey( ) != NULL );

    rstrValue.Empty( );

    try
    {
        // Open a new key if needed.
        if ( pszKeyName != NULL )
        {
            dwStatus = ClusterRegOpenKey( Hkey( ), pszKeyName, KEY_READ, &hkey );
            if ( dwStatus != ERROR_SUCCESS )
            {
                return dwStatus;
            } // if
        }  // if:  need to open a subkey
        else
        {
            hkey = Hkey( );
        } // else

        // Get the size of the value.
        dwValueLen = 0;
        dwStatus = ClusterRegQueryValue(
                        hkey,
                        pszValueName,
                        &dwValueType,
                        NULL,
                        &dwValueLen
                        );
        if ( ( dwStatus == ERROR_SUCCESS ) || ( dwStatus == ERROR_MORE_DATA ) )
        {
            ASSERT( dwValueType == REG_SZ );

            // Allocate enough space for the data.
            pwszValue = rstrValue.GetBuffer( dwValueLen / sizeof( WCHAR ) );
            ASSERT( pwszValue != NULL );
            dwValueLen += 1 * sizeof( WCHAR );    // Don't forget the final null-terminator.

            // Read the value.
            dwStatus = ClusterRegQueryValue(
                            hkey,
                            pszValueName,
                            &dwValueType,
                            (LPBYTE ) pwszValue,
                            &dwValueLen
                            );
            if ( dwStatus == ERROR_SUCCESS )
            {
                ASSERT( dwValueType == REG_SZ );
            }  // if:  value read successfully
            rstrValue.ReleaseBuffer( );
        }  // if:  got the size successfully
    }  // try
    catch ( CMemoryException * pme )
    {
        pme->Delete( );
        dwStatus = ERROR_NOT_ENOUGH_MEMORY;
    }  // catch:  CMemoryException

    if ( pszKeyName != NULL )
    {
        ClusterRegCloseKey( hkey );
    } // if

    return dwStatus;

}  //*** CClusterItem::DwReadValue( LPCTSTR, CString& )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::DwReadValue
//
//  Routine Description:
//      Read a REG_MULTI_SZ value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to read.
//      pszKeyName      [IN] Name of the key where the value resides.
//      rlstrValue      [OUT] String list in which to return the values.
//
//  Return Value:
//      dwStatus    ERROR_SUCCESS = success, !0 = failure
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CClusterItem::DwReadValue(
    IN LPCTSTR          pszValueName,
    IN LPCTSTR          pszKeyName,
    OUT CStringList &   rlstrValue
    )
{
    DWORD               dwStatus;
    LPWSTR              pwszValue   = NULL;
    LPWSTR              pwszCurValue;
    DWORD               dwValueLen;
    DWORD               dwValueType;
    HKEY                hkey        = NULL;
    CWaitCursor         wc;

    ASSERT( pszValueName != NULL );
    ASSERT( Hkey( ) != NULL );

    rlstrValue.RemoveAll( );

    try
    {
        // Open a new key if needed.
        if ( pszKeyName != NULL )
        {
            dwStatus = ClusterRegOpenKey( Hkey( ), pszKeyName, KEY_READ, &hkey );
            if ( dwStatus != ERROR_SUCCESS )
            {
                return dwStatus;
            } // if
        }  // if:  need to open a subkey
        else
            hkey = Hkey( );

        // Get the size of the value.
        dwValueLen = 0;
        dwStatus = ClusterRegQueryValue(
                        hkey,
                        pszValueName,
                        &dwValueType,
                        NULL,
                        &dwValueLen
                        );
        if ( ( dwStatus == ERROR_SUCCESS ) || ( dwStatus == ERROR_MORE_DATA ) )
        {
            ASSERT( dwValueType == REG_MULTI_SZ );

            // Allocate enough space for the data.
            dwValueLen += 1 * sizeof( WCHAR );    // Don't forget the final null-terminator.
            pwszValue = new WCHAR[ dwValueLen / sizeof( WCHAR ) ];
            if ( pwszValue == NULL )
            {
                AfxThrowMemoryException();
            } // if: error allocating the value

            // Read the value.
            dwStatus = ClusterRegQueryValue(
                            hkey,
                            pszValueName,
                            &dwValueType,
                            (LPBYTE) pwszValue,
                            &dwValueLen
                            );
            if ( dwStatus == ERROR_SUCCESS )
            {
                ASSERT( dwValueType == REG_MULTI_SZ );

                // Add each string from the value into the string list.
                for ( pwszCurValue = pwszValue
                        ; *pwszCurValue != L'\0'
                        ; pwszCurValue += lstrlenW( pwszCurValue ) + 1
                        )
                {
                    rlstrValue.AddTail( pwszCurValue );
                } // for
            }  // if:  read the value successfully
        }  // if:  got the size successfully
    }  // try
    catch ( CMemoryException * pme )
    {
        pme->Delete( );
        dwStatus = ERROR_NOT_ENOUGH_MEMORY;
    }  // catch:  CMemoryException

    delete [] pwszValue;
    if ( pszKeyName != NULL )
    {
        ClusterRegCloseKey( hkey );
    } // if

    return dwStatus;

}  //*** CClusterItem::DwReadValue( LPCTSTR, CStringList& )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::DwReadValue
//
//  Routine Description:
//      Read a REG_DWORD value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to read.
//      pszKeyName      [IN] Name of the key where the value resides.
//      pdwValue        [OUT] DWORD in which to return the value.
//
//  Return Value:
//      dwStatus    ERROR_SUCCESS = success, !0 = failure
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CClusterItem::DwReadValue(
    IN LPCTSTR      pszValueName,
    IN LPCTSTR      pszKeyName,
    OUT DWORD *     pdwValue
    )
{
    DWORD       dwStatus;
    DWORD       dwValue;
    DWORD       dwValueLen;
    DWORD       dwValueType;
    HKEY        hkey;
    CWaitCursor wc;

    ASSERT( pszValueName != NULL );
    ASSERT( pdwValue != NULL );
    ASSERT( Hkey( ) != NULL );

    // Open a new key if needed.
    if ( pszKeyName != NULL )
    {
        dwStatus = ClusterRegOpenKey( Hkey( ), pszKeyName, KEY_READ, &hkey );
        if ( dwStatus != ERROR_SUCCESS )
        {
            return dwStatus;
        } // if
    }  // if:  need to open a subkey
    else
    {
        hkey = Hkey( );
    } // else

    // Read the value.
    dwValueLen = sizeof( dwValue );
    dwStatus = ClusterRegQueryValue(
                    hkey,
                    pszValueName,
                    &dwValueType,
                    (LPBYTE) &dwValue,
                    &dwValueLen
                    );
    if ( dwStatus == ERROR_SUCCESS )
    {
        ASSERT( dwValueType == REG_DWORD );
        ASSERT( dwValueLen == sizeof( dwValue ) );
        *pdwValue = dwValue;
    }  // if:  value read successfully

    if ( pszKeyName != NULL )
    {
        ClusterRegCloseKey( hkey );
    } // if

    return dwStatus;

}  //*** CClusterItem::DwReadValue( LPCTSTR, DWORD* )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::DwReadValue
//
//  Routine Description:
//      Read a REG_DWORD value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to read.
//      pszKeyName      [IN] Name of the key where the value resides.
//      pdwValue        [OUT] DWORD in which to return the value.
//      dwDefault       [IN] Default value if parameter not set.
//
//  Return Value:
//      dwStatus    ERROR_SUCCESS = success, !0 = failure
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CClusterItem::DwReadValue(
    IN LPCTSTR      pszValueName,
    IN LPCTSTR      pszKeyName,
    OUT DWORD *     pdwValue,
    IN DWORD        dwDefault
    )
{
    DWORD       dwStatus;
    CWaitCursor wc;

    // Read the value.
    dwStatus = DwReadValue( pszValueName, pszKeyName, pdwValue );
    if ( dwStatus == ERROR_FILE_NOT_FOUND )
    {
        *pdwValue = dwDefault;
        dwStatus = ERROR_SUCCESS;
    }  // if:  value not set

    return dwStatus;

}  //*** CClusterItem::DwReadValue( LPCTSTR, DWORD*, DWORD )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::DwReadValue
//
//  Routine Description:
//      Read a REG_BINARY value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to read.
//      pszKeyName      [IN] Name of the key where the value resides.
//      ppbValue        [OUT] Pointer in which to return the data.  Caller
//                          is responsible for deallocating the data.
//
//  Return Value:
//      dwStatus    ERROR_SUCCESS = success, !0 = failure
//
//--
/////////////////////////////////////////////////////////////////////////////
DWORD CClusterItem::DwReadValue(
    IN LPCTSTR      pszValueName,
    IN LPCTSTR      pszKeyName,
    OUT LPBYTE *    ppbValue
    )
{
    DWORD               dwStatus;
    DWORD               dwValueLen;
    DWORD               dwValueType;
    LPBYTE              pbValue     = NULL;
    HKEY                hkey;
    CWaitCursor         wc;

    ASSERT( pszValueName != NULL );
    ASSERT( ppbValue != NULL );
    ASSERT( Hkey( ) != NULL );

    delete [] *ppbValue;
    *ppbValue = NULL;

    // Open a new key if needed.
    if ( pszKeyName != NULL )
    {
        dwStatus = ClusterRegOpenKey( Hkey( ), pszKeyName, KEY_READ, &hkey );
        if ( dwStatus != ERROR_SUCCESS )
        {
            return dwStatus;
        } // if
    }  // if:  need to open a subkey
    else
    {
        hkey = Hkey( );
    } // else

    // Get the length of the value.
    dwValueLen = 0;
    dwStatus = ClusterRegQueryValue(
                    hkey,
                    pszValueName,
                    &dwValueType,
                    NULL,
                    &dwValueLen
                    );
    if ( ( dwStatus != ERROR_SUCCESS )
      && ( dwStatus != ERROR_MORE_DATA ) )
    {
        if ( pszKeyName != NULL )
        {
            ClusterRegCloseKey( hkey );
        } // if
        return dwStatus;
    }  // if:  error getting the length

    ASSERT( dwValueType == REG_BINARY );

    // Allocate a buffer,
    try
    {
        pbValue = new BYTE[ dwValueLen ];
        if ( pbValue == NULL )
        {
            AfxThrowMemoryException();
        } // if: error allocating the buffer
    }  // try
    catch ( CMemoryException * )
    {
        if ( pszKeyName != NULL )
        {
            ClusterRegCloseKey( hkey );
        } // if
        dwStatus = ERROR_NOT_ENOUGH_MEMORY;
        return dwStatus;
    }  // catch:  CMemoryException

    // Read the value.
    dwStatus = ClusterRegQueryValue(
                    hkey,
                    pszValueName,
                    &dwValueType,
                    pbValue,
                    &dwValueLen
                    );
    if ( dwStatus == ERROR_SUCCESS )
    {
        *ppbValue = pbValue;
    } // if
    else
    {
        delete [] pbValue;
    } // else

    if ( pszKeyName != NULL )
    {
        ClusterRegCloseKey( hkey );
    } // if

    return dwStatus;

}  //*** CClusterItem::DwReadValue( LPCTSTR, LPBYTE* )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::WriteValue
//
//  Routine Description:
//      Write a REG_SZ value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to write.
//      pszKeyName      [IN] Name of the key where the value resides.
//      rstrValue       [IN] Value data.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      CNTException( dwStatus )
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::WriteValue(
    IN LPCTSTR          pszValueName,
    IN LPCTSTR          pszKeyName,
    IN const CString &  rstrValue
    )
{
    DWORD       dwStatus;
    HKEY            hkey;
    CWaitCursor wc;

    ASSERT( pszValueName != NULL );
    ASSERT( Hkey( ) != NULL );

    // Open a new key if needed.
    if ( pszKeyName != NULL )
    {
        dwStatus = ClusterRegOpenKey( Hkey( ), pszKeyName, KEY_ALL_ACCESS, &hkey );
        if ( dwStatus != ERROR_SUCCESS )
        {
            ThrowStaticException( dwStatus );
        } // if
    }  // if:  need to open a subkey
    else
    {
        hkey = Hkey( );
    } // else

    // Write the value.
    dwStatus = ClusterRegSetValue(
                    hkey,
                    pszValueName,
                    REG_SZ,
                    (CONST BYTE *) (LPCTSTR) rstrValue,
                    ( rstrValue.GetLength( ) + 1 ) * sizeof( WCHAR )
                    );
    if ( pszKeyName != NULL )
    {
        ClusterRegCloseKey( hkey );
    } // if
    if ( dwStatus != ERROR_SUCCESS )
    {
        ThrowStaticException( dwStatus );
    } // if

}  //*** CClusterItem::WriteValue( LPCTSTR, CString& )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::WriteValue
//
//  Routine Description:
//      Write a REG_MULTI_SZ value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to write.
//      pszKeyName      [IN] Name of the key where the value resides.
//      rlstrValue      [IN] Value data.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      CNTException( dwStatus )
//      Any exceptions thrown by new.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::WriteValue(
    IN LPCTSTR              pszValueName,
    IN LPCTSTR              pszKeyName,
    IN const CStringList &  rlstrValue
    )
{
    DWORD           dwStatus;
    LPWSTR          pwszValue   = NULL;
    LPWSTR          pwszCurValue;
    POSITION        posStr;
    DWORD           cbValueLen;
    HKEY            hkey;
    CWaitCursor wc;

    ASSERT( pszValueName != NULL );
    ASSERT( Hkey( ) != NULL );

    // Get the size of the value.
    posStr = rlstrValue.GetHeadPosition( );
    cbValueLen = 0;
    while ( posStr != NULL )
    {
        cbValueLen += ( rlstrValue.GetNext( posStr ).GetLength( ) + 1 ) * sizeof( TCHAR );
    }  // while:  more items in the list
    cbValueLen += 1 * sizeof( WCHAR );    // Extra NULL at the end.

    // Allocate the value buffer.
    pwszValue = new WCHAR[cbValueLen / sizeof( WCHAR )];
    if ( pwszValue == NULL )
    {
        ThrowStaticException( GetLastError( ) );
    } // if

    // Copy the strings to the values.
    posStr = rlstrValue.GetHeadPosition( );
    for ( pwszCurValue = pwszValue ; posStr != NULL ; pwszCurValue += lstrlenW( pwszCurValue ) + 1 )
    {
        lstrcpyW( pwszCurValue, rlstrValue.GetNext( posStr ) );
    }  // for:  each item in the list
    pwszCurValue[0] = L'\0';

    // Open a new key if needed.
    if ( pszKeyName != NULL )
    {
        dwStatus = ClusterRegOpenKey( Hkey( ), pszKeyName, KEY_ALL_ACCESS, &hkey );
        if ( dwStatus != ERROR_SUCCESS )
        {
            delete [] pwszValue;
            ThrowStaticException( dwStatus );
        }  // if:  error opening the key
    }  // if:  need to open a subkey
    else
    {
        hkey = Hkey( );
    } // else

    // Write the value.
    dwStatus = ClusterRegSetValue(
                    hkey,
                    pszValueName,
                    REG_MULTI_SZ,
                    (CONST BYTE *) pwszValue,
                    cbValueLen - ( 1 * sizeof( WCHAR ) )
                    );
    delete [] pwszValue;
    if ( pszKeyName != NULL )
    {
        ClusterRegCloseKey( hkey );
    } // if
    if ( dwStatus != ERROR_SUCCESS )
    {
        ThrowStaticException( dwStatus );
    } // if

}  //*** CClusterItem::WriteValue( LPCTSTR, CStringList& )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::WriteValue
//
//  Routine Description:
//      Write a REG_DWORD value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to write.
//      pszKeyName      [IN] Name of the key where the value resides.
//      dwValue         [IN] Value data.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      CNTException( dwStatus )
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::WriteValue(
    IN LPCTSTR      pszValueName,
    IN LPCTSTR      pszKeyName,
    IN DWORD        dwValue
    )
{
    DWORD       dwStatus;
    HKEY        hkey;
    CWaitCursor wc;

    ASSERT( pszValueName != NULL );
    ASSERT( Hkey( ) != NULL );

    // Open a new key if needed.
    if ( pszKeyName != NULL )
    {
        dwStatus = ClusterRegOpenKey( Hkey( ), pszKeyName, KEY_ALL_ACCESS, &hkey );
        if ( dwStatus != ERROR_SUCCESS )
        {
            ThrowStaticException( dwStatus );
        } // if
    }  // if:  need to open a subkey
    else
        hkey = Hkey( );

    // Write the value.
    dwStatus = ClusterRegSetValue(
                    hkey,
                    pszValueName,
                    REG_DWORD,
                    (CONST BYTE *) &dwValue,
                    sizeof( dwValue )
                    );
    if ( pszKeyName != NULL )
    {
        ClusterRegCloseKey( hkey );
    } // if
    if ( dwStatus != ERROR_SUCCESS )
    {
        ThrowStaticException( dwStatus );
    } // if

}  //*** CClusterItem::WriteValue( LPCTSTR, DWORD )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::WriteValue
//
//  Routine Description:
//      Write a REG_BINARY value for this item if it hasn't changed.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to write.
//      pszKeyName      [IN] Name of the key where the value resides.
//      pbValue         [IN] Value data.
//      cbValue         [IN] Size of value data.
//      ppbPrevValue    [IN OUT] Previous value.
//      cbPrevValue     [IN] Size of the previous data.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      CNTException( dwStatus )
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::WriteValue(
    IN LPCTSTR          pszValueName,
    IN LPCTSTR          pszKeyName,
    IN const LPBYTE     pbValue,
    IN DWORD            cbValue,
    IN OUT LPBYTE *     ppbPrevValue,
    IN DWORD            cbPrevValue
    )
{
    DWORD               dwStatus;
    LPBYTE              pbPrevValue = NULL;
    HKEY                hkey;
    CWaitCursor         wc;

    ASSERT( pszValueName != NULL );
    ASSERT( pbValue != NULL );
    ASSERT( ppbPrevValue != NULL );
    ASSERT( cbValue > 0 );
    ASSERT( Hkey( ) != NULL );

    // See if the data has changed.
    if ( cbValue == cbPrevValue )
    {
        DWORD       ib;

        for ( ib = 0 ; ib < cbValue ; ib++ )
        {
            if ( pbValue[ ib ] != (*ppbPrevValue )[ ib ] )
            {
                break;
            } // if
        }  // for:  each byte in the value
        if ( ib == cbValue )
        {
            return;
        } // if
    }  // if:  lengths are the same

    // Allocate a new buffer for the previous data pointer.
    pbPrevValue = new BYTE[ cbValue ];
    if ( pbPrevValue == NULL )
    {
        ThrowStaticException( GetLastError( ) );
    } // if: error allocating previous data buffer
    CopyMemory( pbPrevValue, pbValue, cbValue );

    // Open a new key if needed.
    if ( pszKeyName != NULL )
    {
        dwStatus = ClusterRegOpenKey( Hkey( ), pszKeyName, KEY_ALL_ACCESS, &hkey );
        if ( dwStatus != ERROR_SUCCESS )
        {
            delete [] pbPrevValue;
            ThrowStaticException( dwStatus );
        }  // if:  error opening the key
    }  // if:  need to open a subkey
    else
    {
        hkey = Hkey( );
    } // else

    // Write the value if it hasn't changed.
    dwStatus = ClusterRegSetValue(
                    hkey,
                    pszValueName,
                    REG_BINARY,
                    pbValue,
                    cbValue
                    );
    if ( pszKeyName != NULL )
    {
        ClusterRegCloseKey( hkey );
    } // if
    if ( dwStatus == ERROR_SUCCESS )
    {
        delete [] *ppbPrevValue;
        *ppbPrevValue = pbPrevValue;
    }  // if:  set was successful
    else
    {
        delete [] pbPrevValue;
        ThrowStaticException( dwStatus );
    }  // else:  error setting the value

}  //*** CClusterItem::WriteValue( LPCTSTR, const LPBYTE )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::DeleteValue
//
//  Routine Description:
//      Delete the value for this item.
//
//  Arguments:
//      pszValueName    [IN] Name of the value to delete.
//      pszKeyName      [IN] Name of the key where the value resides.
//      rstrValue       [IN] Value data.
//
//  Return Value:
//      None.
//
//  Exceptions Thrown:
//      CNTException( dwStatus )
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::DeleteValue(
    IN LPCTSTR      pszValueName,
    IN LPCTSTR      pszKeyName
    )
{
    DWORD       dwStatus;
    HKEY        hkey;
    CWaitCursor wc;

    ASSERT( pszValueName != NULL );
    ASSERT( Hkey( ) != NULL );

    // Open a new key if needed.
    if ( pszKeyName != NULL )
    {
        dwStatus = ClusterRegOpenKey( Hkey( ), pszKeyName, KEY_ALL_ACCESS, &hkey );
        if ( dwStatus != ERROR_SUCCESS )
        {
            ThrowStaticException( dwStatus );
        } // if
    }  // if:  need to open a subkey
    else
    {
        hkey = Hkey( );
    } // else

    // Delete the value.
    dwStatus = ClusterRegDeleteValue( hkey, pszValueName );
    if ( pszKeyName != NULL )
    {
        ClusterRegCloseKey( hkey );
    } // if
    if ( dwStatus != ERROR_SUCCESS )
    {
        ThrowStaticException( dwStatus );
    } // if

}  //*** CClusterItem::DeleteValue( LPCTSTR )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::BDifferent
//
//  Routine Description:
//      Compare two string lists.
//
//  Arguments:
//      rlstr1      [IN] First string list.
//      rlstr2      [IN] Second string list.
//
//  Return Value:
//      TRUE        Lists are different.
//      FALSE       Lists are the same.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CClusterItem::BDifferent(
    IN const CStringList &  rlstr1,
    IN const CStringList &  rlstr2
    )
{
    BOOL    bDifferent;

    if ( rlstr1.GetCount( ) == rlstr2.GetCount( ) )
    {
        POSITION    posStr;

        bDifferent = FALSE;
        posStr = rlstr1.GetHeadPosition( );
        while ( posStr != NULL )
        {
            if ( rlstr2.Find( rlstr1.GetNext( posStr ) ) == 0 )
            {
                bDifferent = TRUE;
                break;
            }  // if:  string wasn't found
        }  // while:  more items in the list
    }  // if:  lists are the same size
    else
    {
        bDifferent = TRUE;
    } // else

    return bDifferent;

}  //*** CClusterItem::BDifferent( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::BDifferentOrdered
//
//  Routine Description:
//      Compare two string lists.
//
//  Arguments:
//      rlstr1      [IN] First string list.
//      rlstr2      [IN] Second string list.
//
//  Return Value:
//      TRUE        Lists are different.
//      FALSE       Lists are the same.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CClusterItem::BDifferentOrdered(
    IN const CStringList &  rlstr1,
    IN const CStringList &  rlstr2
    )
{
    BOOL    bDifferent;

    if ( rlstr1.GetCount( ) == rlstr2.GetCount( ) )
    {
        POSITION    posStr1;
        POSITION    posStr2;

        bDifferent = FALSE;
        posStr1 = rlstr1.GetHeadPosition( );
        posStr2 = rlstr2.GetHeadPosition( );
        while ( posStr1 != NULL )
        {
            if ( posStr2 == NULL )
            {
                bDifferent = TRUE;
                break;
            }  // if:  fewer strings in second list
            if ( rlstr1.GetNext( posStr1 ) != rlstr2.GetNext( posStr2 ) )
            {
                bDifferent = TRUE;
                break;
            }  // if:  strings are different
        }  // while:  more items in the list
        if ( posStr2 != NULL )
        {
            bDifferent = TRUE;
        } // if
    }  // if:  lists are the same size
    else
    {
        bDifferent = TRUE;
    } // else

    return bDifferent;

}  //*** CClusterItem::BDifferentOrdered( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::BGetColumnData
//
//  Routine Description:
//      Returns a string with the column data for a
//
//  Arguments:
//      colid           [IN] Column ID.
//      rstrText        [OUT] String in which to return the text for the column.
//
//  Return Value:
//      TRUE        Column data returned.
//      FALSE       Column ID not recognized.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CClusterItem::BGetColumnData( IN COLID colid, OUT CString & rstrText )
{
    BOOL    bSuccess;

    switch ( colid )
    {
        case IDS_COLTEXT_NAME:
            rstrText = StrName( );
            bSuccess = TRUE;
            break;
        case IDS_COLTEXT_TYPE:
            rstrText = StrType( );
            bSuccess = TRUE;
            break;
        case IDS_COLTEXT_DESCRIPTION:
            rstrText = StrDescription( );
            bSuccess = TRUE;
            break;
        default:
            bSuccess = FALSE;
            rstrText = _T("");
            break;
    }  // switch:  colid

    return bSuccess;

}  //*** CClusterItem::BGetColumnData( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::GetTreeName
//
//  Routine Description:
//      Returns a string to be used in a tree control.
//
//  Arguments:
//      rstrName    [OUT] String in which to return the name.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
#ifdef _DISPLAY_STATE_TEXT_IN_TREE
void CClusterItem::GetTreeName( OUT CString & rstrName ) const
{
    rstrName = StrName( );

}  //*** CClusterItem::GetTreeName( )
#endif

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::PmenuPopup
//
//  Routine Description:
//      Returns a popup menu.
//
//  Arguments:
//      None.
//
//  Return Value:
//      pmenu       A popup menu for the item.
//
//--
/////////////////////////////////////////////////////////////////////////////
CMenu * CClusterItem::PmenuPopup( void )
{
    CMenu * pmenu   = NULL;

    if ( IdmPopupMenu( ) != NULL )
    {
        // Update the state of the item before we construct its menu.
        UpdateState( );

        // Load the menu.
        pmenu = new CMenu;
        if ( pmenu == NULL )
        {
            return NULL;
        } // if
        if ( ! pmenu->LoadMenu( IdmPopupMenu( ) ) )
        {
            delete pmenu;
            pmenu = NULL;
        }  // if:  error loading the menu
    }  // if:  there is a menu for this item

    return pmenu;

}  //*** CClusterItem::PmenuPopup( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::AddTreeItem
//
//  Routine Description:
//      Add a tree item to the list item back pointer list.
//
//  Arguments:
//      pti         [IN] Tree item to add.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::AddTreeItem( CTreeItem * pti )
{
    POSITION    pos;

    ASSERT_VALID( pti );

    // Find the item in the list.
    pos = LptiBackPointers( ).Find( pti );

    // If it wasn't found, add it.
    if ( pos == NULL )
    {
        LptiBackPointers( ).AddTail( pti );
        Trace( g_tagClusItemCreate, _T("AddTreeItem( ) - Adding tree item backptr from '%s' in '%s' - %d in list"), StrName( ), ( pti->PtiParent( ) == NULL ? _T("<ROOT>") : pti->PtiParent( )->StrName( ) ), LptiBackPointers( ).GetCount( ) );
    }  // if:  item found in list

}  //*** CClusterItem::AddTreeItem( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::AddListItem
//
//  Routine Description:
//      Add a list item to the list item back pointer list.
//
//  Arguments:
//      pli         [IN] List item to add.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::AddListItem( CListItem * pli )
{
    POSITION    pos;

    ASSERT_VALID( pli );

    // Find the item in the list.
    pos = LpliBackPointers( ).Find( pli );

    // If it wasn't found, add it.
    if ( pos == NULL )
    {
        LpliBackPointers( ).AddTail( pli );
        Trace( g_tagClusItemCreate, _T("AddListItem( ) - Adding list item backptr from '%s' in '%s' - %d in list"), StrName( ), ( pli->PtiParent( ) == NULL ? _T("<ROOT>") : pli->PtiParent( )->StrName( ) ), LpliBackPointers( ).GetCount( ) );
    }  // if:  item found in list

}  //*** CClusterItem::AddListItem( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::RemoveTreeItem
//
//  Routine Description:
//      Remove a tree item from the tree item back pointer list.
//
//  Arguments:
//      pti         [IN] Tree item to remove.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::RemoveTreeItem( CTreeItem * pti )
{
    POSITION    pos;

    ASSERT_VALID( pti );

    // Find the item in the list.
    pos = LptiBackPointers( ).Find( pti );

    // If it was found, remove it.
    if ( pos != NULL )
    {
        LptiBackPointers( ).RemoveAt( pos );
        Trace( g_tagClusItemDelete, _T("RemoveTreeItem( ) - Deleting tree item backptr from '%s' in '%s' - %d left"), StrName( ), ( pti->PtiParent( ) == NULL ? _T("<ROOT>") : pti->PtiParent( )->StrName( ) ), LptiBackPointers( ).GetCount( ) );
    }  // if:  item found in list

}  //*** CClusterItem::RemoveTreeItem( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::RemoveListItem
//
//  Routine Description:
//      Remove a list item from the list item back pointer list.
//
//  Arguments:
//      pli         [IN] List item to remove.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::RemoveListItem( CListItem * pli )
{
    POSITION    pos;

    ASSERT_VALID( pli );

    // Find the item in the list.
    pos = LpliBackPointers( ).Find( pli );

    // If it was found, remove it.
    if ( pos != NULL )
    {
        LpliBackPointers( ).RemoveAt( pos );
        Trace( g_tagClusItemDelete, _T("RemoveListItem( ) - Deleting list item backptr from '%s' in '%s' - %d left"), StrName( ), ( pli->PtiParent( ) == NULL ? _T("<ROOT>") : pli->PtiParent( )->StrName( ) ), LpliBackPointers( ).GetCount( ) );
    }  // if:  item found in list

}  //*** CClusterItem::RemoveListItem( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CResource::CClusterItem
//
//  Routine Description:
//      Determines whether menu items corresponding to ID_FILE_RENAME
//      should be enabled or not.
//
//  Arguments:
//      pCmdUI      [IN OUT] Command routing object.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::OnUpdateRename( CCmdUI * pCmdUI )
{
    pCmdUI->Enable( BCanBeEdited( ) );

}  //*** CClusterItem::OnUpdateRename( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::OnUpdateProperties
//
//  Routine Description:
//      Determines whether menu items corresponding to ID_FILE_PROPERTIES
//      should be enabled or not.
//
//  Arguments:
//      pCmdUI      [IN OUT] Command routing object.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::OnUpdateProperties( CCmdUI * pCmdUI )
{
    pCmdUI->Enable( FALSE );

}  //*** CClusterItem::OnUpdateProperties( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::OnCmdProperties
//
//  Routine Description:
//      Processes the ID_FILE_PROPERTIES menu command.
//
//  Arguments:
//      None.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void CClusterItem::OnCmdProperties( void )
{
    BDisplayProperties( );

}  //*** CClusterItem::OnCmdProperties( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::BDisplayProperties
//
//  Routine Description:
//      Display properties for the object.
//
//  Arguments:
//      bReadOnly   [IN] Don't allow edits to the object properties.
//
//  Return Value:
//      TRUE    OK pressed.
//      FALSE   OK not pressed.
//
//--
/////////////////////////////////////////////////////////////////////////////
BOOL CClusterItem::BDisplayProperties( IN BOOL bReadOnly )
{
    AfxMessageBox( TEXT("Properties are not available."), MB_OK | MB_ICONWARNING );
    return FALSE;

}  //*** CClusterItem::BDisplayProperties( )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  CClusterItem::OnClusterNotify
//
//  Routine Description:
//      Handler for the WM_CAM_CLUSTER_NOTIFY message.
//      Processes cluster notifications for this object.
//
//  Arguments:
//      pnotify     [IN OUT] Object describing the notification.
//
//  Return Value:
//      Value returned from the application method.
//
//--
/////////////////////////////////////////////////////////////////////////////
LRESULT CClusterItem::OnClusterNotify( IN OUT CClusterNotify * pnotify )
{
    return 0;

}  //*** CClusterItem::OnClusterNotify( )


//*************************************************************************//


/////////////////////////////////////////////////////////////////////////////
// Global Functions
/////////////////////////////////////////////////////////////////////////////

/////////////////////////////////////////////////////////////////////////////
//++
//
//  DestructElements
//
//  Routine Description:
//      Destroys CClusterItem* elements.
//
//  Arguments:
//      pElements   Array of pointers to elements to destruct.
//      nCount      Number of elements to destruct.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
extern void AFXAPI DestructElements( CClusterItem ** pElements, int nCount )
{
    ASSERT( nCount == 0
         || AfxIsValidAddress( pElements, nCount * sizeof( CClusterItem * ) ) );

    // call the destructor(s )
    for ( ; nCount--; pElements++ )
    {
        ASSERT_VALID( *pElements );
        (*pElements)->Release( );
    }  // for:  each item in the array

}  //*** DestructElements( CClusterItem** )

/////////////////////////////////////////////////////////////////////////////
//++
//
//  DeleteAllItemData
//
//  Routine Description:
//      Deletes all item data in a CList.
//
//  Arguments:
//      rlp     [IN OUT] List whose data is to be deleted.
//
//  Return Value:
//      None.
//
//--
/////////////////////////////////////////////////////////////////////////////
void DeleteAllItemData( IN OUT CClusterItemList & rlp )
{
    POSITION        pos;
    CClusterItem *  pci;

    // Delete all the items in the Contained list.
    pos = rlp.GetHeadPosition( );
    while ( pos != NULL )
    {
        pci = rlp.GetNext( pos );
        ASSERT_VALID( pci );
//      Trace( g_tagClusItemDelete, _T("DeleteAllItemData(rlpci ) - Deleting cluster item '%s'"), pci->StrName( ) );
        pci->Delete( );
    }  // while:  more items in the list

}  //*** DeleteAllItemData( )