Team Fortress 2 Source Code as on 22/4/2020
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

647 lines
20 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // List of perforce files and operations
  4. //
  5. //=============================================================================
  6. #include "vgui_controls/perforcefilelistframe.h"
  7. #include "tier1/KeyValues.h"
  8. #include "vgui_controls/Button.h"
  9. #include "vgui_controls/ListPanel.h"
  10. #include "vgui_controls/Splitter.h"
  11. #include "vgui_controls/TextEntry.h"
  12. #include "vgui_controls/MessageBox.h"
  13. #include "tier2/tier2.h"
  14. #include "p4lib/ip4.h"
  15. #include "filesystem.h"
  16. #include "vgui/IVGui.h"
  17. // memdbgon must be the last include file in a .cpp file!!!
  18. #include "tier0/memdbgon.h"
  19. //-----------------------------------------------------------------------------
  20. // Sort by asset name
  21. //-----------------------------------------------------------------------------
  22. static int __cdecl OperationSortFunc( vgui::ListPanel *pPanel, const vgui::ListPanelItem &item1, const vgui::ListPanelItem &item2 )
  23. {
  24. const char *string1 = item1.kv->GetString("operation");
  25. const char *string2 = item2.kv->GetString("operation");
  26. int nRetVal = Q_stricmp( string1, string2 );
  27. if ( nRetVal != 0 )
  28. return nRetVal;
  29. string1 = item1.kv->GetString("filename");
  30. string2 = item2.kv->GetString("filename");
  31. return Q_stricmp( string1, string2 );
  32. }
  33. static int __cdecl FileBrowserSortFunc( vgui::ListPanel *pPanel, const vgui::ListPanelItem &item1, const vgui::ListPanelItem &item2 )
  34. {
  35. const char *string1 = item1.kv->GetString("filename");
  36. const char *string2 = item2.kv->GetString("filename");
  37. return Q_stricmp( string1, string2 );
  38. }
  39. //-----------------------------------------------------------------------------
  40. // Constructor
  41. //-----------------------------------------------------------------------------
  42. COperationFileListFrame::COperationFileListFrame( vgui::Panel *pParent, const char *pTitle, const char *pColumnHeader, bool bShowDescription, bool bShowOkOnly, int nDialogID ) :
  43. BaseClass( pParent, "PerforceFileList" )
  44. {
  45. m_pText = NULL;
  46. vgui::Panel *pBrowserParent = this;
  47. m_pDescription = NULL;
  48. m_pSplitter = NULL;
  49. if ( bShowDescription )
  50. {
  51. m_pSplitter = new vgui::Splitter( this, "Splitter", vgui::SPLITTER_MODE_HORIZONTAL, 1 );
  52. pBrowserParent = m_pSplitter->GetChild( 0 );
  53. vgui::Panel *pDescParent = m_pSplitter->GetChild( 1 );
  54. m_pDescription = new vgui::TextEntry( pDescParent, "Description" );
  55. m_pDescription->SetMultiline( true );
  56. m_pDescription->SetCatchEnterKey( true );
  57. m_pDescription->SetText( "<enter description here>" );
  58. }
  59. // FIXME: Might be nice to have checkboxes per row
  60. m_pFileBrowser = new vgui::ListPanel( pBrowserParent, "Browser" );
  61. m_pFileBrowser->AddColumnHeader( 0, "operation", "Operation", 52, 0 );
  62. m_pFileBrowser->AddColumnHeader( 1, "filename", pColumnHeader, 128, vgui::ListPanel::COLUMN_RESIZEWITHWINDOW );
  63. m_pFileBrowser->SetSelectIndividualCells( false );
  64. m_pFileBrowser->SetMultiselectEnabled( false );
  65. m_pFileBrowser->SetEmptyListText( "No Perforce Operations" );
  66. m_pFileBrowser->SetDragEnabled( true );
  67. m_pFileBrowser->AddActionSignalTarget( this );
  68. m_pFileBrowser->SetSortFunc( 0, OperationSortFunc );
  69. m_pFileBrowser->SetSortFunc( 1, FileBrowserSortFunc );
  70. m_pFileBrowser->SetSortColumn( 0 );
  71. m_pYesButton = new vgui::Button( this, "YesButton", "Yes", this, "Yes" );
  72. m_pNoButton = new vgui::Button( this, "NoButton", "No", this, "No" );
  73. SetBlockDragChaining( true );
  74. SetDeleteSelfOnClose( true );
  75. if ( bShowDescription )
  76. {
  77. LoadControlSettingsAndUserConfig( "resource/perforcefilelistdescription.res", nDialogID );
  78. }
  79. else
  80. {
  81. LoadControlSettingsAndUserConfig( "resource/perforcefilelist.res", nDialogID );
  82. }
  83. if ( bShowOkOnly )
  84. {
  85. m_pYesButton->SetText( "#MessageBox_OK" );
  86. m_pNoButton->SetVisible( false );
  87. }
  88. m_pContextKeyValues = NULL;
  89. SetTitle( pTitle, false );
  90. }
  91. COperationFileListFrame::~COperationFileListFrame()
  92. {
  93. SaveUserConfig();
  94. CleanUpMessage();
  95. if ( m_pText )
  96. {
  97. delete[] m_pText;
  98. }
  99. }
  100. //-----------------------------------------------------------------------------
  101. // Deletes the message
  102. //-----------------------------------------------------------------------------
  103. void COperationFileListFrame::CleanUpMessage()
  104. {
  105. if ( m_pContextKeyValues )
  106. {
  107. m_pContextKeyValues->deleteThis();
  108. m_pContextKeyValues = NULL;
  109. }
  110. }
  111. //-----------------------------------------------------------------------------
  112. // Performs layout
  113. //-----------------------------------------------------------------------------
  114. void COperationFileListFrame::PerformLayout()
  115. {
  116. BaseClass::PerformLayout();
  117. if ( m_pSplitter )
  118. {
  119. int x, y, w, h;
  120. GetClientArea( x, y, w, h );
  121. y += 6;
  122. h -= 36;
  123. m_pSplitter->SetBounds( x, y, w, h );
  124. }
  125. }
  126. //-----------------------------------------------------------------------------
  127. // Adds files to the frame
  128. //-----------------------------------------------------------------------------
  129. void COperationFileListFrame::ClearAllOperations()
  130. {
  131. m_pFileBrowser->RemoveAll();
  132. }
  133. //-----------------------------------------------------------------------------
  134. // Adds the strings to the list panel
  135. //-----------------------------------------------------------------------------
  136. void COperationFileListFrame::AddOperation( const char *pOperation, const char *pFileName )
  137. {
  138. KeyValues *kv = new KeyValues( "node", "filename", pFileName );
  139. kv->SetString( "operation", pOperation );
  140. m_pFileBrowser->AddItem( kv, 0, false, false );
  141. }
  142. void COperationFileListFrame::AddOperation( const char *pOperation, const char *pFileName, const Color& clr )
  143. {
  144. KeyValues *kv = new KeyValues( "node", "filename", pFileName );
  145. kv->SetString( "operation", pOperation );
  146. kv->SetColor( "cellcolor", clr );
  147. m_pFileBrowser->AddItem( kv, 0, false, false );
  148. }
  149. //-----------------------------------------------------------------------------
  150. // Resizes the operation column to fit the operation text
  151. //-----------------------------------------------------------------------------
  152. void COperationFileListFrame::ResizeOperationColumnToContents()
  153. {
  154. m_pFileBrowser->ResizeColumnToContents( 0 );
  155. }
  156. //-----------------------------------------------------------------------------
  157. // Sets the column header for the 'operation' column
  158. //-----------------------------------------------------------------------------
  159. void COperationFileListFrame::SetOperationColumnHeaderText( const char *pText )
  160. {
  161. m_pFileBrowser->SetColumnHeaderText( 0, pText );
  162. }
  163. //-----------------------------------------------------------------------------
  164. // Adds the strings to the list panel
  165. //-----------------------------------------------------------------------------
  166. void COperationFileListFrame::DoModal( KeyValues *pContextKeyValues, const char *pMessage )
  167. {
  168. m_MessageName = pMessage ? pMessage : "OperationConfirmed";
  169. CleanUpMessage();
  170. m_pContextKeyValues = pContextKeyValues;
  171. m_pFileBrowser->SortList();
  172. if ( m_pNoButton->IsVisible() )
  173. {
  174. m_pYesButton->SetEnabled( m_pFileBrowser->GetItemCount() != 0 );
  175. }
  176. BaseClass::DoModal();
  177. }
  178. //-----------------------------------------------------------------------------
  179. // Retrieves the number of files, the file names, and operations
  180. //-----------------------------------------------------------------------------
  181. int COperationFileListFrame::GetOperationCount()
  182. {
  183. return m_pFileBrowser->GetItemCount();
  184. }
  185. const char *COperationFileListFrame::GetFileName( int i )
  186. {
  187. int nItemId = m_pFileBrowser->GetItemIDFromRow( i );
  188. KeyValues *pKeyValues = m_pFileBrowser->GetItem( nItemId );
  189. return pKeyValues->GetString( "filename" );
  190. }
  191. const char *COperationFileListFrame::GetOperation( int i )
  192. {
  193. int nItemId = m_pFileBrowser->GetItemIDFromRow( i );
  194. KeyValues *pKeyValues = m_pFileBrowser->GetItem( nItemId );
  195. return pKeyValues->GetString( "operation" );
  196. }
  197. //-----------------------------------------------------------------------------
  198. // Retreives the description (only if it was shown)
  199. //-----------------------------------------------------------------------------
  200. const char *COperationFileListFrame::GetDescription()
  201. {
  202. return m_pText;
  203. }
  204. //-----------------------------------------------------------------------------
  205. // Returns the message name
  206. //-----------------------------------------------------------------------------
  207. const char *COperationFileListFrame::CompletionMessage()
  208. {
  209. return m_MessageName;
  210. }
  211. //-----------------------------------------------------------------------------
  212. // On command
  213. //-----------------------------------------------------------------------------
  214. void COperationFileListFrame::OnCommand( const char *pCommand )
  215. {
  216. if ( !Q_stricmp( pCommand, "Yes" ) )
  217. {
  218. if ( m_pDescription )
  219. {
  220. int nLen = m_pDescription->GetTextLength() + 1;
  221. m_pText = new char[ nLen ];
  222. m_pDescription->GetText( m_pText, nLen );
  223. }
  224. KeyValues *pActionKeys;
  225. if ( PerformOperation() )
  226. {
  227. pActionKeys = new KeyValues( CompletionMessage(), "operationPerformed", 1 );
  228. }
  229. else
  230. {
  231. pActionKeys = new KeyValues( CompletionMessage(), "operationPerformed", 0 );
  232. }
  233. if ( m_pContextKeyValues )
  234. {
  235. pActionKeys->AddSubKey( m_pContextKeyValues );
  236. m_pContextKeyValues = NULL;
  237. }
  238. CloseModal();
  239. PostActionSignal( pActionKeys );
  240. return;
  241. }
  242. if ( !Q_stricmp( pCommand, "No" ) )
  243. {
  244. KeyValues *pActionKeys = new KeyValues( CompletionMessage(), "operationPerformed", 0 );
  245. if ( m_pContextKeyValues )
  246. {
  247. pActionKeys->AddSubKey( m_pContextKeyValues );
  248. m_pContextKeyValues = NULL;
  249. }
  250. CloseModal();
  251. PostActionSignal( pActionKeys );
  252. return;
  253. }
  254. BaseClass::OnCommand( pCommand );
  255. }
  256. //-----------------------------------------------------------------------------
  257. //
  258. // Version that does the work of perforce actions
  259. //
  260. //-----------------------------------------------------------------------------
  261. CPerforceFileListFrame::CPerforceFileListFrame( vgui::Panel *pParent, const char *pTitle, const char *pColumnHeader, PerforceAction_t action ) :
  262. BaseClass( pParent, pTitle, pColumnHeader, (action == PERFORCE_ACTION_FILE_SUBMIT), false, OPERATION_DIALOG_ID_PERFORCE )
  263. {
  264. m_Action = action;
  265. }
  266. CPerforceFileListFrame::~CPerforceFileListFrame()
  267. {
  268. }
  269. //-----------------------------------------------------------------------------
  270. // Activates the modal dialog
  271. //-----------------------------------------------------------------------------
  272. void CPerforceFileListFrame::DoModal( KeyValues *pContextKeys, const char *pMessage )
  273. {
  274. BaseClass::DoModal( pContextKeys, pMessage ? pMessage : "PerforceActionConfirmed" );
  275. }
  276. //-----------------------------------------------------------------------------
  277. // Adds a file for open
  278. //-----------------------------------------------------------------------------
  279. void CPerforceFileListFrame::AddFileForOpen( const char *pFullPath )
  280. {
  281. if ( !p4 )
  282. return;
  283. bool bIsInPerforce = p4->IsFileInPerforce( pFullPath );
  284. bool bIsOpened = ( p4->GetFileState( pFullPath ) != P4FILE_UNOPENED );
  285. switch( m_Action )
  286. {
  287. case PERFORCE_ACTION_FILE_ADD:
  288. if ( !bIsInPerforce && !bIsOpened )
  289. {
  290. AddOperation( "Add", pFullPath );
  291. }
  292. break;
  293. case PERFORCE_ACTION_FILE_EDIT:
  294. if ( bIsInPerforce && !bIsOpened )
  295. {
  296. AddOperation( "Edit", pFullPath );
  297. }
  298. break;
  299. case PERFORCE_ACTION_FILE_DELETE:
  300. if ( bIsInPerforce && !bIsOpened )
  301. {
  302. AddOperation( "Delete", pFullPath );
  303. }
  304. break;
  305. }
  306. }
  307. //-----------------------------------------------------------------------------
  308. // Add files to dialog for submit/revert dialogs
  309. //-----------------------------------------------------------------------------
  310. void CPerforceFileListFrame::AddFileForSubmit( const char *pFullPath, P4FileState_t state )
  311. {
  312. if ( state == P4FILE_UNOPENED )
  313. return;
  314. char pBuf[128];
  315. const char *pPrefix = (m_Action == PERFORCE_ACTION_FILE_REVERT) ? "Revert" : "Submit";
  316. switch( state )
  317. {
  318. case P4FILE_OPENED_FOR_ADD:
  319. Q_snprintf( pBuf, sizeof(pBuf), "%s Add", pPrefix );
  320. AddOperation( pBuf, pFullPath );
  321. break;
  322. case P4FILE_OPENED_FOR_EDIT:
  323. Q_snprintf( pBuf, sizeof(pBuf), "%s Edit", pPrefix );
  324. AddOperation( pBuf, pFullPath );
  325. break;
  326. case P4FILE_OPENED_FOR_DELETE:
  327. Q_snprintf( pBuf, sizeof(pBuf), "%s Delete", pPrefix );
  328. AddOperation( pBuf, pFullPath );
  329. break;
  330. case P4FILE_OPENED_FOR_INTEGRATE:
  331. Q_snprintf( pBuf, sizeof(pBuf), "%s Integrate", pPrefix );
  332. AddOperation( pBuf, pFullPath );
  333. break;
  334. }
  335. }
  336. //-----------------------------------------------------------------------------
  337. // Version of AddFile that accepts full paths
  338. //-----------------------------------------------------------------------------
  339. void CPerforceFileListFrame::AddFile( const char *pFullPath )
  340. {
  341. if ( !p4 )
  342. return;
  343. if ( m_Action < PERFORCE_ACTION_FILE_REVERT )
  344. {
  345. // If the file wasn't found on the disk, then abort
  346. if ( g_pFullFileSystem->FileExists( pFullPath, NULL ) )
  347. {
  348. AddFileForOpen( pFullPath );
  349. }
  350. return;
  351. }
  352. // Deal with submit, revert
  353. bool bFileExists = g_pFullFileSystem->FileExists( pFullPath, NULL );
  354. P4FileState_t state = p4->GetFileState( pFullPath );
  355. if ( bFileExists || (state == P4FILE_OPENED_FOR_DELETE) )
  356. {
  357. AddFileForSubmit( pFullPath, state );
  358. }
  359. }
  360. //-----------------------------------------------------------------------------
  361. // Version of AddFile that accepts relative paths + search path ids
  362. //-----------------------------------------------------------------------------
  363. void CPerforceFileListFrame::AddFile( const char *pRelativePath, const char *pPathId )
  364. {
  365. if ( !p4 )
  366. return;
  367. // Deal with add, open, edit
  368. if ( m_Action < PERFORCE_ACTION_FILE_REVERT )
  369. {
  370. // If the file wasn't found on the disk, then abort
  371. if ( g_pFullFileSystem->FileExists( pRelativePath, pPathId ) )
  372. {
  373. char pFullPath[MAX_PATH];
  374. g_pFullFileSystem->RelativePathToFullPath( pRelativePath, pPathId, pFullPath, sizeof( pFullPath ) );
  375. AddFileForOpen( pFullPath );
  376. }
  377. return;
  378. }
  379. // Deal with submit, revert
  380. // First, handle the case where the file exists on the drive
  381. char pFullPath[MAX_PATH];
  382. if ( g_pFullFileSystem->FileExists( pRelativePath, pPathId ) )
  383. {
  384. g_pFullFileSystem->RelativePathToFullPath( pRelativePath, pPathId, pFullPath, sizeof( pFullPath ) );
  385. P4FileState_t state = p4->GetFileState( pFullPath );
  386. AddFileForSubmit( pFullPath, state );
  387. return;
  388. }
  389. // Get the list of opened files, cache it off so we aren't continually reasking
  390. if ( Q_stricmp( pPathId, m_LastOpenedFilePathId ) )
  391. {
  392. p4->GetOpenedFileListInPath( pPathId, m_OpenedFiles );
  393. m_LastOpenedFilePathId = pPathId;
  394. }
  395. // If the file doesn't exist, it was opened for delete.
  396. // Using the client spec of the path, we need to piece together
  397. // the full path; the full path unfortunately is usually ambiguous:
  398. // you can never exactly know which mod it came from.
  399. char pTemp[MAX_PATH];
  400. char pSearchString[MAX_PATH];
  401. Q_strncpy( pSearchString, pRelativePath, sizeof(pSearchString) );
  402. Q_FixSlashes( pSearchString );
  403. int k;
  404. int nOpenedFileCount = m_OpenedFiles.Count();
  405. for ( k = 0; k < nOpenedFileCount; ++k )
  406. {
  407. if ( m_OpenedFiles[k].m_eOpenState != P4FILE_OPENED_FOR_DELETE )
  408. continue;
  409. // Check to see if the end of the local file matches the file
  410. const char *pLocalFile = p4->String( m_OpenedFiles[k].m_sLocalFile );
  411. // This ensures the full path lies under the search path
  412. if ( !g_pFullFileSystem->FullPathToRelativePathEx( pLocalFile, pPathId, pTemp, sizeof(pTemp) ) )
  413. continue;
  414. // The relative paths had better be the same
  415. if ( Q_stricmp( pTemp, pSearchString ) )
  416. continue;
  417. AddFileForSubmit( pLocalFile, m_OpenedFiles[k].m_eOpenState );
  418. break;
  419. }
  420. }
  421. //-----------------------------------------------------------------------------
  422. // Does the perforce operation
  423. //-----------------------------------------------------------------------------
  424. bool CPerforceFileListFrame::PerformOperation( )
  425. {
  426. if ( !p4 )
  427. return false;
  428. int nFileCount = GetOperationCount();
  429. const char **ppFileNames = (const char**)_alloca( nFileCount * sizeof(char*) );
  430. for ( int i = 0; i < nFileCount; ++i )
  431. {
  432. ppFileNames[i] = GetFileName( i );
  433. }
  434. bool bSuccess = false;
  435. switch ( m_Action )
  436. {
  437. case PERFORCE_ACTION_FILE_ADD:
  438. bSuccess = p4->OpenFilesForAdd( nFileCount, ppFileNames );
  439. break;
  440. case PERFORCE_ACTION_FILE_EDIT:
  441. bSuccess = p4->OpenFilesForEdit( nFileCount, ppFileNames );
  442. break;
  443. case PERFORCE_ACTION_FILE_DELETE:
  444. bSuccess = p4->OpenFilesForDelete( nFileCount, ppFileNames );
  445. break;
  446. case PERFORCE_ACTION_FILE_REVERT:
  447. bSuccess = p4->RevertFiles( nFileCount, ppFileNames );
  448. break;
  449. case PERFORCE_ACTION_FILE_SUBMIT:
  450. {
  451. // Ensure a description was added
  452. const char *pDescription = GetDescription();
  453. if ( !pDescription[0] || !Q_stricmp( pDescription, "<enter description here>" ) )
  454. {
  455. vgui::MessageBox *pError = new vgui::MessageBox( "Submission Error!", "Description required for submission.", GetParent() );
  456. pError->SetSmallCaption( true );
  457. pError->DoModal();
  458. return false;
  459. }
  460. else
  461. {
  462. bSuccess = p4->SubmitFiles( nFileCount, ppFileNames, pDescription );
  463. }
  464. }
  465. break;
  466. }
  467. const char *pErrorString = p4->GetLastError();
  468. if ( !bSuccess )
  469. {
  470. vgui::MessageBox *pError = new vgui::MessageBox( "Perforce Error!", pErrorString, GetParent() );
  471. pError->SetSmallCaption( true );
  472. pError->DoModal();
  473. }
  474. #if 0
  475. if ( *pErrorString )
  476. {
  477. if ( V_strstr( pErrorString, "opened for add" ) )
  478. return bSuccess;
  479. if ( V_strstr( pErrorString, "opened for edit" ) )
  480. return bSuccess;
  481. // TODO - figure out the rest of these...
  482. const char *pPrefix = "Perforce has generated the following message which may or may not be an error.\n"
  483. "Please email joe with the text of the message, whether you think it was an error, and what perforce operation you where performing.\n"
  484. "To copy the message, hit ~ to enter the console, where you will find the message reprinted.\n"
  485. "Select the lines of text in the message, right click, select Copy, and then paste into an email message.\n\n";
  486. static int nPrefixLen = V_strlen( pPrefix );
  487. int nErrorStringLength = V_strlen( pErrorString );
  488. char *pMsg = (char*)_alloca( nPrefixLen + nErrorStringLength + 1 );
  489. V_strcpy( pMsg, pPrefix );
  490. V_strcpy( pMsg + nPrefixLen, pErrorString );
  491. vgui::MessageBox *pError = new vgui::MessageBox( "Dubious Perforce Message", pMsg, GetParent() );
  492. pError->SetSmallCaption( true );
  493. pError->DoModal();
  494. }
  495. #endif
  496. return bSuccess;
  497. }
  498. //-----------------------------------------------------------------------------
  499. // Show the perforce query dialog
  500. //-----------------------------------------------------------------------------
  501. void ShowPerforceQuery( vgui::Panel *pParent, const char *pFileName, vgui::Panel *pActionSignalTarget, KeyValues *pKeyValues, PerforceAction_t actionFilter )
  502. {
  503. if ( !p4 )
  504. {
  505. KeyValues *pSpoofKeys = new KeyValues( "PerforceQueryCompleted", "operationPerformed", 1 );
  506. if ( pKeyValues )
  507. {
  508. pSpoofKeys->AddSubKey( pKeyValues );
  509. }
  510. vgui::ivgui()->PostMessage( pActionSignalTarget->GetVPanel(), pSpoofKeys, 0 );
  511. return;
  512. }
  513. // Refresh the current perforce settings
  514. p4->RefreshActiveClient();
  515. PerforceAction_t action = PERFORCE_ACTION_NONE;
  516. const char *pTitle = NULL;
  517. if ( !p4->IsFileInPerforce( pFileName ) )
  518. {
  519. // If the file isn't in perforce, ask to add it
  520. action = PERFORCE_ACTION_FILE_ADD;
  521. pTitle = "Add File to Perforce?";
  522. }
  523. else if ( p4->GetFileState( pFileName ) == P4FILE_UNOPENED )
  524. {
  525. // If the file isn't checked out yet, ask to check it out
  526. action = PERFORCE_ACTION_FILE_EDIT;
  527. pTitle = "Check Out File from Perforce?";
  528. }
  529. if ( ( action == PERFORCE_ACTION_NONE ) || ( ( actionFilter != PERFORCE_ACTION_NONE ) && ( actionFilter != action ) ) )
  530. {
  531. // Spoof a completion event
  532. KeyValues *pSpoofKeys = new KeyValues( "PerforceQueryCompleted", "operationPerformed", 1 );
  533. if ( pKeyValues )
  534. {
  535. pSpoofKeys->AddSubKey( pKeyValues );
  536. }
  537. vgui::ivgui()->PostMessage( pActionSignalTarget->GetVPanel(), pSpoofKeys, 0 );
  538. return;
  539. }
  540. CPerforceFileListFrame *pQuery = new CPerforceFileListFrame( pParent, pTitle, "File", action );
  541. pQuery->AddFile( pFileName );
  542. if ( pActionSignalTarget )
  543. {
  544. pQuery->AddActionSignalTarget( pActionSignalTarget );
  545. }
  546. pQuery->DoModal( pKeyValues, "PerforceQueryCompleted" );
  547. }