600 lines
15 KiB
C++
600 lines
15 KiB
C++
//========= Copyright Valve Corporation, All rights reserved. ============//
|
|
//
|
|
// Purpose:
|
|
//
|
|
// $NoKeywords: $
|
|
//=============================================================================//
|
|
|
|
#include "stdafx.h"
|
|
#include "History.h"
|
|
#include "GlobalFunctions.h"
|
|
#include "MapDoc.h"
|
|
#include "MapWorld.h"
|
|
#include "SearchReplaceDlg.h"
|
|
#include "hammer.h"
|
|
#include "Selection.h"
|
|
|
|
// memdbgon must be the last include file in a .cpp file!!!
|
|
#include <tier0/memdbgon.h>
|
|
|
|
|
|
//
|
|
// Context data for a FindFirstObject/FindNextObject session.
|
|
//
|
|
struct FindObject_t
|
|
{
|
|
//
|
|
// Where to look: in the world or in the selection set.
|
|
//
|
|
FindReplaceIn_t eFindIn;
|
|
|
|
CMapWorld *pWorld;
|
|
EnumChildrenPos_t WorldPos; // A position in the world tree for world searches.
|
|
|
|
CUtlVector<CMapClass *> SelectionList; // A copy of the selection list for selection only searches.
|
|
int nSelectionIndex; // The index into the selection list for iterating the selection list.
|
|
|
|
//
|
|
// What to look for.
|
|
//
|
|
CString strFindText;
|
|
bool bVisiblesOnly;
|
|
bool bCaseSensitive;
|
|
bool bWholeWord;
|
|
};
|
|
|
|
|
|
CMapClass *FindNextObject(FindObject_t &FindObject);
|
|
bool FindCheck(CMapClass *pObject, FindObject_t &FindObject);
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the string matches the search criteria, false if not.
|
|
// Input : pszString - String to check.
|
|
// FindObject - Search criteria, including string to search for.
|
|
//-----------------------------------------------------------------------------
|
|
bool MatchString(const char *pszString, FindObject_t &FindObject)
|
|
{
|
|
if (FindObject.bWholeWord)
|
|
{
|
|
if (FindObject.bCaseSensitive)
|
|
{
|
|
return (!strcmp(pszString, FindObject.strFindText));
|
|
}
|
|
|
|
return (!stricmp(pszString, FindObject.strFindText));
|
|
}
|
|
|
|
if (FindObject.bCaseSensitive)
|
|
{
|
|
return (strstr(pszString, FindObject.strFindText) != NULL);
|
|
}
|
|
|
|
return (Q_stristr(pszString, FindObject.strFindText) != NULL);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Returns true if the string matches the search criteria, false if not.
|
|
// Input : pszIn -
|
|
// pszOut - String to check.
|
|
// FindObject - Search criteria, including string to search for.
|
|
//-----------------------------------------------------------------------------
|
|
bool ReplaceString(char *pszOut, const char *pszIn, FindObject_t &FindObject, const char *pszReplace)
|
|
{
|
|
//
|
|
// Whole matches are simple, just strcpy the replacement string into the out buffer.
|
|
//
|
|
if (FindObject.bWholeWord)
|
|
{
|
|
if (FindObject.bCaseSensitive && (!strcmp(pszIn, FindObject.strFindText)))
|
|
{
|
|
strcpy(pszOut, pszReplace);
|
|
return true;
|
|
}
|
|
|
|
if (!stricmp(pszIn, FindObject.strFindText))
|
|
{
|
|
strcpy(pszOut, pszReplace);
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Partial matches are a little tougher.
|
|
//
|
|
const char *pszStart = NULL;
|
|
if (FindObject.bCaseSensitive)
|
|
{
|
|
pszStart = strstr(pszIn, FindObject.strFindText);
|
|
}
|
|
else
|
|
{
|
|
pszStart = Q_stristr(pszIn, FindObject.strFindText);
|
|
}
|
|
|
|
if (pszStart != NULL)
|
|
{
|
|
int nOffset = pszStart - pszIn;
|
|
|
|
strncpy(pszOut, pszIn, nOffset);
|
|
pszOut += nOffset;
|
|
pszIn += nOffset + strlen(FindObject.strFindText);
|
|
|
|
strcpy(pszOut, pszReplace);
|
|
pszOut += strlen(pszReplace);
|
|
|
|
strcpy(pszOut, pszIn);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Begins a Find or Find/Replace operation.
|
|
//-----------------------------------------------------------------------------
|
|
CMapClass *FindFirstObject(FindObject_t &FindObject)
|
|
{
|
|
CMapClass *pObject = NULL;
|
|
|
|
if (FindObject.eFindIn == FindInWorld)
|
|
{
|
|
// Search the entire world.
|
|
pObject = FindObject.pWorld->GetFirstDescendent(FindObject.WorldPos);
|
|
}
|
|
else
|
|
{
|
|
// Search the selection only.
|
|
if (FindObject.SelectionList.Count())
|
|
{
|
|
pObject = FindObject.SelectionList.Element(0);
|
|
FindObject.nSelectionIndex = 1;
|
|
}
|
|
}
|
|
|
|
if (!pObject)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
if (FindCheck(pObject, FindObject))
|
|
{
|
|
return pObject;
|
|
}
|
|
|
|
return FindNextObject(FindObject);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : pObject -
|
|
//-----------------------------------------------------------------------------
|
|
CMapClass *FindNextObject(FindObject_t &FindObject)
|
|
{
|
|
while (true)
|
|
{
|
|
CMapClass *pObject = NULL;
|
|
if (FindObject.eFindIn == FindInWorld)
|
|
{
|
|
// Search the entire world.
|
|
pObject = FindObject.pWorld->GetNextDescendent(FindObject.WorldPos);
|
|
}
|
|
else
|
|
{
|
|
// Search the selection only.
|
|
if (FindObject.nSelectionIndex < FindObject.SelectionList.Count())
|
|
{
|
|
pObject = FindObject.SelectionList.Element(FindObject.nSelectionIndex);
|
|
FindObject.nSelectionIndex++;
|
|
}
|
|
}
|
|
|
|
if ((!pObject) || FindCheck(pObject, FindObject))
|
|
{
|
|
return pObject;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : pObject -
|
|
// FindObject -
|
|
// Output : Returns true if the object matches the search criteria, false if not.
|
|
//-----------------------------------------------------------------------------
|
|
bool FindCheck(CMapClass *pObject, FindObject_t &FindObject)
|
|
{
|
|
CMapEntity *pEntity = dynamic_cast <CMapEntity *>(pObject);
|
|
if (!pEntity)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (FindObject.bVisiblesOnly && !pObject->IsVisible())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
//
|
|
// Search keyvalues.
|
|
//
|
|
for ( int i=pEntity->GetFirstKeyValue(); i != pEntity->GetInvalidKeyValue(); i=pEntity->GetNextKeyValue( i ) )
|
|
{
|
|
const char *pszValue = pEntity->GetKeyValue(i);
|
|
if (pszValue && MatchString(pszValue, FindObject))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
//
|
|
// Search connections.
|
|
//
|
|
int nConnCount = pEntity->Connections_GetCount();
|
|
for (int i = 0; i < nConnCount; i++)
|
|
{
|
|
CEntityConnection *pConn = pEntity->Connections_Get(i);
|
|
if (pConn)
|
|
{
|
|
if (MatchString(pConn->GetTargetName(), FindObject) ||
|
|
MatchString(pConn->GetParam(), FindObject))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : pLastFound -
|
|
// FindObject -
|
|
// pszReplaceText -
|
|
// Output : Returns the number of occurrences of the find text that were replaced.
|
|
//-----------------------------------------------------------------------------
|
|
int FindReplace(CMapEntity *pEntity, FindObject_t &FindObject, const char *pszReplace)
|
|
{
|
|
int nReplacedCount = 0;
|
|
|
|
//
|
|
// Replace keyvalues.
|
|
//
|
|
for ( int i=pEntity->GetFirstKeyValue(); i != pEntity->GetInvalidKeyValue(); i=pEntity->GetNextKeyValue( i ) )
|
|
{
|
|
const char *pszValue = pEntity->GetKeyValue(i);
|
|
char szNewValue[MAX_PATH];
|
|
if (pszValue && ReplaceString(szNewValue, pszValue, FindObject, pszReplace))
|
|
{
|
|
const char *pszKey = pEntity->GetKey(i);
|
|
if (pszKey)
|
|
{
|
|
pEntity->SetKeyValue(pszKey, szNewValue);
|
|
nReplacedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
//
|
|
// Replace connections.
|
|
//
|
|
int nConnCount = pEntity->Connections_GetCount();
|
|
for (int i = 0; i < nConnCount; i++)
|
|
{
|
|
CEntityConnection *pConn = pEntity->Connections_Get(i);
|
|
if (pConn)
|
|
{
|
|
char szNewValue[MAX_PATH];
|
|
|
|
if (ReplaceString(szNewValue, pConn->GetTargetName(), FindObject, pszReplace))
|
|
{
|
|
pConn->SetTargetName(szNewValue);
|
|
nReplacedCount++;
|
|
}
|
|
|
|
if (ReplaceString(szNewValue, pConn->GetParam(), FindObject, pszReplace))
|
|
{
|
|
pConn->SetParam(szNewValue);
|
|
nReplacedCount++;
|
|
}
|
|
}
|
|
}
|
|
|
|
return nReplacedCount;
|
|
}
|
|
|
|
|
|
BEGIN_MESSAGE_MAP(CSearchReplaceDlg, CDialog)
|
|
//{{AFX_MSG_MAP(CSearchReplaceDlg)
|
|
ON_WM_SHOWWINDOW()
|
|
ON_COMMAND_EX(IDC_FIND_NEXT, OnFindReplace)
|
|
ON_COMMAND_EX(IDC_REPLACE, OnFindReplace)
|
|
ON_COMMAND_EX(IDC_REPLACE_ALL, OnFindReplace)
|
|
//}}AFX_MSG_MAP
|
|
END_MESSAGE_MAP()
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : pParent -
|
|
//-----------------------------------------------------------------------------
|
|
CSearchReplaceDlg::CSearchReplaceDlg(CWnd *pParent)
|
|
: CDialog(CSearchReplaceDlg::IDD, pParent)
|
|
{
|
|
m_bNewSearch = true;
|
|
|
|
//{{AFX_DATA_INIT(CSearchReplaceDlg)
|
|
m_bVisiblesOnly = FALSE;
|
|
m_nFindIn = FindInWorld;
|
|
m_bWholeWord = FALSE;
|
|
m_bCaseSensitive = FALSE;
|
|
//}}AFX_DATA_INIT
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Output : Returns TRUE on success, FALSE on failure.
|
|
//-----------------------------------------------------------------------------
|
|
BOOL CSearchReplaceDlg::Create(CWnd *pwndParent)
|
|
{
|
|
return CDialog::Create(CSearchReplaceDlg::IDD, pwndParent);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
// Input : pDX -
|
|
//-----------------------------------------------------------------------------
|
|
void CSearchReplaceDlg::DoDataExchange(CDataExchange* pDX)
|
|
{
|
|
CDialog::DoDataExchange(pDX);
|
|
|
|
//{{AFX_DATA_MAP(CSearchReplaceDlg)
|
|
DDX_Check(pDX, IDC_VISIBLES_ONLY, m_bVisiblesOnly);
|
|
DDX_Check(pDX, IDC_WHOLE_WORD, m_bWholeWord);
|
|
DDX_Check(pDX, IDC_CASE_SENSITIVE, m_bCaseSensitive);
|
|
DDX_Text(pDX, IDC_FIND_TEXT, m_strFindText);
|
|
DDX_Text(pDX, IDC_REPLACE_TEXT, m_strReplaceText);
|
|
DDX_Radio(pDX, IDC_SELECTION, m_nFindIn);
|
|
//}}AFX_DATA_MAP
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CSearchReplaceDlg::OnCancel(void)
|
|
{
|
|
ShowWindow(SW_HIDE);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Fill out the find criteria from the dialog controls.
|
|
// Input : FindObject -
|
|
//-----------------------------------------------------------------------------
|
|
void CSearchReplaceDlg::GetFindCriteria(FindObject_t &FindObject, CMapDoc *pDoc)
|
|
{
|
|
FindObject.pWorld = pDoc->GetMapWorld();
|
|
|
|
if (m_nFindIn == FindInSelection)
|
|
{
|
|
FindObject.eFindIn = FindInSelection;
|
|
|
|
FindObject.SelectionList.RemoveAll();
|
|
|
|
const CMapObjectList *pSelection = pDoc->GetSelection()->GetList();
|
|
for (int i = 0; i < pSelection->Count(); i++)
|
|
{
|
|
CMapClass *pObject = pSelection->Element(i);
|
|
if ( pObject->IsGroup() )
|
|
{
|
|
// If it's a group, get all the entities in the group.
|
|
const CMapObjectList *pChildren = pObject->GetChildren();
|
|
FOR_EACH_OBJ( *pChildren, pos )
|
|
{
|
|
FindObject.SelectionList.AddToTail( pChildren->Element(pos) );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FindObject.SelectionList.AddToTail(pObject);
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
FindObject.eFindIn = FindInWorld;
|
|
}
|
|
|
|
FindObject.strFindText = m_strFindText;
|
|
FindObject.bVisiblesOnly = (m_bVisiblesOnly == TRUE);
|
|
FindObject.bWholeWord = (m_bWholeWord == TRUE);
|
|
FindObject.bCaseSensitive = (m_bCaseSensitive == TRUE);
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called when they hit the Find, the Replace, or the Replace All button.
|
|
// Input : uCmd - The ID of the button the user hit, IDC_FIND_NEXT or IDC_REPLACE.
|
|
// Output : Returns TRUE to indicate that the message was handled.
|
|
//-----------------------------------------------------------------------------
|
|
BOOL CSearchReplaceDlg::OnFindReplace(UINT uCmd)
|
|
{
|
|
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
|
|
if (!pDoc)
|
|
{
|
|
return TRUE;
|
|
}
|
|
|
|
static FindObject_t FindObject;
|
|
static CMapClass *pLastFound = NULL;
|
|
static int nReplaceCount = 0;
|
|
FindObject_t TempFindObject;
|
|
|
|
bool bDone = false;
|
|
|
|
UpdateData();
|
|
GetFindCriteria(TempFindObject, pDoc);
|
|
|
|
if ( strcmp(TempFindObject.strFindText, FindObject.strFindText) != 0 )
|
|
{
|
|
m_bNewSearch = true;
|
|
}
|
|
|
|
do
|
|
{
|
|
CMapClass *pObject = NULL;
|
|
|
|
if (m_bNewSearch)
|
|
{
|
|
//
|
|
// New search. Fetch the data from the controls.
|
|
//
|
|
UpdateData();
|
|
GetFindCriteria(FindObject, pDoc);
|
|
|
|
//
|
|
// We have to keep track of the last object in the iteration for replacement,
|
|
// because replacement is done when me advance to the next object.
|
|
//
|
|
pLastFound = NULL;
|
|
nReplaceCount = 0;
|
|
|
|
pObject = FindFirstObject(FindObject);
|
|
}
|
|
else
|
|
{
|
|
pObject = FindNextObject(FindObject);
|
|
}
|
|
|
|
//
|
|
// Replace All is undone as single operation. Mark the undo position the first time
|
|
// we find a match during a Replace All.
|
|
//
|
|
if (m_bNewSearch && (uCmd == IDC_REPLACE_ALL) && pObject)
|
|
{
|
|
GetHistory()->MarkUndoPosition(pDoc->GetSelection()->GetList(), "Replace Text");
|
|
}
|
|
|
|
//
|
|
// If we have an object to do the replace on, do the replace.
|
|
//
|
|
if (pLastFound && ((uCmd == IDC_REPLACE) || (uCmd == IDC_REPLACE_ALL)))
|
|
{
|
|
if (uCmd == IDC_REPLACE)
|
|
{
|
|
// Allow for undo each time we do a Replace.
|
|
GetHistory()->MarkUndoPosition(NULL, "Replace Text");
|
|
}
|
|
|
|
//
|
|
// Do the replace on the last matching object we found. This lets the user see what
|
|
// object will be modified before it is done.
|
|
//
|
|
GetHistory()->Keep(pLastFound);
|
|
nReplaceCount += FindReplace((CMapEntity *)pLastFound, FindObject, m_strReplaceText);
|
|
|
|
GetDlgItem(IDCANCEL)->SetWindowText("Close");
|
|
}
|
|
|
|
if (pObject)
|
|
{
|
|
//
|
|
// We found an object that satisfies our search.
|
|
//
|
|
if ((uCmd == IDC_FIND_NEXT) || (uCmd == IDC_REPLACE))
|
|
{
|
|
//
|
|
// Highlight the match.
|
|
//
|
|
pDoc->SelectObject(pObject, scClear | scSelect);
|
|
pDoc->CenterViewsOnSelection();
|
|
}
|
|
|
|
//
|
|
// Stop after one match unless we are doing a Replace All.
|
|
//
|
|
if (uCmd != IDC_REPLACE_ALL)
|
|
{
|
|
bDone = true;
|
|
}
|
|
|
|
m_bNewSearch = false;
|
|
pLastFound = pObject;
|
|
}
|
|
else
|
|
{
|
|
//
|
|
// No more objects in the search set match our criteria.
|
|
//
|
|
if ((m_bNewSearch) || (uCmd != IDC_REPLACE_ALL))
|
|
{
|
|
CString str;
|
|
str.Format("Finished searching for '%s'.", m_strFindText.GetBuffer());
|
|
MessageBox(str, "Find/Replace Text", MB_OK);
|
|
|
|
// TODO: put the old selection back
|
|
}
|
|
else if (uCmd == IDC_REPLACE_ALL)
|
|
{
|
|
CString str;
|
|
str.Format("Replaced %d occurrences of the string '%s' with '%s'.", nReplaceCount, m_strFindText.GetBuffer(), m_strReplaceText.GetBuffer());
|
|
MessageBox(str, "Find/Replace Text", MB_OK);
|
|
}
|
|
|
|
m_bNewSearch = true;
|
|
bDone = true;
|
|
}
|
|
|
|
} while (!bDone);
|
|
|
|
return TRUE;
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose:
|
|
//-----------------------------------------------------------------------------
|
|
void CSearchReplaceDlg::OnOK()
|
|
{
|
|
}
|
|
|
|
|
|
//-----------------------------------------------------------------------------
|
|
// Purpose: Called any time we are hidden or shown.
|
|
// Input : bShow -
|
|
// nStatus -
|
|
//-----------------------------------------------------------------------------
|
|
void CSearchReplaceDlg::OnShowWindow(BOOL bShow, UINT nStatus)
|
|
{
|
|
if (bShow)
|
|
{
|
|
m_bNewSearch = true;
|
|
GetDlgItem(IDCANCEL)->SetWindowText("Cancel");
|
|
|
|
m_nFindIn = FindInWorld;
|
|
CMapDoc *pDoc = CMapDoc::GetActiveMapDoc();
|
|
if (pDoc)
|
|
{
|
|
if ( !pDoc->GetSelection()->IsEmpty() )
|
|
{
|
|
m_nFindIn = FindInSelection;
|
|
}
|
|
}
|
|
|
|
// Populate the controls with the current data.
|
|
UpdateData(FALSE);
|
|
}
|
|
|
|
CDialog::OnShowWindow(bShow, nStatus);
|
|
}
|