//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose: 
//
// $NoKeywords: $
//=============================================================================//

#include "stdafx.h"
#include "hammer.h"
#include "ObjectProperties.h"
#include "ObjectPage.h"
#include "OP_Flags.h"
#include "OP_Groups.h"
#include "OP_Entity.h"
#include "OP_Output.h"
#include "OP_Model.h"
#include "OP_Input.h"
#include "MapDoc.h"
#include "MapView.h"
#include "MapEntity.h"
#include "MapGroup.h"
#include "MapInstance.h"
#include "MapSolid.h"
#include "MapStudioModel.h"
#include "MapWorld.h"
#include "History.h"
#include "GlobalFunctions.h"
#include "Selection.h"
#include "CustomMessages.h"
#include "Camera.h"
#include "Manifest.h"

// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>

//
// Layout types for remembering the last layout of the dialog. We could
// also remember this as an array of booleans for which pages were visible.
//
enum LayoutType_t
{
	ltZero,			// Special enums for initialization
	ltNone,

	ltSolid,		// Enable groups only
	ltSolidMulti,	// Enable none
	ltEntity,		// Enable entity, flags, groups
	ltEntityMulti,	// Enable entity, flags
	ltWorld,		// Enable entity, flags, groups
	ltModelEntity,	// Enable entity, flags, groups, model, 
	ltMulti			// Enable none
};



IMPLEMENT_DYNAMIC(CObjectProperties, CPropertySheet)


BEGIN_MESSAGE_MAP(CObjectProperties, CPropertySheet)
	//{{AFX_MSG_MAP(CObjectProperties)
	ON_WM_KILLFOCUS()
	ON_WM_ACTIVATE()
	ON_WM_CLOSE()
	ON_WM_PAINT()
	ON_WM_SIZE()
	ON_WM_SHOWWINDOW()
	ON_WM_CREATE()
	ON_COMMAND(IDOK, OnApply )
	ON_COMMAND(ID_APPLY_NOW, OnApply )
	ON_COMMAND(IDCANCEL, OnCancel)
	ON_COMMAND(IDI_INPUT, OnInputs)
	ON_COMMAND(IDI_OUTPUT, OnOutputs)
	ON_COMMAND(IDD_EDIT_INSTANCE, OnEditInstance)
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

IMPLEMENT_DYNAMIC(editCMapClass, CObject);
IMPLEMENT_DYNAMIC(editCEditGameClass, CObject);


static editCMapClass e_CMapClass;
static editCEditGameClass e_CEditGameClass;


//-----------------------------------------------------------------------------
// Purpose: Constructor.
//-----------------------------------------------------------------------------
CObjectProperties::CObjectProperties(void) :
	CPropertySheet()
{
	m_bDummy = false;
	m_pDummy = NULL;
	m_pInputButton = NULL;
	m_pOutputButton = NULL;
	m_pInstanceButton = NULL;
	m_pOrgObjects = NULL;
	m_bDataDirty = false;
	m_bCanEdit = false;

	CreatePages();
}


//-----------------------------------------------------------------------------
// Purpose: Constructor.
// Input  : nIDCaption - 
//			pParentWnd - 
//			iSelectPage - 
//-----------------------------------------------------------------------------
CObjectProperties::CObjectProperties(UINT nIDCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CPropertySheet(nIDCaption, pParentWnd, iSelectPage)
{
	m_bDummy = false;
	m_pDummy = NULL;
	m_pInputButton = NULL;
	m_pOutputButton = NULL;
	m_pInstanceButton = NULL;
	m_bCanEdit = false;

	CreatePages();
}


//-----------------------------------------------------------------------------
// Purpose: Constructor.
// Input  : pszCaption - 
//			pParentWnd - 
//			iSelectPage - 
//-----------------------------------------------------------------------------
CObjectProperties::CObjectProperties(LPCTSTR pszCaption, CWnd* pParentWnd, UINT iSelectPage)
	:CPropertySheet(pszCaption, pParentWnd, iSelectPage)
{
	m_bDummy = false;
	m_pDummy = NULL;
	m_pInputButton = NULL;
	m_pOutputButton = NULL;
	m_pInstanceButton = NULL;
	m_bCanEdit = false;

	CreatePages();
}


//-----------------------------------------------------------------------------
// Purpose: Destructor.
//-----------------------------------------------------------------------------
CObjectProperties::~CObjectProperties()
{
	delete m_pDummy;

	delete m_pEntity;
	delete m_pFlags;
	delete m_pGroups;
	delete m_pOutput;
	delete m_pInput;
	delete m_pModel;

	delete m_pInputButton;
	delete m_pOutputButton;
	delete m_pInstanceButton;

	delete[] m_ppPages;
}


//-----------------------------------------------------------------------------
// Purpose: Creates all possible pages and attaches our object list to them.
//			Not all will be used depending on the types of objects being edited.
//-----------------------------------------------------------------------------
void CObjectProperties::CreatePages(void)
{
	//VPROF_BUDGET( "CObjectProperties::CreatePages", "Object Properties" );

	m_pEntity = new COP_Entity;
	m_pEntity->SetObjectList(&m_DstObjects);

	m_pFlags = new COP_Flags;
	m_pFlags->SetObjectList(&m_DstObjects);

	// There are some dependencies between the entity and flags tabs since
	// they both edit the spawnflags property.
	m_pEntity->SetFlagsPage( m_pFlags );
	m_pFlags->SetEntityPage( m_pEntity );

	m_pGroups = new COP_Groups;
	m_pGroups->SetObjectList(&m_DstObjects);

	m_pOutput = new COP_Output;
	m_pOutput->SetObjectList(&m_DstObjects);

	m_pInput = new COP_Input;
	m_pInput->SetObjectList(&m_DstObjects);

	m_pModel = new COP_Model;
	m_pModel->SetObjectList(&m_DstObjects);

	m_pDummy = new CPropertyPage(IDD_OBJPAGE_DUMMY);

	m_ppPages = NULL;
	m_nPages = 0;

	m_pLastActivePage = NULL;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pType - 
//-----------------------------------------------------------------------------
PVOID CObjectProperties::GetEditObject(CRuntimeClass *pType)
{
	//VPROF_BUDGET( "CObjectProperties::GetEditObject", "Object Properties" );

	if (pType == RUNTIME_CLASS(editCMapClass))
	{
		return PVOID((CMapClass*)&e_CMapClass);
	}
	else if (pType == RUNTIME_CLASS(editCEditGameClass))
	{
		return PVOID((CEditGameClass*)&e_CEditGameClass);
	}

	Assert(0);
	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : pobj - 
//			pType - 
//-----------------------------------------------------------------------------
PVOID CObjectProperties::GetEditObjectFromMapObject(CMapClass *pobj, CRuntimeClass *pType)
{
	//VPROF_BUDGET( "CObjectProperties::GetEditObjectFromMapObject", "Object Properties" );

	if (pType == RUNTIME_CLASS(editCMapClass))
	{
		return PVOID(pobj);
	}
	else if (pType == RUNTIME_CLASS(editCEditGameClass))
	{
		if (pobj->IsMapClass(MAPCLASS_TYPE(CMapEntity)))
		{
			return PVOID((CEditGameClass*)((CMapEntity*)pobj));
		}

		if (pobj->IsMapClass(MAPCLASS_TYPE(CMapWorld)))
		{
			return PVOID((CEditGameClass*)((CMapWorld*)pobj));
		}
	}

	return NULL;
}


//-----------------------------------------------------------------------------
// Purpose: 
// Input  : *pobj - 
//-----------------------------------------------------------------------------
void CObjectProperties::CopyDataToEditObjects(CMapClass *pobj)
{
	//VPROF_BUDGET( "CObjectProperties::CopyDataToEditObjects", "Object Properties" );

	//
	// All copies here are done without updating object dependencies, because
	// we're copying to a place that is outside of the world.
	//
	e_CMapClass.CopyFrom(pobj, false);

	if (pobj->IsMapClass(MAPCLASS_TYPE(CMapEntity)))
	{
		e_CEditGameClass.CopyFrom((CEditGameClass *)((CMapEntity *)pobj));
	}
	else if (pobj->IsMapClass(MAPCLASS_TYPE(CMapWorld)))
	{
		e_CEditGameClass.CopyFrom((CEditGameClass *)((CMapWorld *)pobj));
	}
}

//------------------------------------------------------------------------------
// Purpose:
// Input  : nState - 
//------------------------------------------------------------------------------
void CObjectProperties::SetOutputButtonState(int nState)
{
	//VPROF_BUDGET( "CObjectProperties::SetOutputButtonState", "Object Properties" );

	if (nState == CONNECTION_GOOD)
	{
		m_pOutputButton->SetIcon(m_hIconOutputGood);
		m_pOutputButton->ShowWindow(SW_SHOW);
		m_pOutputButton->Invalidate();
		m_pOutputButton->UpdateWindow();
	}
	else if (nState == CONNECTION_BAD)
	{
		m_pOutputButton->SetIcon(m_hIconOutputBad);
		m_pOutputButton->ShowWindow(SW_SHOW);
		m_pOutputButton->Invalidate();
		m_pOutputButton->UpdateWindow();
	}
	else
	{
		m_pOutputButton->ShowWindow(SW_HIDE);
		m_pOutputButton->Invalidate();
		m_pOutputButton->UpdateWindow();
	}
}


//------------------------------------------------------------------------------
// Purpose:
// Input  : nState - 
//------------------------------------------------------------------------------
void CObjectProperties::SetInputButtonState(int nState)
{
	//VPROF_BUDGET( "CObjectProperties::SetInputButtonState", "Object Properties" );

	if (nState == CONNECTION_GOOD)
	{
		m_pInputButton->SetIcon(m_hIconInputGood);
		m_pInputButton->ShowWindow(SW_SHOW);
		m_pInputButton->Invalidate();
		m_pInputButton->UpdateWindow();
	}
	else if (nState == CONNECTION_BAD)
	{
		m_pInputButton->SetIcon(m_hIconInputBad);
		m_pInputButton->ShowWindow(SW_SHOW);
		m_pInputButton->Invalidate();
		m_pInputButton->UpdateWindow();
	}
	else
	{
		m_pInputButton->ShowWindow(SW_HIDE);
		m_pInputButton->Invalidate();
		m_pInputButton->UpdateWindow();
	}
}


//------------------------------------------------------------------------------
// Purpose: Set icon being displayed on output button.
//------------------------------------------------------------------------------
void CObjectProperties::UpdateOutputButton(void)
{
	//VPROF_BUDGET( "CObjectProperties::UpdateOutputButton", "Object Properties" );

	if (!m_pOutputButton)
	{
		return;
	}

	bool bHaveConnection = false;
	bool bIgnoreHiddenTargets = false;
	if ( m_pOutput )
		bIgnoreHiddenTargets = !m_pOutput->ShouldShowHiddenTargets();

	FOR_EACH_OBJ( m_DstObjects, pos )
	{
		CMapClass *pObject = m_DstObjects.Element(pos);

		if ((pObject != NULL) && (pObject->IsMapClass(MAPCLASS_TYPE(CMapEntity))))
		{
			CMapEntity *pEntity = (CMapEntity *)pObject;
			int nStatus = CEntityConnection::ValidateOutputConnections(pEntity, true, bIgnoreHiddenTargets);
			if (nStatus == CONNECTION_BAD)
			{
				SetOutputButtonState(CONNECTION_BAD);
				return;
			}
			else if (nStatus == CONNECTION_GOOD)
			{
				bHaveConnection = true;
			}
		}
	}
	if (bHaveConnection)
	{
		SetOutputButtonState(CONNECTION_GOOD);
	}
	else
	{
		SetOutputButtonState(CONNECTION_NONE);
	}
}


//------------------------------------------------------------------------------
// Purpose: Set icon being displayed on input button.
//------------------------------------------------------------------------------
void CObjectProperties::UpdateInputButton()
{
	//VPROF_BUDGET( "CObjectProperties::UpdateInputButton", "Object Properties" );

	if (!m_pInputButton)
	{
		return;
	}

	bool bHaveConnection = false;

	FOR_EACH_OBJ( m_DstObjects, pos )
	{
		CMapClass *pObject = m_DstObjects.Element(pos);

		if ((pObject != NULL) && (pObject->IsMapClass(MAPCLASS_TYPE(CMapEntity))))
		{
			CMapEntity *pEntity = (CMapEntity *)pObject;
			int nStatus = CEntityConnection::ValidateInputConnections(pEntity, false);
			if (nStatus == CONNECTION_BAD)
			{
				SetInputButtonState(CONNECTION_BAD);
				return;
			}
			else if (nStatus == CONNECTION_GOOD)
			{
				bHaveConnection = true;
			}
		}
	}
	if (bHaveConnection)
	{
		SetInputButtonState(CONNECTION_GOOD);
	}
	else
	{
		SetInputButtonState(CONNECTION_NONE);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Finds/Creates the buttons.
//-----------------------------------------------------------------------------
void CObjectProperties::CreateButtons(void)
{
	//VPROF_BUDGET( "CObjectProperties::CreateButtons", "Object Properties" );

#if 0
	// Get the screen location of the hidden apply button(ID_APPLY_NOW)
	rect	rcButton;
	pApplyButton->GetWindowRect( &rcButton );

	// Grab, enable and rename the OK button to be Apply 
	// (Because <enter> only accelerates IDOK)
	// and we dont want "OK" (apply+close) functionality
	CButton	*pOKButton = reinterpret_cast<CButton *>(GetDlgItem(IDOK)); 
	pOKButton->SetWindowTextA("Apply");
	pOKButton->EnableWindow();
	pOKButton->ShowWindow(SW_SHOWNA);
	pOKButton->MoveWindow(&rcButton);
#else
	// Grab, enable and DONT show the OK button
	// (Because <enter> only accelerates IDOK)
	// and we dont want "OK" (apply+close) functionality
	CButton	*pOKButton = reinterpret_cast<CButton *>(GetDlgItem(IDOK)); 
	pOKButton->EnableWindow();
	// Dont show the window, just make it active to forward <enter> -> IDOK -> OnApply

	// Grab and enable & show the hidden Apply button too
	CButton *pApplyButton = reinterpret_cast<CButton *>(GetDlgItem(ID_APPLY_NOW));
	pApplyButton->SetButtonStyle( pApplyButton->GetButtonStyle() | BS_DEFPUSHBUTTON );
	pApplyButton->EnableWindow();
	pApplyButton->ShowWindow(SW_SHOWNA);
#endif
	// Grab and enable & show the hidden Cancel button too
	CButton *pCancelButton = reinterpret_cast<CButton *>(GetDlgItem(IDCANCEL));
	pCancelButton->EnableWindow();
	pCancelButton->ShowWindow(SW_SHOWNA);

	//
	// Load Icons
	//
	CWinApp *pApp = AfxGetApp();
	m_hIconOutputGood = pApp->LoadIcon(IDI_OUTPUT);
	m_hIconOutputBad  = pApp->LoadIcon(IDI_OUTPUTBAD);
	m_hIconInputGood  = pApp->LoadIcon(IDI_INPUT);
	m_hIconInputBad   = pApp->LoadIcon(IDI_INPUTBAD);

	// Create buttons to display connection status icons
	CRect rect;
	GetWindowRect(&rect);
	rect.InflateRect(0, 0, 0, 32);
	MoveWindow(&rect, FALSE);
	GetClientRect(&rect);

	m_pInputButton = new CButton;
	m_pInputButton->Create(_T("My button"), WS_CHILD|WS_VISIBLE|BS_ICON|BS_FLAT, CRect(6,rect.bottom - 34,38,rect.bottom - 2), this, IDI_INPUT);

	m_pOutputButton = new CButton;
	m_pOutputButton->Create(_T("My button"), WS_CHILD|WS_VISIBLE|BS_ICON|BS_FLAT, CRect(40,rect.bottom - 34,72,rect.bottom - 2), this, IDI_OUTPUT);

	m_pInstanceButton = new CButton;
	m_pInstanceButton->Create( _T( "Edit Instance" ), WS_CHILD|WS_VISIBLE|BS_TEXT, CRect( 6, rect.bottom - 28, 140, rect.bottom - 4 ), this, IDD_EDIT_INSTANCE );
}


//-----------------------------------------------------------------------------
// Purpose: Returns the appropriate page layout for the current object list.
//-----------------------------------------------------------------------------
void CObjectProperties::GetTabsForLayout(LayoutType_t eLayoutType, bool &bEntity, bool &bGroups, bool &bFlags, bool &bModel)
{
	//VPROF_BUDGET( "CObjectProperties::GetTabsForLayout", "Object Properties" );

	bEntity = bGroups = bFlags = bModel = false;

	switch (eLayoutType)
	{
		case ltEntity:
		case ltEntityMulti:
		case ltModelEntity:
		{
			bFlags = true;
			bEntity = true;
			bGroups = true;
			bModel = (eLayoutType == ltModelEntity);
			break;
		}

		case ltSolid:
		{
			bGroups = true;
			break;
		}

		case ltWorld:
		{
			bEntity = true;
			break;
		}

		case ltMulti:
		case ltSolidMulti:
		{
			bGroups = true;
			break;
		}
	}
}


//-----------------------------------------------------------------------------
// Purpose: Returns the appropriate page layout for the current object list.
//-----------------------------------------------------------------------------
LayoutType_t CObjectProperties::GetLayout(void)
{
	//VPROF_BUDGET( "CObjectProperties::GetLayout", "Object Properties" );

	LayoutType_t eLayoutType = ltNone;

	if ((m_DstObjects.Count() == 0) || (CMapDoc::GetActiveMapDoc() == NULL))
	{
		eLayoutType = ltNone;
	}
	else
	{
		//
		// Figure out which layout to use based on the objects being edited.
		//
		bool bFirst = true;
		MAPCLASSTYPE PrevType = MAPCLASS_TYPE(CMapEntity);
		
		FOR_EACH_OBJ( m_DstObjects, pos )
		{
			CMapClass *pObject = m_DstObjects.Element(pos);
			MAPCLASSTYPE ThisType = pObject->GetType();
			
			if (bFirst)
			{
				bFirst = false;

				if (ThisType == MAPCLASS_TYPE(CMapEntity))
				{
					CMapEntity *pEntity = (CMapEntity *)pObject;

					//
					// Only show the model tab when we have a single entity selected that
					// has a model helper.
					//
					if (m_DstObjects.Count() == 1)
					{
						if (pEntity->GetChildOfType((CMapStudioModel *)NULL))
						{
							eLayoutType = ltModelEntity;
						}
						else
						{
							eLayoutType = ltEntity;
						}
					}
					else
					{
						eLayoutType = ltEntityMulti;
					}
				}
				else if ((ThisType == MAPCLASS_TYPE(CMapSolid)) ||
						(ThisType == MAPCLASS_TYPE(CMapGroup)))
				{
					eLayoutType = (m_DstObjects.Count() == 1) ? ltSolid : ltSolidMulti;
				}
				else if (ThisType == MAPCLASS_TYPE(CMapWorld))
				{
					eLayoutType = ltWorld;
				}
			}
			else if (ThisType != PrevType)
			{
				eLayoutType = ltMulti;
			}

			PrevType = ThisType;
		}
	}

	return eLayoutType;
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectProperties::RestoreActivePage(void)
{
	//VPROF_BUDGET( "CObjectProperties::RestoreActivePage", "Object Properties" );

	//
	// Try to restore the previously active page. If it is not in the page list
	// just activate page zero.
	//
	bool bPageSet = false;
	for (int i = 0; i < m_nPages; i++)
	{
		if (m_ppPages[i] == m_pLastActivePage)
		{
			SetActivePage(m_pLastActivePage);
			bPageSet = true;
			break;
		}
	}

	if (!bPageSet)
	{
		SetActivePage(0);
	}
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectProperties::SaveActivePage(void)
{
	//VPROF_BUDGET( "CObjectProperties::SaveActivePage", "Object Properties" );

	CObjectPage *pPage = (CObjectPage *)GetActivePage();
	if (pPage != NULL)
	{
		m_pLastActivePage = pPage;
	}
}


//-----------------------------------------------------------------------------
// Purpose: Sets up pages to display based on "m_DstObjects".
// Output : Returns TRUE if the page structure changed, FALSE if not.
//-----------------------------------------------------------------------------
BOOL CObjectProperties::SetupPages(void)
{
	//VPROF_BUDGET( "CObjectProperties::SetupPages", "Object Properties" );

	static bool bFirstTime = true;
	static LayoutType_t eLastLayoutType = ltZero;
	static LayoutType_t eLastValidLayoutType = ltZero;

	//
	// Save the current active page.
	//
	if ((eLastLayoutType != ltZero) && (eLastLayoutType != ltNone))
	{	
		SaveActivePage();
	}

	//
	// Determine the appropriate layout for the current object list.
	//	
	LayoutType_t eLayoutType = GetLayout();

	bool bEntity;
	bool bGroups;
	bool bFlags;
	bool bModel;
	GetTabsForLayout(eLayoutType, bEntity, bGroups, bFlags, bModel);

	//
	// If the layout has not changed, we're done. All the pages are already set up.
	//
	if (eLayoutType == eLastLayoutType)
	{
		//
		// Try to restore the previously active page. If it has been deleted just
		// activate page zero.
		//
		RestoreActivePage();
		return(FALSE);
	}

	//
	// Forget the last active page when the layout changes from one
	// valid layout to another (such as from entity to solid).
	// Don't reset when switching between model entities and non-model entities,
	// because it's annoying to be switched away from the Outputs tab.
	//
	if ((eLayoutType != ltNone) && (eLayoutType != eLastValidLayoutType) &&
		!((eLayoutType == ltEntity) && (eLastValidLayoutType == ltModelEntity)) &&
		!((eLayoutType == ltModelEntity) && (eLastValidLayoutType == ltEntity)))
	{
		m_pLastActivePage = NULL;
		eLastValidLayoutType = eLayoutType;
	}

	eLastLayoutType = eLayoutType;

	CObjectPage::s_bRESTRUCTURING = TRUE;

	UINT nAddPages = bEntity + bGroups + bFlags + bModel;

	// don't want to change focus .. just pages!
	CWnd *pActiveWnd = GetActiveWindow();

	bool bDisabledraw = false;
	if (::IsWindow(m_hWnd) && IsWindowVisible())
	{
		SetRedraw(FALSE);
		bDisabledraw = true;
	}

	if (!m_bDummy && (nAddPages == 0))
	{
		AddPage(m_pDummy);
		m_bDummy = true;
	}
	else if (m_bDummy && (nAddPages > 0))
	{
		RemovePage(m_pDummy);
		m_bDummy = false;
	}

	struct
	{
		bool m_bIsVisible;
		bool m_bWantVisible;
		CObjectPage *m_pPage;
	} pages[] =
	{
		{false, bEntity, m_pEntity},
		{false, bEntity, m_pOutput},
		{false, bEntity, m_pInput},
		{false, bModel, m_pModel},
		{false, bFlags, m_pFlags},
		{false, bGroups, m_pGroups}
	};

	// First, remove pages that we don't want visible.
	// Also store if they're visible.
	for ( int i=0; i < ARRAYSIZE( pages ); i++ )
	{
		pages[i].m_bIsVisible = ( GetPageIndex( pages[i].m_pPage ) != -1 );
		if ( pages[i].m_bIsVisible && !pages[i].m_bWantVisible)
		{
			// It's visible but they don't want it there.
			RemovePage( pages[i].m_pPage );
			pages[i].m_bIsVisible = false;
		}
	}
	
	// We're about to add pages, but it'll only add them to the right of what's already there, 
	// so we must get rid of anything to the right of our leftmost addition.
	for ( int i=0; i < ARRAYSIZE( pages ); i++ )
	{
		if ( !pages[i].m_bIsVisible && pages[i].m_bWantVisible )
		{
			// Ok, page i needs to be on, so nuke everything to the right of it.
			for ( int j=i+1; j < ARRAYSIZE( pages ); j++ )
			{
				if ( pages[j].m_bIsVisible )
				{
					RemovePage( pages[j].m_pPage );
					pages[j].m_bIsVisible = false;
				}
			}
			break;
		}
	}
	
	for ( int i=0; i < ARRAYSIZE( pages ); i++ )
	{
		if ( !pages[i].m_bIsVisible && pages[i].m_bWantVisible )
			AddPage( pages[i].m_pPage );
	}

	//
	// Store active pages in our array.
	//
	if (!m_bDummy)
	{
		delete[] m_ppPages;
		m_nPages = GetPageCount();
		m_ppPages = new CObjectPage*[m_nPages];

		for (int i = 0; i < m_nPages; i++)
		{
			m_ppPages[i] = (CObjectPage *)GetPage(i);
			m_ppPages[i]->m_bFirstTimeActive = true;
			m_ppPages[i]->m_bHasUpdatedData = false;
		}
	}

	CObjectPage::s_bRESTRUCTURING = FALSE;

	//VPROF_BUDGET( "CObjectProperties::RestoreActivePage", "Object Properties" );
	RestoreActivePage();

	//
	// Enable redraws if they were disabled above.
	//
	if (bDisabledraw)
	{
		SetRedraw(TRUE);
		Invalidate(FALSE);
	}

	// Set button status
	UpdateOutputButton();
	UpdateInputButton();

	if (pActiveWnd != NULL)
	{
		pActiveWnd->SetActiveWindow();
	}

	bFirstTime = false;	

	return TRUE;	// pages changed - return true
}


//------------------------------------------------------------------------------
// Purpose: Set object properties dialogue to the Output tab and highlight
//			the given item
// Input  : pConnection - 
//------------------------------------------------------------------------------
void CObjectProperties::SetPageToOutput(CEntityConnection *pConnection)
{
	if ( m_bDataDirty )
		ReloadData();

	SetActivePage(m_pOutput);
	m_pOutput->SetSelectedConnection(pConnection);
}

void CObjectProperties::SetPageToInput(CEntityConnection *pConnection)
{
	if ( m_bDataDirty )
		ReloadData();

	SetActivePage(m_pInput);
	
	m_pInput->SetSelectedConnection(pConnection);
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectProperties::SaveData(void)
{
	//VPROF_BUDGET( "CObjectProperties::SaveData", "Object Properties" );

	//
	// Make sure window is visible - don't want to save otherwise.
	//
	if (!IsWindowVisible())
	{
		return;
	}

	// we should never save in a dirty state
	if ( m_bDataDirty )
		return;

	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
	if (!pDoc || !m_DstObjects.Count() || m_bDummy)
	{
		return;
	}

	//
	// Transfer all page data to the objects being edited.
	//
	GetHistory()->MarkUndoPosition( pDoc->GetSelection()->GetList(), "Change Properties");

	// Don't keep all the world's children when we're editing the world, because
	// that's really slow (and pointless since all we're changing is keyvalues).
	bool bKeptWorld = false;
	if (m_DstObjects.Count() == 1)
	{
		CMapClass *pObject = m_DstObjects.Element( 0 );
		if ( IsWorldObject(pObject) )
		{
			GetHistory()->KeepNoChildren(pObject);
			bKeptWorld = true;
		}
	}

	if (!bKeptWorld)
	{
		GetHistory()->Keep(&m_DstObjects);
	}

	for (int i = 0; i < m_nPages; i++)
	{
		//
		// Pages that have never been shown have no hwnd.
		//
		if (IsWindow(m_ppPages[i]->m_hWnd) && m_ppPages[i]->m_bHasUpdatedData )
		{
			m_ppPages[i]->SaveData();
		}
	}

	// Objects may have changed. Update the views.
		
	pDoc->SetModifiedFlag();
}


//-----------------------------------------------------------------------------
// Purpose: Submits the objects to be edited to the property pages so they can
//			update their controls.
// Input  : iPage - Page index or -1 to update all pages.
//-----------------------------------------------------------------------------
void CObjectProperties::LoadDataForPages(int iPage)
{
	//VPROF_BUDGET( "CObjectProperties::LoadDataForPages", "Object Properties" );
	
	if (m_bDummy)
	{
		return;
	}

	//
	// Determine whether we are editing multiple objects or not.
	//
	bool bMultiEdit = (m_DstObjects.Count() > 1);
	m_bCanEdit = true;

	//
	// Submit the edit objects to each page one at a time.
	//
	int nMode = CObjectPage::LoadFirstData;
	
	FOR_EACH_OBJ( m_DstObjects, pos )
	{
		CMapClass *pobj = m_DstObjects.Element(pos);

		if ( pobj->IsEditable() == false )
		{
			m_bCanEdit = false;
		}

		if (iPage != -1)
		{
			//
			// Specific page.
			//
			m_ppPages[iPage]->SetMultiEdit(bMultiEdit);

			void *pObject = GetEditObjectFromMapObject(pobj, m_ppPages[iPage]->GetEditObjectRuntimeClass());
			if (pObject != NULL)
			{
				m_ppPages[iPage]->UpdateData(nMode, pObject, m_bCanEdit);
				m_ppPages[iPage]->m_bHasUpdatedData = true;
			}
		}
		else for (int i = 0; i < m_nPages; i++)
		{
			//
			// All pages.
			//
			m_ppPages[i]->SetMultiEdit(bMultiEdit);

			// This page hasn't even been shown yet. Don't bother updating its data.
			if (m_ppPages[i]->m_bFirstTimeActive)
				continue;

			void *pObject = GetEditObjectFromMapObject(pobj, m_ppPages[i]->GetEditObjectRuntimeClass());
			if (pObject != NULL)
			{
				m_ppPages[i]->UpdateData(nMode, pObject, m_bCanEdit);
				m_ppPages[i]->m_bHasUpdatedData = true;
			}								  
		}

		nMode = CObjectPage::LoadData;
	}

	CButton *pApplyButton = reinterpret_cast<CButton *>(GetDlgItem(ID_APPLY_NOW));
	pApplyButton->EnableWindow( ( m_bCanEdit ? TRUE : FALSE ) );

	//
	// Tell the pages that we are done submitting data.
	//
	if (iPage != -1)
	{
		//
		// Specific page.
		//
		m_ppPages[iPage]->UpdateData(CObjectPage::LoadFinished, NULL, m_bCanEdit);
	}
	else for (int i = 0; i < m_nPages; i++)
	{
		//
		// All pages.
		//

		// This page hasn't even been shown yet. Don't bother updating its data.
		if (m_ppPages[i]->m_bFirstTimeActive)
			continue;

		m_ppPages[i]->UpdateData(CObjectPage::LoadFinished, NULL, m_bCanEdit);
	}

	//
	// Update the input/output icons based on the new data.
	//
	UpdateOutputButton();
	UpdateInputButton();
}


//-----------------------------------------------------------------------------
// Purpose: Adds the object to m_DstObjects unless it is a group, in which case
//			it is expanded (recursively) to its children.
//-----------------------------------------------------------------------------
void CObjectProperties::AddObjectExpandGroups(CMapClass *pObject)
{
	//VPROF_BUDGET( "CObjectProperties::AddObjectExpandGroups", "Object Properties" );

	if (pObject->IsGroup())
	{
		const CMapObjectList *pChildren = pObject->GetChildren();
		
		FOR_EACH_OBJ( *pChildren, pos )
		{
			AddObjectExpandGroups( pChildren->Element(pos) );
		}
	}
	else
	{
		m_DstObjects.AddToTail(pObject);
	}
}


//-----------------------------------------------------------------------------
// Purpose: Updates the property page data when the selection contents change.
// Input  : pObjects - List of currently selected objects.
//-----------------------------------------------------------------------------
void CObjectProperties::ReloadData()
{
	//VPROF_BUDGET( "CObjectProperties::LoadData", "Object Properties" );

	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();

	//
	// Disable window so it does not gain focus during this operation.
	//
	EnableWindow(FALSE);


	//
	// Transfer the objects from pObjects to m_DstObjects, expanding
	// groups to their member children.
	//
	m_DstObjects.RemoveAll();
	if ( m_pOrgObjects )
	{
		FOR_EACH_OBJ( (*m_pOrgObjects), pos )
		{
			AddObjectExpandGroups( m_pOrgObjects->Element(pos) );
		}
	}

	m_pInstanceButton->ShowWindow( SW_HIDE );

	//
	// If there is only one object selected, copy its data to our temporary
	// edit objects.
	//
	if (m_DstObjects.Count() == 1)
	{
		//
		// Copy the single destination object's data to our temporary
		// edit objects.
		//
		CMapClass *pobj = m_DstObjects.Element(0);
		CopyDataToEditObjects( pobj );

		//
		// Set the window title to include the object's description.
		//
		char szTitle[MAX_PATH];
		sprintf(szTitle, "Object Properties: %s", pobj->GetDescription());
		SetWindowText(szTitle);

		CManifestInstance	*pManifestInstance = dynamic_cast< CManifestInstance * >( pobj );
		if ( pManifestInstance )
		{
			CManifest *pManifest = CMapDoc::GetManifest();

			if ( pManifest )
			{
				ShowWindow( SW_HIDE );
				if ( pDoc )
				{
					pDoc->UpdateAllViews( MAPVIEW_UPDATE_SELECTION | MAPVIEW_UPDATE_TOOL | MAPVIEW_RENDER_NOW );
				}
				pManifest->SetPrimaryMap( pManifestInstance->GetManifestMap() );
				return;
			}
		}

		CMapEntity	*pEntity = dynamic_cast< CMapEntity * >( pobj );
		if ( pEntity )
		{
			if ( strcmpi( pEntity->GetClassName(), "func_instance" ) == 0 )
			{
				pDoc->PopulateInstance( pEntity );
				CMapInstance	*pMapInstance = pEntity->GetChildOfType( ( CMapInstance * )NULL );
				if ( pMapInstance && pMapInstance->GetInstancedMap() )
				{
					m_pInstanceButton->ShowWindow( SW_SHOW );
				}
			}
			else if ( strcmpi( pEntity->GetClassName(), "func_instance_parms" ) == 0 )
			{
				if ( pDoc )
				{
					pDoc->PopulateInstanceParms( pEntity );
				}
			}
		}

	}
	else if (m_DstObjects.Count() > 1)
	{
		SetWindowText("Object Properties: multiple objects");
	}
	else
	{
		SetWindowText("Object Properties");
	}

	SetupPages();
	LoadDataForPages();

	EnableWindow(TRUE);

	m_bDataDirty = false;
}


BOOL CObjectProperties::OnInitDialog() 
{
	BOOL b = CPropertySheet::OnInitDialog();
	SetWindowText("Object Properties");

	CreateButtons();
	UpdateAnchors( NULL );
		
	return b;
}


void CObjectProperties::UpdateAnchors( CWnd *pPage )
{
	if ( !GetSafeHwnd() )
		return;
		
	// Anchor stuff.
	HWND hTab = NULL;
	if ( GetTabControl() )
		hTab = GetTabControl()->GetSafeHwnd();

	CAnchorDef anchorDefs[] =
	{
		CAnchorDef( IDOK, k_eSimpleAnchorBottomRight ),
		CAnchorDef( ID_APPLY_NOW, k_eSimpleAnchorBottomRight ),
		CAnchorDef( IDCANCEL, k_eSimpleAnchorBottomRight ),
		CAnchorDef( IDI_INPUT, k_eSimpleAnchorBottomRight ),
		CAnchorDef( IDI_OUTPUT, k_eSimpleAnchorBottomRight ),
		CAnchorDef( IDD_EDIT_INSTANCE, k_eSimpleAnchorBottomRight ),
		CAnchorDef( hTab, k_eSimpleAnchorAllSides ),
		CAnchorDef( pPage ? pPage->GetSafeHwnd() : (HWND)NULL, k_eSimpleAnchorAllSides )
	};
	m_AnchorMgr.Init( GetSafeHwnd(), anchorDefs, ARRAYSIZE( anchorDefs ) );
}


//-----------------------------------------------------------------------------
// Purpose: Closes the object properties dialog, saving changes.
//-----------------------------------------------------------------------------
void CObjectProperties::OnClose(void)
{
	//VPROF_BUDGET( "CObjectProperties::OnClose", "Object Properties" );
	OnApply();

	ShowWindow(SW_HIDE);
}

void CObjectProperties::OnPaint()
{
	CPaintDC dc(this); // device context for painting

	if ( m_bDataDirty )
		ReloadData(); 
}

//-----------------------------------------------------------------------------
// Purpose: 
// Input  : bShow - 
//			nStatus - 
//-----------------------------------------------------------------------------
void CObjectProperties::OnShowWindow(BOOL bShow, UINT nStatus) 
{
	//VPROF_BUDGET( "CObjectProperties::OnShowWindow", "Object Properties" );

	// Forget the last active page when the window is hidden or shown.
	// FIXME: SetupPages calls SaveActivePage, so we must switch to page 0 here
	SetActivePage(0);
	m_pLastActivePage = NULL;

	CPropertySheet::OnShowWindow(bShow, nStatus);

	for (int i = 0; i < m_nPages; i++)
	{
		m_ppPages[i]->OnShowPropertySheet(bShow, nStatus);
	}
}


void CObjectProperties::OnSize( UINT nType, int cx, int cy )
{
	m_AnchorMgr.OnSize();
}


//-----------------------------------------------------------------------------
// Purpose: Handles the Apply button.
//-----------------------------------------------------------------------------
void CObjectProperties::OnApply(void)
{
	//VPROF_BUDGET( "CObjectProperties::OnApply", "Object Properties" );

	if ( !m_bCanEdit )
	{
		return;
	}

	CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
	if ( !pDoc )
		return;

	//We lock visgroup updates here because activities in the object properties dialog can 
	//change visgroups which, if updated, will change the object properties, causing problems.
	//All visgroup updates will occur at the end of this apply operation.
    bool bLocked = pDoc->VisGroups_LockUpdates( true );

	for (int i = 0; i < m_nPages; i++)
	{
		if (!m_ppPages[i]->OnApply())
		{
			return;
		}
	}

	//
	// Save and reload the data so the GUI updates.
	//
	SaveData();

	ReloadData();
	
	// Pass along the apply message to the entities.
	FOR_EACH_OBJ( m_DstObjects, pos )
	{
		CMapClass *pObject = m_DstObjects.Element( pos );
		if ( pObject )
		{
			pObject->OnApply();
		}
	}

	if ( bLocked )
	{
		pDoc->VisGroups_LockUpdates( false );
	}
}

//-----------------------------------------------------------------------------
// Purpose: Handles <return> keys sent to OK -> apply instead
//-----------------------------------------------------------------------------
void CObjectProperties::OnOK(void)
{
	//VPROF_BUDGET( "CObjectProperties::OnClose", "Object Properties" );
	OnApply();
}

//-----------------------------------------------------------------------------
// Purpose: Handles the Apply button.
//-----------------------------------------------------------------------------
void CObjectProperties::OnCancel(void)
{
	ShowWindow(SW_HIDE);

	// reload original data and overwrite any changes made prio
	ReloadData();
}


//-----------------------------------------------------------------------------
// Purpose: Handles the input icon button.
//-----------------------------------------------------------------------------
void CObjectProperties::OnInputs(void)
{
	SetActivePage(m_pInput);
}


//-----------------------------------------------------------------------------
// Purpose: Handles the output icon button.
//-----------------------------------------------------------------------------
void CObjectProperties::OnOutputs(void)
{
	SetActivePage(m_pOutput);
}


//-----------------------------------------------------------------------------
// Purpose: handle the pushing of the Edit Instance button.  Will attempt to 
//			switch to the map document containing the instance.
// Input  : none
// Output : none
//-----------------------------------------------------------------------------
void CObjectProperties::OnEditInstance(void)
{
	if (m_DstObjects.Count() == 1)
	{
		CMapClass	*pObj = m_DstObjects.Element( 0 );
		CMapEntity	*pEntity = dynamic_cast< CMapEntity * >( pObj );

		if ( pEntity )
		{
			EnumChildrenPos_t	pos;
			CMapClass *pChild = pEntity->GetFirstDescendent( pos );
			while ( pChild != NULL )
			{
				CMapInstance *pMapInstance = dynamic_cast< CMapInstance * >( pChild );
				if ( pMapInstance != NULL )
				{
					OnClose();

					pMapInstance->SwitchTo();
				}

				pChild = pEntity->GetNextDescendent( pos );
			}
		}
	}

}


//-----------------------------------------------------------------------------
//-----------------------------------------------------------------------------
int CObjectProperties::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	//VPROF_BUDGET( "CObjectProperties::OnCreate", "Object Properties" );

	lpCreateStruct->dwExStyle |= WS_EX_TOOLWINDOW;

	if (CPropertySheet::OnCreate(lpCreateStruct) == -1)
	{
		return -1;
	}

	return 0;
}

void CObjectProperties::SetObjectList(const CMapObjectList *pObjectList)
{
	m_pOrgObjects = pObjectList;
	MarkDataDirty();
}


//-----------------------------------------------------------------------------
// Purpose: 
//-----------------------------------------------------------------------------
void CObjectProperties::MarkDataDirty()
{
	//VPROF_BUDGET( "CObjectProperties::RefreshData", "Object Properties" );

	// if flag already set, dont touch anything
	if ( m_bDataDirty )
		return;

	for (int i = 0; i < m_nPages; i++)
	{
		if (m_ppPages[i]->m_hWnd)
		{
			m_ppPages[i]->RememberState();
			m_ppPages[i]->MarkDataDirty();
		}
	}

	Invalidate( false );

	m_DstObjects.RemoveAll();

	m_bDataDirty = true;
}