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.
570 lines
17 KiB
570 lines
17 KiB
//========= Copyright Valve Corporation, All rights reserved. ============// |
|
// |
|
// Purpose: Contains a list of files, determines their perforce status |
|
// |
|
// $NoKeywords: $ |
|
//===========================================================================// |
|
|
|
#include <vgui_controls/PerforceFileList.h> |
|
#include <vgui_controls/ListPanel.h> |
|
#include <vgui_controls/Label.h> |
|
#include <vgui_controls/ImageList.h> |
|
#include "tier1/KeyValues.h" |
|
#include <vgui/ISurface.h> |
|
#include "filesystem.h" |
|
#include "p4lib/ip4.h" |
|
#include "tier2/tier2.h" |
|
|
|
// memdbgon must be the last include file in a .cpp file!!! |
|
#include <tier0/memdbgon.h> |
|
|
|
|
|
using namespace vgui; |
|
|
|
|
|
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 ListFileSizeSortFunc(ListPanel *pPanel, const ListPanelItem &item1, const ListPanelItem &item2 ) |
|
{ |
|
return ListBaseIntegerSortFunc( pPanel, item1, item2, "filesizeint" ); |
|
} |
|
|
|
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" ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Dictionary of start dir contexts |
|
//----------------------------------------------------------------------------- |
|
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", "#PerforceFileList_Col_Name", 175, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileNameSortFunc , Label::a_west }, |
|
{ "type", "#PerforceFileList_Col_Type", 150, 20, 10000, 0, &ListFileTypeSortFunc , Label::a_west }, |
|
{ "in_perforce", "#PerforceFileList_Col_InPerforce", 50, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileAttributesSortFunc , Label::a_west }, |
|
{ "synched", "#PerforceFileList_Col_Synched", 50, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileAttributesSortFunc , Label::a_west }, |
|
{ "checked_out", "#PerforceFileList_Col_Checked_Out", 50, 20, 10000, ListPanel::COLUMN_UNHIDABLE, &ListFileAttributesSortFunc , Label::a_west }, |
|
{ "attributes", "#PerforceFileList_Col_Attributes", 50, 20, 10000, ListPanel::COLUMN_HIDDEN, &ListFileAttributesSortFunc , Label::a_west }, |
|
}; |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Constructor |
|
//----------------------------------------------------------------------------- |
|
PerforceFileList::PerforceFileList( Panel *pParent, const char *pPanelName ) : |
|
BaseClass( pParent, pPanelName ) |
|
{ |
|
SetMultiselectEnabled( false ); |
|
m_bShowDeletedFiles = false; |
|
|
|
// list panel |
|
for ( int i = 0; i < ARRAYSIZE( g_ColInfo ); ++i ) |
|
{ |
|
const ColumnInfo_t& info = g_ColInfo[ i ]; |
|
|
|
AddColumnHeader( i, info.columnName, info.columnText, info.startingWidth, info.minWidth, info.maxWidth, info.flags ); |
|
SetSortFunc( i, info.pfnSort ); |
|
SetColumnTextAlignment( i, info.alignment ); |
|
} |
|
|
|
SetSortColumn( 0 ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Destructor |
|
//----------------------------------------------------------------------------- |
|
PerforceFileList::~PerforceFileList() |
|
{ |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Apply scheme settings |
|
//----------------------------------------------------------------------------- |
|
void PerforceFileList::ApplySchemeSettings(IScheme *pScheme) |
|
{ |
|
BaseClass::ApplySchemeSettings( pScheme ); |
|
|
|
ImageList *pImageList = new ImageList( false ); |
|
pImageList->AddImage( scheme()->GetImage( "resource/icon_file", false ) ); |
|
pImageList->AddImage( scheme()->GetImage( "resource/icon_folder", false ) ); |
|
pImageList->AddImage( scheme()->GetImage( "resource/icon_folder_selected", false ) ); |
|
|
|
SetImageList( pImageList, true ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Toggle showing deleted files or not |
|
//----------------------------------------------------------------------------- |
|
void PerforceFileList::ShowDeletedFiles( bool bShowDeletedFiles ) |
|
{ |
|
if ( m_bShowDeletedFiles != bShowDeletedFiles ) |
|
{ |
|
m_bShowDeletedFiles = bShowDeletedFiles; |
|
|
|
for ( int i = FirstItem(); i != InvalidItemID(); i = NextItem( i ) ) |
|
{ |
|
KeyValues *pKeyValues = GetItem( i ); |
|
if ( !pKeyValues->GetInt( "deleted", 0 ) ) |
|
continue; |
|
|
|
SetItemVisible( i, m_bShowDeletedFiles ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Add a directory to the directory list, returns client spec |
|
//----------------------------------------------------------------------------- |
|
void PerforceFileList::AddItemToDirectoryList( const char *pFullPath, int nItemID, bool bIsDirectory ) |
|
{ |
|
char pDirectoryBuf[MAX_PATH]; |
|
Q_ExtractFilePath( pFullPath, pDirectoryBuf, sizeof(pDirectoryBuf) ); |
|
Q_StripTrailingSlash( pDirectoryBuf ); |
|
pFullPath = pDirectoryBuf; |
|
|
|
DirectoryInfo_t *pInfo; |
|
UtlSymId_t i = m_Directories.Find( pFullPath ); |
|
if ( i != m_Directories.InvalidIndex() ) |
|
{ |
|
pInfo = &m_Directories[i]; |
|
} |
|
else |
|
{ |
|
char pClientSpec[MAX_PATH]; |
|
if ( !p4->GetClientSpecForDirectory( pFullPath, pClientSpec, sizeof(pClientSpec) ) ) |
|
{ |
|
pClientSpec[0] = 0; |
|
} |
|
|
|
pInfo = &m_Directories[ pFullPath ]; |
|
pInfo->m_ClientSpec = pClientSpec; |
|
} |
|
|
|
pInfo->m_ItemIDs.AddToTail( nItemID ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Add a file to the file list. |
|
//----------------------------------------------------------------------------- |
|
int PerforceFileList::AddFileToFileList( const char *pFullPath, bool bExistsOnDisk ) |
|
{ |
|
bool bIsFileWriteable = bExistsOnDisk ? g_pFullFileSystem->IsFileWritable( pFullPath, NULL ) : true; |
|
|
|
// add the file to the list |
|
KeyValues *kv = new KeyValues("item"); |
|
|
|
const char *pRelativePath = Q_UnqualifiedFileName( pFullPath ); |
|
kv->SetString( "text", pRelativePath ); |
|
kv->SetString( "fullpath", pFullPath ); |
|
kv->SetInt( "image", 1 ); |
|
|
|
IImage *pImage = surface()->GetIconImageForFullPath( pFullPath ); |
|
if ( pImage ) |
|
{ |
|
kv->SetPtr( "iconImage", (void *)pImage ); |
|
} |
|
|
|
kv->SetInt( "imageSelected", 1 ); |
|
kv->SetInt( "directory", 0 ); |
|
|
|
// These are computed by Refresh |
|
kv->SetInt( "in_perforce", 0 ); |
|
kv->SetInt( "synched", 0 ); |
|
kv->SetInt( "checked_out", 0 ); |
|
kv->SetInt( "deleted", 0 ); |
|
|
|
wchar_t pFileType[ 80 ]; |
|
g_pFullFileSystem->GetFileTypeForFullPath( pFullPath, pFileType, sizeof( pFileType ) ); |
|
|
|
kv->SetWString( "type", pFileType ); |
|
kv->SetString( "attributes", bIsFileWriteable ? "" : "R" ); |
|
|
|
int nItemID = AddItem( kv, 0, false, false ); |
|
kv->deleteThis(); |
|
|
|
AddItemToDirectoryList( pFullPath, nItemID, false ); |
|
return nItemID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Add a directory to the file list. |
|
//----------------------------------------------------------------------------- |
|
int PerforceFileList::AddDirectoryToFileList( const char *pFullPath, bool bExistsOnDisk ) |
|
{ |
|
KeyValues *kv = new KeyValues("item"); |
|
|
|
const char *pRelativePath = Q_UnqualifiedFileName( pFullPath ); |
|
kv->SetString( "text", pRelativePath ); |
|
kv->SetString( "fullpath", pFullPath ); |
|
kv->SetPtr( "iconImage", (void *)NULL ); |
|
kv->SetInt( "image", 2 ); |
|
kv->SetInt( "imageSelected", 3 ); |
|
kv->SetInt( "directory", 1 ); |
|
|
|
// These are computed by Refresh |
|
kv->SetInt( "in_perforce", 0 ); |
|
kv->SetInt( "synched", 0 ); |
|
kv->SetInt( "checked_out", 0 ); |
|
kv->SetInt( "deleted", 0 ); |
|
|
|
kv->SetString( "type", "#PerforceFileList_FileType_Folder" ); |
|
kv->SetString( "attributes", "D" ); |
|
|
|
int nItemID = AddItem(kv, 0, false, false); |
|
kv->deleteThis(); |
|
|
|
AddItemToDirectoryList( pFullPath, nItemID, true ); |
|
return nItemID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Add a file or directory to the file list. |
|
//----------------------------------------------------------------------------- |
|
int PerforceFileList::AddFile( const char *pFullPath, int nFileExists, int nIsDirectory ) |
|
{ |
|
if ( !pFullPath ) |
|
return InvalidItemID(); |
|
|
|
if ( !Q_IsAbsolutePath( pFullPath ) ) |
|
{ |
|
Warning( "Absolute paths required for PerforceFileList::AddFile!\n" |
|
"\"%s\" is not an abolute path", pFullPath ); |
|
return InvalidItemID(); |
|
} |
|
|
|
char pFixedPath[MAX_PATH]; |
|
Q_strncpy( pFixedPath, pFullPath, sizeof(pFixedPath) ); |
|
Q_FixSlashes( pFixedPath ); |
|
|
|
// Check to see if the file is on disk |
|
int nItemID = -1; |
|
bool bFileExists, bIsDirectory; |
|
if ( nFileExists < 0 ) |
|
{ |
|
bFileExists = g_pFullFileSystem->FileExists( pFixedPath ) ; |
|
} |
|
else |
|
{ |
|
bFileExists = ( nFileExists != 0 ); |
|
} |
|
|
|
if ( nIsDirectory < 0 ) |
|
{ |
|
if ( bFileExists ) |
|
{ |
|
bIsDirectory = g_pFullFileSystem->IsDirectory( pFixedPath ); |
|
} |
|
else |
|
{ |
|
int nLen = Q_strlen( pFixedPath ); |
|
bIsDirectory = ( pFixedPath[nLen-1] == CORRECT_PATH_SEPARATOR ); |
|
} |
|
} |
|
else |
|
{ |
|
bIsDirectory = ( nIsDirectory != 0 ); |
|
} |
|
|
|
if ( bIsDirectory ) |
|
{ |
|
nItemID = AddDirectoryToFileList( pFixedPath, bFileExists ); |
|
} |
|
else |
|
{ |
|
nItemID = AddFileToFileList( pFixedPath, bFileExists ); |
|
} |
|
|
|
return nItemID; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Remove all files from the list |
|
//----------------------------------------------------------------------------- |
|
void PerforceFileList::RemoveAllFiles() |
|
{ |
|
RemoveAll(); |
|
m_Directories.Clear(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Finds a file in the p4 list |
|
//----------------------------------------------------------------------------- |
|
static P4File_t *FindFileInPerforceList( const char *pFileName, CUtlVector<P4File_t> &fileList, bool *pFound ) |
|
{ |
|
int nCount = fileList.Count(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
if ( pFound[i] ) |
|
continue; |
|
|
|
const char *pPerforceFileName = p4->String( fileList[i].m_sLocalFile ); |
|
if ( !Q_stricmp( pPerforceFileName, pFileName ) ) |
|
{ |
|
pFound[i] = true; |
|
return &fileList[i]; |
|
} |
|
} |
|
return NULL; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Refresh perforce information |
|
//----------------------------------------------------------------------------- |
|
void PerforceFileList::RefreshPerforceState( int nItemID, bool bFileExists, P4File_t *pFileInfo ) |
|
{ |
|
KeyValues *kv = GetItem( nItemID ); |
|
|
|
bool bIsSynched = false; |
|
bool bIsFileInPerforce = (pFileInfo != NULL); |
|
if ( bIsFileInPerforce ) |
|
{ |
|
if ( pFileInfo->m_bDeleted != bFileExists ) |
|
{ |
|
bIsSynched = ( pFileInfo->m_bDeleted || ( pFileInfo->m_iHeadRevision == pFileInfo->m_iHaveRevision ) ); |
|
} |
|
} |
|
else |
|
{ |
|
bIsSynched = !bFileExists; |
|
} |
|
|
|
bool bIsDeleted = bIsFileInPerforce && !bFileExists && pFileInfo->m_bDeleted; |
|
|
|
kv->SetInt( "in_perforce", bIsFileInPerforce ); |
|
kv->SetInt( "synched", bIsSynched ); |
|
kv->SetInt( "checked_out", bIsFileInPerforce && ( pFileInfo->m_eOpenState != P4FILE_UNOPENED ) ); |
|
kv->SetInt( "deleted", bIsDeleted ); |
|
|
|
if ( bIsDeleted ) |
|
{ |
|
SetItemVisible( nItemID, m_bShowDeletedFiles ); |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Refresh perforce information |
|
//----------------------------------------------------------------------------- |
|
void PerforceFileList::Refresh() |
|
{ |
|
/* |
|
// Slow method.. does too many perforce operations |
|
for ( int i = FirstItem(); i != InvalidItemID(); i = NextItem( i ) ) |
|
{ |
|
const char *pFile = GetFile( i ); |
|
|
|
P4File_t fileInfo; |
|
bool bIsFileInPerforce = p4->GetFileInfo( pFile, &fileInfo ); |
|
bool bFileExists = g_pFullFileSystem->FileExists( pFile ); |
|
RefreshPerforceState( i, bFileExists, bIsFileInPerforce ? &fileInfo : NULL ); |
|
} |
|
*/ |
|
|
|
// NOTE: Reducing the # of perforce calls is important for performance |
|
int nCount = m_Directories.GetNumStrings(); |
|
for ( int i = 0; i < nCount; ++i ) |
|
{ |
|
const char *pDirectory = m_Directories.String(i); |
|
DirectoryInfo_t *pInfo = &m_Directories[i]; |
|
|
|
// Retrives files, uses faster method to avoid finding clientspec |
|
CUtlVector<P4File_t> &fileList = p4->GetFileListUsingClientSpec( pDirectory, pInfo->m_ClientSpec ); |
|
int nFileCount = fileList.Count(); |
|
bool *pFound = (bool*)_alloca( nFileCount * sizeof(bool) ); |
|
memset( pFound, 0, nFileCount * sizeof(bool) ); |
|
|
|
int nItemCount = pInfo->m_ItemIDs.Count(); |
|
for ( int j = 0; j < nItemCount; ++j ) |
|
{ |
|
int nItemID = pInfo->m_ItemIDs[j]; |
|
const char *pFileName = GetFile( nItemID ); |
|
bool bFileExists = g_pFullFileSystem->FileExists( pFileName ); |
|
P4File_t *pFileInfo = FindFileInPerforceList( pFileName, fileList, pFound ); |
|
RefreshPerforceState( nItemID, bFileExists, pFileInfo ); |
|
} |
|
} |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Is a particular list item a directory? |
|
//----------------------------------------------------------------------------- |
|
bool PerforceFileList::IsDirectoryItem( int nItemID ) |
|
{ |
|
KeyValues *kv = GetItem( nItemID ); |
|
return kv->GetInt( "directory", 0 ) != 0; |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Returns the file associated with a particular item ID |
|
//----------------------------------------------------------------------------- |
|
const char *PerforceFileList::GetFile( int nItemID ) |
|
{ |
|
KeyValues *kv = GetItem( nItemID ); |
|
Assert( kv ); |
|
return kv->GetString( "fullpath", "<no file>" ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Find the item ID associated with a particular file |
|
//----------------------------------------------------------------------------- |
|
int PerforceFileList::FindFile( const char *pFullPath ) |
|
{ |
|
for ( int i = FirstItem(); i != InvalidItemID(); i = NextItem( i ) ) |
|
{ |
|
const char *pFile = GetFile( i ); |
|
if ( !Q_stricmp( pFile, pFullPath ) ) |
|
return i; |
|
} |
|
return InvalidItemID(); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Is a file already in the list? |
|
//----------------------------------------------------------------------------- |
|
bool PerforceFileList::IsFileInList( const char *pFullPath ) |
|
{ |
|
return ( FindFile( pFullPath ) != InvalidItemID() ); |
|
} |
|
|
|
|
|
//----------------------------------------------------------------------------- |
|
// Purpose: Double-click: expand folders |
|
//----------------------------------------------------------------------------- |
|
void PerforceFileList::OnMouseDoublePressed( MouseCode code ) |
|
{ |
|
if ( code == MOUSE_LEFT ) |
|
{ |
|
// select the item |
|
OnMousePressed(code); |
|
|
|
// post a special message |
|
if ( GetSelectedItemsCount() > 0 ) |
|
{ |
|
PostActionSignal( new KeyValues("ItemDoubleClicked" ) ); |
|
} |
|
return; |
|
} |
|
|
|
BaseClass::OnMouseDoublePressed( code ); |
|
} |
|
|
|
|
|
|