//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // // $NoKeywords: $ // //=============================================================================// #include "dme_controls/filelistmanager.h" #include "vgui_controls/FileOpenDialog.h" #include "vgui_controls/menu.h" #include "vgui_controls/messagebox.h" #include "datamodel/idatamodel.h" #include "datamodel/dmelement.h" #include "datamodel/dmattribute.h" #include "datamodel/dmattributevar.h" #include "vgui/ISurface.h" #include #include "vgui/mousecode.h" #include "tier1/strtools.h" #include "tier1/KeyValues.h" #include "tier2/tier2.h" #include "p4lib/ip4.h" #include "filesystem.h" #include "dme_controls/INotifyUI.h" // memdbgon must be the last include file in a .cpp file!!! #include "tier0/memdbgon.h" template < int C > int ListPanelStringSortFunc( vgui::ListPanel *pPanel, const vgui::ListPanelItem &item1, const vgui::ListPanelItem &item2 ); struct ColumnInfo_t { char const *columnName; char const *columnText; int startingWidth; int flags; vgui::SortFunc *pfnSort; vgui::Label::Alignment alignment; }; enum ColumnIndex_t { CI_FILENAME, CI_PATH, CI_LOADED, CI_NUMELEMENTS, CI_CHANGED, CI_INPERFORCE, CI_OPENFOREDIT, }; const int baseflags = vgui::ListPanel::COLUMN_UNHIDABLE; const int fixedflags = vgui::ListPanel::COLUMN_UNHIDABLE | vgui::ListPanel::COLUMN_FIXEDSIZE; static ColumnInfo_t g_ColInfo[] = { { "filename", "#BxFileManager_Filename", 150, baseflags, ListPanelStringSortFunc< CI_FILENAME >, vgui::Label::a_west }, { "path", "#BxFileManager_Path", 240, baseflags, ListPanelStringSortFunc< CI_PATH >, vgui::Label::a_west }, { "loaded", "#BxFileManager_Loaded", 40, fixedflags, ListPanelStringSortFunc< CI_LOADED >, vgui::Label::a_center }, { "numelements", "#BxFileManager_NumElements", 60, fixedflags, ListPanelStringSortFunc< CI_NUMELEMENTS >, vgui::Label::a_east }, { "changed", "#BxFileManager_Changed", 50, fixedflags, ListPanelStringSortFunc< CI_CHANGED >, vgui::Label::a_center }, { "in_perforce", "#BxFileManager_P4Exists", 35, fixedflags, ListPanelStringSortFunc< CI_INPERFORCE >, vgui::Label::a_center }, { "open_for_edit", "#BxFileManager_P4Edit", 40, fixedflags, ListPanelStringSortFunc< CI_OPENFOREDIT >, vgui::Label::a_center }, }; const char *GetKey( ColumnIndex_t ci ) { return g_ColInfo[ ci ].columnName; } template < int C > int ListPanelStringSortFunc( vgui::ListPanel *pPanel, const vgui::ListPanelItem &item1, const vgui::ListPanelItem &item2 ) { NOTE_UNUSED( pPanel ); const char *pKey = GetKey( ( ColumnIndex_t )C ); const char *string1 = item1.kv->GetString( pKey ); const char *string2 = item2.kv->GetString( pKey ); return Q_stricmp( string1, string2 ); } void AddColumn( CFileListManager *pFileManager, ColumnIndex_t ci ) { pFileManager->AddColumnHeader( ci, g_ColInfo[ ci ].columnName, g_ColInfo[ ci ].columnText, g_ColInfo[ ci ].startingWidth, g_ColInfo[ ci ].flags ); pFileManager->SetSortFunc( ci, g_ColInfo[ ci ].pfnSort ); pFileManager->SetColumnTextAlignment( ci, g_ColInfo[ ci ].alignment ); } CFileListManager::CFileListManager( vgui::Panel *parent ) : BaseClass( parent, "FileListManager" ) { SetMultiselectEnabled( true ); SetVisible( true ); m_bRefreshRequired = false; SetSize( 800, 200 ); SetPos( 100, 100 ); AddColumn( this, CI_FILENAME ); AddColumn( this, CI_PATH ); AddColumn( this, CI_LOADED ); AddColumn( this, CI_NUMELEMENTS ); AddColumn( this, CI_CHANGED ); AddColumn( this, CI_INPERFORCE ); AddColumn( this, CI_OPENFOREDIT ); SetSortColumn( 0 ); Refresh(); SetScheme( vgui::scheme()->LoadSchemeFromFile( "Resource/BoxRocket.res", "BoxRocket" ) ); // LoadControlSettings( "resource/BxFileListManager.res" ); } int CFileListManager::AddItem( DmFileId_t fileid, const char *pFilename, const char *pPath, bool bLoaded, int nElements, bool bChanged, bool bInPerforce, bool bOpenForEdit ) { KeyValues *kv = new KeyValues( "", GetKey( CI_FILENAME ), pFilename, GetKey( CI_PATH ), pPath ); kv->SetInt ( GetKey( CI_NUMELEMENTS ), nElements ); kv->SetString( GetKey( CI_LOADED ), bLoaded ? "Y" : "N" ); kv->SetString( GetKey( CI_CHANGED ), bChanged ? "Y" : "N" ); kv->SetString( GetKey( CI_INPERFORCE ), bInPerforce ? "Y" : "N" ); kv->SetString( GetKey( CI_OPENFOREDIT ), bOpenForEdit ? "Y" : "N" ); int itemID = BaseClass::AddItem( kv, fileid, false, false ); kv->deleteThis(); return itemID; } void CFileListManager::SetLoaded( DmFileId_t fileid, bool bLoaded ) { CNotifyScopeGuard notify( "CFileListManager::SetLoaded", NOTIFY_SOURCE_FILE_LIST_MANAGER, NOTIFY_SETDIRTYFLAG ); if ( bLoaded ) { const char *pFilename = g_pDataModel->GetFileName( fileid ); Assert( pFilename ); if ( !pFilename ) return; CDisableUndoScopeGuard guard; CDmElement *pRoot = NULL; g_pDataModel->RestoreFromFile( pFilename, NULL, NULL, &pRoot, CR_DELETE_NEW ); } else { CDisableUndoScopeGuard guard; g_pDataModel->UnloadFile( fileid ); } } void CFileListManager::OnMousePressed( vgui::MouseCode code ) { // determine where we were pressed int x, y, row, column; vgui::input()->GetCursorPos( x, y ); GetCellAtPos( x, y, row, column ); if ( code == MOUSE_LEFT ) { bool bIsFakeToggleButton = column == CI_LOADED; if ( bIsFakeToggleButton && row >= 0 && row < GetItemCount() ) { int itemID = GetItemIDFromRow( row ); KeyValues *kv = GetItem( itemID ); const char *pStr = kv->GetString( GetKey( ( ColumnIndex_t )column ), "" ); Assert( *pStr == 'Y' || *pStr == 'N' ); bool bSet = *pStr == 'N'; // bSet is the NEW state, not the old one kv->SetString( GetKey( ( ColumnIndex_t )column ), bSet ? "Y" : "N" ); SetLoaded( ( DmFileId_t )GetItemUserData( itemID ), bSet ); // get the key focus RequestFocus(); return; } } else if ( code == MOUSE_RIGHT ) { int itemID = -1; if ( row >= 0 && row < GetItemCount() ) { itemID = GetItemIDFromRow( row ); if ( !IsItemSelected( itemID ) ) { SetSingleSelectedItem( itemID ); } } KeyValues *kv = new KeyValues( "OpenContextMenu", "itemID", itemID ); OnOpenContextMenu( kv ); kv->deleteThis(); return; } BaseClass::OnMousePressed( code ); } int AddMenuItemHelper( vgui::Menu *pMenu, const char *pItemName, const char *pKVName, vgui::Panel *pTarget, bool bEnabled ) { int id = pMenu->AddMenuItem( pItemName, new KeyValues( pKVName ), pTarget ); pMenu->SetItemEnabled( id, bEnabled ); return id; } void CFileListManager::OnOpenContextMenu( KeyValues *pParams ) { if ( m_hContextMenu.Get() ) { delete m_hContextMenu.Get(); m_hContextMenu = NULL; } m_hContextMenu = new vgui::Menu( this, "ContextMenu" ); int itemID = pParams->GetInt( "itemID", -1 ); if ( itemID < 0 ) { AddMenuItemHelper( m_hContextMenu, "Open File...", "open", this, true ); // Is this how we should load other files??? } else { bool bP4Connected = p4->IsConnectedToServer(); int nSelected = GetSelectedItemsCount(); int nLoaded = 0; int nChanged = 0; int nOnDisk = 0; int nInPerforce = 0; int nOpenForEdit = 0; for ( int i = 0; i < nSelected; ++i ) { int itemId = GetSelectedItem( i ); DmFileId_t fileid = ( DmFileId_t )GetItemUserData( itemId ); if ( g_pDataModel->IsFileLoaded( fileid ) ) { ++nLoaded; ++nChanged; // TODO - find out for real } const char *pFilename = g_pDataModel->GetFileName( fileid ); if ( g_pFullFileSystem->FileExists( pFilename ) ) { ++nOnDisk; } if ( bP4Connected ) { if ( p4->IsFileInPerforce( pFilename ) ) { ++nInPerforce; if ( p4->GetFileState( pFilename ) != P4FILE_UNOPENED ) { ++nOpenForEdit; } } } } AddMenuItemHelper( m_hContextMenu, "Load", "load", this, nLoaded < nSelected && nOnDisk > 0 ); AddMenuItemHelper( m_hContextMenu, "Unload", "unload", this, nLoaded > 0 ); AddMenuItemHelper( m_hContextMenu, "Save", "save", this, nChanged > 0 && nOnDisk == nSelected ); AddMenuItemHelper( m_hContextMenu, "Save As...", "saveas", this, nLoaded == 1 && nSelected == 1 ); AddMenuItemHelper( m_hContextMenu, "Add To Perforce", "p4add", this, nInPerforce < nSelected && nOnDisk > 0 ); AddMenuItemHelper( m_hContextMenu, "Open For Edit", "p4edit", this, nOpenForEdit < nSelected && nOnDisk > 0 ); } vgui::Menu::PlaceContextMenu( this, m_hContextMenu.Get() ); } void CFileListManager::OnLoadFiles( KeyValues *pParams ) { CNotifyScopeGuard notify( "CFileListManager::OnLoadFiles", NOTIFY_SOURCE_FILE_LIST_MANAGER, NOTIFY_SETDIRTYFLAG ); int nSelected = GetSelectedItemsCount(); for ( int i = 0; i < nSelected; ++i ) { int itemId = GetSelectedItem( i ); DmFileId_t fileid = ( DmFileId_t )GetItemUserData( itemId ); if ( !g_pDataModel->IsFileLoaded( fileid ) ) { SetLoaded( fileid, true ); } } Refresh(); } void CFileListManager::OnUnloadFiles( KeyValues *pParams ) { CNotifyScopeGuard notify( "CFileListManager::OnUnloadFiles", NOTIFY_SOURCE_FILE_LIST_MANAGER, NOTIFY_SETDIRTYFLAG ); int nSelected = GetSelectedItemsCount(); for ( int i = 0; i < nSelected; ++i ) { int itemId = GetSelectedItem( i ); DmFileId_t fileid = ( DmFileId_t )GetItemUserData( itemId ); if ( g_pDataModel->IsFileLoaded( fileid ) ) { SetLoaded( fileid, false ); } } Refresh(); } void CFileListManager::OnSaveFiles( KeyValues *pParams ) { int nSelected = GetSelectedItemsCount(); for ( int i = 0; i < nSelected; ++i ) { int itemId = GetSelectedItem( i ); DmFileId_t fileid = ( DmFileId_t )GetItemUserData( itemId ); if ( !g_pDataModel->IsFileLoaded( fileid ) ) continue; const char *pFilename = g_pDataModel->GetFileName( fileid ); Assert( pFilename ); if ( !pFilename ) continue; CDmElement *pRoot = GetElement< CDmElement >( g_pDataModel->GetFileRoot( fileid ) ); Assert( pRoot ); if ( !pRoot ) continue; const char *pFileFormat = g_pDataModel->GetFileFormat( fileid ); const char *pEncoding = g_pDataModel->GetDefaultEncoding( pFileFormat ); g_pDataModel->SaveToFile( pFilename, NULL, pEncoding, pFileFormat, pRoot ); } Refresh(); } void CFileListManager::OnOpenFile( KeyValues *pParams ) { KeyValues *pContextKeyValues = new KeyValues( "OnOpen" ); vgui::FileOpenDialog *pFileOpenDialog = new vgui::FileOpenDialog( this, "Save .dmx File As", false, pContextKeyValues ); pFileOpenDialog->AddFilter( "*.dmx", "DmElements File (*.dmx)", true ); pFileOpenDialog->AddActionSignalTarget( this ); pFileOpenDialog->SetDeleteSelfOnClose( true ); pFileOpenDialog->DoModal( false ); } void CFileListManager::OnSaveFileAs( KeyValues *pParams ) { int nSelected = GetSelectedItemsCount(); Assert( nSelected == 1 ); if ( nSelected != 1 ) return; KeyValues *pContextKeyValues = new KeyValues( "OnSaveAs" ); pContextKeyValues->SetInt( "itemId", GetSelectedItem( 0 ) ); DmFileId_t fileid = ( DmFileId_t )GetItemUserData( GetSelectedItem( 0 ) ); const char *pFileFormat = g_pDataModel->GetFileFormat( fileid ); vgui::FileOpenDialog *pFileOpenDialog = new vgui::FileOpenDialog( this, "Save .dmx File As", false, pContextKeyValues ); // if this control is moved to vgui_controls, change the default format to "dmx", the generic dmx format pFileOpenDialog->AddFilter( "*.dmx", "Generic MovieObjects File (*.dmx)", false, "movieobjects" ); if ( V_strcmp( pFileFormat, "movieobjects" ) != 0 ) { char description[ 256 ]; V_snprintf( description, sizeof( description ), "%s (*.dmx)", g_pDataModel->GetFormatDescription( pFileFormat ) ); pFileOpenDialog->AddFilter( "*.dmx", description, true, pFileFormat ); } pFileOpenDialog->AddActionSignalTarget( this ); pFileOpenDialog->SetDeleteSelfOnClose( true ); pFileOpenDialog->DoModal( false ); } void CFileListManager::OnFileSelected( KeyValues *pParams ) { const char *pFullPath = pParams->GetString( "fullpath" ); if ( !pFullPath || !pFullPath[ 0 ] ) return; KeyValues *pSaveAsKey = pParams->FindKey( "OnSaveAs" ); if ( pSaveAsKey ) { int itemId = pSaveAsKey->GetInt( "itemId", -1 ); Assert( itemId != -1 ); if ( itemId == -1 ) return; DmFileId_t fileid = ( DmFileId_t )GetItemUserData( itemId ); Assert( fileid != DMFILEID_INVALID ); if ( fileid == DMFILEID_INVALID ) return; CDmElement *pRoot = GetElement< CDmElement >( g_pDataModel->GetFileRoot( fileid ) ); Assert( pRoot ); if ( !pRoot ) return; const char *pFormat = pParams->GetString( "filterinfo" ); Assert( pFormat ); if ( !pFormat ) return; g_pDataModel->SetFileName( fileid, pFullPath ); g_pDataModel->SaveToFile( pFullPath, NULL, g_pDataModel->GetDefaultEncoding( pFormat ), pFormat, pRoot ); Refresh(); return; } KeyValues *pOpenKey = pParams->FindKey( "OnOpen" ); if ( pOpenKey ) { CDmElement *pRoot = NULL; g_pDataModel->RestoreFromFile( pFullPath, NULL, NULL, &pRoot ); Refresh(); return; } } void CFileListManager::OnAddToPerforce( KeyValues *pParams ) { int nFileCount = 0; int nSelected = GetSelectedItemsCount(); const char **ppFileNames = ( const char** )_alloca( nSelected * sizeof( char* ) ); for ( int i = 0; i < nSelected; ++i ) { int itemId = GetSelectedItem( i ); DmFileId_t fileid = ( DmFileId_t )GetItemUserData( itemId ); const char *pFilename = g_pDataModel->GetFileName( fileid ); Assert( pFilename ); if ( !pFilename ) continue; ++nFileCount; ppFileNames[ i ] = pFilename; } bool bSuccess = p4->OpenFilesForAdd( nFileCount, ppFileNames ); if ( !bSuccess ) { vgui::MessageBox *pError = new vgui::MessageBox( "Perforce Error!", p4->GetLastError(), GetParent() ); pError->SetSmallCaption( true ); pError->DoModal(); } Refresh(); } void CFileListManager::OnOpenForEdit( KeyValues *pParams ) { int nFileCount = 0; int nSelected = GetSelectedItemsCount(); const char **ppFileNames = ( const char** )_alloca( nSelected * sizeof( char* ) ); for ( int i = 0; i < nSelected; ++i ) { int itemId = GetSelectedItem( i ); DmFileId_t fileid = ( DmFileId_t )GetItemUserData( itemId ); const char *pFilename = g_pDataModel->GetFileName( fileid ); Assert( pFilename ); if ( !pFilename ) continue; ++nFileCount; ppFileNames[ i ] = pFilename; } bool bSuccess = p4->OpenFilesForEdit( nFileCount, ppFileNames ); if ( !bSuccess ) { vgui::MessageBox *pError = new vgui::MessageBox( "Perforce Error!", p4->GetLastError(), GetParent() ); pError->SetSmallCaption( true ); pError->DoModal(); } Refresh(); } void CFileListManager::OnDataChanged( KeyValues *pParams ) { int nNotifyFlags = pParams->GetInt( "notifyFlags" ); if ( ( nNotifyFlags & NOTIFY_CHANGE_TOPOLOGICAL ) == 0 ) return; int nNotifySource = pParams->GetInt( "source" ); if ( nNotifySource == NOTIFY_SOURCE_FILE_LIST_MANAGER ) return; if ( !IsVisible() ) { m_bRefreshRequired = true; return; } int nCount = GetItemCount(); int nFiles = g_pDataModel->NumFileIds(); bool bPerformFullRefresh = ( nCount != nFiles ); if ( !bPerformFullRefresh ) { const char *pNameKey = GetKey( CI_FILENAME ); for ( int i = 0; i < nCount; ++i ) { DmFileId_t fileid = g_pDataModel->GetFileId( i ); const char *pFileName = g_pDataModel->GetFileName( fileid ); if ( !pFileName || !*pFileName ) { bPerformFullRefresh = true; break; } pFileName = V_UnqualifiedFileName( pFileName ); KeyValues *pKeyValues = GetItem( i ); bPerformFullRefresh = ( fileid != (DmFileId_t)GetItemUserData(i) ) || Q_stricmp( pFileName, pKeyValues->GetString( pNameKey ) ); if ( bPerformFullRefresh ) break; pKeyValues->SetInt ( GetKey( CI_NUMELEMENTS ), g_pDataModel->NumElementsInFile( fileid ) ); pKeyValues->SetString( GetKey( CI_LOADED ), g_pDataModel->IsFileLoaded( fileid ) ? "Y" : "N" ); pKeyValues->SetString( GetKey( CI_CHANGED ), false ? "Y" : "N" ); ApplyItemChanges( i ); } } if ( bPerformFullRefresh ) { Refresh(); return; } } void CFileListManager::Refresh() { m_bRefreshRequired = false; RemoveAll(); const bool bP4Connected = p4 ? p4->IsConnectedToServer() : false; int nFiles = g_pDataModel->NumFileIds(); for ( int i = 0; i < nFiles; ++i ) { DmFileId_t fileid = g_pDataModel->GetFileId( i ); const char *pFileName = g_pDataModel->GetFileName( fileid ); if ( !pFileName || !*pFileName ) continue; // skip DMFILEID_INVALID and the default fileid "" bool bLoaded = g_pDataModel->IsFileLoaded( fileid ); int nElements = g_pDataModel->NumElementsInFile( fileid ); bool bChanged = false; // TODO - find out for real bool bInPerforce = bP4Connected && p4->IsFileInPerforce( pFileName ); bool bOpenForEdit = bInPerforce && p4->GetFileState( pFileName ) != P4FILE_UNOPENED; char path[ 256 ]; V_ExtractFilePath( pFileName, path, sizeof( path ) ); AddItem( fileid, V_UnqualifiedFileName( pFileName ), path, bLoaded, nElements, bChanged, bInPerforce, bOpenForEdit ); } } void CFileListManager::OnThink( ) { BaseClass::OnThink(); if ( m_bRefreshRequired && IsVisible() ) { Refresh(); } } void CFileListManager::OnCommand( const char *cmd ) { // if ( !Q_stricmp( cmd, "foo" ) ) ... BaseClass::OnCommand( cmd ); } //----------------------------------------------------------------------------- // // CFileManagerFrame methods // //----------------------------------------------------------------------------- CFileManagerFrame::CFileManagerFrame( vgui::Panel *parent ) : BaseClass( parent, "FileManagerFrame" ) { SetTitle( "#BxFileManagerFrame", true ); SetSizeable( true ); SetCloseButtonVisible( false ); SetMinimumSize( 200, 200 ); SetVisible( true ); SetSize( 800, 200 ); SetPos( 100, 100 ); m_pFileListManager = new CFileListManager( this ); Refresh(); SetScheme( vgui::scheme()->LoadSchemeFromFile( "Resource/BoxRocket.res", "BoxRocket" ) ); } void CFileManagerFrame::Refresh() { m_pFileListManager->Refresh(); } void CFileManagerFrame::OnCommand( const char *cmd ) { BaseClass::OnCommand( cmd ); m_pFileListManager->OnCommand( cmd ); } void CFileManagerFrame::PerformLayout() { BaseClass::PerformLayout(); int iWidth, iHeight; GetSize( iWidth, iHeight ); m_pFileListManager->SetPos( 0, GetCaptionHeight() ); m_pFileListManager->SetSize( iWidth, iHeight - GetCaptionHeight() ); }