summaryrefslogtreecommitdiff
path: root/vgui2/dme_controls/ElementPropertiesTree.cpp
diff options
context:
space:
mode:
authorFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
committerFluorescentCIAAfricanAmerican <[email protected]>2020-04-22 12:56:21 -0400
commit3bf9df6b2785fa6d951086978a3e66f49427166a (patch)
tree2c0f1f0c63c4832882bc93814ebd2c2b1c6224e5 /vgui2/dme_controls/ElementPropertiesTree.cpp
downloadarchived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.tar.xz
archived-source-engine-2018-hl2-src-3bf9df6b2785fa6d951086978a3e66f49427166a.zip
Diffstat (limited to 'vgui2/dme_controls/ElementPropertiesTree.cpp')
-rw-r--r--vgui2/dme_controls/ElementPropertiesTree.cpp4496
1 files changed, 4496 insertions, 0 deletions
diff --git a/vgui2/dme_controls/ElementPropertiesTree.cpp b/vgui2/dme_controls/ElementPropertiesTree.cpp
new file mode 100644
index 0000000..b953cd9
--- /dev/null
+++ b/vgui2/dme_controls/ElementPropertiesTree.cpp
@@ -0,0 +1,4496 @@
+//========= Copyright Valve Corporation, All rights reserved. ============//
+//
+// Purpose:
+//
+// $NoKeywords: $
+//
+//=============================================================================//
+
+#include "dme_controls/elementpropertiestree.h"
+#include "tier1/KeyValues.h"
+#include "datamodel/dmelement.h"
+
+#include "vgui/IInput.h"
+#include "vgui/ISurface.h"
+#include "vgui/ISystem.h"
+#include "vgui/IVgui.h"
+#include "vgui/Cursor.h"
+#include "vgui_controls/TextEntry.h"
+#include "vgui_controls/ComboBox.h"
+#include "vgui_controls/Button.h"
+#include "vgui_controls/FileOpenDialog.h"
+#include "vgui_controls/Menu.h"
+#include "vgui_controls/MenuItem.h"
+#include "vgui_controls/MenuButton.h"
+#include "vgui_controls/PanelListPanel.h"
+#include "vgui_controls/ScrollBar.h"
+#include "movieobjects/dmeeditortypedictionary.h"
+#include "dme_controls/AttributeTextPanel.h"
+#include "dme_controls/DmePanel.h"
+#include "dme_controls/dmecontrols_utils.h"
+#include "tier1/ConVar.h"
+#include "tier2/fileutils.h"
+
+// memdbgon must be the last include file in a .cpp file!!!
+#include "tier0/memdbgon.h"
+
+
+//-----------------------------------------------------------------------------
+// Forward declarations
+//-----------------------------------------------------------------------------
+class CElementTreeViewListControl;
+
+using namespace vgui;
+
+
+//-----------------------------------------------------------------------------
+//
+// CElementTree
+//
+//-----------------------------------------------------------------------------
+class CElementTree : public TreeView
+{
+ DECLARE_CLASS_SIMPLE( CElementTree, TreeView );
+public:
+ CElementTree( CElementPropertiesTreeInternal *parent, const char *panelName );
+ ~CElementTree();
+
+ virtual void OnCommand( const char *cmd );
+ virtual void ApplySchemeSettings( IScheme *pScheme );
+ virtual void InvalidateLayout( bool layoutNow = false, bool reloadScheme = false );
+ virtual void GenerateChildrenOfNode(int itemIndex);
+ // override to open a custom context menu on a node being selected and right-clicked
+ virtual void GenerateContextMenu( int itemIndex, int x, int y );
+
+ virtual void GenerateDragDataForItem( int itemIndex, KeyValues *msg );
+
+ virtual void OnLabelChanged( int itemIndex, const char *oldString, const char *newString );
+
+ virtual bool IsItemDroppable( int m_ItemIndex, CUtlVector< KeyValues * >& msglist );
+ virtual void OnItemDropped( int m_ItemIndex, CUtlVector< KeyValues * >& msglist );
+ virtual bool GetItemDropContextMenu( int itemIndex, Menu *menu, CUtlVector< KeyValues * >& msglist );
+ virtual HCursor GetItemDropCursor( int itemIndex, CUtlVector< KeyValues * >& msglist );
+
+ ScrollBar *GetScrollBar();
+
+private:
+ Menu *m_pEditMenu;
+ CElementPropertiesTreeInternal *m_pParent;
+ ScrollBar *m_pVertSB;
+};
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CElementTree::CElementTree( CElementPropertiesTreeInternal *parent, const char *panelName ) :
+ BaseClass( (Panel *)parent, panelName ),
+ m_pEditMenu( 0 ),
+ m_pParent( parent )
+{
+ SetAllowLabelEditing( true );
+ SetAllowMultipleSelections( true );
+
+ m_pVertSB = SetScrollBarExternal( true, this );
+}
+
+//-----------------------------------------------------------------------------
+// Destructor
+//-----------------------------------------------------------------------------
+CElementTree::~CElementTree()
+{
+ delete m_pEditMenu;
+}
+
+ScrollBar *CElementTree::GetScrollBar()
+{
+ return m_pVertSB;
+}
+
+bool CElementTree::IsItemDroppable( int itemIndex, CUtlVector< KeyValues * >& msglist )
+{
+ return m_pParent->IsItemDroppable( itemIndex, msglist );
+}
+
+bool CElementTree::GetItemDropContextMenu( int itemIndex, Menu *menu, CUtlVector< KeyValues * >& msglist )
+{
+ return m_pParent->GetItemDropContextMenu( itemIndex, menu, msglist );
+}
+
+void CElementTree::OnItemDropped( int itemIndex, CUtlVector< KeyValues * >& msglist )
+{
+ m_pParent->OnItemDropped( itemIndex, msglist );
+}
+
+HCursor CElementTree::GetItemDropCursor( int itemIndex, CUtlVector< KeyValues * >& msglist )
+{
+ return m_pParent->GetItemDropCursor( itemIndex, msglist );
+}
+
+void CElementTree::OnLabelChanged( int itemIndex, const char *oldString, const char *newString )
+{
+ m_pParent->OnLabelChanged( itemIndex, oldString, newString );
+}
+
+void CElementTree::GenerateDragDataForItem( int itemIndex, KeyValues *msg )
+{
+ m_pParent->GenerateDragDataForItem( itemIndex, msg );
+}
+
+// override to open a custom context menu on a node being selected and right-clicked
+void CElementTree::GenerateContextMenu( int itemIndex, int x, int y )
+{
+ m_pParent->GenerateContextMenu( itemIndex, x, y );
+}
+
+
+void CElementTree::ApplySchemeSettings( IScheme *pScheme )
+{
+ // Intentionally skip to Panel:: instead of BaseClass::!!!
+ Panel::ApplySchemeSettings( pScheme );
+
+ SetFont( pScheme->GetFont( "DmePropertyVerySmall", IsProportional() ) );
+}
+
+void CElementTree::InvalidateLayout( bool layoutNow, bool reloadScheme )
+{
+ BaseClass::InvalidateLayout( layoutNow, reloadScheme );
+ if ( GetParent() && !reloadScheme )
+ {
+ GetParent()->InvalidateLayout( layoutNow, false );
+ }
+}
+
+void CElementTree::OnCommand( const char *cmd )
+{
+ // Relay to parent
+ GetParent()->OnCommand( cmd );
+}
+
+void CElementTree::GenerateChildrenOfNode(int itemIndex)
+{
+ m_pParent->GenerateChildrenOfNode( itemIndex );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Class: CElementTreeViewListControl
+//
+//-----------------------------------------------------------------------------
+CElementTreeViewListControl::CElementTreeViewListControl( Panel *pParent, const char *pName )
+ : BaseClass( pParent, pName ), m_Panels( 0, 0, PanelsLessFunc )
+{
+
+ m_iTreeColumnWidth = 200;
+ m_iFontSize = 1;
+ m_bMouseLeftIsDown = false;
+ m_bMouseIsDragging = false;
+ m_bDrawGrid = false;
+
+ // why do this here?
+ SetScheme( vgui::scheme()->LoadSchemeFromFile( "Resource/BoxRocket.res", "BoxRocket" ) );
+
+ // the column lable font
+ vgui::IScheme *scheme = vgui::scheme()->GetIScheme( GetScheme() );
+ HFont font = scheme->GetFont( "DefaultVerySmall", IsProportional() );
+
+ SetTitleBarInfo( font, 18 );
+
+ SetPostChildPaintEnabled( true );
+ SetBorderColor( Color( 255, 255, 196, 64 ) );
+
+ SetKeyBoardInputEnabled( true );
+}
+
+
+int CElementTreeViewListControl::AddItem( KeyValues *data, bool allowLabelEditing, int parentItemIndex, CUtlVector< vgui::Panel * >& columnPanels )
+{
+ int itemIndex = GetTree()->AddItem( data, parentItemIndex );
+ if ( allowLabelEditing )
+ {
+ GetTree()->SetLabelEditingAllowed( itemIndex, allowLabelEditing );
+ }
+
+ GetTree()->SetItemFgColor( itemIndex, GetFgColor() );
+ GetTree()->SetItemBgColor( itemIndex, GetBgColor() );
+
+ ColumnPanels_t search;
+ search.treeViewItem = itemIndex;
+
+ int idx = m_Panels.Find( search );
+ if ( idx == m_Panels.InvalidIndex() )
+ {
+ ColumnPanels_t newInfo;
+ newInfo.treeViewItem = itemIndex;
+ idx = m_Panels.Insert( newInfo );
+ }
+
+ ColumnPanels_t& info = m_Panels[ idx ];
+
+ info.SetList( columnPanels );
+
+ int c = columnPanels.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ if ( columnPanels[ i ] )
+ {
+ columnPanels[ i ]->SetParent( this );
+ }
+ }
+
+// GetTree()->InvalidateLayout( false, true );
+
+ return itemIndex;
+}
+
+
+//-----------------------------------------------------------------------------
+// Removes an item recursively
+//-----------------------------------------------------------------------------
+void CElementTreeViewListControl::RemoveItem_R( int nItemIndex )
+{
+ ColumnPanels_t search;
+ search.treeViewItem = nItemIndex;
+ int idx = m_Panels.Find( search );
+ if ( idx != m_Panels.InvalidIndex() )
+ {
+ ColumnPanels_t& info = m_Panels[ idx ];
+ int nCount = info.m_Columns.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ if ( info.m_Columns[i] )
+ {
+ info.m_Columns[i]->SetParent( (Panel*)NULL );
+ info.m_Columns[i]->MarkForDeletion();
+ }
+ }
+ m_Panels.RemoveAt( idx );
+ }
+
+ int nCount = GetTree()->GetNumChildren( nItemIndex );
+ for ( int i = 0; i < nCount; ++i )
+ {
+ RemoveItem_R( GetTree()->GetChild( nItemIndex, i ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Removes an item
+//-----------------------------------------------------------------------------
+void CElementTreeViewListControl::RemoveItem( int nItemIndex )
+{
+ RemoveItem_R( nItemIndex );
+ GetTree()->RemoveItem( nItemIndex, false, true );
+ RecalculateRows();
+ InvalidateLayout();
+}
+
+
+int CElementTreeViewListControl::GetTreeColumnWidth()
+{
+ return m_iTreeColumnWidth;
+}
+
+void CElementTreeViewListControl::SetTreeColumnWidth(int w)
+{
+ m_iTreeColumnWidth = w;
+ SetColumnInfo( 0, "Tree", m_iTreeColumnWidth );
+}
+
+void CElementTreeViewListControl::ApplySchemeSettings( IScheme *pScheme )
+{
+ BaseClass::ApplySchemeSettings( pScheme );
+ GetTree()->SetFont( pScheme->GetFont( "DmePropertyVerySmall", IsProportional() ) );
+}
+
+
+//-----------------------------------------------------------------------------
+// Purpose: Handle mouse drag resize of tree column (hack)
+//-----------------------------------------------------------------------------
+void CElementTreeViewListControl::OnCursorMoved(int x, int y)
+{
+
+ if ( ( x > m_iTreeColumnWidth - 12 ) &&
+ ( x < m_iTreeColumnWidth + 12 ) )
+ {
+ SetCursor( dc_sizewe );
+ if ( m_bMouseLeftIsDown )
+ {
+ m_bMouseIsDragging = true;
+ }
+ }
+ else
+ {
+ SetCursor( dc_arrow );
+ }
+
+ if ( m_bMouseIsDragging )
+ {
+ SetCursor( dc_sizewe );
+ SetTreeColumnWidth( x );
+ InvalidateLayout( true );
+ }
+
+}
+
+void CElementTreeViewListControl::OnMousePressed( MouseCode code )
+{
+ BaseClass::OnMousePressed( code );
+ if ( code == MOUSE_LEFT )
+ {
+ m_bMouseLeftIsDown = true;
+ }
+ input()->SetMouseCapture(GetVPanel());
+ RequestFocus();
+}
+
+void CElementTreeViewListControl::OnMouseReleased( MouseCode code )
+{
+ BaseClass::OnMouseReleased( code );
+ if ( code == MOUSE_LEFT )
+ {
+ m_bMouseLeftIsDown = false;
+ m_bMouseIsDragging = false;
+ }
+ input()->SetMouseCapture(NULL);
+}
+
+void CElementTreeViewListControl::OnMouseDoublePressed( MouseCode code )
+{
+ int x, y;
+ input()->GetCursorPos(x, y);
+ ScreenToLocal(x, y);
+
+ // resize the column to the max width of the tree
+ if ( ( x > m_iTreeColumnWidth - 12 ) &&
+ ( x < m_iTreeColumnWidth + 12 ) )
+ {
+ ResizeTreeToExpandedWidth();
+ }
+
+ BaseClass::OnMouseDoublePressed( code );
+
+}
+
+void CElementTreeViewListControl::ResizeTreeToExpandedWidth()
+{
+ int rows = GetNumRows();
+ int vbarTop, nItemsVisible;
+ bool hbarVisible = false;
+ GetTree()->GetVBarInfo( vbarTop, nItemsVisible, hbarVisible );
+ int vBarWidth = 0;
+ if ( nItemsVisible <= rows )
+ {
+ vBarWidth = 27;
+ }
+ SetTreeColumnWidth( GetTree()->GetVisibleMaxWidth() + vBarWidth + 14 );
+}
+
+void CElementTreeViewListControl::OnMouseWheeled(int delta)
+{
+
+ bool ctrl = (input()->IsKeyDown(KEY_LCONTROL) || input()->IsKeyDown(KEY_RCONTROL));
+ bool alt = (input()->IsKeyDown(KEY_LALT) || input()->IsKeyDown(KEY_RALT));
+
+ if ( ctrl )
+ {
+ SetFontSize( GetFontSize() + delta );
+ }
+ else if ( alt )
+ {
+ ToggleDrawGrid();
+ }
+ else
+ {
+ // scroll the treeview control
+ ScrollBar *sb = ((CElementTree *)GetTree())->GetScrollBar();
+ sb->SetValue( sb->GetValue() + ( delta * -3 ) );
+ }
+}
+
+void CElementTreeViewListControl::ToggleDrawGrid()
+{
+ m_bDrawGrid = !m_bDrawGrid;
+}
+
+bool CElementTreeViewListControl::IsDrawingGrid()
+{
+ return m_bDrawGrid;
+}
+
+void CElementTreeViewListControl::PostChildPaint()
+{
+ // why isn't SetBorderColor doing the job???
+ vgui::surface()->DrawSetColor( Color( 255, 255, 196, 32 ) );
+
+ int left, top, right, bottom;
+ int wide, tall;
+ GetSize( wide, tall );
+ GetGridElementBounds( 0, 0, left, top, right, bottom );
+ vgui::surface()->DrawFilledRect( right, 1, right+3, tall );
+
+ if ( m_bDrawGrid )
+ {
+ int numColumns = GetNumColumns();
+ int rows = GetNumRows();
+
+ int vbarTop, nItemsVisible;
+ bool hbarVisible = false;
+ GetTree()->GetVBarInfo( vbarTop, nItemsVisible, hbarVisible );
+
+ int vBarWidth = 0;
+ if ( nItemsVisible <= rows )
+ {
+ vBarWidth = 21;
+ }
+
+ if ( hbarVisible )
+ {
+ --nItemsVisible;
+ }
+
+ for ( int col = 0; col < numColumns; ++col )
+ {
+ for ( int row = 0; row < rows; ++row )
+ {
+ GetGridElementBounds( col, row, left, top, right, bottom );
+ if (col == 0)
+ {
+ vgui::surface()->DrawLine( left+4, bottom, right, bottom );
+ }
+ else
+ {
+ vgui::surface()->DrawLine( left-3, bottom, right-2, bottom );
+ }
+ }
+ }
+ }
+}
+
+int CElementTreeViewListControl::GetScrollBarSize()
+{
+ ScrollBar *sb = ((CElementTree *)GetTree())->GetScrollBar();
+ if ( sb )
+ {
+ return sb->GetWide();
+ }
+ return 0;
+}
+
+void CElementTreeViewListControl::PerformLayout()
+{
+ BaseClass::PerformLayout();
+
+ // Assume all invisible at first
+ HideAll();
+
+ GetTree()->PerformLayout();
+
+ ScrollBar *sb = ((CElementTree *)GetTree())->GetScrollBar();
+ if ( sb && sb->GetParent() )
+ {
+ sb->SetBounds( sb->GetParent()->GetWide() - sb->GetWide(), 0, sb->GetWide(), sb->GetParent()->GetTall() );
+ }
+
+ int rowheight = GetTree()->GetRowHeight();
+ int treetop, visitems;
+ bool hbarVisible = false;
+ GetTree()->GetVBarInfo( treetop, visitems, hbarVisible );
+ if ( hbarVisible )
+ {
+ --visitems;
+ }
+
+ int offset = -treetop * rowheight;
+
+ int headerHeight = GetTitleBarHeight();
+
+ int numColumns = GetNumColumns();
+ // Now position column panels into the correct spot
+ int rows = GetNumRows();
+ int visItemCount = 0;
+
+ for ( int row = 0; row < rows; ++row )
+ {
+ int tvi = GetTreeItemAtRow( row );
+
+ for ( int col = 0; col < numColumns; ++col )
+ {
+ int left, top, right, bottom;
+ GetGridElementBounds( col, row, left, top, right, bottom );
+
+ ColumnPanels_t search;
+ search.treeViewItem = tvi;
+
+ int idx = m_Panels.Find( search );
+ if ( idx != m_Panels.InvalidIndex() )
+ {
+ ColumnPanels_t& info = m_Panels[ idx ];
+
+ if ( col >= info.m_Columns.Count() )
+ continue;
+
+ vgui::Panel *p = info.m_Columns[ col ];
+ if ( !p )
+ {
+ continue;
+ }
+
+ bool vis = top + offset >= headerHeight;
+ if ( vis )
+ {
+ ++visItemCount;
+
+ if ( visItemCount > visitems )
+ {
+ vis = false;
+ }
+ }
+
+ p->SetVisible( vis );
+ p->SetBounds( left + 4, top + offset, right - left, bottom - top );
+
+ p->InvalidateLayout();
+ }
+ else
+ {
+ Assert( 0 );
+ }
+ }
+ }
+}
+
+void CElementTreeViewListControl::HideAll()
+{
+ for ( int i = m_Panels.FirstInorder(); i != m_Panels.InvalidIndex(); i = m_Panels.NextInorder( i ) )
+ {
+ ColumnPanels_t& info = m_Panels[ i ];
+ int c = info.m_Columns.Count();
+ for ( int j = 0 ; j < c; ++j )
+ {
+ Panel *panel = info.m_Columns[ j ];
+ if ( !panel )
+ {
+ continue;
+ }
+ panel->SetVisible( false );
+ }
+ }
+}
+
+void CElementTreeViewListControl::RemoveAll()
+{
+ GetTree()->RemoveAll();
+
+ for ( int i = m_Panels.FirstInorder(); i != m_Panels.InvalidIndex(); i = m_Panels.NextInorder( i ) )
+ {
+ ColumnPanels_t& info = m_Panels[ i ];
+ int c = info.m_Columns.Count();
+ for ( int j = 0 ; j < c; ++j )
+ {
+ delete info.m_Columns[ j ];
+ }
+ info.m_Columns.RemoveAll();
+ }
+ m_Panels.RemoveAll();
+ InvalidateLayout();
+}
+
+HFont CElementTreeViewListControl::GetFont( int size )
+{
+ vgui::IScheme *scheme = vgui::scheme()->GetIScheme( GetScheme() );
+
+ switch(size)
+ {
+ case 1:
+ return scheme->GetFont( "DmePropertyVerySmall", IsProportional() );
+ case 2:
+ return scheme->GetFont( "DmePropertySmall", IsProportional() );
+ case 3:
+ return scheme->GetFont( "DmeProperty", IsProportional() );
+ case 4:
+ return scheme->GetFont( "DmePropertyLarge", IsProportional() );
+ case 5:
+ return scheme->GetFont( "DmePropertyVeryLarge", IsProportional() );
+ default:
+ return NULL;
+ }
+}
+
+void CElementTreeViewListControl::SetFont( HFont font )
+{
+ // set the font for the tree
+ GetTree()->SetFont( font );
+
+ // and now set the font on the data column...
+ for ( int i = m_Panels.FirstInorder(); i != m_Panels.InvalidIndex(); i = m_Panels.NextInorder( i ) )
+ {
+ ColumnPanels_t& info = m_Panels[ i ];
+ int c = info.m_Columns.Count();
+ for ( int j = 0 ; j < c; ++j )
+ {
+ Panel *panel = info.m_Columns[ j ];
+ if ( !panel )
+ {
+ continue;
+ }
+
+ CBaseAttributePanel *attrPanel = dynamic_cast< CBaseAttributePanel * >( panel );
+ if ( !attrPanel )
+ {
+ continue;
+ }
+ attrPanel->SetFont( font );
+ }
+ }
+}
+
+int CElementTreeViewListControl::GetFontSize()
+{
+ return m_iFontSize;
+}
+
+void CElementTreeViewListControl::SetFontSize( int size )
+{
+ m_iFontSize = min( 5, max( 1, size ) );
+ SetFont( GetFont( m_iFontSize ) );
+}
+
+void CElementTreeViewListControl::ExpandItem(int itemIndex, bool bExpand)
+{
+ GetTree()->ExpandItem( itemIndex, bExpand );
+}
+
+bool CElementTreeViewListControl::IsItemExpanded( int itemIndex )
+{
+ return GetTree()->IsItemExpanded( itemIndex );
+}
+
+bool CElementTreeViewListControl::IsItemSelected( int itemIndex )
+{
+ return GetTree()->IsItemSelected( itemIndex );
+}
+
+
+KeyValues *CElementTreeViewListControl::GetItemData(int itemIndex)
+{
+ return GetTree()->GetItemData( itemIndex );
+}
+
+
+class CHistoryMenuButton : public MenuButton
+{
+DECLARE_CLASS_SIMPLE( CHistoryMenuButton, MenuButton );
+public:
+ CHistoryMenuButton( Panel *parent, const char *panelName, const char *text, CElementPropertiesTreeInternal *tree, int whichMenu );
+
+ virtual void OnShowMenu( Menu *menu );
+ virtual int OnCheckMenuItemCount();
+
+
+private:
+ CElementPropertiesTreeInternal *m_pPropertiesTreeInternal;
+ int m_nWhichMenu;
+};
+
+CHistoryMenuButton::CHistoryMenuButton( Panel *parent, const char *panelName, const char *text, CElementPropertiesTreeInternal *tree, int whichMenu )
+ : BaseClass( parent, panelName, text ), m_pPropertiesTreeInternal( tree ), m_nWhichMenu( whichMenu )
+{
+ Assert( m_pPropertiesTreeInternal );
+}
+
+int CHistoryMenuButton::OnCheckMenuItemCount()
+{
+ Assert( m_pPropertiesTreeInternal );
+ if ( !m_pPropertiesTreeInternal )
+ return 0;
+
+ return m_pPropertiesTreeInternal->GetHistoryMenuItemCount( m_nWhichMenu );
+}
+
+void CHistoryMenuButton::OnShowMenu( Menu *menu )
+{
+ Assert( m_pPropertiesTreeInternal );
+ if ( !m_pPropertiesTreeInternal )
+ return;
+
+ m_pPropertiesTreeInternal->PopulateHistoryMenu( m_nWhichMenu, menu );
+}
+
+class CSearchComboBox : public ComboBox
+{
+ DECLARE_CLASS_SIMPLE( CSearchComboBox, ComboBox );
+public:
+
+ CSearchComboBox( CElementPropertiesTreeInternal *tree, vgui::Panel *parent, const char *panelName, int numLines, bool allowEdit );
+
+ virtual void OnMenuItemSelected();
+ virtual void OnShowMenu(Menu *menu);
+
+private:
+
+ CElementPropertiesTreeInternal *m_pTree;
+};
+
+CSearchComboBox::CSearchComboBox( CElementPropertiesTreeInternal *tree, vgui::Panel *parent, const char *panelName, int numLines, bool allowEdit )
+ : BaseClass( parent, panelName, numLines, allowEdit ), m_pTree( tree )
+{
+ Assert( m_pTree );
+}
+
+void CSearchComboBox::OnShowMenu(Menu *menu)
+{
+ menu->DeleteAllItems();
+ Assert( m_pTree );
+ if ( m_pTree )
+ {
+ m_pTree->PopulateHistoryMenu( CElementPropertiesTreeInternal::DME_PROPERTIESTREE_MENU_SEARCHHSITORY, menu );
+ }
+}
+
+void CSearchComboBox::OnMenuItemSelected()
+{
+ BaseClass::OnMenuItemSelected();
+
+ int idx = GetActiveItem();
+ if ( idx < 0 )
+ return;
+
+ char name[ 256 ];
+ GetItemText( idx, name, sizeof( name ) );
+
+ Assert( m_pTree );
+ if ( m_pTree && name[ 0 ] )
+ {
+ m_pTree->OnNavSearch( name );
+ }
+}
+
+class CPropertiesTreeToolbar : public Panel
+{
+ DECLARE_CLASS_SIMPLE( CPropertiesTreeToolbar, Panel );
+public:
+ CPropertiesTreeToolbar( vgui::Panel *parent, const char *panelName, CElementPropertiesTreeInternal *tree );
+
+ virtual void ApplySchemeSettings( IScheme *scheme );
+
+ virtual void PerformLayout();
+
+ MESSAGE_FUNC( OnTextNewLine, "TextNewLine" );
+
+ virtual void OnKeyCodeTyped( KeyCode code );
+
+ void UpdateButtonState();
+private:
+
+ CElementPropertiesTreeInternal *m_pTree;
+
+ CHistoryMenuButton *m_pBack;
+ CHistoryMenuButton *m_pFwd;
+ Label *m_pSearchLabel;
+ CSearchComboBox *m_pSearch;
+ // Button *m_pShowSearchResults;
+};
+
+CPropertiesTreeToolbar::CPropertiesTreeToolbar( vgui::Panel *parent, const char *panelName, CElementPropertiesTreeInternal *tree ) :
+ BaseClass( parent, panelName ), m_pTree( tree )
+{
+ Assert( m_pTree );
+
+ SetPaintBackgroundEnabled( false );
+
+ m_pBack = new CHistoryMenuButton( this, "Nav_Back", "#Dme_NavBack", tree, CElementPropertiesTreeInternal::DME_PROPERTIESTREE_MENU_BACKWARD );
+ m_pBack->SetCommand( new KeyValues( "OnNavigateBack", "item", -1 ) );
+ m_pBack->AddActionSignalTarget( parent );
+ m_pBack->SetDropMenuButtonStyle( true );
+
+ m_pBack->SetMenu( new Menu( this, "Nav_BackMenu" ) );
+
+ m_pFwd = new CHistoryMenuButton( this, "Nav_Forward", "#Dme_NavForward", tree, CElementPropertiesTreeInternal::DME_PROPERTIESTREE_MENU_FORWARD );
+ m_pFwd->SetCommand( new KeyValues( "OnNavigateForward", "item", -1 ) );
+ m_pFwd->AddActionSignalTarget( parent );
+ m_pFwd->SetDropMenuButtonStyle( true );
+ m_pFwd->SetMenu( new Menu( this, "Nav_FwdMenu" ) );
+
+ m_pSearch = new CSearchComboBox( tree, this, "Nav_Search", 20, true );
+ m_pSearch->SendNewLine( true );
+ m_pSearch->SelectAllOnFocusAlways( true );
+ m_pSearch->AddActionSignalTarget( this );
+
+ /*
+ m_pShowSearchResults = new Button( this, "Nav_ShowResults", "Show Results" );
+ m_pShowSearchResults->SetCommand( new KeyValues( "OnShowSearchResults" ) );
+ m_pShowSearchResults->AddActionSignalTarget( parent );
+ */
+
+ m_pSearchLabel = new Label( this, "Nav_SearchLabel", "#Dme_NavSearch" );
+}
+
+void CPropertiesTreeToolbar::UpdateButtonState()
+{
+ m_pBack->SetEnabled( m_pTree->GetHistoryMenuItemCount( CElementPropertiesTreeInternal::DME_PROPERTIESTREE_MENU_BACKWARD ) > 0 ? true : false );
+ m_pFwd->SetEnabled( m_pTree->GetHistoryMenuItemCount( CElementPropertiesTreeInternal::DME_PROPERTIESTREE_MENU_FORWARD ) > 0 ? true : false );
+
+ //m_pShowSearchResults->SetEnabled( m_pTree->GetHistoryMenuItemCount( CElementPropertiesTreeInternal::DME_PROPERTIESTREE_MENU_SEARCHHSITORY ) > 0 ? true : false );
+}
+
+void CPropertiesTreeToolbar::OnTextNewLine()
+{
+ Panel *parent = GetParent();
+ Assert( parent );
+ if ( !parent )
+ return;
+
+ char searchBuf[ 256 ];
+ m_pSearch->GetText( searchBuf, sizeof( searchBuf ) );
+
+ KeyValues *msg = new KeyValues( "OnNavigateSearch", "text", searchBuf );
+
+ PostMessage( parent, msg );
+}
+
+void CPropertiesTreeToolbar::OnKeyCodeTyped( KeyCode code )
+{
+ switch ( code )
+ {
+ case KEY_F3:
+ {
+ bool shift = (input()->IsKeyDown(KEY_LSHIFT) || input()->IsKeyDown(KEY_RSHIFT));
+
+ Panel *parent = GetParent();
+ Assert( parent );
+ if ( parent )
+ {
+ KeyValues *msg = new KeyValues( "OnNavigateSearchAgain", "direction", shift ? -1 : 1 );
+ PostMessage( parent, msg );
+ }
+ }
+ break;
+ default:
+ BaseClass::OnKeyCodeTyped( code );
+ break;
+ }
+}
+
+void CPropertiesTreeToolbar::ApplySchemeSettings( IScheme *scheme )
+{
+ BaseClass::ApplySchemeSettings( scheme );
+
+ m_pBack->SetFont( scheme->GetFont( "DefaultVerySmall" ) );
+ m_pFwd->SetFont( scheme->GetFont( "DefaultVerySmall" ) );
+ m_pSearch->SetFont( scheme->GetFont( "DefaultVerySmall" ) );
+ m_pSearchLabel->SetFont( scheme->GetFont( "DefaultVerySmall" ) );
+ //m_pShowSearchResults->SetFont( scheme->GetFont( "DefaultVerySmall" ) );
+
+ m_pSearch->SendNewLine( true );
+ m_pSearch->SelectAllOnFocusAlways( true );
+
+ m_pBack->GetMenu()->SetFont( scheme->GetFont( "DefaultVerySmall" ) );
+ m_pFwd->GetMenu()->SetFont( scheme->GetFont( "DefaultVerySmall" ) );
+}
+
+void CPropertiesTreeToolbar::PerformLayout()
+{
+ BaseClass::PerformLayout();
+ int w, h;
+ GetSize( w, h );
+
+ int buttonw = 75;
+ int buttonh = h - 6;
+
+ int x = 2;
+
+ m_pBack->SetBounds( x, 3, buttonw, buttonh );
+
+ x += buttonw + 2;
+
+ m_pFwd->SetBounds( x, 3, buttonw, buttonh );
+
+ x += buttonw + 15;
+
+ m_pSearchLabel->SetBounds( x, 2, 50, buttonh );
+
+ x += 50 + 2;
+
+ int textw = ( w - 2 ) - x;
+
+ //textw -= 75;
+
+ m_pSearch->SetBounds( x, 2, textw, buttonh );
+
+ //x += textw;
+
+ //m_pShowSearchResults->SetBounds( x, 2, 75, buttonh );
+
+}
+
+//-----------------------------------------------------------------------------
+//
+// CElementPropertiesTreeInternal
+//
+//-----------------------------------------------------------------------------
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+CElementPropertiesTreeInternal::CElementPropertiesTreeInternal(
+ vgui::Panel *parent, IDmNotify *pNotify, CDmElement *pObject, bool autoApply /* = true */, CDmeEditorTypeDictionary *pDict /* = NULL */ ) :
+ BaseClass( parent, "ElementPropertiesTree" ),
+ m_pNotify( pNotify ),
+ m_hTypeDictionary( pDict ),
+ m_bAutoApply( autoApply ), m_bShowMemoryUsage( false )
+{
+ m_hObject = pObject;
+ m_bSuppressHistoryUpdates = false;
+ m_nCurrentHistoryPosition = 0;
+ m_szSearchStr[ 0 ] = 0;
+ m_nCurrentSearchResult = 0;
+
+ SetVisible( true );
+
+ Assert( m_pNotify );
+
+ CElementTree *dmeTree = new CElementTree( this, "ElementTree" );
+ dmeTree->SetDragEnabledItems( true );
+
+ m_pTree = new CElementTreeViewListControl( this, "ElementTreeList" );
+ m_pTree->SetTreeView( dmeTree );
+ m_pTree->SetNumColumns( 2 );
+ m_pTree->SetColumnInfo( 0, "Tree", m_pTree->GetTreeColumnWidth() );
+ m_pTree->SetColumnInfo( 1, "Data", 1600 );
+
+ m_pToolBar = new CPropertiesTreeToolbar( this, "ElementTreeToolbar", this );
+ // m_pToolBar->SetTreeView( dmeTree );
+
+ ScrollBar *sb = dmeTree->GetScrollBar();
+ if ( sb )
+ {
+ sb->SetParent( m_pTree );
+ }
+
+ SETUP_PANEL( dmeTree );
+ SETUP_PANEL( m_pTree );
+ SETUP_PANEL( m_pToolBar );
+
+ {
+ CDmElement *pResults = CreateElement< CDmElement >( "Search Results", DMFILEID_INVALID );
+ Assert( pResults );
+ pResults->AddAttributeElementArray< CDmElement >( "results" );
+ m_SearchResultsRoot = pResults;
+ }
+
+ LoadControlSettings( "resource/BxElementPropertiesTree.res" );
+
+ m_hDragCopyCursor = surface()->CreateCursorFromFile( "resource/drag_copy.cur" );
+ m_hDragLinkCursor = surface()->CreateCursorFromFile( "resource/drag_link.cur" );
+ m_hDragMoveCursor = surface()->CreateCursorFromFile( "resource/drag_move.cur" );
+}
+
+
+//-----------------------------------------------------------------------------
+// Destructor
+//-----------------------------------------------------------------------------
+CElementPropertiesTreeInternal::~CElementPropertiesTreeInternal()
+{
+ if ( m_SearchResultsRoot.Get() )
+ {
+ g_pDataModel->DestroyElement( m_SearchResultsRoot );
+ }
+}
+
+void CElementPropertiesTreeInternal::UpdateButtonState()
+{
+ m_pToolBar->UpdateButtonState();
+}
+
+
+//-----------------------------------------------------------------------------
+// Message sent when something changed the element you're looking at
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::OnElementChangedExternally( int valuesOnly )
+{
+ Refresh( valuesOnly ? REFRESH_VALUES_ONLY : REFRESH_TREE_VIEW );
+}
+
+
+//-----------------------------------------------------------------------------
+// Sets the type dictionary
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::SetTypeDictionary( CDmeEditorTypeDictionary *pDict )
+{
+ m_hTypeDictionary = pDict;
+}
+
+
+//-----------------------------------------------------------------------------
+// Initialization of the tree
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::Init( )
+{
+ if ( !m_hObject.Get() )
+ return;
+
+ UpdateTree();
+}
+
+
+//-----------------------------------------------------------------------------
+// Applies changes to all attributes
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::ApplyChanges()
+{
+ Assert( !m_bAutoApply );
+
+ if ( !m_hObject.Get() )
+ return;
+
+ int nCount = m_AttributeWidgets.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ attributewidgetfactorylist->ApplyChanges( m_AttributeWidgets[i].m_pValueWidget, this );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Refreshes all attributes
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::Refresh( RefreshType_t rebuild /* = false */, bool preservePrevSelectedItem /*= false*/ )
+{
+ if ( !m_hObject.Get() )
+ return;
+
+ if ( rebuild == REFRESH_REBUILD )
+ {
+ SetObject( m_hObject.Get() );
+ return;
+ }
+
+ if ( rebuild != REFRESH_VALUES_ONLY )
+ {
+ RefreshTreeView( preservePrevSelectedItem );
+ }
+ else
+ {
+ RefreshTreeItemState( m_pTree->GetTree()->GetRootItemIndex() );
+ }
+ int nCount = m_AttributeWidgets.Count();
+ for ( int i = 0; i < nCount; ++i )
+ {
+ attributewidgetfactorylist->Refresh( m_AttributeWidgets[i].m_pValueWidget, this );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Purpose: Start editing label, in place
+// Input : -
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::OnRename()
+{
+ if ( m_pTree->GetTree()->GetSelectedItemCount() != 1 )
+ return;
+
+ m_pTree->GetTree()->StartEditingLabel( m_pTree->GetTree()->GetFirstSelectedItem() );
+}
+
+void CElementPropertiesTreeInternal::OnCopy()
+{
+ CUtlVector< int > selected;
+ m_pTree->GetTree()->GetSelectedItems( selected ) ;
+ int c = selected.Count();
+ if ( c <= 0 )
+ return;
+
+ // add in reverse order, since selection[0] is the last item selected
+ CUtlVector< KeyValues * > list;
+ for ( int i = c - 1; i >= 0; --i )
+ {
+ KeyValues *data = new KeyValues( "Clipboard" );
+ m_pTree->GetTree()->GenerateDragDataForItem( selected[ i ], data );
+ list.AddToTail( data );
+ }
+
+ if ( list.Count() > 0 )
+ {
+ g_pDataModel->SetClipboardData( list );
+ }
+}
+
+void CElementPropertiesTreeInternal::GetPathToItem( CUtlVector< TreeItem_t > &path, int itemIndex )
+{
+ for ( int idx = itemIndex; idx != m_pTree->GetTree()->GetRootItemIndex(); idx = m_pTree->GetTree()->GetItemParent( idx ) )
+ {
+ KeyValues *itemData = m_pTree->GetTree()->GetItemData( idx );
+ bool isArrayElement = !itemData->IsEmpty( "arrayIndex" );
+
+ TreeItem_t treeitem;
+ treeitem.m_pElement = GetElementKeyValue< CDmElement >( itemData, "ownerelement" );
+ treeitem.m_pAttributeName = itemData->GetString( "attributeName", "" );
+ treeitem.m_pArrayElement = isArrayElement ? GetElementKeyValue< CDmElement >( itemData, "dmeelement" ) : NULL;
+ path.AddToTail( treeitem );
+ }
+}
+
+int CElementPropertiesTreeInternal::OpenPath( const CUtlVector< TreeItem_t > &path )
+{
+ bool bFound = false;
+
+ int itemIndex = m_pTree->GetTree()->GetRootItemIndex();
+ int nPathItems = path.Count();
+ for ( int i = 0; i < nPathItems; ++i )
+ {
+ const TreeItem_t &childTreeItem = path[ i ];
+
+ bFound = false;
+
+ int nChildren = m_pTree->GetTree()->GetNumChildren( itemIndex );
+ for ( int i = 0; i < nChildren; ++i )
+ {
+ int nChildIndex = m_pTree->GetTree()->GetChild( itemIndex, i );
+ KeyValues *childData = m_pTree->GetTree()->GetItemData( nChildIndex );
+
+ bool isArrayElement = !childData->IsEmpty( "arrayIndex" );
+ CDmElement *pOwnerElement = GetElementKeyValue< CDmElement >( childData, "ownerelement" );
+ const char *pAttributeName = childData->GetString( "attributeName", "" );
+ CDmAttribute *pAttribute = pOwnerElement->GetAttribute( pAttributeName );
+
+ if ( isArrayElement )
+ {
+ Assert( childTreeItem.m_pArrayElement );
+ Assert( !V_strcmp( childTreeItem.m_pAttributeName, pAttributeName ) );
+ int nArrayIndex = childData->GetInt( "arrayIndex", -1 );
+ const CDmrElementArray<> array( pAttribute );
+ if ( nArrayIndex >= 0 && array[ nArrayIndex ] == childTreeItem.m_pArrayElement )
+ {
+ bFound = true;
+ itemIndex = nChildIndex;
+ break;
+ }
+ }
+ else
+ {
+ Assert( !childTreeItem.m_pArrayElement );
+ if ( !V_strcmp( childTreeItem.m_pAttributeName, pAttributeName ) )
+ {
+ bFound = true;
+ itemIndex = nChildIndex;
+ break;
+ }
+ }
+ }
+
+ if ( !bFound )
+ return -1;
+ }
+
+ return bFound ? itemIndex : -1;
+}
+
+void CElementPropertiesTreeInternal::OnPaste_( bool reference )
+{
+ CUtlVector< int > selected;
+ m_pTree->GetTree()->GetSelectedItems( selected ) ;
+ int c = selected.Count();
+ if ( !c )
+ return;
+
+ // Just choose first item for now
+ int itemIndex = selected[ 0 ];
+ KeyValues *itemData = m_pTree->GetItemData( itemIndex );
+ if ( !itemData )
+ return;
+
+ const char *elementType = itemData->GetString( "droppableelementtype" );
+ if ( !elementType || !elementType[ 0 ] )
+ return;
+
+ bool isArrayElement = !itemData->IsEmpty( "arrayIndex" );
+
+ //Check to see if this attribute refers to an element
+ CDmAttribute *pAttribute = ElementTree_GetAttribute( itemData );
+ if ( !pAttribute )
+ return;
+
+ DmAttributeType_t attType = pAttribute ? pAttribute->GetType() : AT_UNKNOWN;
+ bool isElementAttribute = attType == AT_ELEMENT || attType == AT_ELEMENT_ARRAY;
+ if ( !isElementAttribute )
+ return;
+
+ // get source data that will be pasted
+ CUtlVector< KeyValues * > msglist;
+ g_pDataModel->GetClipboardData( msglist );
+
+ CUtlVector< CDmElement * > list;
+ ElementTree_GetDroppableItems( msglist, "dmeelement", list );
+ if ( !list.Count() )
+ return;
+
+ // Pasting after an element array item or at the end of an element array
+ if ( isArrayElement || attType == AT_ELEMENT_ARRAY )
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, reference ? "Paste Reference" : "Paste" );
+
+ CDmrElementArray<> array( pAttribute );
+ int nArrayIndex = isArrayElement ? itemData->GetInt( "arrayIndex" ) + 1 : array.Count();
+ DropItemsIntoArray( array, msglist, list, nArrayIndex, reference ? DO_LINK : DO_COPY );
+ }
+ // Pasting onto an element attribute
+ else
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, reference ? "Paste Reference" : "Paste" );
+
+ pAttribute->SetValue( reference ? list[ 0 ] : list[ 0 ]->Copy() );
+ }
+
+ CUtlVector< TreeItem_t > dropTargetPath;
+ if ( isArrayElement )
+ {
+ itemIndex = m_pTree->GetTree()->GetItemParent( itemIndex ); // if we're an array element, start with the array itself
+ }
+ GetPathToItem( dropTargetPath, itemIndex );
+
+ // Does a forced refresh
+ Refresh( REFRESH_TREE_VIEW );
+
+ itemIndex = OpenPath( dropTargetPath );
+ if ( attType == AT_ELEMENT_ARRAY )
+ {
+ m_pTree->GetTree()->ExpandItem( itemIndex, true );
+ }
+}
+
+void CElementPropertiesTreeInternal::OnPaste()
+{
+ Warning( "CElementPropertiesTreeInternal::OnPaste\n" );
+ OnPaste_( false );
+}
+
+void CElementPropertiesTreeInternal::OnPasteReference()
+{
+ Warning( "CElementPropertiesTreeInternal::OnPasteReference\n" );
+ OnPaste_( true );
+}
+
+void CElementPropertiesTreeInternal::OnPasteInsert()
+{
+ Warning( "CElementPropertiesTreeInternal::OnPasteInsert\n" );
+}
+
+void RemoveAllReferencesToElement( CDmElement *pElement )
+{
+ if ( pElement == NULL )
+ return;
+
+ for ( DmElementHandle_t hElement = g_pDataModel->FirstAllocatedElement();
+ hElement != DMELEMENT_HANDLE_INVALID;
+ hElement = g_pDataModel->NextAllocatedElement( hElement ) )
+ {
+ CDmElement *pElt = g_pDataModel->GetElement( hElement );
+ if ( pElt )
+ {
+ pElt->RemoveAllReferencesToElement( pElement );
+ }
+ }
+}
+
+void CElementPropertiesTreeInternal::OnDeleteSelected()
+{
+ CUtlVector< KeyValues * > selection;
+ m_pTree->GetTree()->GetSelectedItemData( selection );
+ int nSelected = selection.Count();
+ if ( !nSelected )
+ return;
+
+ bool bChangeOccurred = false;
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, "Delete Elements" );
+
+ for ( int si = 0; si < nSelected; ++si )
+ {
+ KeyValues *item = selection[ si ];
+ Assert( item );
+
+ // Check to see if this attribute refers to an element
+ CDmElement *pOwner = GetElementKeyValue<CDmElement>( item, "ownerelement" );
+ if ( pOwner == NULL )
+ continue;
+
+ CDmAttribute *pAttr = pOwner->GetAttribute( item->GetString( "attributeName" ) );
+ if ( pAttr == NULL )
+ continue;
+
+ bChangeOccurred = true;
+
+ DmAttributeType_t attrType = pAttr->GetType();
+ if ( attrType == AT_ELEMENT )
+ {
+ CDmElement *pElement = pAttr->GetValueElement<CDmElement>();
+ RemoveAllReferencesToElement( pElement );
+ }
+ else if ( attrType == AT_ELEMENT_ARRAY )
+ {
+ const CDmrElementArray<> array( pAttr );
+ int n = array.Count();
+ int index = item->GetInt( "arrayIndex", -1 );
+ if ( index >= 0 )
+ {
+ CDmElement *pElement = array[ index ];
+ RemoveAllReferencesToElement( pElement );
+ }
+ else
+ {
+ for ( int i = 0; i < n; ++i )
+ {
+ CDmElement *pElement = array[ i ];
+ RemoveAllReferencesToElement( pElement );
+ }
+ }
+ }
+ }
+ }
+
+ // Does a forced refresh
+ if ( bChangeOccurred )
+ {
+ Refresh( REFRESH_TREE_VIEW );
+ }
+}
+
+void CElementPropertiesTreeInternal::OnCut()
+{
+ OnCopy();
+ OnRemove();
+}
+
+void CElementPropertiesTreeInternal::OnClear()
+{
+ bool bNeedRefresh = false;
+ CUtlVector< KeyValues * > data;
+ m_pTree->GetTree()->GetSelectedItemData( data );
+ int c = data.Count();
+ if ( !c )
+ return;
+
+ CElementTreeNotifyScopeGuard notify( "CElementPropertiesTreeInternal::OnClear", NOTIFY_SETDIRTYFLAG, m_pNotify );
+
+ for ( int i = 0; i < c; ++i )
+ {
+ KeyValues *item = data[ i ];
+ Assert( item );
+
+ //Check to see if this attribute refers to an element
+ CDmElement *pOwner = GetElementKeyValue<CDmElement>( item, "ownerelement" );
+ const char *pAttributeName = item->GetString( "attributeName" );
+
+ if ( pOwner && pAttributeName[ 0 ] )
+ {
+ CDmAttribute *pAttribute = pOwner->GetAttribute( pAttributeName );
+ DmAttributeType_t attType = pAttribute ? pAttribute->GetType( ) : AT_UNKNOWN;
+ switch ( attType )
+ {
+ default:
+ break;
+
+ case AT_ELEMENT:
+ {
+ bNeedRefresh = true;
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Element" );
+ pAttribute->SetValue( DMELEMENT_HANDLE_INVALID );
+ }
+ break;
+
+ case AT_ELEMENT_ARRAY:
+ {
+ bNeedRefresh = true;
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Element" );
+ CDmrGenericArray array( pAttribute );
+ if ( array.IsValid() )
+ {
+ array.RemoveAll();
+ }
+ }
+ break;
+ }
+ }
+ }
+
+ if ( bNeedRefresh )
+ {
+ // Does a forced refresh
+ Refresh( REFRESH_TREE_VIEW );
+ }
+}
+
+// For each owner/attribute have an entry and them for each arrayIndex into any array type, need to sort by arrayIndex so we can remove them in reverse order
+
+static bool ArrayIndexLessFunc( KeyValues * const &lhs, KeyValues* const &rhs )
+{
+ bool arrayItem1 = !lhs->IsEmpty( "arrayIndex" ) ? true : false;
+ int arrayIndex1 = lhs->GetInt( "arrayIndex" );
+ bool arrayItem2 = !rhs->IsEmpty( "arrayIndex" ) ? true : false;
+ int arrayIndex2 = rhs->GetInt( "arrayIndex" );
+
+ if ( !arrayItem1 || !arrayItem2 )
+ return lhs < rhs;
+
+ return arrayIndex1 < arrayIndex2;
+}
+
+struct OwnerAttribute_t
+{
+ OwnerAttribute_t() : sortedData( 0, 0, ArrayIndexLessFunc )
+ {
+ }
+
+ OwnerAttribute_t( const OwnerAttribute_t& src ) : sortedData( 0, 0, ArrayIndexLessFunc )
+ {
+ pOwner = src.pOwner;
+ symAttribute = src.symAttribute;
+ for ( int i = src.sortedData.FirstInorder(); i != src.sortedData.InvalidIndex(); i = src.sortedData.NextInorder( i ) )
+ {
+ sortedData.Insert( src.sortedData[ i ] );
+ }
+ }
+
+ static bool LessFunc( const OwnerAttribute_t& lhs, const OwnerAttribute_t& rhs )
+ {
+ if ( lhs.pOwner != rhs.pOwner )
+ return lhs.pOwner < rhs.pOwner;
+
+ return Q_stricmp( lhs.symAttribute.String(), rhs.symAttribute.String() ) < 0;
+ }
+
+ CDmElement *pOwner;
+ CUtlSymbol symAttribute;
+
+ CUtlRBTree< KeyValues *, int > sortedData;
+};
+
+class CSortedElementData
+{
+public:
+ CSortedElementData() : m_Sorted( 0, 0, OwnerAttribute_t::LessFunc )
+ {
+ }
+
+ void AddData( CDmElement *pOwner, const char *attribute, KeyValues *data )
+ {
+ OwnerAttribute_t search;
+ search.pOwner = pOwner;
+ search.symAttribute = attribute;
+
+ int idx = m_Sorted.Find( search );
+ if ( idx == m_Sorted.InvalidIndex() )
+ {
+ idx = m_Sorted.Insert( search );
+ }
+
+ OwnerAttribute_t *entry = &m_Sorted[ idx ];
+ Assert( entry );
+
+ entry->sortedData.Insert( data );
+ }
+
+ CUtlRBTree< OwnerAttribute_t, int > m_Sorted;
+};
+
+bool CElementPropertiesTreeInternal::OnRemoveFromData( KeyValues *item )
+{
+ Assert( item );
+
+ bool arrayItem = !item->IsEmpty( "arrayIndex" );
+ int arrayIndex = item->GetInt( "arrayIndex" );
+
+ //Warning( " item[ %i ] (array? %s)\n", arrayIndex, arrayItem ? "yes" : "no" );
+
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( item, "ownerelement" );
+ const char *pAttributeName = item->GetString( "attributeName" );
+ if ( !pOwner || !pAttributeName[ 0 ] )
+ return false;
+
+ CDmAttribute *pAttribute = pOwner->GetAttribute( pAttributeName );
+ DmAttributeType_t attType = pAttribute ? pAttribute->GetType( ) : AT_UNKNOWN;
+
+ if ( arrayItem && IsArrayType( attType ) )
+ {
+ CDmrGenericArray array( pAttribute );
+ if ( !array.IsValid() )
+ return false;
+
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Array element" );
+
+ array.Remove( arrayIndex );
+ return true;
+ }
+
+ if ( attType == AT_ELEMENT )
+ {
+ if ( pOwner->GetValue< DmElementHandle_t >( pAttributeName ) != DMELEMENT_HANDLE_INVALID )
+ {
+ // remove the referenced element from this attribute
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Element" );
+ pAttribute->SetValue( DMELEMENT_HANDLE_INVALID );
+ }
+ else if ( !pAttribute->IsFlagSet( FATTRIB_EXTERNAL ) && !pAttribute->IsFlagSet( FATTRIB_READONLY ) )
+ {
+ // remove the attribute
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Attribute" );
+ pOwner->RemoveAttribute( pAttributeName );
+ }
+ return true;
+ }
+
+ if ( attType == AT_ELEMENT_ARRAY )
+ {
+ CDmrGenericArray array( pOwner, pAttributeName );
+ if ( array.IsValid() && array.Count() > 0 )
+ {
+ // remove the all the elements from the array
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Element Array Items" );
+ array.RemoveAll();
+ }
+ else if ( !pAttribute->IsFlagSet( FATTRIB_EXTERNAL ) && !pAttribute->IsFlagSet( FATTRIB_READONLY ) )
+ {
+ // remove the attribute
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Attribute" );
+ pOwner->RemoveAttribute( pAttributeName );
+ }
+ return true;
+ }
+
+ if ( !pAttribute->IsFlagSet( FATTRIB_EXTERNAL )
+ && !pAttribute->IsFlagSet( FATTRIB_TOPOLOGICAL )
+ && !pAttribute->IsFlagSet( FATTRIB_READONLY ) )
+ {
+ if ( attType >= AT_FIRST_ARRAY_TYPE )
+ {
+ CDmrGenericArray array( pOwner, pAttributeName );
+ if ( array.IsValid() && array.Count() > 0 )
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Array Items" );
+ array.RemoveAll();
+ }
+ else
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Attribute" );
+ pOwner->RemoveAttribute( pAttributeName );
+ }
+ }
+ else
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, "Remove Attribute" );
+ pOwner->RemoveAttribute( pAttributeName );
+ }
+ return true;
+ }
+ return false;
+}
+
+bool CElementPropertiesTreeInternal::OnRemoveFromData( CUtlVector< KeyValues * >& list )
+{
+ CSortedElementData sorted;
+ int i;
+ int c = list.Count();
+ for ( i = 0 ; i < c; ++i )
+ {
+ KeyValues *item = list[ i ];
+ //Check to see if this attribute refers to an element
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( item, "ownerelement" );
+ const char *pAttributeName = item->GetString( "attributeName" );
+ if ( !pOwner || !pAttributeName[ 0 ] )
+ continue;
+
+ sorted.AddData( pOwner, pAttributeName, item );
+ }
+
+ bool bRefreshRequired = false;
+
+ // Now walk the data in reverse order
+ for ( i = sorted.m_Sorted.FirstInorder(); i != sorted.m_Sorted.InvalidIndex(); i = sorted.m_Sorted.NextInorder( i ) )
+ {
+ OwnerAttribute_t& entry = sorted.m_Sorted[ i ];
+
+ // Walk it backward by array index...
+ for ( int j = entry.sortedData.LastInorder(); j != entry.sortedData.InvalidIndex(); j = entry.sortedData.PrevInorder( j ) )
+ {
+ KeyValues *item = entry.sortedData[ j ];
+ bRefreshRequired = OnRemoveFromData( item ) || bRefreshRequired;
+ }
+ }
+
+ return bRefreshRequired;
+}
+
+void CElementPropertiesTreeInternal::OnRemove()
+{
+ CElementTreeNotifyScopeGuard notify( "CElementPropertiesTreeInternal::OnRemove", NOTIFY_SETDIRTYFLAG, m_pNotify );
+
+ CUtlVector< KeyValues * > data;
+ m_pTree->GetTree()->GetSelectedItemData( data );
+ bool bRefreshNeeded = OnRemoveFromData( data );
+ if ( bRefreshNeeded )
+ {
+ // Refresh the tree
+ Refresh( REFRESH_TREE_VIEW );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Sorts by name
+//-----------------------------------------------------------------------------
+int ElementNameSortFunc( const void *arg1, const void *arg2 )
+{
+ CDmElement *pElement1 = *(CDmElement**)arg1;
+ CDmElement *pElement2 = *(CDmElement**)arg2;
+
+ const char *pName1 = pElement1 ? pElement1->GetName() : "";
+ const char *pName2 = pElement2 ? pElement2->GetName() : "";
+
+ return Q_stricmp( pName1, pName2 );
+}
+
+void CElementPropertiesTreeInternal::OnSortByName()
+{
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, "Sort Element Array Attribute" );
+
+ CUtlVector< KeyValues * > list;
+ m_pTree->GetTree()->GetSelectedItemData( list );
+
+ int c = list.Count();
+
+ bool bRefreshNeeded = false;
+ for ( int i = 0 ; i < c; ++i )
+ {
+ KeyValues *item = list[ i ];
+
+ //Check to see if this attribute refers to an element
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( item, "ownerelement" );
+ const char *pAttributeName = item->GetString( "attributeName" );
+
+ CDmrElementArray<> elementArray( pOwner, pAttributeName );
+ if ( !elementArray.IsValid() )
+ continue;
+
+ int nCount = elementArray.Count();
+ if ( nCount == 0 )
+ continue;
+
+ bRefreshNeeded = true;
+ CDmElement **pArray = ( CDmElement** )_alloca( nCount * sizeof( CDmElement* ) );
+ for ( int i = 0; i < nCount; ++i )
+ {
+ pArray[i] = elementArray[i];
+ }
+
+ qsort( pArray, nCount, sizeof( CDmElement* ), ElementNameSortFunc );
+
+ elementArray.RemoveAll();
+ elementArray.AddMultipleToTail( nCount );
+
+ for ( int i = 0; i < nCount; ++i )
+ {
+ elementArray.Set( i, pArray[i] );
+ }
+ }
+
+ if ( bRefreshNeeded )
+ {
+ // Refresh the tree
+ Refresh( REFRESH_TREE_VIEW );
+ }
+}
+
+void CElementPropertiesTreeInternal::JumpToHistoryItem()
+{
+ if ( m_nCurrentHistoryPosition < 0 || m_nCurrentHistoryPosition >= m_hHistory.Count() )
+ {
+ m_nCurrentHistoryPosition = 0;
+ }
+
+ if ( !m_hHistory.Count() )
+ return;
+
+ CDmElement *element = m_hHistory[ m_nCurrentHistoryPosition ].Get();
+ if ( !element )
+ return;
+
+ bool save = m_bSuppressHistoryUpdates;
+ m_bSuppressHistoryUpdates = true;
+
+ SetObject( element );
+
+ m_bSuppressHistoryUpdates = save;
+
+ // Does a forced refresh
+ Refresh( REFRESH_TREE_VIEW );
+
+ // Used by the Dme panel to refresh the combo boxes when we change objects
+ KeyValues *kv = new KeyValues( "NotifyViewedElementChanged" );
+ SetElementKeyValue( kv, "dmeelement", element );
+ PostActionSignal( kv );
+}
+
+void CElementPropertiesTreeInternal::OnShowSearchResults()
+{
+ if ( !m_SearchResults.Count() )
+ return;
+
+ if ( !m_SearchResultsRoot.Get() )
+ return;
+
+ SetObject( m_SearchResultsRoot.Get() );
+
+ // Used by the Dme panel to refresh the combo boxes when we change objects
+ KeyValues *kv = new KeyValues( "NotifyViewedElementChanged" );
+ SetElementKeyValue( kv, "dmeelement", m_SearchResultsRoot.Get() );
+ PostActionSignal( kv );
+}
+
+void CElementPropertiesTreeInternal::OnNavBack( int item )
+{
+ int c = m_hHistory.Count();
+ if ( c <= 1 )
+ return;
+
+ if ( item == -1 )
+ {
+ if ( m_nCurrentHistoryPosition >= c - 1 )
+ return;
+
+ item = 1;
+ }
+
+ m_nCurrentHistoryPosition += item;
+ Assert( m_nCurrentHistoryPosition < c );
+
+ JumpToHistoryItem();
+
+ UpdateButtonState();
+}
+
+void CElementPropertiesTreeInternal::OnNavForward( int item )
+{
+ int c = m_hHistory.Count();
+ if ( c <= 0 )
+ return;
+
+ if ( item == -1 )
+ {
+ if ( m_nCurrentHistoryPosition <= 0 )
+ return;
+
+ item = 0;
+ }
+
+ ++item;
+
+ m_nCurrentHistoryPosition -= item;
+ Assert( m_nCurrentHistoryPosition >= 0 );
+
+ JumpToHistoryItem();
+
+ UpdateButtonState();
+}
+
+bool CElementPropertiesTreeInternal::BuildExpansionListToFindElement_R(
+ CUtlRBTree< CDmElement *, int >& visited,
+ int depth,
+ SearchResult_t &sr,
+ CDmElement *owner,
+ CDmElement *element,
+ const char *attributeName,
+ int arrayIndex,
+ CUtlVector< int >& expandIndices
+ )
+{
+ if ( !element )
+ return true;
+
+ if ( visited.Find( element ) != visited.InvalidIndex() )
+ return true;
+
+ visited.Insert( element );
+
+ int nAttributes = element->AttributeCount();
+
+ if ( element == sr.handle.Get() )
+ {
+ if ( sr.attributeName.Length() > 0 )
+ {
+ int idx = nAttributes - 1;
+ for ( CDmAttribute *attribute = element->FirstAttribute(); attribute; attribute = attribute->NextAttribute(), --idx )
+ {
+ const char *attributeName = attribute->GetName();
+ if ( !Q_stricmp( attributeName, sr.attributeName.Get() ) )
+ {
+ expandIndices.AddToTail( idx );
+ break;
+ }
+ }
+ }
+ return false;
+ }
+
+ int idx = nAttributes - 1;
+ for ( CDmAttribute *attribute = element->FirstAttribute(); attribute; attribute = attribute->NextAttribute(), --idx )
+ {
+ const char *attributeName = attribute->GetName();
+ if ( attribute->GetType() == AT_ELEMENT )
+ {
+ if ( !BuildExpansionListToFindElement_R( visited, depth + 1, sr, element, attribute->GetValueElement<CDmElement>(), attributeName, -1, expandIndices ) )
+ {
+ expandIndices.AddToTail( idx );
+ return false;
+ }
+ }
+ else if ( attribute->GetType() == AT_ELEMENT_ARRAY )
+ {
+ // Walk child objects
+ const CDmrElementArray<CDmElement> elementArray( attribute );
+ int c = elementArray.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ if ( !BuildExpansionListToFindElement_R( visited, depth + 1, sr, element, elementArray[ i ], attributeName, i, expandIndices ) )
+ {
+ expandIndices.AddToTail( i );
+ expandIndices.AddToTail( idx );
+ return false;
+ }
+ }
+ }
+ }
+
+ return true;
+}
+
+static ConVar dme_properties_maxsearchresults( "dme_properties_maxsearchresults", "50", 0, "Max number of search results to track." );
+
+void CElementPropertiesTreeInternal::FindMatchingElements_R( CUtlRBTree< CDmElement *, int >& visited, const char *searchstr, CDmElement *element, CUtlVector< SearchResult_t >& list )
+{
+ if ( list.Count() >= dme_properties_maxsearchresults.GetInt() )
+ return;
+
+ if ( !element )
+ return;
+
+ if ( visited.Find( element ) != visited.InvalidIndex() )
+ return;
+
+ visited.Insert( element );
+
+ if ( Q_stristr( element->GetName(), searchstr ) )
+ {
+ CDmeHandle< CDmElement > h;
+ h = element;
+
+ SearchResult_t sr;
+ sr.handle = h;
+ sr.attributeName = "";
+
+ if ( list.Find( sr ) == list.InvalidIndex() )
+ {
+ list.AddToTail( sr );
+ }
+ }
+
+ for ( CDmAttribute *attribute = element->FirstAttribute(); attribute; attribute = attribute->NextAttribute() )
+ {
+ const char *attributeName = attribute->GetName();
+ if ( Q_stristr( attributeName, searchstr ) )
+ {
+ CDmeHandle< CDmElement > h;
+ h = element;
+
+ SearchResult_t sr;
+ sr.handle = h;
+ sr.attributeName = attributeName;
+
+ if ( list.Find( sr ) == list.InvalidIndex() )
+ {
+ list.AddToTail( sr );
+ }
+ }
+
+ if ( attribute->GetType() == AT_ELEMENT )
+ {
+ FindMatchingElements_R( visited, searchstr, attribute->GetValueElement<CDmElement>(), list );
+ }
+ else if ( attribute->GetType() == AT_ELEMENT_ARRAY )
+ {
+ // Walk child objects
+ const CDmrElementArray<CDmElement> elementArray( attribute );
+ int c = elementArray.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ FindMatchingElements_R( visited, searchstr, elementArray[ i ], list );
+ }
+ }
+ }
+}
+
+void CElementPropertiesTreeInternal::OnNavigateSearchAgain( int direction )
+{
+ if ( m_SearchResults.Count() <= 0 )
+ {
+ surface()->PlaySound("common/warning.wav");
+ return;
+ }
+
+ if ( direction < 0 )
+ {
+ direction = -1;
+ }
+ else if ( direction >= 0 )
+ {
+ direction = 1;
+ }
+
+ m_nCurrentSearchResult = m_nCurrentSearchResult + direction;
+
+ if ( m_nCurrentSearchResult < 0 )
+ {
+ m_nCurrentSearchResult = 0;
+ surface()->PlaySound("common/warning.wav");
+ }
+ else if ( m_nCurrentSearchResult >= m_SearchResults.Count() )
+ {
+ m_nCurrentSearchResult = m_SearchResults.Count() - 1;
+ surface()->PlaySound("common/warning.wav");
+ }
+
+ NavigateToSearchResult();
+
+ UpdateButtonState();
+}
+
+void CElementPropertiesTreeInternal::NavigateToSearchResult()
+{
+ if ( !m_SearchResults.Count() )
+ return;
+
+// SetObject( m_SearchResultsRoot.Get() );
+
+ CUtlVector< int > expandIndices;
+ CUtlRBTree< CDmElement *, int > visited( 0, 0, DefLessFunc( CDmElement * ) );
+
+ BuildExpansionListToFindElement_R(
+ visited,
+ 0,
+ m_SearchResults[ m_nCurrentSearchResult ],
+ m_hObject.Get(),
+ m_hObject.Get(),
+ "name",
+ -1,
+ expandIndices );
+
+ expandIndices.AddToTail( 0 );
+
+ // Close the tree and re-create the root node only
+ UpdateTree();
+
+ // NOTE: Updating the tree could have changed the root item index
+ int nIndex = m_pTree->GetTree()->GetRootItemIndex();
+ int c = expandIndices.Count();
+ for ( int i = c - 2; i >= 0 ; --i )
+ {
+ int idx = expandIndices[ i ];
+
+ // Expand the item
+ m_pTree->ExpandItem( nIndex, true );
+
+#ifdef _DEBUG
+ int children = m_pTree->GetTree()->GetNumChildren( nIndex );
+ if ( idx >= children )
+ {
+ Assert( 0 );
+ break;
+ }
+#endif
+ int childIndex = m_pTree->GetTree()->GetChild( nIndex, idx );
+ nIndex = childIndex;
+ }
+
+ m_pTree->ExpandItem( nIndex, true );
+
+ // Add to selection, but don't request focus (3rd param)
+ m_pTree->GetTree()->AddSelectedItem( nIndex, true, false );
+ m_pTree->GetTree()->MakeItemVisible( nIndex );
+
+ m_pTree->ResizeTreeToExpandedWidth();
+
+ DevMsg( "Displaying search result %d of %d\n", m_nCurrentSearchResult + 1, m_SearchResults.Count() );
+}
+
+void CElementPropertiesTreeInternal::OnNavSearch( const char *text )
+{
+ Msg( "OnNavSearch(%s)\n", text);
+ if ( !text || !*text )
+ {
+ UpdateButtonState();
+ return;
+ }
+
+ bool changed = Q_stricmp( text, m_szSearchStr ) != 0 ? true : false;
+ if ( changed )
+ {
+ m_SearchResults.RemoveAll();
+ Q_strncpy( m_szSearchStr, text, sizeof( m_szSearchStr ) );
+ m_nCurrentSearchResult = 0;
+
+ CUtlRBTree< CDmElement *, int > visited( 0, 0, DefLessFunc( CDmElement * ) );
+
+ FindMatchingElements_R( visited, m_szSearchStr, m_hObject.Get(), m_SearchResults );
+
+ AddToSearchHistory( text );
+
+ if ( m_SearchResultsRoot.Get() )
+ {
+ CDisableUndoScopeGuard guard;
+
+ int c = m_SearchResults.Count();
+
+ char sz[ 512 ];
+ Q_snprintf( sz, sizeof( sz ), "Search Results [%d] for '%s'", c, m_szSearchStr );
+
+ m_SearchResultsRoot->SetName( sz );
+
+ CDmrElementArray<> array( m_SearchResultsRoot, "results" );
+ if ( array.IsValid() )
+ {
+ array.RemoveAll();
+ for ( int i = 0; i < c; ++i )
+ {
+ if ( m_SearchResults[ i ].handle.Get() )
+ {
+ array.AddToTail( m_SearchResults[ i ].handle.GetHandle() );
+ }
+ }
+ }
+ }
+ }
+ else
+ {
+ ++m_nCurrentSearchResult;
+ }
+
+ if ( !m_SearchResults.Count() )
+ {
+ // Close the tree and re-create the root node only
+ UpdateTree();
+
+ int nIndex = m_pTree->GetTree()->GetRootItemIndex();
+ m_pTree->ExpandItem( nIndex, true );
+
+ m_pTree->ResizeTreeToExpandedWidth();
+
+ UpdateButtonState();
+ return;
+ }
+
+ m_nCurrentSearchResult = clamp( m_nCurrentSearchResult, 0, m_SearchResults.Count() - 1 );
+
+ NavigateToSearchResult();
+
+ UpdateButtonState();
+}
+
+int CElementPropertiesTreeInternal::GetHistoryMenuItemCount( int whichMenu )
+{
+ int c = m_hHistory.Count();
+ if ( !c )
+ return 0;
+
+ if ( m_nCurrentHistoryPosition == -1 )
+ {
+ m_nCurrentHistoryPosition = 0;
+ }
+
+ switch ( whichMenu )
+ {
+ default:
+ Assert( 0 );
+ break;
+ case DME_PROPERTIESTREE_MENU_BACKWARD:
+ {
+ return c - ( m_nCurrentHistoryPosition + 1 );
+ }
+ break;
+ case DME_PROPERTIESTREE_MENU_FORWARD:
+ {
+ return m_nCurrentHistoryPosition;
+ }
+ break;
+ case DME_PROPERTIESTREE_MENU_SEARCHHSITORY:
+ {
+ return m_SearchHistory.Count();
+ }
+ break;
+ }
+
+ return 0;
+}
+
+void CElementPropertiesTreeInternal::PopulateHistoryMenu( int whichMenu, Menu *menu )
+{
+ ValidateHistory();
+
+ int c = m_hHistory.Count();
+
+ if ( m_nCurrentHistoryPosition == -1 )
+ {
+ m_nCurrentHistoryPosition = 0;
+ }
+
+ menu->DeleteAllItems();
+ switch ( whichMenu )
+ {
+ default:
+ Assert( 0 );
+ break;
+ case DME_PROPERTIESTREE_MENU_BACKWARD:
+ {
+ for ( int i = m_nCurrentHistoryPosition + 1; i < c; ++i )
+ {
+ CDmElement *element = m_hHistory[ i ].Get();
+ char sz[ 256 ];
+ Q_snprintf( sz, sizeof( sz ), "%s < %s >", element->GetName(), element->GetTypeString() );
+ menu->AddMenuItem( "backitem", sz, new KeyValues( "OnNavigateBack", "item", i ), this );
+ }
+ }
+ break;
+ case DME_PROPERTIESTREE_MENU_FORWARD:
+ {
+ for ( int i = 0 ; i < m_nCurrentHistoryPosition; ++i )
+ {
+ CDmElement *element = m_hHistory[ m_nCurrentHistoryPosition - i - 1 ].Get();
+ char sz[ 256 ];
+ Q_snprintf( sz, sizeof( sz ), "%s < %s >", element->GetName(), element->GetTypeString() );
+ menu->AddMenuItem( "fwditem", sz, new KeyValues( "OnNavigateForward", "item", i ), this );
+ }
+ }
+ break;
+ case DME_PROPERTIESTREE_MENU_SEARCHHSITORY:
+ {
+ int c = m_SearchHistory.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ CUtlString& str = m_SearchHistory[ i ];
+ menu->AddMenuItem( "search", str.Get(), new KeyValues( "OnNavSearch", "text", str.Get() ), this );
+ }
+ }
+ break;
+ }
+}
+
+void CElementPropertiesTreeInternal::AddToSearchHistory( const char *str )
+{
+ CUtlString historyString;
+ historyString = str;
+
+ int c = m_SearchHistory.Count();
+ for ( int i = c - 1; i >= 0; --i )
+ {
+ CUtlString& entry = m_SearchHistory[ i ];
+ if ( entry == historyString )
+ {
+ m_SearchHistory.Remove( i );
+ break;
+ }
+ }
+
+ while ( m_SearchHistory.Count() >= DME_PROPERTIESTREE_MAXSEARCHHISTORYITEMS )
+ {
+ m_SearchHistory.Remove( m_SearchHistory.Count() - 1 );
+ }
+
+ // Newest item at head of list
+ m_SearchHistory.AddToHead( historyString );
+}
+
+void CElementPropertiesTreeInternal::AddToHistory( CDmElement *element )
+{
+ if ( m_bSuppressHistoryUpdates )
+ return;
+
+ if ( !element )
+ return;
+
+ CDmeHandle< CDmElement > h;
+ h = element;
+
+ // Purge the forward list
+ if ( m_nCurrentHistoryPosition > 0 )
+ {
+ m_hHistory.RemoveMultiple( 0, m_nCurrentHistoryPosition );
+ m_nCurrentHistoryPosition = 0;
+ }
+
+ // Remove if it's already in the list
+ m_hHistory.FindAndRemove( h );
+
+ // Make sure there's room
+ while ( m_hHistory.Count() >= DME_PROPERTIESTREE_MAXHISTORYITEMS )
+ {
+ m_hHistory.Remove( m_hHistory.Count() - 1 );
+ }
+
+ // Most recent is at head
+ m_hHistory.AddToHead( h );
+
+ ValidateHistory();
+
+ UpdateButtonState();
+}
+
+void CElementPropertiesTreeInternal::ValidateHistory()
+{
+ int i;
+ int c = m_hHistory.Count();
+ for ( i = c - 1 ; i >= 0; --i )
+ {
+ if ( !m_hHistory[ i ].Get() )
+ {
+ m_hHistory.Remove( i );
+ if ( i && i == m_nCurrentHistoryPosition )
+ {
+ --m_nCurrentHistoryPosition;
+ }
+ }
+ }
+}
+
+void CElementPropertiesTreeInternal::SpewHistory()
+{
+ int i;
+ int c = m_hHistory.Count();
+ for ( i = 0 ; i < c; ++i )
+ {
+ CDmElement *element = m_hHistory[ i ].Get();
+ Assert( element );
+ if ( !element )
+ continue;
+
+ Msg( "%s: [%02d] %s <%s>\n",
+ ( ( i < m_nCurrentHistoryPosition ) ? "Fwd" : ( i == m_nCurrentHistoryPosition ? "Current" : "Backward" ) ),
+ i,
+ element->GetName(),
+ element->GetTypeString() );
+ }
+}
+
+
+void CElementPropertiesTreeInternal::AddAttribute( const char *pAttributeName, KeyValues *pContext )
+{
+ if ( !pAttributeName || !pAttributeName[ 0 ] )
+ {
+ Warning( "Can't add attribute with an empty name\n" );
+ return;
+ }
+
+ const char *pAttributeType = pContext->GetString( "attributeType" );
+ CDmElement *pElement = GetElementKeyValue< CDmElement >( pContext, "element" );
+ if ( !pAttributeType || !pAttributeType[0] || !pElement )
+ return;
+
+ DmAttributeType_t attributeType = g_pDataModel->GetAttributeTypeForName( pAttributeType );
+ if ( attributeType == AT_UNKNOWN )
+ {
+ Warning( "Can't add attribute '%s' because type '%s' is not known\n", pAttributeName, pAttributeType );
+ return;
+ }
+
+ // Make sure attribute name isn't taken already
+ if ( pElement->HasAttribute( pAttributeName ) )
+ {
+ Warning( "Can't add attribute '%s', attribute with that name already exists\n", pAttributeName );
+ return;
+ }
+
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, "Add Attribute" );
+ CDmAttribute *pAttribute = pElement->AddAttribute( pAttributeName, attributeType );
+ if ( pAttribute )
+ {
+ pAttribute->AddFlag( FATTRIB_USERDEFINED );
+ }
+ Refresh( REFRESH_TREE_VIEW );
+}
+
+void CElementPropertiesTreeInternal::SetElementAttribute( const char *pElementName, KeyValues *pContext )
+{
+ if ( !pElementName || !pElementName[ 0 ] )
+ {
+ Warning( "Can't set an element attribute with an unnamed element!\n" );
+ return;
+ }
+
+ const char *pAttributeName = pContext->GetString( "attributeName" );
+ const char *pElementType = pContext->GetString( "elementType" );
+ CDmElement *pElement = GetElementKeyValue< CDmElement >( pContext, "element" );
+ if ( !pElementType || !pElementType[0] || !pElement )
+ return;
+
+ bool bRefreshRequired = false;
+
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, "Set Element" );
+ DmElementHandle_t newElement = g_pDataModel->CreateElement( pElementType, pElementName, pElement->GetFileId() );
+ if ( newElement == DMELEMENT_HANDLE_INVALID )
+ return;
+
+ CDmAttribute *pAttribute = pElement->GetAttribute( pAttributeName );
+ DmAttributeType_t type = pAttribute ? pAttribute->GetType() : AT_UNKNOWN;
+ switch( type )
+ {
+ case AT_ELEMENT:
+ pAttribute->SetValue( newElement );
+ bRefreshRequired = true;
+ break;
+
+ case AT_ELEMENT_ARRAY:
+ {
+ CDmrElementArray<> array( pAttribute );
+ if ( !array.IsValid() )
+ {
+ g_pDataModel->DestroyElement( newElement );
+ return;
+ }
+
+ int idx = pContext->GetInt( "index", -1 );
+
+ bRefreshRequired = true;
+ if ( idx == -1 )
+ {
+ array.AddToTail( newElement );
+ }
+ else
+ {
+ array.SetHandle( idx, newElement );
+ }
+ }
+ break;
+ }
+ }
+
+ if ( bRefreshRequired )
+ {
+ Refresh( REFRESH_TREE_VIEW );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Called by the input dialog for add attribute + set element
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::OnInputCompleted( KeyValues *pParams )
+{
+ KeyValues *pDlg = pParams->FindKey( "OnAddAttribute", false );
+ if ( pDlg )
+ {
+ const char *pAttributeName = pParams->GetString( "text" );
+ AddAttribute( pAttributeName, pDlg );
+ return;
+ }
+
+ pDlg = pParams->FindKey( "OnSetElement", false );
+ if ( pDlg )
+ {
+ const char *pElementName = pParams->GetString( "text" );
+ SetElementAttribute( pElementName, pDlg );
+ return;
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Forwards commands to parent
+//-----------------------------------------------------------------------------
+bool CElementPropertiesTreeInternal::ShowSetElementAttributeDialog( CDmElement *pOwner,
+ const char *pAttributeName, int nArrayIndex, const char *pElementType )
+{
+ if ( !pOwner || !pAttributeName || !pAttributeName[ 0 ] || !pElementType || !pElementType[ 0 ] )
+ return false;
+
+ static int elemNum = 0;
+ char elemName[ 512 ];
+ if ( elemNum++ == 0 )
+ {
+ Q_snprintf( elemName, sizeof( elemName ), "newElement" );
+ }
+ else
+ {
+ Q_snprintf( elemName, sizeof( elemName ), "newElement%i", elemNum );
+ }
+
+ KeyValues *kv = new KeyValues( "OnSetElement", "attributeName", pAttributeName );
+ SetElementKeyValue( kv, "element", pOwner );
+ kv->SetInt( "index", nArrayIndex );
+ kv->SetString( "elementType", pElementType );
+
+ InputDialog *pSetAttributeDialog = new InputDialog( this, "Set Element", "Element Name:", elemName );
+ pSetAttributeDialog->SetSmallCaption( true );
+ pSetAttributeDialog->SetDeleteSelfOnClose( true );
+ pSetAttributeDialog->DoModal( kv );
+ return true;
+}
+
+
+bool CElementPropertiesTreeInternal::ShowAddAttributeDialog( CDmElement *pElement, const char *pAttributeType )
+{
+ if ( !pElement || !pAttributeType || !pAttributeType[ 0 ] )
+ return false;
+
+ static int attrNum = 0;
+ char attrName[ 512 ];
+ if ( attrNum++ == 0 )
+ {
+ Q_snprintf( attrName, sizeof( attrName ), "newAttribute" );
+ }
+ else
+ {
+ Q_snprintf( attrName, sizeof( attrName ), "newAttribute%i", attrNum );
+ }
+
+ KeyValues *kv = new KeyValues( "OnAddAttribute", "attributeType", pAttributeType );
+ SetElementKeyValue( kv, "element", pElement );
+
+ InputDialog *pAddDialog = new InputDialog( this, "Add Attribute", "Attribute Name:", attrName );
+ pAddDialog->SetSmallCaption( true );
+ pAddDialog->SetDeleteSelfOnClose( true );
+ pAddDialog->DoModal( kv );
+ return true;
+}
+
+
+//-----------------------------------------------------------------------------
+// Forwards commands to parent
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::OnCommand( const char *cmd )
+{
+ CUtlVector< KeyValues * > data;
+ m_pTree->GetTree()->GetSelectedItemData( data );
+ if ( !data.Count() )
+ return;
+
+ int c = data.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ KeyValues *item = data[ i ];
+ Assert( item );
+
+ // Check to see if this attribute refers to an element
+ const char *pElementType = StringAfterPrefix( cmd, "element_" );
+ if ( pElementType )
+ {
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( item, "ownerelement" );
+ const char *pAttributeName = item->GetString( "attributeName" );
+ bool arrayItem = !item->IsEmpty( "arrayIndex" );
+ int arrayIndex = item->GetInt( "arrayIndex" );
+ if ( ShowSetElementAttributeDialog( pOwner, pAttributeName, arrayItem ? arrayIndex : -1, pElementType ) )
+ return;
+ continue;
+ }
+
+ const char *pAttributeType = StringAfterPrefix( cmd, "attribute_" );
+ if ( pAttributeType )
+ {
+ CDmElement *pElement = GetElementKeyValue< CDmElement >( item, "dmeelement" );
+ if ( ShowAddAttributeDialog( pElement, pAttributeType ) )
+ return;
+ continue;
+ }
+ }
+
+ if ( GetParent() )
+ {
+ GetParent()->OnCommand( cmd );
+ }
+}
+
+void CElementPropertiesTreeInternal::OnShowMemoryUsage()
+{
+ m_bShowMemoryUsage = !m_bShowMemoryUsage;
+ Refresh( REFRESH_TREE_VIEW, true );
+}
+
+void CElementPropertiesTreeInternal::OnAddItem()
+{
+ CUtlVector< KeyValues * > data;
+ m_pTree->GetTree()->GetSelectedItemData( data );
+ int c = data.Count();
+ if ( c == 0 )
+ return;
+
+ bool bRefreshRequired = false;
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, "Add Item(s)" );
+
+ for ( int i = 0; i < c; ++i )
+ {
+ KeyValues *item = data[ i ];
+ Assert( item );
+
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( item, "ownerelement" );
+ const char *pAttributeName = item->GetString( "attributeName" );
+ CDmAttribute *pAttribute = pOwner->GetAttribute( pAttributeName );
+ DmAttributeType_t attType = pAttribute ? pAttribute->GetType() : AT_UNKNOWN;
+
+ if ( attType == AT_ELEMENT_ARRAY )
+ {
+ CDmrElementArray<> array( pAttribute );
+ if ( !array.IsValid() )
+ continue;
+
+ CUtlSymbol typeSymbol = array.GetElementType();
+ const char *pElementType = g_pDataModel->GetString( typeSymbol );
+ const char *pElementTypeName = StringAfterPrefix( pElementType, "Dme" );
+ if ( !pElementTypeName )
+ {
+ Warning( "CElementPropertiesTreeInternal::OnAddItem: Unknown Element Type %s\n", pElementType );
+ continue;
+ }
+
+ // make up a unique name
+ static int elementNum = 0;
+ char elementName[ 256 ];
+ if ( elementNum++ == 0 )
+ {
+ Q_snprintf( elementName, sizeof( elementName ), "new%s", pElementTypeName );
+ }
+ else
+ {
+ Q_snprintf( elementName, sizeof( elementName ), "new%s%i", pElementTypeName, elementNum );
+ }
+
+ DmElementHandle_t newElement = g_pDataModel->CreateElement( pElementType, elementName, pOwner->GetFileId() );
+ if ( newElement != DMELEMENT_HANDLE_INVALID )
+ {
+ array.AddToTail( newElement );
+ bRefreshRequired = true;
+ }
+ continue;
+ }
+
+ if ( attType >= AT_FIRST_ARRAY_TYPE )
+ {
+ CDmrGenericArray arrayAttr( pAttribute );
+ if ( arrayAttr.IsValid() )
+ {
+ arrayAttr.AddToTail();
+ bRefreshRequired = true;
+ }
+ continue;
+ }
+ }
+
+ if ( !bRefreshRequired )
+ {
+ guard.Abort();
+ }
+ }
+
+ if ( bRefreshRequired )
+ {
+ // Does a forced refresh
+ Refresh( REFRESH_TREE_VIEW );
+ }
+}
+
+void CElementPropertiesTreeInternal::OnSetShared( KeyValues *params )
+{
+ bool bShared = params->GetInt( "shared" ) != 0;
+
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, bShared ? "Mark Shared" : "Mark Not Shared" );
+
+ CUtlVector< KeyValues* > selected;
+ m_pTree->GetTree()->GetSelectedItemData( selected );
+ int nSelected = selected.Count();
+ for ( int i = 0; i < nSelected; ++i )
+ {
+ KeyValues *kv = selected[ i ];
+ CDmElement *pElement = GetElementKeyValue<CDmElement>( kv, "dmeelement" );
+
+ // element attribute or element array item
+ if ( pElement )
+ {
+ pElement->SetShared( bShared );
+ continue;
+ }
+
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( kv, "ownerelement" );
+ const char *pAttributeName = kv->GetString( "attributeName" );
+
+ const CDmrElementArray<> array( pOwner, pAttributeName );
+ if ( !array.IsValid() )
+ continue; // value attribute, value array item, or value array
+
+ // element array attribute
+ int nCount = array.Count();
+ for ( int j = 0; j < nCount; ++j )
+ {
+ CDmElement *pElement = array[ j ];
+ if ( !pElement )
+ continue;
+
+ pElement->SetShared( bShared );
+ }
+ }
+
+ Refresh( REFRESH_TREE_VIEW, true );
+}
+
+void CElementPropertiesTreeInternal::OnChangeFile( KeyValues *params )
+{
+ const char *pFileName = params->GetString( "filename" );
+ DmFileId_t fileid = g_pDataModel->GetFileId( pFileName );
+
+ CUtlVector< KeyValues * > data;
+ m_pTree->GetTree()->GetSelectedItemData( data );
+ int nSelected = data.Count();
+ if ( !nSelected )
+ return;
+
+ CElementTreeNotifyScopeGuard notify( "CElementPropertiesTreeInternal::OnChangeFile", NOTIFY_SETDIRTYFLAG, m_pNotify );
+
+ bool bRefreshRequired = false;
+ for ( int i = 0; i < nSelected; ++i )
+ {
+ KeyValues *item = data[ i ];
+ Assert( item );
+
+ //Check to see if this attribute refers to an element
+ CDmElement *pElement = GetElementKeyValue< CDmElement >( item, "dmeelement" );
+ if ( !pElement )
+ continue;
+
+ if ( fileid == DMFILEID_INVALID )
+ {
+ fileid = g_pDataModel->FindOrCreateFileId( pElement->GetName() );
+ g_pDataModel->SetFileRoot( fileid, pElement->GetHandle() );
+ }
+
+ pElement->SetFileId( fileid, TD_DEEP );
+ bRefreshRequired = true;
+ }
+
+ if ( bRefreshRequired )
+ {
+ Refresh( REFRESH_REBUILD );
+ }
+}
+
+void CElementPropertiesTreeInternal::OnShowFileDialog( KeyValues *params )
+{
+ const char *pTitle = params->GetString( "title" );
+ bool bOpenOnly = params->GetInt( "openOnly" ) != 0;
+ KeyValues *pContext = params->FindKey( "context" );
+ FileOpenDialog *pDialog = new FileOpenDialog( this, pTitle, bOpenOnly, pContext->MakeCopy() );
+
+ char pStartingDir[ MAX_PATH ];
+ GetModSubdirectory( NULL, pStartingDir, sizeof( pStartingDir ) );
+ Q_StripTrailingSlash( pStartingDir );
+
+ pDialog->SetStartDirectoryContext( pTitle, pStartingDir );
+ pDialog->AddFilter( "*.*", "All Files (*.*)", false );
+ pDialog->AddFilter( "*.dmx", "Generic MovieObjects File (*.dmx)", true, "movieobjects" ); // read/write generic movieobjects files
+
+ pDialog->SetDeleteSelfOnClose( true );
+ pDialog->AddActionSignalTarget( this );
+ pDialog->DoModal( true );
+}
+
+void CElementPropertiesTreeInternal::OnImportElement( const char *pFullPath, KeyValues *pContext )
+{
+ CDmElement *pRoot = NULL;
+ DmFileId_t tempFileid;
+ {
+ CDisableUndoScopeGuard guard;
+ tempFileid = g_pDataModel->RestoreFromFile( pFullPath, NULL, NULL, &pRoot, CR_FORCE_COPY );
+ }
+ if ( !pRoot )
+ return;
+
+ CDmElement *pParent = GetElementKeyValue<CDmElement>( pContext, "owner" );
+
+ pRoot->SetFileId( pParent->GetFileId(), TD_DEEP, true );
+ g_pDataModel->RemoveFileId( tempFileid );
+
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, "Import Element" );
+
+ const char *pAttributeName = pContext->GetString( "attribute" );
+ int nArrayIndex = pContext->GetInt( "index", -1 );
+ DmElementHandle_t hRoot = pRoot->GetHandle();
+ if ( nArrayIndex >= 0 )
+ {
+ CDmrElementArray<> elemArrayAttr( pParent, pAttributeName );
+ elemArrayAttr.SetHandle( nArrayIndex, hRoot );
+ }
+ else
+ {
+ CDmAttribute *pAttribute = pParent->GetAttribute( pAttributeName );
+ if ( pAttribute->GetType() == AT_ELEMENT )
+ {
+ pAttribute->SetValue( hRoot );
+ }
+ else if ( pAttribute->GetType() == AT_ELEMENT_ARRAY )
+ {
+ CDmrElementArray<> elemArrayAttr( pAttribute );
+ elemArrayAttr.AddToTail( hRoot );
+ }
+ }
+ }
+
+ Refresh( REFRESH_TREE_VIEW );
+}
+
+void CElementPropertiesTreeInternal::OnExportElement( const char *pFullPath, KeyValues *pContext )
+{
+ CDmElement *pRoot = NULL;
+
+ CUtlVector< KeyValues * > selection;
+ m_pTree->GetTree()->GetSelectedItemData( selection );
+ int nSelected = selection.Count();
+ if ( nSelected <= 1 )
+ {
+ pRoot = GetElementKeyValue<CDmElement>( pContext, "element" );
+ }
+ else
+ {
+ // HACK - this is just a temporary hack - we should really force serialization to traverse past fileid changes in this case
+ KeyValues *item = selection[ 0 ];
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( item, "ownerelement" );
+ DmFileId_t fileid = pOwner->GetFileId();
+
+ pRoot = CreateElement< CDmElement >( pFullPath, fileid );
+ CDmrElementArray<> children( pRoot, "children", true );
+
+ for ( int si = 0; si < nSelected; ++si )
+ {
+ KeyValues *item = selection[ si ];
+ Assert( item );
+
+ //Check to see if this attribute refers to an element
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( item, "ownerelement" );
+ if ( pOwner == NULL )
+ continue;
+
+ CDmAttribute *pAttr = pOwner->GetAttribute( item->GetString( "attributeName" ) );
+ if ( pAttr == NULL )
+ continue;
+
+ DmAttributeType_t attrType = pAttr->GetType();
+ if ( attrType == AT_ELEMENT )
+ {
+ children.AddToTail( pAttr->GetValue< DmElementHandle_t >() );
+ }
+ else if ( attrType == AT_ELEMENT_ARRAY )
+ {
+ const CDmrElementArray<> arrayAttr( pAttr );
+ int n = arrayAttr.Count();
+ int index = item->GetInt( "arrayIndex", -1 );
+ if ( index >= 0 )
+ {
+ children.AddToTail( arrayAttr[ index ] );
+ }
+ else
+ {
+ for ( int i = 0; i < n; ++i )
+ {
+ children.AddToTail( arrayAttr[ i ] );
+ }
+ }
+ }
+ }
+ }
+
+ // if this control is ever moved to vgui_controls, change the default format to "dmx", the generic dmx format
+ const char *pFileFormat = "movieobjects";
+ const char *pFileEncoding = g_pDataModel->GetDefaultEncoding( pFileFormat );
+ g_pDataModel->SaveToFile( pFullPath, NULL, pFileEncoding, pFileFormat, pRoot );
+
+ if ( nSelected > 1 )
+ {
+ DestroyElement( pRoot );
+ }
+}
+
+void CElementPropertiesTreeInternal::OnFileSelected( KeyValues *params )
+{
+ const char *pFullPath = params->GetString( "fullpath" );
+ KeyValues *pContext = params->FindKey( "context" );
+ const char *pCommand = pContext->GetString( "command" );
+ if ( V_strcmp( pCommand, "OnImportElement" ) == 0 )
+ {
+ OnImportElement( pFullPath, pContext );
+ }
+ else if ( V_strcmp( pCommand, "OnExportElement" ) == 0 )
+ {
+ OnExportElement( pFullPath, pContext );
+ }
+ else
+ {
+ Assert( 0 );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Creates an attribute data widget using a specifically requested widget
+//-----------------------------------------------------------------------------
+vgui::Panel *CElementPropertiesTreeInternal::CreateAttributeDataWidget( CDmElement *pElement,
+ const char *pWidgetName, CDmElement *obj, CDmAttribute *pAttribute, int nArrayIndex )
+{
+ AttributeWidgetInfo_t info;
+ SetupWidgetInfo( &info, pElement, pAttribute, nArrayIndex );
+ IAttributeWidgetFactory *pFactory = attributewidgetfactorylist->GetWidgetFactory( pWidgetName );
+ if ( !pFactory )
+ return NULL;
+ return pFactory->Create( NULL, info );
+}
+
+
+// ------------------------------------------------------------------------------
+
+void CElementPropertiesTreeInternal::UpdateTree()
+{
+ m_pTree->RemoveAll();
+ if ( m_hObject.Get() )
+ {
+ m_AttributeWidgets.RemoveAll();
+
+ char label[ 256 ];
+ Q_snprintf( label, sizeof( label ), "%s", m_hObject->GetValueString( "name" ) );
+ bool editableLabel = true;
+
+ KeyValues *kv = new KeyValues( "item" );
+ kv->SetString( "Text", label );
+ kv->SetInt( "Expand", 1 );
+ kv->SetInt( "dmeelement", m_hObject.Get() ? m_hObject.Get()->GetHandle() : DMELEMENT_HANDLE_INVALID );
+ kv->SetInt( "ownerelement", m_hObject.Get() ? m_hObject.Get()->GetHandle() : DMELEMENT_HANDLE_INVALID );
+ kv->SetString( "attributeName", "name" );
+ kv->SetInt( "root", m_hObject.Get() ? m_hObject.Get()->GetHandle() : DMELEMENT_HANDLE_INVALID);
+ kv->SetInt( "editablelabel", editableLabel ? 1 : 0 );
+
+ CDmElement *pElement = m_hObject.Get();
+ vgui::Panel *widget = CreateAttributeDataWidget( pElement, "element", pElement, NULL );
+
+ CUtlVector< Panel * > columns;
+ columns.AddToTail( NULL );
+ columns.AddToTail( widget );
+ int rootIndex = m_pTree->AddItem( kv, editableLabel, -1, columns );
+
+ m_pTree->GetTree()->SetItemFgColor( rootIndex, Color( 66, 196, 66, 255 ) );
+ m_pTree->GetTree()->SetItemSelectionUnfocusedBgColor( rootIndex, Color( 255, 153, 35, 255 ) );
+
+ kv->deleteThis();
+
+ // open up the root item (for now)
+ m_pTree->ExpandItem(rootIndex, true);
+
+ if ( m_SearchResultsRoot.Get() == m_hObject.Get() )
+ {
+ // Expand "results" too
+ TreeItem_t item;
+
+ item.m_pArrayElement = NULL;
+ item.m_pElement = m_SearchResultsRoot.Get();
+ item.m_pAttributeName = "results";
+
+ // Look for a match
+ int nChildIndex = FindTreeItem( rootIndex, item );
+ if ( nChildIndex >= 0 )
+ {
+ m_pTree->ExpandItem( nChildIndex, true );
+ }
+ }
+ }
+ m_pTree->InvalidateLayout();
+}
+
+void CElementPropertiesTreeInternal::GenerateDragDataForItem( int itemIndex, KeyValues *msg )
+{
+ KeyValues *data = m_pTree->GetItemData( itemIndex );
+ if ( !data || !msg )
+ {
+ return;
+ }
+
+ msg->SetInt( "dmeelement", data->GetInt( "dmeelement" ) );
+ msg->SetInt( "ownerelement", data->GetInt( "ownerelement" ) );
+ msg->SetString( "attributeName", data->GetString( "attributeName" ) );
+ msg->SetInt( "arrayIndex", data->GetInt( "arrayIndex" ) );
+
+ msg->SetString( "text", data->GetString( "Text" ) );
+}
+
+struct DataModelFilenameArray
+{
+ int Count() const
+ {
+ return g_pDataModel->NumFileIds();
+ }
+ const char *operator[]( int i ) const
+ {
+ return g_pDataModel->GetFileName( g_pDataModel->GetFileId( i ) );
+ }
+};
+
+void CElementPropertiesTreeInternal::GenerateContextMenu( int itemIndex, int x, int y )
+{
+ KeyValues *data = m_pTree->GetItemData( itemIndex );
+ if ( !data )
+ {
+ Assert( data );
+ return;
+ }
+
+ if ( m_hContextMenu.Get() )
+ {
+ delete m_hContextMenu.Get();
+ m_hContextMenu = NULL;
+ }
+
+ m_hContextMenu = new Menu( this, "ActionMenu" );
+ m_hContextMenu->SetFont( m_pTree->GetTree()->GetFont() );
+ Menu::PlaceContextMenu( this, m_hContextMenu.Get() );
+ int id;
+
+ // ----------------------------------------------------
+ // What have we clicked on?
+
+ // inspect the data
+ CDmElement *pElement = GetElementKeyValue< CDmElement >( data, "dmeelement" );
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( data, "ownerelement" );
+ const char *pAttributeName = data->GetString( "attributeName" );
+ int nArrayIndex = data->GetInt( "arrayIndex", -1 );
+
+ // get the type
+ CDmAttribute *pAttribute = pOwner->GetAttribute( pAttributeName );
+ DmAttributeType_t attributeType = pAttribute->GetType();
+
+ // figure out the context
+ CDmrGenericArray array( pAttribute );
+
+ bool bIsAttribute = data->IsEmpty( "arrayIndex" );
+ bool bIsArrayItem = !bIsAttribute;
+ bool bIsArrayAttribute = !bIsArrayItem && ( attributeType >= AT_FIRST_ARRAY_TYPE );
+ bool bIsArrayAttributeEmpty = bIsArrayAttribute && ( array.Count() == 0 );
+ bool bIsElementAttribute = bIsAttribute && ( attributeType == AT_ELEMENT );
+ bool bIsElementArrayAttribute = bIsArrayAttribute && ( attributeType == AT_ELEMENT_ARRAY );
+ bool bIsElementArrayItem = bIsArrayItem && ( attributeType == AT_ELEMENT_ARRAY );
+ bool bIsElementAttributeNull = bIsElementAttribute && ( pElement == NULL );
+
+ // ----------------------------------------------------
+ // menu title == what's my context? ( 3 x 2 )
+ // 3: Item | Array | Attribute
+ // 2: Element | Not Element
+
+ if ( bIsElementArrayItem )
+ {
+ m_hContextMenu->AddCheckableMenuItem( "* Element Item Operations *", this );
+ }
+ else if ( bIsElementArrayAttribute )
+ {
+ m_hContextMenu->AddCheckableMenuItem( "* Element Array Operations *", this );
+ }
+ else if ( bIsElementAttribute )
+ {
+ m_hContextMenu->AddCheckableMenuItem( "* Element Attribute Operations *", this );
+ }
+ else if ( bIsArrayItem )
+ {
+ m_hContextMenu->AddCheckableMenuItem( "* Item Operations *", this );
+ }
+ else if ( bIsArrayAttribute )
+ {
+ m_hContextMenu->AddCheckableMenuItem( "* Array Operations *", this );
+ }
+ else if ( bIsAttribute )
+ {
+ m_hContextMenu->AddCheckableMenuItem( "* Attribute Operations *", this );
+ }
+
+ m_hContextMenu->AddSeparator();
+
+ // ----------------------------------------------------
+ // basic ops:
+
+ // cut / copy / paste
+ m_hContextMenu->AddMenuItem( "#DmeElementPropertiesCut", new KeyValues( "OnCut" ), this );
+ m_hContextMenu->AddMenuItem( "#DmeElementPropertiesCopy", new KeyValues( "OnCopy" ), this );
+ id = m_hContextMenu->AddMenuItem( "#DmeElementPropertiesPaste", new KeyValues( "OnPaste" ), this );
+ m_hContextMenu->SetItemEnabled( id, vgui::system()->GetClipboardTextCount() > 0 );
+
+ // paste special
+ // Would have to get the clipboard contents and examine to enable a cascading "Paste Special" menu here
+ Menu *pasteSpecial = new Menu( this, "Paste Special" );
+ pasteSpecial->SetFont( m_pTree->GetTree()->GetFont() );
+ id = m_hContextMenu->AddCascadingMenuItem( "#DmeElementPropertiesPasteSpecial", this, pasteSpecial );
+ m_hContextMenu->SetItemEnabled( id, vgui::system()->GetClipboardTextCount() > 0 );
+ id = pasteSpecial->AddMenuItem( "Nothing Special", this );
+ pasteSpecial->SetItemEnabled( id, false );
+
+ // clear or remove
+ int removeItemID;
+ if ( bIsArrayAttribute && !bIsArrayAttributeEmpty )
+ {
+ removeItemID = m_hContextMenu->AddMenuItem( "#DmeElementPropertiesClear", new KeyValues( "OnRemove" ), this );
+ }
+ else
+ {
+ removeItemID = m_hContextMenu->AddMenuItem( "#DmeElementPropertiesRemove", new KeyValues( "OnRemove" ), this );
+ }
+
+ // ----------------------------------------------------
+ // other ops
+
+ // Rename...
+ if ( data->GetInt( "editablelabel" ) )
+ {
+ if ( bIsArrayItem )
+ {
+ m_hContextMenu->AddMenuItem( "Rename Element...", new KeyValues( "OnRename" ), this );
+ }
+ else
+ {
+ m_hContextMenu->AddMenuItem( "Rename Attribute...", new KeyValues( "OnRename" ), this );
+ }
+ }
+
+ // sort by name
+ if ( bIsElementArrayAttribute && !bIsArrayAttributeEmpty )
+ {
+ m_hContextMenu->AddMenuItem( "#DmeElementPropertiesSortByName", new KeyValues( "OnSortByName" ), this );
+ }
+
+ // ----------------------------------------------------
+ // Add item/attr/elem ops:
+
+ // Add Item
+ if ( bIsArrayAttribute && !bIsElementArrayAttribute )
+ {
+ m_hContextMenu->AddMenuItem( "#DmeElementPropertiesAddItem", new KeyValues( "OnAddItem" ), this );
+ }
+
+ // Add Attribute
+ if ( ( bIsElementAttribute && !bIsElementAttributeNull ) || m_pTree->GetTree()->GetRootItemIndex() == itemIndex || bIsElementArrayItem )
+ {
+ Menu *addMenu = new Menu( this, "AddAttribute" );
+ addMenu->SetFont( m_pTree->GetTree()->GetFont() );
+ m_hContextMenu->AddCascadingMenuItem( "#DmeElementPropertiesAddAttribute", this, addMenu );
+ {
+ for ( int i = AT_FIRST_VALUE_TYPE; i < AT_TYPE_COUNT; ++i )
+ {
+ const char *typeName = g_pDataModel->GetAttributeNameForType( (DmAttributeType_t)i );
+ if ( typeName && typeName[ 0 ] )
+ {
+ char add_attribute[ 256 ];
+ Q_snprintf( add_attribute, sizeof( add_attribute ), "attribute_%s", typeName );
+ id = addMenu->AddMenuItem( typeName, new KeyValues( "Command", "command", add_attribute ), this );
+ addMenu->GetMenuItem( id )->SetContentAlignment( Label::a_center );
+ }
+ }
+ }
+
+ }
+
+ // New, Add or Replace Element
+ if ( bIsElementAttribute || bIsElementArrayAttribute || bIsElementArrayItem )
+ {
+ Menu *addMenu = new Menu( this, "SetElement" );
+ addMenu->SetFont( m_pTree->GetTree()->GetFont() );
+
+ if ( bIsElementArrayAttribute )
+ {
+ m_hContextMenu->AddCascadingMenuItem( "#DmeElementPropertiesAddElement", this, addMenu );
+ }
+ else if ( bIsElementAttributeNull )
+ {
+ m_hContextMenu->AddCascadingMenuItem( "#DmeElementPropertiesNewElement", this, addMenu );
+ }
+ else if ( bIsElementAttribute || bIsElementArrayItem )
+ {
+ m_hContextMenu->AddCascadingMenuItem( "#DmeElementPropertiesReplaceElement", this, addMenu );
+ }
+
+ // Populate from factories
+ for ( int i = g_pDataModel->GetFirstFactory(); g_pDataModel->IsValidFactory( i ); i = g_pDataModel->GetNextFactory( i ) )
+ {
+ const char *elementType = g_pDataModel->GetFactoryName( i );
+ Assert( elementType && elementType[ 0 ] );
+
+ char add_element[ 256 ];
+ Q_snprintf( add_element, sizeof( add_element ), "element_%s", elementType );
+ id = addMenu->AddMenuItem( elementType, new KeyValues( "Command", "command", add_element ), this );
+ addMenu->GetMenuItem( id )->SetContentAlignment( Label::a_center );
+ }
+ }
+
+ // sharing
+ if ( ( bIsElementAttribute && !bIsElementAttributeNull ) || m_pTree->GetTree()->GetRootItemIndex() == itemIndex || bIsElementArrayAttribute || bIsElementArrayItem )
+ {
+ CUtlVector< KeyValues* > selected;
+ m_pTree->GetTree()->GetSelectedItemData( selected );
+ int nElements = 0;
+ int nShared = 0;
+ int nSelected = selected.Count();
+ for ( int i = 0; i < nSelected; ++i )
+ {
+ KeyValues *kv = selected[ i ];
+ CDmElement *pElement = GetElementKeyValue<CDmElement>( kv, "dmeelement" );
+
+ // element attribute or element array item
+ if ( pElement )
+ {
+ ++nElements;
+ if ( pElement->IsShared() )
+ {
+ ++nShared;
+ }
+ continue;
+ }
+
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( kv, "ownerelement" );
+ const char *pAttributeName = kv->GetString( "attributeName" );
+
+ const CDmrElementArray<> array( pOwner, pAttributeName );
+ if ( !array.IsValid() )
+ continue; // value attribute, value array item, or value array
+
+ // element array attribute
+ int nCount = array.Count();
+ for ( int j = 0; j < nCount; ++j )
+ {
+ CDmElement *pElement = array[ j ];
+ if ( !pElement )
+ continue;
+
+ ++nElements;
+ if ( pElement->IsShared() )
+ {
+ ++nShared;
+ }
+ }
+ }
+
+ if ( nShared < nElements )
+ {
+ m_hContextMenu->AddMenuItem( "Mark Shared", new KeyValues( "OnSetShared", "shared", 1 ), this );
+ }
+ if ( nShared > 0 )
+ {
+ m_hContextMenu->AddMenuItem( "Mark Not Shared", new KeyValues( "OnSetShared", "shared", 0 ), this );
+ }
+ }
+
+ // import element
+ if ( bIsElementAttribute || bIsElementArrayAttribute || bIsElementArrayItem )
+ {
+ KeyValues *pContext = new KeyValues( "context", "command", "OnImportElement" );
+ pContext->SetInt( "owner", ( int )pOwner->GetHandle() );
+ pContext->SetString( "attribute", pAttributeName );
+ pContext->SetInt( "index", nArrayIndex );
+
+ KeyValues *kv = new KeyValues( "OnShowFileDialog", "title", "Import Element" );
+ kv->SetInt( "openOnly", 1 );
+ kv->AddSubKey( pContext );
+ m_hContextMenu->AddMenuItem( "Import element...", kv, this );
+ }
+
+ // export element
+ if ( ( bIsElementAttribute && !bIsElementAttributeNull ) || m_pTree->GetTree()->GetRootItemIndex() == itemIndex || bIsElementArrayItem )
+ {
+ KeyValues *pContext = new KeyValues( "context", "command", "OnExportElement" );
+ pContext->SetInt( "element", ( int )pElement->GetHandle() );
+
+ KeyValues *kv = new KeyValues( "OnShowFileDialog", "title", "Export Element" );
+ kv->SetInt( "openOnly", 0 );
+ kv->AddSubKey( pContext );
+ m_hContextMenu->AddMenuItem( "Export element...", kv, this );
+ }
+
+ if ( pElement )
+ {
+ Menu *menu = new Menu( this, "ChangeFile" );
+ menu->SetFont( m_pTree->GetTree()->GetFont() );
+
+ m_hContextMenu->AddCascadingMenuItem( "#DmeElementPropertiesChangeFileAssociation", this, menu );
+
+ int nFiles = g_pDataModel->NumFileIds();
+ for ( int i = 0; i < nFiles; ++i )
+ {
+ DmFileId_t fileid = g_pDataModel->GetFileId( i );
+ const char *pFileName = g_pDataModel->GetFileName( fileid );
+
+ if ( !pFileName || !*pFileName )
+ continue; // skip invalid and default fileids
+
+ char cmd[ 256 ];
+ Q_snprintf( cmd, sizeof( cmd ), "element_changefile %s", pFileName );
+
+ const char *pText = pFileName;
+ char text[ 256 ];
+ if ( pElement->GetFileId() == fileid )
+ {
+ Q_snprintf( text, sizeof( text ), "* %s", pFileName );
+ pText = text;
+ }
+
+ menu->AddMenuItem( pText, new KeyValues( "OnChangeFile", "filename", pFileName ), this );
+ }
+
+ char filename[ MAX_PATH ];
+ V_GenerateUniqueName( filename, sizeof( filename ), "unnamed", DataModelFilenameArray() );
+
+ menu->AddMenuItem( "<new file>", new KeyValues( "OnChangeFile", "filename", filename ), this );
+ }
+
+ // ----------------------------------------------------
+ // finally add a seperator after the "Remove" item, unless it's the last item
+
+ if ( ( m_hContextMenu->GetItemCount() - 1 ) != removeItemID )
+ {
+ m_hContextMenu->AddSeparatorAfterItem( removeItemID );
+ }
+
+ // ----------------------------------------------------
+}
+
+void CElementPropertiesTreeInternal::GenerateChildrenOfNode( int itemIndex )
+{
+ KeyValues *data = m_pTree->GetItemData( itemIndex );
+ if ( !data )
+ {
+ Assert( data );
+ return;
+ }
+
+ //Check to see if this attribute refers to an element
+ CDmElement *obj = GetElementKeyValue<CDmElement>( data, "dmeelement" );
+ if ( obj )
+ {
+ InsertAttributes( itemIndex, obj );
+ return;
+ }
+
+ // Check to see if this node is an array entry, and then do nothing
+ if ( !data->IsEmpty( "arrayIndex" ) )
+ return;
+
+ // Check to see if this attribute is an array attribute
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( data, "ownerelement" );
+ if ( pOwner )
+ {
+ const char *pAttributeName = data->GetString( "attributeName" );
+ CDmAttribute *pAttribute = pOwner->GetAttribute( pAttributeName );
+ if ( pAttribute && IsArrayType( pAttribute->GetType() ) )
+ {
+ InsertAttributeArrayMembers( itemIndex, pOwner, pAttribute );
+ return;
+ }
+ }
+}
+
+void CElementPropertiesTreeInternal::OnLabelChanged( int itemIndex, const char *oldString, const char *newString )
+{
+ KeyValues *data = m_pTree->GetItemData( itemIndex );
+ if ( !data )
+ {
+ Assert( data );
+ return;
+ }
+
+ // No change!!!
+ if ( !Q_stricmp( oldString, newString ) )
+ return;
+
+ CDmElement *pElement = GetElementKeyValue< CDmElement >( data, "dmeelement" );
+ bool bEditableLabel = data->GetInt( "editablelabel" );
+
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( data, "ownerelement" );
+ const char *pAttributeName = data->GetString( "attributeName" );
+
+ int bIsAttribute = data->GetInt( "isAttribute" );
+
+ int nNotifyFlags = 0;
+ if ( bEditableLabel )
+ {
+ if ( pElement && !bIsAttribute )
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, "Rename Object" );
+ pElement->SetName( newString );
+ nNotifyFlags = NOTIFY_CHANGE_ATTRIBUTE_VALUE;
+ }
+ else if ( pOwner && pAttributeName )
+ {
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, "Rename Attribute" );
+ pOwner->RenameAttribute( pAttributeName, newString );
+ nNotifyFlags = NOTIFY_CHANGE_TOPOLOGICAL;
+ }
+ }
+
+ if ( nNotifyFlags )
+ {
+ Refresh( ( nNotifyFlags == NOTIFY_CHANGE_ATTRIBUTE_VALUE ) ? REFRESH_VALUES_ONLY : REFRESH_TREE_VIEW );
+ }
+}
+
+bool CElementPropertiesTreeInternal::IsItemDroppable( int itemIndex, CUtlVector< KeyValues * >& msglist )
+{
+ KeyValues *itemData = m_pTree->GetItemData( itemIndex );
+ if ( !itemData )
+ return false;
+
+ const char *elementType = itemData->GetString( "droppableelementtype" );
+ if ( !elementType || !elementType[ 0 ] )
+ return false;
+
+ CUtlVector< CDmElement * > list;
+ return ElementTree_GetDroppableItems( msglist, elementType, list );
+}
+
+HCursor CElementPropertiesTreeInternal::GetItemDropCursor( int itemIndex, CUtlVector< KeyValues * >& msglist )
+{
+ DropOperation_t op = GetDropOperation( itemIndex, msglist );
+ if ( op == DO_COPY )
+ return m_hDragCopyCursor;
+ if ( op == DO_MOVE )
+ return m_hDragMoveCursor;
+ Assert( op == DO_LINK );
+ return m_hDragLinkCursor;
+}
+
+struct ArrayItem_t
+{
+ ArrayItem_t( CDmAttribute *pAttr = NULL, int nIndex = -1 ) : m_pAttr( pAttr ), m_nIndex( nIndex ) {}
+
+ static bool LessFunc( const ArrayItem_t &lhs, const ArrayItem_t &rhs )
+ {
+ if ( lhs.m_pAttr != rhs.m_pAttr )
+ return lhs.m_pAttr < rhs.m_pAttr;
+ return lhs.m_nIndex < rhs.m_nIndex;
+ }
+
+ CDmAttribute *m_pAttr;
+ int m_nIndex;
+};
+
+void CElementPropertiesTreeInternal::DropItemsIntoArray( CDmrElementArray<> &array, CUtlVector< KeyValues* > &msglist, CUtlVector< CDmElement* > &list, int nArrayIndex, DropOperation_t op )
+{
+ int nElements = list.Count();
+ if ( op == DO_COPY )
+ {
+ CUtlVector< CDmElement* > copylist;
+ CopyElements( list, copylist );
+ list.Swap( copylist );
+ }
+ else if ( op == DO_MOVE )
+ {
+ m_pTree->GetTree()->ClearSelection();
+
+ CUtlRBTree< ArrayItem_t > arrayItemSorter( 0, msglist.Count(), ArrayItem_t::LessFunc );
+
+ // sort all element array items and set element attributes to NULL
+ int nMsgs = msglist.Count();
+ for ( int i = 0; i < nMsgs; ++i )
+ {
+ KeyValues *itemData = msglist[ i ];
+
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( itemData, "ownerelement" );
+ const char *pAttributeName = itemData->GetString( "attributeName" );
+
+ if ( !pOwner || !pAttributeName || !*pAttributeName )
+ continue;
+
+ bool isArrayElement = !itemData->IsEmpty( "arrayIndex" );
+ CDmAttribute *pAttribute = pOwner->GetAttribute( pAttributeName, isArrayElement ? AT_ELEMENT_ARRAY : AT_ELEMENT );
+ if ( !pAttribute )
+ continue;
+
+ if ( isArrayElement )
+ {
+ int nIndex = itemData->GetInt( "arrayIndex", -1 );
+ if ( nIndex < 0 )
+ continue;
+
+ arrayItemSorter.Insert( ArrayItem_t( pAttribute, nIndex ) );
+ }
+ else
+ {
+ pAttribute->SetValue( DMELEMENT_HANDLE_INVALID );
+ }
+ }
+
+ // walk through all array items, back to front, so that removing won't mess up the indices
+ for ( int i = arrayItemSorter.LastInorder(); i != arrayItemSorter.InvalidIndex(); i = arrayItemSorter.PrevInorder( i ) )
+ {
+ ArrayItem_t &arrayItem = arrayItemSorter[ i ];
+
+ CDmrElementArray<> srcArray( arrayItem.m_pAttr );
+ srcArray.Remove( arrayItem.m_nIndex );
+
+ if ( arrayItem.m_pAttr == array.GetAttribute() && arrayItem.m_nIndex < nArrayIndex )
+ {
+ --nArrayIndex; // update nArrayIndex when items before it are removed
+ }
+ }
+ }
+
+ int base = array.InsertMultipleBefore( nArrayIndex, nElements );
+ // array.SetMultiple( base, nElements, list.Base() );
+ for ( int i = 0; i < nElements; ++i )
+ {
+ array.Set( base + i, list[ i ] );
+ if ( array[ base + i ] != list[ i ] )
+ {
+ // if couldn't be dropped into array, skip it and merge remaining items down by one
+ Assert( array[ base + i ] == NULL );
+ array.Remove( base + nElements - 1 );
+ --base;
+ }
+ }
+}
+
+CElementPropertiesTreeInternal::DropOperation_t CElementPropertiesTreeInternal::GetDropOperation( int itemIndex, CUtlVector< KeyValues * >& msglist )
+{
+ bool bCtrlDown = input()->IsKeyDown( KEY_LCONTROL ) || input()->IsKeyDown( KEY_RCONTROL );
+ bool bAltDown = input()->IsKeyDown( KEY_LALT ) || input()->IsKeyDown( KEY_RALT );
+ bool bShiftDown = input()->IsKeyDown( KEY_LSHIFT ) || input()->IsKeyDown( KEY_RSHIFT );
+
+ if ( bAltDown || ( bShiftDown && bCtrlDown ) )
+ return DO_LINK;
+ if ( bCtrlDown )
+ return DO_COPY;
+ if ( bShiftDown )
+ return DO_MOVE;
+
+ KeyValues *itemData = m_pTree->GetItemData( itemIndex );
+ Assert( itemData );
+ if ( !itemData )
+ return DO_LINK;
+
+ if ( !ElementTree_IsArrayItem( itemData ) && ElementTree_GetAttributeType( itemData ) != AT_ELEMENT_ARRAY )
+ return DO_LINK; // dropping to a non-array attribute
+
+ DropOperation_t op = DO_UNKNOWN;
+ int nMsgs = msglist.Count();
+ for ( int i = 0; i < nMsgs; ++i )
+ {
+ KeyValues *pMsg = msglist[ i ];
+ if ( !pMsg || !GetElementKeyValue< CDmElement >( pMsg , "dmeelement" ) )
+ continue; // skip non-element drag/drop items
+
+ if ( !ElementTree_IsArrayItem( pMsg ) || ElementTree_GetAttributeType( pMsg ) != AT_ELEMENT_ARRAY )
+ return DO_LINK; // dragging from a non-array attribute
+
+ op = DO_MOVE; // basically, op will only stay DO_MOVE if *every* item is a non-element or is an array (or array item)
+ }
+
+ if ( op == DO_UNKNOWN )
+ {
+ Assert( 0 );
+ return DO_LINK;
+ }
+
+ return op;
+}
+
+void CElementPropertiesTreeInternal::OnItemDropped( int itemIndex, CUtlVector< KeyValues * >& msglist )
+{
+ if ( !msglist.Count() )
+ return;
+
+ KeyValues *itemData = m_pTree->GetItemData( itemIndex );
+ if ( !itemData )
+ return;
+
+ const char *elementType = itemData->GetString( "droppableelementtype" );
+ if ( !elementType || !elementType[ 0 ] )
+ return;
+
+ CUtlVector< CDmElement * > list;
+ ElementTree_GetDroppableItems( msglist, "dmeelement", list );
+ if ( !list.Count() )
+ return;
+
+ bool isArrayElement = !itemData->IsEmpty( "arrayIndex" );
+ //Check to see if this attribute refers to an element
+ CDmElement *pOwner = GetElementKeyValue< CDmElement >( itemData, "ownerelement" );
+ const char *pAttributeName = itemData->GetString( "attributeName" );
+
+ if ( !pOwner )
+ return;
+
+ if ( !pAttributeName[ 0 ] )
+ return;
+
+ CDmAttribute *pAttribute = pOwner->GetAttribute( pAttributeName );
+ DmAttributeType_t attType = pAttribute ? pAttribute->GetType() : AT_UNKNOWN;
+ bool isElementAttribute = attType == AT_ELEMENT || attType == AT_ELEMENT_ARRAY;
+ if ( !isElementAttribute )
+ return;
+
+ DropOperation_t op = GetDropOperation( itemIndex, msglist );
+
+ const char *cmd = msglist[ 0 ]->GetString( "command" );
+
+ // Mouse if over an array entry which is an element array type...
+ if ( isArrayElement )
+ {
+ bool bReplace = Q_stricmp( cmd, "replace" ) == 0;
+ bool bBefore = Q_stricmp( cmd, "before" ) == 0;
+ bool bAfter = Q_stricmp( cmd, "after" ) == 0 || Q_stricmp( cmd, "default" ) == 0;
+ if ( !bReplace && !bBefore && !bAfter )
+ {
+ Warning( "Unknown command '%s'\n", cmd );
+ return;
+ }
+
+ char str[ 128 ];
+ V_snprintf( str, sizeof( str ), "%s %s element%s",
+ bReplace ? "Replace with" : "Insert",
+ op == DO_COPY ? "copied" : ( op == DO_MOVE ? "moved" : "referenced" ),
+ bBefore ? " before" : bAfter ? " after" : "" );
+
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, str );
+
+ int nArrayIndex = itemData->GetInt( "arrayIndex" );
+ if ( bAfter )
+ {
+ ++nArrayIndex;
+ }
+
+ CDmrElementArray<> array( pAttribute );
+ if ( bReplace )
+ {
+ array.Remove( nArrayIndex );
+ }
+
+ DropItemsIntoArray( array, msglist, list, nArrayIndex, op );
+ }
+ // Mouse is over an element attribute or element array attribute
+ else
+ {
+ // No head/tail stuff for AT_ELEMENT, just replace what's there
+ if ( attType == AT_ELEMENT )
+ {
+ char str[ 128 ];
+ V_snprintf( str, sizeof( str ), "Replace with %s element",
+ op == DO_COPY ? "copied" : ( op == DO_MOVE ? "moved" : "referenced" ) );
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, str );
+
+ pAttribute->SetValue( op == DO_COPY ? list[ 0 ]->Copy() : list[ 0 ] );
+
+ if ( op == DO_MOVE )
+ {
+ int c = msglist.Count();
+ for ( int i = 0; i < c; ++i )
+ {
+ KeyValues *data = msglist[ i ];
+ CDmElement *e = GetElementKeyValue<CDmElement>( data, "dmeelement" );
+ Assert( !e || e == list[ 0 ] );
+ if ( e != list[ 0 ] )
+ continue;
+
+ OnRemoveFromData( data );
+ break;
+ }
+ m_pTree->GetTree()->ClearSelection();
+ }
+ }
+ else
+ {
+ bool bTail = !cmd[ 0 ] || !Q_stricmp( cmd, "default" ) || !Q_stricmp( cmd, "tail" );
+ bool bHead = !bTail && !Q_stricmp( cmd, "head" );
+ bool bReplace = !bTail && !bHead && !Q_stricmp( cmd, "replace" );
+ if ( !bTail && !bHead && !bReplace )
+ {
+ Warning( "Unknown command '%s'\n", cmd );
+ return;
+ }
+
+ char str[ 128 ];
+ V_snprintf( str, sizeof( str ), "%s %s elements%s",
+ bReplace ? "Replace array with" : "Insert",
+ op == DO_COPY ? "copied" : ( op == DO_MOVE ? "moved" : "referenced" ),
+ bHead ? " at head" : bTail ? " at tail" : "" );
+
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, str );
+
+ CDmrElementArray<> array( pAttribute );
+ if ( bReplace )
+ {
+ array.RemoveAll();
+ }
+
+ DropItemsIntoArray( array, msglist, list, bTail ? array.Count() : 0, op );
+ }
+ }
+
+ CUtlVector< TreeItem_t > dropTargetPath;
+ if ( isArrayElement )
+ {
+ itemIndex = m_pTree->GetTree()->GetItemParent( itemIndex ); // if we're an array element, start with the array itself
+ }
+ GetPathToItem( dropTargetPath, itemIndex );
+
+ // Does a forced refresh
+ Refresh( REFRESH_TREE_VIEW );
+
+ itemIndex = OpenPath( dropTargetPath );
+ if ( attType == AT_ELEMENT_ARRAY )
+ {
+ m_pTree->GetTree()->ExpandItem( itemIndex, true );
+ }
+
+ if ( op == DO_MOVE )
+ {
+ if ( isArrayElement || attType == AT_ELEMENT_ARRAY )
+ {
+ int nElements = list.Count();
+ for ( int i = 0; i < nElements; ++i )
+ {
+ int nChildren = m_pTree->GetTree()->GetNumChildren( itemIndex );
+ for ( int ci = 0; ci < nChildren; ++ci )
+ {
+ int nChildItem = m_pTree->GetTree()->GetChild( itemIndex, ci );
+ KeyValues *pChildData = m_pTree->GetTree()->GetItemData( nChildItem );
+ if ( list[ i ] == GetElementKeyValue< CDmElement >( pChildData, "dmeelement" ) )
+ {
+ m_pTree->GetTree()->AddSelectedItem( nChildItem, false );
+ }
+ }
+ }
+ }
+ else
+ {
+ m_pTree->GetTree()->AddSelectedItem( itemIndex, true );
+ }
+ }
+}
+
+bool CElementPropertiesTreeInternal::GetItemDropContextMenu( int itemIndex, Menu *menu, CUtlVector< KeyValues * >& msglist )
+{
+ KeyValues *itemData = m_pTree->GetItemData( itemIndex );
+
+ bool isArrayElement = !itemData->IsEmpty( "arrayIndex" );
+ //Check to see if this attribute refers to an element
+ CDmElement *pOwner = GetElementKeyValue<CDmElement>( itemData, "ownerelement" );
+ const char *pAttributeName = itemData->GetString( "attributeName" );
+
+ if ( !pOwner )
+ return false;
+ if ( !pAttributeName[ 0 ] )
+ return false;
+
+ bool isElementAttribute = false;
+ CDmAttribute *pAttribute = pOwner->GetAttribute( pAttributeName );
+ DmAttributeType_t attType = pAttribute ? pAttribute->GetType() : AT_UNKNOWN;
+ switch ( attType )
+ {
+ default:
+ break;
+ case AT_ELEMENT:
+ case AT_ELEMENT_ARRAY:
+ isElementAttribute = true;
+ break;
+ }
+
+ if ( isArrayElement && isElementAttribute )
+ {
+ menu->AddMenuItem( "After", "Insert after", "after", this );
+ menu->AddMenuItem( "Before", "Insert before", "before", this );
+ menu->AddMenuItem( "Replace", "Replace", "replace", this );
+ return true;
+ }
+ else
+ {
+ if ( isElementAttribute && attType == AT_ELEMENT_ARRAY )
+ {
+ CDmrGenericArray array( pAttribute );
+ if ( array.IsValid() && array.Count() > 0 )
+ {
+ menu->AddMenuItem( "Tail", "Insert at tail", "tail", this );
+ menu->AddMenuItem( "Head", "Insert at head", "head", this );
+ menu->AddMenuItem( "Replace", "Replace", "replace", this );
+ return true;
+ }
+ }
+ }
+
+ return false;
+}
+
+
+//-----------------------------------------------------------------------------
+// Set/get object
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::SetObject( CDmElement *object )
+{
+ m_pTree->RemoveAll();
+ m_AttributeWidgets.RemoveAll();
+
+ AddToHistory( object );
+
+ m_hObject = object;
+
+ Init( );
+}
+
+CDmElement *CElementPropertiesTreeInternal::GetObject()
+{
+ return m_hObject.Get();
+}
+
+
+//-----------------------------------------------------------------------------
+// Gets tree view text
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::GetTreeViewText( CDmElement* obj, CDmAttribute *pAttribute, int nArrayIndex, char *pBuffer, int nMaxLen, bool& editableText )
+{
+ pBuffer[0] = 0;
+
+ editableText = false;
+
+ if ( !obj )
+ return;
+
+ const char *pAttributeName = pAttribute->GetName();
+
+ if ( nArrayIndex < 0 )
+ {
+ // non-array types
+ Q_strncpy( pBuffer, pAttributeName, nMaxLen );
+
+ editableText = !pAttribute->IsFlagSet( FATTRIB_EXTERNAL ) && !pAttribute->IsFlagSet( FATTRIB_READONLY );
+ }
+ else
+ {
+ // array types
+ DmAttributeType_t type = pAttribute->GetType( );
+ if ( type == AT_ELEMENT_ARRAY )
+ {
+ const CDmrElementArray<> elementArray( pAttribute );
+ CDmElement *pEntryElement = elementArray[nArrayIndex];
+ if ( pEntryElement )
+ {
+ Q_snprintf( pBuffer, nMaxLen, "%s", pEntryElement->GetValueString( "name" ) );
+ editableText = true;
+ }
+ }
+ else
+ {
+ Q_snprintf( pBuffer, nMaxLen, "%s[%d]", pAttributeName, nArrayIndex );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Finds the tree index of a child matching the particular element + attribute
+//-----------------------------------------------------------------------------
+int CElementPropertiesTreeInternal::FindTreeItem( int nParentIndex, const TreeItem_t &info )
+{
+ // Look for a match
+ int nCount = m_pTree->GetTree()->GetNumChildren( nParentIndex );
+ for ( int i = nCount; --i >= 0; )
+ {
+ int nChildIndex = m_pTree->GetTree()->GetChild( nParentIndex, i );
+ KeyValues *data = m_pTree->GetItemData( nChildIndex );
+ Assert( data );
+
+ CDmElement *pElement = GetElementKeyValue< CDmElement >( data, "ownerelement" );
+ const char *pAttributeName = data->GetString( "attributeName" );
+ CDmElement *pArrayElement = NULL;
+ if ( data->GetInt( "arrayIndex", -1 ) != -1 )
+ {
+ // Only arrays of element pointers should refer to this
+ pArrayElement = GetElementKeyValue< CDmElement >( data, "dmeelement" );
+ }
+
+ if ( ( pElement == info.m_pElement ) && ( pArrayElement == info.m_pArrayElement ) &&
+ !Q_stricmp( pAttributeName, info.m_pAttributeName ) )
+ {
+ return nChildIndex;
+ }
+ }
+ return -1;
+}
+
+void CElementPropertiesTreeInternal::SpewOpenItems( int depth, OpenItemTree_t &tree, int nOpenTreeIndex, int nItemIndex )
+{
+ int i = tree.FirstChild( nOpenTreeIndex );
+ if ( nOpenTreeIndex != tree.InvalidIndex() )
+ {
+ TreeInfo_t& info = tree[ nOpenTreeIndex ];
+
+ if ( info.m_nFlags & EP_EXPANDED )
+ {
+ Msg( "[%d] Marking %s <%s> %s array(%s) [expanded %i]\n",
+ depth,
+ info.m_Item.m_pElement->GetName(),
+ info.m_Item.m_pElement->GetTypeString(),
+ info.m_Item.m_pAttributeName.Get(),
+ info.m_Item.m_pArrayElement ? info.m_Item.m_pArrayElement->GetName() : "NULL",
+ info.m_nFlags & EP_EXPANDED ? 1 : 0 );
+ }
+ }
+
+ while ( i != tree.InvalidIndex() )
+ {
+ TreeInfo_t& info = tree[ i ];
+ // Look for a match
+ int nChildIndex = FindTreeItem( nItemIndex, info.m_Item );
+ if ( nChildIndex != -1 )
+ {
+ SpewOpenItems( depth + 1, tree, i, nChildIndex );
+ }
+ else
+ {
+ }
+ i = tree.NextSibling( i );
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Expands all items in the open item tree if they exist
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::ExpandOpenItems( OpenItemTree_t &tree, int nOpenTreeIndex, int nItemIndex, bool makeVisible )
+{
+ int i = tree.FirstChild( nOpenTreeIndex );
+ if ( nOpenTreeIndex != tree.InvalidIndex() )
+ {
+ TreeInfo_t& info = tree[ nOpenTreeIndex ];
+ if ( info.m_nFlags & EP_EXPANDED )
+ {
+ // Expand the item
+ m_pTree->ExpandItem( nItemIndex , true );
+ }
+ if ( info.m_nFlags & EP_SELECTED )
+ {
+ m_pTree->GetTree()->AddSelectedItem( nItemIndex, false, false );
+ if ( makeVisible )
+ {
+ m_pTree->GetTree()->MakeItemVisible( nItemIndex );
+ }
+ }
+ }
+
+ while ( i != tree.InvalidIndex() )
+ {
+ TreeInfo_t& info = tree[ i ];
+ // Look for a match
+ int nChildIndex = FindTreeItem( nItemIndex, info.m_Item );
+ if ( nChildIndex != -1 )
+ {
+ ExpandOpenItems( tree, i, nChildIndex, makeVisible );
+ }
+ else
+ {
+ if ( info.m_nFlags & EP_SELECTED )
+ {
+ // Look for preserved item
+ int nChildIndex = FindTreeItem( nItemIndex, info.m_Preserved );
+ if ( nChildIndex != -1 )
+ {
+ m_pTree->GetTree()->AddSelectedItem( nChildIndex, false, false );
+ if ( makeVisible )
+ {
+ m_pTree->GetTree()->MakeItemVisible( nChildIndex );
+ }
+ }
+ }
+ }
+ i = tree.NextSibling( i );
+ }
+}
+
+void CElementPropertiesTreeInternal::FillInDataForItem( TreeItem_t &item, int nItemIndex )
+{
+ KeyValues *data = m_pTree->GetItemData( nItemIndex );
+ if ( !data )
+ return;
+
+ item.m_pElement = GetElementKeyValue< CDmElement >( data, "ownerelement" );
+ item.m_pAttributeName = data->GetString( "attributeName" );
+ if ( data->GetInt( "arrayIndex", -1 ) != -1 )
+ {
+ // Only arrays of element pointers should refer to this
+ item.m_pArrayElement = GetElementKeyValue< CDmElement >( data, "dmeelement" );
+ }
+ else
+ {
+ item.m_pArrayElement = NULL;
+ }
+}
+
+//-----------------------------------------------------------------------------
+// Builds a list of open items
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::BuildOpenItemList( OpenItemTree_t &tree, int nParent, int nItemIndex, bool preservePrevSelectedItem )
+{
+ KeyValues *data = m_pTree->GetItemData( nItemIndex );
+ if ( !data )
+ return;
+
+ bool expanded = m_pTree->IsItemExpanded( nItemIndex );
+ bool selected = m_pTree->IsItemSelected( nItemIndex );
+
+ int flags = 0;
+ if ( expanded )
+ {
+ flags |= EP_EXPANDED;
+ }
+ if ( selected )
+ {
+ flags |= EP_SELECTED;
+ }
+
+ int nChild = tree.InsertChildAfter( nParent, tree.InvalidIndex() );
+ TreeInfo_t &info = tree[nChild];
+ FillInDataForItem( info.m_Item, nItemIndex );
+ info.m_nFlags = flags;
+
+ if ( selected )
+ {
+ // Set up prev an next item
+ int preserve = preservePrevSelectedItem
+ ? m_pTree->GetTree()->GetPrevChildItemIndex( nItemIndex ) :
+ m_pTree->GetTree()->GetNextChildItemIndex( nItemIndex );
+
+ if ( preserve != -1 )
+ {
+ FillInDataForItem( info.m_Preserved, preserve );
+ }
+ }
+
+ // Deal with children
+ int nCount = m_pTree->GetTree()->GetNumChildren( nItemIndex );
+ for ( int i = 0; i < nCount; ++i )
+ {
+ int nChildIndex = m_pTree->GetTree()->GetChild( nItemIndex, i );
+ BuildOpenItemList( tree, nChild, nChildIndex, preservePrevSelectedItem );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Builds a list of open items
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::RefreshTreeView( bool preservePrevSelectedItem /*= false*/ )
+{
+ int nIndex = m_pTree->GetTree()->GetRootItemIndex();
+ if ( nIndex >= 0 )
+ {
+ // remember where the tree is scrolled to
+ ScrollBar *sBar = ((CElementTree *)m_pTree->GetTree())->GetScrollBar();
+ int sBarPos = sBar->GetValue();
+
+ // Build a tree of every open item in the tree view
+ OpenItemTree_t openItems;
+ BuildOpenItemList( openItems, openItems.InvalidIndex(), nIndex, preservePrevSelectedItem );
+
+ // Close the tree and re-create the root node only
+ UpdateTree();
+
+ // NOTE: Updating the tree could have changed the root item index
+ nIndex = m_pTree->GetTree()->GetRootItemIndex();
+
+ // Iterate through all previously open items and expand them if they exist
+ if ( openItems.Root() != openItems.InvalidIndex() )
+ {
+ ExpandOpenItems( openItems, openItems.Root(), nIndex, false );
+ }
+
+ // and now set the scroll pos back to where is was
+ // note: the layout needs to be re-Performed so that the
+ // scrollbars _range values are corrent or the SetValue will fail.
+ m_pTree->GetTree()->PerformLayout();
+ sBar->SetValue( sBarPos );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Refreshes the color state of the tree
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::SetTreeItemColor( int nItemID, CDmElement *pEntryElement, bool bIsElementArrayItem, bool bEditableLabel )
+{
+ // dim any element tree items if they are muted or not visible
+ bool bIsDim = false;
+ int dimAlpha = 128;
+ if ( pEntryElement != NULL )
+ {
+ if ( ( pEntryElement->HasAttribute( "visible" ) && !pEntryElement->GetValue< bool >( "visible" ) )
+ || ( pEntryElement->HasAttribute( "mute" ) && pEntryElement->GetValue< bool >( "mute" ) ) )
+ {
+ bIsDim = true;
+ }
+ }
+
+ // the unfocused Bg color should match the focused one
+ // so that we can dim lables based on visibility and mute state.
+ // note: focus is unimportant in this context ( I'm pretty sure )
+ m_pTree->GetTree()->SetItemSelectionBgColor( nItemID, Color( 255, 153, 35, bIsDim ? dimAlpha : 255 ) );
+ m_pTree->GetTree()->SetItemSelectionUnfocusedBgColor( nItemID, Color( 255, 153, 35, bIsDim ? dimAlpha : 255 ) );
+
+ if ( bIsElementArrayItem )
+ {
+ // element array items are green
+ m_pTree->GetTree()->SetItemFgColor( nItemID, Color( 66, 196, 66, bIsDim ? dimAlpha : 255 ) );
+ }
+ else if ( bEditableLabel )
+ {
+ // custom attributes are light yellow
+ m_pTree->GetTree()->SetItemFgColor( nItemID, Color( 190, 190, 105, bIsDim ? dimAlpha : 255 ) );
+ }
+ else
+ {
+ // otherwise it's just light grey
+ m_pTree->GetTree()->SetItemFgColor( nItemID, Color( 160, 160, 160, bIsDim ? dimAlpha : 255 ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Refreshes the color state of the tree
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::RefreshTreeItemState( int nItemID )
+{
+ if ( nItemID < 0 )
+ return;
+
+ KeyValues *kv = m_pTree->GetTree()->GetItemData( nItemID );
+ CDmElement *pEntryElement = GetElementKeyValue<CDmElement>( kv, "dmeelement" );
+ bool bIsElementArrayItem = kv->GetInt( "elementArrayItem", 0 );
+ bool bEditableLabel = kv->GetInt( "editablelabel", 0 );
+ SetTreeItemColor( nItemID, pEntryElement, bIsElementArrayItem, bEditableLabel );
+
+ int nChildCount = m_pTree->GetTree()->GetNumChildren( nItemID );
+ for ( int i = 0; i < nChildCount; ++i )
+ {
+ int nChildID = m_pTree->GetTree()->GetChild( nItemID, i );
+ RefreshTreeItemState( nChildID );
+ }
+}
+
+
+/*
+//-----------------------------------------------------------------------------
+// Adds a single entry into the tree
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::SetTreeEntryDimState( )
+{
+
+}
+*/
+
+
+//-----------------------------------------------------------------------------
+// Adds a single entry into the tree
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::CreateTreeEntry( int parentNodeIndex, CDmElement* obj, CDmAttribute *pAttribute, int nArrayIndex, AttributeWidgets_t &widgets )
+{
+ char pText[ 512 ];
+ bool bEditableLabel = false;
+ bool bIsExpandable = false;
+ CDmElement *pEntryElement = NULL;
+
+ GetTreeViewText( obj, pAttribute, nArrayIndex, pText, sizeof(pText), bEditableLabel );
+
+ const char *pAttributeName = pAttribute->GetName();
+ DmAttributeType_t type = pAttribute->GetType( );
+
+ bool bIsArrayItem = ( nArrayIndex > -1 );
+ bool bIsArrayAttribute = ( type >= AT_FIRST_ARRAY_TYPE ) && ! bIsArrayItem;
+ bool bIsElementAttribute = ( type == AT_ELEMENT ) && ! bIsArrayItem;
+ bool bIsElementArrayItem = ( type == AT_ELEMENT_ARRAY ) && bIsArrayItem;
+ bool bIsElementArrayAttribute = ( type == AT_ELEMENT_ARRAY ) && ! bIsArrayItem;
+
+ bool bIsDroppable = bIsElementArrayItem || bIsElementAttribute || bIsElementArrayAttribute;
+
+ if ( bIsElementArrayItem )
+ {
+ const CDmrElementArray<> elementArray( pAttribute );
+ pEntryElement = elementArray[nArrayIndex];
+ bIsExpandable = true;
+ }
+ else if ( bIsElementAttribute )
+ {
+ pEntryElement = obj->GetValueElement< CDmElement>( pAttributeName );
+ bIsExpandable = ( pEntryElement != NULL );
+ }
+ else if ( bIsArrayAttribute )
+ {
+ CDmrGenericArray array( pAttribute );
+ bIsExpandable = array.Count() > 0;
+ }
+
+ KeyValues *kv = new KeyValues( "item" );
+ kv->SetString( "Text", pText );
+ kv->SetInt( "Expand", bIsExpandable );
+ SetElementKeyValue( kv, "dmeelement", pEntryElement );
+ SetElementKeyValue( kv, "ownerelement", obj );
+ kv->SetString( "attributeName", pAttributeName );
+ kv->SetPtr( "widget", widgets.m_pValueWidget );
+ kv->SetInt( "elementArrayItem", bIsElementArrayItem ? 1 : 0 );
+ kv->SetInt( "editablelabel", bEditableLabel ? 1 : 0 );
+ kv->SetInt( "isAttribute", bIsArrayItem ? 0 : 1 );
+ kv->SetInt( "droppable", bIsDroppable ? 1 : 0 );
+ kv->SetFloat( "drophoverdelay", 1.0f );
+
+ if ( bIsArrayItem )
+ {
+ kv->SetInt( "arrayIndex", nArrayIndex );
+ }
+
+ if ( bIsDroppable )
+ {
+ // Can always drop onto arrays
+ kv->SetString( "droppableelementtype", "dmeelement" ); // FIXME: Should be able to restrict to certain types!!!
+ }
+
+ CUtlVector< vgui::Panel * > columns;
+ columns.AddToTail( NULL );
+ columns.AddToTail( widgets.m_pValueWidget );
+ int itemIndex = m_pTree->AddItem( kv, bEditableLabel, parentNodeIndex, columns );
+ SetTreeItemColor( itemIndex, pEntryElement, bIsElementArrayItem, bEditableLabel );
+ kv->deleteThis();
+}
+
+//-----------------------------------------------------------------------------
+// Sets up the attribute widget init info for a particular attribute
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::SetupWidgetInfo( AttributeWidgetInfo_t *pInfo, CDmElement *obj, CDmAttribute *pAttribute, int nArrayIndex )
+{
+ const char *pAttributeName = pAttribute ? pAttribute->GetName() : "";
+
+ pInfo->m_pNotify = m_pNotify;
+ pInfo->m_bAutoApply = m_bAutoApply;
+ pInfo->m_pElement = obj;
+ pInfo->m_pAttributeName = pAttributeName;
+ pInfo->m_nArrayIndex = nArrayIndex;
+ pInfo->m_pEditorTypeDictionary = m_hTypeDictionary;
+ pInfo->m_pEditorInfo = NULL;
+ pInfo->m_bShowMemoryUsage = m_bShowMemoryUsage;
+ if ( m_hTypeDictionary && pAttributeName )
+ {
+ if ( nArrayIndex < 0 )
+ {
+ pInfo->m_pEditorInfo = m_hTypeDictionary->GetAttributeInfo( obj, pAttributeName );
+ }
+ else
+ {
+ pInfo->m_pEditorInfo = m_hTypeDictionary->GetAttributeArrayInfo( obj, pAttributeName );
+ }
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds a single editable attributes of the element to the tree
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::InsertSingleAttribute( int parentNodeIndex, CDmElement *obj, CDmAttribute *pAttribute, int nArrayIndex )
+{
+ const char *attributeName = pAttribute->GetName();
+ NOTE_UNUSED( attributeName );
+
+ // Get information about the widget to create
+
+ IAttributeWidgetFactory *pFactory = NULL;
+ if ( nArrayIndex >= 0 )
+ {
+ pFactory = attributewidgetfactorylist->GetArrayWidgetFactory( obj, pAttribute, m_hTypeDictionary );
+ }
+ else
+ {
+ pFactory = attributewidgetfactorylist->GetWidgetFactory( obj, pAttribute, m_hTypeDictionary );
+ }
+ if ( !pFactory )
+ return;
+
+ // Create the widget
+ AttributeWidgetInfo_t info;
+ SetupWidgetInfo( &info, obj, pAttribute, nArrayIndex );
+
+ AttributeWidgets_t attributeWidget;
+ attributeWidget.m_pValueWidget = pFactory->Create( NULL, info );
+
+ // set it to the current font size
+ CBaseAttributePanel *attrPanel = dynamic_cast< CBaseAttributePanel * >( attributeWidget.m_pValueWidget );
+ if ( attrPanel )
+ {
+ attrPanel->SetFont( m_pTree->GetFont( m_pTree->GetFontSize() ) );
+ }
+
+ // Now create the tree-view entry
+ CreateTreeEntry( parentNodeIndex, obj, pAttribute, nArrayIndex, attributeWidget );
+
+ // Add the attribute to the list of them
+ m_AttributeWidgets.AddToTail( attributeWidget );
+}
+
+
+//-----------------------------------------------------------------------------
+// Used to insert attributes in alphabetical order
+//-----------------------------------------------------------------------------
+struct AttributeInfo_t
+{
+ CDmAttribute *m_pAttribute;
+ const char *m_pName;
+};
+
+
+//-----------------------------------------------------------------------------
+// Adds editable attributes of the element to the tree
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::InsertAttributes( int parentNodeIndex, CDmElement *obj )
+{
+ Assert( obj );
+
+ // Build a list of attributes for sorting
+ AttributeInfo_t *pInfo = (AttributeInfo_t*)_alloca( obj->AttributeCount() * sizeof(AttributeInfo_t) );
+ int nCount = 0;
+ for ( CDmAttribute *pAttribute = obj->FirstAttribute(); pAttribute; pAttribute = pAttribute->NextAttribute() )
+ {
+ pInfo[nCount].m_pAttribute = pAttribute;
+ pInfo[nCount].m_pName = pAttribute->GetName();
+ ++nCount;
+ }
+
+ // Iterate over each element and create a widget and tree entry for it
+ for ( int i = nCount - 1; i >= 0; --i )
+ {
+ InsertSingleAttribute( parentNodeIndex, obj, pInfo[i].m_pAttribute );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Removes an item from the tree recursively
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::RemoveItem_R( int nItemIndex )
+{
+ KeyValues *data = m_pTree->GetItemData( nItemIndex );
+ if ( data )
+ {
+ AttributeWidgets_t search;
+ search.m_pValueWidget = static_cast<vgui::Panel*>( data->GetPtr( "widget", NULL ) );
+ if ( search.m_pValueWidget )
+ {
+ m_AttributeWidgets.FindAndRemove( search );
+ }
+ }
+
+ int nCount = m_pTree->GetTree()->GetNumChildren( nItemIndex );
+ for ( int i = 0; i < nCount; ++i )
+ {
+ RemoveItem_R( m_pTree->GetTree()->GetChild( nItemIndex, i ) );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Removes an item from the tree
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::RemoveItem( int nItemIndex )
+{
+ RemoveItem_R( nItemIndex );
+ m_pTree->RemoveItem( nItemIndex );
+}
+
+
+//-----------------------------------------------------------------------------
+// Adds editable attribute array entries to the tree
+//-----------------------------------------------------------------------------
+void CElementPropertiesTreeInternal::InsertAttributeArrayMembers( int parentNodeIndex, CDmElement *obj, CDmAttribute *pAttribute )
+{
+ Assert( obj );
+
+ // Iterate over each element and create a widget and tree entry for it
+ CDmrGenericArray array( pAttribute );
+ int c = array.Count();
+ for ( int i = 0; i < c; i++ )
+ {
+ InsertSingleAttribute( parentNodeIndex, obj, pAttribute, i );
+ }
+}
+
+void CElementPropertiesTreeInternal::OnKeyDelete()
+{
+ RemoveSelected( false );
+}
+
+void CElementPropertiesTreeInternal::OnKeyBackspace()
+{
+ RemoveSelected( true );
+}
+
+void CElementPropertiesTreeInternal::RemoveSelected( bool selectLeft )
+{
+ CElementTreeUndoScopeGuard guard( NOTIFY_SETDIRTYFLAG, m_pNotify, "Delete Items" );
+
+ CUtlVector< KeyValues * > itemData;
+ m_pTree->GetTree()->GetSelectedItemData( itemData );
+ bool bRefreshNeeded = OnRemoveFromData( itemData );
+
+ if ( itemData.Count() > 1 )
+ {
+ // dont try to maintain the selection if multiple items are selected
+ m_pTree->GetTree()->ClearSelection();
+ }
+
+ if ( bRefreshNeeded )
+ {
+ // Refresh the tree
+ Refresh( REFRESH_TREE_VIEW, selectLeft );
+ }
+}
+
+bool CElementPropertiesTreeInternal::IsLabelBeingEdited() const
+{
+ return m_pTree->GetTree()->IsLabelBeingEdited();
+}
+
+bool CElementPropertiesTreeInternal::HasItemsSelected() const
+{
+ return m_pTree->GetTree()->GetSelectedItemCount() > 0 ? true : false;
+}
+
+
+//-----------------------------------------------------------------------------
+// protected accessors
+//-----------------------------------------------------------------------------
+KeyValues *CElementPropertiesTreeInternal::GetTreeItemData( int itemIndex )
+{
+ return m_pTree->GetItemData( itemIndex );
+}
+
+
+void CElementPropertiesTreeInternal::OnRefresh()
+{
+ // Does a forced refresh
+ Refresh( REFRESH_TREE_VIEW );
+
+ CElementTreeNotifyScopeGuard notify( "CElementPropertiesTreeInternal::OnRefresh", NOTIFY_SETDIRTYFLAG | NOTIFY_CHANGE_TOPOLOGICAL, m_pNotify );
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// CElementPropertiesTree methods
+//
+//-----------------------------------------------------------------------------
+CElementPropertiesTree::CElementPropertiesTree( vgui::Panel *parent, IDmNotify *pNotify, CDmElement *pObject, CDmeEditorTypeDictionary *pDict )
+ : BaseClass( parent, "ElementPropertiesTreeFrame" )
+{
+ SetTitle( "#BxElementPropertiesTree", true );
+
+ SetSizeable( true );
+ SetCloseButtonVisible( false );
+ SetMinimumSize( 600, 200 );
+
+ m_pProperties = new CElementPropertiesTreeInternal( this, pNotify, pObject, false, pDict );
+
+ m_pOK = new Button( this, "OK", "OK", this, "close" );
+ m_pApply = new Button( this, "Apply", "Apply", this, "apply" );
+ m_pCancel = new Button( this, "Cancel", "Cancel", this, "cancel" );
+
+ SetScheme( vgui::scheme()->LoadSchemeFromFile( "Resource/BoxRocket.res", "BoxRocket" ) );
+ LoadControlSettings( "resource/BxElementPropertiesTreeFrame.res" );
+}
+
+void CElementPropertiesTree::Init( )
+{
+ m_pProperties->Init( );
+}
+
+void CElementPropertiesTree::ActivateBuildMode()
+{
+ BaseClass::ActivateBuildMode();
+ m_pProperties->ActivateBuildMode();
+}
+
+void CElementPropertiesTree::Refresh( CElementPropertiesTreeInternal::RefreshType_t rebuild /* = REFRESH_REBUILD */, bool preservePrevSelectedItem /*= false*/ )
+{
+ m_pProperties->Refresh( rebuild, preservePrevSelectedItem );
+}
+
+void CElementPropertiesTree::GenerateChildrenOfNode(int itemIndex)
+{
+ m_pProperties->GenerateChildrenOfNode( itemIndex );
+}
+
+void CElementPropertiesTree::SetObject( CDmElement *object )
+{
+ m_pProperties->SetObject( object );
+}
+
+void CElementPropertiesTree::OnCommand( const char *cmd )
+{
+ if ( !Q_stricmp( cmd, "close" ) )
+ {
+ m_pProperties->ApplyChanges();
+ MarkForDeletion();
+ }
+ else if ( !Q_stricmp( cmd, "apply" ) )
+ {
+ m_pProperties->ApplyChanges();
+ m_pProperties->Refresh();
+ }
+ else if ( !Q_stricmp( cmd, "cancel" ) )
+ {
+ MarkForDeletion();
+ }
+ else
+ {
+ BaseClass::OnCommand( cmd );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+//
+// Hook this into the DmePanel editing system
+//
+//-----------------------------------------------------------------------------
+IMPLEMENT_DMEPANEL_FACTORY( CDmeElementPanel, DmElement, "DmeElementDefault", "Dme Element Editor", true );
+
+
+//-----------------------------------------------------------------------------
+// Constructor
+//-----------------------------------------------------------------------------
+#pragma warning (disable:4355)
+CDmeElementPanel::CDmeElementPanel( vgui::Panel *pParent, const char *pPanelName ) :
+ BaseClass( pParent, this, NULL )
+{
+}
+#pragma warning (default:4355)
+
+
+//-----------------------------------------------------------------------------
+// Called when the panel changes something
+//-----------------------------------------------------------------------------
+void CDmeElementPanel::NotifyDataChanged( const char *pReason, int nNotifySource, int nNotifyFlags )
+{
+ if ( nNotifyFlags & ( NOTIFY_CHANGE_TOPOLOGICAL | NOTIFY_CHANGE_ATTRIBUTE_VALUE | NOTIFY_CHANGE_ATTRIBUTE_ARRAY_SIZE ) )
+ {
+ KeyValues *pKeyValues = new KeyValues( "DmeElementChanged", "notifyFlags", nNotifyFlags );
+ pKeyValues->SetString( "reason", pReason );
+ pKeyValues->SetInt( "source", nNotifySource );
+ PostActionSignal( pKeyValues );
+ }
+}
+
+
+//-----------------------------------------------------------------------------
+// Called by the DmePanel framework to hook an element to this
+//-----------------------------------------------------------------------------
+void CDmeElementPanel::SetDmeElement( CDmElement *pElement )
+{
+ SetObject( pElement );
+}