source-engine/vgui2/vgui_controls/DirectorySelectDialog.cpp

594 lines
18 KiB
C++
Raw Normal View History

2020-04-22 16:56:21 +00:00
//========= Copyright Valve Corporation, All rights reserved. ============//
//
// Purpose:
//
//=============================================================================//
#define PROTECTED_THINGS_DISABLE
#include <vgui_controls/Button.h>
#include <vgui_controls/ComboBox.h>
#include <vgui_controls/DirectorySelectDialog.h>
#include <vgui_controls/TreeView.h>
#include <vgui_controls/ImageList.h>
#include <vgui_controls/MessageBox.h>
#include <vgui/Cursor.h>
#include <KeyValues.h>
#include <vgui/IInput.h>
#include <vgui/ISurface.h>
#include <vgui/ISystem.h>
#include <filesystem.h>
#ifdef WIN32
#include <direct.h>
#include <stdio.h>
#include <io.h>
#endif
#include <sys/types.h>
#include <sys/stat.h>
// memdbgon must be the last include file in a .cpp file!!!
#include <tier0/memdbgon.h>
using namespace vgui;
DirectoryTreeView::DirectoryTreeView(DirectorySelectDialog *parent, const char *name) : TreeView(parent, name)
{
m_pParent = parent;
}
void DirectoryTreeView::GenerateChildrenOfNode(int itemIndex)
{
m_pParent->GenerateChildrenOfDirectoryNode(itemIndex);
}
//-----------------------------------------------------------------------------
// Purpose: Used to prompt the user to create a directory
//-----------------------------------------------------------------------------
class CreateDirectoryDialog : public Frame
{
DECLARE_CLASS_SIMPLE(CreateDirectoryDialog, Frame);
public:
CreateDirectoryDialog(Panel *parent, const char *defaultCreateDirName) : BaseClass(parent, NULL)
{
SetSize(320, 100);
SetSizeable(false);
SetTitle("Choose directory name", false);
MoveToCenterOfScreen();
m_pOKButton = new Button(this, "OKButton", "#vgui_ok");
m_pCancelButton = new Button(this, "OKButton", "#vgui_cancel");
m_pNameEntry = new TextEntry(this, "NameEntry");
m_pOKButton->SetCommand("OK");
m_pCancelButton->SetCommand("Close");
m_pNameEntry->SetText(defaultCreateDirName);
m_pNameEntry->RequestFocus();
m_pNameEntry->SelectAllText(true);
// If some other window was hogging the input focus, then we have to hog it or else we'll never get input.
m_PrevAppFocusPanel = vgui::input()->GetAppModalSurface();
if ( m_PrevAppFocusPanel )
vgui::input()->SetAppModalSurface( GetVPanel() );
}
~CreateDirectoryDialog()
{
if ( m_PrevAppFocusPanel )
vgui::input()->SetAppModalSurface( m_PrevAppFocusPanel );
}
virtual void PerformLayout()
{
BaseClass::PerformLayout();
m_pNameEntry->SetBounds(24, 32, GetWide() - 48, 24);
m_pOKButton->SetBounds(GetWide() - 176, 64, 72, 24);
m_pCancelButton->SetBounds(GetWide() - 94, 64, 72, 24);
}
virtual void OnCommand(const char *command)
{
if (!stricmp(command, "OK"))
{
PostActionSignal(new KeyValues("CreateDirectory", "dir", GetControlString("NameEntry")));
Close();
}
else
{
BaseClass::OnCommand(command);
}
}
virtual void OnClose()
{
BaseClass::OnClose();
MarkForDeletion();
}
private:
vgui::Button *m_pOKButton;
vgui::Button *m_pCancelButton;
vgui::TextEntry *m_pNameEntry;
vgui::VPANEL m_PrevAppFocusPanel;
};
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
DirectorySelectDialog::DirectorySelectDialog(vgui::Panel *parent, const char *title) : Frame(parent, NULL)
{
SetTitle(title, true);
SetSize(320, 360);
SetMinimumSize(300, 240);
m_szCurrentDir[0] = 0;
m_szDefaultCreateDirName[0] = 0;
m_pDirTree = new DirectoryTreeView(this, "DirTree");
m_pDriveCombo = new ComboBox(this, "DriveCombo", 6, false);
m_pCancelButton = new Button(this, "CancelButton", "#VGui_Cancel");
m_pSelectButton = new Button(this, "SelectButton", "#VGui_Select");
m_pCreateButton = new Button(this, "CreateButton", "#VGui_CreateFolder");
m_pCancelButton->SetCommand("Cancel");
m_pSelectButton->SetCommand("Select");
m_pCreateButton->SetCommand("Create");
}
//-----------------------------------------------------------------------------
// Purpose: lays out controls
//-----------------------------------------------------------------------------
void DirectorySelectDialog::PerformLayout()
{
BaseClass::PerformLayout();
// lay out all the controls
m_pDriveCombo->SetBounds(24, 30, GetWide() - 48, 24);
m_pDirTree->SetBounds(24, 64, GetWide() - 48, GetTall() - 128);
m_pCreateButton->SetBounds(24, GetTall() - 48, 104, 24);
m_pSelectButton->SetBounds(GetWide() - 172, GetTall() - 48, 72, 24);
m_pCancelButton->SetBounds(GetWide() - 96, GetTall() - 48, 72, 24);
}
//-----------------------------------------------------------------------------
// Purpose: lays out controls
//-----------------------------------------------------------------------------
void DirectorySelectDialog::ApplySchemeSettings(IScheme *pScheme)
{
ImageList *imageList = new ImageList(false);
imageList->AddImage(scheme()->GetImage("Resource/icon_folder", false));
imageList->AddImage(scheme()->GetImage("Resource/icon_folder_selected", false));
m_pDirTree->SetImageList(imageList, true);
BaseClass::ApplySchemeSettings(pScheme);
}
//-----------------------------------------------------------------------------
// Purpose: Move the start string forward until we hit a slash and return the
// the first character past the trailing slash
//-----------------------------------------------------------------------------
inline const char *MoveToNextSubDir( const char *pStart, int *nCount )
{
int nMoved = 0;
// Move past pre-pended slash
if ( pStart[nMoved] == '\\' )
{
nMoved++;
}
// Move past the current block of text until we've hit the next path seperator (or end)
while ( pStart[nMoved] != '\\' && pStart[nMoved] != '\0' )
{
nMoved++;
}
// Move past trailing slash
if ( pStart[nMoved] == '\\' )
{
nMoved++;
}
// Give back a count if they've supplied a pointer
if ( nCount != NULL )
{
*nCount = nMoved;
}
// The beginning of the next string, past slash
return (pStart+nMoved);
}
//-----------------------------------------------------------------------------
// Purpose: Walk through our directory structure given a path as our guide, while expanding
// and populating the nodes of the tree view to match
// Input : *path - path (with drive letter) to show
//-----------------------------------------------------------------------------
void DirectorySelectDialog::ExpandTreeToPath( const char *lpszPath, bool bSelectFinalDirectory /*= true*/ )
{
// Make sure our slashes are correct!
char workPath[MAX_PATH];
Q_strncpy( workPath, lpszPath, sizeof(workPath) );
Q_FixSlashes( workPath );
// Set us to the work drive
SetStartDirectory( workPath );
// Check that the path is valid
if ( workPath[0] == '\0' || DoesDirectoryHaveSubdirectories( m_szCurrentDrive, "" ) == false )
{
// Failing, start in C:
SetStartDirectory( "C:\\" );
}
// Start at the root of our tree
int nItemIndex = m_pDirTree->GetRootItemIndex();
// Move past the drive letter to the first subdir
int nPathPos = 0;
const char *lpszSubDirName = MoveToNextSubDir( workPath, &nPathPos );
const char *lpszLastSubDirName = NULL;
int nPathIncr = 0;
char subDirName[MAX_PATH];
// While there are subdirectory names present, expand and populate the tree with their subdirectories
while ( lpszSubDirName[0] != '\0' )
{
// Move our string pointer forward while keeping where our last subdir started off
lpszLastSubDirName = lpszSubDirName;
lpszSubDirName = MoveToNextSubDir( lpszSubDirName, &nPathIncr );
// Get the span between the last subdir and the new one
Q_StrLeft( lpszLastSubDirName, nPathIncr, subDirName, sizeof(subDirName) );
Q_StripTrailingSlash( subDirName );
// Increment where we are in the string for use later
nPathPos += nPathIncr;
// Run through the list and expand to our currently selected directory
for ( int i = 0; i < m_pDirTree->GetNumChildren( nItemIndex ); i++ )
{
// Get the child and data for it
int nChild = m_pDirTree->GetChild( nItemIndex, i );
KeyValues *pValues = m_pDirTree->GetItemData( nChild );
// See if this matches
if ( Q_stricmp( pValues->GetString( "Text" ), subDirName ) == 0 )
{
// This is the new root item
nItemIndex = nChild;
// Get the full path (starting from the drive letter) up to our current subdir
Q_strncpy( subDirName, workPath, nPathPos );
Q_AppendSlash( subDirName, sizeof(subDirName) );
// Expand the tree node and populate its subdirs for our next iteration
ExpandTreeNode( subDirName, nItemIndex );
break;
}
}
}
// Select our last directory if we've been asked to (and it's valid)
if ( bSelectFinalDirectory && m_pDirTree->IsItemIDValid( nItemIndex ) )
{
// If we don't call this once before selecting an item, the tree will not be properly expanded
// before it calculates how to show the selected item in the view
PerformLayout();
// Select that item
m_pDirTree->AddSelectedItem( nItemIndex, true );
}
}
//-----------------------------------------------------------------------------
// Purpose: sets where it should start searching
//-----------------------------------------------------------------------------
void DirectorySelectDialog::SetStartDirectory(const char *path)
{
strncpy(m_szCurrentDir, path, sizeof(m_szCurrentDir));
strncpy(m_szCurrentDrive, path, sizeof(m_szCurrentDrive));
m_szCurrentDrive[sizeof(m_szCurrentDrive) - 1] = 0;
char *firstSlash = strstr(m_szCurrentDrive, "\\");
if (firstSlash)
{
firstSlash[1] = 0;
}
BuildDirTree();
BuildDriveChoices();
// update state of create directory button
int selectedIndex = m_pDirTree->GetFirstSelectedItem();
if (m_pDirTree->IsItemIDValid(selectedIndex))
{
m_pCreateButton->SetEnabled(true);
}
else
{
m_pCreateButton->SetEnabled(false);
}
}
//-----------------------------------------------------------------------------
// Purpose: sets what name should show up by default in the create directory dialog
//-----------------------------------------------------------------------------
void DirectorySelectDialog::SetDefaultCreateDirectoryName(const char *defaultCreateDirName)
{
strncpy(m_szDefaultCreateDirName, defaultCreateDirName, sizeof(m_szDefaultCreateDirName));
m_szDefaultCreateDirName[sizeof(m_szDefaultCreateDirName) - 1] = 0;
}
//-----------------------------------------------------------------------------
// Purpose: opens the dialog
//-----------------------------------------------------------------------------
void DirectorySelectDialog::DoModal()
{
input()->SetAppModalSurface(GetVPanel());
BaseClass::Activate();
MoveToCenterOfScreen();
}
//-----------------------------------------------------------------------------
// Purpose: Builds drive choices
//-----------------------------------------------------------------------------
void DirectorySelectDialog::BuildDriveChoices()
{
m_pDriveCombo->DeleteAllItems();
char drives[256] = { 0 };
int len = system()->GetAvailableDrives(drives, sizeof(drives));
char *pBuf = drives;
KeyValues *kv = new KeyValues("drive");
for (int i = 0; i < len / 4; i++)
{
kv->SetString("drive", pBuf);
int itemID = m_pDriveCombo->AddItem(pBuf, kv);
if (!stricmp(pBuf, m_szCurrentDrive))
{
m_pDriveCombo->ActivateItem(itemID);
}
pBuf += 4;
}
kv->deleteThis();
}
//-----------------------------------------------------------------------------
// Purpose: Builds the base tree directory
//-----------------------------------------------------------------------------
void DirectorySelectDialog::BuildDirTree()
{
// clear current tree
m_pDirTree->RemoveAll();
// add in a root
int rootIndex = m_pDirTree->AddItem(new KeyValues("root", "Text", m_szCurrentDrive), -1);
// build first level of the tree
ExpandTreeNode(m_szCurrentDrive, rootIndex);
// start the root expanded
m_pDirTree->ExpandItem(rootIndex, true);
}
//-----------------------------------------------------------------------------
// Purpose: expands a path
//-----------------------------------------------------------------------------
void DirectorySelectDialog::ExpandTreeNode(const char *path, int parentNodeIndex)
{
// set the small wait cursor
surface()->SetCursor(dc_waitarrow);
// get all the subfolders of the current drive
char searchString[512];
sprintf(searchString, "%s*.*", path);
FileFindHandle_t h;
const char *pFileName = g_pFullFileSystem->FindFirstEx( searchString, NULL, &h );
for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( h ) )
{
if ( !Q_stricmp( pFileName, ".." ) || !Q_stricmp( pFileName, "." ) )
continue;
KeyValues *kv = new KeyValues("item");
kv->SetString("Text", pFileName);
// set the folder image
kv->SetInt("Image", 1);
kv->SetInt("SelectedImage", 1);
kv->SetInt("Expand", DoesDirectoryHaveSubdirectories(path, pFileName));
m_pDirTree->AddItem(kv, parentNodeIndex);
}
g_pFullFileSystem->FindClose( h );
}
//-----------------------------------------------------------------------------
// Purpose:
//-----------------------------------------------------------------------------
bool DirectorySelectDialog::DoesDirectoryHaveSubdirectories(const char *path, const char *dir)
{
char searchString[512];
sprintf(searchString, "%s%s\\*.*", path, dir);
FileFindHandle_t h;
const char *pFileName = g_pFullFileSystem->FindFirstEx( searchString, NULL, &h );
for ( ; pFileName; pFileName = g_pFullFileSystem->FindNext( h ) )
{
char szFullPath[ MAX_PATH ];
Q_snprintf( szFullPath, sizeof(szFullPath), "%s\\%s", path, pFileName );
Q_FixSlashes( szFullPath );
if ( g_pFullFileSystem->IsDirectory( szFullPath ) )
{
g_pFullFileSystem->FindClose( h );
return true;
}
}
g_pFullFileSystem->FindClose( h );
return false;
}
//-----------------------------------------------------------------------------
// Purpose: Generates the children for the specified node
//-----------------------------------------------------------------------------
void DirectorySelectDialog::GenerateChildrenOfDirectoryNode(int nodeIndex)
{
// generate path
char path[512];
GenerateFullPathForNode(nodeIndex, path, sizeof(path));
// expand out
ExpandTreeNode(path, nodeIndex);
}
//-----------------------------------------------------------------------------
// Purpose: creates the full path for a node
//-----------------------------------------------------------------------------
void DirectorySelectDialog::GenerateFullPathForNode(int nodeIndex, char *path, int pathBufferSize)
{
// get all the nodes
CUtlLinkedList<int, int> nodes;
nodes.AddToTail(nodeIndex);
int parentIndex = nodeIndex;
while (1)
{
parentIndex = m_pDirTree->GetItemParent(parentIndex);
if (parentIndex == -1)
break;
nodes.AddToHead(parentIndex);
}
// walk the nodes, adding to the path
path[0] = 0;
bool bFirst = true;
FOR_EACH_LL( nodes, i )
{
KeyValues *kv = m_pDirTree->GetItemData( nodes[i] );
strcat(path, kv->GetString("Text"));
if (!bFirst)
{
strcat(path, "\\");
}
bFirst = false;
}
}
//-----------------------------------------------------------------------------
// Purpose: Handles combo box changes
//-----------------------------------------------------------------------------
void DirectorySelectDialog::OnTextChanged()
{
KeyValues *kv = m_pDriveCombo->GetActiveItemUserData();
if (!kv)
return;
const char *newDrive = kv->GetString("drive");
if (stricmp(newDrive, m_szCurrentDrive))
{
// drive changed, reset
SetStartDirectory(newDrive);
}
}
//-----------------------------------------------------------------------------
// Purpose: creates a directory
//-----------------------------------------------------------------------------
void DirectorySelectDialog::OnCreateDirectory(const char *dir)
{
int selectedIndex = m_pDirTree->GetFirstSelectedItem();
if (m_pDirTree->IsItemIDValid(selectedIndex))
{
char fullPath[512];
GenerateFullPathForNode(selectedIndex, fullPath, sizeof(fullPath));
// create the new directory underneath
strcat(fullPath, dir);
if (_mkdir(fullPath) == 0)
{
// add new path to tree view
KeyValues *kv = new KeyValues("item");
kv->SetString("Text", dir);
// set the folder image
kv->SetInt("Image", 1);
kv->SetInt("SelectedImage", 1);
int itemID = m_pDirTree->AddItem(kv, selectedIndex);
// select the item
m_pDirTree->AddSelectedItem( itemID, true );
}
else
{
// print error message
MessageBox *box = new MessageBox("#vgui_CreateDirectoryFail_Title", "#vgui_CreateDirectoryFail_Info");
box->DoModal(this);
}
}
}
//-----------------------------------------------------------------------------
// Purpose: dialog closes
//-----------------------------------------------------------------------------
void DirectorySelectDialog::OnClose()
{
BaseClass::OnClose();
MarkForDeletion();
}
//-----------------------------------------------------------------------------
// Purpose: handles button commands
//-----------------------------------------------------------------------------
void DirectorySelectDialog::OnCommand(const char *command)
{
if (!stricmp(command, "Cancel"))
{
Close();
}
else if (!stricmp(command, "Select"))
{
// path selected
int selectedIndex = m_pDirTree->GetFirstSelectedItem();
if (m_pDirTree->IsItemIDValid(selectedIndex))
{
char fullPath[512];
GenerateFullPathForNode(selectedIndex, fullPath, sizeof(fullPath));
PostActionSignal(new KeyValues("DirectorySelected", "dir", fullPath));
Close();
}
}
else if (!stricmp(command, "Create"))
{
int selectedIndex = m_pDirTree->GetFirstSelectedItem();
if (m_pDirTree->IsItemIDValid(selectedIndex))
{
CreateDirectoryDialog *dlg = new CreateDirectoryDialog(this, m_szDefaultCreateDirName);
dlg->AddActionSignalTarget(this);
dlg->Activate();
}
}
else
{
BaseClass::OnCommand(command);
}
}
//-----------------------------------------------------------------------------
// Purpose: Update the text in the combo
//-----------------------------------------------------------------------------
void DirectorySelectDialog::OnTreeViewItemSelected()
{
int selectedIndex = m_pDirTree->GetFirstSelectedItem();
if (!m_pDirTree->IsItemIDValid(selectedIndex))
{
m_pCreateButton->SetEnabled(false);
return;
}
m_pCreateButton->SetEnabled(true);
// build the string
char fullPath[512];
GenerateFullPathForNode(selectedIndex, fullPath, sizeof(fullPath));
int itemID = m_pDriveCombo->GetActiveItem();
m_pDriveCombo->UpdateItem(itemID, fullPath, NULL);
m_pDriveCombo->SetText(fullPath);
}