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.

593 lines
18 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose:
  4. //
  5. //=============================================================================//
  6. #define PROTECTED_THINGS_DISABLE
  7. #include <vgui_controls/Button.h>
  8. #include <vgui_controls/ComboBox.h>
  9. #include <vgui_controls/DirectorySelectDialog.h>
  10. #include <vgui_controls/TreeView.h>
  11. #include <vgui_controls/ImageList.h>
  12. #include <vgui_controls/MessageBox.h>
  13. #include <vgui/Cursor.h>
  14. #include <KeyValues.h>
  15. #include <vgui/IInput.h>
  16. #include <vgui/ISurface.h>
  17. #include <vgui/ISystem.h>
  18. #include <filesystem.h>
  19. #ifdef WIN32
  20. #include <direct.h>
  21. #include <stdio.h>
  22. #include <io.h>
  23. #endif
  24. #include <sys/types.h>
  25. #include <sys/stat.h>
  26. // memdbgon must be the last include file in a .cpp file!!!
  27. #include <tier0/memdbgon.h>
  28. using namespace vgui;
  29. DirectoryTreeView::DirectoryTreeView(DirectorySelectDialog *parent, const char *name) : TreeView(parent, name)
  30. {
  31. m_pParent = parent;
  32. }
  33. void DirectoryTreeView::GenerateChildrenOfNode(int itemIndex)
  34. {
  35. m_pParent->GenerateChildrenOfDirectoryNode(itemIndex);
  36. }
  37. //-----------------------------------------------------------------------------
  38. // Purpose: Used to prompt the user to create a directory
  39. //-----------------------------------------------------------------------------
  40. class CreateDirectoryDialog : public Frame
  41. {
  42. DECLARE_CLASS_SIMPLE(CreateDirectoryDialog, Frame);
  43. public:
  44. CreateDirectoryDialog(Panel *parent, const char *defaultCreateDirName) : BaseClass(parent, NULL)
  45. {
  46. SetSize(320, 100);
  47. SetSizeable(false);
  48. SetTitle("Choose directory name", false);
  49. MoveToCenterOfScreen();
  50. m_pOKButton = new Button(this, "OKButton", "#vgui_ok");
  51. m_pCancelButton = new Button(this, "OKButton", "#vgui_cancel");
  52. m_pNameEntry = new TextEntry(this, "NameEntry");
  53. m_pOKButton->SetCommand("OK");
  54. m_pCancelButton->SetCommand("Close");
  55. m_pNameEntry->SetText(defaultCreateDirName);
  56. m_pNameEntry->RequestFocus();
  57. m_pNameEntry->SelectAllText(true);
  58. // If some other window was hogging the input focus, then we have to hog it or else we'll never get input.
  59. m_PrevAppFocusPanel = vgui::input()->GetAppModalSurface();
  60. if ( m_PrevAppFocusPanel )
  61. vgui::input()->SetAppModalSurface( GetVPanel() );
  62. }
  63. ~CreateDirectoryDialog()
  64. {
  65. if ( m_PrevAppFocusPanel )
  66. vgui::input()->SetAppModalSurface( m_PrevAppFocusPanel );
  67. }
  68. virtual void PerformLayout()
  69. {
  70. BaseClass::PerformLayout();
  71. m_pNameEntry->SetBounds(24, 32, GetWide() - 48, 24);
  72. m_pOKButton->SetBounds(GetWide() - 176, 64, 72, 24);
  73. m_pCancelButton->SetBounds(GetWide() - 94, 64, 72, 24);
  74. }
  75. virtual void OnCommand(const char *command)
  76. {
  77. if (!stricmp(command, "OK"))
  78. {
  79. PostActionSignal(new KeyValues("CreateDirectory", "dir", GetControlString("NameEntry")));
  80. Close();
  81. }
  82. else
  83. {
  84. BaseClass::OnCommand(command);
  85. }
  86. }
  87. virtual void OnClose()
  88. {
  89. BaseClass::OnClose();
  90. MarkForDeletion();
  91. }
  92. private:
  93. vgui::Button *m_pOKButton;
  94. vgui::Button *m_pCancelButton;
  95. vgui::TextEntry *m_pNameEntry;
  96. vgui::VPANEL m_PrevAppFocusPanel;
  97. };
  98. //-----------------------------------------------------------------------------
  99. // Purpose: Constructor
  100. //-----------------------------------------------------------------------------
  101. DirectorySelectDialog::DirectorySelectDialog(vgui::Panel *parent, const char *title) : Frame(parent, NULL)
  102. {
  103. SetTitle(title, true);
  104. SetSize(320, 360);
  105. SetMinimumSize(300, 240);
  106. m_szCurrentDir[0] = 0;
  107. m_szDefaultCreateDirName[0] = 0;
  108. m_pDirTree = new DirectoryTreeView(this, "DirTree");
  109. m_pDriveCombo = new ComboBox(this, "DriveCombo", 6, false);
  110. m_pCancelButton = new Button(this, "CancelButton", "#VGui_Cancel");
  111. m_pSelectButton = new Button(this, "SelectButton", "#VGui_Select");
  112. m_pCreateButton = new Button(this, "CreateButton", "#VGui_CreateFolder");
  113. m_pCancelButton->SetCommand("Cancel");
  114. m_pSelectButton->SetCommand("Select");
  115. m_pCreateButton->SetCommand("Create");
  116. }
  117. //-----------------------------------------------------------------------------
  118. // Purpose: lays out controls
  119. //-----------------------------------------------------------------------------
  120. void DirectorySelectDialog::PerformLayout()
  121. {
  122. BaseClass::PerformLayout();
  123. // lay out all the controls
  124. m_pDriveCombo->SetBounds(24, 30, GetWide() - 48, 24);
  125. m_pDirTree->SetBounds(24, 64, GetWide() - 48, GetTall() - 128);
  126. m_pCreateButton->SetBounds(24, GetTall() - 48, 104, 24);
  127. m_pSelectButton->SetBounds(GetWide() - 172, GetTall() - 48, 72, 24);
  128. m_pCancelButton->SetBounds(GetWide() - 96, GetTall() - 48, 72, 24);
  129. }
  130. //-----------------------------------------------------------------------------
  131. // Purpose: lays out controls
  132. //-----------------------------------------------------------------------------
  133. void DirectorySelectDialog::ApplySchemeSettings(IScheme *pScheme)
  134. {
  135. ImageList *imageList = new ImageList(false);
  136. imageList->AddImage(scheme()->GetImage("Resource/icon_folder", false));
  137. imageList->AddImage(scheme()->GetImage("Resource/icon_folder_selected", false));
  138. m_pDirTree->SetImageList(imageList, true);
  139. BaseClass::ApplySchemeSettings(pScheme);
  140. }
  141. //-----------------------------------------------------------------------------
  142. // Purpose: Move the start string forward until we hit a slash and return the
  143. // the first character past the trailing slash
  144. //-----------------------------------------------------------------------------
  145. inline const char *MoveToNextSubDir( const char *pStart, int *nCount )
  146. {
  147. int nMoved = 0;
  148. // Move past pre-pended slash
  149. if ( pStart[nMoved] == '\\' )
  150. {
  151. nMoved++;
  152. }
  153. // Move past the current block of text until we've hit the next path seperator (or end)
  154. while ( pStart[nMoved] != '\\' && pStart[nMoved] != '\0' )
  155. {
  156. nMoved++;
  157. }
  158. // Move past trailing slash
  159. if ( pStart[nMoved] == '\\' )
  160. {
  161. nMoved++;
  162. }
  163. // Give back a count if they've supplied a pointer
  164. if ( nCount != NULL )
  165. {
  166. *nCount = nMoved;
  167. }
  168. // The beginning of the next string, past slash
  169. return (pStart+nMoved);
  170. }
  171. //-----------------------------------------------------------------------------
  172. // Purpose: Walk through our directory structure given a path as our guide, while expanding
  173. // and populating the nodes of the tree view to match
  174. // Input : *path - path (with drive letter) to show
  175. //-----------------------------------------------------------------------------
  176. void DirectorySelectDialog::ExpandTreeToPath( const char *lpszPath, bool bSelectFinalDirectory /*= true*/ )
  177. {
  178. // Make sure our slashes are correct!
  179. char workPath[MAX_PATH];
  180. Q_strncpy( workPath, lpszPath, sizeof(workPath) );
  181. Q_FixSlashes( workPath );
  182. // Set us to the work drive
  183. SetStartDirectory( workPath );
  184. // Check that the path is valid
  185. if ( workPath[0] == '\0' || DoesDirectoryHaveSubdirectories( m_szCurrentDrive, "" ) == false )
  186. {
  187. // Failing, start in C:
  188. SetStartDirectory( "C:\\" );
  189. }
  190. // Start at the root of our tree
  191. int nItemIndex = m_pDirTree->GetRootItemIndex();
  192. // Move past the drive letter to the first subdir
  193. int nPathPos = 0;
  194. const char *lpszSubDirName = MoveToNextSubDir( workPath, &nPathPos );
  195. const char *lpszLastSubDirName = NULL;
  196. int nPathIncr = 0;
  197. char subDirName[MAX_PATH];
  198. // While there are subdirectory names present, expand and populate the tree with their subdirectories
  199. while ( lpszSubDirName[0] != '\0' )
  200. {
  201. // Move our string pointer forward while keeping where our last subdir started off
  202. lpszLastSubDirName = lpszSubDirName;
  203. lpszSubDirName = MoveToNextSubDir( lpszSubDirName, &nPathIncr );
  204. // Get the span between the last subdir and the new one
  205. Q_StrLeft( lpszLastSubDirName, nPathIncr, subDirName, sizeof(subDirName) );
  206. Q_StripTrailingSlash( subDirName );
  207. // Increment where we are in the string for use later
  208. nPathPos += nPathIncr;
  209. // Run through the list and expand to our currently selected directory
  210. for ( int i = 0; i < m_pDirTree->GetNumChildren( nItemIndex ); i++ )
  211. {
  212. // Get the child and data for it
  213. int nChild = m_pDirTree->GetChild( nItemIndex, i );
  214. KeyValues *pValues = m_pDirTree->GetItemData( nChild );
  215. // See if this matches
  216. if ( Q_stricmp( pValues->GetString( "Text" ), subDirName ) == 0 )
  217. {
  218. // This is the new root item
  219. nItemIndex = nChild;
  220. // Get the full path (starting from the drive letter) up to our current subdir
  221. Q_strncpy( subDirName, workPath, nPathPos );
  222. Q_AppendSlash( subDirName, sizeof(subDirName) );
  223. // Expand the tree node and populate its subdirs for our next iteration
  224. ExpandTreeNode( subDirName, nItemIndex );
  225. break;
  226. }
  227. }
  228. }
  229. // Select our last directory if we've been asked to (and it's valid)
  230. if ( bSelectFinalDirectory && m_pDirTree->IsItemIDValid( nItemIndex ) )
  231. {
  232. // If we don't call this once before selecting an item, the tree will not be properly expanded
  233. // before it calculates how to show the selected item in the view
  234. PerformLayout();
  235. // Select that item
  236. m_pDirTree->AddSelectedItem( nItemIndex, true );
  237. }
  238. }
  239. //-----------------------------------------------------------------------------
  240. // Purpose: sets where it should start searching
  241. //-----------------------------------------------------------------------------
  242. void DirectorySelectDialog::SetStartDirectory(const char *path)
  243. {
  244. strncpy(m_szCurrentDir, path, sizeof(m_szCurrentDir));
  245. strncpy(m_szCurrentDrive, path, sizeof(m_szCurrentDrive));
  246. m_szCurrentDrive[sizeof(m_szCurrentDrive) - 1] = 0;
  247. char *firstSlash = strstr(m_szCurrentDrive, "\\");
  248. if (firstSlash)
  249. {
  250. firstSlash[1] = 0;
  251. }
  252. BuildDirTree();
  253. BuildDriveChoices();
  254. // update state of create directory button
  255. int selectedIndex = m_pDirTree->GetFirstSelectedItem();
  256. if (m_pDirTree->IsItemIDValid(selectedIndex))
  257. {
  258. m_pCreateButton->SetEnabled(true);
  259. }
  260. else
  261. {
  262. m_pCreateButton->SetEnabled(false);
  263. }
  264. }
  265. //-----------------------------------------------------------------------------
  266. // Purpose: sets what name should show up by default in the create directory dialog
  267. //-----------------------------------------------------------------------------
  268. void DirectorySelectDialog::SetDefaultCreateDirectoryName(const char *defaultCreateDirName)
  269. {
  270. strncpy(m_szDefaultCreateDirName, defaultCreateDirName, sizeof(m_szDefaultCreateDirName));
  271. m_szDefaultCreateDirName[sizeof(m_szDefaultCreateDirName) - 1] = 0;
  272. }
  273. //-----------------------------------------------------------------------------
  274. // Purpose: opens the dialog
  275. //-----------------------------------------------------------------------------
  276. void DirectorySelectDialog::DoModal()
  277. {
  278. input()->SetAppModalSurface(GetVPanel());
  279. BaseClass::Activate();
  280. MoveToCenterOfScreen();
  281. }
  282. //-----------------------------------------------------------------------------
  283. // Purpose: Builds drive choices
  284. //-----------------------------------------------------------------------------
  285. void DirectorySelectDialog::BuildDriveChoices()
  286. {
  287. m_pDriveCombo->DeleteAllItems();
  288. char drives[256] = { 0 };
  289. int len = system()->GetAvailableDrives(drives, sizeof(drives));
  290. char *pBuf = drives;
  291. KeyValues *kv = new KeyValues("drive");
  292. for (int i = 0; i < len / 4; i++)
  293. {
  294. kv->SetString("drive", pBuf);
  295. int itemID = m_pDriveCombo->AddItem(pBuf, kv);
  296. if (!stricmp(pBuf, m_szCurrentDrive))
  297. {
  298. m_pDriveCombo->ActivateItem(itemID);
  299. }
  300. pBuf += 4;
  301. }
  302. kv->deleteThis();
  303. }
  304. //-----------------------------------------------------------------------------
  305. // Purpose: Builds the base tree directory
  306. //-----------------------------------------------------------------------------
  307. void DirectorySelectDialog::BuildDirTree()
  308. {
  309. // clear current tree
  310. m_pDirTree->RemoveAll();
  311. // add in a root
  312. int rootIndex = m_pDirTree->AddItem(new KeyValues("root", "Text", m_szCurrentDrive), -1);
  313. // build first level of the tree
  314. ExpandTreeNode(m_szCurrentDrive, rootIndex);
  315. // start the root expanded
  316. m_pDirTree->ExpandItem(rootIndex, true);
  317. }
  318. //-----------------------------------------------------------------------------
  319. // Purpose: expands a path
  320. //-----------------------------------------------------------------------------
  321. void DirectorySelectDialog::ExpandTreeNode(const char *path, int parentNodeIndex)
  322. {
  323. // set the small wait cursor
  324. surface()->SetCursor(dc_waitarrow);
  325. // get all the subfolders of the current drive
  326. char searchString[512];
  327. sprintf(searchString, "%s*.*", path);
  328. FileFindHandle_t h;
  329. const char *pFileName = g_pFullFileSystem->FindFirstEx( searchString, NULL, &h );
  330. for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( h ) )
  331. {
  332. if ( !Q_stricmp( pFileName, ".." ) || !Q_stricmp( pFileName, "." ) )
  333. continue;
  334. KeyValues *kv = new KeyValues("item");
  335. kv->SetString("Text", pFileName);
  336. // set the folder image
  337. kv->SetInt("Image", 1);
  338. kv->SetInt("SelectedImage", 1);
  339. kv->SetInt("Expand", DoesDirectoryHaveSubdirectories(path, pFileName));
  340. m_pDirTree->AddItem(kv, parentNodeIndex);
  341. }
  342. g_pFullFileSystem->FindClose( h );
  343. }
  344. //-----------------------------------------------------------------------------
  345. // Purpose:
  346. //-----------------------------------------------------------------------------
  347. bool DirectorySelectDialog::DoesDirectoryHaveSubdirectories(const char *path, const char *dir)
  348. {
  349. char searchString[512];
  350. sprintf(searchString, "%s%s\\*.*", path, dir);
  351. FileFindHandle_t h;
  352. const char *pFileName = g_pFullFileSystem->FindFirstEx( searchString, NULL, &h );
  353. for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( h ) )
  354. {
  355. char szFullPath[ MAX_PATH ];
  356. Q_snprintf( szFullPath, sizeof(szFullPath), "%s\\%s", path, pFileName );
  357. Q_FixSlashes( szFullPath );
  358. if ( g_pFullFileSystem->IsDirectory( szFullPath ) )
  359. {
  360. g_pFullFileSystem->FindClose( h );
  361. return true;
  362. }
  363. }
  364. g_pFullFileSystem->FindClose( h );
  365. return false;
  366. }
  367. //-----------------------------------------------------------------------------
  368. // Purpose: Generates the children for the specified node
  369. //-----------------------------------------------------------------------------
  370. void DirectorySelectDialog::GenerateChildrenOfDirectoryNode(int nodeIndex)
  371. {
  372. // generate path
  373. char path[512];
  374. GenerateFullPathForNode(nodeIndex, path, sizeof(path));
  375. // expand out
  376. ExpandTreeNode(path, nodeIndex);
  377. }
  378. //-----------------------------------------------------------------------------
  379. // Purpose: creates the full path for a node
  380. //-----------------------------------------------------------------------------
  381. void DirectorySelectDialog::GenerateFullPathForNode(int nodeIndex, char *path, int pathBufferSize)
  382. {
  383. // get all the nodes
  384. CUtlLinkedList<int, int> nodes;
  385. nodes.AddToTail(nodeIndex);
  386. int parentIndex = nodeIndex;
  387. while (1)
  388. {
  389. parentIndex = m_pDirTree->GetItemParent(parentIndex);
  390. if (parentIndex == -1)
  391. break;
  392. nodes.AddToHead(parentIndex);
  393. }
  394. // walk the nodes, adding to the path
  395. path[0] = 0;
  396. bool bFirst = true;
  397. FOR_EACH_LL( nodes, i )
  398. {
  399. KeyValues *kv = m_pDirTree->GetItemData( nodes[i] );
  400. strcat(path, kv->GetString("Text"));
  401. if (!bFirst)
  402. {
  403. strcat(path, "\\");
  404. }
  405. bFirst = false;
  406. }
  407. }
  408. //-----------------------------------------------------------------------------
  409. // Purpose: Handles combo box changes
  410. //-----------------------------------------------------------------------------
  411. void DirectorySelectDialog::OnTextChanged()
  412. {
  413. KeyValues *kv = m_pDriveCombo->GetActiveItemUserData();
  414. if (!kv)
  415. return;
  416. const char *newDrive = kv->GetString("drive");
  417. if (stricmp(newDrive, m_szCurrentDrive))
  418. {
  419. // drive changed, reset
  420. SetStartDirectory(newDrive);
  421. }
  422. }
  423. //-----------------------------------------------------------------------------
  424. // Purpose: creates a directory
  425. //-----------------------------------------------------------------------------
  426. void DirectorySelectDialog::OnCreateDirectory(const char *dir)
  427. {
  428. int selectedIndex = m_pDirTree->GetFirstSelectedItem();
  429. if (m_pDirTree->IsItemIDValid(selectedIndex))
  430. {
  431. char fullPath[512];
  432. GenerateFullPathForNode(selectedIndex, fullPath, sizeof(fullPath));
  433. // create the new directory underneath
  434. strcat(fullPath, dir);
  435. if (_mkdir(fullPath) == 0)
  436. {
  437. // add new path to tree view
  438. KeyValues *kv = new KeyValues("item");
  439. kv->SetString("Text", dir);
  440. // set the folder image
  441. kv->SetInt("Image", 1);
  442. kv->SetInt("SelectedImage", 1);
  443. int itemID = m_pDirTree->AddItem(kv, selectedIndex);
  444. // select the item
  445. m_pDirTree->AddSelectedItem( itemID, true );
  446. }
  447. else
  448. {
  449. // print error message
  450. MessageBox *box = new MessageBox("#vgui_CreateDirectoryFail_Title", "#vgui_CreateDirectoryFail_Info");
  451. box->DoModal(this);
  452. }
  453. }
  454. }
  455. //-----------------------------------------------------------------------------
  456. // Purpose: dialog closes
  457. //-----------------------------------------------------------------------------
  458. void DirectorySelectDialog::OnClose()
  459. {
  460. BaseClass::OnClose();
  461. MarkForDeletion();
  462. }
  463. //-----------------------------------------------------------------------------
  464. // Purpose: handles button commands
  465. //-----------------------------------------------------------------------------
  466. void DirectorySelectDialog::OnCommand(const char *command)
  467. {
  468. if (!stricmp(command, "Cancel"))
  469. {
  470. Close();
  471. }
  472. else if (!stricmp(command, "Select"))
  473. {
  474. // path selected
  475. int selectedIndex = m_pDirTree->GetFirstSelectedItem();
  476. if (m_pDirTree->IsItemIDValid(selectedIndex))
  477. {
  478. char fullPath[512];
  479. GenerateFullPathForNode(selectedIndex, fullPath, sizeof(fullPath));
  480. PostActionSignal(new KeyValues("DirectorySelected", "dir", fullPath));
  481. Close();
  482. }
  483. }
  484. else if (!stricmp(command, "Create"))
  485. {
  486. int selectedIndex = m_pDirTree->GetFirstSelectedItem();
  487. if (m_pDirTree->IsItemIDValid(selectedIndex))
  488. {
  489. CreateDirectoryDialog *dlg = new CreateDirectoryDialog(this, m_szDefaultCreateDirName);
  490. dlg->AddActionSignalTarget(this);
  491. dlg->Activate();
  492. }
  493. }
  494. else
  495. {
  496. BaseClass::OnCommand(command);
  497. }
  498. }
  499. //-----------------------------------------------------------------------------
  500. // Purpose: Update the text in the combo
  501. //-----------------------------------------------------------------------------
  502. void DirectorySelectDialog::OnTreeViewItemSelected()
  503. {
  504. int selectedIndex = m_pDirTree->GetFirstSelectedItem();
  505. if (!m_pDirTree->IsItemIDValid(selectedIndex))
  506. {
  507. m_pCreateButton->SetEnabled(false);
  508. return;
  509. }
  510. m_pCreateButton->SetEnabled(true);
  511. // build the string
  512. char fullPath[512];
  513. GenerateFullPathForNode(selectedIndex, fullPath, sizeof(fullPath));
  514. int itemID = m_pDriveCombo->GetActiveItem();
  515. m_pDriveCombo->UpdateItem(itemID, fullPath, NULL);
  516. m_pDriveCombo->SetText(fullPath);
  517. }