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.
1706 lines
47 KiB
1706 lines
47 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Implementation of vgui generic open file dialog |
|
// |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
|
|
#define PROTECTED_THINGS_DISABLE |
|
|
|
#if !defined( _X360 ) && defined( WIN32 ) |
|
#include "winlite.h" |
|
#include <shellapi.h> |
|
#elif defined( POSIX ) |
|
#include <stdlib.h> |
|
#define _stat stat |
|
#define _wcsnicmp wcsncmp |
|
#elif defined( _X360 ) |
|
#else |
|
#error |
|
#endif |
|
|
|
#undef GetCurrentDirectory |
|
#include "filesystem.h" |
|
#include <sys/stat.h> |
|
|
|
#include "tier1/utldict.h" |
|
#include "tier1/utlstring.h" |
|
|
|
#include <vgui/IScheme.h> |
|
#include <vgui/ISurface.h> |
|
#include <vgui/ISystem.h> |
|
#include <KeyValues.h> |
|
#include <vgui/IVGui.h> |
|
#include <vgui/ILocalize.h> |
|
#include <vgui/IInput.h> |
|
|
|
#include <vgui_controls/FileOpenDialog.h> |
|
|
|
#include <vgui_controls/Button.h> |
|
#include <vgui_controls/ComboBox.h> |
|
#include <vgui_controls/ImagePanel.h> |
|
#include <vgui_controls/InputDialog.h> |
|
#include <vgui_controls/Label.h> |
|
#include <vgui_controls/ListPanel.h> |
|
#include <vgui_controls/TextEntry.h> |
|
#include <vgui_controls/ImageList.h> |
|
#include <vgui_controls/MenuItem.h> |
|
#include <vgui_controls/Tooltip.h> |
|
|
|
#if defined( _X360 ) |
|
#include "xbox/xbox_win32stubs.h" |
|
#undef GetCurrentDirectory |
|
#endif |
|
|
|
#include <time.h> |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
using namespace vgui; |
|
|
|
static int s_nLastSortColumn = 0; |
|
|
|
static int ListFileNameSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) |
|
{ |
|
NOTE_UNUSED( pPanel ); |
|
|
|
bool dir1 = item1.kv->GetInt("directory") == 1; |
|
bool dir2 = item2.kv->GetInt("directory") == 1; |
|
|
|
// if they're both not directories of files, return if dir1 is a directory (before files) |
|
if (dir1 != dir2) |
|
{ |
|
return dir1 ? -1 : 1; |
|
} |
|
|
|
const char *string1 = item1.kv->GetString("text"); |
|
const char *string2 = item2.kv->GetString("text"); |
|
|
|
// YWB: Mimic windows behavior where filenames starting with numbers are sorted based on numeric part |
|
int num1 = Q_atoi( string1 ); |
|
int num2 = Q_atoi( string2 ); |
|
|
|
if ( num1 != 0 && |
|
num2 != 0 ) |
|
{ |
|
if ( num1 < num2 ) |
|
return -1; |
|
else if ( num1 > num2 ) |
|
return 1; |
|
} |
|
|
|
// Push numbers before everything else |
|
if ( num1 != 0 ) |
|
{ |
|
return -1; |
|
} |
|
|
|
// Push numbers before everything else |
|
if ( num2 != 0 ) |
|
{ |
|
return 1; |
|
} |
|
|
|
return Q_stricmp( string1, string2 ); |
|
} |
|
|
|
static int ListBaseStringSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *fieldName ) |
|
{ |
|
bool dir1 = item1.kv->GetInt("directory") == 1; |
|
bool dir2 = item2.kv->GetInt("directory") == 1; |
|
|
|
// if they're both not directories of files, return if dir1 is a directory (before files) |
|
if (dir1 != dir2) |
|
{ |
|
return -1; |
|
} |
|
|
|
const char *string1 = item1.kv->GetString(fieldName); |
|
const char *string2 = item2.kv->GetString(fieldName); |
|
int cval = Q_stricmp(string1, string2); |
|
if ( cval == 0 ) |
|
{ |
|
// Use filename to break ties |
|
return ListFileNameSortFunc( pPanel, item1, item2 ); |
|
} |
|
|
|
return cval; |
|
} |
|
|
|
static int ListBaseIntegerSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *fieldName ) |
|
{ |
|
bool dir1 = item1.kv->GetInt("directory") == 1; |
|
bool dir2 = item2.kv->GetInt("directory") == 1; |
|
|
|
// if they're both not directories of files, return if dir1 is a directory (before files) |
|
if (dir1 != dir2) |
|
{ |
|
return -1; |
|
} |
|
|
|
int i1 = item1.kv->GetInt(fieldName); |
|
int i2 = item2.kv->GetInt(fieldName); |
|
if ( i1 == i2 ) |
|
{ |
|
// Use filename to break ties |
|
return ListFileNameSortFunc( pPanel, item1, item2 ); |
|
} |
|
|
|
return ( i1 < i2 ) ? -1 : 1; |
|
} |
|
|
|
static int ListBaseInteger64SortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2, char const *lowfield, char const *highfield ) |
|
{ |
|
bool dir1 = item1.kv->GetInt("directory") == 1; |
|
bool dir2 = item2.kv->GetInt("directory") == 1; |
|
|
|
// if they're both not directories of files, return if dir1 is a directory (before files) |
|
if (dir1 != dir2) |
|
{ |
|
return dir1 ? -1 : 1; |
|
} |
|
|
|
uint32 l1 = item1.kv->GetInt(lowfield); |
|
uint32 h1 = item1.kv->GetInt(highfield); |
|
uint32 l2 = item2.kv->GetInt(lowfield); |
|
uint32 h2 = item2.kv->GetInt(highfield); |
|
uint64 i1 = (uint64)( (uint64)l1 | ( (uint64)h1 << 32 ) ); |
|
uint64 i2 = (uint64)( (uint64)l2 | ( (uint64)h2 << 32 ) ); |
|
|
|
if ( i1 == i2 ) |
|
{ |
|
// Use filename to break ties |
|
return ListFileNameSortFunc( pPanel, item1, item2 ); |
|
} |
|
|
|
return ( i1 < i2 ) ? -1 : 1; |
|
} |
|
|
|
|
|
static int ListFileSizeSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) |
|
{ |
|
return ListBaseIntegerSortFunc( pPanel, item1, item2, "filesizeint" ); |
|
} |
|
|
|
static int ListFileModifiedSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) |
|
{ |
|
// NOTE: Backward order to get most recent files first |
|
return ListBaseInteger64SortFunc( pPanel, item2, item1, "modifiedint_low", "modifiedint_high" ); |
|
} |
|
static int ListFileCreatedSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) |
|
{ |
|
// NOTE: Backward order to get most recent files first |
|
return ListBaseInteger64SortFunc( pPanel, item2, item1, "createdint_low", "createdint_high" ); |
|
} |
|
static int ListFileAttributesSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) |
|
{ |
|
return ListBaseStringSortFunc( pPanel, item1, item2, "attributes" ); |
|
} |
|
static int ListFileTypeSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) |
|
{ |
|
return ListBaseStringSortFunc( pPanel, item1, item2, "type" ); |
|
} |
|
|
|
|
|
|
|
namespace vgui |
|
{ |
|
|
|
class FileCompletionMenu : public Menu |
|
{ |
|
public: |
|
FileCompletionMenu(Panel *parent, const char *panelName) : Menu(parent, panelName) |
|
{ |
|
} |
|
|
|
// override it so it doesn't request focus |
|
virtual void SetVisible(bool state) |
|
{ |
|
Panel::SetVisible(state); |
|
} |
|
|
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// File completion edit text entry |
|
//----------------------------------------------------------------------------- |
|
class FileCompletionEdit : public TextEntry |
|
{ |
|
DECLARE_CLASS_SIMPLE( FileCompletionEdit, TextEntry ); |
|
|
|
public: |
|
FileCompletionEdit(Panel *parent); |
|
~FileCompletionEdit(); |
|
|
|
int AddItem(const char *itemText, KeyValues *userData); |
|
int AddItem(const wchar_t *itemText, KeyValues *userData); |
|
void DeleteAllItems(); |
|
int GetItemCount(); |
|
int GetItemIDFromRow(int row); |
|
int GetRowFromItemID(int itemID); |
|
virtual void PerformLayout(); |
|
void OnSetText(const wchar_t *newtext); |
|
virtual void OnKillFocus(); |
|
void HideMenu(void); |
|
void ShowMenu(void); |
|
virtual void OnKeyCodeTyped(KeyCode code); |
|
MESSAGE_FUNC_INT( OnMenuItemHighlight, "MenuItemHighlight", itemID ); |
|
|
|
private: |
|
FileCompletionMenu *m_pDropDown; |
|
}; |
|
|
|
|
|
|
|
FileCompletionEdit::FileCompletionEdit(Panel *parent) : TextEntry(parent, NULL) |
|
{ |
|
m_pDropDown = new FileCompletionMenu(this, NULL); |
|
m_pDropDown->AddActionSignalTarget(this); |
|
} |
|
|
|
FileCompletionEdit::~FileCompletionEdit() |
|
{ |
|
delete m_pDropDown; |
|
} |
|
|
|
int FileCompletionEdit::AddItem(const char *itemText, KeyValues *userData) |
|
{ |
|
// when the menu item is selected it will send the custom message "SetText" |
|
return m_pDropDown->AddMenuItem(itemText, new KeyValues("SetText", "text", itemText), this, userData); |
|
} |
|
int FileCompletionEdit::AddItem(const wchar_t *itemText, KeyValues *userData) |
|
{ |
|
// add the element to the menu |
|
// when the menu item is selected it will send the custom message "SetText" |
|
KeyValues *kv = new KeyValues("SetText"); |
|
kv->SetWString("text", itemText); |
|
|
|
// get an ansi version for the menuitem name |
|
char ansi[128]; |
|
g_pVGuiLocalize->ConvertUnicodeToANSI(itemText, ansi, sizeof(ansi)); |
|
return m_pDropDown->AddMenuItem(ansi, kv, this, userData); |
|
} |
|
|
|
void FileCompletionEdit::DeleteAllItems() |
|
{ |
|
m_pDropDown->DeleteAllItems(); |
|
} |
|
|
|
int FileCompletionEdit::GetItemCount() |
|
{ |
|
return m_pDropDown->GetItemCount(); |
|
} |
|
|
|
int FileCompletionEdit::GetItemIDFromRow(int row) |
|
{ |
|
// valid from [0, GetItemCount) |
|
return m_pDropDown->GetMenuID(row); |
|
} |
|
|
|
int FileCompletionEdit::GetRowFromItemID(int itemID) |
|
{ |
|
int i; |
|
for (i=0;i<GetItemCount();i++) |
|
{ |
|
if (m_pDropDown->GetMenuID(i) == itemID) |
|
return i; |
|
} |
|
return -1; |
|
} |
|
|
|
void FileCompletionEdit::PerformLayout() |
|
{ |
|
BaseClass::PerformLayout(); |
|
|
|
m_pDropDown->PositionRelativeToPanel( this, Menu::DOWN, 0 ); |
|
|
|
// reset the width of the drop down menu to be the width of this edit box |
|
m_pDropDown->SetFixedWidth(GetWide()); |
|
m_pDropDown->ForceCalculateWidth(); |
|
} |
|
|
|
void FileCompletionEdit::OnSetText(const wchar_t *newtext) |
|
{ |
|
// see if the combobox text has changed, and if so, post a message detailing the new text |
|
wchar_t wbuf[255]; |
|
GetText( wbuf, 254 ); |
|
|
|
if ( wcscmp(wbuf, newtext) ) |
|
{ |
|
// text has changed |
|
SetText(newtext); |
|
|
|
// fire off that things have changed |
|
PostActionSignal(new KeyValues("TextChanged", "text", newtext)); |
|
Repaint(); |
|
} |
|
} |
|
|
|
void FileCompletionEdit::OnKillFocus() |
|
{ |
|
HideMenu(); |
|
BaseClass::OnKillFocus(); |
|
} |
|
|
|
void FileCompletionEdit::HideMenu(void) |
|
{ |
|
// hide the menu |
|
m_pDropDown->SetVisible(false); |
|
} |
|
|
|
void FileCompletionEdit::ShowMenu(void) |
|
{ |
|
// reset the dropdown's position |
|
m_pDropDown->InvalidateLayout(); |
|
|
|
// make sure we're at the top of the draw order (and therefore our children as well) |
|
// this important to make sure the menu will be drawn in the foreground |
|
MoveToFront(); |
|
|
|
// reset the drop down |
|
m_pDropDown->ClearCurrentlyHighlightedItem(); |
|
|
|
// limit it to only 6 |
|
if (m_pDropDown->GetItemCount() > 6) |
|
{ |
|
m_pDropDown->SetNumberOfVisibleItems(6); |
|
} |
|
else |
|
{ |
|
m_pDropDown->SetNumberOfVisibleItems(m_pDropDown->GetItemCount()); |
|
} |
|
// show the menu |
|
m_pDropDown->SetVisible(true); |
|
|
|
Repaint(); |
|
} |
|
|
|
void FileCompletionEdit::OnKeyCodeTyped(KeyCode code) |
|
{ |
|
if ( code == KEY_DOWN ) |
|
{ |
|
if (m_pDropDown->GetItemCount() > 0) |
|
{ |
|
int menuID = m_pDropDown->GetCurrentlyHighlightedItem(); |
|
int row = -1; |
|
if ( menuID == -1 ) |
|
{ |
|
row = m_pDropDown->GetItemCount() - 1; |
|
} |
|
else |
|
{ |
|
row = GetRowFromItemID(menuID); |
|
} |
|
row++; |
|
if (row == m_pDropDown->GetItemCount()) |
|
{ |
|
row = 0; |
|
} |
|
menuID = GetItemIDFromRow(row); |
|
m_pDropDown->SetCurrentlyHighlightedItem(menuID); |
|
return; |
|
} |
|
} |
|
else if ( code == KEY_UP ) |
|
{ |
|
if (m_pDropDown->GetItemCount() > 0) |
|
{ |
|
int menuID = m_pDropDown->GetCurrentlyHighlightedItem(); |
|
int row = -1; |
|
if ( menuID == -1 ) |
|
{ |
|
row = 0; |
|
} |
|
else |
|
{ |
|
row = GetRowFromItemID(menuID); |
|
} |
|
row--; |
|
if ( row < 0 ) |
|
{ |
|
row = m_pDropDown->GetItemCount() - 1; |
|
} |
|
menuID = GetItemIDFromRow(row); |
|
m_pDropDown->SetCurrentlyHighlightedItem(menuID); |
|
return; |
|
} |
|
} |
|
else if ( code == KEY_ESCAPE ) |
|
{ |
|
if ( m_pDropDown->IsVisible() ) |
|
{ |
|
HideMenu(); |
|
return; |
|
} |
|
} |
|
BaseClass::OnKeyCodeTyped(code); |
|
return; |
|
} |
|
|
|
void FileCompletionEdit::OnMenuItemHighlight( int itemID ) |
|
{ |
|
char wbuf[80]; |
|
if ( m_pDropDown->IsValidMenuID(itemID) ) |
|
{ |
|
m_pDropDown->GetMenuItem(itemID)->GetText(wbuf, 80); |
|
} |
|
else |
|
{ |
|
wbuf[0] = 0; |
|
} |
|
SetText(wbuf); |
|
RequestFocus(); |
|
GotoTextEnd(); |
|
} |
|
|
|
|
|
} // namespace vgui |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Dictionary of start dir contexts |
|
//----------------------------------------------------------------------------- |
|
static CUtlDict< CUtlString, unsigned short > s_StartDirContexts; |
|
|
|
struct ColumnInfo_t |
|
{ |
|
char const *columnName; |
|
char const *columnText; |
|
int startingWidth; |
|
int minWidth; |
|
int maxWidth; |
|
int flags; |
|
SortFunc *pfnSort; |
|
Label::Alignment alignment; |
|
}; |
|
|
|
static ColumnInfo_t g_ColInfo[] = |
|
{ |
|
{ "text", "#FileOpenDialog_Col_Name", 175, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileNameSortFunc , Label::a_west }, |
|
{ "filesize", "#FileOpenDialog_Col_Size", 100, 20, 10000, 0, &ListFileSizeSortFunc , Label::a_east }, |
|
{ "type", "#FileOpenDialog_Col_Type", 150, 20, 10000, 0, &ListFileTypeSortFunc , Label::a_west }, |
|
{ "modified", "#FileOpenDialog_Col_DateModified", 125, 20, 10000, 0, &ListFileModifiedSortFunc , Label::a_west }, |
|
// { "created", "#FileOpenDialog_Col_DateCreated", 125, 20, 10000, ListPanel::COLUMN_HIDDEN, &ListFileCreatedSortFunc , Label::a_west }, |
|
{ "attributes", "#FileOpenDialog_Col_Attributes", 50, 20, 10000, ListPanel::COLUMN_HIDDEN, &ListFileAttributesSortFunc , Label::a_west }, |
|
}; |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Constructor |
|
//----------------------------------------------------------------------------- |
|
FileOpenDialog::FileOpenDialog(Panel *parent, const char *title, bool bOpenOnly, KeyValues* pContextKeyValues ) : |
|
Frame( parent, "FileOpenDialog" ) |
|
{ |
|
m_DialogType = bOpenOnly ? FOD_OPEN : FOD_SAVE; |
|
Init( title, pContextKeyValues ); |
|
} |
|
|
|
|
|
FileOpenDialog::FileOpenDialog( Panel *parent, const char *title, FileOpenDialogType_t type, KeyValues *pContextKeyValues ) : |
|
Frame( parent, "FileOpenDialog" ) |
|
{ |
|
m_DialogType = type; |
|
Init( title, pContextKeyValues ); |
|
} |
|
|
|
void FileOpenDialog::Init( const char *title, KeyValues *pContextKeyValues ) |
|
{ |
|
m_bFileSelected = false; |
|
SetTitle(title, true); |
|
SetMinimizeButtonVisible(false); |
|
|
|
#ifdef POSIX |
|
Q_strncpy(m_szLastPath, "/", sizeof( m_szLastPath ) ); |
|
#else |
|
Q_strncpy(m_szLastPath, "c:\\", sizeof( m_szLastPath ) ); |
|
#endif |
|
|
|
m_pContextKeyValues = pContextKeyValues; |
|
|
|
// Get the list of available drives and put them in a menu here. |
|
// Start with the directory we are in. |
|
m_pFullPathEdit = new ComboBox(this, "FullPathEdit", 6, false); |
|
m_pFullPathEdit->GetTooltip()->SetTooltipFormatToSingleLine(); |
|
|
|
// list panel |
|
m_pFileList = new ListPanel(this, "FileList"); |
|
for ( int i = 0; i < ARRAYSIZE( g_ColInfo ); ++i ) |
|
{ |
|
const ColumnInfo_t& info = g_ColInfo[ i ]; |
|
|
|
m_pFileList->AddColumnHeader( i, info.columnName, info.columnText, info.startingWidth, info.minWidth, info.maxWidth, info.flags ); |
|
m_pFileList->SetSortFunc( i, info.pfnSort ); |
|
m_pFileList->SetColumnTextAlignment( i, info.alignment ); |
|
} |
|
|
|
m_pFileList->SetSortColumn( s_nLastSortColumn ); |
|
m_pFileList->SetMultiselectEnabled( false ); |
|
|
|
// file name edit box |
|
m_pFileNameEdit = new FileCompletionEdit(this); |
|
m_pFileNameEdit->AddActionSignalTarget(this); |
|
|
|
m_pFileTypeCombo = new ComboBox( this, "FileTypeCombo", 6, false ); |
|
|
|
switch ( m_DialogType ) |
|
{ |
|
case FOD_OPEN: |
|
m_pOpenButton = new Button( this, "OpenButton", "#FileOpenDialog_Open", this ); |
|
break; |
|
case FOD_SAVE: |
|
m_pOpenButton = new Button( this, "OpenButton", "#FileOpenDialog_Save", this ); |
|
break; |
|
case FOD_SELECT_DIRECTORY: |
|
m_pOpenButton = new Button( this, "OpenButton", "#FileOpenDialog_Select", this ); |
|
m_pFileTypeCombo->SetVisible( false ); |
|
break; |
|
} |
|
|
|
m_pCancelButton = new Button( this, "CancelButton", "#FileOpenDialog_Cancel", this ); |
|
m_pFolderUpButton = new Button( this, "FolderUpButton", "", this ); |
|
m_pFolderUpButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_Up" ); |
|
m_pNewFolderButton = new Button( this, "NewFolderButton", "", this ); |
|
m_pNewFolderButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_NewFolder" ); |
|
m_pOpenInExplorerButton = new Button( this, "OpenInExplorerButton", "", this ); |
|
|
|
#if defined ( OSX ) |
|
m_pOpenInExplorerButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_OpenInFinderButton" ); |
|
#elif defined ( POSIX ) |
|
m_pOpenInExplorerButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_OpenInDesktopManagerButton" ); |
|
#else // Assume Windows / Explorer |
|
m_pOpenInExplorerButton->GetTooltip()->SetText( "#FileOpenDialog_ToolTip_OpenInExplorerButton" ); |
|
#endif |
|
|
|
Label *lookIn = new Label( this, "LookInLabel", "#FileOpenDialog_Look_in" ); |
|
Label *fileName = new Label( this, "FileNameLabel", |
|
( m_DialogType != FOD_SELECT_DIRECTORY ) ? "#FileOpenDialog_File_name" : "#FileOpenDialog_Directory_Name" ); |
|
|
|
m_pFolderIcon = new ImagePanel(NULL, "FolderIcon"); |
|
|
|
// set up the control's initial positions |
|
SetSize( 600, 260 ); |
|
|
|
int nFileEditLeftSide = ( m_DialogType != FOD_SELECT_DIRECTORY ) ? 84 : 100; |
|
int nFileNameWidth = ( m_DialogType != FOD_SELECT_DIRECTORY ) ? 72 : 82; |
|
|
|
m_pFullPathEdit->SetBounds(67, 32, 310, 24); |
|
m_pFolderUpButton->SetBounds(362, 32, 24, 24); |
|
m_pNewFolderButton->SetBounds(392, 32, 24, 24); |
|
m_pOpenInExplorerButton->SetBounds(332, 32, 24, 24); |
|
m_pFileList->SetBounds(10, 60, 406, 130); |
|
m_pFileNameEdit->SetBounds( nFileEditLeftSide, 194, 238, 24); |
|
m_pFileTypeCombo->SetBounds( nFileEditLeftSide, 224, 238, 24); |
|
m_pOpenButton->SetBounds(336, 194, 74, 24); |
|
m_pCancelButton->SetBounds(336, 224, 74, 24); |
|
lookIn->SetBounds(10, 32, 55, 24); |
|
fileName->SetBounds(10, 194, nFileNameWidth, 24); |
|
|
|
// set autolayout parameters |
|
m_pFullPathEdit->SetAutoResize( Panel::PIN_TOPLEFT, Panel::AUTORESIZE_RIGHT, 67, 32, -100, 0 ); |
|
m_pFileNameEdit->SetAutoResize( Panel::PIN_BOTTOMLEFT, Panel::AUTORESIZE_RIGHT, nFileEditLeftSide, -42, -104, 0 ); |
|
m_pFileTypeCombo->SetAutoResize( Panel::PIN_BOTTOMLEFT, Panel::AUTORESIZE_RIGHT, nFileEditLeftSide, -12, -104, 0 ); |
|
m_pFileList->SetAutoResize( Panel::PIN_TOPLEFT, Panel::AUTORESIZE_DOWNANDRIGHT, 10, 60, -10, -70 ); |
|
|
|
m_pFolderUpButton->SetPinCorner( Panel::PIN_TOPRIGHT, -40, 32 ); |
|
m_pNewFolderButton->SetPinCorner( Panel::PIN_TOPRIGHT, -10, 32 ); |
|
m_pOpenInExplorerButton->SetPinCorner( Panel::PIN_TOPRIGHT, -70, 32 ); |
|
m_pOpenButton->SetPinCorner( Panel::PIN_BOTTOMRIGHT, -16, -42 ); |
|
m_pCancelButton->SetPinCorner( Panel::PIN_BOTTOMRIGHT, -16, -12 ); |
|
lookIn->SetPinCorner( Panel::PIN_TOPLEFT, 10, 32 ); |
|
fileName->SetPinCorner( Panel::PIN_BOTTOMLEFT, 10, -42 ); |
|
|
|
// label settings |
|
lookIn->SetContentAlignment(Label::a_west); |
|
fileName->SetContentAlignment(Label::a_west); |
|
|
|
lookIn->SetAssociatedControl(m_pFullPathEdit); |
|
fileName->SetAssociatedControl(m_pFileNameEdit); |
|
|
|
if ( m_DialogType != FOD_SELECT_DIRECTORY ) |
|
{ |
|
Label *fileType = new Label(this, "FileTypeLabel", "#FileOpenDialog_File_type"); |
|
fileType->SetBounds(10, 224, 72, 24); |
|
fileType->SetPinCorner( Panel::PIN_BOTTOMLEFT, 10, -12 ); |
|
fileType->SetContentAlignment(Label::a_west); |
|
fileType->SetAssociatedControl( m_pFileTypeCombo ); |
|
} |
|
|
|
// set tab positions |
|
GetFocusNavGroup().SetDefaultButton(m_pOpenButton); |
|
|
|
m_pFileNameEdit->SetTabPosition(1); |
|
m_pFileTypeCombo->SetTabPosition(2); |
|
m_pOpenButton->SetTabPosition(3); |
|
m_pCancelButton->SetTabPosition(4); |
|
m_pFullPathEdit->SetTabPosition(5); |
|
m_pFileList->SetTabPosition(6); |
|
|
|
m_pOpenButton->SetCommand( ( m_DialogType != FOD_SELECT_DIRECTORY ) ? new KeyValues( "OnOpen" ) : new KeyValues( "SelectFolder" ) ); |
|
m_pCancelButton->SetCommand( "CloseModal" ); |
|
m_pFolderUpButton->SetCommand( new KeyValues( "OnFolderUp" ) ); |
|
m_pNewFolderButton->SetCommand( new KeyValues( "OnNewFolder" ) ); |
|
m_pOpenInExplorerButton->SetCommand( new KeyValues( "OpenInExplorer" ) ); |
|
|
|
SetSize( 600, 384 ); |
|
|
|
m_nStartDirContext = s_StartDirContexts.InvalidIndex(); |
|
|
|
// Set our starting path to the current directory |
|
char pLocalPath[255]; |
|
g_pFullFileSystem->GetCurrentDirectory( pLocalPath , 255 ); |
|
if ( !pLocalPath[0] || ( IsOSX() && V_strlen(pLocalPath) <= 2 ) ) |
|
{ |
|
const char *pszHomeDir = getenv( "HOME" ); |
|
V_strcpy_safe( pLocalPath, pszHomeDir ); |
|
} |
|
|
|
SetStartDirectory( pLocalPath ); |
|
|
|
// Because these call through virtual functions, we can't issue them in the constructor, so we post a message to ourselves instead!! |
|
PostMessage( GetVPanel(), new KeyValues( "PopulateFileList" ) ); |
|
PostMessage( GetVPanel(), new KeyValues( "PopulateDriveList" ) ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Destructor |
|
//----------------------------------------------------------------------------- |
|
FileOpenDialog::~FileOpenDialog() |
|
{ |
|
s_nLastSortColumn = m_pFileList->GetSortColumn(); |
|
if ( m_pContextKeyValues ) |
|
{ |
|
m_pContextKeyValues->deleteThis(); |
|
m_pContextKeyValues = NULL; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Apply scheme settings |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::ApplySchemeSettings(IScheme *pScheme) |
|
{ |
|
BaseClass::ApplySchemeSettings(pScheme); |
|
m_pFolderIcon->SetImage(scheme()->GetImage("resource/icon_folder", false)); |
|
m_pFolderUpButton->AddImage(scheme()->GetImage("resource/icon_folderup", false), -3); |
|
m_pNewFolderButton->AddImage( scheme()->GetImage("resource/icon_newfolder", false), -3 ); |
|
m_pOpenInExplorerButton->AddImage( scheme()->GetImage("resource/icon_play_once", false), -3 ); |
|
|
|
ImageList *imageList = new ImageList(false); |
|
imageList->AddImage(scheme()->GetImage("resource/icon_file", false)); |
|
imageList->AddImage(scheme()->GetImage("resource/icon_folder", false)); |
|
imageList->AddImage(scheme()->GetImage("resource/icon_folder_selected", false)); |
|
|
|
m_pFileList->SetImageList(imageList, true); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Prevent default button ('select') from getting triggered |
|
// when selecting directories. Instead, open the directory |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::OnKeyCodeTyped(KeyCode code) |
|
{ |
|
if ( m_DialogType == FOD_SELECT_DIRECTORY && code == KEY_ENTER ) |
|
{ |
|
OnOpen(); |
|
} |
|
else |
|
{ |
|
BaseClass::OnKeyCodeTyped( code ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::PopulateDriveList() |
|
{ |
|
char fullpath[MAX_PATH * 4]; |
|
char subDirPath[MAX_PATH * 4]; |
|
GetCurrentDirectory(fullpath, sizeof(fullpath) - MAX_PATH); |
|
Q_strncpy(subDirPath, fullpath, sizeof( subDirPath ) ); |
|
|
|
m_pFullPathEdit->DeleteAllItems(); |
|
|
|
#ifdef WIN32 |
|
// populate the drive list |
|
char buf[512]; |
|
int len = system()->GetAvailableDrives(buf, 512); |
|
char *pBuf = buf; |
|
for (int i=0; i < len / 4; i++) |
|
{ |
|
m_pFullPathEdit->AddItem(pBuf, NULL); |
|
|
|
// is this our drive - add all subdirectories |
|
if (!_strnicmp(pBuf, fullpath, 2)) |
|
{ |
|
int indent = 0; |
|
char *pData = fullpath; |
|
while (*pData) |
|
{ |
|
if ( *pData == CORRECT_PATH_SEPARATOR ) |
|
{ |
|
if (indent > 0) |
|
{ |
|
memset(subDirPath, ' ', indent); |
|
memcpy(subDirPath+indent, fullpath, pData-fullpath); |
|
subDirPath[indent+pData-fullpath] = 0; |
|
|
|
m_pFullPathEdit->AddItem(subDirPath, NULL); |
|
} |
|
indent += 2; |
|
} |
|
pData++; |
|
} |
|
} |
|
pBuf += 4; |
|
} |
|
#else |
|
m_pFullPathEdit->AddItem("/", NULL); |
|
|
|
char *pData = fullpath; |
|
int indent = 0; |
|
while (*pData) |
|
{ |
|
if (*pData == '/' && ( pData[1] != '\0' ) ) |
|
{ |
|
if (indent > 0) |
|
{ |
|
memset(subDirPath, ' ', indent); |
|
memcpy(subDirPath+indent, fullpath, pData-fullpath); |
|
subDirPath[indent+pData-fullpath] = 0; |
|
|
|
m_pFullPathEdit->AddItem(subDirPath, NULL); |
|
} |
|
indent += 2; |
|
} |
|
pData++; |
|
} |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Delete self on close |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::OnClose() |
|
{ |
|
s_nLastSortColumn = m_pFileList->GetSortColumn(); |
|
if ( !m_bFileSelected ) |
|
{ |
|
KeyValues *pKeyValues = new KeyValues( "FileSelectionCancelled" ); |
|
PostActionSignal( pKeyValues ); |
|
m_bFileSelected = true; |
|
} |
|
|
|
m_pFileNameEdit->SetText(""); |
|
m_pFileNameEdit->HideMenu(); |
|
|
|
if ( vgui::input()->GetAppModalSurface() == GetVPanel() ) |
|
{ |
|
input()->SetAppModalSurface(NULL); |
|
} |
|
|
|
BaseClass::OnClose(); |
|
} |
|
|
|
void FileOpenDialog::OnFolderUp() |
|
{ |
|
MoveUpFolder(); |
|
OnOpen(); |
|
} |
|
|
|
void FileOpenDialog::OnInputCompleted( KeyValues *data ) |
|
{ |
|
if ( m_hInputDialog.Get() ) |
|
{ |
|
delete m_hInputDialog.Get(); |
|
} |
|
|
|
input()->SetAppModalSurface( m_SaveModal ); |
|
m_SaveModal = 0; |
|
|
|
NewFolder( data->GetString( "text" ) ); |
|
OnOpen(); |
|
} |
|
|
|
void FileOpenDialog::OnInputCanceled() |
|
{ |
|
input()->SetAppModalSurface( m_SaveModal ); |
|
m_SaveModal = 0; |
|
} |
|
|
|
void FileOpenDialog::OnNewFolder() |
|
{ |
|
if ( m_hInputDialog.Get() ) |
|
delete m_hInputDialog.Get(); |
|
|
|
m_hInputDialog = new InputDialog( this, "#FileOpenDialog_NewFolder_InputTitle", "#FileOpenDialog_NewFolderPrompt", "#FileOpenDialog_NewFolder_DefaultName" ); |
|
if ( m_hInputDialog.Get() ) |
|
{ |
|
m_SaveModal = input()->GetAppModalSurface(); |
|
|
|
KeyValues *pContextKeyValues = new KeyValues( "NewFolder" ); |
|
m_hInputDialog->SetSmallCaption( true ); |
|
m_hInputDialog->SetMultiline( false ); |
|
m_hInputDialog->DoModal( pContextKeyValues ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Opens the current file/folder in explorer |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::OnOpenInExplorer() |
|
{ |
|
char pCurrentDirectory[MAX_PATH]; |
|
GetCurrentDirectory( pCurrentDirectory, sizeof(pCurrentDirectory) ); |
|
#if !defined( _X360 ) && defined( WIN32 ) |
|
ShellExecute( NULL, NULL, pCurrentDirectory, NULL, NULL, SW_SHOWNORMAL ); |
|
#elif defined( OSX ) |
|
char szCmd[ MAX_PATH * 2]; |
|
Q_snprintf( szCmd, sizeof(szCmd), "/usr/bin/open \"%s\"", pCurrentDirectory ); |
|
::system( szCmd ); |
|
#elif defined( LINUX ) |
|
char szCmd[ MAX_PATH * 2 ]; |
|
Q_snprintf( szCmd, sizeof(szCmd), "xdg-open \"%s\" &", pCurrentDirectory ); |
|
::system( szCmd ); |
|
#endif |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle for button commands |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::OnCommand(const char *command) |
|
{ |
|
if (!stricmp(command, "Cancel")) |
|
{ |
|
Close(); |
|
} |
|
else |
|
{ |
|
BaseClass::OnCommand(command); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Sets the start directory context (and resets the start directory in the process) |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::SetStartDirectoryContext( const char *pStartDirContext, const char *pDefaultDir ) |
|
{ |
|
bool bUseCurrentDirectory = true; |
|
if ( pStartDirContext ) |
|
{ |
|
m_nStartDirContext = s_StartDirContexts.Find( pStartDirContext ); |
|
if ( m_nStartDirContext == s_StartDirContexts.InvalidIndex() ) |
|
{ |
|
m_nStartDirContext = s_StartDirContexts.Insert( pStartDirContext, pDefaultDir ); |
|
bUseCurrentDirectory = ( pDefaultDir == NULL ); |
|
} |
|
else |
|
{ |
|
bUseCurrentDirectory = false; |
|
} |
|
} |
|
else |
|
{ |
|
m_nStartDirContext = s_StartDirContexts.InvalidIndex(); |
|
} |
|
|
|
if ( !bUseCurrentDirectory ) |
|
{ |
|
SetStartDirectory( s_StartDirContexts[m_nStartDirContext].Get() ); |
|
} |
|
else |
|
{ |
|
// Set our starting path to the current directory |
|
char pLocalPath[255]; |
|
g_pFullFileSystem->GetCurrentDirectory( pLocalPath, 255 ); |
|
SetStartDirectory( pLocalPath ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Set the starting directory of the file search. |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::SetStartDirectory( const char *dir ) |
|
{ |
|
m_pFullPathEdit->SetText(dir); |
|
|
|
// ensure it's validity |
|
ValidatePath(); |
|
|
|
// Store this in the start directory list |
|
if ( m_nStartDirContext != s_StartDirContexts.InvalidIndex() ) |
|
{ |
|
char pDirBuf[MAX_PATH]; |
|
GetCurrentDirectory( pDirBuf, sizeof(pDirBuf) ); |
|
s_StartDirContexts[ m_nStartDirContext ] = pDirBuf; |
|
} |
|
|
|
PopulateDriveList(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Add filters for the drop down combo box |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::AddFilter( const char *filter, const char *filterName, bool bActive, const char *pFilterInfo ) |
|
{ |
|
KeyValues *kv = new KeyValues("item"); |
|
kv->SetString( "filter", filter ); |
|
kv->SetString( "filterinfo", pFilterInfo ); |
|
int itemID = m_pFileTypeCombo->AddItem(filterName, kv); |
|
if ( bActive ) |
|
{ |
|
m_pFileTypeCombo->ActivateItem(itemID); |
|
} |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Activate the dialog |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::DoModal( bool bUnused ) |
|
{ |
|
m_bFileSelected = false; |
|
m_pFileNameEdit->RequestFocus(); |
|
BaseClass::DoModal(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Gets the directory this is currently in |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::GetCurrentDirectory(char *buf, int bufSize) |
|
{ |
|
// get the text from the text entry |
|
m_pFullPathEdit->GetText(buf, bufSize); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Get the last selected file name |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::GetSelectedFileName(char *buf, int bufSize) |
|
{ |
|
m_pFileNameEdit->GetText(buf, bufSize); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Creates a new folder |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::NewFolder( char const *folderName ) |
|
{ |
|
char pCurrentDirectory[MAX_PATH]; |
|
GetCurrentDirectory( pCurrentDirectory, sizeof(pCurrentDirectory) ); |
|
|
|
char pFullPath[MAX_PATH]; |
|
char pNewFolderName[MAX_PATH]; |
|
Q_strncpy( pNewFolderName, folderName, sizeof(pNewFolderName) ); |
|
int i = 2; |
|
do |
|
{ |
|
Q_MakeAbsolutePath( pFullPath, sizeof(pFullPath), pNewFolderName, pCurrentDirectory ); |
|
if ( !g_pFullFileSystem->FileExists( pFullPath, NULL ) && |
|
!g_pFullFileSystem->IsDirectory( pFullPath, NULL ) ) |
|
{ |
|
g_pFullFileSystem->CreateDirHierarchy( pFullPath, NULL ); |
|
m_pFileNameEdit->SetText( pNewFolderName ); |
|
return; |
|
} |
|
|
|
Q_snprintf( pNewFolderName, sizeof(pNewFolderName), "%s%d", folderName, i ); |
|
++i; |
|
} while ( i <= 999 ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Move the directory structure up |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::MoveUpFolder() |
|
{ |
|
char fullpath[MAX_PATH * 4]; |
|
GetCurrentDirectory(fullpath, sizeof(fullpath) - MAX_PATH); |
|
|
|
Q_StripLastDir( fullpath, sizeof( fullpath ) ); |
|
// append a trailing slash |
|
Q_AppendSlash( fullpath, sizeof( fullpath ) ); |
|
|
|
SetStartDirectory(fullpath); |
|
PopulateFileList(); |
|
InvalidateLayout(); |
|
Repaint(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Validate that the current path is valid |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::ValidatePath() |
|
{ |
|
char fullpath[MAX_PATH * 4]; |
|
GetCurrentDirectory(fullpath, sizeof(fullpath) - MAX_PATH); |
|
Q_RemoveDotSlashes( fullpath ); |
|
|
|
// when statting a directory on Windows, you want to include |
|
// the terminal slash exactly when you are statting a root |
|
// directory. PKMN. |
|
#ifdef _WIN32 |
|
if ( Q_strlen( fullpath ) != 3 ) |
|
{ |
|
Q_StripTrailingSlash( fullpath ); |
|
} |
|
#endif |
|
// cleanup the path, we format tabs into the list to make it pretty in the UI |
|
Q_StripPrecedingAndTrailingWhitespace( fullpath ); |
|
|
|
struct _stat buf; |
|
if ( ( 0 == _stat( fullpath, &buf ) ) && |
|
( 0 != ( buf.st_mode & S_IFDIR ) ) ) |
|
{ |
|
Q_AppendSlash( fullpath, sizeof( fullpath ) ); |
|
Q_strncpy(m_szLastPath, fullpath, sizeof(m_szLastPath)); |
|
} |
|
else |
|
{ |
|
// failed to load file, use the previously successful path |
|
} |
|
|
|
m_pFullPathEdit->SetText(m_szLastPath); |
|
m_pFullPathEdit->GetTooltip()->SetText(m_szLastPath); |
|
} |
|
|
|
#ifdef WIN32 |
|
const char *GetAttributesAsString( DWORD dwAttributes ) |
|
{ |
|
static char out[ 256 ]; |
|
out[ 0 ] = 0; |
|
if ( dwAttributes & FILE_ATTRIBUTE_ARCHIVE ) |
|
{ |
|
Q_strncat( out, "A", sizeof( out ), COPY_ALL_CHARACTERS ); |
|
} |
|
if ( dwAttributes & FILE_ATTRIBUTE_COMPRESSED ) |
|
{ |
|
Q_strncat( out, "C", sizeof( out ), COPY_ALL_CHARACTERS ); |
|
} |
|
if ( dwAttributes & FILE_ATTRIBUTE_DIRECTORY ) |
|
{ |
|
Q_strncat( out, "D", sizeof( out ), COPY_ALL_CHARACTERS ); |
|
} |
|
if ( dwAttributes & FILE_ATTRIBUTE_HIDDEN ) |
|
{ |
|
Q_strncat( out, "H", sizeof( out ), COPY_ALL_CHARACTERS ); |
|
} |
|
if ( dwAttributes & FILE_ATTRIBUTE_READONLY ) |
|
{ |
|
Q_strncat( out, "R", sizeof( out ), COPY_ALL_CHARACTERS ); |
|
} |
|
if ( dwAttributes & FILE_ATTRIBUTE_SYSTEM ) |
|
{ |
|
Q_strncat( out, "S", sizeof( out ), COPY_ALL_CHARACTERS ); |
|
} |
|
if ( dwAttributes & FILE_ATTRIBUTE_TEMPORARY ) |
|
{ |
|
Q_strncat( out, "T", sizeof( out ), COPY_ALL_CHARACTERS ); |
|
} |
|
return out; |
|
} |
|
|
|
const char *GetFileTimetamp( FILETIME ft ) |
|
{ |
|
SYSTEMTIME local; |
|
FILETIME localFileTime; |
|
FileTimeToLocalFileTime( &ft, &localFileTime ); |
|
FileTimeToSystemTime( &localFileTime, &local ); |
|
|
|
static char out[ 256 ]; |
|
|
|
bool am = true; |
|
WORD hour = local.wHour; |
|
if ( hour >= 12 ) |
|
{ |
|
am = false; |
|
// 12:42 pm displays as 12:42 pm |
|
// 13:42 pm displays as 1:42 pm |
|
if ( hour > 12 ) |
|
{ |
|
hour -= 12; |
|
} |
|
} |
|
Q_snprintf( out, sizeof( out ), "%d/%02d/%04d %d:%02d %s", |
|
local.wMonth, |
|
local.wDay, |
|
local.wYear, |
|
hour, |
|
local.wMinute, |
|
am ? "AM" : "PM" // TODO: Localize this? |
|
); |
|
return out; |
|
} |
|
#endif |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Fill the filelist with the names of all the files in the current directory |
|
//----------------------------------------------------------------------------- |
|
#define MAX_FILTER_LENGTH 255 |
|
void FileOpenDialog::PopulateFileList() |
|
{ |
|
// clear the current list |
|
m_pFileList->DeleteAllItems(); |
|
|
|
FileFindHandle_t findHandle; |
|
char pszFileModified[64]; |
|
|
|
// get the current directory |
|
char currentDir[MAX_PATH * 4]; |
|
char dir[MAX_PATH * 4]; |
|
char filterList[MAX_FILTER_LENGTH+1]; |
|
GetCurrentDirectory(currentDir, sizeof(dir)); |
|
|
|
KeyValues *combokv = m_pFileTypeCombo->GetActiveItemUserData(); |
|
if (combokv) |
|
{ |
|
Q_strncpy(filterList, combokv->GetString("filter", "*"), MAX_FILTER_LENGTH); |
|
} |
|
else |
|
{ |
|
// add wildcard for search |
|
Q_strncpy(filterList, "*\0", MAX_FILTER_LENGTH); |
|
} |
|
|
|
|
|
char *filterPtr = filterList; |
|
KeyValues *kv = new KeyValues("item"); |
|
|
|
if ( m_DialogType != FOD_SELECT_DIRECTORY ) |
|
{ |
|
while ((filterPtr != NULL) && (*filterPtr != 0)) |
|
{ |
|
// parse the next filter in the list. |
|
char curFilter[MAX_FILTER_LENGTH]; |
|
curFilter[0] = 0; |
|
int i = 0; |
|
while ((filterPtr != NULL) && ((*filterPtr == ',') || (*filterPtr == ';') || (*filterPtr <= ' '))) |
|
{ |
|
++filterPtr; |
|
} |
|
while ((filterPtr != NULL) && (*filterPtr != ',') && (*filterPtr != ';') && (*filterPtr > ' ')) |
|
{ |
|
curFilter[i++] = *(filterPtr++); |
|
} |
|
curFilter[i] = 0; |
|
|
|
if (curFilter[0] == 0) |
|
{ |
|
break; |
|
} |
|
|
|
Q_snprintf( dir, MAX_PATH*4, "%s%s", currentDir, curFilter ); |
|
|
|
// Open the directory and walk it, loading files |
|
const char *pszFileName = g_pFullFileSystem->FindFirst( dir, &findHandle ); |
|
while ( pszFileName ) |
|
{ |
|
if ( !g_pFullFileSystem->FindIsDirectory( findHandle ) |
|
|| !IsOSX() |
|
|| ( IsOSX() && g_pFullFileSystem->FindIsDirectory( findHandle ) && Q_stristr( pszFileName, ".app" ) ) ) |
|
{ |
|
char pFullPath[MAX_PATH]; |
|
Q_snprintf( pFullPath, MAX_PATH, "%s%s", currentDir, pszFileName ); |
|
|
|
// add the file to the list |
|
kv->SetString( "text", pszFileName ); |
|
|
|
kv->SetInt( "image", 1 ); |
|
|
|
IImage *image = surface()->GetIconImageForFullPath( pFullPath ); |
|
|
|
if ( image ) |
|
{ |
|
kv->SetPtr( "iconImage", (void *)image ); |
|
} |
|
|
|
kv->SetInt("imageSelected", 1); |
|
kv->SetInt("directory", 0); |
|
|
|
kv->SetString( "filesize", Q_pretifymem( g_pFullFileSystem->Size( pFullPath ), 0, true ) ); |
|
Q_FixSlashes( pFullPath ); |
|
wchar_t fileType[ 80 ]; |
|
g_pFullFileSystem->GetFileTypeForFullPath( pFullPath, fileType, sizeof( fileType ) ); |
|
kv->SetWString( "type", fileType ); |
|
|
|
kv->SetString( "attributes", g_pFullFileSystem->IsFileWritable( pFullPath )? "" : "R" ); |
|
|
|
time_t fileModified = g_pFullFileSystem->GetFileTime( pFullPath ); |
|
g_pFullFileSystem->FileTimeToString( pszFileModified, sizeof( pszFileModified ), fileModified ); |
|
kv->SetString( "modified", pszFileModified ); |
|
|
|
// kv->SetString( "created", GetFileTimetamp( findData.ftCreationTime ) ); |
|
|
|
m_pFileList->AddItem(kv, 0, false, false); |
|
} |
|
|
|
pszFileName = g_pFullFileSystem->FindNext( findHandle ); |
|
} |
|
g_pFullFileSystem->FindClose( findHandle ); |
|
} |
|
} |
|
|
|
// find all the directories |
|
GetCurrentDirectory( dir, sizeof(dir) ); |
|
Q_strncat(dir, "*", sizeof( dir ), COPY_ALL_CHARACTERS); |
|
|
|
const char *pszFileName = g_pFullFileSystem->FindFirst( dir, &findHandle ); |
|
while ( pszFileName ) |
|
{ |
|
if ( pszFileName[0] != '.' && g_pFullFileSystem->FindIsDirectory( findHandle ) |
|
&& ( !IsOSX() || ( IsOSX() && !Q_stristr( pszFileName, ".app" ) ) ) ) |
|
{ |
|
char pFullPath[MAX_PATH]; |
|
Q_snprintf( pFullPath, MAX_PATH, "%s%s", currentDir, pszFileName ); |
|
|
|
kv->SetString("text", pszFileName ); |
|
kv->SetPtr( "iconImage", (void *)NULL ); |
|
kv->SetInt("image", 2); |
|
kv->SetInt("imageSelected", 3); |
|
kv->SetInt("directory", 1); |
|
|
|
kv->SetString( "filesize", "" ); |
|
kv->SetString( "type", "#FileOpenDialog_FileType_Folder" ); |
|
|
|
kv->SetString( "attributes", g_pFullFileSystem->IsFileWritable( pFullPath )? "" : "R" ); |
|
|
|
time_t fileModified = g_pFullFileSystem->GetFileTime( pFullPath ); |
|
g_pFullFileSystem->FileTimeToString( pszFileModified, sizeof( pszFileModified ), fileModified ); |
|
kv->SetString( "modified", pszFileModified ); |
|
|
|
// kv->SetString( "created", GetFileTimetamp( findData.ftCreationTime ) ); |
|
|
|
m_pFileList->AddItem( kv, 0, false, false ); |
|
} |
|
|
|
pszFileName = g_pFullFileSystem->FindNext( findHandle ); |
|
} |
|
g_pFullFileSystem->FindClose( findHandle ); |
|
|
|
kv->deleteThis(); |
|
m_pFileList->SortList(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Does the specified extension match something in the filter list? |
|
//----------------------------------------------------------------------------- |
|
bool FileOpenDialog::ExtensionMatchesFilter( const char *pExt ) |
|
{ |
|
KeyValues *combokv = m_pFileTypeCombo->GetActiveItemUserData(); |
|
if ( !combokv ) |
|
return true; |
|
|
|
char filterList[MAX_FILTER_LENGTH+1]; |
|
Q_strncpy( filterList, combokv->GetString("filter", "*"), MAX_FILTER_LENGTH ); |
|
|
|
char *filterPtr = filterList; |
|
while ((filterPtr != NULL) && (*filterPtr != 0)) |
|
{ |
|
// parse the next filter in the list. |
|
char curFilter[MAX_FILTER_LENGTH]; |
|
curFilter[0] = 0; |
|
int i = 0; |
|
while ((filterPtr != NULL) && ((*filterPtr == ',') || (*filterPtr == ';') || (*filterPtr <= ' '))) |
|
{ |
|
++filterPtr; |
|
} |
|
while ((filterPtr != NULL) && (*filterPtr != ',') && (*filterPtr != ';') && (*filterPtr > ' ')) |
|
{ |
|
curFilter[i++] = *(filterPtr++); |
|
} |
|
curFilter[i] = 0; |
|
|
|
if (curFilter[0] == 0) |
|
break; |
|
|
|
if ( !Q_stricmp( curFilter, "*" ) || !Q_stricmp( curFilter, "*.*" ) ) |
|
return true; |
|
|
|
// FIXME: This isn't exactly right, but tough cookies; |
|
// it assumes the first two characters of the filter are *. |
|
Assert( curFilter[0] == '*' && curFilter[1] == '.' ); |
|
if ( !Q_stricmp( &curFilter[2], pExt ) ) |
|
return true; |
|
} |
|
|
|
return false; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Choose the first non *.* filter in the filter list |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::ChooseExtension( char *pExt, int nBufLen ) |
|
{ |
|
pExt[0] = 0; |
|
|
|
KeyValues *combokv = m_pFileTypeCombo->GetActiveItemUserData(); |
|
if ( !combokv ) |
|
return; |
|
|
|
char filterList[MAX_FILTER_LENGTH+1]; |
|
Q_strncpy( filterList, combokv->GetString("filter", "*"), MAX_FILTER_LENGTH ); |
|
|
|
char *filterPtr = filterList; |
|
while ((filterPtr != NULL) && (*filterPtr != 0)) |
|
{ |
|
// parse the next filter in the list. |
|
char curFilter[MAX_FILTER_LENGTH]; |
|
curFilter[0] = 0; |
|
int i = 0; |
|
while ((filterPtr != NULL) && ((*filterPtr == ',') || (*filterPtr == ';') || (*filterPtr <= ' '))) |
|
{ |
|
++filterPtr; |
|
} |
|
while ((filterPtr != NULL) && (*filterPtr != ',') && (*filterPtr != ';') && (*filterPtr > ' ')) |
|
{ |
|
curFilter[i++] = *(filterPtr++); |
|
} |
|
curFilter[i] = 0; |
|
|
|
if (curFilter[0] == 0) |
|
break; |
|
|
|
if ( !Q_stricmp( curFilter, "*" ) || !Q_stricmp( curFilter, "*.*" ) ) |
|
continue; |
|
|
|
// FIXME: This isn't exactly right, but tough cookies; |
|
// it assumes the first two characters of the filter are *. |
|
Assert( curFilter[0] == '*' && curFilter[1] == '.' ); |
|
Q_strncpy( pExt, &curFilter[1], nBufLen ); |
|
break; |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Saves the file to the start dir context |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::SaveFileToStartDirContext( const char *pFullPath ) |
|
{ |
|
if ( m_nStartDirContext == s_StartDirContexts.InvalidIndex() ) |
|
return; |
|
|
|
char pPath[MAX_PATH]; |
|
pPath[0] = 0; |
|
Q_ExtractFilePath( pFullPath, pPath, sizeof(pPath) ); |
|
s_StartDirContexts[ m_nStartDirContext ] = pPath; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Posts a file selected message |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::PostFileSelectedMessage( const char *pFileName ) |
|
{ |
|
m_bFileSelected = true; |
|
|
|
// open the file! |
|
KeyValues *pKeyValues = new KeyValues( "FileSelected", "fullpath", pFileName ); |
|
KeyValues *pFilterKeys = m_pFileTypeCombo->GetActiveItemUserData(); |
|
const char *pFilterInfo = pFilterKeys ? pFilterKeys->GetString( "filterinfo", NULL ) : NULL; |
|
if ( pFilterInfo ) |
|
{ |
|
pKeyValues->SetString( "filterinfo", pFilterInfo ); |
|
} |
|
if ( m_pContextKeyValues ) |
|
{ |
|
pKeyValues->AddSubKey( m_pContextKeyValues ); |
|
m_pContextKeyValues = NULL; |
|
} |
|
PostActionSignal( pKeyValues ); |
|
CloseModal(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Selects the current folder |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::OnSelectFolder() |
|
{ |
|
ValidatePath(); |
|
|
|
// construct a file path |
|
char pFileName[MAX_PATH]; |
|
GetSelectedFileName( pFileName, sizeof( pFileName ) ); |
|
|
|
Q_StripTrailingSlash( pFileName ); |
|
|
|
if ( !stricmp(pFileName, "..") ) |
|
{ |
|
MoveUpFolder(); |
|
|
|
// clear the name text |
|
m_pFileNameEdit->SetText(""); |
|
return; |
|
} |
|
|
|
if ( !stricmp(pFileName, ".") ) |
|
{ |
|
// clear the name text |
|
m_pFileNameEdit->SetText(""); |
|
return; |
|
} |
|
|
|
// Compute the full path |
|
char pFullPath[MAX_PATH * 4]; |
|
if ( !Q_IsAbsolutePath( pFileName ) ) |
|
{ |
|
GetCurrentDirectory(pFullPath, sizeof(pFullPath) - MAX_PATH); |
|
strcat( pFullPath, pFileName ); |
|
if ( !pFileName[0] ) |
|
{ |
|
Q_StripTrailingSlash( pFullPath ); |
|
} |
|
} |
|
else |
|
{ |
|
Q_strncpy( pFullPath, pFileName, sizeof(pFullPath) ); |
|
} |
|
|
|
if ( g_pFullFileSystem->FileExists( pFullPath ) ) |
|
{ |
|
// open the file! |
|
SaveFileToStartDirContext( pFullPath ); |
|
PostFileSelectedMessage( pFullPath ); |
|
return; |
|
} |
|
|
|
PopulateDriveList(); |
|
PopulateFileList(); |
|
InvalidateLayout(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle the open button being pressed |
|
// checks on what has changed and acts accordingly |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::OnOpen() |
|
{ |
|
ValidatePath(); |
|
|
|
// construct a file path |
|
char pFileName[MAX_PATH]; |
|
GetSelectedFileName( pFileName, sizeof( pFileName ) ); |
|
|
|
if( !pFileName[0] ) |
|
return; |
|
|
|
int nLen = Q_strlen( pFileName ); |
|
bool bSpecifiedDirectory = ( pFileName[nLen-1] == '/' || pFileName[nLen-1] == '\\' ) && (!IsOSX() || ( IsOSX() && !Q_stristr( pFileName, ".app" ) ) ); |
|
Q_StripTrailingSlash( pFileName ); |
|
|
|
if ( !stricmp(pFileName, "..") ) |
|
{ |
|
MoveUpFolder(); |
|
|
|
// clear the name text |
|
m_pFileNameEdit->SetText(""); |
|
return; |
|
} |
|
|
|
if ( !stricmp(pFileName, ".") ) |
|
{ |
|
// clear the name text |
|
m_pFileNameEdit->SetText(""); |
|
return; |
|
} |
|
|
|
// Compute the full path |
|
char pFullPath[MAX_PATH * 4]; |
|
if ( !Q_IsAbsolutePath( pFileName ) ) |
|
{ |
|
GetCurrentDirectory(pFullPath, sizeof(pFullPath) - MAX_PATH); |
|
Q_AppendSlash( pFullPath, sizeof( pFullPath ) ); |
|
strcat(pFullPath, pFileName); |
|
if ( !pFileName[0] ) |
|
{ |
|
Q_StripTrailingSlash( pFullPath ); |
|
} |
|
} |
|
else |
|
{ |
|
Q_strncpy( pFullPath, pFileName, sizeof(pFullPath) ); |
|
} |
|
|
|
Q_StripTrailingSlash( pFullPath ); |
|
|
|
// when statting a directory on Windows, you want to include |
|
// the terminal slash exactly when you are statting a root |
|
// directory. PKMN. |
|
#ifdef _WIN32 |
|
if ( Q_strlen( pFullPath ) == 2 ) |
|
{ |
|
Q_AppendSlash( pFullPath, Q_ARRAYSIZE( pFullPath ) ); |
|
} |
|
#endif |
|
|
|
|
|
// If the name specified is a directory, then change directory |
|
if ( g_pFullFileSystem->IsDirectory( pFullPath, NULL ) && |
|
( !IsOSX() || ( IsOSX() && !Q_stristr( pFullPath, ".app" ) ) ) ) |
|
{ |
|
// it's a directory; change to the specified directory |
|
if ( !bSpecifiedDirectory ) |
|
{ |
|
Q_AppendSlash( pFullPath, Q_ARRAYSIZE( pFullPath ) ); |
|
} |
|
SetStartDirectory( pFullPath ); |
|
|
|
// clear the name text |
|
m_pFileNameEdit->SetText(""); |
|
m_pFileNameEdit->HideMenu(); |
|
|
|
PopulateDriveList(); |
|
PopulateFileList(); |
|
InvalidateLayout(); |
|
return; |
|
} |
|
else if ( bSpecifiedDirectory ) |
|
{ |
|
PopulateDriveList(); |
|
PopulateFileList(); |
|
InvalidateLayout(); |
|
return; |
|
} |
|
|
|
// Append suffix of the first filter that isn't *.* |
|
char extension[512]; |
|
Q_ExtractFileExtension( pFullPath, extension, sizeof(extension) ); |
|
if ( !ExtensionMatchesFilter( extension ) ) |
|
{ |
|
ChooseExtension( extension, sizeof(extension) ); |
|
Q_SetExtension( pFullPath, extension, sizeof(pFullPath) ); |
|
} |
|
|
|
if ( g_pFullFileSystem->FileExists( pFullPath ) ) |
|
{ |
|
// open the file! |
|
SaveFileToStartDirContext( pFullPath ); |
|
PostFileSelectedMessage( pFullPath ); |
|
return; |
|
} |
|
|
|
// file not found |
|
if ( ( m_DialogType == FOD_SAVE ) && pFileName[0] ) |
|
{ |
|
// open the file! |
|
SaveFileToStartDirContext( pFullPath ); |
|
PostFileSelectedMessage( pFullPath ); |
|
return; |
|
} |
|
|
|
PopulateDriveList(); |
|
PopulateFileList(); |
|
InvalidateLayout(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: using the file edit box as a prefix, create a menu of all possible files |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::PopulateFileNameCompletion() |
|
{ |
|
char buf[80]; |
|
m_pFileNameEdit->GetText(buf, 80); |
|
wchar_t wbuf[80]; |
|
m_pFileNameEdit->GetText(wbuf, 80); |
|
int bufLen = wcslen(wbuf); |
|
|
|
// delete all items before we check if there's even a string |
|
m_pFileNameEdit->DeleteAllItems(); |
|
|
|
// no string at all - don't show even bother showing it |
|
if (bufLen == 0) |
|
{ |
|
m_pFileNameEdit->HideMenu(); |
|
return; |
|
} |
|
|
|
// what files use current string as a prefix? |
|
int nCount = m_pFileList->GetItemCount(); |
|
int i; |
|
for ( i = 0 ; i < nCount ; i++ ) |
|
{ |
|
KeyValues *kv = m_pFileList->GetItem(m_pFileList->GetItemIDFromRow(i)); |
|
const wchar_t *wszString = kv->GetWString("text"); |
|
if ( !_wcsnicmp(wbuf, wszString, bufLen) ) |
|
{ |
|
m_pFileNameEdit->AddItem(wszString, NULL); |
|
} |
|
} |
|
|
|
// if there are any items - show the menu |
|
if ( m_pFileNameEdit->GetItemCount() > 0 ) |
|
{ |
|
m_pFileNameEdit->ShowMenu(); |
|
} |
|
else |
|
{ |
|
m_pFileNameEdit->HideMenu(); |
|
} |
|
|
|
m_pFileNameEdit->InvalidateLayout(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle an item in the list being selected |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::OnItemSelected() |
|
{ |
|
// make sure only one item is selected |
|
if (m_pFileList->GetSelectedItemsCount() != 1) |
|
{ |
|
m_pFileNameEdit->SetText(""); |
|
} |
|
else |
|
{ |
|
// put the file name into the text edit box |
|
KeyValues *data = m_pFileList->GetItem(m_pFileList->GetSelectedItem(0)); |
|
m_pFileNameEdit->SetText(data->GetString("text")); |
|
} |
|
|
|
InvalidateLayout(); |
|
} |
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Handle an item in the Drive combo box being selected |
|
//----------------------------------------------------------------------------- |
|
void FileOpenDialog::OnTextChanged(KeyValues *kv) |
|
{ |
|
Panel *pPanel = (Panel *) kv->GetPtr("panel", NULL); |
|
|
|
// first check which control had its text changed! |
|
if (pPanel == m_pFullPathEdit) |
|
{ |
|
m_pFileNameEdit->HideMenu(); |
|
m_pFileNameEdit->SetText(""); |
|
OnOpen(); |
|
} |
|
else if (pPanel == m_pFileNameEdit) |
|
{ |
|
PopulateFileNameCompletion(); |
|
} |
|
else if (pPanel == m_pFileTypeCombo) |
|
{ |
|
m_pFileNameEdit->HideMenu(); |
|
PopulateFileList(); |
|
} |
|
}
|
|
|