//====== Copyright © 1996-2008, Valve Corporation, All rights reserved. ======= // // Purpose: Implementation of the CDmeControlGroup class. The CDmeControlGroup // class provides hierarchical grouping of animation controls and used for // selection of the animation set controls. // //============================================================================= #include "movieobjects/dmecontrolgroup.h" #include "movieobjects/dmetransform.h" #include "movieobjects/dmetransformcontrol.h" #include "movieobjects/dmeanimationset.h" #include "datamodel/dmelementfactoryhelper.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" //------------------------------------------------------------------------------------------------- // Expose this class to the scene database //------------------------------------------------------------------------------------------------- IMPLEMENT_ELEMENT_FACTORY( DmeControlGroup, CDmeControlGroup ); //------------------------------------------------------------------------------------------------- // Purpose: Provide post construction processing. //------------------------------------------------------------------------------------------------- void CDmeControlGroup::OnConstruction() { m_Children.Init( this, "children" ); m_Controls.Init( this, "controls" ); m_GroupColor.InitAndSet( this, "groupColor", Color( 200, 200, 200, 255 ) ); m_ControlColor.InitAndSet( this, "controlColor", Color( 200, 200, 200, 255 ) ); m_Visible.InitAndSet( this, "visible", true ); m_Selectable.InitAndSet( this, "selectable", true ); m_Snappable.InitAndSet( this, "snappable", true ); } //------------------------------------------------------------------------------------------------- // Purpose: Provide processing and cleanup before shutdown //------------------------------------------------------------------------------------------------- void CDmeControlGroup::OnDestruction() { } //------------------------------------------------------------------------------------------------- // Purpose: Add a the provided control to the group, if the control is currently in another group // it will be removed from the other group first. //------------------------------------------------------------------------------------------------- void CDmeControlGroup::AddControl( CDmElement *pControl, const CDmElement *pInsertBeforeControl ) { if ( pControl == NULL ) return; // Remove the control from any group it is currently in. CDmeControlGroup *pCurrentGroup = FindGroupContainingControl( pControl ); if ( pCurrentGroup ) { pCurrentGroup->RemoveControl( pControl ); } // If a insert location control was specified find it in the list of controls int nInsertLocation = m_Controls.InvalidIndex(); if ( pInsertBeforeControl ) { nInsertLocation = m_Controls.Find( pInsertBeforeControl ); } // Add the control to the group if ( nInsertLocation != m_Controls.InvalidIndex() ) { m_Controls.InsertBefore( nInsertLocation, pControl ); } else { m_Controls.AddToTail( pControl ); } } //------------------------------------------------------------------------------------------------- // Purpose: Remove a control from the group. This will only search the immediate group for the // specified control and remove it. It will not remove the control if it is in a child of this // group. Returns false if the control was not found. //------------------------------------------------------------------------------------------------- bool CDmeControlGroup::RemoveControl( const CDmElement *pControl ) { if ( pControl == NULL ) return false; int nControls = m_Controls.Count(); for ( int iControl = 0; iControl < nControls; ++iControl ) { if ( pControl == m_Controls[ iControl ] ) { m_Controls.Remove( iControl ); return true; } } return false; } //------------------------------------------------------------------------------------------------- // Purpose: Get a flat list of all of the controls in the group. If the recursive flag is true // a flat list of all of the controls in the entire sub-tree of the group will be returned. If // the recursive flag is false on //------------------------------------------------------------------------------------------------- void CDmeControlGroup::GetControlsInGroup( CUtlVector< CDmElement* > &controlList, bool recursive ) const { // If the recursive flag is set add all of the controls // of the entire tree of each child group within the group. if ( recursive ) { int nChildren = m_Children.Count(); for ( int iChild = 0; iChild < nChildren; ++iChild ) { CDmeControlGroup *pChild = m_Children[ iChild ]; if ( pChild ) { pChild->GetControlsInGroup( controlList, true ); } } } // Add the controls from this group. int nControls = m_Controls.Count(); for ( int iControl = 0; iControl < nControls; ++iControl ) { CDmElement *pControl = m_Controls[ iControl ]; if ( pControl ) { controlList.AddToTail( pControl ); } } } //------------------------------------------------------------------------------------------------- // Purpose: Find a control with the specified name within the group. If the recursive flag is true // the entire sub-tree of the group will be searched, otherwise only the immediate control will // be searched for the group. If the parent group pointer is provided it will be returned with the // group to which the control belongs directly. //------------------------------------------------------------------------------------------------- CDmElement *CDmeControlGroup::FindControlByName( const char *pchName, bool recursive, CDmeControlGroup **pParentGroup ) { // Search the controls contained directly by the group for one with the specified name. int nControls = m_Controls.Count(); for ( int iControl = 0; iControl < nControls; ++iControl ) { CDmElement *pControl = m_Controls[ iControl ]; if ( pControl ) { if ( V_stricmp( pControl->GetName(), pchName ) == 0 ) { if ( pParentGroup ) { *pParentGroup = this; } return pControl; } } } // If the control was not found in the controls contained directly by the group // search the children and their sub-trees if the recursive flag is true. if ( recursive ) { int nChildren = m_Children.Count(); for ( int iChild = 0; iChild < nChildren; ++iChild ) { CDmeControlGroup *pChild = m_Children[ iChild ]; if ( pChild ) { CDmElement *pControl = pChild->FindControlByName( pchName, true, pParentGroup ); if ( pControl ) return pControl; } } } return NULL; } //------------------------------------------------------------------------------------------------- // Purpose: Find the group to which the specified control belongs, if any. This function searches // for any control groups which reference the specified control. It simply returns the first one // it finds, as a control should only every belong to a single control group. //------------------------------------------------------------------------------------------------- CDmeControlGroup *CDmeControlGroup::FindGroupContainingControl( const CDmElement* pControl ) { return FindReferringElement< CDmeControlGroup >( pControl, "controls" ); } //------------------------------------------------------------------------------------------------- // Purpose: Make the specified group a child of this group. The group will be removed from the // child list of any other group to which it may currently belong. //------------------------------------------------------------------------------------------------- void CDmeControlGroup::AddChild( CDmeControlGroup *pGroup, const CDmeControlGroup *pInsertBeforeGroup ) { // Can't make a group its own child Assert( pGroup != this ); if ( pGroup == this ) return; // Remove the group from its current control group if it belongs one. CDmeControlGroup *pParentGroup = pGroup->FindParent(); if ( pParentGroup ) { pParentGroup->RemoveChild( pGroup ); } // If a insert location group was specified find it in the list of children int nInsertLocation = m_Children.InvalidIndex(); if ( pInsertBeforeGroup ) { nInsertLocation = m_Children.Find( pInsertBeforeGroup ); } // Add the specified group as child of this group. if ( nInsertLocation != m_Children.InvalidIndex() ) { m_Children.InsertBefore( nInsertLocation, pGroup ); } else { m_Children.AddToTail( pGroup ); } } //------------------------------------------------------------------------------------------------- // Purpose: Remove the specified child group. Searches the immediate children of the node for the // specified group and removes it from the child list if the group is found. Returns true if the // group is found, false if the group is not found. //------------------------------------------------------------------------------------------------- bool CDmeControlGroup::RemoveChild( const CDmeControlGroup *pGroup ) { int nChildren = m_Children.Count(); for ( int iChild = 0; iChild < nChildren; ++iChild ) { if ( m_Children[ iChild ] == pGroup ) { m_Children.Remove( iChild ); return true; } } return false; } //------------------------------------------------------------------------------------------------- // Purpose: Move the specified child group to the top of the list //------------------------------------------------------------------------------------------------- void CDmeControlGroup::MoveChildToTop( const CDmeControlGroup *pGroup ) { // Make sure the group is actually a child, and move it // to the top of the list if it is not already there. int nChildren = m_Children.Count(); for ( int iChild = 1; iChild < nChildren; ++iChild ) { if ( m_Children[ iChild ] == pGroup ) { CDmeControlGroup *pChild = m_Children[ iChild ]; m_Children.Remove( iChild ); m_Children.InsertBefore( 0, pChild ); break; } } } //------------------------------------------------------------------------------------------------- // Purpose: Move the specified child group to the bottom of the list //------------------------------------------------------------------------------------------------- void CDmeControlGroup::MoveChildToBottom( const CDmeControlGroup *pGroup ) { // Make sure the group is actually a child, and move it // to the bottom of the list if it is not already there. int nChildren = m_Children.Count(); for ( int iChild = 0; iChild < (nChildren - 1); ++iChild ) { if ( m_Children[ iChild ] == pGroup ) { CDmeControlGroup *pChild = m_Children[ iChild ]; m_Children.Remove( iChild ); m_Children.AddToTail( pChild ); break; } } } //----------------------------------------------------------------------------- // Compare the two groups by name for an ascending sort //----------------------------------------------------------------------------- int CDmeControlGroup::CompareByNameAscending( CDmeControlGroup * const *pGroupA, CDmeControlGroup * const *pGroupB ) { return V_stricmp( (*pGroupA)->GetName(), (*pGroupB)->GetName() ); } //----------------------------------------------------------------------------- // Compare the two groups by name for a descending sort //----------------------------------------------------------------------------- int CDmeControlGroup::CompareByNameDecending( CDmeControlGroup * const *pGroupA, CDmeControlGroup * const *pGroupB ) { return V_stricmp( (*pGroupB)->GetName(), (*pGroupA)->GetName() ); } //------------------------------------------------------------------------------------------------- // Sore the children by name //------------------------------------------------------------------------------------------------- void CDmeControlGroup::SortChildrenByName( bool bAscending ) { // Copy the children into a temporary array to be sorted. int nNumChildren = m_Children.Count(); CUtlVector< CDmeControlGroup * > sortedList( 0, nNumChildren ); for ( int iChild = 0; iChild < nNumChildren; ++iChild ) { CDmeControlGroup *pGroup = m_Children[ iChild ]; if ( pGroup ) { sortedList.AddToTail( pGroup ); } } // Sort the temporary array in ascending or descending order if ( bAscending ) { sortedList.Sort( CompareByNameAscending ); } else { sortedList.Sort( CompareByNameDecending ); } // Remove all of the children from the original list and then add them back in sorted order m_Children.RemoveAll(); int nNumSorted = sortedList.Count(); for ( int iChild = 0; iChild < nNumSorted; ++iChild ) { CDmeControlGroup *pGroup = sortedList[ iChild ]; if ( pGroup ) { m_Children.AddToTail( pGroup ); } } } //------------------------------------------------------------------------------------------------- // Determine if the group has child of the specified name //------------------------------------------------------------------------------------------------- bool CDmeControlGroup::HasChildGroup( const char *pchName, bool recursive ) { if ( FindChildByName( pchName, recursive ) == NULL ) return false; return true; } //------------------------------------------------------------------------------------------------- // Purpose: Find the child group with the specified name. If the recursive flag is true the entire // sub-tree of the group will be searched, otherwise only the immediate children of the group will // be searched for the specified child. If a parent group pointer is provided it will be returned // with the immediate parent in which the child was located. //------------------------------------------------------------------------------------------------- CDmeControlGroup *CDmeControlGroup::FindChildByName( const char *pchName, bool recursive, CDmeControlGroup **pParentGroup ) { // Search the immediate children for a group with the specified name. int nChildren = m_Children.Count(); for ( int iChild = 0; iChild < nChildren; ++iChild ) { CDmeControlGroup *pChild = m_Children[ iChild ]; if ( pChild ) { if ( V_stricmp( pChild->GetName(), pchName ) == 0 ) { if ( pParentGroup ) { *pParentGroup = this; } return pChild; } } } // If the group was not found in the immediate children of the current group and the recursive // flag is set, search the sub-trees of all the children for the specified group. if ( recursive ) { for ( int iChild = 0; iChild < nChildren; ++iChild ) { CDmeControlGroup *pChild = m_Children[ iChild ]; if ( pChild ) { CDmeControlGroup *pGroup = pChild->FindChildByName( pchName, true, pParentGroup ); if ( pGroup ) return pGroup; } } } return NULL; } //------------------------------------------------------------------------------------------------- // Purpose: Find the parent of the group. Searches for groups which reference this group as a // child. Each group is allowed to be the child of only one group, so the first group found is // returned. //------------------------------------------------------------------------------------------------- CDmeControlGroup *CDmeControlGroup::FindParent() const { const static CUtlSymbolLarge symChildren = g_pDataModel->GetSymbol( "children" ); CDmeControlGroup *pParent = FindReferringElement< CDmeControlGroup >( this, symChildren ); return pParent; } //------------------------------------------------------------------------------------------------- // Determine if this group is an ancestor of the specified group //------------------------------------------------------------------------------------------------- bool CDmeControlGroup::IsAncestorOfGroup( const CDmeControlGroup *pGroup ) const { if ( pGroup == NULL ) return false; const CDmeControlGroup *pCurrentGroup = pGroup; const CDmeControlGroup *pParent = pGroup->FindParent(); while ( pParent ) { if ( pParent == this ) return true; pCurrentGroup = pParent; pParent = pParent->FindParent(); Assert( pCurrentGroup != pParent ); if ( pCurrentGroup == pParent ) break; } return false; } //------------------------------------------------------------------------------------------------- // Create a control group with the provided name and add it to the specified parent. If a child of // the specified name already exists it will be returned and no new group will be created. //------------------------------------------------------------------------------------------------- CDmeControlGroup *CDmeControlGroup::CreateControlGroup( const char *pchName ) { CDmeControlGroup *pExistingGroup = FindChildByName( pchName, false ); if ( pExistingGroup ) return pExistingGroup; // Create the new control group with the specified name CDmeControlGroup *pNewGroup = CreateElement< CDmeControlGroup >( pchName, GetFileId() ); // Add the group to as a child of this group AddChild( pNewGroup ); return pNewGroup; } //------------------------------------------------------------------------------------------------- // Purpose: Get a flat list of all of the groups in sub-tree of the group //------------------------------------------------------------------------------------------------- void CDmeControlGroup::GetAllChildren( CUtlVector< DmElementHandle_t > &childGroupList ) const { int nChildren = m_Children.Count(); for ( int iChild = 0; iChild < nChildren; ++iChild ) { CDmeControlGroup *pChild = m_Children[ iChild ]; if ( pChild ) { childGroupList.AddToTail( pChild->GetHandle() ); pChild->GetAllChildren( childGroupList ); } } } //------------------------------------------------------------------------------------------------- // Recursively destroy the children of the specified group which have no controls or sub groups //------------------------------------------------------------------------------------------------- bool CDmeControlGroup::DestroyEmptyChildren_R( CDmeControlGroup *pGroup ) { int nNumChildren = pGroup->m_Children.Count(); // Build a list of the children which are empty and should be destroyed. This // process will recursively remove empty children of the children so that if // a child has only empty sub-children then it will still be removed. CUtlVector< CDmeControlGroup * > childrenToDestroy( 0, nNumChildren ); for ( int iChild = 0; iChild < nNumChildren; ++iChild ) { CDmeControlGroup *pChild = pGroup->m_Children[ iChild ]; if ( pChild ) { if ( DestroyEmptyChildren_R( pChild ) ) { childrenToDestroy.AddToTail( pChild ); } } } // Destroy the empty children int nNumToDestroy = childrenToDestroy.Count(); for ( int iChild = 0; iChild < nNumToDestroy; ++iChild ) { CDmeControlGroup *pChild = childrenToDestroy[ iChild ]; pGroup->RemoveChild( pChild ); } // If this node is now empty return true indicating that it may be destroyed return ( ( pGroup->m_Children.Count() == 0 ) && ( pGroup->m_Controls.Count() == 0 ) ); } //------------------------------------------------------------------------------------------------- // Destroy all of the empty children of the group, will not destroy this group even it is empty. //------------------------------------------------------------------------------------------------- void CDmeControlGroup::DestroyEmptyChildren() { DestroyEmptyChildren_R( this ); } //------------------------------------------------------------------------------------------------- // Purpose: Destroy the control group, moving all of its children and controls into this node //------------------------------------------------------------------------------------------------- void CDmeControlGroup::DestroyGroup( CDmeControlGroup *pGroup, CDmeControlGroup *pRecipient, bool recursive ) { if ( pGroup == NULL ) return; // Remove the group from its parent CDmeControlGroup *pParent = pGroup->FindParent(); if ( pParent ) { pParent->RemoveChild( pGroup ); if ( pRecipient == NULL ) { pRecipient = pParent; } } // Destroy the group and all of its children if specified DestroyGroup_R( pGroup, pRecipient, recursive ); } //------------------------------------------------------------------------------------------------- // Purpose: Recursively destroy the child groups of the specified group and and the controls to the // specified recipient group //------------------------------------------------------------------------------------------------- void CDmeControlGroup::DestroyGroup_R( CDmeControlGroup *pGroup, CDmeControlGroup *pRecipient, bool recursive ) { if ( pGroup == NULL ) return; // If the group is not empty there must be a recipient to receive its controls and groups if ( pRecipient == NULL && !pGroup->IsEmpty() ) { Assert( pGroup->IsEmpty() || pRecipient ); return; } // Iterate through the children, if recursive destroy the // children otherwise copy the children to the recipient. int nChildren = pGroup->m_Children.Count(); for ( int iChild = 0; iChild < nChildren; ++iChild ) { CDmeControlGroup *pChild = pGroup->m_Children[ iChild ]; if ( pChild ) { if ( recursive ) { DestroyGroup_R( pChild, pRecipient, true ); } else { pRecipient->m_Children.AddToTail( pChild ); } } } // Copy all the controls of the node into the recipient int nControls = pGroup->m_Controls.Count(); for ( int iControl = 0; iControl < nControls; ++iControl ) { CDmElement *pControl = pGroup->m_Controls[ iControl ]; pRecipient->m_Controls.AddToTail( pControl ); } // Destroy the group DestroyElement( pGroup ); } //------------------------------------------------------------------------------------------------- // Remove all of the children and controls from the group //------------------------------------------------------------------------------------------------- void CDmeControlGroup::RemoveAllChildrenAndControls() { m_Children.RemoveAll(); m_Controls.RemoveAll(); } //------------------------------------------------------------------------------------------------- // Purpose: Set the color of the group, this is the color that is used when displaying the group in // the user interface. //------------------------------------------------------------------------------------------------- void CDmeControlGroup::SetGroupColor( const Color &groupColor, bool bRecursive ) { m_GroupColor = groupColor; if ( !bRecursive ) return; int nChildren = m_Children.Count(); for ( int iChild = 0; iChild < nChildren; ++iChild ) { if ( m_Children[ iChild ] ) { m_Children[ iChild ]->SetGroupColor( groupColor, true ); } } } //------------------------------------------------------------------------------------------------- // Purpose: Set the color to be used on the controls of the group //------------------------------------------------------------------------------------------------- void CDmeControlGroup::SetControlColor( const Color &controlColor, bool bRecursive ) { m_ControlColor = controlColor; if ( !bRecursive ) return; int nChildren = m_Children.Count(); for ( int iChild = 0; iChild < nChildren; ++iChild ) { if ( m_Children[ iChild ] ) { m_Children[ iChild ]->SetControlColor( controlColor, true ); } } } //----------------------------------------------------------------------------- // Set the visible state of the group //----------------------------------------------------------------------------- void CDmeControlGroup::SetVisible( bool bVisible ) { m_Visible = bVisible; } //----------------------------------------------------------------------------- // Enable or disable selection of the controls //----------------------------------------------------------------------------- void CDmeControlGroup::SetSelectable( bool bSelectable ) { m_Selectable = bSelectable; } //----------------------------------------------------------------------------- // Enable or disable control snapping //----------------------------------------------------------------------------- void CDmeControlGroup::SetSnappable( bool bSnappable ) { m_Snappable = bSnappable; } //----------------------------------------------------------------------------- // Purpose: Determine if there are any controls or children in the group //----------------------------------------------------------------------------- bool CDmeControlGroup::IsEmpty() const { if ( m_Controls.Count() > 0 ) return false; if ( m_Children.Count() > 0 ) return false; return true; } //----------------------------------------------------------------------------- // Is the group visible //----------------------------------------------------------------------------- bool CDmeControlGroup::IsVisible() const { CDmeControlGroup *pParent = FindParent(); if ( pParent && !pParent->IsVisible() ) return false; return m_Visible; } //----------------------------------------------------------------------------- // Can controls in the group be selected in the viewport //----------------------------------------------------------------------------- bool CDmeControlGroup::IsSelectable() const { CDmeControlGroup *pParent = FindParent(); if ( pParent && !pParent->IsSelectable() ) return false; return m_Selectable; } //----------------------------------------------------------------------------- // Can controls in the group be snapped to in the viewport //----------------------------------------------------------------------------- bool CDmeControlGroup::IsSnappable() const { CDmeControlGroup *pParent = FindParent(); if ( pParent && !pParent->IsSnappable() ) return false; return m_Snappable; } //----------------------------------------------------------------------------- // Find the shared ancestor between this control group and the specified control // group. Will return NULL if groups are not in the same tree and do not share a // common ancestor. If one group is an ancestor of the other group then that // group will be returned, so result may be one of the nodes which is not // technically an ancestor of that node. //----------------------------------------------------------------------------- CDmeControlGroup *CDmeControlGroup::FindCommonAncestor( CDmeControlGroup *pControlGroupB ) { CDmeControlGroup *pControlGroupA = this; // If the specified group is this group then // the common ancestor is the group itself. if ( pControlGroupA == pControlGroupB ) return pControlGroupA; // Build the path from each group to the root CUtlVector< CDmeControlGroup * > pathToGroupA; CUtlVector< CDmeControlGroup * > pathToGroupB; pControlGroupA->BuildPathFromRoot( pathToGroupA ); pControlGroupB->BuildPathFromRoot( pathToGroupB ); // Now walk each of the the paths until they diverge CDmeControlGroup *pCommonGroup = NULL; int nNumSteps = MIN( pathToGroupA.Count(), pathToGroupB.Count() ); int iStep = 0; while ( iStep < nNumSteps ) { if ( pathToGroupA[ iStep ] != pathToGroupB[ iStep ] ) break; pCommonGroup = pathToGroupA[ iStep ]; ++iStep; } return pCommonGroup; } //----------------------------------------------------------------------------- // Find the root control group which this control group is in the sub tree of. //----------------------------------------------------------------------------- CDmeControlGroup *CDmeControlGroup::FindRootControlGroup() { CDmeControlGroup *pCurrent = this; CDmeControlGroup *pParent = pCurrent->FindParent(); while ( pParent ) { pCurrent = pParent; pParent = pParent->FindParent(); } return pCurrent; } //----------------------------------------------------------------------------- // Build a list of the control group that form the path to the root of the tree // to which the control group belongs //----------------------------------------------------------------------------- void CDmeControlGroup::BuildPathFromRoot( CUtlVector< CDmeControlGroup * > &pathToGroup ) { CUtlVector< CDmeControlGroup * > pathToRoot( 0, 16 ); CDmeControlGroup *pCurrent = this; while ( pCurrent ) { pathToRoot.AddToTail( pCurrent ); pCurrent = pCurrent->FindParent(); } int nNumGroups = pathToRoot.Count(); pathToGroup.SetCount( nNumGroups ); for ( int iGroup = 0; iGroup < nNumGroups; ++iGroup ) { pathToGroup[ iGroup ] = pathToRoot[ nNumGroups - 1 - iGroup ]; } } //----------------------------------------------------------------------------- // Find the animation set associated with the control group //----------------------------------------------------------------------------- CDmeAnimationSet *CDmeControlGroup::FindAnimationSet( bool bSearchAncestors ) const { const static CUtlSymbolLarge symRootControlGroup = g_pDataModel->GetSymbol( "rootControlGroup" ); const CDmeControlGroup *pCurrent = this; while ( pCurrent ) { CDmeAnimationSet *pAnimationSet = FindReferringElement< CDmeAnimationSet >( pCurrent, symRootControlGroup ); if ( pAnimationSet != NULL ) return pAnimationSet; if ( bSearchAncestors == false ) break; const CDmeControlGroup *pParent = pCurrent->FindParent(); if ( pCurrent == pParent ) break; pCurrent = pParent; } return NULL; }