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.

1701 lines
47 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. // Purpose: Implementation of vgui generic open file dialog
  4. //
  5. // $NoKeywords: $
  6. //===========================================================================//
  7. #define PROTECTED_THINGS_DISABLE
  8. #if !defined( _X360 ) && defined( WIN32 )
  9. #include "winlite.h"
  10. #include <shellapi.h>
  11. #elif defined( POSIX )
  12. #include <stdlib.h>
  13. #define _stat stat
  14. #define _wcsnicmp wcsncmp
  15. #elif defined( _X360 )
  16. #else
  17. #error
  18. #endif
  19. #undef GetCurrentDirectory
  20. #include "filesystem.h"
  21. #include <sys/stat.h>
  22. #include "tier1/utldict.h"
  23. #include "tier1/utlstring.h"
  24. #include <vgui/IScheme.h>
  25. #include <vgui/ISurface.h>
  26. #include <vgui/ISystem.h>
  27. #include <KeyValues.h>
  28. #include <vgui/IVGui.h>
  29. #include <vgui/ILocalize.h>
  30. #include <vgui/IInput.h>
  31. #include <vgui_controls/FileOpenDialog.h>
  32. #include <vgui_controls/Button.h>
  33. #include <vgui_controls/ComboBox.h>
  34. #include <vgui_controls/ImagePanel.h>
  35. #include <vgui_controls/InputDialog.h>
  36. #include <vgui_controls/Label.h>
  37. #include <vgui_controls/ListPanel.h>
  38. #include <vgui_controls/TextEntry.h>
  39. #include <vgui_controls/ImageList.h>
  40. #include <vgui_controls/MenuItem.h>
  41. #include <vgui_controls/Tooltip.h>
  42. #if defined( _X360 )
  43. #include "xbox/xbox_win32stubs.h"
  44. #undef GetCurrentDirectory
  45. #endif
  46. // memdbgon must be the last include file in a .cpp file!!!
  47. #include <tier0/memdbgon.h>
  48. using namespace vgui;
  49. static int s_nLastSortColumn = 0;
  50. static int ListFileNameSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 )
  51. {
  52. NOTE_UNUSED( pPanel );
  53. bool dir1 = item1.kv->GetInt("directory") == 1;
  54. bool dir2 = item2.kv->GetInt("directory") == 1;
  55. // if they're both not directories of files, return if dir1 is a directory (before files)
  56. if (dir1 != dir2)
  57. {
  58. return dir1 ? -1 : 1;
  59. }
  60. const char *string1 = item1.kv->GetString("text");
  61. const char *string2 = item2.kv->GetString("text");
  62. // YWB: Mimic windows behavior where filenames starting with numbers are sorted based on numeric part
  63. int num1 = Q_atoi( string1 );
  64. int num2 = Q_atoi( string2 );
  65. if ( num1 != 0 &&
  66. num2 != 0 )
  67. {
  68. if ( num1 < num2 )
  69. return -1;
  70. else if ( num1 > num2 )
  71. return 1;
  72. }
  73. // Push numbers before everything else
  74. if ( num1 != 0 )
  75. {
  76. return -1;
  77. }
  78. // Push numbers before everything else
  79. if ( num2 != 0 )
  80. {
  81. return 1;
  82. }
  83. return Q_stricmp( string1, string2 );
  84. }
  85. static int ListBaseStringSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *fieldName )
  86. {
  87. bool dir1 = item1.kv->GetInt("directory") == 1;
  88. bool dir2 = item2.kv->GetInt("directory") == 1;
  89. // if they're both not directories of files, return if dir1 is a directory (before files)
  90. if (dir1 != dir2)
  91. {
  92. return -1;
  93. }
  94. const char *string1 = item1.kv->GetString(fieldName);
  95. const char *string2 = item2.kv->GetString(fieldName);
  96. int cval = Q_stricmp(string1, string2);
  97. if ( cval == 0 )
  98. {
  99. // Use filename to break ties
  100. return ListFileNameSortFunc( pPanel, item1, item2 );
  101. }
  102. return cval;
  103. }
  104. static int ListBaseIntegerSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *fieldName )
  105. {
  106. bool dir1 = item1.kv->GetInt("directory") == 1;
  107. bool dir2 = item2.kv->GetInt("directory") == 1;
  108. // if they're both not directories of files, return if dir1 is a directory (before files)
  109. if (dir1 != dir2)
  110. {
  111. return -1;
  112. }
  113. int i1 = item1.kv->GetInt(fieldName);
  114. int i2 = item2.kv->GetInt(fieldName);
  115. if ( i1 == i2 )
  116. {
  117. // Use filename to break ties
  118. return ListFileNameSortFunc( pPanel, item1, item2 );
  119. }
  120. return ( i1 < i2 ) ? -1 : 1;
  121. }
  122. static int ListBaseInteger64SortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *lowfield, char const *highfield )
  123. {
  124. bool dir1 = item1.kv->GetInt("directory") == 1;
  125. bool dir2 = item2.kv->GetInt("directory") == 1;
  126. // if they're both not directories of files, return if dir1 is a directory (before files)
  127. if (dir1 != dir2)
  128. {
  129. return dir1 ? -1 : 1;
  130. }
  131. uint32 l1 = item1.kv->GetInt(lowfield);
  132. uint32 h1 = item1.kv->GetInt(highfield);
  133. uint32 l2 = item2.kv->GetInt(lowfield);
  134. uint32 h2 = item2.kv->GetInt(highfield);
  135. uint64 i1 = (uint64)( (uint64)l1 | ( (uint64)h1 << 32 ) );
  136. uint64 i2 = (uint64)( (uint64)l2 | ( (uint64)h2 << 32 ) );
  137. if ( i1 == i2 )
  138. {
  139. // Use filename to break ties
  140. return ListFileNameSortFunc( pPanel, item1, item2 );
  141. }
  142. return ( i1 < i2 ) ? -1 : 1;
  143. }
  144. static int ListFileSizeSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 )
  145. {
  146. return ListBaseIntegerSortFunc( pPanel, item1, item2, "filesizeint" );
  147. }
  148. static int ListFileModifiedSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 )
  149. {
  150. // NOTE: Backward order to get most recent files first
  151. return ListBaseInteger64SortFunc( pPanel, item2, item1, "modifiedint_low", "modifiedint_high" );
  152. }
  153. static int ListFileCreatedSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 )
  154. {
  155. // NOTE: Backward order to get most recent files first
  156. return ListBaseInteger64SortFunc( pPanel, item2, item1, "createdint_low", "createdint_high" );
  157. }
  158. static int ListFileAttributesSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 )
  159. {
  160. return ListBaseStringSortFunc( pPanel, item1, item2, "attributes" );
  161. }
  162. static int ListFileTypeSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 )
  163. {
  164. return ListBaseStringSortFunc( pPanel, item1, item2, "type" );
  165. }
  166. namespace vgui
  167. {
  168. class FileCompletionMenu : public Menu
  169. {
  170. public:
  171. FileCompletionMenu(Panel *parent, const char *panelName) : Menu(parent, panelName)
  172. {
  173. }
  174. // override it so it doesn't request focus
  175. virtual void SetVisible(bool state)
  176. {
  177. Panel::SetVisible(state);
  178. }
  179. };
  180. //-----------------------------------------------------------------------------
  181. // File completion edit text entry
  182. //-----------------------------------------------------------------------------
  183. class FileCompletionEdit : public TextEntry
  184. {
  185. DECLARE_CLASS_SIMPLE( FileCompletionEdit, TextEntry );
  186. public:
  187. FileCompletionEdit(Panel *parent);
  188. ~FileCompletionEdit();
  189. int AddItem(const char *itemText, KeyValues *userData);
  190. int AddItem(const wchar_t *itemText, KeyValues *userData);
  191. void DeleteAllItems();
  192. int GetItemCount();
  193. int GetItemIDFromRow(int row);
  194. int GetRowFromItemID(int itemID);
  195. virtual void PerformLayout();
  196. void OnSetText(const wchar_t *newtext);
  197. virtual void OnKillFocus();
  198. void HideMenu(void);
  199. void ShowMenu(void);
  200. virtual void OnKeyCodeTyped(KeyCode code);
  201. MESSAGE_FUNC_INT( OnMenuItemHighlight, "MenuItemHighlight", itemID );
  202. private:
  203. FileCompletionMenu *m_pDropDown;
  204. };
  205. FileCompletionEdit::FileCompletionEdit(Panel *parent) : TextEntry(parent, NULL)
  206. {
  207. m_pDropDown = new FileCompletionMenu(this, NULL);
  208. m_pDropDown->AddActionSignalTarget(this);
  209. }
  210. FileCompletionEdit::~FileCompletionEdit()
  211. {
  212. delete m_pDropDown;
  213. }
  214. int FileCompletionEdit::AddItem(const char *itemText, KeyValues *userData)
  215. {
  216. // when the menu item is selected it will send the custom message "SetText"
  217. return m_pDropDown->AddMenuItem(itemText, new KeyValues("SetText", "text", itemText), this, userData);
  218. }
  219. int FileCompletionEdit::AddItem(const wchar_t *itemText, KeyValues *userData)
  220. {
  221. // add the element to the menu
  222. // when the menu item is selected it will send the custom message "SetText"
  223. KeyValues *kv = new KeyValues("SetText");
  224. kv->SetWString("text", itemText);
  225. // get an ansi version for the menuitem name
  226. char ansi[128];
  227. g_pVGuiLocalize->ConvertUnicodeToANSI(itemText, ansi, sizeof(ansi));
  228. return m_pDropDown->AddMenuItem(ansi, kv, this, userData);
  229. }
  230. void FileCompletionEdit::DeleteAllItems()
  231. {
  232. m_pDropDown->DeleteAllItems();
  233. }
  234. int FileCompletionEdit::GetItemCount()
  235. {
  236. return m_pDropDown->GetItemCount();
  237. }
  238. int FileCompletionEdit::GetItemIDFromRow(int row)
  239. {
  240. // valid from [0, GetItemCount)
  241. return m_pDropDown->GetMenuID(row);
  242. }
  243. int FileCompletionEdit::GetRowFromItemID(int itemID)
  244. {
  245. int i;
  246. for (i=0;i<GetItemCount();i++)
  247. {
  248. if (m_pDropDown->GetMenuID(i) == itemID)
  249. return i;
  250. }
  251. return -1;
  252. }
  253. void FileCompletionEdit::PerformLayout()
  254. {
  255. BaseClass::PerformLayout();
  256. m_pDropDown->PositionRelativeToPanel( this, Menu::DOWN, 0 );
  257. // reset the width of the drop down menu to be the width of this edit box
  258. m_pDropDown->SetFixedWidth(GetWide());
  259. m_pDropDown->ForceCalculateWidth();
  260. }
  261. void FileCompletionEdit::OnSetText(const wchar_t *newtext)
  262. {
  263. // see if the combobox text has changed, and if so, post a message detailing the new text
  264. wchar_t wbuf[255];
  265. GetText( wbuf, 254 );
  266. if ( wcscmp(wbuf, newtext) )
  267. {
  268. // text has changed
  269. SetText(newtext);
  270. // fire off that things have changed
  271. PostActionSignal(new KeyValues("TextChanged", "text", newtext));
  272. Repaint();
  273. }
  274. }
  275. void FileCompletionEdit::OnKillFocus()
  276. {
  277. HideMenu();
  278. BaseClass::OnKillFocus();
  279. }
  280. void FileCompletionEdit::HideMenu(void)
  281. {
  282. // hide the menu
  283. m_pDropDown->SetVisible(false);
  284. }
  285. void FileCompletionEdit::ShowMenu(void)
  286. {
  287. // reset the dropdown's position
  288. m_pDropDown->InvalidateLayout();
  289. // make sure we're at the top of the draw order (and therefore our children as well)
  290. // this important to make sure the menu will be drawn in the foreground
  291. MoveToFront();
  292. // reset the drop down
  293. m_pDropDown->ClearCurrentlyHighlightedItem();
  294. // limit it to only 6
  295. if (m_pDropDown->GetItemCount() > 6)
  296. {
  297. m_pDropDown->SetNumberOfVisibleItems(6);
  298. }
  299. else
  300. {
  301. m_pDropDown->SetNumberOfVisibleItems(m_pDropDown->GetItemCount());
  302. }
  303. // show the menu
  304. m_pDropDown->SetVisible(true);
  305. Repaint();
  306. }
  307. void FileCompletionEdit::OnKeyCodeTyped(KeyCode code)
  308. {
  309. if ( code == KEY_DOWN )
  310. {
  311. if (m_pDropDown->GetItemCount() > 0)
  312. {
  313. int menuID = m_pDropDown->GetCurrentlyHighlightedItem();
  314. int row = -1;
  315. if ( menuID == -1 )
  316. {
  317. row = m_pDropDown->GetItemCount() - 1;
  318. }
  319. else
  320. {
  321. row = GetRowFromItemID(menuID);
  322. }
  323. row++;
  324. if (row == m_pDropDown->GetItemCount())
  325. {
  326. row = 0;
  327. }
  328. menuID = GetItemIDFromRow(row);
  329. m_pDropDown->SetCurrentlyHighlightedItem(menuID);
  330. return;
  331. }
  332. }
  333. else if ( code == KEY_UP )
  334. {
  335. if (m_pDropDown->GetItemCount() > 0)
  336. {
  337. int menuID = m_pDropDown->GetCurrentlyHighlightedItem();
  338. int row = -1;
  339. if ( menuID == -1 )
  340. {
  341. row = 0;
  342. }
  343. else
  344. {
  345. row = GetRowFromItemID(menuID);
  346. }
  347. row--;
  348. if ( row < 0 )
  349. {
  350. row = m_pDropDown->GetItemCount() - 1;
  351. }
  352. menuID = GetItemIDFromRow(row);
  353. m_pDropDown->SetCurrentlyHighlightedItem(menuID);
  354. return;
  355. }
  356. }
  357. else if ( code == KEY_ESCAPE )
  358. {
  359. if ( m_pDropDown->IsVisible() )
  360. {
  361. HideMenu();
  362. return;
  363. }
  364. }
  365. BaseClass::OnKeyCodeTyped(code);
  366. return;
  367. }
  368. void FileCompletionEdit::OnMenuItemHighlight( int itemID )
  369. {
  370. char wbuf[80];
  371. if ( m_pDropDown->IsValidMenuID(itemID) )
  372. {
  373. m_pDropDown->GetMenuItem(itemID)->GetText(wbuf, 80);
  374. }
  375. else
  376. {
  377. wbuf[0] = 0;
  378. }
  379. SetText(wbuf);
  380. RequestFocus();
  381. GotoTextEnd();
  382. }
  383. } // namespace vgui
  384. //-----------------------------------------------------------------------------
  385. // Dictionary of start dir contexts
  386. //-----------------------------------------------------------------------------
  387. static CUtlDict< CUtlString, unsigned short > s_StartDirContexts;
  388. struct ColumnInfo_t
  389. {
  390. char const *columnName;
  391. char const *columnText;
  392. int startingWidth;
  393. int minWidth;
  394. int maxWidth;
  395. int flags;
  396. SortFunc *pfnSort;
  397. Label::Alignment alignment;
  398. };
  399. static ColumnInfo_t g_ColInfo[] =
  400. {
  401. { "text", "#FileOpenDialog_Col_Name", 175, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileNameSortFunc , Label::a_west },
  402. { "filesize", "#FileOpenDialog_Col_Size", 100, 20, 10000, 0, &ListFileSizeSortFunc , Label::a_east },
  403. { "type", "#FileOpenDialog_Col_Type", 150, 20, 10000, 0, &ListFileTypeSortFunc , Label::a_west },
  404. { "modified", "#FileOpenDialog_Col_DateModified", 125, 20, 10000, 0, &ListFileModifiedSortFunc , Label::a_west },
  405. // { "created", "#FileOpenDialog_Col_DateCreated", 125, 20, 10000, ListPanel::COLUMN_HIDDEN, &ListFileCreatedSortFunc , Label::a_west },
  406. { "attributes", "#FileOpenDialog_Col_Attributes", 50, 20, 10000, ListPanel::COLUMN_HIDDEN, &ListFileAttributesSortFunc , Label::a_west },
  407. };
  408. //-----------------------------------------------------------------------------
  409. // Purpose: Constructor
  410. //-----------------------------------------------------------------------------
  411. FileOpenDialog::FileOpenDialog(Panel *parent, const char *title, bool bOpenOnly, KeyValues* pContextKeyValues ) :
  412. Frame( parent, "FileOpenDialog" )
  413. {
  414. m_DialogType = bOpenOnly ? FOD_OPEN : FOD_SAVE;
  415. Init( title, pContextKeyValues );
  416. }
  417. FileOpenDialog::FileOpenDialog( Panel *parent, const char *title, FileOpenDialogType_t type, KeyValues *pContextKeyValues ) :
  418. Frame( parent, "FileOpenDialog" )
  419. {
  420. m_DialogType = type;
  421. Init( title, pContextKeyValues );
  422. }
  423. void FileOpenDialog::Init( const char *title, KeyValues *pContextKeyValues )
  424. {
  425. m_bFileSelected = false;
  426. SetTitle(title, true);
  427. SetMinimizeButtonVisible(false);
  428. #ifdef POSIX
  429. Q_strncpy(m_szLastPath, "/", sizeof( m_szLastPath ) );
  430. #else
  431. Q_strncpy(m_szLastPath, "c:\\", sizeof( m_szLastPath ) );
  432. #endif
  433. m_pContextKeyValues = pContextKeyValues;
  434. // Get the list of available drives and put them in a menu here.
  435. // Start with the directory we are in.
  436. m_pFullPathEdit = new ComboBox(this, "FullPathEdit", 6, false);
  437. m_pFullPathEdit->GetTooltip()->SetTooltipFormatToSingleLine();
  438. // list panel
  439. m_pFileList = new ListPanel(this, "FileList");
  440. for ( int i = 0; i < ARRAYSIZE( g_ColInfo ); ++i )
  441. {
  442. const ColumnInfo_t& info = g_ColInfo[ i ];
  443. m_pFileList->AddColumnHeader( i, info.columnName, info.columnText, info.startingWidth, info.minWidth, info.maxWidth, info.flags );
  444. m_pFileList->SetSortFunc( i, info.pfnSort );
  445. m_pFileList->SetColumnTextAlignment( i, info.alignment );
  446. }
  447. m_pFileList->SetSortColumn( s_nLastSortColumn );
  448. m_pFileList->SetMultiselectEnabled( false );
  449. // file name edit box
  450. m_pFileNameEdit = new FileCompletionEdit(this);
  451. m_pFileNameEdit->AddActionSignalTarget(this);
  452. m_pFileTypeCombo = new ComboBox( this, "FileTypeCombo", 6, false );
  453. switch ( m_DialogType )
  454. {
  455. case FOD_OPEN:
  456. m_pOpenButton = new Button( this, "OpenButton", "#FileOpenDialog_Open", this );
  457. break;
  458. case FOD_SAVE:
  459. m_pOpenButton = new Button( this, "OpenButton", "#FileOpenDialog_Save", this );
  460. break;
  461. case FOD_SELECT_DIRECTORY:
  462. m_pOpenButton = new Button( this, "OpenButton", "#FileOpenDialog_Select", this );
  463. m_pFileTypeCombo->SetVisible( false );
  464. break;
  465. }
  466. m_pCancelButton = new Button( this, "CancelButton", "#FileOpenDialog_Cancel", this );
  467. m_pFolderUpButton = new Button( this, "FolderUpButton", "", this );
  468. m_pFolderUpButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_Up" );
  469. m_pNewFolderButton = new Button( this, "NewFolderButton", "", this );
  470. m_pNewFolderButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_NewFolder" );
  471. m_pOpenInExplorerButton = new Button( this, "OpenInExplorerButton", "", this );
  472. #if defined ( OSX )
  473. m_pOpenInExplorerButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_OpenInFinderButton" );
  474. #elif defined ( POSIX )
  475. m_pOpenInExplorerButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_OpenInDesktopManagerButton" );
  476. #else // Assume Windows / Explorer
  477. m_pOpenInExplorerButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_OpenInExplorerButton" );
  478. #endif
  479. Label *lookIn = new Label( this, "LookInLabel", "#FileOpenDialog_Look_in" );
  480. Label *fileName = new Label( this, "FileNameLabel",
  481. ( m_DialogType != FOD_SELECT_DIRECTORY ) ? "#FileOpenDialog_File_name" : "#FileOpenDialog_Directory_Name" );
  482. m_pFolderIcon = new ImagePanel(NULL, "FolderIcon");
  483. // set up the control's initial positions
  484. SetSize( 600, 260 );
  485. int nFileEditLeftSide = ( m_DialogType != FOD_SELECT_DIRECTORY ) ? 84 : 100;
  486. int nFileNameWidth = ( m_DialogType != FOD_SELECT_DIRECTORY ) ? 72 : 82;
  487. m_pFullPathEdit->SetBounds(67, 32, 310, 24);
  488. m_pFolderUpButton->SetBounds(362, 32, 24, 24);
  489. m_pNewFolderButton->SetBounds(392, 32, 24, 24);
  490. m_pOpenInExplorerButton->SetBounds(332, 32, 24, 24);
  491. m_pFileList->SetBounds(10, 60, 406, 130);
  492. m_pFileNameEdit->SetBounds( nFileEditLeftSide, 194, 238, 24);
  493. m_pFileTypeCombo->SetBounds( nFileEditLeftSide, 224, 238, 24);
  494. m_pOpenButton->SetBounds(336, 194, 74, 24);
  495. m_pCancelButton->SetBounds(336, 224, 74, 24);
  496. lookIn->SetBounds(10, 32, 55, 24);
  497. fileName->SetBounds(10, 194, nFileNameWidth, 24);
  498. // set autolayout parameters
  499. m_pFullPathEdit->SetAutoResize( Panel::PIN_TOPLEFT, Panel::AUTORESIZE_RIGHT, 67, 32, -100, 0 );
  500. m_pFileNameEdit->SetAutoResize( Panel::PIN_BOTTOMLEFT, Panel::AUTORESIZE_RIGHT, nFileEditLeftSide, -42, -104, 0 );
  501. m_pFileTypeCombo->SetAutoResize( Panel::PIN_BOTTOMLEFT, Panel::AUTORESIZE_RIGHT, nFileEditLeftSide, -12, -104, 0 );
  502. m_pFileList->SetAutoResize( Panel::PIN_TOPLEFT, Panel::AUTORESIZE_DOWNANDRIGHT, 10, 60, -10, -70 );
  503. m_pFolderUpButton->SetPinCorner( Panel::PIN_TOPRIGHT, -40, 32 );
  504. m_pNewFolderButton->SetPinCorner( Panel::PIN_TOPRIGHT, -10, 32 );
  505. m_pOpenInExplorerButton->SetPinCorner( Panel::PIN_TOPRIGHT, -70, 32 );
  506. m_pOpenButton->SetPinCorner( Panel::PIN_BOTTOMRIGHT, -16, -42 );
  507. m_pCancelButton->SetPinCorner( Panel::PIN_BOTTOMRIGHT, -16, -12 );
  508. lookIn->SetPinCorner( Panel::PIN_TOPLEFT, 10, 32 );
  509. fileName->SetPinCorner( Panel::PIN_BOTTOMLEFT, 10, -42 );
  510. // label settings
  511. lookIn->SetContentAlignment(Label::a_west);
  512. fileName->SetContentAlignment(Label::a_west);
  513. lookIn->SetAssociatedControl(m_pFullPathEdit);
  514. fileName->SetAssociatedControl(m_pFileNameEdit);
  515. if ( m_DialogType != FOD_SELECT_DIRECTORY )
  516. {
  517. Label *fileType = new Label(this, "FileTypeLabel", "#FileOpenDialog_File_type");
  518. fileType->SetBounds(10, 224, 72, 24);
  519. fileType->SetPinCorner( Panel::PIN_BOTTOMLEFT, 10, -12 );
  520. fileType->SetContentAlignment(Label::a_west);
  521. fileType->SetAssociatedControl( m_pFileTypeCombo );
  522. }
  523. // set tab positions
  524. GetFocusNavGroup().SetDefaultButton(m_pOpenButton);
  525. m_pFileNameEdit->SetTabPosition(1);
  526. m_pFileTypeCombo->SetTabPosition(2);
  527. m_pOpenButton->SetTabPosition(3);
  528. m_pCancelButton->SetTabPosition(4);
  529. m_pFullPathEdit->SetTabPosition(5);
  530. m_pFileList->SetTabPosition(6);
  531. m_pOpenButton->SetCommand( ( m_DialogType != FOD_SELECT_DIRECTORY ) ? new KeyValues( "OnOpen" ) : new KeyValues( "SelectFolder" ) );
  532. m_pCancelButton->SetCommand( "CloseModal" );
  533. m_pFolderUpButton->SetCommand( new KeyValues( "OnFolderUp" ) );
  534. m_pNewFolderButton->SetCommand( new KeyValues( "OnNewFolder" ) );
  535. m_pOpenInExplorerButton->SetCommand( new KeyValues( "OpenInExplorer" ) );
  536. SetSize( 600, 384 );
  537. m_nStartDirContext = s_StartDirContexts.InvalidIndex();
  538. // Set our starting path to the current directory
  539. char pLocalPath[255];
  540. g_pFullFileSystem->GetCurrentDirectory( pLocalPath , 255 );
  541. if ( !pLocalPath[0] || ( IsOSX() && V_strlen(pLocalPath) <= 2 ) )
  542. {
  543. const char *pszHomeDir = getenv( "HOME" );
  544. V_strcpy_safe( pLocalPath, pszHomeDir );
  545. }
  546. SetStartDirectory( pLocalPath );
  547. // Because these call through virtual functions, we can't issue them in the constructor, so we post a message to ourselves instead!!
  548. PostMessage( GetVPanel(), new KeyValues( "PopulateFileList" ) );
  549. PostMessage( GetVPanel(), new KeyValues( "PopulateDriveList" ) );
  550. }
  551. //-----------------------------------------------------------------------------
  552. // Purpose: Destructor
  553. //-----------------------------------------------------------------------------
  554. FileOpenDialog::~FileOpenDialog()
  555. {
  556. s_nLastSortColumn = m_pFileList->GetSortColumn();
  557. if ( m_pContextKeyValues )
  558. {
  559. m_pContextKeyValues->deleteThis();
  560. m_pContextKeyValues = NULL;
  561. }
  562. }
  563. //-----------------------------------------------------------------------------
  564. // Purpose: Apply scheme settings
  565. //-----------------------------------------------------------------------------
  566. void FileOpenDialog::ApplySchemeSettings(IScheme *pScheme)
  567. {
  568. BaseClass::ApplySchemeSettings(pScheme);
  569. m_pFolderIcon->SetImage(scheme()->GetImage("resource/icon_folder", false));
  570. m_pFolderUpButton->AddImage(scheme()->GetImage("resource/icon_folderup", false), -3);
  571. m_pNewFolderButton->AddImage( scheme()->GetImage("resource/icon_newfolder", false), -3 );
  572. m_pOpenInExplorerButton->AddImage( scheme()->GetImage("resource/icon_play_once", false), -3 );
  573. ImageList *imageList = new ImageList(false);
  574. imageList->AddImage(scheme()->GetImage("resource/icon_file", false));
  575. imageList->AddImage(scheme()->GetImage("resource/icon_folder", false));
  576. imageList->AddImage(scheme()->GetImage("resource/icon_folder_selected", false));
  577. m_pFileList->SetImageList(imageList, true);
  578. }
  579. //-----------------------------------------------------------------------------
  580. // Prevent default button ('select') from getting triggered
  581. // when selecting directories. Instead, open the directory
  582. //-----------------------------------------------------------------------------
  583. void FileOpenDialog::OnKeyCodeTyped(KeyCode code)
  584. {
  585. if ( m_DialogType == FOD_SELECT_DIRECTORY && code == KEY_ENTER )
  586. {
  587. OnOpen();
  588. }
  589. else
  590. {
  591. BaseClass::OnKeyCodeTyped( code );
  592. }
  593. }
  594. //-----------------------------------------------------------------------------
  595. // Purpose:
  596. //-----------------------------------------------------------------------------
  597. void FileOpenDialog::PopulateDriveList()
  598. {
  599. char fullpath[MAX_PATH * 4];
  600. char subDirPath[MAX_PATH * 4];
  601. GetCurrentDirectory(fullpath, sizeof(fullpath) - MAX_PATH);
  602. Q_strncpy(subDirPath, fullpath, sizeof( subDirPath ) );
  603. m_pFullPathEdit->DeleteAllItems();
  604. #ifdef WIN32
  605. // populate the drive list
  606. char buf[512];
  607. int len = system()->GetAvailableDrives(buf, 512);
  608. char *pBuf = buf;
  609. for (int i=0; i < len / 4; i++)
  610. {
  611. m_pFullPathEdit->AddItem(pBuf, NULL);
  612. // is this our drive - add all subdirectories
  613. if (!_strnicmp(pBuf, fullpath, 2))
  614. {
  615. int indent = 0;
  616. char *pData = fullpath;
  617. while (*pData)
  618. {
  619. if ( *pData == CORRECT_PATH_SEPARATOR )
  620. {
  621. if (indent > 0)
  622. {
  623. memset(subDirPath, ' ', indent);
  624. memcpy(subDirPath+indent, fullpath, pData-fullpath);
  625. subDirPath[indent+pData-fullpath] = 0;
  626. m_pFullPathEdit->AddItem(subDirPath, NULL);
  627. }
  628. indent += 2;
  629. }
  630. pData++;
  631. }
  632. }
  633. pBuf += 4;
  634. }
  635. #else
  636. m_pFullPathEdit->AddItem("/", NULL);
  637. char *pData = fullpath;
  638. int indent = 0;
  639. while (*pData)
  640. {
  641. if (*pData == '/' && ( pData[1] != '\0' ) )
  642. {
  643. if (indent > 0)
  644. {
  645. memset(subDirPath, ' ', indent);
  646. memcpy(subDirPath+indent, fullpath, pData-fullpath);
  647. subDirPath[indent+pData-fullpath] = 0;
  648. m_pFullPathEdit->AddItem(subDirPath, NULL);
  649. }
  650. indent += 2;
  651. }
  652. pData++;
  653. }
  654. #endif
  655. }
  656. //-----------------------------------------------------------------------------
  657. // Purpose: Delete self on close
  658. //-----------------------------------------------------------------------------
  659. void FileOpenDialog::OnClose()
  660. {
  661. s_nLastSortColumn = m_pFileList->GetSortColumn();
  662. if ( !m_bFileSelected )
  663. {
  664. KeyValues *pKeyValues = new KeyValues( "FileSelectionCancelled" );
  665. PostActionSignal( pKeyValues );
  666. m_bFileSelected = true;
  667. }
  668. m_pFileNameEdit->SetText("");
  669. m_pFileNameEdit->HideMenu();
  670. if ( vgui::input()->GetAppModalSurface() == GetVPanel() )
  671. {
  672. input()->SetAppModalSurface(NULL);
  673. }
  674. BaseClass::OnClose();
  675. }
  676. void FileOpenDialog::OnFolderUp()
  677. {
  678. MoveUpFolder();
  679. OnOpen();
  680. }
  681. void FileOpenDialog::OnInputCompleted( KeyValues *data )
  682. {
  683. if ( m_hInputDialog.Get() )
  684. {
  685. delete m_hInputDialog.Get();
  686. }
  687. input()->SetAppModalSurface( m_SaveModal );
  688. m_SaveModal = 0;
  689. NewFolder( data->GetString( "text" ) );
  690. OnOpen();
  691. }
  692. void FileOpenDialog::OnInputCanceled()
  693. {
  694. input()->SetAppModalSurface( m_SaveModal );
  695. m_SaveModal = 0;
  696. }
  697. void FileOpenDialog::OnNewFolder()
  698. {
  699. if ( m_hInputDialog.Get() )
  700. delete m_hInputDialog.Get();
  701. m_hInputDialog = new InputDialog( this, "#FileOpenDialog_NewFolder_InputTitle", "#FileOpenDialog_NewFolderPrompt", "#FileOpenDialog_NewFolder_DefaultName" );
  702. if ( m_hInputDialog.Get() )
  703. {
  704. m_SaveModal = input()->GetAppModalSurface();
  705. KeyValues *pContextKeyValues = new KeyValues( "NewFolder" );
  706. m_hInputDialog->SetSmallCaption( true );
  707. m_hInputDialog->SetMultiline( false );
  708. m_hInputDialog->DoModal( pContextKeyValues );
  709. }
  710. }
  711. //-----------------------------------------------------------------------------
  712. // Opens the current file/folder in explorer
  713. //-----------------------------------------------------------------------------
  714. void FileOpenDialog::OnOpenInExplorer()
  715. {
  716. char pCurrentDirectory[MAX_PATH];
  717. GetCurrentDirectory( pCurrentDirectory, sizeof(pCurrentDirectory) );
  718. #if !defined( _X360 ) && defined( WIN32 )
  719. ShellExecute( NULL, NULL, pCurrentDirectory, NULL, NULL, SW_SHOWNORMAL );
  720. #elif defined( OSX )
  721. char szCmd[ MAX_PATH * 2];
  722. Q_snprintf( szCmd, sizeof(szCmd), "/usr/bin/open \"%s\"", pCurrentDirectory );
  723. ::system( szCmd );
  724. #elif defined( LINUX )
  725. char szCmd[ MAX_PATH * 2 ];
  726. Q_snprintf( szCmd, sizeof(szCmd), "xdg-open \"%s\" &", pCurrentDirectory );
  727. ::system( szCmd );
  728. #endif
  729. }
  730. //-----------------------------------------------------------------------------
  731. // Purpose: Handle for button commands
  732. //-----------------------------------------------------------------------------
  733. void FileOpenDialog::OnCommand(const char *command)
  734. {
  735. if (!stricmp(command, "Cancel"))
  736. {
  737. Close();
  738. }
  739. else
  740. {
  741. BaseClass::OnCommand(command);
  742. }
  743. }
  744. //-----------------------------------------------------------------------------
  745. // Sets the start directory context (and resets the start directory in the process)
  746. //-----------------------------------------------------------------------------
  747. void FileOpenDialog::SetStartDirectoryContext( const char *pStartDirContext, const char *pDefaultDir )
  748. {
  749. bool bUseCurrentDirectory = true;
  750. if ( pStartDirContext )
  751. {
  752. m_nStartDirContext = s_StartDirContexts.Find( pStartDirContext );
  753. if ( m_nStartDirContext == s_StartDirContexts.InvalidIndex() )
  754. {
  755. m_nStartDirContext = s_StartDirContexts.Insert( pStartDirContext, pDefaultDir );
  756. bUseCurrentDirectory = ( pDefaultDir == NULL );
  757. }
  758. else
  759. {
  760. bUseCurrentDirectory = false;
  761. }
  762. }
  763. else
  764. {
  765. m_nStartDirContext = s_StartDirContexts.InvalidIndex();
  766. }
  767. if ( !bUseCurrentDirectory )
  768. {
  769. SetStartDirectory( s_StartDirContexts[m_nStartDirContext].Get() );
  770. }
  771. else
  772. {
  773. // Set our starting path to the current directory
  774. char pLocalPath[255];
  775. g_pFullFileSystem->GetCurrentDirectory( pLocalPath, 255 );
  776. SetStartDirectory( pLocalPath );
  777. }
  778. }
  779. //-----------------------------------------------------------------------------
  780. // Purpose: Set the starting directory of the file search.
  781. //-----------------------------------------------------------------------------
  782. void FileOpenDialog::SetStartDirectory( const char *dir )
  783. {
  784. m_pFullPathEdit->SetText(dir);
  785. // ensure it's validity
  786. ValidatePath();
  787. // Store this in the start directory list
  788. if ( m_nStartDirContext != s_StartDirContexts.InvalidIndex() )
  789. {
  790. char pDirBuf[MAX_PATH];
  791. GetCurrentDirectory( pDirBuf, sizeof(pDirBuf) );
  792. s_StartDirContexts[ m_nStartDirContext ] = pDirBuf;
  793. }
  794. PopulateDriveList();
  795. }
  796. //-----------------------------------------------------------------------------
  797. // Purpose: Add filters for the drop down combo box
  798. //-----------------------------------------------------------------------------
  799. void FileOpenDialog::AddFilter( const char *filter, const char *filterName, bool bActive, const char *pFilterInfo )
  800. {
  801. KeyValues *kv = new KeyValues("item");
  802. kv->SetString( "filter", filter );
  803. kv->SetString( "filterinfo", pFilterInfo );
  804. int itemID = m_pFileTypeCombo->AddItem(filterName, kv);
  805. if ( bActive )
  806. {
  807. m_pFileTypeCombo->ActivateItem(itemID);
  808. }
  809. }
  810. //-----------------------------------------------------------------------------
  811. // Purpose: Activate the dialog
  812. //-----------------------------------------------------------------------------
  813. void FileOpenDialog::DoModal( bool bUnused )
  814. {
  815. m_bFileSelected = false;
  816. m_pFileNameEdit->RequestFocus();
  817. BaseClass::DoModal();
  818. }
  819. //-----------------------------------------------------------------------------
  820. // Purpose: Gets the directory this is currently in
  821. //-----------------------------------------------------------------------------
  822. void FileOpenDialog::GetCurrentDirectory(char *buf, int bufSize)
  823. {
  824. // get the text from the text entry
  825. m_pFullPathEdit->GetText(buf, bufSize);
  826. }
  827. //-----------------------------------------------------------------------------
  828. // Purpose: Get the last selected file name
  829. //-----------------------------------------------------------------------------
  830. void FileOpenDialog::GetSelectedFileName(char *buf, int bufSize)
  831. {
  832. m_pFileNameEdit->GetText(buf, bufSize);
  833. }
  834. //-----------------------------------------------------------------------------
  835. // Creates a new folder
  836. //-----------------------------------------------------------------------------
  837. void FileOpenDialog::NewFolder( char const *folderName )
  838. {
  839. char pCurrentDirectory[MAX_PATH];
  840. GetCurrentDirectory( pCurrentDirectory, sizeof(pCurrentDirectory) );
  841. char pFullPath[MAX_PATH];
  842. char pNewFolderName[MAX_PATH];
  843. Q_strncpy( pNewFolderName, folderName, sizeof(pNewFolderName) );
  844. int i = 2;
  845. do
  846. {
  847. Q_MakeAbsolutePath( pFullPath, sizeof(pFullPath), pNewFolderName, pCurrentDirectory );
  848. if ( !g_pFullFileSystem->FileExists( pFullPath, NULL ) &&
  849. !g_pFullFileSystem->IsDirectory( pFullPath, NULL ) )
  850. {
  851. g_pFullFileSystem->CreateDirHierarchy( pFullPath, NULL );
  852. m_pFileNameEdit->SetText( pNewFolderName );
  853. return;
  854. }
  855. Q_snprintf( pNewFolderName, sizeof(pNewFolderName), "%s%d", folderName, i );
  856. ++i;
  857. } while ( i <= 999 );
  858. }
  859. //-----------------------------------------------------------------------------
  860. // Purpose: Move the directory structure up
  861. //-----------------------------------------------------------------------------
  862. void FileOpenDialog::MoveUpFolder()
  863. {
  864. char fullpath[MAX_PATH * 4];
  865. GetCurrentDirectory(fullpath, sizeof(fullpath) - MAX_PATH);
  866. Q_StripLastDir( fullpath, sizeof( fullpath ) );
  867. // append a trailing slash
  868. Q_AppendSlash( fullpath, sizeof( fullpath ) );
  869. SetStartDirectory(fullpath);
  870. PopulateFileList();
  871. InvalidateLayout();
  872. Repaint();
  873. }
  874. //-----------------------------------------------------------------------------
  875. // Purpose: Validate that the current path is valid
  876. //-----------------------------------------------------------------------------
  877. void FileOpenDialog::ValidatePath()
  878. {
  879. char fullpath[MAX_PATH * 4];
  880. GetCurrentDirectory(fullpath, sizeof(fullpath) - MAX_PATH);
  881. Q_RemoveDotSlashes( fullpath );
  882. // when statting a directory on Windows, you want to include
  883. // the terminal slash exactly when you are statting a root
  884. // directory. PKMN.
  885. #ifdef _WIN32
  886. if ( Q_strlen( fullpath ) != 3 )
  887. {
  888. Q_StripTrailingSlash( fullpath );
  889. }
  890. #endif
  891. // cleanup the path, we format tabs into the list to make it pretty in the UI
  892. Q_StripPrecedingAndTrailingWhitespace( fullpath );
  893. struct _stat buf;
  894. if ( ( 0 == _stat( fullpath, &buf ) ) &&
  895. ( 0 != ( buf.st_mode & S_IFDIR ) ) )
  896. {
  897. Q_AppendSlash( fullpath, sizeof( fullpath ) );
  898. Q_strncpy(m_szLastPath, fullpath, sizeof(m_szLastPath));
  899. }
  900. else
  901. {
  902. // failed to load file, use the previously successful path
  903. }
  904. m_pFullPathEdit->SetText(m_szLastPath);
  905. m_pFullPathEdit->GetTooltip()->SetText(m_szLastPath);
  906. }
  907. #ifdef WIN32
  908. const char *GetAttributesAsString( DWORD dwAttributes )
  909. {
  910. static char out[ 256 ];
  911. out[ 0 ] = 0;
  912. if ( dwAttributes & FILE_ATTRIBUTE_ARCHIVE )
  913. {
  914. Q_strncat( out, "A", sizeof( out ), COPY_ALL_CHARACTERS );
  915. }
  916. if ( dwAttributes & FILE_ATTRIBUTE_COMPRESSED )
  917. {
  918. Q_strncat( out, "C", sizeof( out ), COPY_ALL_CHARACTERS );
  919. }
  920. if ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY )
  921. {
  922. Q_strncat( out, "D", sizeof( out ), COPY_ALL_CHARACTERS );
  923. }
  924. if ( dwAttributes & FILE_ATTRIBUTE_HIDDEN )
  925. {
  926. Q_strncat( out, "H", sizeof( out ), COPY_ALL_CHARACTERS );
  927. }
  928. if ( dwAttributes & FILE_ATTRIBUTE_READONLY )
  929. {
  930. Q_strncat( out, "R", sizeof( out ), COPY_ALL_CHARACTERS );
  931. }
  932. if ( dwAttributes & FILE_ATTRIBUTE_SYSTEM )
  933. {
  934. Q_strncat( out, "S", sizeof( out ), COPY_ALL_CHARACTERS );
  935. }
  936. if ( dwAttributes & FILE_ATTRIBUTE_TEMPORARY )
  937. {
  938. Q_strncat( out, "T", sizeof( out ), COPY_ALL_CHARACTERS );
  939. }
  940. return out;
  941. }
  942. const char *GetFileTimetamp( FILETIME ft )
  943. {
  944. SYSTEMTIME local;
  945. FILETIME localFileTime;
  946. FileTimeToLocalFileTime( &ft, &localFileTime );
  947. FileTimeToSystemTime( &localFileTime, &local );
  948. static char out[ 256 ];
  949. bool am = true;
  950. WORD hour = local.wHour;
  951. if ( hour >= 12 )
  952. {
  953. am = false;
  954. // 12:42 pm displays as 12:42 pm
  955. // 13:42 pm displays as 1:42 pm
  956. if ( hour > 12 )
  957. {
  958. hour -= 12;
  959. }
  960. }
  961. Q_snprintf( out, sizeof( out ), "%d/%02d/%04d %d:%02d %s",
  962. local.wMonth,
  963. local.wDay,
  964. local.wYear,
  965. hour,
  966. local.wMinute,
  967. am ? "AM" : "PM" // TODO: Localize this?
  968. );
  969. return out;
  970. }
  971. #endif
  972. //-----------------------------------------------------------------------------
  973. // Purpose: Fill the filelist with the names of all the files in the current directory
  974. //-----------------------------------------------------------------------------
  975. #define MAX_FILTER_LENGTH 255
  976. void FileOpenDialog::PopulateFileList()
  977. {
  978. // clear the current list
  979. m_pFileList->DeleteAllItems();
  980. FileFindHandle_t findHandle;
  981. char pszFileModified[64];
  982. // get the current directory
  983. char currentDir[MAX_PATH * 4];
  984. char dir[MAX_PATH * 4];
  985. char filterList[MAX_FILTER_LENGTH+1];
  986. GetCurrentDirectory(currentDir, sizeof(dir));
  987. KeyValues *combokv = m_pFileTypeCombo->GetActiveItemUserData();
  988. if (combokv)
  989. {
  990. Q_strncpy(filterList, combokv->GetString("filter", "*"), MAX_FILTER_LENGTH);
  991. }
  992. else
  993. {
  994. // add wildcard for search
  995. Q_strncpy(filterList, "*\0", MAX_FILTER_LENGTH);
  996. }
  997. char *filterPtr = filterList;
  998. KeyValues *kv = new KeyValues("item");
  999. if ( m_DialogType != FOD_SELECT_DIRECTORY )
  1000. {
  1001. while ((filterPtr != NULL) && (*filterPtr != 0))
  1002. {
  1003. // parse the next filter in the list.
  1004. char curFilter[MAX_FILTER_LENGTH];
  1005. curFilter[0] = 0;
  1006. int i = 0;
  1007. while ((filterPtr != NULL) && ((*filterPtr == ',') || (*filterPtr == ';') || (*filterPtr <= ' ')))
  1008. {
  1009. ++filterPtr;
  1010. }
  1011. while ((filterPtr != NULL) && (*filterPtr != ',') && (*filterPtr != ';') && (*filterPtr > ' '))
  1012. {
  1013. curFilter[i++] = *(filterPtr++);
  1014. }
  1015. curFilter[i] = 0;
  1016. if (curFilter[0] == 0)
  1017. {
  1018. break;
  1019. }
  1020. Q_snprintf( dir, MAX_PATH*4, "%s%s", currentDir, curFilter );
  1021. // Open the directory and walk it, loading files
  1022. const char *pszFileName = g_pFullFileSystem->FindFirst( dir, &findHandle );
  1023. while ( pszFileName )
  1024. {
  1025. if ( !g_pFullFileSystem->FindIsDirectory( findHandle )
  1026. || !IsOSX()
  1027. || ( IsOSX() && g_pFullFileSystem->FindIsDirectory( findHandle ) && Q_stristr( pszFileName, ".app" ) ) )
  1028. {
  1029. char pFullPath[MAX_PATH];
  1030. Q_snprintf( pFullPath, MAX_PATH, "%s%s", currentDir, pszFileName );
  1031. // add the file to the list
  1032. kv->SetString( "text", pszFileName );
  1033. kv->SetInt( "image", 1 );
  1034. IImage *image = surface()->GetIconImageForFullPath( pFullPath );
  1035. if ( image )
  1036. {
  1037. kv->SetPtr( "iconImage", (void *)image );
  1038. }
  1039. kv->SetInt("imageSelected", 1);
  1040. kv->SetInt("directory", 0);
  1041. kv->SetString( "filesize", Q_pretifymem( g_pFullFileSystem->Size( pFullPath ), 0, true ) );
  1042. Q_FixSlashes( pFullPath );
  1043. wchar_t fileType[ 80 ];
  1044. g_pFullFileSystem->GetFileTypeForFullPath( pFullPath, fileType, sizeof( fileType ) );
  1045. kv->SetWString( "type", fileType );
  1046. kv->SetString( "attributes", g_pFullFileSystem->IsFileWritable( pFullPath )? "" : "R" );
  1047. long fileModified = g_pFullFileSystem->GetFileTime( pFullPath );
  1048. g_pFullFileSystem->FileTimeToString( pszFileModified, sizeof( pszFileModified ), fileModified );
  1049. kv->SetString( "modified", pszFileModified );
  1050. // kv->SetString( "created", GetFileTimetamp( findData.ftCreationTime ) );
  1051. m_pFileList->AddItem(kv, 0, false, false);
  1052. }
  1053. pszFileName = g_pFullFileSystem->FindNext( findHandle );
  1054. }
  1055. g_pFullFileSystem->FindClose( findHandle );
  1056. }
  1057. }
  1058. // find all the directories
  1059. GetCurrentDirectory( dir, sizeof(dir) );
  1060. Q_strncat(dir, "*", sizeof( dir ), COPY_ALL_CHARACTERS);
  1061. const char *pszFileName = g_pFullFileSystem->FindFirst( dir, &findHandle );
  1062. while ( pszFileName )
  1063. {
  1064. if ( pszFileName[0] != '.' && g_pFullFileSystem->FindIsDirectory( findHandle )
  1065. && ( !IsOSX() || ( IsOSX() && !Q_stristr( pszFileName, ".app" ) ) ) )
  1066. {
  1067. char pFullPath[MAX_PATH];
  1068. Q_snprintf( pFullPath, MAX_PATH, "%s%s", currentDir, pszFileName );
  1069. kv->SetString("text", pszFileName );
  1070. kv->SetPtr( "iconImage", (void *)NULL );
  1071. kv->SetInt("image", 2);
  1072. kv->SetInt("imageSelected", 3);
  1073. kv->SetInt("directory", 1);
  1074. kv->SetString( "filesize", "" );
  1075. kv->SetString( "type", "#FileOpenDialog_FileType_Folder" );
  1076. kv->SetString( "attributes", g_pFullFileSystem->IsFileWritable( pFullPath )? "" : "R" );
  1077. long fileModified = g_pFullFileSystem->GetFileTime( pFullPath );
  1078. g_pFullFileSystem->FileTimeToString( pszFileModified, sizeof( pszFileModified ), fileModified );
  1079. kv->SetString( "modified", pszFileModified );
  1080. // kv->SetString( "created", GetFileTimetamp( findData.ftCreationTime ) );
  1081. m_pFileList->AddItem( kv, 0, false, false );
  1082. }
  1083. pszFileName = g_pFullFileSystem->FindNext( findHandle );
  1084. }
  1085. g_pFullFileSystem->FindClose( findHandle );
  1086. kv->deleteThis();
  1087. m_pFileList->SortList();
  1088. }
  1089. //-----------------------------------------------------------------------------
  1090. // Does the specified extension match something in the filter list?
  1091. //-----------------------------------------------------------------------------
  1092. bool FileOpenDialog::ExtensionMatchesFilter( const char *pExt )
  1093. {
  1094. KeyValues *combokv = m_pFileTypeCombo->GetActiveItemUserData();
  1095. if ( !combokv )
  1096. return true;
  1097. char filterList[MAX_FILTER_LENGTH+1];
  1098. Q_strncpy( filterList, combokv->GetString("filter", "*"), MAX_FILTER_LENGTH );
  1099. char *filterPtr = filterList;
  1100. while ((filterPtr != NULL) && (*filterPtr != 0))
  1101. {
  1102. // parse the next filter in the list.
  1103. char curFilter[MAX_FILTER_LENGTH];
  1104. curFilter[0] = 0;
  1105. int i = 0;
  1106. while ((filterPtr != NULL) && ((*filterPtr == ',') || (*filterPtr == ';') || (*filterPtr <= ' ')))
  1107. {
  1108. ++filterPtr;
  1109. }
  1110. while ((filterPtr != NULL) && (*filterPtr != ',') && (*filterPtr != ';') && (*filterPtr > ' '))
  1111. {
  1112. curFilter[i++] = *(filterPtr++);
  1113. }
  1114. curFilter[i] = 0;
  1115. if (curFilter[0] == 0)
  1116. break;
  1117. if ( !Q_stricmp( curFilter, "*" ) || !Q_stricmp( curFilter, "*.*" ) )
  1118. return true;
  1119. // FIXME: This isn't exactly right, but tough cookies;
  1120. // it assumes the first two characters of the filter are *.
  1121. Assert( curFilter[0] == '*' && curFilter[1] == '.' );
  1122. if ( !Q_stricmp( &curFilter[2], pExt ) )
  1123. return true;
  1124. }
  1125. return false;
  1126. }
  1127. //-----------------------------------------------------------------------------
  1128. // Choose the first non *.* filter in the filter list
  1129. //-----------------------------------------------------------------------------
  1130. void FileOpenDialog::ChooseExtension( char *pExt, int nBufLen )
  1131. {
  1132. pExt[0] = 0;
  1133. KeyValues *combokv = m_pFileTypeCombo->GetActiveItemUserData();
  1134. if ( !combokv )
  1135. return;
  1136. char filterList[MAX_FILTER_LENGTH+1];
  1137. Q_strncpy( filterList, combokv->GetString("filter", "*"), MAX_FILTER_LENGTH );
  1138. char *filterPtr = filterList;
  1139. while ((filterPtr != NULL) && (*filterPtr != 0))
  1140. {
  1141. // parse the next filter in the list.
  1142. char curFilter[MAX_FILTER_LENGTH];
  1143. curFilter[0] = 0;
  1144. int i = 0;
  1145. while ((filterPtr != NULL) && ((*filterPtr == ',') || (*filterPtr == ';') || (*filterPtr <= ' ')))
  1146. {
  1147. ++filterPtr;
  1148. }
  1149. while ((filterPtr != NULL) && (*filterPtr != ',') && (*filterPtr != ';') && (*filterPtr > ' '))
  1150. {
  1151. curFilter[i++] = *(filterPtr++);
  1152. }
  1153. curFilter[i] = 0;
  1154. if (curFilter[0] == 0)
  1155. break;
  1156. if ( !Q_stricmp( curFilter, "*" ) || !Q_stricmp( curFilter, "*.*" ) )
  1157. continue;
  1158. // FIXME: This isn't exactly right, but tough cookies;
  1159. // it assumes the first two characters of the filter are *.
  1160. Assert( curFilter[0] == '*' && curFilter[1] == '.' );
  1161. Q_strncpy( pExt, &curFilter[1], nBufLen );
  1162. break;
  1163. }
  1164. }
  1165. //-----------------------------------------------------------------------------
  1166. // Saves the file to the start dir context
  1167. //-----------------------------------------------------------------------------
  1168. void FileOpenDialog::SaveFileToStartDirContext( const char *pFullPath )
  1169. {
  1170. if ( m_nStartDirContext == s_StartDirContexts.InvalidIndex() )
  1171. return;
  1172. char pPath[MAX_PATH];
  1173. pPath[0] = 0;
  1174. Q_ExtractFilePath( pFullPath, pPath, sizeof(pPath) );
  1175. s_StartDirContexts[ m_nStartDirContext ] = pPath;
  1176. }
  1177. //-----------------------------------------------------------------------------
  1178. // Posts a file selected message
  1179. //-----------------------------------------------------------------------------
  1180. void FileOpenDialog::PostFileSelectedMessage( const char *pFileName )
  1181. {
  1182. m_bFileSelected = true;
  1183. // open the file!
  1184. KeyValues *pKeyValues = new KeyValues( "FileSelected", "fullpath", pFileName );
  1185. KeyValues *pFilterKeys = m_pFileTypeCombo->GetActiveItemUserData();
  1186. const char *pFilterInfo = pFilterKeys ? pFilterKeys->GetString( "filterinfo", NULL ) : NULL;
  1187. if ( pFilterInfo )
  1188. {
  1189. pKeyValues->SetString( "filterinfo", pFilterInfo );
  1190. }
  1191. if ( m_pContextKeyValues )
  1192. {
  1193. pKeyValues->AddSubKey( m_pContextKeyValues );
  1194. m_pContextKeyValues = NULL;
  1195. }
  1196. PostActionSignal( pKeyValues );
  1197. CloseModal();
  1198. }
  1199. //-----------------------------------------------------------------------------
  1200. // Selects the current folder
  1201. //-----------------------------------------------------------------------------
  1202. void FileOpenDialog::OnSelectFolder()
  1203. {
  1204. ValidatePath();
  1205. // construct a file path
  1206. char pFileName[MAX_PATH];
  1207. GetSelectedFileName( pFileName, sizeof( pFileName ) );
  1208. Q_StripTrailingSlash( pFileName );
  1209. if ( !stricmp(pFileName, "..") )
  1210. {
  1211. MoveUpFolder();
  1212. // clear the name text
  1213. m_pFileNameEdit->SetText("");
  1214. return;
  1215. }
  1216. if ( !stricmp(pFileName, ".") )
  1217. {
  1218. // clear the name text
  1219. m_pFileNameEdit->SetText("");
  1220. return;
  1221. }
  1222. // Compute the full path
  1223. char pFullPath[MAX_PATH * 4];
  1224. if ( !Q_IsAbsolutePath( pFileName ) )
  1225. {
  1226. GetCurrentDirectory(pFullPath, sizeof(pFullPath) - MAX_PATH);
  1227. strcat( pFullPath, pFileName );
  1228. if ( !pFileName[0] )
  1229. {
  1230. Q_StripTrailingSlash( pFullPath );
  1231. }
  1232. }
  1233. else
  1234. {
  1235. Q_strncpy( pFullPath, pFileName, sizeof(pFullPath) );
  1236. }
  1237. if ( g_pFullFileSystem->FileExists( pFullPath ) )
  1238. {
  1239. // open the file!
  1240. SaveFileToStartDirContext( pFullPath );
  1241. PostFileSelectedMessage( pFullPath );
  1242. return;
  1243. }
  1244. PopulateDriveList();
  1245. PopulateFileList();
  1246. InvalidateLayout();
  1247. }
  1248. //-----------------------------------------------------------------------------
  1249. // Purpose: Handle the open button being pressed
  1250. // checks on what has changed and acts accordingly
  1251. //-----------------------------------------------------------------------------
  1252. void FileOpenDialog::OnOpen()
  1253. {
  1254. ValidatePath();
  1255. // construct a file path
  1256. char pFileName[MAX_PATH];
  1257. GetSelectedFileName( pFileName, sizeof( pFileName ) );
  1258. int nLen = Q_strlen( pFileName );
  1259. bool bSpecifiedDirectory = ( pFileName[nLen-1] == '/' || pFileName[nLen-1] == '\\' ) && (!IsOSX() || ( IsOSX() && !Q_stristr( pFileName, ".app" ) ) );
  1260. Q_StripTrailingSlash( pFileName );
  1261. if ( !stricmp(pFileName, "..") )
  1262. {
  1263. MoveUpFolder();
  1264. // clear the name text
  1265. m_pFileNameEdit->SetText("");
  1266. return;
  1267. }
  1268. if ( !stricmp(pFileName, ".") )
  1269. {
  1270. // clear the name text
  1271. m_pFileNameEdit->SetText("");
  1272. return;
  1273. }
  1274. // Compute the full path
  1275. char pFullPath[MAX_PATH * 4];
  1276. if ( !Q_IsAbsolutePath( pFileName ) )
  1277. {
  1278. GetCurrentDirectory(pFullPath, sizeof(pFullPath) - MAX_PATH);
  1279. Q_AppendSlash( pFullPath, sizeof( pFullPath ) );
  1280. strcat(pFullPath, pFileName);
  1281. if ( !pFileName[0] )
  1282. {
  1283. Q_StripTrailingSlash( pFullPath );
  1284. }
  1285. }
  1286. else
  1287. {
  1288. Q_strncpy( pFullPath, pFileName, sizeof(pFullPath) );
  1289. }
  1290. Q_StripTrailingSlash( pFullPath );
  1291. // when statting a directory on Windows, you want to include
  1292. // the terminal slash exactly when you are statting a root
  1293. // directory. PKMN.
  1294. #ifdef _WIN32
  1295. if ( Q_strlen( pFullPath ) == 2 )
  1296. {
  1297. Q_AppendSlash( pFullPath, Q_ARRAYSIZE( pFullPath ) );
  1298. }
  1299. #endif
  1300. // If the name specified is a directory, then change directory
  1301. if ( g_pFullFileSystem->IsDirectory( pFullPath, NULL ) &&
  1302. ( !IsOSX() || ( IsOSX() && !Q_stristr( pFullPath, ".app" ) ) ) )
  1303. {
  1304. // it's a directory; change to the specified directory
  1305. if ( !bSpecifiedDirectory )
  1306. {
  1307. Q_AppendSlash( pFullPath, Q_ARRAYSIZE( pFullPath ) );
  1308. }
  1309. SetStartDirectory( pFullPath );
  1310. // clear the name text
  1311. m_pFileNameEdit->SetText("");
  1312. m_pFileNameEdit->HideMenu();
  1313. PopulateDriveList();
  1314. PopulateFileList();
  1315. InvalidateLayout();
  1316. return;
  1317. }
  1318. else if ( bSpecifiedDirectory )
  1319. {
  1320. PopulateDriveList();
  1321. PopulateFileList();
  1322. InvalidateLayout();
  1323. return;
  1324. }
  1325. // Append suffix of the first filter that isn't *.*
  1326. char extension[512];
  1327. Q_ExtractFileExtension( pFullPath, extension, sizeof(extension) );
  1328. if ( !ExtensionMatchesFilter( extension ) )
  1329. {
  1330. ChooseExtension( extension, sizeof(extension) );
  1331. Q_SetExtension( pFullPath, extension, sizeof(pFullPath) );
  1332. }
  1333. if ( g_pFullFileSystem->FileExists( pFullPath ) )
  1334. {
  1335. // open the file!
  1336. SaveFileToStartDirContext( pFullPath );
  1337. PostFileSelectedMessage( pFullPath );
  1338. return;
  1339. }
  1340. // file not found
  1341. if ( ( m_DialogType == FOD_SAVE ) && pFileName[0] )
  1342. {
  1343. // open the file!
  1344. SaveFileToStartDirContext( pFullPath );
  1345. PostFileSelectedMessage( pFullPath );
  1346. return;
  1347. }
  1348. PopulateDriveList();
  1349. PopulateFileList();
  1350. InvalidateLayout();
  1351. }
  1352. //-----------------------------------------------------------------------------
  1353. // Purpose: using the file edit box as a prefix, create a menu of all possible files
  1354. //-----------------------------------------------------------------------------
  1355. void FileOpenDialog::PopulateFileNameCompletion()
  1356. {
  1357. char buf[80];
  1358. m_pFileNameEdit->GetText(buf, 80);
  1359. wchar_t wbuf[80];
  1360. m_pFileNameEdit->GetText(wbuf, 80);
  1361. int bufLen = wcslen(wbuf);
  1362. // delete all items before we check if there's even a string
  1363. m_pFileNameEdit->DeleteAllItems();
  1364. // no string at all - don't show even bother showing it
  1365. if (bufLen == 0)
  1366. {
  1367. m_pFileNameEdit->HideMenu();
  1368. return;
  1369. }
  1370. // what files use current string as a prefix?
  1371. int nCount = m_pFileList->GetItemCount();
  1372. int i;
  1373. for ( i = 0 ; i < nCount ; i++ )
  1374. {
  1375. KeyValues *kv = m_pFileList->GetItem(m_pFileList->GetItemIDFromRow(i));
  1376. const wchar_t *wszString = kv->GetWString("text");
  1377. if ( !_wcsnicmp(wbuf, wszString, bufLen) )
  1378. {
  1379. m_pFileNameEdit->AddItem(wszString, NULL);
  1380. }
  1381. }
  1382. // if there are any items - show the menu
  1383. if ( m_pFileNameEdit->GetItemCount() > 0 )
  1384. {
  1385. m_pFileNameEdit->ShowMenu();
  1386. }
  1387. else
  1388. {
  1389. m_pFileNameEdit->HideMenu();
  1390. }
  1391. m_pFileNameEdit->InvalidateLayout();
  1392. }
  1393. //-----------------------------------------------------------------------------
  1394. // Purpose: Handle an item in the list being selected
  1395. //-----------------------------------------------------------------------------
  1396. void FileOpenDialog::OnItemSelected()
  1397. {
  1398. // make sure only one item is selected
  1399. if (m_pFileList->GetSelectedItemsCount() != 1)
  1400. {
  1401. m_pFileNameEdit->SetText("");
  1402. }
  1403. else
  1404. {
  1405. // put the file name into the text edit box
  1406. KeyValues *data = m_pFileList->GetItem(m_pFileList->GetSelectedItem(0));
  1407. m_pFileNameEdit->SetText(data->GetString("text"));
  1408. }
  1409. InvalidateLayout();
  1410. }
  1411. //-----------------------------------------------------------------------------
  1412. // Purpose: Handle an item in the Drive combo box being selected
  1413. //-----------------------------------------------------------------------------
  1414. void FileOpenDialog::OnTextChanged(KeyValues *kv)
  1415. {
  1416. Panel *pPanel = (Panel *) kv->GetPtr("panel", NULL);
  1417. // first check which control had its text changed!
  1418. if (pPanel == m_pFullPathEdit)
  1419. {
  1420. m_pFileNameEdit->HideMenu();
  1421. m_pFileNameEdit->SetText("");
  1422. OnOpen();
  1423. }
  1424. else if (pPanel == m_pFileNameEdit)
  1425. {
  1426. PopulateFileNameCompletion();
  1427. }
  1428. else if (pPanel == m_pFileTypeCombo)
  1429. {
  1430. m_pFileNameEdit->HideMenu();
  1431. PopulateFileList();
  1432. }
  1433. }