//========= 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 );
}