//========= Copyright © 1996-2005, Valve Corporation, All rights reserved. ============// // // Purpose: The document. Exposes functions for object creation, deletion, and // manipulation. Holds the current tool. Handles GUI messages that are // view-independent. // //=============================================================================// #include "stdafx.h" #include "Selection.h" #include "mapdoc.h" #include "MapHelper.h" #include "MapSolid.h" #include "Manifest.h" #include "mapdefs.h" #include "globalfunctions.h" #include "mainfrm.h" #include "objectproperties.h" CSelection::CSelection(void) { m_pDocument = NULL; } CSelection::~CSelection(void) { } void CSelection::Init( CMapDoc *pDocument ) { m_pDocument = pDocument; m_eSelectMode = selectGroups; m_SelectionList.RemoveAll(); ClearHitList(); m_LastValidBounds.bmins = Vector(0, 0, 0); m_LastValidBounds.bmaxs = Vector(64, 64, 64); UpdateSelectionBounds(); } bool CSelection::IsSelected(CMapClass *pobj) { return (m_SelectionList.Find(pobj) != m_SelectionList.InvalidIndex()); } void CSelection::GetLastValidBounds(Vector &vecMins, Vector &vecMaxs) { vecMins = m_LastValidBounds.bmins; vecMaxs = m_LastValidBounds.bmaxs; } bool CSelection::GetBounds(Vector &vecMins, Vector &vecMaxs) { if ( m_bBoundsDirty ) UpdateSelectionBounds(); if ( m_SelectionList.Count() == 0) return false; vecMins = m_Bounds.bmins; vecMaxs = m_Bounds.bmaxs; return true;; } bool CSelection::GetLogicalBounds(Vector2D &vecMins, Vector2D &vecMaxs) { if ( m_bBoundsDirty ) UpdateSelectionBounds(); if ( m_SelectionList.Count() == 0) return false; vecMins = m_vecLogicalMins; vecMaxs = m_vecLogicalMaxs; return true; } //----------------------------------------------------------------------------- // Purpose: Used for translations. Uses entity origins and brush bounds. // That way, when moving stuff, the entity origins will stay on the grid. //----------------------------------------------------------------------------- void CSelection::GetBoundsForTranslation( Vector &vecMins, Vector &vecMaxs ) { vecMins.Init( COORD_NOTINIT, COORD_NOTINIT, 0 ); vecMaxs.Init( -COORD_NOTINIT, -COORD_NOTINIT, 0 ); // If there are any solids, then only use the bounds for those. Otherwise, // an entity that is off the grid can pull all the solids off the grid and you never want that. int nSolids = 0; for (int i = 0; i < m_SelectionList.Count(); i++) { CMapClass *pobj = m_SelectionList[i]; CEditGameClass *pEdit = dynamic_cast< CEditGameClass* >( pobj ); if ( (pEdit && pEdit->IsSolidClass()) || dynamic_cast(pobj) ) { ++nSolids; } } for (int i = 0; i < m_SelectionList.Count(); i++) { CMapClass *pobj = m_SelectionList[i]; // update physical bounds Vector mins, maxs; CEditGameClass *pEdit = dynamic_cast< CEditGameClass* >( pobj ); if ( (pEdit && pEdit->IsSolidClass()) || dynamic_cast(pobj) ) { pobj->GetRender2DBox(mins, maxs); } else if ( nSolids == 0 ) { pobj->GetOrigin( mins ); maxs = mins; } VectorMin( mins, vecMins, vecMins ); VectorMax( maxs, vecMaxs, vecMaxs ); } } void CSelection::UpdateSelectionBounds( void ) { m_Bounds.ResetBounds(); m_vecLogicalMins[0] = m_vecLogicalMins[1] = COORD_NOTINIT; m_vecLogicalMaxs[0] = m_vecLogicalMaxs[1] = -COORD_NOTINIT; for (int i = 0; i < m_SelectionList.Count(); i++) { CMapClass *pobj = m_SelectionList[i]; // update physical bounds Vector mins,maxs; pobj->GetRender2DBox(mins, maxs); m_Bounds.UpdateBounds(mins, maxs); // update logical bounds Vector2D logicalMins,logicalMaxs; pobj->GetRenderLogicalBox( logicalMins, logicalMaxs ); Vector2DMin( logicalMins, m_vecLogicalMins, m_vecLogicalMins ); Vector2DMax( logicalMaxs, m_vecLogicalMaxs, m_vecLogicalMaxs ); } // remeber bounds if valid if ( m_Bounds.IsValidBox() ) { m_LastValidBounds = m_Bounds; } m_bBoundsDirty = false; } bool CSelection::GetBoundsCenter(Vector &vecCenter) { if ( m_bBoundsDirty ) UpdateSelectionBounds(); if ( m_SelectionList.Count() == 0 ) return false; m_Bounds.GetBoundsCenter( vecCenter ); return true; } bool CSelection::GetLogicalBoundsCenter( Vector2D &vecCenter ) { if ( m_bBoundsDirty ) UpdateSelectionBounds(); if ( m_SelectionList.Count() == 0 ) return false; vecCenter = (m_vecLogicalMins+m_vecLogicalMaxs)/2; return true; } bool CSelection::IsEmpty() { return m_SelectionList.Count() == 0; } const CMapObjectList *CSelection::GetList() { return &m_SelectionList; } const CMapObjectList* CSelection::GetHitList() { return &m_HitList; } int CSelection::GetCount() { return m_SelectionList.Count(); } //----------------------------------------------------------------------------- // Purpose: Returns the current selection mode. The selection mode determines // what gets selected when the user clicks on something - the group, // the entity, or the solid. //----------------------------------------------------------------------------- SelectMode_t CSelection::GetMode() { return m_eSelectMode; } void CSelection::SetSelectionState(SelectionState_t eSelectionState) { for ( int i=0; i)m_SelectionList.Element(i); CMapEntity *pObject = (CMapEntity *)pMapClass; pObject->SetSelectionState( eSelectionState ); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- bool CSelection::IsAnEntitySelected(void) { if (m_SelectionList.Count() > 0) { int nSelCount = m_SelectionList.Count(); for (int i = 0; i < nSelCount; i++) { CMapClass *pObject = m_SelectionList.Element(i); CMapEntity *pEntity = dynamic_cast (pObject); if (pEntity != NULL) { return true; } } } return false; } //----------------------------------------------------------------------------- // Purpose: Returns true if the selection is editable. Every object must be // individually editable for this routine to return true. //----------------------------------------------------------------------------- bool CSelection::IsEditable() { if ( m_SelectionList.Count() > 0 ) { int nSelCount = m_SelectionList.Count(); for (int i = 0; i < nSelCount; i++) { CMapClass *pObject = m_SelectionList.Element(i); if ( pObject->IsEditable() == false ) { return false; } } } return true; } //----------------------------------------------------------------------------- // Purpose: Returns true if the selection is copyable. CManifestInstance classes // are not copyable. //----------------------------------------------------------------------------- bool CSelection::IsCopyable() { if ( m_SelectionList.Count() > 0 ) { int nSelCount = m_SelectionList.Count(); for (int i = 0; i < nSelCount; i++) { CMapClass *pObject = m_SelectionList.Element(i); if ( pObject->IsMapClass( MAPCLASS_TYPE( CManifestInstance ) ) ) { return false; } } } return true; } //----------------------------------------------------------------------------- // Purpose: Sets the current selection mode, which determines which objects // are selected when the user clicks on things. //----------------------------------------------------------------------------- void CSelection::SetMode(SelectMode_t eNewSelectMode) { SelectMode_t eOldSelectMode = m_eSelectMode; m_eSelectMode = eNewSelectMode; if ((eOldSelectMode == selectSolids) || ((eOldSelectMode == selectObjects) && (eNewSelectMode == selectGroups))) { // // If we are going from a more specific selection mode to a less specific one, // clear the selection. This avoids unexpectedly selecting new things. // SelectObject(NULL, scClear|scSaveChanges); } else { // // Put all the children of the selected objects in a list, along with their children. // CMapObjectList NewList; int nSelCount = m_SelectionList.Count(); for (int i = 0; i < nSelCount; i++) { CMapClass *pObject = m_SelectionList[i]; AddLeavesToListCallback(pObject, &NewList); pObject->EnumChildren((ENUMMAPCHILDRENPROC)AddLeavesToListCallback, (DWORD)&NewList); } SelectObject(NULL, scClear|scSaveChanges); // // Add child objects to selection. // for (int pos=0;posPrepareSelection(eNewSelectMode); if (pSelObject) { SelectObject(pSelObject, scSelect); } } } } //----------------------------------------------------------------------------- // Purpose: // Input : *pObject - //----------------------------------------------------------------------------- void CSelection::AddHit(CMapClass *pObject) { if ( m_HitList.Find(pObject) == -1 ) { m_HitList.AddToTail(pObject); } } //----------------------------------------------------------------------------- // Purpose: //----------------------------------------------------------------------------- void CSelection::ClearHitList(void) { m_HitList.RemoveAll(); m_iCurHit = -1; } bool CSelection::RemoveAll(void) { for ( int i=0;iSetSelectionState(SELECT_NONE); } m_SelectionList.RemoveAll(); SetBoundsDirty(); return true; } bool CSelection::RemoveDead(void) { bool bFoundOne = false; for ( int i=m_SelectionList.Count()-1; i>=0; i-- ) { CMapClass *pObject = m_SelectionList.Element(i); if (!pObject->GetParent()) { m_SelectionList.FastRemove(i); pObject->SetSelectionState(SELECT_NONE); bFoundOne = true; } } // TODO check if we do the same as in SelectObject SetBoundsDirty(); return bFoundOne; } //----------------------------------------------------------------------------- // Purpose: Removes objects that are not visible from the selection set. //----------------------------------------------------------------------------- bool CSelection::RemoveInvisibles(void) { bool bFoundOne = false; for ( int i=m_SelectionList.Count()-1; i>=0; i-- ) { CMapClass *pObject = m_SelectionList.Element(i); if ( !pObject->IsVisible() ) { m_SelectionList.FastRemove(i); pObject->SetSelectionState(SELECT_NONE); bFoundOne = true; } } SetBoundsDirty(); return bFoundOne; } //----------------------------------------------------------------------------- // Purpose: // Input : iIndex - // bUpdateViews - //----------------------------------------------------------------------------- void CSelection::SetCurrentHit(int iIndex, bool bCascading) { if ( m_HitList.Count() == 0) { Assert( m_iCurHit == -1); return; } // save & toggle old selection off if (m_iCurHit != -1) { CMapClass *pObject = m_HitList[m_iCurHit]; SelectObject(pObject, scToggle|scSaveChanges); } if (iIndex == hitNext) { // hit next object m_iCurHit++; } else if (iIndex == hitPrev) { // hit prev object m_iCurHit--; } else { m_iCurHit = iIndex; } // make sure curhit is valid if (m_iCurHit >= m_HitList.Count()) { m_iCurHit = 0; } else if (m_iCurHit < 0) { m_iCurHit = m_HitList.Count() - 1; } CMapClass *pObject = m_HitList[m_iCurHit]; if ( bCascading ) { // Build actual selection list based on cascading... CUtlRBTree< CMapClass*, unsigned short > tree( 0, 0, DefLessFunc( CMapClass* ) ); bool bRecursive = false; // not used yet m_pDocument->BuildCascadingSelectionList( pObject, tree, bRecursive ); CMapObjectList list; list.AddToTail( pObject ); bool bRootIsSelected = IsSelected(pObject); bool bUniformSelectionState = true; for ( unsigned short h = tree.FirstInorder(); h != tree.InvalidIndex(); h = tree.NextInorder(h) ) { list.AddToTail( list[h] ); if ( IsSelected( list[h] ) != bRootIsSelected ) { bUniformSelectionState = false; } } /* Change toggle to select or unselect if we're toggling and cascading // but the root + children have different selection state if ( ( !bUniformSelectionState ) && ( cmd == scToggle ) ) { cmd = bRootIsSelected ? scSelect : scUnselect; }*/ SelectObjectList( &list, scSelect ); } else { SelectObject(pObject, scToggle ); } } //----------------------------------------------------------------------------- // Purpose: // Input : pobj - // cmd - //----------------------------------------------------------------------------- bool CSelection::SelectObject(CMapClass *pObj, int cmd) { // if no object is given we only can execute the clear command if ( pObj == NULL ) { // check if selection is already empty if (m_SelectionList.Count() == 0) return false; // nothing to do if ( cmd & scClear ) { RemoveAll(); } } else // object oriented operation { int iIndex = m_SelectionList.Find(pObj); bool bAlreadySelected = iIndex != -1; if ( cmd & scToggle ) { if ( bAlreadySelected ) cmd |= scUnselect; else cmd |= scSelect; } if ( cmd & scSelect ) { if ( cmd & scClear ) { // if we re-selected the only selected element, nothing changes if ( bAlreadySelected && m_SelectionList.Count() == 1 ) return false; RemoveAll(); bAlreadySelected = false; // reset that flag } if ( bAlreadySelected ) return false; m_SelectionList.AddToTail(pObj); pObj->SetSelectionState(SELECT_NORMAL); } else if ( (cmd & scUnselect) && bAlreadySelected ) { // ok unselect an yet selected object m_SelectionList.FastRemove(iIndex); pObj->SetSelectionState(SELECT_NONE); } else { return false; // nothing was changed } } // ok something in the selection was changed, set dirty flags SetBoundsDirty(); if ( cmd & scSaveChanges ) { // changing the selection automatically saves changes made to the properties dialog GetMainWnd()->pObjectProperties->SaveData( SAVEDATA_SELECTION_CHANGED ); } // always mark data dirty GetMainWnd()->pObjectProperties->MarkDataDirty(); // uddate all views m_pDocument->UpdateAllViews( MAPVIEW_UPDATE_SELECTION ); return true; } //----------------------------------------------------------------------------- // Purpose: Clears the current selection and selects everything in the given list. // Input : pList - Objects to select. //----------------------------------------------------------------------------- void CSelection::SelectObjectList( const CMapObjectList *pList, int cmd ) { // Clear the current selection. // Clear the current selection. if ( cmd & scSaveChanges ) { GetMainWnd()->pObjectProperties->SaveData( SAVEDATA_SELECTION_CHANGED ); cmd &= ~scSaveChanges; } if ( cmd & scClear ) { RemoveAll(); cmd &= ~scClear; } if ( pList != NULL ) { for (int pos=0;posCount();pos++) { CMapClass *pObject = (CUtlReference< CMapClass >)pList->Element(pos); CMapClass *pSelObject = pObject->PrepareSelection( m_eSelectMode ); if (pSelObject) { SelectObject( pSelObject, cmd ); } } } }