// NeL - MMORPG Framework <http://dev.ryzom.com/projects/nel/>
// Copyright (C) 2010  Winch Gate Property Limited
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License as
// published by the Free Software Foundation, either version 3 of the
// License, or (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.


// particle_tree_ctrl.cpp : implementation file
//



#include "std_afx.h"
#include "object_viewer.h"
#include "particle_tree_ctrl.h"
#include "located_bindable_dialog.h"
#include "emitter_dlg.h"
#include "main_frame.h"
#include "particle_system_edit.h"
#include "particle_dlg.h"
#include "start_stop_particle_system.h"
#include "edit_ps_sound.h"
#include "edit_ps_light.h"
#include "dup_ps.h"
#include "object_viewer.h"
#include "ps_mover_dlg.h"
#include "set_value_dlg.h"
#include "create_file_dlg.h"
#include "located_properties.h"
#include "located_bindable_dialog.h"
#include "located_target_dlg.h"
#include "lb_extern_id_dlg.h"
#include "skippable_message_box.h"
// 
#include "nel/3d/particle_system.h"
#include "nel/3d/particle_system_model.h"
#include "nel/3d/particle_system_shape.h"
#include "nel/3d/ps_located.h"
#include "nel/3d/ps_particle.h"
#include "nel/3d/ps_mesh.h"
#include "nel/3d/ps_force.h"
#include "nel/3d/ps_zone.h"
#include "nel/3d/ps_sound.h"
#include "nel/3d/ps_emitter.h"
#include "nel/3d/ps_edit.h"
#include "nel/3d/nelu.h"
//
#include "nel/misc/path.h"
#include "nel/misc/file.h"







using NL3D::CParticleSystem;
using NL3D::CParticleSystemModel;
using NL3D::CPSLocated;
using NL3D::CPSLocatedBindable;
using NL3D::CNELU;





/////////////////////////////////////////////////////////////////////////////
// CParticleTreeCtrl


enum TPSIcon 
{
	PSIconForce			          = 0, 
	PSIconParticle		          = 1, 
	PSIconEmitter		          = 2, 
	PSIconLight                   = 3, 
	PSIconCollisionZone           = 4, 
	PSIconSound                   = 5, 
	PSIconParticleSystem          = 6, 
	PSIconLocated		          = 7,
	PSIconLocatedInstance         = 8,
	PSIconWorkspace				  = 9,
	PSIconParticleSystemNotLoaded = 10
};

static const uint IconIDs[] = 
{ 
	IDB_FORCE, 
	IDB_PARTICLE, 
	IDB_EMITTER, 
	IDB_LIGHT, 
	IDB_COLLISION_ZONE, 
	IDB_SOUND, 
	IDB_PARTICLE_SYSTEM, 
	IDB_LOCATED, 
	IDB_LOCATED_INSTANCE, 
	IDB_PS_WORKSPACE, 
	IDB_PARTICLE_SYSTEM_NOT_LOADED
};
static const uint NumIconIDs = sizeof(IconIDs) / sizeof(uint);

// this map is used to create increasing names
static std::map<std::string,  uint> _PSElementIdentifiers;


//****************************************************************************************************************
CParticleTreeCtrl::CParticleTreeCtrl(CParticleDlg *pdlg)
{
	CBitmap bm[NumIconIDs];
	_ImageList.Create(16,  16,  ILC_COLOR4,  0,  NumIconIDs);
	for (uint k = 0; k  < NumIconIDs; ++k)
	{
		bm[k].LoadBitmap(IconIDs[k]);
		_ImageList.Add(&bm[k],  RGB(1,  1,  1));
	}
	_ParticleDlg = pdlg;
	_LastClickedPS = NULL;
	_LastActiveNode = NULL;
	_ViewFilenameFlag = true;
}

//****************************************************************************************************************
CParticleTreeCtrl::~CParticleTreeCtrl()
{
	reset();	
}

//****************************************************************************************************************
void CParticleTreeCtrl::reset()
{
	if (IsWindow(*this))
	{	
		DeleteAllItems();
	}
	for (std::vector<CNodeType *>::iterator it = _NodeTypes.begin(); it != _NodeTypes.end(); ++it)
	{
		delete *it;
	}
	_NodeTypes.clear();
}

//****************************************************************************************************************
void CParticleTreeCtrl::rebuildLocatedInstance(CParticleWorkspace::CNode &node)
{
	HTREEITEM currPS = getTreeItem(&node);
	nlassert(currPS);
	HTREEITEM currLocated = this->GetChildItem(currPS);
	while(currLocated)
	{
		CNodeType *nt = (CNodeType *) GetItemData(currLocated);
		nlassert(nt->Type == CNodeType::located);
		CPSLocated *loc = nt->Loc;
		for (uint32 k = 0; k < loc->getSize(); ++k)
		{
			CNodeType *newNt = new CNodeType(loc,  k);
			_NodeTypes.push_back(newNt);
			// bind located instance icon
			InsertItem(TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT,  "instance",  PSIconLocatedInstance,  PSIconLocatedInstance,  0,  0,  (LPARAM) newNt,  currLocated,  TVI_LAST);
		}
		currLocated = GetNextItem(currLocated,  TVGN_NEXT);
	}
	Invalidate();
}

//=====================================================================================================
void CParticleTreeCtrl::suppressLocatedInstanceNbItem(CParticleWorkspace::CNode &node,  uint32 newSize)
{
	HTREEITEM currPS = getTreeItem(&node);
	nlassert(currPS);
	HTREEITEM currLocated,  currLocElement,  nextCurrLocElement;
	currLocated = this->GetChildItem(currPS);
	while(currLocated)
	{				
		currLocElement = GetChildItem(currLocated);			
		while (currLocElement)
		{
			CNodeType *nt = (CNodeType *) GetItemData(currLocElement);

			nextCurrLocElement = GetNextItem(currLocElement,  TVGN_NEXT);
			// remove instance item
			if (nt->Type == CNodeType::locatedInstance)
			{
				if (nt->LocatedInstanceIndex >= newSize)
				{
					removeTreePart(currLocElement);					
				}
			}
			currLocElement = nextCurrLocElement;		
		}
		
		currLocated = GetNextItem(currLocated,  TVGN_NEXT);
	}	
	Invalidate();
}


//=====================================================================================================
HTREEITEM CParticleTreeCtrl::buildTreeFromPS(CParticleWorkspace::CNode &node,  HTREEITEM rootHandle,  HTREEITEM prevSibling /*= TVI_LAST*/)
{		
	// for now,  there's only one root ...			
	CNodeType *nt = new CNodeType(&node);
	_NodeTypes.push_back(nt);
	if (node.isLoaded())
	{	
		// bind particle system icon
		HTREEITEM psRoot =  InsertItem(TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT, computeCaption(node).c_str(),  PSIconParticleSystem,  PSIconParticleSystem,  0,  0,  NULL,  rootHandle,  prevSibling);
		// set the param (doesn't seems to work during first creation)
		SetItemData(psRoot,  (LPARAM) nt);
		// now,  create each located		
		for (uint k = 0; k < node.getPSPointer()->getNbProcess(); k++)
		{				
			CPSLocated *loc = dynamic_cast<CPSLocated *>(node.getPSPointer()->getProcess(k));		
			if (loc) createNodeFromLocated(loc,  psRoot);
		}
		rebuildLocatedInstance(node);
		return psRoot;
	}
	else
	{
		// bind a bitmap that say that the PS hasn't been loaded
		HTREEITEM psRoot =  InsertItem(TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT, computeCaption(node).c_str(),  PSIconParticleSystemNotLoaded,  PSIconParticleSystemNotLoaded,  0,  0,  NULL,  rootHandle,  prevSibling);
		SetItemData(psRoot,  (LPARAM) nt);
		return psRoot;
	}
}

//=====================================================================================================
void CParticleTreeCtrl::buildTreeFromWorkSpace(CParticleWorkspace &ws)
{
	reset();
	DeleteAllItems();
	CNodeType *nt = new CNodeType(&ws);
	_NodeTypes.push_back(nt);
	// bind particle system icon
	HTREEITEM rootHandle =  InsertItem(TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_TEXT,  computeCaption(ws).c_str(),  PSIconWorkspace,  PSIconWorkspace,  0,  0,  NULL,  NULL,  TVI_LAST);
	// set the param (doesn't seems to work during first creation)
	SetItemData(rootHandle,  (LPARAM) nt);
	// now,  create each particle system
	for (uint k = 0; k < ws.getNumNode(); ++k)
	{				
		buildTreeFromPS(*ws.getNode(k),  rootHandle);
	}
}

//=====================================================================================================
void CParticleTreeCtrl::createNodeFromLocated(NL3D::CPSLocated *loc,  HTREEITEM rootHandle)
{	
	// insert an item for the located
	CNodeType *nt = new CNodeType(loc);
	_NodeTypes.push_back(nt);
	// bind located icon
	HTREEITEM nodeHandle =  InsertItem(TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM |TVIF_TEXT,  loc->getName().c_str() ,  PSIconLocated,  PSIconLocated,  0,  0,  (LPARAM) nt,  rootHandle,  TVI_LAST);
	// now,  insert each object that is bound to the located	
	for (uint l = 0; l < loc->getNbBoundObjects(); ++l)
	{
		createNodeFromLocatedBindable(loc->getBoundObject(l),  nodeHandle);				
	}
}

//=====================================================================================================
void CParticleTreeCtrl::createNodeFromLocatedBindable(NL3D::CPSLocatedBindable *lb,  HTREEITEM rootHandle)
{
	// we ordered the image so that they match the type for a located bindable (force,  particles,  collision zones...)
	CNodeType *nt = new CNodeType(lb);
	_NodeTypes.push_back(nt);
	InsertItem(TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT ,  lb->getName().c_str() ,  lb->getType(),  lb->getType(),  PSIconForce,  PSIconForce,  (LPARAM) nt,  rootHandle,  TVI_LAST);
}



BEGIN_MESSAGE_MAP(CParticleTreeCtrl,  CTreeCtrl)
	//{{AFX_MSG_MAP(CParticleTreeCtrl)
	ON_NOTIFY_REFLECT(TVN_SELCHANGED,  OnSelchanged)
	ON_WM_RBUTTONDOWN()
	ON_NOTIFY_REFLECT(TVN_ENDLABELEDIT,  OnEndlabeledit)
	ON_NOTIFY_REFLECT(TVN_BEGINLABELEDIT, OnBeginlabeledit)
	ON_WM_LBUTTONDBLCLK()
	ON_NOTIFY_REFLECT(TVN_KEYDOWN, OnKeydown)
	ON_WM_KEYUP()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

//=====================================================================================================
void CParticleTreeCtrl::init(void)
{
	this->SetImageList(&_ImageList,  TVSIL_NORMAL);
}

//=====================================================================================================
void CParticleTreeCtrl::updateRightPane(CNodeType &nt)
{
	switch (nt.Type)
	{
		case CNodeType::located:
		{	
			nlassert(getOwnerNode(&nt));
			CLocatedProperties *lp = new CLocatedProperties(getOwnerNode(&nt), nt.Loc,  _ParticleDlg);	
			lp->init(0,  0);	
			_ParticleDlg->setRightPane(lp);
			if (_LastClickedPS)
			{
				_LastClickedPS->setCurrentEditedElement(NULL);
			}
			return;
		}
		break;
		case CNodeType::locatedBindable:
		{
			if (dynamic_cast<NL3D::CPSEmitter *>(nt.Bind))
			{
				CEmitterDlg *ed =  new CEmitterDlg(getOwnerNode(&nt), static_cast<NL3D::CPSEmitter *>(nt.Bind),  _ParticleDlg);
				ed->init(_ParticleDlg);
				_ParticleDlg->setRightPane(ed);
			}
			else
			if (dynamic_cast<NL3D::CPSTargetLocatedBindable *>(nt.Bind))
			{
				CLocatedTargetDlg *ltd =  new CLocatedTargetDlg(getOwnerNode(&nt),  static_cast<NL3D::CPSTargetLocatedBindable *>(nt.Bind),  _ParticleDlg);
				ltd->init(_ParticleDlg);
				_ParticleDlg->setRightPane(ltd);
			}
			else
			if (dynamic_cast<NL3D::CPSSound *>(nt.Bind))
			{
				nlassert(getOwnerNode(&nt));
				CEditPSSound *epss =  new CEditPSSound(getOwnerNode(&nt),  static_cast<NL3D::CPSSound *>(nt.Bind));
				epss->init(_ParticleDlg);
				_ParticleDlg->setRightPane(epss);
			}
			else
			if (dynamic_cast<NL3D::CPSLight *>(nt.Bind))
			{
				nlassert(getOwnerNode(&nt));
				CEditPSLight *epsl =  new CEditPSLight(getOwnerNode(&nt),  static_cast<NL3D::CPSLight *>(nt.Bind));
				epsl->init(_ParticleDlg);
				_ParticleDlg->setRightPane(epsl);
			}
			else
			{
				nlassert(getOwnerNode(&nt));
				CLocatedBindableDialog *lbd = new CLocatedBindableDialog(getOwnerNode(&nt), nt.Bind);
				lbd->init(_ParticleDlg);
				_ParticleDlg->setRightPane(lbd);			
			}
			if (_LastClickedPS)
			{
				_LastClickedPS->setCurrentEditedElement(NULL);
			}
			return;
		}
		break;
		case CNodeType::particleSystem:
		{
			// see if the particle system has been loaded
			if (nt.PS->isLoaded())
			{			
				CParticleSystemEdit *pse = new CParticleSystemEdit(nt.PS,  this);
				pse->init(_ParticleDlg);
				_ParticleDlg->setRightPane(pse);
			}
			else
			{
				_ParticleDlg->setRightPane(NULL);
			}
			if (_LastClickedPS)
			{
				_LastClickedPS->setCurrentEditedElement(NULL);
			}
		}
		break;
		case CNodeType::locatedInstance:
		{			
			NL3D::CParticleSystem *ps = nt.Loc->getOwner();									
			_LastClickedPS = ps;			
			CPSMoverDlg *moverDlg = new CPSMoverDlg(getOwnerNode(&nt),  this,  &_ParticleDlg->MainFrame->ObjView->getMouseListener(),  nt.Loc,  nt.LocatedInstanceIndex);			
			moverDlg->init(_ParticleDlg);
			_ParticleDlg->setRightPane(moverDlg);
			CObjectViewer *ov = _ParticleDlg->MainFrame->ObjView;
			if(_ParticleDlg->MainFrame->isMoveElement())
			{
				ov->getMouseListener().setModelMatrix(_ParticleDlg->getElementMatrix());
			}
			ps->setCurrentEditedElement(nt.Loc,  nt.LocatedInstanceIndex,  moverDlg->getLocatedBindable());			
		}
		break;
		case CNodeType::workspace:
		{
			if (_LastClickedPS)
			{
				_LastClickedPS->setCurrentEditedElement(NULL);
			}
			_ParticleDlg->setRightPane(NULL);
		}
		break;
	}		
}

/////////////////////////////////////////////////////////////////////////////
// CParticleTreeCtrl message handlers


void CParticleTreeCtrl::OnSelchanged(NMHDR* pNMHDR,  LRESULT* pResult) 
{
	NM_TREEVIEW* pNMTreeView = (NM_TREEVIEW*)pNMHDR;	
	*pResult = 0;
	CNodeType *nt = (CNodeType  *) pNMTreeView->itemNew.lParam;	
	nlassert(nt);
	updateRightPane(*nt);
}

//****************************************************************************************************************
void CParticleTreeCtrl::OnRButtonDown(UINT nFlags,  CPoint point) 
{

	if (_LastClickedPS)
	{
		_LastClickedPS->setCurrentEditedElement(NULL);
	}
	// test whether there is an item under that point
	UINT flags;
	HTREEITEM item  = this->HitTest(point,  &flags);
	if (item)
	{
		this->SelectItem(item);
		RECT r;
		GetWindowRect(&r);

		CMenu  menu;
		CMenu* subMenu;

		TVITEM item;
		item.mask = TVIF_HANDLE | TVIF_PARAM;
		item.hItem = GetSelectedItem();
		GetItem(&item);

		CNodeType *nt =  (CNodeType *) item.lParam;
		UINT bIsRunning = MF_BYCOMMAND | MF_DISABLED | MF_GRAYED;
		CParticleWorkspace::CNode *node = getOwnerNode(nt);
		if (node)
		{		
			if (!node->isStateMemorized() && _ParticleDlg->StartStopDlg->getState() != CStartStopParticleSystem::RunningMultiple)
			{			
				bIsRunning = MF_ENABLED;
			}
		}

		switch (nt->Type)
		{
			case CNodeType::located:
			{
				 menu.LoadMenu(IDR_LOCATED_MENU);
				 menu.EnableMenuItem(ID_INSTANCIATE_LOCATED,   bIsRunning);
				 menu.EnableMenuItem(IDM_COPY_LOCATED,   bIsRunning);
				 menu.EnableMenuItem(IDM_PASTE_BINDABLE,   bIsRunning);
			}
			break;
			case CNodeType::locatedBindable:
				menu.LoadMenu(IDR_LOCATED_BINDABLE_MENU);		 				
				menu.GetSubMenu(0)->CheckMenuItem(IDM_LB_LOD1N2,  MF_UNCHECKED | MF_BYCOMMAND);
				menu.GetSubMenu(0)->CheckMenuItem(IDM_LB_LOD1,  MF_UNCHECKED | MF_BYCOMMAND);
				menu.GetSubMenu(0)->CheckMenuItem(IDM_LB_LOD2,  MF_UNCHECKED | MF_BYCOMMAND);
				// check the menu to tell which lod is used for this located bindable
				if (nt->Bind->getLOD() == NL3D::PSLod1n2) menu.GetSubMenu(0)->CheckMenuItem(IDM_LB_LOD1N2,  MF_CHECKED | MF_BYCOMMAND);
				if (nt->Bind->getLOD() == NL3D::PSLod1) menu.GetSubMenu(0)->CheckMenuItem(IDM_LB_LOD1,  MF_CHECKED | MF_BYCOMMAND);
				if (nt->Bind->getLOD() == NL3D::PSLod2) menu.GetSubMenu(0)->CheckMenuItem(IDM_LB_LOD2,  MF_CHECKED | MF_BYCOMMAND);

				 menu.EnableMenuItem(IDM_COPY_BINDABLE,   bIsRunning);
			break;
			case CNodeType::particleSystem:
				if (nt->PS->isLoaded())
				{
					 menu.LoadMenu(IDR_PARTICLE_SYSTEM_MENU);	
 					 menu.EnableMenuItem(IDM_PASTE_LOCATED,   bIsRunning);
				}
				else
				{
					menu.LoadMenu(IDR_PARTICLE_SYSTEM_NOT_LOADED_MENU);
				}
			break;
			case CNodeType::locatedInstance:
				menu.LoadMenu(IDR_LOCATED_INSTANCE_MENU);
			break;
			case CNodeType::workspace:
				menu.LoadMenu(IDR_PARTICLE_WORKSPACE_MENU);
			break;
			default:
				return;
			break;
		}		
		subMenu = menu.GetSubMenu(0);    
		nlassert(subMenu);
		subMenu->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON,  r.left + point.x,  r.top + point.y,  this);
	}	
}

//****************************************************************************************************************
void CParticleTreeCtrl::deleteSelection()
{
	if (!GetSelectedItem()) return;
	CNodeType *nt = (CNodeType *) GetItemData(GetSelectedItem());
	switch(nt->Type)
	{
		case CNodeType::located:
		{			
			_ParticleDlg->setRightPane(NULL);			
			CPSLocated *loc = nt->Loc;
			touchPSState(nt);
			CParticleWorkspace::CNode *ownerNode = getOwnerNode(nt);
			nlassert(ownerNode);
			ownerNode->setModified(true);
			// if the system is running,  we must destroy initial infos about the located, 
			// as they won't need to be restored when the stop button will be pressed			
			ownerNode->removeLocated(loc);
			_ParticleDlg->StartStopDlg->resetAutoCount(getOwnerNode(nt));
			nt->getOwnerPS()->remove(loc);	
			removeTreePart(GetSelectedItem());
		}
		break;
		case CNodeType::particleSystem:
		{		
			if (GetSelectedItem() == _LastActiveNode)
			{
				setActiveNode(NULL);
				_ParticleDlg->setActiveNode(NULL);
			}
			nt->PS->getWorkspace()->removeNode(nt->PS);
			removeTreePart(GetSelectedItem());
			_ParticleDlg->setRightPane(NULL);
		}
		break;
		case CNodeType::locatedBindable:
		{			
			CPSLocatedBindable *lb = nt->Bind;
			touchPSState(nt);
			CParticleWorkspace::CNode *ownerNode = getOwnerNode(nt);
			nlassert(ownerNode);
			// if the system is running,  we must destroy initial infos 
			// that what saved about the located bindable,  when the start button was pressed,  as they won't need
			// to be restored
			ownerNode->removeLocatedBindable(lb);
			ownerNode->setModified(true);
			_ParticleDlg->StartStopDlg->resetAutoCount(getOwnerNode(nt));
			lb->getOwner()->remove(lb);						
			removeTreePart(GetSelectedItem());
			_ParticleDlg->setRightPane(NULL);
		}
		break;
		case CNodeType::locatedInstance:
		{
			nlassert(nt->Type == CNodeType::locatedInstance);
			nlassert(getOwnerNode(nt));
			getOwnerNode(nt)->setModified(true);
			_ParticleDlg->StartStopDlg->resetAutoCount(getOwnerNode(nt));
			CParticleWorkspace::CNode *owner = getOwnerNode(nt);
			nlassert(owner);
			//suppressLocatedInstanceNbItem(*owner);
			NL3D::CPSEmitter::setBypassEmitOnDeath(true);
			nt->Loc->deleteElement(nt->LocatedInstanceIndex);					
			NL3D::CPSEmitter::setBypassEmitOnDeath(false);
			CParticleWorkspace::CNode *ownerNode = getOwnerNode(nt);
			nlassert(ownerNode);
			// Move selection to parent
			HTREEITEM currItem = GetSelectedItem();
			SelectItem(GetParentItem(GetSelectedItem()));
			removeTreePart(currItem);
			suppressLocatedInstanceNbItem(*ownerNode, 0);
			rebuildLocatedInstance(*ownerNode);			
		}
		break;
		case CNodeType::workspace:
			// no-op -> close the workpsace
		break;
		default:
			nlassert(0);
		break;
	}
}

//****************************************************************************************************************
BOOL CParticleTreeCtrl::OnCmdMsg(UINT nID,  int nCode,  void* pExtra,  AFX_CMDHANDLERINFO* pHandlerInfo) 
{
	if (nCode != 0) return CTreeCtrl::OnCmdMsg(nID,  nCode,  pExtra,  pHandlerInfo);
	CPSLocatedBindable *toCreate = NULL;	
	CNodeType *nt = (CNodeType *) GetItemData(GetSelectedItem());
	bool	  createLocAndBindable = false; // when set to true,  must create a located and a simultaneously a located bindable		
	switch(nID)
	{
			///////////////
		// workspace //
		///////////////
		case IDM_INSERT_NEW_PS:
			nlassert(nt->Type == CNodeType::workspace);
			insertNewPS(*nt->WS);
		break;
		case IDM_CREATE_NEW_PS:
			nlassert(nt->Type == CNodeType::workspace);
			createNewPS(*nt->WS);
		break;
		case IDM_REMOVE_ALL_PS:
			removeAllPS(*nt->WS);
		break;
		case IDM_PS_SORT_BY_FILENAME:
		{
			nlassert(nt->Type == CNodeType::workspace);
			struct CSortByFilename : public CParticleWorkspace::ISort
			{
				bool less(const CParticleWorkspace::CNode &lhs, const CParticleWorkspace::CNode &rhs) const
				{					
					return lhs.getFilename() < rhs.getFilename();					
				}
			};
			sortWorkspace(*nt->WS, CSortByFilename());
		}			
		break;
		case IDM_PS_SORT_BY_NAME:
		{
			nlassert(nt->Type == CNodeType::workspace);
			struct CSortByName : public CParticleWorkspace::ISort
			{
				bool less(const CParticleWorkspace::CNode &lhs, const CParticleWorkspace::CNode &rhs) const
				{					
					if (!rhs.isLoaded())
					{
						if (lhs.isLoaded()) return true;
						return lhs.getFilename() < rhs.getFilename();
					}
					if (!lhs.isLoaded())
					{
						if (rhs.isLoaded()) return false;
					}
					return lhs.getPSPointer()->getName() < rhs.getPSPointer()->getName();
				}
			};
			sortWorkspace(*nt->WS, CSortByName());
		}
		break;
		case IDM_SAVE_PS_WORKSPACE:
		{
			_ParticleDlg->OnSavePsWorkspace();
		}
		break;
		case IDM_LOAD_PS_WORKSPACE:
		{
			_ParticleDlg->OnLoadPSWorkspace();
		}
		break;
		///////////////
		// particles //
		///////////////
		case IDM_DOT_LOC: createLocAndBindable = true;
		case IDM_DOT:
			toCreate = new NL3D::CPSDot;
		break;
		case IDM_LOOKAT_LOC:  createLocAndBindable = true;
		case IDM_LOOKAT:
			toCreate = new NL3D::CPSFaceLookAt; 
		break;
		case IDM_FANLIGHT_LOC:  createLocAndBindable = true;
		case IDM_FANLIGHT:
			toCreate = new NL3D::CPSFanLight;
		break;
		case IDM_RIBBON_LOC:  createLocAndBindable = true;
		case IDM_RIBBON:
			toCreate = new NL3D::CPSRibbon;
		break;
		case IDM_TAILDOT_LOC: createLocAndBindable = true;
		case IDM_TAILDOT:
			toCreate = new NL3D::CPSTailDot; 
		break;
		case IDM_MESH_LOC:  createLocAndBindable = true;
		case IDM_MESH:
			toCreate = new NL3D::CPSMesh;			
		break;
		case IDM_CONSTRAINT_MESH_LOC:  createLocAndBindable = true;
		case IDM_CONSTRAINT_MESH:
			toCreate = new NL3D::CPSConstraintMesh;
		break;
		case IDM_FACE_LOC:  createLocAndBindable = true;
		case IDM_FACE:
			toCreate = new NL3D::CPSFace;
		break;
		case IDM_SHOCKWAVE_LOC:  createLocAndBindable = true;
		case IDM_SHOCKWAVE:
			toCreate = new NL3D::CPSShockWave;
		break;
		case IDM_RIBBON_LOOK_AT_LOC:  createLocAndBindable = true;
		case IDM_RIBBON_LOOK_AT:
			toCreate = new NL3D::CPSRibbonLookAt;
		break;

		//////////////
		// emitters //
		//////////////

		case IDM_DIRECTIONNAL_EMITTER_LOC:  createLocAndBindable = true;
		case IDM_DIRECTIONNAL_EMITTER:
			toCreate = new NL3D::CPSEmitterDirectionnal;
		break;
		case IDM_OMNIDIRECTIONNAL_EMITTER_LOC:  createLocAndBindable = true;
		case IDM_OMNIDIRECTIONNAL_EMITTER:
			toCreate = new NL3D::CPSEmitterOmni;
		break;
		case IDM_CONIC_EMITTER_LOC:  createLocAndBindable = true;
		case IDM_CONIC_EMITTER:
			toCreate = new NL3D::CPSEmitterConic;
		break; 
		case IDM_RECTANGLE_EMITTER_LOC:  createLocAndBindable = true;
		case IDM_RECTANGLE_EMITTER:
			toCreate = new NL3D::CPSEmitterRectangle;
		break;
		case IDM_SPHERICAL_EMITTER_LOC:  createLocAndBindable = true;
		case IDM_SPHERICAL_EMITTER:
			toCreate = new NL3D::CPSSphericalEmitter;
		break;
		case IDM_RADIAL_EMITTER_LOC:  createLocAndBindable = true;
		case IDM_RADIAL_EMITTER:
			toCreate = new NL3D::CPSRadialEmitter;
		break;

		////////////////
		//   Zones    //
		////////////////
		
		case IDM_ZONE_PLANE_LOC:  createLocAndBindable = true;
		case IDM_ZONE_PLANE:
			toCreate = new NL3D::CPSZonePlane;
		break;
		case IDM_ZONE_SPHERE_LOC:  createLocAndBindable = true;
		case IDM_ZONE_SPHERE:
			toCreate = new NL3D::CPSZoneSphere;
		break;
		case IDM_ZONE_DISC_LOC:  createLocAndBindable = true;
		case IDM_ZONE_DISC:
			toCreate = new NL3D::CPSZoneDisc;
		break;
		case IDM_ZONE_RECTANGLE_LOC:  createLocAndBindable = true;
		case IDM_ZONE_RECTANGLE:
			toCreate = new NL3D::CPSZoneRectangle;
		break;
		case IDM_ZONE_CYLINDER_LOC:  createLocAndBindable = true;
		case IDM_ZONE_CYLINDER:
			toCreate = new NL3D::CPSZoneCylinder;
		break;

		///////////////
		//   forces  //
		///////////////
		case IDM_GRAVITY_FORCE_LOC:  createLocAndBindable = true;
		case IDM_GRAVITY_FORCE:
			toCreate = new NL3D::CPSGravity;
		break;
		case IDM_DIRECTIONNAL_FORCE_LOC:  createLocAndBindable = true;
		case IDM_DIRECTIONNAL_FORCE:
			toCreate = new NL3D::CPSDirectionnalForce;
		break;
		case IDM_SPRING_FORCE_LOC:  createLocAndBindable = true;
		case IDM_SPRING_FORCE:
			toCreate = new NL3D::CPSSpring;
		break;
		case IDM_FLUID_FRICTION_LOC:  createLocAndBindable = true;
		case IDM_FLUID_FRICTION:
			toCreate = new NL3D::CPSFluidFriction;
		break;
		case IDM_CENTRAL_GRAVITY_LOC:  createLocAndBindable = true;
		case IDM_CENTRAL_GRAVITY:
			toCreate = new NL3D::CPSCentralGravity;
		break;
		case IDM_CYLINDRIC_VORTEX_LOC:  createLocAndBindable = true;
		case IDM_CYLINDRIC_VORTEX:
			toCreate = new NL3D::CPSCylindricVortex;
		break;
		case IDM_BROWNIAN_MOVE_LOC:  createLocAndBindable = true;
		case IDM_BROWNIAN_MOVE:
			toCreate = new NL3D::CPSBrownianForce;
		break;
		case IDM_MAGNETIC_FORCE_LOC:  createLocAndBindable = true;
		case IDM_MAGNETIC_FORCE:
			toCreate = new NL3D::CPSMagneticForce;
		break;

		///////////////
		//    sound  //
		///////////////
		case IDM_SOUND_LOC:  createLocAndBindable = true;
		case IDM_SOUND:
			toCreate = new NL3D::CPSSound;
			if (!_ParticleDlg->StartStopDlg->isRunning())
			{
				(static_cast<NL3D::CPSSound *>(toCreate))->stopSound();
			}
		break;

		///////////////
		//    light  //
		///////////////
		case IDM_LIGHT_LOC:  createLocAndBindable = true;
		case IDM_LIGHT:
			toCreate = new NL3D::CPSLight;			
		break;

		//////////////
		// deletion //
		//////////////
		case IDM_DELETE_LOCATED:
		{
			deleteSelection();
			return TRUE;			
			//return CTreeCtrl::OnCmdMsg(nID,  nCode,  pExtra,  pHandlerInfo);
		}
		break;


		case IDM_DELETE_LOCATED_BINDABLE:
		{								
			deleteSelection();			
			return TRUE;			
		}
		break;

		case IDM_DELETE_LOCATED_INSTANCE:
		{
			deleteSelection();
			return TRUE;
		}
		break; 

		// instanciate an element
		case ID_INSTANCIATE_LOCATED:
		{						
			getOwnerNode(nt)->setModified(true);
			_ParticleDlg->StartStopDlg->resetAutoCount(getOwnerNode(nt));
			if (nt->Loc->getSize() == nt->Loc->getMaxSize())
			{
				nt->Loc->resize(nt->Loc->getMaxSize() + 1);
				// force a re-update of the left pane
				NM_TREEVIEW nmt;
				LRESULT pResult;
				nmt.itemNew.lParam = GetItemData(GetSelectedItem());
				OnSelchanged((NMHDR *) &nmt,  &pResult);
			}
			sint32 objIndex = nt->Loc->newElement(NLMISC::CVector::Null,  NLMISC::CVector::Null,  NULL,  0,  nt->Loc->getMatrixMode(),  0.f);
			nt = new CNodeType(nt->Loc,  objIndex);
			_NodeTypes.push_back(nt);
			// insert the element in the tree
			HTREEITEM root = InsertItem(TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT,  "instance",  PSIconLocatedInstance,  PSIconLocatedInstance,  0,  0,  (LPARAM) nt,  GetSelectedItem(),  TVI_LAST);
			SetItemData(root, (DWORD) nt);
			Invalidate();
		}
		break;

		////////////
		// LOD OP //
		////////////
		case IDM_LB_LOD1N2:
			nlassert(nt->Type = CNodeType::locatedBindable);
			nt->Bind->setLOD(NL3D::PSLod1n2);
			getOwnerNode(nt)->setModified(true);
		break;
		case IDM_LB_LOD1:
			nlassert(nt->Type = CNodeType::locatedBindable);
			nt->Bind->setLOD(NL3D::PSLod1);
			getOwnerNode(nt)->setModified(true);
		break;
		case IDM_LB_LOD2:
			nlassert(nt->Type = CNodeType::locatedBindable);
			nt->Bind->setLOD(NL3D::PSLod2);
			getOwnerNode(nt)->setModified(true);
		break;

		////////////////
		// extern ID  //
		////////////////
		case IDM_LB_EXTERN_ID:
		{
			nlassert(nt->Type = CNodeType::locatedBindable);
			nlassert(nt->Bind);
			CLBExternIDDlg 	dlg(nt->Bind->getExternID());
			INT_PTR res = dlg.DoModal();
			if ( res == IDOK )
			{
				nt->Bind->setExternID( dlg.getNewID() );
				getOwnerNode(nt)->setModified(true);
			}
		}
		break;
		////////////////////////
		//		COPY / PASTE  //
		////////////////////////
		case IDM_COPY_LOCATED:
			nlassert(nt->Type == CNodeType::located);
			nlassert(nt->Loc);			
			_LocatedCopy.reset(NLMISC::safe_cast<NL3D::CPSLocated *>(::DupPSLocated(nt->Loc)));
		break;
		case IDM_COPY_BINDABLE:
			nlassert(nt->Type == CNodeType::locatedBindable);
			nlassert(nt->Bind);			
			_LocatedBindableCopy.reset(::DupPSLocatedBindable(nt->Bind));
		break;
		case IDM_PASTE_LOCATED:
		{			
			nlassert(nt->Type== CNodeType::particleSystem);
			nlassert(nt->PS);
			getOwnerNode(nt)->setModified(true);
			_ParticleDlg->StartStopDlg->resetAutoCount(getOwnerNode(nt));
			CPSLocated *copy = dynamic_cast<CPSLocated *>(::DupPSLocated(_LocatedCopy.get()));
			if (!copy) break;
			if (nt->PS->getPSPointer()->attach(copy))
			{			
				createNodeFromLocated(copy, GetSelectedItem());
				Invalidate();
			}
			else
			{
				CString mess;
				mess.LoadString(IDS_PS_NO_FINITE_DURATION);
				CString errorStr;
				errorStr.LoadString(IDS_ERROR);
				MessageBox((LPCTSTR) mess,  (LPCTSTR) errorStr,  MB_ICONEXCLAMATION);
			}
		}
		break;
		case IDM_PASTE_BINDABLE:
		{
			nlassert(nt->Type == CNodeType::located);
			nlassert(nt->Loc);
			getOwnerNode(nt)->setModified(true);
			_ParticleDlg->StartStopDlg->resetAutoCount(getOwnerNode(nt));
			CPSLocatedBindable *copy = ::DupPSLocatedBindable(_LocatedBindableCopy.get());
			if (!copy) break;
			if (nt->Loc->bind(copy))
			{			
				createNodeFromLocatedBindable(copy,  GetSelectedItem());
				Invalidate();
			}
			else
			{
				delete copy;
				CString mess;
				mess.LoadString(IDS_PS_NO_FINITE_DURATION);
				CString errorStr;
				errorStr.LoadString(IDS_ERROR);
				MessageBox((LPCTSTR) mess,  (LPCTSTR) errorStr,  MB_ICONEXCLAMATION);
			}
		}
		break;


		////////////////////////
		// PARTICLE SYSTEM OP //
		////////////////////////
		case IDM_SET_ACTIVE_PARTICLE_SYSTEM:
			nlassert(nt->Type == CNodeType::particleSystem);
			setActiveNode(nt->PS);
			_ParticleDlg->setActiveNode(nt->PS);
		break;
		case ID_MENU_NEWLOCATED:
		{			
			getOwnerNode(nt)->setModified(true);
			createLocated(nt->PS->getPSPointer(),  GetSelectedItem());	
			Invalidate();
		}
		break;
		case IDM_RELOAD_PS:
		{			
			nlassert(!nt->PS->isLoaded());
			_ParticleDlg->loadPS(*this,  *nt->PS,  CParticleDlg::ReportError);
			if (nt->PS->isLoaded())
			{						
				// remove old icon
				HTREEITEM root = GetParentItem(GetSelectedItem());
				HTREEITEM previousSibling = GetPrevSiblingItem(GetSelectedItem());
				DeleteItem(GetSelectedItem());			
				// add newly loaded ps in the tree
				buildTreeFromPS(*nt->PS,  root,  previousSibling);
				updateRightPane(*nt);
			}
		}			
		break;
		case IDM_REMOVE_PS_FROM_WORKSPACE:
		{
			deleteSelection();			
			return TRUE;
		}
		break;		
		case IDM_MERGE_PS:
		{			
			_ParticleDlg->StartStopDlg->stop();
			static char BASED_CODE szFilter[] = "ps & shapes files(*.ps;*.shape)|*.ps; *.shape||";
			CFileDialog fd( TRUE,  ".ps",  "*.ps;*.shape",  0,  szFilter);
			
			if (fd.DoModal() == IDOK)
			{
				CParticleWorkspace::CNode *ownerNode = getOwnerNode(nt);
				nlassert(ownerNode);				
				// Add to the path
				char drive[256];
				char dir[256];
				char path[256];
				// Add search path for the texture
				_splitpath (fd.GetPathName(),  drive,  dir,  NULL,  NULL);
				_makepath (path,  drive,  dir,  NULL,  NULL);
				NLMISC::CPath::addSearchPath (path);				
				std::auto_ptr<NL3D::CShapeBank> sb(new NL3D::CShapeBank);
				CParticleSystemModel *psm  = NULL;
				try
				{					
					NL3D::CShapeStream ss;
					NLMISC::CIFile inputFile;
					inputFile.open((LPCTSTR) fd.GetPathName());
					ss.serial(inputFile);
					std::string shapeName = NLMISC::CFile::getFilename((LPCTSTR) fd.GetPathName());
					sb->add(shapeName, ss.getShapePointer());
					NL3D::CShapeBank *oldSB = CNELU::Scene->getShapeBank();
					CNELU::Scene->setShapeBank(sb.get());
					NL3D::CTransformShape *trs = CNELU::Scene->createInstance(shapeName);
					NL3D::CNELU::Scene->setShapeBank(oldSB);
					if (!trs)
					{
						localizedMessageBox(*this, IDS_COULDNT_INSTANCIATE_PS,  IDS_ERROR, MB_OK|MB_ICONEXCLAMATION);
						return false;
					}
					psm = dynamic_cast<CParticleSystemModel *>(trs);
					if (!psm)
					{
						localizedMessageBox(*this, IDS_COULDNT_INSTANCIATE_PS,  IDS_ERROR, MB_OK|MB_ICONEXCLAMATION);
						// Not a particle system
						NL3D::CShapeBank *oldSB = CNELU::Scene->getShapeBank();
						NL3D::CNELU::Scene->setShapeBank(sb.get());
						NL3D::CNELU::Scene->deleteInstance(trs);
						NL3D::CNELU::Scene->setShapeBank(oldSB);
						return false;
					}
				}
				catch(NLMISC::EStream &e)
				{
					MessageBox(e.what(),  getStrRsc(IDS_ERROR),  MB_OK|MB_ICONEXCLAMATION);
					return TRUE;
				}							
				ownerNode->setResetAutoCountFlag(false);								
				bool wasActiveNode = _LastActiveNode == GetSelectedItem();
				if (wasActiveNode)
				{
					_ParticleDlg->StartStopDlg->stop();
				}
				// link to the root for manipulation
				_ParticleDlg->_ObjView->getSceneRoot()->hrcLinkSon(psm);
				bool mergeOK = nt->PS->getPSPointer()->merge( NLMISC::safe_cast<NL3D::CParticleSystemShape *>((NL3D::IShape *) psm->Shape));
				NL3D::CShapeBank *oldSB = CNELU::Scene->getShapeBank();
				CNELU::Scene->setShapeBank(sb.get());
				CNELU::Scene->deleteInstance(psm);
				CNELU::Scene->setShapeBank(oldSB);
				if (!mergeOK)
				{					
					localizedMessageBox(*this, IDS_PS_NO_FINITE_DURATION,  IDS_ERROR,  MB_ICONEXCLAMATION);
					return TRUE;
				}				
				if (wasActiveNode)
				{					
					setActiveNode(NULL);					
					_ParticleDlg->setActiveNode(NULL);
				}
				HTREEITEM prevSibling = GetPrevSiblingItem(GetSelectedItem());
				HTREEITEM prevParent = GetParentItem(GetSelectedItem());
				removeTreePart(GetSelectedItem());				
				ownerNode->setModified(true);
				HTREEITEM newRoot = buildTreeFromPS(*ownerNode,  prevParent,  prevSibling);
				Select(newRoot, TVGN_CARET);
				if (wasActiveNode)
				{
					setActiveNode(ownerNode);
					_ParticleDlg->setActiveNode(ownerNode);
				}
				updateRightPane(*nt);				
				_LastClickedPS = NULL;				
			}								
		}
		break;
		case ID_MENU_SAVE_PS:
			nlassert(getOwnerNode(nt));
			_ParticleDlg->savePS(*this,  *getOwnerNode(nt),  false);	
		break;
		case IDM_SAVE_PS_AS:
		{
			if (nt->PS->getResetAutoCountFlag() && nt->PS->getPSPointer()->getAutoCountFlag())
			{		
				MessageBox(nt->PS->getFilename().c_str() + getStrRsc(IDS_AUTO_COUNT_ERROR), getStrRsc(IDS_WARNING), MB_ICONEXCLAMATION);				
			}			
			else
			{			
				_ParticleDlg->StartStopDlg->stop();
				std::string fileName = nt->PS->getFilename();
				static char BASED_CODE szFilter[] = "ps & shapes files(*.ps;*.shape)|*.ps; *.shape||";
				CFileDialog fd(FALSE,  ".ps",  fileName.c_str(),  OFN_OVERWRITEPROMPT,  szFilter, this);				
				if (fd.DoModal() == IDOK)
				{
					_ParticleDlg->savePSAs(*this, *nt->PS, (LPCTSTR) fd.GetPathName(), false);
				}
			}
		}
		break;
		case IDM_CLEAR_PS_CONTENT:
		{
			if (localizedMessageBox(*this, IDS_CLEAR_CONTENT,  IDS_PARTICLE_EDITOR,  MB_YESNO) == IDYES)
			{				
				CParticleWorkspace::CNode *ownerNode = getOwnerNode(nt);
				nlassert(ownerNode);
				ownerNode->setResetAutoCountFlag(false);								
				bool wasActiveNode = _LastActiveNode == GetSelectedItem();
				if (wasActiveNode)
				{					
					setActiveNode(NULL);					
					_ParticleDlg->setActiveNode(NULL);
				}
				HTREEITEM prevSibling = GetPrevSiblingItem(GetSelectedItem());
				HTREEITEM prevParent = GetParentItem(GetSelectedItem());
				removeTreePart(GetSelectedItem());
				ownerNode->createEmptyPS();
				ownerNode->setModified(true);
				buildTreeFromPS(*ownerNode,  prevParent,  prevSibling);
				if (wasActiveNode)
				{
					setActiveNode(ownerNode);
					_ParticleDlg->setActiveNode(ownerNode);
				}
				updateRightPane(*nt);
				_LastClickedPS = NULL;
			}
		}
		break;
		//////////
		// MISC //
		//////////
		case  IDM_FORCE_ZBIAS:
			// querry zbias
			CSetValueDlg valueDlg;			
			// Set default value
			valueDlg.Value="0.00";
			valueDlg.Title.LoadString(IDS_FORCE_ZBIAS);			
			// Open dialog
			if (valueDlg.DoModal ()==IDOK)
			{
				float value;		
				int dummy; // to avoid non numeric characters at the end
				if (sscanf ((LPCTSTR)(valueDlg.Value + "\n0"),  "%f\n%d",  &value,  &dummy) == 2)
				{
					nlassert(getOwnerNode(nt)->getPSPointer());
					getOwnerNode(nt)->getPSPointer()->setZBias(-value);
					getOwnerNode(nt)->setModified(true);
				}
				else
				{
					CString caption;
					CString mess;
					caption.LoadString(IDS_CAPTION_ERROR);
					mess.LoadString(IDS_BAD_ZBIAS);								
					MessageBox((LPCTSTR) mess,  (LPCTSTR) caption,  MB_ICONERROR);
				}
			}			
		break;
	}


	if (toCreate)
	{
		HTREEITEM son,  lastSon,  father;
		if (createLocAndBindable)
		{
			
			std::pair<CParticleTreeCtrl::CNodeType *,  HTREEITEM> p = createLocated(nt->PS->getPSPointer(),  GetSelectedItem());
			nt = p.first;
			son = 0;
			father = p.second;
			lastSon = p.second;
			nlassert(getOwnerNode(nt) && getOwnerNode(nt)->getPSPointer());				
			if (getOwnerNode(nt)->getPSPointer()->getBypassMaxNumIntegrationSteps())
			{		
				if (toCreate->getType() == NL3D::PSParticle || toCreate->getType() == NL3D::PSEmitter)
				{				
					nt->Loc->setInitialLife(1.f);
				}
				// object must have finite duration with that flag				
			}
		}
		else
		{
			son = GetChildItem(GetSelectedItem());
			father = GetSelectedItem();
		}				
		if (!nt->Loc->bind(toCreate))
		{
			MessageBox("The system is flagged with 'No max Nb steps',  or uses the preset 'Spell FX'. System must have finite duration. Can't add object. To solve this,  set a limited life time for the father.",  "Error",  MB_ICONEXCLAMATION);
			delete toCreate;
			if (createLocAndBindable)
			{			
				nlassert(0);
				/* ps->remove(nt->Loc);	
				DeleteItem(father);				
				_NodeTypes.erase(std::find(_NodeTypes.begin(),  _NodeTypes.end(),  nt));
				delete nt;*/
			}
			return CTreeCtrl::OnCmdMsg(nID,  nCode,  pExtra,  pHandlerInfo);
		}			
		// complete the name
		std::string name = toCreate->getName();
		char num[128];
		if (_PSElementIdentifiers.count(name))
		{			 
			sprintf(num,  "%d",  ++_PSElementIdentifiers[name]);
			toCreate->setName(name + num);
		}
		else
		{ 
			_PSElementIdentifiers[toCreate->getName()] = 0;
			toCreate->setName(name + "0");
		}						
		CNodeType *newNt = new CNodeType(toCreate);
		_NodeTypes.push_back(newNt);

		// insert the element in the tree
		// we want that the instance always appears in the last position				
		if (!createLocAndBindable)
		{
			if (!son)
			{
				lastSon = GetSelectedItem();
			}
			else
			{

				lastSon  = TVI_FIRST;

				
				while (son != NULL)
				{				
					if (((CNodeType *) GetItemData(son))->Type == CNodeType::locatedInstance)
					{
						break;
					}
					lastSon = son;
					son = GetChildItem(son);
				}
			}
		}
		getOwnerNode(nt)->setModified(true);
		// TODO : an enum for CPSLocatedBindable::getType would be better...
		InsertItem(TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT,  toCreate->getName().c_str(),  toCreate->getType(),  toCreate->getType(),  0,  0,  (LPARAM) newNt,  father,  lastSon);		
		touchPSState(nt);		
		Invalidate();		
		_ParticleDlg->StartStopDlg->resetAutoCount(getOwnerNode(nt));
	}
	return CTreeCtrl::OnCmdMsg(nID,  nCode,  pExtra,  pHandlerInfo);
}

//****************************************************************************************************************
std::pair<CParticleTreeCtrl::CNodeType *,  HTREEITEM> CParticleTreeCtrl::createLocated(NL3D::CParticleSystem *ps,  HTREEITEM headItem)
{ 
	std::string name; 
	char num[128];
	if (_PSElementIdentifiers.count(std::string("located")))
	{
		sprintf(num,  "located %d",  ++_PSElementIdentifiers[std::string("located")]);
		name = num;
	}
	else
	{
		name = std::string("located 0");
		_PSElementIdentifiers["located"] = 0;
	}			
	CPSLocated *loc = new CPSLocated;
	loc->setName(name);
	loc->setMatrixMode(NL3D::PSFXWorldMatrix);
	ps->attach(loc);

	CNodeType *newNt = new CNodeType(loc);
	_NodeTypes.push_back(newNt);
	// insert item in tree
	HTREEITEM insertedItem = InsertItem(TVIF_IMAGE | TVIF_SELECTEDIMAGE | TVIF_PARAM | TVIF_TEXT,  name.c_str(),  PSIconLocated,  PSIconLocated,  0,  0,  (LPARAM) newNt,  headItem,  TVI_LAST);
	touchPSState(newNt);	
	return std::make_pair(newNt,  insertedItem);
}


//****************************************************************************************************************
/// The user finished to edit a label in the tree
void CParticleTreeCtrl::OnEndlabeledit(NMHDR* pNMHDR,  LRESULT* pResult) 
{
	NMTVDISPINFO* info = (NMTVDISPINFO*)pNMHDR;
	*pResult = 0;
	if (info->item.pszText)
	{
		CNodeType *nt = (CNodeType *) info->item.lParam;
		switch (nt->Type)
		{
			case CNodeType::workspace:
			{
				nt->WS->setName(std::string(info->item.pszText));
				workspaceModifiedFlagChanged(*nt->WS); // change name (this may be called twice because of the modification callback, but this doesn't matter)
			}
			break;
			case CNodeType::particleSystem:
			{
				if (!nt->PS->isLoaded())
				{
					localizedMessageBox(*this, IDS_CANT_CHANGE_PS_NAME, IDS_ERROR, MB_OK|MB_ICONEXCLAMATION);
				}
				else
				{				
					nt->PS->getPSPointer()->setName(std::string(info->item.pszText));
					nt->PS->setModified(true);
				}
				this->SetItemText(info->item.hItem,  computeCaption(*nt->PS).c_str());
			}
			break;
			case CNodeType::located:
			{
				nlassert(getOwnerNode(nt));
				getOwnerNode(nt)->setModified(true);
				this->SetItemText(info->item.hItem,  info->item.pszText);
				nt->Loc->setName(std::string(info->item.pszText));
			}
			break;
			case CNodeType::locatedBindable:
			{
				nlassert(getOwnerNode(nt));
				getOwnerNode(nt)->setModified(true);
				this->SetItemText(info->item.hItem,  info->item.pszText);
				nt->Bind->setName(std::string(info->item.pszText));
			}
			break;
		}

	}
}


//****************************************************************************************************************
void CParticleTreeCtrl::moveElement(const NLMISC::CMatrix &m)
{
	NLMISC::CMatrix mat;

	// no == operator yet... did the matrix change ?
	if (m.getPos() == mat.getPos()
		&& m.getI() == mat.getI() 
		&& m.getJ() == mat.getJ() 
		&& m.getK() == mat.getK() 
	   ) return;

	nlassert(_ParticleDlg);	
	
	// the current element must be an instance
	if (::IsWindow(m_hWnd))
	{
		HTREEITEM currItem = GetSelectedItem();
		if (currItem)
		{			
			CNodeType *nt = (CNodeType *) GetItemData(currItem);
			if (nt->Type == CNodeType::locatedInstance)
			{
				if (nt->Loc->getMatrixMode() == NL3D::PSFXWorldMatrix)
				{
					mat = _ParticleDlg->getPSWorldMatrix().inverted() * m;	
				}
				else if (nt->Loc->getMatrixMode() == NL3D::PSIdentityMatrix)
				{
					mat = m;
				}
				else
				{
					return;
				}

				CWnd *rightPane = _ParticleDlg->getRightPane();
				NL3D::IPSMover *psm = NULL;
				if (dynamic_cast<CPSMoverDlg *>(rightPane ))
				{
					CPSMoverDlg *rp = (CPSMoverDlg *) rightPane;
					psm = ((CPSMoverDlg *) rightPane)->getMoverInterface();
													
					if (psm && !psm->onlyStoreNormal())
					{									
						psm->setMatrix(rp->getLocatedIndex(),  mat);										
					}
					else
					{
						rp->getLocated()->getPos()[rp->getLocatedIndex()] = mat.getPos();
					}

					// update the dialog				
					rp->updatePosition();
				}
			}
		}
	}
}


//****************************************************************************************************************
NLMISC::CMatrix CParticleTreeCtrl::getElementMatrix(void) const
{
	HTREEITEM currItem = GetSelectedItem();
	if (currItem)
	{
		CNodeType *nt = (CNodeType *) GetItemData(currItem);
		if (nt->Type == CNodeType::locatedInstance)
		{
			NLMISC::CVector pos = nt->Loc->getPos()[nt->LocatedInstanceIndex];
			NLMISC::CMatrix m;
			m.identity();
			m.setPos(pos);
			if (nt->Loc->getMatrixMode() == NL3D::PSFXWorldMatrix)
			{
				m = _ParticleDlg->getPSWorldMatrix() * m;
			}
			return m; 
		}
	}

	return NLMISC::CMatrix::Identity;
}


//****************************************************************************************************************
CParticleSystem *CParticleTreeCtrl::CNodeType::getOwnerPS() const
{
	switch(Type)
	{
		case located:			return Loc->getOwner();
		case particleSystem:	return PS->getPSPointer();
		case locatedBindable:	return Bind->getOwner()->getOwner();
		case locatedInstance:	return Loc->getOwner();
		case workspace:			return NULL;
		default:
			nlassert(0);
		break;
	}
	return NULL;
}

//****************************************************************************************************************
CParticleWorkspace::CNode *CParticleTreeCtrl::getOwnerNode(CNodeType *nt) const
{
	if (!nt) return NULL;
	HTREEITEM node = getTreeItem(nt);	
	while(node)
	{
		CNodeType *nt = (CNodeType *) GetItemData(node);
		if (!nt) return NULL;
		if (nt->Type == CNodeType::particleSystem)
		{
			return nt->PS;
		}
		node = GetParentItem(node);
	}
	return NULL;
}

//****************************************************************************************************************
void CParticleTreeCtrl::updateCaption(CParticleWorkspace::CNode &node)
{
	HTREEITEM item = getTreeItem(&node);
	if (!item) return;
	// update name of ps to dipslay a star in front of it (this tells that the ps has been modified)
	SetItemText(item,  computeCaption(node).c_str());	
}

//****************************************************************************************************************
std::string CParticleTreeCtrl::computeCaption(const std::string &path, const std::string &userName, bool modified)
{	
	std::string name;
	if (modified)
	{
		name = "* ";
	}
	if (!userName.empty())
	{ 
		name += userName;
		if (_ViewFilenameFlag)
		{
			name += " - ";
		}
	}
	if (_ViewFilenameFlag)
	{	
		name += NLMISC::CFile::getFilename(path);
	}
	return name;
}

//****************************************************************************************************************
std::string CParticleTreeCtrl::computeCaption(CParticleWorkspace::CNode &node)
{
	std::string baseCaption;
	if (node.isLoaded())
	{	
		baseCaption = computeCaption(node.getRelativePath(), node.getPSPointer()->getName(), node.isModified());
	}
	else
	{
		baseCaption = computeCaption(node.getRelativePath(), "", false);
	}	
	if (!node.getTriggerAnim().empty())
	{
		baseCaption = "(" + node.getTriggerAnim() + ") " + baseCaption;
	}
	if (node.getParentSkel())
	{	
		baseCaption = "(L) " + baseCaption;
	}
	return baseCaption;
}

//****************************************************************************************************************
std::string CParticleTreeCtrl::computeCaption(CParticleWorkspace &workspace)
{
	return computeCaption(workspace.getFilename(), workspace.getName(), workspace.isModified());	
}

//****************************************************************************************************************
void CParticleTreeCtrl::insertNewPS(CParticleWorkspace &pws)
{
	static const char BASED_CODE szFilter[] = "NeL Particle systems (*.ps)|*.ps||";
	CFileDialog fd(TRUE,  ".ps",  "*.ps",  OFN_ALLOWMULTISELECT|OFN_FILEMUSTEXIST,  szFilter,  this);	
	const uint MAX_NUM_CHAR = 65536;
	char filenamesBuf[MAX_NUM_CHAR];
	strcpy(filenamesBuf, "*.ps");
	fd.m_ofn.lpstrFile = filenamesBuf;
	fd.m_ofn.nMaxFile = MAX_NUM_CHAR - 1;
	if (fd.DoModal() == IDOK)
	{
		CParticleWorkspace::IModificationCallback *oldCallback = pws.getModificationCallback();
		pws.setModificationCallback(NULL);
		POSITION pos = fd.GetStartPosition();
		bool diplayLoadingError = true;
		bool diplayNodeAlreadyInserted = true;
		bool hasSelectedNode = _ParticleDlg->getActiveNode() != NULL;
		CParticleWorkspace::CNode *firstLoadedNode = NULL;
		while (pos)
		{
			CString path = fd.GetNextPathName(pos);						
			CParticleWorkspace::CNode *node = pws.addNode((LPCTSTR) path);
			if (!node)
			{
				if (diplayNodeAlreadyInserted)
				{
					if (pos)
					{					
						CSkippableMessageBox smb(path + getStrRsc(IDS_PS_ALREADY_INSERTED), getStrRsc(IDS_ERROR), this);
						smb.DoModal();
						diplayNodeAlreadyInserted = !smb.getBypassFlag();
					}
					else
					{
						MessageBox(NLMISC::CFile::getFilename((LPCTSTR)path).c_str() + getStrRsc(IDS_PS_ALREADY_INSERTED), getStrRsc(IDS_ERROR), MB_OK|MB_ICONEXCLAMATION);
					}
				}								
				continue;				
			}
			if (diplayLoadingError)
			{
				diplayLoadingError = _ParticleDlg->loadPS(*this,  *node, pos ? CParticleDlg::ReportErrorSkippable : CParticleDlg::ReportError);
			}
			else
			{
				_ParticleDlg->loadPS(*this,  *node, CParticleDlg::Silent);
			}
			if (!node->isLoaded()) 
			{
				pws.removeNode(pws.getNumNode() - 1);				
			}
			else
			{		
				if (!firstLoadedNode) firstLoadedNode = node;			
				buildTreeFromPS(*node,  GetRootItem());
			}
		}
		pws.setModificationCallback(oldCallback);
		if (firstLoadedNode)
		{
			expandRoot();
			pws.touch();
			if (!hasSelectedNode)
			{
				_ParticleDlg->setActiveNode(firstLoadedNode);
				setActiveNode(firstLoadedNode);
			}
		}
		// update modified state
		SetItemText(GetRootItem(), computeCaption(pws).c_str());
	}
	
}

//****************************************************************************************************************
void CParticleTreeCtrl::createNewPS(CParticleWorkspace &pws)
{
	CCreateFileDlg cfd(getStrRsc(IDS_CREATE_NEW_PS), NLMISC::CPath::standardizeDosPath(pws.getPath()), "ps", this);
	if (cfd.DoModal() == IDOK)
	{
		if (pws.containsFile(cfd.getFileName()))
		{
			MessageBox((LPCTSTR) (CString(NLMISC::CFile::getFilename(cfd.getFileName()).c_str()) + getStrRsc(IDS_PS_ALREADY_INSERTED)),  getStrRsc(IDS_ERROR),  MB_ICONEXCLAMATION);
		}
		if (cfd.touchFile())
		{
			CParticleWorkspace::CNode *node = pws.addNode(cfd.getFullPath());
			nlassert(node); // should always succeed because we tested if file already exists
			node->createEmptyPS();
			_ParticleDlg->savePS(*this, *node,   false); // write empty ps to disk
			// create an icon at the end of workspace
			buildTreeFromPS(*node,  GetRootItem());
		}
	}
}

//****************************************************************************************************************
void CParticleTreeCtrl::removeTreePart(HTREEITEM root)
{
	CNodeType *nt = (CNodeType *) GetItemData(root);
	_NodeTypes.erase(std::find(_NodeTypes.begin(),  _NodeTypes.end(),  nt));
	delete nt;
	HTREEITEM child = GetChildItem(root);
	while (child)
	{
		HTREEITEM tmpChild = child;
		child = GetNextSiblingItem(child);
		removeTreePart(tmpChild);
	}	
	DeleteItem(root);
}

//****************************************************************************************************************
HTREEITEM CParticleTreeCtrl::getTreeItem(CNodeType *nt) const
{
	if (!GetRootItem()) return NULL;
	std::stack<HTREEITEM> leftToTraverse;
	leftToTraverse.push(GetRootItem());
	while (!leftToTraverse.empty())
	{
		HTREEITEM curr = leftToTraverse.top();
		leftToTraverse.pop();
		if ((CNodeType *) GetItemData(curr) == nt)
		{
			return curr;
		}
		if (GetChildItem(curr)) leftToTraverse.push(GetChildItem(curr));
		if (GetNextSiblingItem(curr)) leftToTraverse.push(GetNextSiblingItem(curr));
	}
	return NULL;
}

//****************************************************************************************************************
HTREEITEM CParticleTreeCtrl::getTreeItem(CParticleWorkspace::CNode *node) const
{
	if (!GetRootItem()) return NULL;
	std::stack<HTREEITEM> leftToTraverse;
	leftToTraverse.push(GetRootItem());
	while (!leftToTraverse.empty())
	{
		HTREEITEM curr = leftToTraverse.top();
		leftToTraverse.pop();
		const CNodeType *nt = (CNodeType *) GetItemData(curr);
		if (nt && nt->Type == CNodeType::particleSystem && nt->PS == node)
		{
			return curr;
		}
		if (GetChildItem(curr)) leftToTraverse.push(GetChildItem(curr));
		if (GetNextSiblingItem(curr)) leftToTraverse.push(GetNextSiblingItem(curr));
	}
	return NULL;
}

//****************************************************************************************************************
void CParticleTreeCtrl::nodeModifiedFlagChanged(CParticleWorkspace::CNode &node)
{
	updateCaption(node);	
}

//****************************************************************************************************************
void CParticleTreeCtrl::workspaceModifiedFlagChanged(CParticleWorkspace &ws)
{
	// for now assume that workspace is the root
	HTREEITEM root = GetRootItem();
	if (!root) return;
	SetItemText(root, (LPCTSTR) computeCaption(ws).c_str());
}

//****************************************************************************************************************
void CParticleTreeCtrl::nodeSkelParentChanged(CParticleWorkspace::CNode &node)
{
	updateCaption(node);	
}

//****************************************************************************************************************
void CParticleTreeCtrl::setActiveNode(CParticleWorkspace::CNode *node)
{
	HTREEITEM newItem = getTreeItem(node);
	if (_LastActiveNode) SetItemState(_LastActiveNode,  0,  TVIS_BOLD);
	if (newItem)
	{	
		SetItemState(newItem,  TVIS_BOLD,  TVIS_BOLD);
	}
	_LastActiveNode = newItem;
}

//****************************************************************************************************************
void CParticleTreeCtrl::touchPSState(CNodeType *nt)
{
	if (!nt) return;
	CParticleWorkspace::CNode *ownerNode = getOwnerNode(nt);
	if (ownerNode && ownerNode->getPSModel())
	{
		ownerNode->getPSModel()->touchLightableState();
		ownerNode->getPSModel()->touchTransparencyState();
	}
}

//****************************************************************************************************************
void CParticleTreeCtrl::OnBeginlabeledit(NMHDR* pNMHDR, LRESULT* pResult) 
{	
	TV_DISPINFO* pTVDispInfo = (TV_DISPINFO*)pNMHDR;	
	NMTVDISPINFO* info = (NMTVDISPINFO*)pNMHDR;
	*pResult = 0;
	CEdit* pEdit = GetEditControl();
	if (!pEdit) return;
	if (info->item.pszText)
	{
		CNodeType *nt = (CNodeType *) info->item.lParam;
		switch (nt->Type)
		{
			case CNodeType::workspace:
				pEdit->SetWindowText(nt->WS->getName().c_str());
			break;
			case CNodeType::particleSystem:
			{
				if (!nt->PS->isLoaded())
				{
					pEdit->SetWindowText("");
					//localizedMessageBox(*this, IDS_CANT_CHANGE_PS_NAME, IDS_ERROR, MB_OK|MB_ICONEXCLAMATION);
				}
				else
				{				
					pEdit->SetWindowText(nt->PS->getPSPointer()->getName().c_str());
				}				
			}
			break;
			case CNodeType::located:
			{				
				pEdit->SetWindowText(nt->Loc->getName().c_str());
			}
			break;
			case CNodeType::locatedBindable:
			{
				pEdit->SetWindowText(nt->Bind->getName().c_str());
			}
			break;
		}
	}	
	*pResult = 0;
}

//****************************************************************************************************************
void CParticleTreeCtrl::OnLButtonDblClk(UINT nFlags, CPoint point) 
{	
	UINT flags;	
	HTREEITEM item  = this->HitTest(point,  &flags);
	if (item)
	{
		CNodeType *nt =  (CNodeType *) GetItemData(item);
		nlassert(nt);
		if (nt->Type == CNodeType::particleSystem && nt->PS->isLoaded())
		{						
			_ParticleDlg->setActiveNode(nt->PS);
			setActiveNode(nt->PS);
			return;
		}
	}
	CTreeCtrl::OnLButtonDblClk(nFlags, point);
}

//****************************************************************************************************************
void CParticleTreeCtrl::sortWorkspace(CParticleWorkspace &ws, CParticleWorkspace::ISort &sorter)
{
	// stop all fx
	nlassert(_ParticleDlg);
	_ParticleDlg->StartStopDlg->stop();
	CParticleWorkspace::CNode *activeNode = _ParticleDlg->getActiveNode();
	setActiveNode(NULL);
	reset();
	ws.sort(sorter);
	buildTreeFromWorkSpace(ws);
	Select(GetRootItem(), TVGN_FIRSTVISIBLE);
	setActiveNode(activeNode);
	expandRoot();
}

//****************************************************************************************************************
void CParticleTreeCtrl::OnKeydown(NMHDR* pNMHDR, LRESULT* pResult) 
{
	/*
	TV_KEYDOWN* pTVKeyDown = (TV_KEYDOWN*)pNMHDR;
	if (pTVKeyDown->wVKey == VK_DELETE)
	{
		deleteSelection();
	}	
	*pResult = 0;
	*/
}

//****************************************************************************************************************
void CParticleTreeCtrl::OnKeyUp(UINT nChar, UINT nRepCnt, UINT nFlags) 
{
	/*
	if (nChar == VK_DELETE)
	{
		deleteSelection();
	}		
	*/
	CTreeCtrl::OnKeyUp(nChar, nRepCnt, nFlags);
}

//****************************************************************************************************************
void CParticleTreeCtrl::expandRoot()
{
	if (GetRootItem()) Expand(GetRootItem(), TVE_EXPAND);
}
	
//****************************************************************************************************************
void CParticleTreeCtrl::setViewFilenameFlag(bool enabled)
{
	if (enabled == _ViewFilenameFlag) return;
	_ViewFilenameFlag = enabled;
	updateAllCaptions();
}

//****************************************************************************************************************
void CParticleTreeCtrl::updateAllCaptions()
{
	if (!GetRootItem()) return;
	std::stack<HTREEITEM> leftToTraverse;
	leftToTraverse.push(GetRootItem());
	// TODO: factorize code to traverse all nodes
	while (!leftToTraverse.empty())
	{
		HTREEITEM curr = leftToTraverse.top();
		leftToTraverse.pop();
		CNodeType *nt = (CNodeType *) GetItemData(curr);
		switch(nt->Type)
		{
			case CNodeType::particleSystem:
				SetItemText(curr, computeCaption(*nt->PS).c_str());
			break;
			case CNodeType::workspace:
				SetItemText(curr, computeCaption(*nt->WS).c_str());
			break;
			case CNodeType::located:			
			case CNodeType::locatedBindable:			
			case CNodeType::locatedInstance:
				// no-op
			break;
			default:
				nlassert(0);
			break;
		}
		if (GetChildItem(curr)) leftToTraverse.push(GetChildItem(curr));
		if (GetNextSiblingItem(curr)) leftToTraverse.push(GetNextSiblingItem(curr));
	}	
}

//****************************************************************************************************************
void CParticleTreeCtrl::removeAllPS(CParticleWorkspace &ws)
{
	if (localizedMessageBox(*this, IDS_REMOVE_ALL_PS, IDS_PARTICLE_SYSTEM_EDITOR, MB_OKCANCEL|MB_ICONQUESTION) != IDOK) return;
	setActiveNode(NULL);
	_ParticleDlg->setActiveNode(NULL);	
	uint numNodes = ws.getNumNode();
	for(uint k = 0; k < numNodes; ++k)
	{
		ws.removeNode((uint) 0);
	}
	reset();
	buildTreeFromWorkSpace(ws);
}