|
|
//====== Copyright � 1996-2005, Valve Corporation, All rights reserved. =======
//
// Purpose:
//
//=============================================================================
#include "dme_controls/BaseAnimSetControlGroupPanel.h"
#include "vgui_controls/TreeView.h"
#include "vgui_controls/Menu.h"
#include "vgui_controls/Button.h"
#include "vgui_controls/ImageList.h"
#include "vgui_controls/Tooltip.h"
#include "tier1/KeyValues.h"
#include "movieobjects/dmeanimationset.h"
#include "movieobjects/dmegamemodel.h"
#include "movieobjects/dmerig.h"
#include "movieobjects/dmetransformcontrol.h"
#include "dme_controls/BaseAnimSetAttributeSliderPanel.h"
#include "dme_controls/BaseAnimationSetEditor.h"
#include "dme_controls/dmecontrols_utils.h"
#include "vgui/ISystem.h"
#include "vgui/ISurface.h"
#include "vgui/IInput.h"
#include "vgui/IVGui.h"
#include "vgui/ILocalize.h"
// memdbgon must be the last include file in a .cpp file!!!
#include "tier0/memdbgon.h"
using namespace vgui;
enum StateIcons_t { STATE_ICON_WORK_CAMERA_PARENT_ACTIVE, STATE_ICON_WORK_CAMERA_PARENT_HIDDEN, STATE_ICON_DAG_LOCKED, STATE_ICON_DAG_LOCKED_PARTIAL, STATE_ICON_DAG_LOCKED_WORLD, STATE_ICON_DAG_LOCKED_WORLD_PARTIAL, STATE_ICON_COUNT };
enum StateIconSetType_t { STATE_ICON_SET_INVALID, STATE_ICON_SET_GROUP, STATE_ICON_SET_CONTROL_POSITION, STATE_ICON_SET_CONTROL_ROTATION, };
//-----------------------------------------------------------------------------
// A panel containing a set of icons providing information about the state of
// an animation group tree item.
//-----------------------------------------------------------------------------
class CAnimGroupStateIconSet : public Panel { DECLARE_CLASS_SIMPLE( CAnimGroupStateIconSet, Panel );
// The sole purpose of the icon button is to allow right clicks
// to be handled and used for context menu creation.
class IconButton : public Button { DECLARE_CLASS_SIMPLE( IconButton, Button );
public: IconButton( CAnimGroupStateIconSet *pIconSet, const char *pName ) : Button( pIconSet, pName, "" ) , m_pIconSet( pIconSet ) {}
void OnMousePressed( MouseCode code ) { if ( code == MOUSE_RIGHT ) { m_pIconSet->IconButtonRightClick(); return; } BaseClass::OnMousePressed( code ); }
CAnimGroupStateIconSet *const m_pIconSet; };
public: CAnimGroupStateIconSet( Panel *pParent, const char *pchName, StateIconSetType_t stateType, CDmeDag *pDag, ImageList &imageList, const int *pImageIndexMap );
virtual void ApplySchemeSettings( IScheme *pScheme ); virtual void PerformLayout(); virtual bool IsDroppable( CUtlVector< KeyValues * > &msglist ); virtual void OnPanelDropped( CUtlVector< KeyValues * >& msglist );
void IconButtonRightClick();
void UpdateState();
private:
MESSAGE_FUNC( OnLockDagButton, "LockDagButton" );
static const CDmeDag *GetDagFromDragElement( CDmElement *pElement );
static const int LOCKED_TOOLTIP_DELAY = 750; static const int UNLOCKED_TOOLTIP_DELAY = 1500; ImageList &m_ImageList; const int *const m_pImageIndexMap; const int m_StateType; CDmeDag *m_pDag; IconButton *m_pLockButton; };
CAnimGroupStateIconSet::CAnimGroupStateIconSet( Panel *pParent, const char *pchName, StateIconSetType_t itemType, CDmeDag *pDag, ImageList &imageList, const int *pImageIndexMap ) : BaseClass( pParent, "AnimGroupStateIconSet" ) , m_ImageList( imageList ) , m_pImageIndexMap( pImageIndexMap ) , m_StateType( itemType ) , m_pDag( pDag ) , m_pLockButton( NULL ) { m_pLockButton = new IconButton( this, "LockButton" ); m_pLockButton->SetVisible( true ); m_pLockButton->AddActionSignalTarget( this ); m_pLockButton->SetCommand( new KeyValues( "LockDagButton" ) ); m_pLockButton->SetKeyBoardInputEnabled( false ); vgui::BaseTooltip *pTooltip = m_pLockButton->GetTooltip(); if ( pTooltip ) { pTooltip->SetTooltipDelay( UNLOCKED_TOOLTIP_DELAY ); pTooltip->SetText( "#LockButtonTip" ); pTooltip->SetTooltipFormatToSingleLine(); }
SetDropEnabled( true ); }
void CAnimGroupStateIconSet::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); Color buttonColor = pScheme->GetColor( "Frame.BgColor", Color( 0, 0, 0, 0 ) ); SetBgColor( buttonColor );
if ( m_pLockButton ) { m_pLockButton->SetDefaultBorder( pScheme->GetBorder( "DepressedBorder" ) );
} }
void CAnimGroupStateIconSet::PerformLayout() { int nHeight = GetTall();
if ( m_pLockButton ) { m_pLockButton->SetBounds( 1, 1, nHeight - 1, nHeight - 1 ); } }
const CDmeDag *CAnimGroupStateIconSet::GetDagFromDragElement( CDmElement *pElement ) { if ( pElement == NULL ) return NULL;
const CDmeDag *pDag = NULL; CDmeTransformControl *pTransformControl = CastElement< CDmeTransformControl >( pElement ); if ( pTransformControl ) { pDag = pTransformControl->GetDag(); } else { pDag = CastElement< CDmeDag >( pElement ); }
return pDag; }
bool CAnimGroupStateIconSet::IsDroppable( CUtlVector< KeyValues * > &msglist ) { int nCount = msglist.Count(); if ( nCount != 1 ) return false;
KeyValues *pData = msglist[ 0 ]; CDmElement *pElement = GetElementKeyValue< CDmElement >( pData, "dmeelement" ); const CDmeDag *pParentDag = GetDagFromDragElement( pElement ); if ( pParentDag == NULL ) return false;
// Can't parent a dag to is child
if ( m_pDag->IsAncestorOfDag( pParentDag ) ) return false;
// Can't parent a dag to itself.
if ( m_pDag == pParentDag ) return false;
return true; }
void CAnimGroupStateIconSet::OnPanelDropped( CUtlVector< KeyValues * >& msglist ) { int nCount = msglist.Count(); if ( nCount == 1 ) { KeyValues *pData = msglist[ 0 ]; CDmElement *pElement = GetElementKeyValue< CDmElement >( pData, "dmeelement" ); const CDmElement *pParentDag = GetDagFromDragElement( pElement );
if ( pParentDag ) { bool bPosition = ( ( m_StateType == STATE_ICON_SET_GROUP ) || ( m_StateType == STATE_ICON_SET_CONTROL_POSITION ) ); bool bRotation = ( ( m_StateType == STATE_ICON_SET_GROUP ) || ( m_StateType == STATE_ICON_SET_CONTROL_ROTATION ) );
KeyValues *pMsgKv = new KeyValues( "SetOverrideParent" ); pMsgKv->SetInt( "targetDag", m_pDag->GetHandle() ); pMsgKv->SetInt( "parentDag", pParentDag->GetHandle() ); pMsgKv->SetBool( "position", bPosition ); pMsgKv->SetBool( "rotation", bRotation ); PostMessage( GetParent(), pMsgKv, 0.0f ); } } }
void CAnimGroupStateIconSet::IconButtonRightClick() { bool bPosition = ( ( m_StateType == STATE_ICON_SET_GROUP ) || ( m_StateType == STATE_ICON_SET_CONTROL_POSITION ) ); bool bRotation = ( ( m_StateType == STATE_ICON_SET_GROUP ) || ( m_StateType == STATE_ICON_SET_CONTROL_ROTATION ) );
KeyValues *pMsgKv = new KeyValues( "OpenLockContextMenu" ); pMsgKv->SetInt( "targetDag", m_pDag->GetHandle() ); pMsgKv->SetBool( "position", bPosition ); pMsgKv->SetBool( "rotation", bRotation );
PostMessage( GetParent(), pMsgKv, 0.0f ); }
void CAnimGroupStateIconSet::UpdateState() { if ( m_pDag == NULL ) return; m_pLockButton->ClearImages(); vgui::BaseTooltip *pTooltip = m_pLockButton->GetTooltip(); bool bPos = false; bool bRot = false; const CDmeDag *pOverrideParent = m_pDag->GetOverrideParent( bPos, bRot, true );
if ( pOverrideParent != NULL ) { bool bLockedToWorld = ( pOverrideParent->GetParent() == NULL ); int nFullLockedIcon = bLockedToWorld ? STATE_ICON_DAG_LOCKED_WORLD : STATE_ICON_DAG_LOCKED; int nPartialLockedIcon = bLockedToWorld ? STATE_ICON_DAG_LOCKED_WORLD_PARTIAL : STATE_ICON_DAG_LOCKED_PARTIAL; // Change the image to the grayed out version if both position and rotation are not overridden
int nImageIndex = 0; if ( m_StateType == STATE_ICON_SET_GROUP ) { nImageIndex = ( bPos && bRot ) ? m_pImageIndexMap[ nFullLockedIcon ] : m_pImageIndexMap[ nPartialLockedIcon ]; } else if ( ( ( m_StateType == STATE_ICON_SET_CONTROL_POSITION ) && bPos ) || ( ( m_StateType == STATE_ICON_SET_CONTROL_ROTATION ) && bRot ) ) { nImageIndex = m_pImageIndexMap[ nFullLockedIcon ]; }
// Update the image of on the button
vgui::IImage *pLockImage = m_ImageList.GetImage( nImageIndex ); m_pLockButton->AddImage( pLockImage, 0 );
// Update the tool tip text describing what dag is new parent
if ( pTooltip ) { if ( bLockedToWorld ) { pTooltip->SetText( "#LockedToWorld" ); } else { const wchar_t *pLabel = g_pVGuiLocalize->Find( "#LockedTo" ); if ( pLabel ) { char itemText[ 32 ]; char tipText[ 64 ]; g_pVGuiLocalize->ConvertUnicodeToANSI( pLabel, itemText, sizeof( itemText ) ); V_snprintf( tipText, sizeof( tipText ), "%s %s", itemText, pOverrideParent->GetName() ); pTooltip->SetText( tipText ); } } pTooltip->SetTooltipDelay( LOCKED_TOOLTIP_DELAY ); } } else { pTooltip->SetTooltipDelay( UNLOCKED_TOOLTIP_DELAY ); pTooltip->SetText( "#LockButtonTip" ); } }
void CAnimGroupStateIconSet::OnLockDagButton() { if ( m_pDag ) { bool bPosition = ( ( m_StateType == STATE_ICON_SET_GROUP ) || ( m_StateType == STATE_ICON_SET_CONTROL_POSITION ) ); bool bRotation = ( ( m_StateType == STATE_ICON_SET_GROUP ) || ( m_StateType == STATE_ICON_SET_CONTROL_ROTATION ) );
KeyValues *pMsgKv = new KeyValues( "ToggleDagLock" ); pMsgKv->SetInt( "targetDag", m_pDag->GetHandle() ); pMsgKv->SetBool( "position", bPosition ); pMsgKv->SetBool( "rotation", bRotation ); PostMessage( GetParent(), pMsgKv, 0.0f ); } }
//-----------------------------------------------------------------------------
// Shows the tree view of the animation groups
//-----------------------------------------------------------------------------
class CAnimGroupTree : public TreeView { DECLARE_CLASS_SIMPLE( CAnimGroupTree, TreeView ); public: CAnimGroupTree( Panel *parent, const char *panelName, CBaseAnimSetControlGroupPanel *groupPanel, bool bStateInterface ); virtual ~CAnimGroupTree();
virtual bool IsItemDroppable( int itemIndex, bool bInsertBefore, CUtlVector< KeyValues * >& msglist ); virtual void OnItemDropped( int itemIndex, bool bInsertBefore, CUtlVector< KeyValues * >& msglist ); virtual bool CanCurrentlyEditLabel( int nItemIndex ) const; virtual void OnLabelChanged( int nItemIndex, char const *pOldString, char const *pNewString ); virtual void GetSelectedItemsForDrag( int nPrimaryDragItem, CUtlVector< int >& list ); virtual void GenerateDragDataForItem( int itemIndex, KeyValues *msg ); virtual void GenerateContextMenu( int itemIndex, int x, int y ); virtual void GenerateChildrenOfNode(int itemIndex);
virtual void RemoveItem(int itemIndex, bool bPromoteChildren, bool bRecursivelyRemove = false ); virtual void RemoveAll();
virtual void ApplySchemeSettings( IScheme *pScheme ); virtual void PaintBackground(); virtual void PerformLayout(); virtual void OnTick();
virtual void OnMousePressed( MouseCode code );
// Item add helpers
int AddAnimationSetToTree( CDmeAnimationSet *pAnimSet ); int AddControlGroupToTree( int parentIndex, CDmeControlGroup *pControlGroup, CDmeControlGroup *pParentGroup, CDmeAnimationSet *pAnimSet ); int AddControlToTree( int parentIndex, CDmElement *pControl, CDmeControlGroup *pControlGroup, CDmeAnimationSet *pAnimSet ); void AddTransformComponentsToTree( int parentIndex, CDmeTransformControl *pTransformControl, CDmeControlGroup *pControlGroup, CDmeAnimationSet *pAnimSet, TransformComponent_t nComponentFlags );
CDmElement *GetTreeItemData( int nTreeIndex, AnimTreeItemType_t *pItemType = NULL, CDmeAnimationSet **ppParentAnimationSet = NULL, CDmeControlGroup **ppParentControlGroup = NULL ) const;
// Get the component flags associated with the specified item
TransformComponent_t GetItemComponentFlags( int nTreeIndex ) const;
// Get the control group associated with the specified tree item
CDmeControlGroup *GetControlGroupForTreeItem( int nTreeItemIndex ) const;
// Get the state icon set panel associated with the specified tree item
CAnimGroupStateIconSet *GetTreeItemStateIconSet( int nTreeItemIndex );
// Get the dag node associated with the specified tree item
CDmeDag *GetDagForTreeItem( int nTreeItemIndex ) const;
// Find the child of the specified item which has the specified element as its "handle" value
int FindChildItemForElement( int nParentIndex, const CDmElement *pElement, TransformComponent_t nComponentFlags = TRANSFORM_COMPONENT_NONE );
// Find the index of the the tree view item that has the specified element as its "handle" value
int FindItemForElement( const CDmElement *pElement, TransformComponent_t nComponentFlags = TRANSFORM_COMPONENT_NONE );
// Build the tree view items from the root down to the specified animation set
int BuildTreeToAnimationSet( CDmeAnimationSet *pAnimationSet );
// Build the tree view items from the root down to the specified control group
int BuildTreeToGroup( CDmeControlGroup *pGroup, CDmeAnimationSet *pAnimationSet );
// Build the tree view items from the root down to the specified control
void BuildTreeToControl( const CDmElement *pElement, TransformComponent_t nComponentFlags );
// Set the selection state on the specified item
void SetItemSelectionState( int nItemIndex, SelectionState_t selectionState );
// Get the selection state of the specified item
SelectionState_t GetItemSelectionState( int nItemIndex ) const;
// Get a list of the controls which are fully selected and whose parents are not fully selected
void GetSelectionRootItems( CUtlVector< int > &selectedItems ) const;
private:
MESSAGE_FUNC( OnClearWorkCameraParent, "ClearWorkCameraParent" ); MESSAGE_FUNC_INT( OnResetTransformPivot, "OnResetTransformPivot", viewCenter ); MESSAGE_FUNC_PARAMS( OnToggleDagLock, "ToggleDagLock", params ); MESSAGE_FUNC_PARAMS( OnSetOverrideParent, "SetOverrideParent", params ); MESSAGE_FUNC_PARAMS( OnOpenLockContextMenu, "OpenLockContextMenu", params );
void CleanupContextMenu();
virtual void OnContextMenuSelection( int itemIndex );
int AddItemToTree( AnimTreeItemType_t itemType, const char *label, int parentIndex, const Color& fg, CDmElement *pElement, CDmeAnimationSet *pAnimSet, CDmeControlGroup *pControlGroup, bool bExpandable, SelectionState_t selection, TransformComponent_t nComponentFlags );
Color ModifyColorByGroupState( const Color &baseColor, CDmeControlGroup *pControlGroup ); void AddDmeControlGroup( int nParentItemIndex, CDmeAnimationSet *pAnimationSet, CDmeControlGroup *pGroup ); bool VisibleControlsBelow_R( CDmeControlGroup *pGroup ); static bool CanAddDragIntoGroup( const CDmeControlGroup *pTargetGroup, const CDmElement *pTargetElement, const CDmElement *pDragElement, bool bInsertBefore );
static SelectionState_t GetSelectionStateForFlags( int nBaseFlags, int nSelectionFlags );
vgui::DHANDLE< vgui::Menu > m_hContextMenu; CBaseAnimSetControlGroupPanel *m_pGroupPanel;
Button *m_pWorkCameraParentButton; ImageList m_Images; int m_StateIconIndices[ STATE_ICON_COUNT ];
Color m_RootColor; Color m_StateColumnColor; int m_nStateColumnWidth; bool m_bStateInterface; };
CAnimGroupTree::CAnimGroupTree( Panel *parent, const char *panelName, CBaseAnimSetControlGroupPanel *groupPanel, bool bStateInterface ) : BaseClass( parent, panelName ), m_pGroupPanel( groupPanel ), m_Images( false ), m_RootColor( 128, 128, 128, 255 ), m_StateColumnColor( 0, 0, 0, 0 ), m_nStateColumnWidth( 0 ), m_bStateInterface( bStateInterface ) { if ( m_bStateInterface ) { m_nStateColumnWidth = 20; SetTreeIndent( m_nStateColumnWidth - 2 ); }
SetShowRootNode( false ); SetDragEnabledItems( true ); SetAllowLabelEditing( true ); SetEnableInsertDropLocation( true );
m_pWorkCameraParentButton = new Button( this, "workCameraParent", "" ); m_pWorkCameraParentButton->SetVisible( false ); m_pWorkCameraParentButton->AddActionSignalTarget( this ); m_pWorkCameraParentButton->SetCommand( new KeyValues( "ClearWorkCameraParent" ) ); m_pWorkCameraParentButton->SetKeyBoardInputEnabled( false );
m_StateIconIndices[ STATE_ICON_WORK_CAMERA_PARENT_ACTIVE ] = m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_referenceframe_active", false ) ); m_StateIconIndices[ STATE_ICON_WORK_CAMERA_PARENT_HIDDEN ] = m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_referenceframe_active_hidden", false ) ); m_StateIconIndices[ STATE_ICON_DAG_LOCKED ] = m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_dag_locked", false ) ); m_StateIconIndices[ STATE_ICON_DAG_LOCKED_PARTIAL ] = m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_dag_locked_grey", false ) ); m_StateIconIndices[ STATE_ICON_DAG_LOCKED_WORLD ] = m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_dag_locked_world", false ) ); m_StateIconIndices[ STATE_ICON_DAG_LOCKED_WORLD_PARTIAL ] = m_Images.AddImage( scheme()->GetImage( "tools/ifm/icon_dag_locked_world_grey", false ) );
ivgui()->AddTickSignal( GetVPanel(), 0 ); }
CAnimGroupTree::~CAnimGroupTree() { CleanupContextMenu(); }
void CAnimGroupTree::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); m_RootColor = pScheme->GetColor( "AnimSet.RootColor", Color( 128, 128, 128, 255 ) ); m_StateColumnColor = pScheme->GetColor( "Frame.BgColor", Color( 0, 0, 0, 0 ) ); SetFont( pScheme->GetFont( "DefaultBold", IsProportional() ) ); }
int CAnimGroupTree::AddAnimationSetToTree( CDmeAnimationSet *pAnimSet ) { // Don't add the animation set if the root control group is not visible
CDmeControlGroup *pRootGroup = pAnimSet->GetRootControlGroup(); if ( !m_pGroupPanel->m_pController->IsControlGroupVisible( pRootGroup ) ) return -1;
int parentIndex = GetRootItemIndex(); SelectionState_t selection = m_pGroupPanel->m_pController->GetSelectionState( pAnimSet ); Color groupColor = ModifyColorByGroupState( m_RootColor, pRootGroup );
return AddItemToTree( ANIMTREE_ITEM_ANIMSET, pAnimSet->GetName(), parentIndex, groupColor, pAnimSet, pAnimSet, NULL, true, selection, TRANSFORM_COMPONENT_NONE ); }
int CAnimGroupTree::AddControlGroupToTree( int parentIndex, CDmeControlGroup *pControlGroup, CDmeControlGroup *pParentGroup, CDmeAnimationSet *pAnimSet ) { SelectionState_t selection = m_pGroupPanel->m_pController->GetSelectionState( pControlGroup ); Color groupColor = ModifyColorByGroupState( pControlGroup->GroupColor(), pControlGroup );
return AddItemToTree( ANIMTREE_ITEM_GROUP, pControlGroup->GetName(), parentIndex, groupColor, pControlGroup, pAnimSet, pParentGroup, true, selection, TRANSFORM_COMPONENT_NONE ); }
int CAnimGroupTree::AddControlToTree( int parentIndex, CDmElement *pControl, CDmeControlGroup *pControlGroup, CDmeAnimationSet *pAnimSet ) { SelectionState_t selection = m_pGroupPanel->m_pController->GetSelectionState( pControl ); bool bTransformControl = pControl->IsA( CDmeTransformControl::GetStaticTypeSymbol() ); TransformComponent_t nComponentFlags = bTransformControl ? TRANSFORM_COMPONENT_ALL : TRANSFORM_COMPONENT_NONE;
Color controlColor = ModifyColorByGroupState( pControlGroup->ControlColor(), pControlGroup );
return AddItemToTree( ANIMTREE_ITEM_CONTROL, pControl->GetName(), parentIndex, controlColor, pControl, pAnimSet, pControlGroup, bTransformControl, selection, nComponentFlags ); }
SelectionState_t CAnimGroupTree::GetSelectionStateForFlags( int nBaseFlags, int nSelectionFlags ) { int nMaskedFlags = nBaseFlags & nSelectionFlags;
if ( nMaskedFlags == nBaseFlags ) return SEL_ALL;
return ( ( nMaskedFlags > 0 ) ? SEL_SOME : SEL_NONE ); }
void CAnimGroupTree::AddTransformComponentsToTree( int nParentIndex, CDmeTransformControl *pControl, CDmeControlGroup *pControlGroup, CDmeAnimationSet *pAnimSet, TransformComponent_t nParentComponentFlags ) { int nSelectionFlags = m_pGroupPanel->m_pController->GetSelectionComponentFlags( pControl ); const char *pName = pControl->GetName();
Color color = ModifyColorByGroupState( pControlGroup->ControlColor(), pControlGroup );
if ( nParentComponentFlags == TRANSFORM_COMPONENT_ALL ) { SelectionState_t posSelection = GetSelectionStateForFlags( TRANSFORM_COMPONENT_POSITION, nSelectionFlags ); SelectionState_t rotSelection = GetSelectionStateForFlags( TRANSFORM_COMPONENT_ROTATION, nSelectionFlags ); AddItemToTree( ANIMTREE_ITEM_COMPONENT, CFmtStr( "%s - pos", pName ), nParentIndex, color, pControl, pAnimSet, pControlGroup, true, posSelection, TRANSFORM_COMPONENT_POSITION ); AddItemToTree( ANIMTREE_ITEM_COMPONENT, CFmtStr( "%s - rot", pName ), nParentIndex, color, pControl, pAnimSet, pControlGroup, true, rotSelection, TRANSFORM_COMPONENT_ROTATION ); } else if ( nParentComponentFlags == TRANSFORM_COMPONENT_POSITION ) { AddItemToTree( ANIMTREE_ITEM_COMPONENT, CFmtStr( "%s - pos.x", pName ), nParentIndex, color, pControl, pAnimSet, NULL, false, GetSelectionStateForFlags( TRANSFORM_COMPONENT_POSITION_X, nSelectionFlags ), TRANSFORM_COMPONENT_POSITION_X ); AddItemToTree( ANIMTREE_ITEM_COMPONENT, CFmtStr( "%s - pos.y", pName ), nParentIndex, color, pControl, pAnimSet, NULL, false, GetSelectionStateForFlags( TRANSFORM_COMPONENT_POSITION_Y, nSelectionFlags ), TRANSFORM_COMPONENT_POSITION_Y ); AddItemToTree( ANIMTREE_ITEM_COMPONENT, CFmtStr( "%s - pos.z", pName ), nParentIndex, color, pControl, pAnimSet, NULL, false, GetSelectionStateForFlags( TRANSFORM_COMPONENT_POSITION_Z, nSelectionFlags ), TRANSFORM_COMPONENT_POSITION_Z ); } else if ( nParentComponentFlags == TRANSFORM_COMPONENT_ROTATION ) { AddItemToTree( ANIMTREE_ITEM_COMPONENT, CFmtStr( "%s - rot.x", pName ), nParentIndex, color, pControl, pAnimSet, NULL, false, GetSelectionStateForFlags( TRANSFORM_COMPONENT_ROTATION_X, nSelectionFlags ), TRANSFORM_COMPONENT_ROTATION_X ); AddItemToTree( ANIMTREE_ITEM_COMPONENT, CFmtStr( "%s - rot.y", pName ), nParentIndex, color, pControl, pAnimSet, NULL, false, GetSelectionStateForFlags( TRANSFORM_COMPONENT_ROTATION_Y, nSelectionFlags ), TRANSFORM_COMPONENT_ROTATION_Y ); AddItemToTree( ANIMTREE_ITEM_COMPONENT, CFmtStr( "%s - rot.z", pName ), nParentIndex, color, pControl, pAnimSet, NULL, false, GetSelectionStateForFlags( TRANSFORM_COMPONENT_ROTATION_Z, nSelectionFlags ), TRANSFORM_COMPONENT_ROTATION_Z ); } }
Color CAnimGroupTree::ModifyColorByGroupState( const Color &baseColor, CDmeControlGroup *pControlGroup ) { Color groupColor = baseColor; int nAlpha = ( pControlGroup->IsSelectable() && pControlGroup->IsVisible() ) ? 255 : 64; groupColor.SetColor( groupColor.r(), groupColor.g(), groupColor.b(), nAlpha ); return groupColor; }
int CAnimGroupTree::AddItemToTree( AnimTreeItemType_t itemType, const char *label, int parentIndex, const Color& fg, CDmElement *pElement, CDmeAnimationSet *pAnimSet, CDmeControlGroup *pControlGroup, bool bExpandable, SelectionState_t selection, TransformComponent_t nComponentFlags ) { DmElementHandle_t hElement = pElement ? pElement->GetHandle() : DMELEMENT_HANDLE_INVALID; DmElementHandle_t hAnimSet = pAnimSet ? pAnimSet->GetHandle() : DMELEMENT_HANDLE_INVALID; DmElementHandle_t hControlGroup = pControlGroup ? pControlGroup->GetHandle() : DMELEMENT_HANDLE_INVALID;
KeyValues *kv = new KeyValues( "item", "text", label ); kv->SetInt( "droppable", 1 ); kv->SetInt( "itemType", ( int )itemType ); kv->SetInt( "handle", ( int )hElement ); kv->SetInt( "animset", ( int )hAnimSet ); kv->SetInt( "controlgroup", ( int )hControlGroup ); kv->SetInt( "selection", ( int )selection ); kv->SetInt( "componentFlags", ( int )nComponentFlags ); kv->SetInt( "Expand", bExpandable ? 1 : 0 ); CDmeTransformControl *pTransformControl = CastElement< CDmeTransformControl >( pElement ); CDmeTransform *pTransform = ( pTransformControl != NULL ) ? pTransformControl->GetTransform() : NULL;
CDmeDag *pDag = ( pTransform != NULL ) ? pTransform->GetDag() : NULL;
if ( m_bStateInterface ) { StateIconSetType_t stateIconSetType = STATE_ICON_SET_INVALID;
if ( itemType == ANIMTREE_ITEM_COMPONENT ) { if ( nComponentFlags == TRANSFORM_COMPONENT_POSITION ) { stateIconSetType = STATE_ICON_SET_CONTROL_POSITION; } else if ( nComponentFlags == TRANSFORM_COMPONENT_ROTATION ) { stateIconSetType = STATE_ICON_SET_CONTROL_ROTATION; } } else if ( itemType == ANIMTREE_ITEM_CONTROL ) { stateIconSetType = STATE_ICON_SET_GROUP; }
if ( pDag && ( stateIconSetType != STATE_ICON_SET_INVALID ) ) { CAnimGroupStateIconSet *pIconSet = new CAnimGroupStateIconSet( this, label, stateIconSetType, pDag, m_Images, m_StateIconIndices ); kv->SetPtr( "stateIconSet", pIconSet ); } }
int idx = AddItem( kv, parentIndex ); SetItemFgColor( idx, fg ); SetItemSelectionTextColor( idx, fg );
SetSilentMode( true );
if ( selection == SEL_ALL || selection == SEL_SOME ) { Color color = ( selection == SEL_ALL ) ? Color( 128, 128, 128, 128 ) : Color( 128, 128, 64, 64 ); SetItemSelectionBgColor( idx, color ); SetItemSelectionUnfocusedBgColor( idx, color ); AddSelectedItem( idx, false, false, true ); } else { Color color( 0, 0, 0, 128 ); SetItemSelectionBgColor( idx, color ); SetItemSelectionUnfocusedBgColor( idx, color ); RemoveSelectedItem( idx ); }
if ( ( itemType == ANIMTREE_ITEM_GROUP ) || ( itemType == ANIMTREE_ITEM_ANIMSET ) ) { SetLabelEditingAllowed( idx, true ); }
SetSilentMode( false );
ExpandItem( idx, false );
kv->deleteThis();
return idx; }
CDmElement *CAnimGroupTree::GetTreeItemData( int nTreeIndex, AnimTreeItemType_t *pItemType /*= NULL */, CDmeAnimationSet **ppParentAnimationSet /*= NULL*/, CDmeControlGroup **ppControlGroup /*= NULL*/ ) const { KeyValues *kv = GetItemData( nTreeIndex ); if ( !kv ) return NULL;
if ( pItemType ) { *pItemType = static_cast< AnimTreeItemType_t >( kv->GetInt( "itemType" ) ); }
if ( ppParentAnimationSet ) { *ppParentAnimationSet = GetElementKeyValue< CDmeAnimationSet >( kv, "animset" ); }
if ( ppControlGroup ) { *ppControlGroup = GetElementKeyValue< CDmeControlGroup >( kv, "controlgroup" ); }
return GetElementKeyValue< CDmElement >( kv, "handle" ); }
TransformComponent_t CAnimGroupTree::GetItemComponentFlags( int nTreeIndex ) const { KeyValues *kv = GetItemData( nTreeIndex ); return static_cast< TransformComponent_t >( kv->GetInt( "componentFlags" ) ); }
CDmeControlGroup *CAnimGroupTree::GetControlGroupForTreeItem( int nItemIndex ) const { CDmElement *pElement = GetTreeItemData( nItemIndex, NULL ); CDmeControlGroup *pControlGroup = NULL; const CDmeAnimationSet *pAnimationSet = CastElement< CDmeAnimationSet >( pElement );
if ( pAnimationSet ) { pControlGroup = pAnimationSet->GetRootControlGroup(); } else { pControlGroup = CastElement< CDmeControlGroup >( pElement ); }
return pControlGroup; }
CAnimGroupStateIconSet *CAnimGroupTree::GetTreeItemStateIconSet( int nTreeIndex ) { KeyValues *kv = GetItemData( nTreeIndex ); if ( !kv ) return NULL;
return static_cast< CAnimGroupStateIconSet * >( kv->GetPtr( "stateIconSet" ) ); }
CDmeDag *CAnimGroupTree::GetDagForTreeItem( int nTreeItemIndex ) const { AnimTreeItemType_t itemType; CDmElement *pElement = GetTreeItemData( nTreeItemIndex, &itemType );
if ( !pElement && itemType != ANIMTREE_ITEM_GROUP ) return NULL;
CDmeTransformControl *pTransformControl = CastElement< CDmeTransformControl >( pElement ); if ( pTransformControl == NULL ) return NULL;
CDmeTransform *pTransform = pTransformControl->GetTransform();
if ( !pTransform ) return NULL;
return pTransform->GetDag(); }
void CAnimGroupTree::CleanupContextMenu() { if ( m_hContextMenu.Get() ) { delete m_hContextMenu.Get(); m_hContextMenu = NULL; } }
bool CAnimGroupTree::CanAddDragIntoGroup( const CDmeControlGroup *pTargetGroup, const CDmElement *pTargetElement, const CDmElement *pDragElement, bool bInsertBefore ) { const static CUtlSymbolLarge symControls = g_pDataModel->GetSymbol( "controls" );
if ( ( pTargetGroup == NULL ) || ( pDragElement == NULL ) || ( pTargetElement == NULL ) ) return false;
// Cannot drag a group into itself
if ( pDragElement == pTargetGroup ) return false;
CDmeAnimationSet *pTargetGroupAnimSet = pTargetGroup->FindAnimationSet( true ); CDmeAnimationSet *pDragGroupAnimSet = NULL; const CDmeControlGroup *pDragGroup = CastElement< CDmeControlGroup >( pDragElement ); bool bTargetElenentIsGroup = pTargetElement->IsA( CDmeControlGroup::GetStaticTypeSymbol() );
if ( pDragGroup ) { // Cannot drag a group into a group that already exists in its sub-tree
if ( pDragGroup->IsAncestorOfGroup( pTargetGroup ) ) return false;
// Cannot insert a group before a control
if ( bInsertBefore && !bTargetElenentIsGroup ) return false; CDmeControlGroup *pDragGroupParent = pDragGroup->FindParent(); pDragGroupAnimSet = ( pDragGroupParent != NULL ) ? pDragGroupParent->FindAnimationSet( true ) : NULL; } else { // Cannot insert a control before a group
if ( bInsertBefore && bTargetElenentIsGroup ) return false;
// Find the animation set to which the control belongs
pDragGroupAnimSet = FindReferringElement< CDmeAnimationSet >( pDragElement, symControls ); }
// If a control group is in the sub-tree of an animation set it must remain in the sub-tree of that animation set,
// it is not in the sub-tree of an animation set it must not be added to the sub-tree of any other animation set.
if ( pDragGroupAnimSet != pTargetGroupAnimSet ) return false;
return true; }
bool CAnimGroupTree::IsItemDroppable( int nItemIndex, bool bInsertBefore, CUtlVector< KeyValues * >& msglist ) { if ( msglist.Count() == 0 ) return false; CDmeControlGroup *pParentControlGroup = NULL; CDmElement *pTargetElement = GetTreeItemData( nItemIndex, NULL, NULL, &pParentControlGroup ); if ( pTargetElement == NULL ) return false;
CDmeControlGroup *pTargetControlGroup = GetControlGroupForTreeItem( nItemIndex ); const CDmeControlGroup *pNewParentGroup = bInsertBefore ? pParentControlGroup : pTargetControlGroup;
// See if there are any messages in the list that will apply to the control group
int nMsgCount = msglist.Count(); for ( int iMsg = 0; iMsg < nMsgCount; ++iMsg ) { KeyValues *pData = msglist[ iMsg ]; if ( pData == NULL ) continue;
if ( pData->FindKey( "color" ) ) { if ( pTargetControlGroup != NULL ) return true; }
const CDmElement *pDragElement = GetElementKeyValue< CDmElement >( pData, "dmeelement" ); if ( CanAddDragIntoGroup( pNewParentGroup, pTargetElement, pDragElement, bInsertBefore ) ) return true; }
return false; }
void CAnimGroupTree::OnItemDropped( int nItemIndex, bool bInsertBefore, CUtlVector< KeyValues * >& msglist ) { if ( !IsItemDroppable( nItemIndex, bInsertBefore, msglist ) ) return;
CDmeControlGroup *pParentControlGroup = NULL; CDmeAnimationSet *pTargetAnimSet = NULL; CDmElement *pTargetElement = GetTreeItemData( nItemIndex, NULL, &pTargetAnimSet, &pParentControlGroup ); if ( pTargetElement == NULL ) return;
CDmeControlGroup *pTargetControlGroup = GetControlGroupForTreeItem( nItemIndex ); CDmeControlGroup *pNewParentGroup = bInsertBefore ? pParentControlGroup : pTargetControlGroup;
CAppUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Drop onto control group" );
int nMsgCount = msglist.Count(); for ( int iMsg = 0; iMsg < nMsgCount; ++iMsg ) { KeyValues *pData = msglist[ iMsg ]; if ( pData == NULL ) continue;
if ( pData->FindKey( "color" ) ) { Color clr = pData->GetColor( "color" ); SetItemFgColor( nItemIndex, clr ); SetItemSelectionTextColor( nItemIndex, clr ); pTargetControlGroup->SetGroupColor( clr, false ); }
CDmElement *pDragElement = GetElementKeyValue< CDmElement >( pData, "dmeelement" ); if ( CanAddDragIntoGroup( pNewParentGroup, pTargetElement, pDragElement, bInsertBefore ) ) { CDmeControlGroup *pDragGroup = CastElement< CDmeControlGroup >( pDragElement ); if ( pDragGroup ) { pNewParentGroup->AddChild( pDragGroup, ( bInsertBefore ? pTargetControlGroup : NULL ) ); } else { pNewParentGroup->AddControl( pDragElement, ( bInsertBefore ? pTargetElement : NULL ) ); }
if ( pTargetAnimSet ) { CDmeControlGroup *pRootControlGroup = pTargetAnimSet->GetRootControlGroup(); if ( pRootControlGroup ) { pRootControlGroup->DestroyEmptyChildren(); } } } } }
void CAnimGroupTree::GetSelectedItemsForDrag( int nPrimaryDragItem, CUtlVector< int >& list ) { // The item actually being dragged will be the first item in the selection list,
// since ancestors of this primary item may also be in the selection we can't
// drag the whole selection, but we do want to drag the siblings of the item.
// So iterate the selection and add any siblings of the primary selected item.
CUtlVector< int > selectedItems; GetSelectedItems( selectedItems );
int nNumSelected = selectedItems.Count(); if ( nNumSelected <= 0 ) return;
AnimTreeItemType_t itemType; CDmeControlGroup *pPrimaryParentGroup = NULL; GetTreeItemData( nPrimaryDragItem, &itemType, NULL, &pPrimaryParentGroup );
if ( itemType == ANIMTREE_ITEM_COMPONENT ) return;
for ( int i = 0 ; i < nNumSelected; ++i ) { CDmeControlGroup *pParentGroup = NULL; GetTreeItemData( selectedItems[ i ], &itemType, NULL, &pParentGroup ); if ( ( pPrimaryParentGroup == pParentGroup ) && ( itemType != ANIMTREE_ITEM_COMPONENT ) ) { list.AddToTail( selectedItems[ i ] ); } } }
void CAnimGroupTree::GenerateDragDataForItem( int nItemIndex, KeyValues *msg ) { AnimTreeItemType_t itemType; CDmElement *pElement = GetTreeItemData( nItemIndex, &itemType ); if ( ( pElement ) && ( itemType != ANIMTREE_ITEM_COMPONENT ) ) { msg->SetInt( "dmeelement", pElement->GetHandle() ); } }
bool CAnimGroupTree::CanCurrentlyEditLabel( int nItemIndex ) const { // The item must still be in the selection
if ( IsItemSelected( nItemIndex ) == false ) return false;
// Parents or children of the item may be selected, but siblings must not be selected.
int nParentIndex = GetItemParent( nItemIndex ); CUtlVector< int > selectedItems; GetSelectedItems( selectedItems );
int nSelectedItems = selectedItems.Count(); for ( int iItem = 0; iItem < nSelectedItems; ++iItem ) { int nSelectedItem = selectedItems[ iItem ]; if ( nItemIndex == nSelectedItem ) continue;
if ( GetItemParent( nSelectedItem) == nParentIndex ) return false; }
return true; }
void CAnimGroupTree::OnLabelChanged( int nItemIndex, char const *pOldString, char const *pNewString ) { CUndoScopeGuard undoSg( "Change group label" );
CDmElement *pElement = GetTreeItemData( nItemIndex ); CDmeControlGroup *pControlGroup = CastElement< CDmeControlGroup >( pElement ); CDmeAnimationSet *pAnimationSet = CastElement< CDmeAnimationSet >( pElement );
if ( pControlGroup ) { CDmeControlGroup *pRootGroup = pControlGroup->FindRootControlGroup(); if ( pRootGroup && pRootGroup->FindChildByName( pNewString, true ) ) { CUtlVector< DmElementHandle_t > childList; pRootGroup->GetAllChildren( childList ); int nIndex = GenerateUniqueNameIndex( pNewString, childList, 0 ); CFmtStr newName( "%s%d", pNewString, nIndex ); pControlGroup->SetName( newName.Access() );
// Force the tree display to update to reflect the modified name
m_pGroupPanel->RebuildTree( true ); } else { pControlGroup->SetName( pNewString ); } } else if ( pAnimationSet ) { KeyValues *pMsgKV = new KeyValues( "SetAnimationSetName", "text", pNewString ); SetElementKeyValue( pMsgKV, "animset", pAnimationSet ); PostMessage( m_pGroupPanel->GetEditor(), pMsgKV, 0.0f ); } }
// override to open a custom context menu on a node being selected and right-clicked
void CAnimGroupTree::GenerateContextMenu( int itemIndex, int x, int y ) { PostMessage( GetParent(), new KeyValues( "TreeViewOpenContextMenu", "itemID", itemIndex ), 0.0f ); }
//-----------------------------------------------------------------------------
// Purpose: Override the default right click selection behavior so that the
// clicked item becomes the first selected item.
//-----------------------------------------------------------------------------
void CAnimGroupTree::OnContextMenuSelection( int itemIndex ) { // Select the item, if it was already selected
// this should make it the first selected item.
AddSelectedItem( itemIndex, !IsItemSelected( itemIndex ) ); Assert( GetFirstSelectedItem() == itemIndex ); }
void CAnimGroupTree::OnClearWorkCameraParent() { m_pGroupPanel->m_pController->SetWorkCameraParent( NULL ); }
void CAnimGroupTree::OnResetTransformPivot( int viewCenter ) { PostMessage( m_pGroupPanel->GetEditor(), new KeyValues( "ResetTransformPivot", "viewCenter", viewCenter ), 0.0f ); }
void CAnimGroupTree::OnToggleDagLock( KeyValues *pParams ) { PostMessage( m_pGroupPanel->GetEditor(), pParams->MakeCopy(), 0.0f );
// Cause perform layout to be run so that the lock icons
// are updated after the message has been processed.
InvalidateLayout(); }
void CAnimGroupTree::OnSetOverrideParent( KeyValues *pParams ) { PostMessage( m_pGroupPanel->GetEditor(), pParams->MakeCopy(), 0.0f ); InvalidateLayout(); }
void CAnimGroupTree::OnOpenLockContextMenu( KeyValues *pParams ) { PostMessage( m_pGroupPanel->GetEditor(), pParams->MakeCopy(), 0.0f ); }
void CAnimGroupTree::OnMousePressed( MouseCode code ) { int mx, my; input()->GetCursorPos( mx, my ); ScreenToLocal( mx, my ); int idx = FindItemUnderMouse( mx, my );
// The default tree behavior ignores the width of the item when testing against the mouse. For
// the animation set editor only the actual are of the item should be considered under the mouse.
if ( IsItemIDValid( idx ) ) { int xPos, yPos, width, height; GetItemBounds( idx, xPos, yPos, width, height );
if ( ( mx < xPos ) || ( my < yPos ) || ( mx > ( xPos + width ) ) || ( my > ( yPos + height) ) ) { idx = -1; } }
bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL)); if ( !ctrl ) { if ( code == MOUSE_RIGHT ) { PostMessage( GetParent(), new KeyValues( "TreeViewOpenContextMenu", "itemID", idx ), 0.0f ); } else { BaseClass::OnMousePressed( code ); } return; }
if ( !IsItemIDValid( idx ) || ( mx >= ( 20 + m_nStateColumnWidth ) ) ) { BaseClass::OnMousePressed( code ); return; }
CDmeTransformControl *pTransformControl = CastElement< CDmeTransformControl >( GetTreeItemData( idx ) ); if ( pTransformControl ) { CDmeDag *pDag = pTransformControl->GetDag(); m_pGroupPanel->m_pController->SetWorkCameraParent( pDag ); }
}
void CAnimGroupTree::PaintBackground() { BaseClass::PaintBackground();
if ( m_bStateInterface ) { int nHeight = GetTall(); vgui::surface()->DrawSetColor( m_StateColumnColor ); vgui::surface()->DrawFilledRect( 0, 0, m_nStateColumnWidth, nHeight ); } }
void CAnimGroupTree::PerformLayout() { BaseClass::PerformLayout();
for ( int itemID = FirstItem(); itemID != InvalidItemID(); itemID = NextItem( itemID ) ) { CAnimGroupStateIconSet *pIconSet = GetTreeItemStateIconSet( itemID ); if ( pIconSet == NULL ) continue;
int nPosX, nPosY, nWidth, nHeight; if ( GetItemBounds( itemID, nPosX, nPosY, nWidth, nHeight ) ) { pIconSet->UpdateState(); pIconSet->SetBounds( 0, nPosY, nHeight, nHeight ); pIconSet->SetVisible( true ); } else { pIconSet->SetVisible( false ); } }
}
void CAnimGroupTree::OnTick() { BaseClass::OnTick();
int nWorkCameraParentItem = -1;
if ( CDmeDag *pWorkCameraParent = m_pGroupPanel->GetWorkCameraParent() ) { for ( int itemID = FirstItem(); itemID != InvalidItemID(); itemID = NextItem( itemID ) ) { CDmeDag *pDag = GetDagForTreeItem( itemID ); if ( pDag == pWorkCameraParent ) { nWorkCameraParentItem = itemID; break; } } }
m_pWorkCameraParentButton->SetVisible( nWorkCameraParentItem != -1 ); if ( nWorkCameraParentItem != -1 ) { int x = 0, y = 0, w = 0, h = 0; GetItemBounds( nWorkCameraParentItem, x, y, w, h ); m_pWorkCameraParentButton->SetBounds( 1 + m_nStateColumnWidth, y + 1, h - 2, h - 2 ); m_pWorkCameraParentButton->ClearImages(); m_pWorkCameraParentButton->AddImage( m_Images.GetImage( m_StateIconIndices[ STATE_ICON_WORK_CAMERA_PARENT_ACTIVE ] ), 0 ); } }
void CAnimGroupTree::RemoveItem( int itemIndex, bool bPromoteChildren, bool bRecursivelyRemove ) { // The tree view implementation uses negative item indices to indicate
// recursion, here we don't care, we just want the current item being removed.
int nActualIndex = ( itemIndex < 0 ) ? -itemIndex : itemIndex; CAnimGroupStateIconSet *pIconSet = GetTreeItemStateIconSet( nActualIndex );
if ( pIconSet ) { delete pIconSet; }
BaseClass::RemoveItem( itemIndex, bPromoteChildren, bRecursivelyRemove ); }
void CAnimGroupTree::RemoveAll() { for ( int itemID = FirstItem(); itemID != InvalidItemID(); itemID = NextItem( itemID ) ) { CAnimGroupStateIconSet *pIconSet = GetTreeItemStateIconSet( itemID ); if ( pIconSet ) { delete pIconSet; } }
BaseClass::RemoveAll(); }
bool CAnimGroupTree::VisibleControlsBelow_R( CDmeControlGroup *pGroup ) { // If the group is not visible itself then it
// cannot have any visible controls below it.
if ( !m_pGroupPanel->m_pController->IsControlGroupVisible( pGroup ) ) return false;
// The group is visible and has controls itself then it has
// visible controls below it, no need to go farther.
if ( pGroup->Controls().Count() > 0 ) return true;
// If the group did not have any controls itself, check all of its children recursively,
// if any are visible and have controls then there are visible controls below this group.
const CDmaElementArray< CDmeControlGroup > &children = pGroup->Children(); int nNumChildren = children.Count(); for ( int iChild = 0; iChild < nNumChildren; ++iChild ) { CDmeControlGroup *pChild = children[ iChild ]; if ( pChild == NULL ) continue;
if ( VisibleControlsBelow_R( pChild ) ) return true; }
return false; }
void CAnimGroupTree::AddDmeControlGroup( int nParentItemIndex, CDmeAnimationSet *pAnimationSet, CDmeControlGroup *pGroup ) { Assert( pGroup ); Assert( pAnimationSet );
// Add the sub-groups
const CDmaElementArray< CDmeControlGroup > &subGroups = pGroup->Children(); int nGroups = subGroups.Count(); for ( int iGroup = 0; iGroup < nGroups; ++iGroup ) { CDmeControlGroup *pSubGroup = subGroups[ iGroup ]; if ( pSubGroup == NULL ) continue;
if ( !VisibleControlsBelow_R( pSubGroup ) ) continue;
AddControlGroupToTree( nParentItemIndex, pSubGroup, pGroup, pAnimationSet ); }
// Add the controls
const CDmaElementArray< CDmElement > &controls = pGroup->Controls(); int nControls = controls.Count(); for ( int iControl = 0; iControl < nControls; ++iControl ) { CDmElement *pControl = controls[ iControl ]; if ( pControl ) { AddControlToTree( nParentItemIndex, pControl, pGroup, pAnimationSet ); } } }
void CAnimGroupTree::GenerateChildrenOfNode(int itemIndex) { BaseClass::GenerateChildrenOfNode( itemIndex );
// Make sure the children are only generated once.
if ( GetNumChildren( itemIndex ) > 0 ) return;
if ( itemIndex == GetRootItemIndex() ) { CDmeFilmClip *pFilmClip = m_pGroupPanel->m_pController->GetAnimationSetClip(); CAnimSetGroupAnimSetTraversal traversal( pFilmClip ); while ( CDmeAnimationSet *pAnimSet = traversal.Next() ) { AddAnimationSetToTree( pAnimSet ); } return; }
// Get type for item
KeyValues *kv = GetItemData( itemIndex ); if ( kv ) { AnimTreeItemType_t itemType = static_cast< AnimTreeItemType_t >( kv->GetInt( "itemType" ) );
CDmElement *pElement = GetElementKeyValue< CDmElement >( kv, "handle" ); CDmeAnimationSet *pAnimationSet = CastElement< CDmeAnimationSet >( GetElementKeyValue< CDmElement >( kv, "animset" ) ); CDmeControlGroup *pControlGroup = CastElement< CDmeControlGroup >( GetElementKeyValue< CDmElement >( kv, "controlgroup" ) ); TransformComponent_t nComponentFlags = static_cast< TransformComponent_t >( kv->GetInt( "componentFlags" ) );
switch ( itemType ) { default: break; case ANIMTREE_ITEM_ANIMSET: // Under anim set we get 1-N groups, expandable if they have children or controls
{ Assert( pAnimationSet );
CDmeControlGroup *pRootGroup = pAnimationSet->GetRootControlGroup(); AddDmeControlGroup( itemIndex, pAnimationSet, pRootGroup ); } break; case ANIMTREE_ITEM_GROUP: // Under group we have subgroups and controls
{ Assert( pAnimationSet ); Assert( pElement ); CDmeControlGroup *pGroup = CastElement< CDmeControlGroup >( pElement ); Assert( pGroup ); AddDmeControlGroup( itemIndex, pAnimationSet, pGroup ); } break; case ANIMTREE_ITEM_CONTROL: case ANIMTREE_ITEM_COMPONENT: // Under transform controls are the position and rotation components
{ Assert( pControlGroup ); Assert( pAnimationSet ); Assert( pElement ); CDmeTransformControl *pTranformControl = CastElement< CDmeTransformControl >( pElement ); if ( pTranformControl ) { AddTransformComponentsToTree( itemIndex, pTranformControl, pControlGroup, pAnimationSet, nComponentFlags ); } } break; } } }
//-----------------------------------------------------------------------------
// Find the child of the specified item which has the specified element as its
// "handle" value
//-----------------------------------------------------------------------------
int CAnimGroupTree::FindChildItemForElement( int nParentIndex, const CDmElement *pElement, TransformComponent_t nComponentFlags ) { int nChildren = GetNumChildren( nParentIndex ); for ( int iChild = 0; iChild < nChildren; ++iChild ) { int nChildIndex = GetChild( nParentIndex, iChild );
KeyValues *pItemData = GetItemData( nChildIndex ); if ( !pItemData ) continue;
CDmElement *pItemElement = GetElementKeyValue< CDmElement >( pItemData, "handle" ); if ( pItemElement == pElement ) { if ( nComponentFlags == 0 ) return nChildIndex;
TransformComponent_t nItemComponentFlags = static_cast< TransformComponent_t >( pItemData->GetInt( "componentFlags" ) ); if ( nItemComponentFlags == nComponentFlags ) return nChildIndex;
if ( ( nItemComponentFlags & nComponentFlags ) == nComponentFlags ) { GenerateChildrenOfNode( nChildIndex ); int nSubChildIndex = FindChildItemForElement( nChildIndex, pElement, nComponentFlags ); if ( nSubChildIndex >= 0 ) return nChildIndex; } } }
return -1; }
//-----------------------------------------------------------------------------
// Find the index of the the tree view item that has the specified element as
// its "handle" value
//-----------------------------------------------------------------------------
int CAnimGroupTree::FindItemForElement( const CDmElement *pElement, TransformComponent_t nComponentFlags ) { int highest = GetHighestItemID(); for ( int i = 0; i < highest; ++i ) { if ( !IsItemIDValid( i ) ) continue;
KeyValues *pItemData = GetItemData( i ); if ( !pItemData ) continue;
CDmElement *pItemElement = GetElementKeyValue< CDmElement >( pItemData, "handle" ); if ( pItemElement == pElement ) { TransformComponent_t nItemComponentFlags = static_cast< TransformComponent_t >( pItemData->GetInt( "componentFlags" ) ); if ( ( nItemComponentFlags == nComponentFlags ) || ( nComponentFlags == 0 ) ) return i; } }
return -1; }
//-----------------------------------------------------------------------------
// Build the tree view items from the root down to the specified animation set
//-----------------------------------------------------------------------------
int CAnimGroupTree::BuildTreeToAnimationSet( CDmeAnimationSet *pAnimationSet ) { if ( pAnimationSet == NULL ) return -1;
// Check to see if the animation set is already has an item, if so just return that.
int nItemIndex = FindItemForElement( pAnimationSet );
// If the item was not found find or create the item for the animation set group containing
// the animation set and then create all its children, including the animation set being looked for.
if ( nItemIndex < 0 ) { int nParentItemIndex = GetRootItemIndex(); GenerateChildrenOfNode( nParentItemIndex ); nItemIndex = FindChildItemForElement( nParentItemIndex, pAnimationSet ); } return nItemIndex; }
//-----------------------------------------------------------------------------
// Build the tree view items from the root down to the specified control group
//-----------------------------------------------------------------------------
int CAnimGroupTree::BuildTreeToGroup( CDmeControlGroup *pGroup, CDmeAnimationSet *pAnimationSet ) { if ( ( pGroup == NULL ) || ( pAnimationSet == NULL ) ) return -1;
// Check to see if the group is already has an item, if so just return that.
int nItemIndex = FindItemForElement( pGroup );
if ( nItemIndex < 0 ) { int nParentItemIndex = -1;
if ( pAnimationSet->GetRootControlGroup() == pGroup ) { nParentItemIndex = BuildTreeToAnimationSet( pAnimationSet ); return nParentItemIndex; } else { CDmeControlGroup* pParentGroup = pGroup->FindParent(); nParentItemIndex = BuildTreeToGroup( pParentGroup, pAnimationSet ); } if ( nParentItemIndex >= 0 ) { GenerateChildrenOfNode( nParentItemIndex ); nItemIndex = FindChildItemForElement( nParentItemIndex, pGroup ); } }
return nItemIndex; }
//-----------------------------------------------------------------------------
// Build the tree view items from the root down to the specified control
//-----------------------------------------------------------------------------
void CAnimGroupTree::BuildTreeToControl( const CDmElement *pControl, TransformComponent_t nComponentFlags ) { if ( pControl == NULL ) return;
// Find the animation set to which the control belongs
CDmeAnimationSet *pAnimationSet = FindAncestorReferencingElement< CDmeAnimationSet >( pControl ); if ( pAnimationSet == NULL ) return;
// Check to see if the control is already has an item, if so just return that.
int nItemIndex = FindItemForElement( pControl, nComponentFlags );
// If the item for the control was not found, find the group containing the control
// and make sure it has an item in the tree and the generate the children for that
// item which will include the item for the control being looked for.
if ( nItemIndex < 0 ) { int nParentItemIndex = -1; CDmeControlGroup *pGroup = CDmeControlGroup::FindGroupContainingControl( pControl ); nParentItemIndex = BuildTreeToGroup( pGroup, pAnimationSet ); if ( nParentItemIndex >= 0 ) { GenerateChildrenOfNode( nParentItemIndex ); nItemIndex = FindChildItemForElement( nParentItemIndex, pControl, nComponentFlags ); } }
if ( nItemIndex >= 0 ) { MakeItemVisible( nItemIndex ); } }
//-----------------------------------------------------------------------------
// Set the selection state on the specified item
//-----------------------------------------------------------------------------
void CAnimGroupTree::SetItemSelectionState( int nItemIndex, SelectionState_t selectionState ) { KeyValues *pItemData = GetItemData( nItemIndex );
if ( pItemData ) { pItemData->SetInt( "selection", ( int )selectionState ); } }
//-----------------------------------------------------------------------------
// Get the selection state of the specified item
//-----------------------------------------------------------------------------
SelectionState_t CAnimGroupTree::GetItemSelectionState( int nItemIndex ) const { KeyValues *pItemData = GetItemData( nItemIndex ); if ( pItemData == NULL) return SEL_EMPTY; return ( SelectionState_t )( pItemData->GetInt( "selection" ) ); }
//-----------------------------------------------------------------------------
// Get a list of the controls which are fully selected and whose parents are
// not fully selected
//-----------------------------------------------------------------------------
void CAnimGroupTree::GetSelectionRootItems( CUtlVector< int > &rootSelectedItems ) const { CUtlVector< int > selectedItems; GetSelectedItems( selectedItems );
int nNumSelectedItems = selectedItems.Count(); rootSelectedItems.EnsureCapacity( nNumSelectedItems );
for ( int iItem = 0; iItem < nNumSelectedItems; ++iItem ) { int nItemIndex = selectedItems[ iItem ];
// Skip any items which are not fully selected
if ( GetItemSelectionState( nItemIndex ) != SEL_ALL ) continue; // Skip any items whose parent is fully selected
int nParentIndex = GetItemParent( nItemIndex ); if ( GetItemSelectionState( nParentIndex ) == SEL_ALL ) continue; rootSelectedItems.AddToTail( nItemIndex ); } }
CBaseAnimSetControlGroupPanel::CBaseAnimSetControlGroupPanel( vgui::Panel *parent, const char *className, CBaseAnimationSetEditor *editor, bool bControlStateInterface ) : BaseClass( parent, className ), m_pController( NULL ) { m_hEditor = editor; m_pController = editor->GetController(); m_pController->AddControlSelectionChangedListener( this );
m_hGroups = SETUP_PANEL( new CAnimGroupTree( this, "AnimSetGroups", this, bControlStateInterface ) ); m_hGroups->SetAutoResize( Panel::PIN_TOPLEFT, Panel::AUTORESIZE_DOWNANDRIGHT, 0, 0, 0, 0 ); m_hGroups->SetAllowMultipleSelections( true ); m_hGroups->AddItem( new KeyValues( "root" ), -1 ); // add (invisible) root
}
CBaseAnimSetControlGroupPanel::~CBaseAnimSetControlGroupPanel() { }
void CBaseAnimSetControlGroupPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); m_FullSelectionColor = pScheme->GetColor( "AnimSet.FullSelectionColor" , Color( 128, 128, 128, 128 ) ); m_PartialSelectionColor = pScheme->GetColor( "AnimSet.PartialSelectionColor" , Color( 128, 128, 64, 64 ) ); m_ContextMenuHighlightColor = pScheme->GetColor( "AnimSet.ContextMenuSelectionColor", Color( 255, 153, 0, 255 ) );
// Normally we shouldn't have to call this but ApplySchemeSettings is called directly
// by the animation set editor, so normal solve traverse isn't performed in that case.
m_hGroups->ApplySchemeSettings( pScheme ); }
void CBaseAnimSetControlGroupPanel::SelectAnimTreeItem( int itemIndex, ESelectionMode selectionMode ) { AnimTreeItemType_t itemType; CDmElement *pElement = m_hGroups->GetTreeItemData( itemIndex, &itemType ); if ( !pElement ) return;
switch ( itemType ) { case ANIMTREE_ITEM_ANIMSET: if ( CDmeAnimationSet *pAnimSet = CastElement< CDmeAnimationSet >( pElement ) ) { m_pController->SelectAnimationSet( pAnimSet, selectionMode ); } break; case ANIMTREE_ITEM_GROUP: if ( CDmeControlGroup *pGroup = CastElement< CDmeControlGroup >( pElement ) ) { m_pController->SelectControlGroup( pGroup, selectionMode ); } break; case ANIMTREE_ITEM_CONTROL: m_pController->SelectControl( pElement, selectionMode ); break; case ANIMTREE_ITEM_COMPONENT: TransformComponent_t nComponentFlags = m_hGroups->GetItemComponentFlags( itemIndex ); m_pController->SelectControl( pElement, selectionMode, nComponentFlags ); break; } }
void CBaseAnimSetControlGroupPanel::OnControlSelectionChanged() { UpdateSelection(); }
void CBaseAnimSetControlGroupPanel::ExpandTreeToControl( const CDmElement *pSelection, TransformComponent_t nComponentFlags ) { m_hGroups->BuildTreeToControl( pSelection, nComponentFlags ); }
void CBaseAnimSetControlGroupPanel::OnTreeViewStartRangeSelection() { m_pController->SetRangeSelectionState( true ); }
void CBaseAnimSetControlGroupPanel::OnTreeViewFinishRangeSelection() { m_pController->SetRangeSelectionState( false ); }
void CBaseAnimSetControlGroupPanel::OnTreeViewItemSelected( int itemIndex, int replaceSelection ) { SelectAnimTreeItem( itemIndex, replaceSelection ? SELECTION_SET : SELECTION_ADD ); }
void CBaseAnimSetControlGroupPanel::OnTreeViewItemDeselected( int itemIndex ) { SelectAnimTreeItem( itemIndex, SELECTION_REMOVE ); }
void CBaseAnimSetControlGroupPanel::OnTreeViewItemSelectionCleared() { m_pController->ClearSelection(); }
void CBaseAnimSetControlGroupPanel::ChangeAnimationSetClip( CDmeFilmClip *pFilmClip ) { RebuildTree( false ); }
void CBaseAnimSetControlGroupPanel::OnControlsAddedOrRemoved() { RebuildTree( true ); }
//-----------------------------------------------------------------------------
// Purpose: Rebuild the tree view from the current control selection hierarchy
//-----------------------------------------------------------------------------
void CBaseAnimSetControlGroupPanel::RebuildTree( bool bRestoreExpansion ) { if ( bRestoreExpansion ) { CUtlVector< ElementExpansion_t > expandedNodes( 0, m_hGroups->GetItemCount() ); CollectExpandedItems( expandedNodes, m_hGroups->GetRootItemIndex() );
m_hGroups->SetSilentMode( true ); m_hGroups->RemoveAll(); m_hGroups->SetSilentMode( false ); m_hGroups->AddItem( new KeyValues( "root" ), -1 ); // add (invisible) root
m_hGroups->ExpandItem( m_hGroups->GetRootItemIndex(), true );
ExpandItems( expandedNodes );
UpdateSelection(); } else { m_hGroups->SetSilentMode( true ); m_hGroups->RemoveAll(); m_hGroups->SetSilentMode( false ); m_hGroups->AddItem( new KeyValues( "root" ), -1 ); // add (invisible) root
if ( CDmeFilmClip *pFilmClip = m_pController->GetAnimationSetClip() ) { CAnimSetGroupAnimSetTraversal traversal( pFilmClip ); while ( CDmeAnimationSet *pAnimSet = traversal.Next() ) { m_hGroups->AddAnimationSetToTree( pAnimSet ); } m_hGroups->ExpandItem( m_hGroups->GetRootItemIndex(), true ); } } }
// pre-order traversal so that we can ExpandItems linearly
void CBaseAnimSetControlGroupPanel::CollectExpandedItems( CUtlVector< ElementExpansion_t > &expandedNodes, int nItemIndex ) { if ( nItemIndex == m_hGroups->InvalidItemID() ) return;
AnimTreeItemType_t itemType; CDmElement *pElement = m_hGroups->GetTreeItemData( nItemIndex, &itemType );
if ( !m_hGroups->IsItemExpanded( nItemIndex ) ) return;
ElementExpansion_t *pExpansionInfo = NULL; if ( pElement ) { int nIndex = expandedNodes.AddToTail(); pExpansionInfo = &expandedNodes[ nIndex ]; pExpansionInfo->m_pElement = pElement; pExpansionInfo->m_ComponentFlags = TRANSFORM_COMPONENT_NONE; }
int nChildren = m_hGroups->GetNumChildren( nItemIndex ); for ( int i = 0; i < nChildren; ++i ) { int nChildIndex = m_hGroups->GetChild( nItemIndex, i ); AnimTreeItemType_t childItemType; m_hGroups->GetTreeItemData( nChildIndex, &childItemType ); if ( childItemType == ANIMTREE_ITEM_COMPONENT ) { if ( m_hGroups->IsItemExpanded( nChildIndex ) && pExpansionInfo ) { pExpansionInfo->m_ComponentFlags |= m_hGroups->GetItemComponentFlags( nChildIndex ); } } else { CollectExpandedItems( expandedNodes, nChildIndex ); }
} }
// assumes expandedNodes have parents before children (ie expandedNodes is a pre-order traversal)
void CBaseAnimSetControlGroupPanel::ExpandItems( const CUtlVector< ElementExpansion_t > &expandedNodes ) { int nExpandedNodes = expandedNodes.Count(); for ( int i = 0; i < nExpandedNodes; ++i ) { CDmElement *pElement = expandedNodes[ i ].m_pElement; int nItemIndex = m_hGroups->FindItemForElement( pElement ); if ( nItemIndex != m_hGroups->InvalidItemID() ) { m_hGroups->ExpandItem( nItemIndex, true );
TransformComponent_t expandedComponents = expandedNodes[ i ].m_ComponentFlags; if ( expandedComponents == TRANSFORM_COMPONENT_NONE ) continue;
int nChildren = m_hGroups->GetNumChildren( nItemIndex ); for ( int i = 0; i < nChildren; ++i ) { int nChildIndex = m_hGroups->GetChild( nItemIndex, i );
AnimTreeItemType_t childItemType; m_hGroups->GetTreeItemData( nChildIndex, &childItemType ); TransformComponent_t childComponents = m_hGroups->GetItemComponentFlags( nChildIndex ); if ( ( childItemType == ANIMTREE_ITEM_COMPONENT ) && ( childComponents & expandedComponents ) ) { m_hGroups->ExpandItem( nChildIndex, true ); } } } } }
void CBaseAnimSetControlGroupPanel::UpdateSelection() { if ( !m_hGroups ) return;
m_hGroups->SetSilentMode( true ); m_hGroups->ClearSelection(); m_hGroups->SetSilentMode( false ); UpdateSelection_R( m_hGroups->GetRootItemIndex() ); }
SelectionState_t CBaseAnimSetControlGroupPanel::UpdateSelection_R( int nParentIndex ) { if ( nParentIndex == m_hGroups->InvalidItemID() ) return SEL_EMPTY;
SelectionState_t selection = SEL_EMPTY;
int nChildren = m_hGroups->GetNumChildren( nParentIndex ); if ( nChildren > 0 ) { for ( int i = 0; i < nChildren; ++i ) { int nChildIndex = m_hGroups->GetChild( nParentIndex, i ); selection += UpdateSelection_R( nChildIndex ); } } else { // check actual controls
AnimTreeItemType_t itemType; CDmElement *pElement = m_hGroups->GetTreeItemData( nParentIndex, &itemType ); if ( !pElement ) return SEL_EMPTY;
switch ( itemType ) { case ANIMTREE_ITEM_ANIMSET: if ( CDmeAnimationSet *pAnimSet = CastElement< CDmeAnimationSet >( pElement ) ) { selection = m_pController->GetSelectionState( pAnimSet ); } break; case ANIMTREE_ITEM_GROUP: if ( CDmeControlGroup *pGroup = CastElement< CDmeControlGroup >( pElement ) ) { selection = m_pController->GetSelectionState( pGroup ); } break; case ANIMTREE_ITEM_CONTROL: selection = m_pController->GetSelectionState( pElement ); break; case ANIMTREE_ITEM_COMPONENT: TransformComponent_t nComponentFlags = m_hGroups->GetItemComponentFlags( nParentIndex ); selection = m_pController->GetSelectionState( pElement, nComponentFlags ); break; } }
m_hGroups->SetItemSelectionState( nParentIndex, selection );
if ( selection == SEL_SOME || selection == SEL_ALL ) { Color color = ( selection == SEL_ALL ) ? m_FullSelectionColor : m_PartialSelectionColor; m_hGroups->SetItemSelectionBgColor( nParentIndex, color ); m_hGroups->SetItemSelectionUnfocusedBgColor( nParentIndex, color );
m_hGroups->SetSilentMode( true ); m_hGroups->AddSelectedItem( nParentIndex, false, false, false ); m_hGroups->SetSilentMode( false ); }
return selection; }
CDmeDag *CBaseAnimSetControlGroupPanel::GetWorkCameraParent() { return m_pController->GetWorkCameraParent(); }
//-----------------------------------------------------------------------------
// Purpose: Handle the request of the tree view to create a context menu
//-----------------------------------------------------------------------------
void CBaseAnimSetControlGroupPanel::OnTreeViewOpenContextMenu( int itemID ) { if ( itemID >= 0 ) { m_hGroups->SetItemSelectionUnfocusedBgColor( itemID, m_ContextMenuHighlightColor ); }
KeyValues *pItemData = m_hGroups->GetItemData( itemID ); m_hEditor->OpenTreeViewContextMenu( pItemData ); }
//-----------------------------------------------------------------------------
// Create a new control group containing the selected controls
//-----------------------------------------------------------------------------
void CBaseAnimSetControlGroupPanel::CreateGroupFromSelectedControls() { CUtlVector< int > selectedItems; m_hGroups->GetSelectionRootItems( selectedItems );
int nNumSelectedItems = selectedItems.Count(); if ( nNumSelectedItems < 0 ) return;
CDmeControlGroup *pCommonAncestor = NULL;
for ( int iItem = 0; iItem < nNumSelectedItems; ++iItem ) { int nItemIndex = selectedItems[ iItem ];
AnimTreeItemType_t itemType; CDmeControlGroup *pParentControlGroup = NULL; m_hGroups->GetTreeItemData( nItemIndex, &itemType, NULL, &pParentControlGroup );
// Currently not allowed to group animation sets
if ( itemType == ANIMTREE_ITEM_ANIMSET ) return;
if ( pCommonAncestor ) { pCommonAncestor = pParentControlGroup->FindCommonAncestor( pCommonAncestor ); } else { pCommonAncestor = pParentControlGroup; }
// If any of the selected items do not have a common
// ancestor, do not allow the group to be created.
if ( pCommonAncestor == NULL ) return; }
// If the selected items did not share a common ancestor a new group cannot be created
if ( pCommonAncestor == NULL ) return;
// Generate a name for the new group which is unique among the children of the group it will belong to.
const CDmeControlGroup *pRootGroup = pCommonAncestor->FindRootControlGroup(); if ( pRootGroup == NULL) return;
CUtlVector< DmElementHandle_t > childList; pRootGroup->GetAllChildren( childList ); int nIndex = GenerateUniqueNameIndex( "group", childList, 1 ); CFmtStr groupName( "group%d", nIndex );
// Create the new group and make it a child of the common ancestor
CDmeControlGroup *pNewGroup = pCommonAncestor->CreateControlGroup( groupName );
// Add the selected items to the new group
if ( pNewGroup ) { for ( int iItem = 0; iItem < nNumSelectedItems; ++iItem ) { int nItemIndex = selectedItems[ iItem ]; AnimTreeItemType_t itemType; CDmElement *pElement = m_hGroups->GetTreeItemData( nItemIndex, &itemType );
if ( itemType == ANIMTREE_ITEM_GROUP ) { pNewGroup->AddChild( CastElement< CDmeControlGroup >( pElement ) ); } else if ( itemType == ANIMTREE_ITEM_CONTROL ) { pNewGroup->AddControl( pElement ); } } // Clean up any empty children which may have been left in the tree.
pCommonAncestor->DestroyEmptyChildren(); // Rebuild the tree so that it includes the new group
RebuildTree( true );
// Find the item corresponding to the new group and set it label editing mode.
int nNewItemIndex = m_hGroups->FindItemForElement( pNewGroup ); m_hGroups->MakeItemVisible( nNewItemIndex ); m_hGroups->StartEditingLabel( nNewItemIndex ); } }
|