//========= Copyright © 1996-2005, 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 // // 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. CMapObjectRefList 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 (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++) { CUtlReference< CMapClass > object = pSelection->Element(i); if ( object->IsGroup() ) { // If it's a group, get all the entities in the group. const CMapObjectList *pChildren = object->GetChildren(); FOR_EACH_OBJ( *pChildren, pos ) { FindObject.SelectionList.AddToTail( pChildren->Element(pos) ); } } else { FindObject.SelectionList.AddToTail( object ); } } } 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); 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, m_strReplaceText); 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); }