//========= Copyright Valve Corporation, All rights reserved. ============// // // Purpose: // //=============================================================================// #include "cbase.h" #if defined( REPLAY_ENABLED ) #include "replaybrowserdetailspanel.h" #include "replaybrowsermainpanel.h" #include "replaybrowseritemmanager.h" #include "replaybrowsermovieplayerpanel.h" #include "replaybrowserrenderdialog.h" #include "vgui/IVGui.h" #include "vgui/ISurface.h" #include "vgui/IInput.h" #include "vgui/ILocalize.h" #include "vgui_controls/FileOpenDialog.h" #include "vgui_controls/PanelListPanel.h" #include "vgui_controls/ScrollBar.h" #include "vgui_controls/ScrollBarSlider.h" #include "vgui_controls/TextEntry.h" #include "vgui_controls/TextImage.h" #include "vgui_avatarimage.h" #include "gamestringpool.h" #include "replay/genericclassbased_replay.h" #include "replaybrowserlistitempanel.h" #include "confirm_dialog.h" #include "replay/ireplaymoviemanager.h" #include "replay/ireplaymanager.h" #include "replay/ireplayrenderqueue.h" #include "replay/screenshot.h" #include "replay/ireplayperformancemanager.h" #include "replay/performance.h" #include "vgui/ISystem.h" #include "youtubeapi.h" #include "replay/replayyoutubeapi.h" #include "ienginevgui.h" #include // memdbgon must be the last include file in a .cpp file!!! #include //----------------------------------------------------------------------------- extern IClientReplayContext *g_pClientReplayContext; extern IReplayMovieManager *g_pReplayMovieManager; extern IReplayPerformanceManager *g_pReplayPerformanceManager; //----------------------------------------------------------------------------- ConVar replay_movie_reveal_warning( "replay_movie_reveal_warning", "1", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_DONTRECORD ); ConVar replay_movie_export_last_dir( "replay_movie_export_last_dir", "", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_DONTRECORD ); ConVar replay_renderqueue_first_add( "replay_renderqueue_first_add", "0", FCVAR_ARCHIVE | FCVAR_CLIENTDLL | FCVAR_HIDDEN | FCVAR_DONTRECORD ); //----------------------------------------------------------------------------- using namespace vgui; //----------------------------------------------------------------------------- class CConfirmDisconnectFromServerDialog : public CConfirmDialog { DECLARE_CLASS_SIMPLE( CConfirmDisconnectFromServerDialog, CConfirmDialog ); public: CConfirmDisconnectFromServerDialog( Panel *pParent ) : BaseClass( pParent ) { surface()->PlaySound( "replay\\replaydialog_warn.wav" ); } const wchar_t *GetText() { return g_pVGuiLocalize->Find( "#Replay_ConfirmDisconnectFromServer" ); } void ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); SetTall( YRES( 170 ) ); } }; //----------------------------------------------------------------------------- CKeyValueLabelPanel::CKeyValueLabelPanel( Panel *pParent, const char *pKey, const char *pValue ) : EditablePanel( pParent, "KeyValueLabelPanel" ) { SetScheme( "ClientScheme" ); m_pLabels[ 0 ] = new CExLabel( this, "KeyLabel", pKey ); m_pLabels[ 1 ] = new CExLabel( this, "ValueLabel", pValue ); } CKeyValueLabelPanel::CKeyValueLabelPanel( Panel *pParent, const char *pKey, const wchar_t *pValue ) : EditablePanel( pParent, "KeyValueLabelPanel" ) { SetScheme( "ClientScheme" ); m_pLabels[ 0 ] = new CExLabel( this, "KeyLabel", pKey ); m_pLabels[ 1 ] = new CExLabel( this, "ValueLabel", pValue ); } void CKeyValueLabelPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); HFont hFont = scheme()->GetIScheme( GetScheme() )->GetFont( "ReplayBrowserSmallest", true ); m_pLabels[ 0 ]->SetFont( hFont ); m_pLabels[ 1 ]->SetFont( hFont ); m_pLabels[ 0 ]->SetFgColor( Color( 119, 107, 95, 255 ) ); m_pLabels[ 1 ]->SetFgColor( Color( 255, 255, 255, 255 ) ); } void CKeyValueLabelPanel::PerformLayout() { BaseClass::PerformLayout(); int nContentWidth, nContentHeight; m_pLabels[0]->GetContentSize( nContentWidth, nContentHeight ); int iMidX = GetParent()->GetWide() * 0.55f; m_pLabels[0]->SetBounds( 0, 0, iMidX, nContentHeight ); m_pLabels[1]->GetContentSize( nContentWidth, nContentHeight ); m_pLabels[1]->SetBounds( iMidX, 0, iMidX, nContentHeight ); } int CKeyValueLabelPanel::GetHeight() const { int nWidth, nHeight; m_pLabels[ 0 ]->GetContentSize( nWidth, nHeight ); return nHeight; } int CKeyValueLabelPanel::GetValueHeight() const { int nWidth, nHeight; m_pLabels[ 1 ]->GetContentSize( nWidth, nHeight ); return nHeight; } void CKeyValueLabelPanel::SetValue( const wchar_t *pValue ) { m_pLabels[ 1 ]->SetText( pValue ); InvalidateLayout(); } //----------------------------------------------------------------------------- CBaseDetailsPanel::CBaseDetailsPanel( Panel *pParent, const char *pName, ReplayHandle_t hReplay ) : EditablePanel( pParent, pName ), m_hReplay( hReplay ), m_bShouldShow( true ) { SetScheme( "ClientScheme" ); m_pInsetPanel = new EditablePanel( this, "InsetPanel" ); } void CBaseDetailsPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); SetBorder( pScheme->GetBorder( "ReplayStatsBorder" ) ); SetBgColor( Color( 0,0,0, 255 ) ); } void CBaseDetailsPanel::PerformLayout() { BaseClass::PerformLayout(); // Setup inset panel bounds const int n = GetMarginSize(); m_pInsetPanel->SetBounds( n, n, GetWide() - 2*n, GetTall() - 2*n ); } //----------------------------------------------------------------------------- CRecordsPanel::CRecordsPanel( Panel *pParent, ReplayHandle_t hReplay ) : CBaseDetailsPanel( pParent, "RecordsPanel", hReplay ) { m_pClassImage = new ImagePanel( this, "ClassImage" ); } void CRecordsPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); SetBorder( pScheme->GetBorder( "ReplayDefaultBorder" ) ); SetBgColor( Color( 0,0,0,0 ) ); } void CRecordsPanel::PerformLayout() { BaseClass::PerformLayout(); // Figure out the class image name char szImage[MAX_OSPATH]; const CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay ); V_snprintf( szImage, sizeof( szImage ), "class_sel_sm_%s_%s", pReplay->GetMaterialFriendlyPlayerClass(), pReplay->GetPlayerTeam() ); // Cause default image to display int nHeight = 0; // Get the image IImage *pImage = scheme()->GetImage( szImage, true ); if ( pImage ) { // Get image dimensions int nImageWidth, nImageHeight; pImage->GetSize( nImageWidth, nImageHeight ); // Compute height of the records panel as a little smaller than the image itself nHeight = nImageHeight * 11 / 16; // Setup the image panel - parent to records panel parent so it goes out of the records panel's bounds a bit const int nMargin = 7; const float flScale = 1.2f; m_pClassImage->SetImage( pImage ); m_pClassImage->SetParent( GetParent() ); m_pClassImage->SetShouldScaleImage( true ); m_pClassImage->SetScaleAmount( flScale ); int nX, nY; GetPos( nX, nY ); m_pClassImage->SetBounds( nX + nMargin, nY - flScale * nImageHeight + GetTall() - nMargin, nImageWidth * flScale, nImageHeight * flScale ); #if !defined( TF_CLIENT_DLL ) m_pClassImage->SetVisible( false ); #endif } SetTall( nHeight ); } //----------------------------------------------------------------------------- CStatsPanel::CStatsPanel( Panel *pParent, ReplayHandle_t hReplay ) : CBaseDetailsPanel( pParent, "StatsPanel", hReplay ) { CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay ); // Don't show the panel unless there are stats to display m_bShouldShow = false; // Create all stat labels RoundStats_t const &stats = pReplay->GetStats(); for ( int i = 0; i < REPLAY_MAX_DISPLAY_GAMESTATS; ++i ) { const int nCurStat = stats.Get( g_pReplayDisplayGameStats[i].m_nStat ); if ( !nCurStat ) { m_paStatLabels[ i ] = NULL; continue; } // Setup value char szValue[256]; V_snprintf( szValue, sizeof( szValue ), "%i", nCurStat ); // Create labels for this stat m_paStatLabels[ i ] = new CKeyValueLabelPanel( GetInset(), g_pReplayDisplayGameStats[i].m_pStatLocalizationToken, szValue ); // At least one stat to display m_bShouldShow = true; } } void CStatsPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); } void CStatsPanel::PerformLayout() { BaseClass::PerformLayout(); int nY = 0; for ( int i = 0; i < REPLAY_MAX_DISPLAY_GAMESTATS; ++i ) { CKeyValueLabelPanel *pCurStatLabels = m_paStatLabels[ i ]; if ( pCurStatLabels ) { pCurStatLabels->SetBounds( 0, nY, GetInset()->GetWide(), YRES(13) ); nY += YRES(13); } } SetTall( nY + GetMarginSize() * 2 ); } //----------------------------------------------------------------------------- CDominationsPanel::CDominationsPanel( Panel *pParent, ReplayHandle_t hReplay ) : CBaseDetailsPanel( pParent, "DominationsPanel", hReplay ) { CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay ); m_pNumDominationsImage = new ImagePanel( GetInset(), "NumDominations" ); char szImage[256]; int nNumDominations = pReplay->GetDominationCount(); // Setup the # of dominations image V_snprintf( szImage, sizeof( szImage ), "../hud/leaderboard_dom%i", nNumDominations ); m_pNumDominationsImage->SetImage( szImage ); // Add avatars for each person dominated if ( steamapicontext && steamapicontext->SteamUtils() && steamapicontext->SteamUtils()->GetConnectedUniverse() ) { for ( int i = 0; i < nNumDominations; ++i ) { CAvatarImage *pAvatar = new CAvatarImage(); CSteamID id( pReplay->GetDomination( i )->m_nVictimFriendId, 1, steamapicontext->SteamUtils()->GetConnectedUniverse(), k_EAccountTypeIndividual ); pAvatar->SetAvatarSteamID( id ); pAvatar->SetAvatarSize( 32, 32 ); pAvatar->UpdateFriendStatus(); ImagePanel *pImagePanel = new ImagePanel( GetInset(), "DominationImage" ); pImagePanel->SetImage( pAvatar ); pImagePanel->SetShouldScaleImage( false ); m_vecDominationImages.AddToTail( pImagePanel ); } } } void CDominationsPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); } void CDominationsPanel::PerformLayout() { BaseClass::PerformLayout(); const int nBuffer = 7; int nImageWidth, nImageHeight; m_pNumDominationsImage->GetImage()->GetSize( nImageWidth, nImageHeight ); m_pNumDominationsImage->SetBounds( 0, 0, nImageWidth, nImageHeight ); int nX = nImageWidth + 2*nBuffer; int nY = 0; for ( int i = 0; i < m_vecDominationImages.Count(); ++i ) { ImagePanel *pImagePanel = m_vecDominationImages[ i ]; pImagePanel->GetImage()->GetSize( nImageWidth, nImageHeight ); m_vecDominationImages[ i ]->SetBounds( nX, nY, nImageWidth, nImageHeight ); nX += nImageWidth + nBuffer; if ( nX + nImageWidth > GetInset()->GetWide() ) { nX = 0; nY += nImageHeight + nBuffer; } } SetTall( nY + nImageHeight + GetMarginSize() * 2 ); } //----------------------------------------------------------------------------- CKillsPanel::CKillsPanel( Panel *pParent, ReplayHandle_t hReplay ) : CBaseDetailsPanel( pParent, "KillsPanel", hReplay ) { // Get the replay from the handle and add all kills CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay ); char szKillCount[64] = "0"; if ( pReplay ) { for ( int i = 0; i < pReplay->GetKillCount(); ++i ) { // Construct path for image char szImgPath[MAX_OSPATH] = ""; #if defined( TF_CLIENT_DLL ) // Get the kill info const CGenericClassBasedReplay::KillData_t *pKill = pReplay->GetKill( i ); char const *pClass = pKill->m_nPlayerClass == TF_CLASS_DEMOMAN ? "demo" : g_aPlayerClassNames_NonLocalized[ pKill->m_nPlayerClass ]; V_snprintf( szImgPath, sizeof( szImgPath ), "../hud/leaderboard_class_%s", pClass ); #elif defined( CSTRIKE_DLL ) V_strcpy( szImgPath, "../hud/scoreboard_dead" ); #endif // Get the image IImage *pImage = scheme()->GetImage( szImgPath, true ); // Create new image panel ImagePanel *pImgPanel = new ImagePanel( GetInset(), "img" ); pImgPanel->SetImage( pImage ); // Cache for later m_vecKillImages.AddToTail( pImgPanel ); } // Copy kill count V_snprintf( szKillCount, sizeof( szKillCount ), "%i", pReplay->GetKillCount() ); } // Create labels m_pKillLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_Kills", szKillCount ); } void CKillsPanel::PerformLayout() { BaseClass::PerformLayout(); m_pKillLabels->SetBounds( 0, 0, GetWide(), m_pKillLabels->GetHeight() ); // Setup image positions int nBuffer = 5; int nY = m_pKillLabels->GetHeight() + nBuffer * 2; int nImageY = nY; int nImageX = 0; for ( int i = 0; i < m_vecKillImages.Count(); ++i ) { IImage *pCurImage = m_vecKillImages[ i ]->GetImage(); if ( !pCurImage ) continue; int nImageWidth, nImageHeight; pCurImage->GetSize( nImageWidth, nImageHeight ); m_vecKillImages[ i ]->SetBounds( nImageX, nImageY, nImageWidth, nImageHeight ); nImageX += nImageWidth + nBuffer; if ( i == 0 ) { nY += nImageHeight; } if ( nImageX + nImageWidth > GetInset()->GetWide() ) { nImageX = 0; nImageY += nImageHeight + nBuffer; nY += nImageHeight + nBuffer; } } // Set the height SetTall( nY + GetMarginSize() * 2 ); } void CKillsPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); } //----------------------------------------------------------------------------- extern const char *GetMapDisplayName( const char *mapName ); CBasicLifeInfoPanel::CBasicLifeInfoPanel( Panel *pParent, ReplayHandle_t hReplay ) : CBaseDetailsPanel( pParent, "BasicLifeInfo", hReplay ) { // Create labels CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( hReplay ); m_pKilledByLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_StatKilledBy", pReplay->WasKilled() ? pReplay->GetKillerName() : "#Replay_None" ); m_pMapLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_OnMap", GetMapDisplayName( pReplay->m_szMapName ) ); m_pLifeLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_Life", CReplayTime::FormatTimeString( (int)pReplay->m_flLength ) ); } void CBasicLifeInfoPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); } void CBasicLifeInfoPanel::PerformLayout() { BaseClass::PerformLayout(); int nBuffer = 5; m_pKilledByLabels->SetBounds( 0, 0, GetWide(), m_pKilledByLabels->GetHeight() ); m_pMapLabels->SetBounds( 0, m_pKilledByLabels->GetTall() + nBuffer, GetWide(), m_pMapLabels->GetHeight() ); int nLifeLabelsY = ( m_pMapLabels->GetTall() + nBuffer ) * 2; m_pLifeLabels->SetBounds( 0, nLifeLabelsY, GetWide(), m_pLifeLabels->GetHeight() ); SetTall( nLifeLabelsY + m_pLifeLabels->GetTall() + GetMarginSize() * 2 ); } //----------------------------------------------------------------------------- CYouTubeInfoPanel::CYouTubeInfoPanel( Panel *pParent ) : CBaseDetailsPanel( pParent, "YouTubeInfo", NULL ), m_pLabels( NULL ) { m_pLabels = new CKeyValueLabelPanel( GetInset(), "#Replay_YouTube", g_pVGuiLocalize->Find( "YouTube_NoStats" ) ); } void CYouTubeInfoPanel::PerformLayout() { BaseClass::PerformLayout(); m_pLabels->SetBounds( 0, 0, GetWide(), m_pLabels->GetValueHeight() ); SetTall( m_pLabels->GetTall() + GetMarginSize() * 2 ); } void CYouTubeInfoPanel::SetInfo( const wchar_t *pInfo ) { m_pLabels->SetValue( pInfo ); InvalidateLayout(); } //----------------------------------------------------------------------------- CTitleEditPanel::CTitleEditPanel( Panel *pParent, QueryableReplayItemHandle_t hReplayItem, IReplayItemManager *pItemManager ) : EditablePanel( pParent, "TitleEditPanel" ), m_hReplayItem( hReplayItem ), m_pItemManager( pItemManager ), m_bMouseOver( false ), m_pTitleEntry( NULL ), m_pHeaderLine( NULL ), m_pClickToEditLabel( NULL ), m_pCaratLabel( NULL ) { ivgui()->AddTickSignal( GetVPanel(), 10 ); } CTitleEditPanel::~CTitleEditPanel() { ivgui()->RemoveTickSignal( GetVPanel() ); } void CTitleEditPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replaybrowser/titleeditpanel.res", "GAME" ); // Get ptr to carat label m_pCaratLabel = dynamic_cast< CExLabel * >( FindChildByName( "CaratLabel" ) ); // Get ptr to "click to edit" label m_pClickToEditLabel = dynamic_cast< CExLabel * >( FindChildByName( "ClickToEditLabel" ) ); // Setup title entry m_pTitleEntry = dynamic_cast< TextEntry * >( FindChildByName( "TitleInput" ) ); m_pTitleEntry->SelectAllOnFocusAlways( true ); #if !defined( TF_CLIENT_DLL ) m_pTitleEntry->SetPaintBorderEnabled( false ); #endif // Setup title entry text IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem ); const wchar_t *pTitle = pReplayItem->GetItemTitle(); m_pTitleEntry->SetText( pTitle[0] ? pTitle : L"#Replay_DefaultDetailsTitle" ); // Cache pointer to the image m_pHeaderLine = dynamic_cast< ImagePanel * >( FindChildByName( "HeaderLine" ) ); } void CTitleEditPanel::PerformLayout() { BaseClass::PerformLayout(); int nCaratW, nCaratH; m_pCaratLabel->GetContentSize( nCaratW, nCaratH ); m_pCaratLabel->SetWide( nCaratW ); m_pCaratLabel->SetTall( nCaratH ); // Get title entry pos int nTitleEntryX, nTitleEntryY; m_pTitleEntry->GetPos( nTitleEntryX, nTitleEntryY ); // Set width of title entry to be width of parent, which has margins m_pTitleEntry->SetToFullHeight(); m_pTitleEntry->SetWide( GetParent()->GetWide() - nTitleEntryX - 1 ); // Get content size for label int nClickToEditW, nClickToEditH; m_pClickToEditLabel->GetContentSize( nClickToEditW, nClickToEditH ); // Set click-to-edit bounds int nTitleEntryTall = m_pTitleEntry->GetTall(); m_pClickToEditLabel->SetBounds( nTitleEntryX + GetParent()->GetWide() - nClickToEditW * 1.4f, nTitleEntryY + ( nTitleEntryTall - nClickToEditH ) / 2, nClickToEditW, nClickToEditH ); // Setup header line position m_pHeaderLine->SetPos( 0, nTitleEntryY + m_pTitleEntry->GetTall() * 1.2f ); } void CTitleEditPanel::OnTick() { int nMouseX, nMouseY; input()->GetCursorPos( nMouseX, nMouseY ); m_bMouseOver = m_pTitleEntry->IsWithin( nMouseX, nMouseY ); } void CTitleEditPanel::PaintBackground() { bool bEditing = m_pTitleEntry->HasFocus(); bool bDrawExtraStuff = !vgui::input()->GetAppModalSurface() && ( m_bMouseOver || bEditing ); // Don't draw extra stuff when render dialog is up // If mouse is over and we're not editing, show the "click to edit" label m_pClickToEditLabel->SetVisible( m_bMouseOver && !bEditing ); // Draw border if necessary if ( bDrawExtraStuff ) { // Use the game UI panel here, since using this panel's vpanel (PushMakeCurrent() is set in // Panel::PaintTraverse(), the function that calls PaintBackground()) causes dimmed top and // left lines. Using the game UI panel allows painting outside of the text entry itself. vgui::VPANEL vGameUI = enginevgui->GetPanel( PANEL_GAMEUIDLL ); // Calculate title entry rect (x0,y0,x1,y1) - move the absolute upper-left corner 1 pixel left & up int aTitleRect[4]; ipanel()->GetAbsPos( m_pTitleEntry->GetVPanel(), aTitleRect[0], aTitleRect[1] ); --aTitleRect[0]; --aTitleRect[1]; aTitleRect[2] = aTitleRect[0] + m_pTitleEntry->GetWide() + 2; aTitleRect[3] = aTitleRect[1] + m_pTitleEntry->GetTall() + 2; surface()->PushMakeCurrent( vGameUI, false ); // Draw background surface()->DrawSetColor( Color( 29, 28, 26, 255 ) ); surface()->DrawFilledRect( aTitleRect[0], aTitleRect[1], aTitleRect[2], aTitleRect[3] ); // Draw stroke surface()->DrawSetColor( Color( 202, 190, 164, 255 ) ); surface()->DrawLine( aTitleRect[0], aTitleRect[1], aTitleRect[2], aTitleRect[1] ); // Top surface()->DrawLine( aTitleRect[0], aTitleRect[1], aTitleRect[0], aTitleRect[3] ); // Left surface()->DrawLine( aTitleRect[0], aTitleRect[3], aTitleRect[2], aTitleRect[3] ); // Bottom surface()->DrawLine( aTitleRect[2], aTitleRect[1], aTitleRect[2], aTitleRect[3] ); // Right surface()->PopMakeCurrent( vGameUI ); } } void CTitleEditPanel::OnKeyCodeTyped( KeyCode code ) { IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem ); const wchar_t *pTitle = pReplayItem->GetItemTitle(); if ( m_pTitleEntry->HasFocus() && pReplayItem ) { if ( code == KEY_ESCAPE ) { // Get replay text and reset it m_pTitleEntry->SetText( pTitle ); // Remove focus GetParent()->GetParent()->RequestFocus(); } else if ( code == KEY_ENTER ) { // If text is empty, reset to old title if ( m_pTitleEntry->GetTextLength() == 0 ) { m_pTitleEntry->SetText( pTitle ); } else // Save title... { // Copy text into the replay // NOTE: SetItemTitle() will mark replay as dirty wchar_t wszNewTitle[MAX_REPLAY_TITLE_LENGTH]; m_pTitleEntry->GetText( wszNewTitle, sizeof( wszNewTitle ) ); pReplayItem->SetItemTitle( wszNewTitle ); // Save! g_pReplayManager->FlagReplayForFlush( pReplayItem->GetItemReplay(), true ); // Notify the thumbnail void *pUserData = pReplayItem->GetUserData(); if ( pUserData ) { CReplayBrowserThumbnail *pThumbnail = (CReplayBrowserThumbnail*)pUserData; pThumbnail->UpdateTitleText(); } } GetParent()->GetParent()->RequestFocus(); } return; } BaseClass::OnKeyCodeTyped( code ); } //----------------------------------------------------------------------------- CPlaybackPanel::CPlaybackPanel( Panel *pParent ) : EditablePanel( pParent, "PlaybackPanel" ) { } CPlaybackPanel::~CPlaybackPanel() { ivgui()->RemoveTickSignal( GetVPanel() ); } void CPlaybackPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replaybrowser/playbackpanel.res", "GAME" ); } void CPlaybackPanel::PerformLayout() { BaseClass::PerformLayout(); } //----------------------------------------------------------------------------- CPlaybackPanelSlideshow::CPlaybackPanelSlideshow( Panel *pParent, ReplayHandle_t hReplay ) : CPlaybackPanel( pParent ), m_hReplay( hReplay ) { m_pScreenshotImage = new CReplayScreenshotSlideshowPanel( this, "Screenshot", hReplay ); } void CPlaybackPanelSlideshow::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replaybrowser/playbackpanelslideshow.res", "GAME" ); m_pNoScreenshotLabel = dynamic_cast< CExLabel * >( FindChildByName( "NoScreenshotLabel" ) ); // Check to see if there's a screenshot CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay ); if ( !pReplay ) return; if ( !pReplay->GetScreenshotCount() && m_pNoScreenshotLabel ) // Show no-screenshot label { m_pNoScreenshotLabel->SetVisible( true ); } } void CPlaybackPanelSlideshow::PerformLayout() { BaseClass::PerformLayout(); CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay ); int nMarginWidth = GetMarginSize(); int nScreenshotWidth = GetViewWidth(); if ( m_pScreenshotImage ) { m_pScreenshotImage->SetBounds( nMarginWidth, nMarginWidth, nScreenshotWidth, GetTall() - 2*nMarginWidth ); // Setup screenshot scale based on width of first screenshot (if there are any screenshots at all) - otherwise don't scale float flScale = pReplay->GetScreenshotCount() == 0 ? 1.0f : ( (float)nScreenshotWidth / ( .95f * pReplay->GetScreenshot( 0 )->m_nWidth ) ); m_pScreenshotImage->GetImagePanel()->SetScaleAmount( flScale ); m_pScreenshotImage->GetImagePanel()->SetShouldScaleImage( true ); } // Setup the label int nLabelW, nLabelH; m_pNoScreenshotLabel->GetContentSize( nLabelW, nLabelH ); m_pNoScreenshotLabel->SetBounds( 0, ( GetTall() - nLabelH ) / 2, GetWide(), nLabelH ); } //----------------------------------------------------------------------------- CPlaybackPanelMovie::CPlaybackPanelMovie( Panel *pParent, ReplayHandle_t hMovie ) : CPlaybackPanel( pParent ) { IReplayMovie *pMovie = g_pReplayMovieManager->GetMovie( hMovie ); m_pMoviePlayerPanel = new CMoviePlayerPanel( this, "MoviePlayer", pMovie->GetMovieFilename() ); m_pMoviePlayerPanel->SetLooping( true ); // TODO: show controls and don't play right away m_pMoviePlayerPanel->Play(); } void CPlaybackPanelMovie::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); } void CPlaybackPanelMovie::PerformLayout() { BaseClass::PerformLayout(); m_pMoviePlayerPanel->SetBounds( 9, 9, GetViewWidth(), GetViewHeight() ); m_pMoviePlayerPanel->SetEnabled( true ); m_pMoviePlayerPanel->SetVisible( true ); m_pMoviePlayerPanel->SetZPos( 101 ); } void CPlaybackPanelMovie::FreeMovieMaterial() { m_pMoviePlayerPanel->FreeMaterial(); } //----------------------------------------------------------------------------- CCutImagePanel::CCutImagePanel( Panel *pParent, const char *pName ) : BaseClass( pParent, pName, "" ), m_pSelectedBorder( NULL ) { } void CCutImagePanel::SetSelected( bool bState ) { BaseClass::SetSelected( bState ); } IBorder *CCutImagePanel::GetBorder( bool bDepressed, bool bArmed, bool bSelected, bool bKeyFocus ) { if ( bSelected ) { return m_pSelectedBorder; } return BaseClass::GetBorder( bDepressed, bArmed, bSelected, bKeyFocus ); } void CCutImagePanel::ApplySchemeSettings( vgui::IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); } //----------------------------------------------------------------------------- #define FOR_EACH_BUTTON( _i ) for ( int _i = 0; _i < BUTTONS_PER_PAGE; ++_i ) CCutsPanel::CCutsPanel( Panel *pParent, ReplayHandle_t hReplay, int iSelectedPerformance ) : BaseClass( pParent, "CutsPanel", hReplay ), m_iPage( 0 ), m_nVisibleButtons( 0 ), m_pVerticalLine( NULL ), m_pNoCutsLabel( NULL ), m_pOriginalLabel( NULL ), m_pCutsLabel( NULL ) { m_hDetailsPanel = dynamic_cast< CReplayDetailsPanel * >( pParent->GetParent() ); FOR_EACH_BUTTON( i ) { const int iButton = i; CFmtStr fmtName( "CutButton%i", iButton ); CExImageButton *pNewButton = new CExImageButton( this, fmtName.Access(), "" ); CFmtStr fmtCommand( "select_%i", iButton ); pNewButton->SetCommand( fmtCommand.Access() ); pNewButton->InvalidateLayout( true, true ); pNewButton->AddActionSignalTarget( this ); pNewButton->SetSelected( i == 0 ); #if !defined( TF_CLIENT_DLL ) pNewButton->SetSelectedColor( Color( 0, 0, 0, 0 ), Color( 122, 25, 16, 255 ) ); #endif const int iPerformance = i - 1; m_aButtons[ i ].m_pButton = pNewButton; m_aButtons[ i ].m_iPerformance = iPerformance; CExButton *pAddToRenderQueueButton = new CExButton( pNewButton, "AddToRenderQueue", "+", this ); m_aButtons[ i ].m_pAddToRenderQueueButton = pAddToRenderQueueButton; } // Layout right now InvalidateLayout( true, true ); // Calculate page SetPage( ( 1 + iSelectedPerformance ) / BUTTONS_PER_PAGE, ( 1 + iSelectedPerformance ) % BUTTONS_PER_PAGE ); ivgui()->AddTickSignal( GetVPanel(), 10 ); } CCutsPanel::~CCutsPanel() { ivgui()->RemoveTickSignal( GetVPanel() ); } void CCutsPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replaybrowser/cutspanel.res", "GAME" ); m_pVerticalLine = dynamic_cast< EditablePanel * >( FindChildByName( "VerticalLine" ) ); m_pNoCutsLabel = dynamic_cast< CExLabel * >( FindChildByName( "NoCutsLabel" ) ); m_pOriginalLabel = dynamic_cast< CExLabel * >( FindChildByName( "OriginalLabel" ) ); m_pCutsLabel = dynamic_cast< CExLabel * >( FindChildByName( "CutsLabel" ) ); m_pNameLabel = dynamic_cast< CExLabel * >( FindChildByName( "NameLabel" ) ); m_pPrevButton = dynamic_cast< CExButton * >( FindChildByName( "PrevButton" ) ); m_pNextButton = dynamic_cast< CExButton * >( FindChildByName( "NextButton" ) ); FOR_EACH_BUTTON( i ) { CExImageButton *pCurButton = m_aButtons[ i ].m_pButton; #if !defined( TF_CLIENT_DLL ) pCurButton->SetPaintBorderEnabled( false ); #endif pCurButton->InvalidateLayout( true, true ); } } void CCutsPanel::ApplySettings( KeyValues *pInResourceData ) { BaseClass::ApplySettings( pInResourceData ); KeyValues *pButtonSettings = pInResourceData->FindKey( "button_settings" ); if ( pButtonSettings ) { KeyValues *pAddToRenderQueueButtonSettings = pButtonSettings->FindKey( "addtorenderqueuebutton_settings" ); CReplay *pReplay = GetGenericClassBasedReplay( m_hReplay ); FOR_EACH_BUTTON( i ) { CExImageButton *pCurButton = m_aButtons[ i ].m_pButton; pCurButton->ApplySettings( pButtonSettings ); // Set screenshot as image if ( pReplay && pReplay->GetScreenshotCount() ) { const float flScale = (float)m_nCutButtonHeight / pReplay->GetScreenshot( 0 )->m_nHeight; int nImageWidth = m_nCutButtonWidth - 2 * m_nCutButtonBuffer; int nImageHeight = m_nCutButtonHeight - 2 * m_nCutButtonBuffer; CFmtStr fmtFile( "replay\\thumbnails\\%s", pReplay->GetScreenshot( 0 )->m_szBaseFilename ); pCurButton->SetSubImage( fmtFile.Access() ); pCurButton->GetImage()->SetScaleAmount( flScale ); pCurButton->GetImage()->SetBounds( m_nCutButtonBuffer, m_nCutButtonBuffer, nImageWidth, nImageHeight ); } if ( pAddToRenderQueueButtonSettings ) { CExButton *pAddToQueueButton = m_aButtons[ i ].m_pAddToRenderQueueButton; pAddToQueueButton->ApplySettings( pAddToRenderQueueButtonSettings ); pAddToQueueButton->AddActionSignalTarget( this ); } } } } void CCutsPanel::PerformLayout() { BaseClass::PerformLayout(); CReplay *pReplay = GetGenericClassBasedReplay( m_hReplay ); const int nNumCuts = pReplay->GetPerformanceCount(); int nX = m_iPage > 0 ? m_pPrevButton->GetWide() + m_nCutButtonSpace : 0; m_nVisibleButtons = 0; FOR_EACH_BUTTON( i ) { CExImageButton *pCurButton = m_aButtons[ i ].m_pButton; const bool bVisible = ButtonToPerformance( i ) < nNumCuts; pCurButton->SetVisible( bVisible ); if ( bVisible ) { ++m_nVisibleButtons; } pCurButton->SetBounds( nX, m_nButtonStartY, m_nCutButtonWidth, m_nCutButtonHeight ); nX += m_nCutButtonWidth; if ( i == 0 && m_iPage == 0 ) { nX += 2 * m_nCutButtonSpaceWide + m_pVerticalLine->GetWide(); } else { nX += m_nCutButtonSpace; } } if ( m_pVerticalLine ) { m_pVerticalLine->SetVisible( m_nVisibleButtons > 0 && m_iPage == 0 ); m_pVerticalLine->SetPos( m_nCutButtonWidth + m_nCutButtonSpaceWide, 0 ); m_pVerticalLine->SetTall( m_nTopMarginHeight + GetTall() ); } const int nRightOfVerticalLineX = m_nCutButtonWidth + m_nCutButtonSpaceWide * 2 + m_pVerticalLine->GetWide(); if ( m_pNoCutsLabel ) { m_pNoCutsLabel->SetVisible( m_nVisibleButtons == 1 && m_iPage == 0 ); int nY = ( GetTall() - m_pNoCutsLabel->GetTall() ) / 2; m_pNoCutsLabel->SetPos( nRightOfVerticalLineX, nY ); } if ( m_pOriginalLabel ) { m_pOriginalLabel->SetVisible( m_iPage == 0 ); } if ( m_pCutsLabel ) { m_pCutsLabel->SetVisible( m_nVisibleButtons > 1 && m_iPage == 0 ); m_pCutsLabel->SetPos( m_nCutButtonWidth + 2 * m_nCutButtonSpaceWide + m_pVerticalLine->GetWide(), 0 ); } bool bPrevCuts = m_iPage > 0; bool bMoreCuts = ( nNumCuts + 1 ) > ( m_iPage + 1 ) * BUTTONS_PER_PAGE; int nY = m_nTopMarginHeight + ( GetTall() - m_pNextButton->GetTall() ) / 2; m_pPrevButton->SetVisible( bPrevCuts ); m_pPrevButton->SetPos( 0, nY ); m_pNextButton->SetVisible( bMoreCuts ); m_pNextButton->SetPos( nX, nY ); } void CCutsPanel::OnTick() { if ( !TFModalStack()->IsEmpty() ) return; int nMouseX, nMouseY; input()->GetCursorPos( nMouseX, nMouseY ); // Early-out if not within the cuts panel at all. if ( !IsWithin( nMouseX, nMouseY ) ) return; int iHoverPerformance = -2; bool bFoundHoverButton = false; FOR_EACH_BUTTON( i ) { CExImageButton *pCurButton = m_aButtons[ i ].m_pButton; bool bIsHoverButton = false; if ( !bFoundHoverButton && pCurButton->IsWithin( nMouseX, nMouseY ) && pCurButton->IsVisible() ) { iHoverPerformance = ButtonToPerformance( i ); bFoundHoverButton = true; bIsHoverButton = true; } CExButton *pAddToRenderQueueButton = m_aButtons[ i ].m_pAddToRenderQueueButton; if ( pAddToRenderQueueButton ) { pAddToRenderQueueButton->SetVisible( bIsHoverButton ); if ( iHoverPerformance >= -1 ) { // Set the text and command based on whether or not the take's already been queued const bool bInQueue = g_pClientReplayContext->GetRenderQueue()->IsInQueue( m_hReplay, iHoverPerformance ); CFmtStr fmtCmd( "%srenderqueue_%i", bInQueue ? "removefrom" : "addto", iHoverPerformance ); pAddToRenderQueueButton->SetCommand( fmtCmd.Access() ); pAddToRenderQueueButton->SetText( bInQueue ? "-" : "+" ); } } } // If the mouse is over a performance button, use that, otherwise use the selected // performance. if ( m_hDetailsPanel.Get() ) { int iSelectedPerformance = m_hDetailsPanel->m_iSelectedPerformance; UpdateNameLabel( iHoverPerformance >= 0 ? iHoverPerformance : iSelectedPerformance >= 0 ? iSelectedPerformance : -1 ); } } int CCutsPanel::ButtonToPerformance( int iButton ) const { return -1 + m_iPage * BUTTONS_PER_PAGE + iButton; } void CCutsPanel::OnCommand( const char *pCommand ) { if ( !V_strnicmp( pCommand, "select_", 7 ) ) { const int iButton = atoi( pCommand + 7 ); SelectButtonFromPerformance( ButtonToPerformance( iButton ) ); } else if ( !V_stricmp( pCommand, "prevpage" ) ) { SetPage( m_iPage - 1 ); } else if ( !V_stricmp( pCommand, "nextpage" ) ) { SetPage( m_iPage + 1 ); } else if ( !V_strnicmp( pCommand, "addtorenderqueue_", 17 ) ) { if ( !replay_renderqueue_first_add.GetInt() ) { ShowMessageBox( "#Replay_FirstRenderQueueAddTitle", "#Replay_FirstRenderQueueAddMsg", "#GameUI_OK" ); replay_renderqueue_first_add.SetValue( 1 ); } const int iPerformance = atoi( pCommand + 17 ); if ( iPerformance >= -1 ) { g_pClientReplayContext->GetRenderQueue()->Add( m_hReplay, iPerformance ); } } else if ( !V_strnicmp( pCommand, "removefromrenderqueue_", 22 ) ) { const int iPerformance = atoi( pCommand + 22 ); if ( iPerformance >= -1 ) { g_pClientReplayContext->GetRenderQueue()->Remove( m_hReplay, iPerformance ); } } } void CCutsPanel::SetPage( int iPage, int iButtonToSelect ) { m_iPage = iPage; FOR_EACH_BUTTON( i ) { ButtonInfo_t *pCurButtonInfo = &m_aButtons[ i ]; const int iPerformance = ButtonToPerformance( i ); pCurButtonInfo->m_iPerformance = iPerformance; } InvalidateLayout( true, false ); SelectButtonFromPerformance( ButtonToPerformance( iButtonToSelect ) ); } const CReplayPerformance *CCutsPanel::GetPerformance( int iPerformance ) const { const CReplay *pReplay = GetGenericClassBasedReplay( m_hReplay ); if ( !pReplay ) return NULL; return iPerformance >= 0 ? pReplay->GetPerformance( iPerformance ) : NULL; } void CCutsPanel::SelectButtonFromPerformance( int iPerformance ) { FOR_EACH_BUTTON( i ) { const ButtonInfo_t *pCurButtonInfo = &m_aButtons[ i ]; CExImageButton *pCurButton = pCurButtonInfo->m_pButton; pCurButton->SetSelected( pCurButtonInfo->m_iPerformance == iPerformance ); pCurButton->InvalidateLayout( true, true ); } // Cache which performance to use in the details panel if ( m_hDetailsPanel.Get() ) { m_hDetailsPanel->m_iSelectedPerformance = iPerformance; } UpdateNameLabel( iPerformance ); } int CCutsPanel::PerformanceToButton( int iPerformance ) const { FOR_EACH_BUTTON( i ) { if ( m_aButtons[ i ].m_iPerformance == iPerformance ) { return i; } } return -1; } void CCutsPanel::UpdateNameLabel( int iPerformance ) { const CReplayPerformance *pPerformance = GetPerformance( iPerformance ); m_pNameLabel->SetText( pPerformance ? pPerformance->m_wszTitle : L"" ); // Get the button (in the range [0,BUTTONS_PER_PAGE]). const int iPerformanceButton = PerformanceToButton( iPerformance ); // Not necessarily the selected button - can be hover button // Get position of the button so we can use it's x position. int aSelectedButtonPos[2]; m_aButtons[ iPerformanceButton ].m_pButton->GetPos( aSelectedButtonPos[0], aSelectedButtonPos[1] ); if ( m_pNameLabel ) { const int nNameLabelX = aSelectedButtonPos[0]; const int nNameLabelY = m_nButtonStartY + m_nCutButtonHeight + m_nNameLabelTopMargin; m_pNameLabel->SetBounds( nNameLabelX, nNameLabelY, GetWide() - nNameLabelX, GetTall() - nNameLabelY ); } } void CCutsPanel::OnPerformanceDeleted( int iPerformance ) { int iButton = PerformanceToButton( iPerformance ); if ( iButton < 0 ) return; // Deleted last performance on page? CReplay *pReplay = GetGenericClassBasedReplay( m_hReplay ); const int nNumCuts = pReplay->GetPerformanceCount(); if ( iPerformance == m_aButtons[ 0 ].m_iPerformance && iPerformance == nNumCuts ) { SetPage( m_iPage - 1, BUTTONS_PER_PAGE - 1 ); } else { SelectButtonFromPerformance( ButtonToPerformance( MIN( m_nVisibleButtons - 1, MAX( 0, iButton ) ) ) ); } // Select the cut prior to the one we just deleted Assert( iPerformance >= 0 ); InvalidateLayout( true, false ); } //----------------------------------------------------------------------------- static void ConfirmUploadMovie( bool bConfirmed, void *pContext ) { if ( bConfirmed ) { CReplayDetailsPanel *pPanel = (CReplayDetailsPanel*)pContext; IQueryableReplayItem *pReplayItem = pPanel->m_pItemManager->GetItem( pPanel->m_hReplayItem ); if ( pReplayItem && pReplayItem->IsItemAMovie() ) { IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem ); if ( YouTube_GetLoginStatus() != kYouTubeLogin_LoggedIn ) { YouTube_ShowLoginDialog( pMovie, pPanel ); } else { YouTube_ShowUploadDialog( pMovie, pPanel ); } } } } class CYouTubeGetStatsHandler : public CYouTubeResponseHandler { public: CYouTubeGetStatsHandler( CReplayDetailsPanel *pPanel ) : m_pPanel( pPanel ) , m_handle( NULL ) { } virtual ~CYouTubeGetStatsHandler() { if ( m_handle != NULL ) { YouTube_CancelGetVideoInfo( m_handle ); } } static bool GetEmptyElementTagContents( const char *pXML, const char *pTag, CUtlString &strTagContents ) { CFmtStr1024 kLinkTagStart( "<%s ", pTag ); const char *kLinkTagEnd = "/>"; const char *pStart = strstr( pXML, kLinkTagStart.Access() ); if ( pStart != NULL ) { pStart += kLinkTagStart.Length(); const char *pEnd = strstr( pStart, kLinkTagEnd ); if ( pEnd != NULL ) { strTagContents.SetDirect( pStart, pEnd - pStart ); return true; } } return false; } static bool GetEmptyTagValue( const char *pTagContents, const char *pKeyName, CUtlString &value ) { CFmtStr1024 kStart( "%s='", pKeyName ); const char *kEnd = "'"; const char *pStart = strstr( pTagContents, kStart.Access() ); if ( pStart != NULL ) { pStart += kStart.Length(); const char *pEnd = strstr( pStart, kEnd ); if ( pEnd != NULL ) { value.SetDirect( pStart, pEnd - pStart ); return true; } } return false; } virtual void HandleResponse( long responseCode, const char *pResponse ) { // @note tom bui: wish I had an XML parser if ( strstr( pResponse, "Private video" ) != NULL ) { m_pPanel->m_pYouTubeInfoPanel->SetInfo( g_pVGuiLocalize->Find( "#YouTube_PrivateVideo" ) ); m_pPanel->SetYouTubeStatus( CReplayDetailsPanel::kYouTubeStatus_Private ); return; } int iNumFavorited = 0; int iNumViews = 0; int iNumLikes = 0; wchar_t wszFavorited[256] = L"0"; wchar_t wszViews[256] = L"0"; CUtlString strTagStatistics; if ( GetEmptyElementTagContents( pResponse, "yt:statistics", strTagStatistics ) ) { CUtlString favoriteCount; CUtlString viewCount; GetEmptyTagValue( strTagStatistics, "favoriteCount", favoriteCount ); GetEmptyTagValue( strTagStatistics, "viewCount", viewCount ); iNumFavorited = Q_atoi( favoriteCount.Get() ); iNumViews = Q_atoi( viewCount.Get() ); g_pVGuiLocalize->ConvertANSIToUnicode( favoriteCount.Get(), wszFavorited, sizeof( wszFavorited ) ); g_pVGuiLocalize->ConvertANSIToUnicode( viewCount.Get(), wszViews, sizeof( wszViews ) ); } wchar_t wszLikes[256] = L"0"; CUtlString strTagRating; if ( GetEmptyElementTagContents( pResponse, "yt:rating", strTagRating ) ) { CUtlString likes; GetEmptyTagValue( strTagRating, "numLikes", likes ); iNumLikes = Q_atoi( likes.Get() ); g_pVGuiLocalize->ConvertANSIToUnicode( likes.Get(), wszLikes, sizeof( wszLikes ) ); } //const char *kLinkStartTag = "ConstructString( wszStats,sizeof( wszStats ), g_pVGuiLocalize->Find( "#YouTube_Stats" ), 3, wszFavorited, wszViews, wszLikes ); if ( m_strVideoURL.IsEmpty() == false ) { m_pPanel->m_pYouTubeInfoPanel->SetInfo( wszStats ); m_pPanel->SetYouTubeStatus( CReplayDetailsPanel::kYouTubeStatus_RetrievedInfo ); m_pPanel->InvalidateLayout(); IGameEvent *event = gameeventmanager->CreateEvent( "replay_youtube_stats" ); if ( event ) { event->SetInt( "views", iNumViews ); event->SetInt( "likes", iNumLikes ); event->SetInt( "favorited", iNumFavorited ); gameeventmanager->FireEventClientSide( event ); } } else { m_pPanel->m_pYouTubeInfoPanel->SetInfo( g_pVGuiLocalize->Find( "#YouTube_CouldNotRetrieveStats" ) ); m_pPanel->SetYouTubeStatus( CReplayDetailsPanel::kYouTubeStatus_CouldNotRetrieveInfo ); } } CReplayDetailsPanel *m_pPanel; YouTubeInfoHandle_t m_handle; CUtlString m_strVideoURL; }; CReplayDetailsPanel::CReplayDetailsPanel( Panel *pParent, QueryableReplayItemHandle_t hReplayItem, int iPerformance, IReplayItemManager *pItemManager ) : EditablePanel( pParent, "DetailsPanel" ), m_hReplayItem( hReplayItem ), m_pItemManager( pItemManager ), m_pCutsPanel( NULL ), m_iSelectedPerformance( iPerformance ), m_pYouTubeResponseHandler( NULL ), m_hExportMovieDialog( NULL ) { m_hReplay = pItemManager->GetItem( hReplayItem )->GetItemReplayHandle(); CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay ); m_pInsetPanel = new EditablePanel( this, "InsetPanel" ); m_pTitleEditPanel = new CTitleEditPanel( GetInset(), m_hReplayItem, m_pItemManager ); m_pPlaybackPanel = new CPlaybackPanelSlideshow( GetInset(), m_hReplay ); m_pRecordsPanel = new CRecordsPanel( GetInset(), m_hReplay ); m_pInfoPanel = new EditablePanel( this, "InfoContainerPanel" ); m_pScrollPanel = new vgui::ScrollableEditablePanel( GetInset(), m_pInfoPanel, "StatsScroller" ); m_pScrollPanel->GetScrollbar()->SetAutohideButtons( true ); #if !defined( TF_CLIENT_DLL ) for ( int i = 0; i < 2; ++i ) { m_pScrollPanel->GetScrollbar()->GetButton( i )->SetPaintBorderEnabled( false ); } #endif m_pBasicInfoPanel = new CBasicLifeInfoPanel( m_pInfoPanel, m_hReplay ); m_pStatsPanel = new CStatsPanel( m_pInfoPanel, m_hReplay ); m_pKillsPanel = new CKillsPanel( m_pInfoPanel, m_hReplay ); const bool bIsMoviePanel = pItemManager->AreItemsMovies(); if ( bIsMoviePanel ) { m_pYouTubeInfoPanel = new CYouTubeInfoPanel( m_pInfoPanel ); } else { m_pCutsPanel = new CCutsPanel( GetInset(), m_hReplay, m_iSelectedPerformance ); } // Add info panels to a list if ( pReplay->GetDominationCount() ) { m_pDominationsPanel = new CDominationsPanel( m_pInfoPanel, m_hReplay ); m_vecInfoPanels.AddToTail( m_pDominationsPanel ); } m_vecInfoPanels.AddToTail( m_pBasicInfoPanel ); m_vecInfoPanels.AddToTail( m_pStatsPanel ); m_vecInfoPanels.AddToTail( m_pKillsPanel ); if ( bIsMoviePanel ) { m_vecInfoPanels.AddToTail( m_pYouTubeInfoPanel ); } m_pYouTubeResponseHandler = new CYouTubeGetStatsHandler( this ); RequestFocus(); } CReplayDetailsPanel::~CReplayDetailsPanel() { m_pDeleteButton->MarkForDeletion(); m_pRenderButton->MarkForDeletion(); m_pPlayButton->MarkForDeletion(); delete m_pYouTubeResponseHandler; } void CReplayDetailsPanel::ApplySchemeSettings( IScheme *pScheme ) { BaseClass::ApplySchemeSettings( pScheme ); LoadControlSettings( "resource/ui/replaybrowser/detailspanel.res", "GAME" ); m_pExportMovie = dynamic_cast< CExButton * >( FindChildByName( "ExportMovieButton" ) ); m_pDeleteButton = dynamic_cast< CExButton * >( FindChildByName( "DeleteButton" ) ); m_pRenderButton = dynamic_cast< CExButton * >( FindChildByName( "RenderButton" ) ); m_pPlayButton = dynamic_cast< CExButton * >( FindChildByName( "PlayButton" ) ); m_pYouTubeUpload = dynamic_cast< CExButton * >( FindChildByName( "YouTubeUploadButton" ) ); m_pYouTubeView = dynamic_cast< CExButton * >( FindChildByName( "ViewYouTubeButton" ) ); m_pYouTubeShareURL = dynamic_cast< CExButton * >( FindChildByName( "ShareYouTubeURLButton" ) ); m_pShowRenderInfoButton = dynamic_cast< CExImageButton * >( FindChildByName( "ShowRenderInfoButton") ); if ( m_pDeleteButton ) { SetXToRed( m_pDeleteButton ); } m_pExportMovie->SetParent( GetInset() ); m_pYouTubeUpload->SetParent( GetInset() ); m_pYouTubeView->SetParent( GetInset() ); m_pYouTubeShareURL->SetParent( GetInset() ); m_pShowRenderInfoButton->SetParent( GetInset() ); m_pDeleteButton->SetParent( GetParent()->GetParent()->GetParent() ); m_pPlayButton->SetParent( GetParent()->GetParent()->GetParent() ); m_pRenderButton->SetParent( GetParent()->GetParent()->GetParent() ); m_pDeleteButton->AddActionSignalTarget( this ); m_pPlayButton->AddActionSignalTarget( this ); m_pRenderButton->AddActionSignalTarget( this ); } void CReplayDetailsPanel::PerformLayout() { BaseClass::PerformLayout(); SetTall( GetParent()->GetTall() ); int nInsetWidth = GetInset()->GetWide(); int nScreenshotWidth = nInsetWidth * .55f; // Setup info panels along the right-hand side const int nBuffer = 7; const int nLeftRightBuffer = 19; int aPlaybackPos[2]; m_pPlaybackPanel->GetPos( aPlaybackPos[0], aPlaybackPos[1] ); int nInfoPanelsStartY = aPlaybackPos[1]; int nInfoPanelsCurrentY = nInfoPanelsStartY; int nRightColumnWidth = nInsetWidth - nScreenshotWidth - nLeftRightBuffer - XRES(20); #if defined( TF_CLIENT_DLL ) if ( m_pRecordsPanel->ShouldShow() ) { m_pRecordsPanel->SetPos( nScreenshotWidth + nLeftRightBuffer, nInfoPanelsStartY ); m_pRecordsPanel->SetWide( nRightColumnWidth ); m_pRecordsPanel->InvalidateLayout( true, true ); m_pRecordsPanel->SetVisible( true ); nInfoPanelsCurrentY += m_pRecordsPanel->GetTall() + nBuffer; } else #endif { m_pRecordsPanel->SetVisible( false ); } int insetX, insetY; GetInset()->GetPos( insetX, insetY ); m_pScrollPanel->SetPos( nScreenshotWidth + nLeftRightBuffer, nInfoPanelsCurrentY ); m_pScrollPanel->SetWide( nRightColumnWidth + XRES(20) ); m_pScrollPanel->SetTall( GetTall() - insetY - nInfoPanelsCurrentY ); m_pInfoPanel->SetWide( nRightColumnWidth ); int nCurrentY = 0; for ( int i = 0; i < m_vecInfoPanels.Count(); ++i ) { CBaseDetailsPanel *pPanel = m_vecInfoPanels[ i ]; if ( pPanel->ShouldShow() ) { // Set the width since these panel's PerformLayout()'s depend on it pPanel->SetWide( nRightColumnWidth ); // Call panel's PerformLayout() now pPanel->InvalidateLayout( true, true ); pPanel->SetPos( 0, nCurrentY ); // Show it pPanel->SetVisible( true ); // Update the current y position based on the panel's height (set in its PerformLayout()) nCurrentY += pPanel->GetTall() + nBuffer; } else { pPanel->SetVisible( false ); } } m_pInfoPanel->SetTall( nCurrentY ); m_pInfoPanel->InvalidateLayout( true ); m_pScrollPanel->InvalidateLayout( true ); m_pScrollPanel->GetScrollbar()->SetAutohideButtons( true ); m_pScrollPanel->GetScrollbar()->InvalidateLayout( true ); // @note Tom Bui: set the positions AGAIN now that we've invalidated, cause VGUI hates me nCurrentY = 0; for ( int i = 0; i < m_vecInfoPanels.Count(); ++i ) { CBaseDetailsPanel *pPanel = m_vecInfoPanels[ i ]; if ( pPanel->ShouldShow() ) { pPanel->SetPos( 0, nCurrentY ); nCurrentY += pPanel->GetTall() + nBuffer; } } // Setup playback panel based on dimensions of first screenshot CGenericClassBasedReplay *pReplay = GetGenericClassBasedReplay( m_hReplay ); float flAspectRatio; if ( pReplay->GetScreenshotCount() ) { const CReplayScreenshot *pScreenshot = pReplay->GetScreenshot( 0 ); flAspectRatio = (float)pScreenshot->m_nWidth / pScreenshot->m_nHeight; } else { // Default to 4:3 if there are no screenshots flAspectRatio = 4.0f/3; } if ( m_pItemManager->AreItemsMovies() ) { m_pRenderButton->SetVisible( false ); m_pPlayButton->SetVisible( false ); m_pExportMovie->SetVisible( true ); m_pShowRenderInfoButton->SetVisible( true ); int nButtonY = nInfoPanelsStartY + m_pPlaybackPanel->GetTall() + YRES( 5 ); int nButtonX = 0; m_pYouTubeUpload->SetPos( nButtonX, nButtonY ); m_pYouTubeView->SetPos( nButtonX, nButtonY ); nButtonX += m_pYouTubeUpload->GetWide() + XRES( 5 ); m_pYouTubeShareURL->SetPos( nButtonX, nButtonY ); nButtonX += m_pYouTubeShareURL->GetWide() + XRES( 5 ); m_pExportMovie->SetPos( nButtonX, nButtonY ); int aDeletePos[2]; m_pDeleteButton->GetPos( aDeletePos[0], aDeletePos[1] ); m_pDeleteButton->SetPos( ScreenWidth() / 2 + XRES( 195 ), aDeletePos[1] ); int aScreenshotPos[2]; m_pPlaybackPanel->GetPos( aScreenshotPos[0], aScreenshotPos[1] ); m_pShowRenderInfoButton->SetPos( aScreenshotPos[0] + m_pPlaybackPanel->GetWide() - m_pShowRenderInfoButton->GetWide() - XRES( 8 ), aScreenshotPos[1] + m_pPlaybackPanel->GetTall() - m_pShowRenderInfoButton->GetTall() - YRES( 8 ) ); // retrieve stats if ( m_pYouTubeResponseHandler->m_handle == NULL ) { IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem ); if ( pReplayItem && pReplayItem->IsItemAMovie() ) { IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem ); if ( pMovie->IsUploaded() ) { m_pYouTubeResponseHandler->m_handle = YouTube_GetVideoInfo( pMovie->GetUploadURL(), *m_pYouTubeResponseHandler ); SetYouTubeStatus( kYouTubeStatus_RetrievingInfo ); } else { SetYouTubeStatus( kYouTubeStatus_NotUploaded ); } } } } else { m_pYouTubeUpload->SetVisible( false ); m_pYouTubeView->SetVisible( false ); m_pYouTubeShareURL->SetVisible( false ); m_pShowRenderInfoButton->SetVisible( false ); // Without this, the name label won't show when we automatically select the recently watched/saved // performance, because the cuts panel width/height isn't set when UpdateNameLabel() gets called // from within CCutsPanel::CCutsPanel(). m_pCutsPanel->UpdateNameLabel( m_iSelectedPerformance ); } } /*static*/ void CReplayDetailsPanel::OnPlayerWarningDlgConfirm( bool bConfirmed, void *pContext ) { CReplayDetailsPanel *pPanel = (CReplayDetailsPanel*)pContext; pPanel->ShowExportDialog(); } void CReplayDetailsPanel::ShowExportDialog() { IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem ); if ( pReplayItem && pReplayItem->IsItemAMovie() ) { IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem ); CFmtStr srcMovieFullFilename( "%s%s", g_pReplayMovieManager->GetRenderDir(), pMovie->GetMovieFilename() ); if ( !g_pFullFileSystem->FileExists( srcMovieFullFilename.Access() ) ) { ShowMessageBox( "#Replay_ExportMovieError_Title", "#Replay_ExportMovieNoFile_Text", "#GameUI_OK" ); return; } } if ( m_hExportMovieDialog == NULL ) { m_hExportMovieDialog = new FileOpenDialog(NULL, "#Replay_FindExportMovieLocation", FOD_SAVE ); #ifdef USE_WEBM_FOR_REPLAY m_hExportMovieDialog->AddFilter("*.webm", "#Replay_WebMMovieFiles", true ); #else m_hExportMovieDialog->AddFilter("*.mov", "#Replay_MovieFiles", true ); #endif m_hExportMovieDialog->AddActionSignalTarget( this ); if ( !FStrEq( replay_movie_export_last_dir.GetString(), "" ) ) { m_hExportMovieDialog->SetStartDirectory( replay_movie_export_last_dir.GetString() ); } } m_hExportMovieDialog->DoModal(false); m_hExportMovieDialog->Activate(); } void CReplayDetailsPanel::OnFileSelected( const char *fullpath ) { // this can take a while, put up a waiting cursor surface()->SetCursor(dc_hourglass); IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem ); if ( pReplayItem && pReplayItem->IsItemAMovie() ) { IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem ); CFmtStr srcMovieFullFilename( "%s%s", g_pReplayMovieManager->GetRenderDir(), pMovie->GetMovieFilename() ); if ( !engine->CopyLocalFile( srcMovieFullFilename.Access(), fullpath ) ) { ShowMessageBox( "#Replay_ExportMovieError_Title", "#Replay_ExportMovieError_Text", "#GameUI_OK" ); } else { ShowMessageBox( "#Replay_ExportMovieSuccess_Title", "#Replay_ExportMovieSuccess_Text", "#GameUI_OK" ); } char basepath[ MAX_PATH ]; Q_ExtractFilePath( fullpath, basepath, sizeof( basepath ) ); replay_movie_export_last_dir.SetValue( basepath ); } // change the cursor back to normal surface()->SetCursor(dc_user); } void CReplayDetailsPanel::OnCommand( const char *pCommand ) { if ( FStrEq( pCommand, "delete_replayitem" ) ) { ReplayUI_GetBrowserPanel()->AttemptToDeleteReplayItem( this, m_hReplayItem, m_pItemManager, m_iSelectedPerformance ); return; } else if ( FStrEq( pCommand, "render_replay_dlg" ) ) { ShowRenderDialog(); return; } else if ( FStrEq( pCommand, "play" ) ) { if ( engine->IsInGame() ) { ShowPlayConfirmationDialog(); } else { g_pClientReplayContext->PlayReplay( m_hReplay, m_iSelectedPerformance, true ); } return; } else if ( FStrEq( pCommand, "exportmovie" ) ) { IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem ); if ( !pReplayItem || !pReplayItem->IsItemAMovie() ) return; IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem ); if ( !pMovie ) return; if ( replay_movie_reveal_warning.GetBool() ) { #ifdef USE_WEBM_FOR_REPLAY CTFMessageBoxDialog *pDialog = ShowMessageBox( "#Replay_Tip", "#Replay_UseVLCPlayer", "#Replay_ThanksIWill", OnPlayerWarningDlgConfirm ); #else CTFMessageBoxDialog *pDialog = ShowMessageBox( "#Replay_Tip", "#Replay_UseQuickTimePlayer", "#Replay_ThanksIWill", OnPlayerWarningDlgConfirm ); #endif pDialog->SetContext( this ); replay_movie_reveal_warning.SetValue( 0 ); } else if ( pMovie->GetRenderSettings().m_bRaw ) { ShowMessageBox( "#Replay_CantExport", "#YouTube_Upload_MovieIsRaw", "#GameUI_OK" ); } else { ShowExportDialog(); } } else if ( FStrEq( pCommand, "youtubeupload" ) ) { IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem ); if ( pReplayItem && pReplayItem->IsItemAMovie() ) { IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem ); if ( !pMovie ) return; if ( pMovie->GetRenderSettings().m_bRaw ) { ShowMessageBox( "#Replay_CantUpload", "#YouTube_Upload_MovieIsRaw", "#GameUI_OK" ); return; } // Movie already exists? CFmtStr srcMovieFullFilename( "%s%s", g_pReplayMovieManager->GetRenderDir(), pMovie->GetMovieFilename() ); if ( !g_pFullFileSystem->FileExists( srcMovieFullFilename.Access() ) ) { ShowMessageBox( "#YouTube_Upload_Title", "#YouTube_Upload_MissingFile", "#GameUI_OK" ); return; } else if ( pMovie->IsUploaded() ) { CTFGenericConfirmDialog *pDialog = ShowConfirmDialog( "#YouTube_Upload_Title", "#YouTube_FileAlreadyUploaded", "#GameUI_OK", "#GameuI_CancelBold", &ConfirmUploadMovie, this ); pDialog->SetContext( this ); } else { ConfirmUploadMovie( true, this ); } } } else if ( FStrEq( pCommand, "viewyoutube" ) ) { if ( steamapicontext && steamapicontext->SteamFriends() && m_pYouTubeResponseHandler->m_strVideoURL.IsEmpty() == false ) { steamapicontext->SteamFriends()->ActivateGameOverlayToWebPage( m_pYouTubeResponseHandler->m_strVideoURL.Get() ); } } else if ( FStrEq( pCommand, "shareyoutubeurl" ) ) { system()->SetClipboardText( m_pYouTubeResponseHandler->m_strVideoURL.Get(), m_pYouTubeResponseHandler->m_strVideoURL.Length() ); ShowMessageBox( "#Replay_CopyURL_Title", "#Replay_CopyURL_Text", "#GameUI_OK" ); } else if ( FStrEq( pCommand, "showrenderinfo" ) ) { ShowRenderInfo(); } else { BaseClass::OnCommand( pCommand ); } } void CReplayDetailsPanel::ShowRenderInfo() { IQueryableReplayItem *pReplayItem = m_pItemManager->GetItem( m_hReplayItem ); if ( !pReplayItem || !pReplayItem->IsItemAMovie() ) return; IReplayMovie *pMovie = static_cast< IReplayMovie * >( pReplayItem ); const ReplayRenderSettings_t &Settings = pMovie->GetRenderSettings(); const wchar_t *pCodecName = g_pVideo ? g_pVideo->GetCodecName( Settings.m_Codec ) : L"?"; wchar_t *pAAEnabled = g_pVGuiLocalize->Find( Settings.m_bAAEnabled ? "#Replay_Enabled" : "#Replay_Disabled" ); wchar_t *pRaw = g_pVGuiLocalize->Find( Settings.m_bRaw ? "#Replay_Yes" : "#Replay_No" ); CFmtStr fmtRes( "%ix%i", Settings.m_nWidth, Settings.m_nHeight ); CFmtStr fmtFramerate( "%.3f", Settings.m_FPS.GetFPS() ); KeyValuesAD kvParams( "params" ); kvParams->SetString( "res", fmtRes.Access() ); kvParams->SetString( "framerate", fmtFramerate.Access() ); kvParams->SetInt( "motionblurquality", Settings.m_nMotionBlurQuality ); kvParams->SetInt( "encodingquality", Settings.m_nEncodingQuality ); kvParams->SetWString( "codec", pCodecName ); kvParams->SetWString( "antialiasing", pAAEnabled ); kvParams->SetString( "rendertime", CReplayTime::FormatTimeString( pMovie->GetRenderTime() ) ); kvParams->SetWString( "raw", pRaw ); wchar_t wszStr[1024]; g_pVGuiLocalize->ConstructString( wszStr, sizeof( wszStr ), "#Replay_MovieRenderInfo", kvParams ); ShowMessageBox( "#Replay_RenderInfo", wszStr, "#GameUI_OK" ); } void CReplayDetailsPanel::GoBack() { // Send to parent GetParent()->OnCommand( "back" ); } void CReplayDetailsPanel::ShowPlayConfirmationDialog() { CConfirmDisconnectFromServerDialog *pConfirm = SETUP_PANEL( new CConfirmDisconnectFromServerDialog( this ) ); if ( pConfirm ) { pConfirm->Show(); } } void CReplayDetailsPanel::OnConfirmDisconnect( KeyValues *pParams ) { if ( pParams->GetBool( "confirmed" ) ) { g_pClientReplayContext->PlayReplay( m_hReplay, m_iSelectedPerformance, true ); } } void CReplayDetailsPanel::OnMessage( const KeyValues* pParams, VPANEL hFromPanel ) { if ( FStrEq( pParams->GetName(), "ReplayItemDeleted" ) ) { const int iPerformance = const_cast< KeyValues * >( pParams )->GetInt( "perf", -1 ); if ( iPerformance >= 0 ) { CReplayPerformance *pPerformance = GetGenericClassBasedReplay( m_hReplay )->GetPerformance( m_iSelectedPerformance ); g_pReplayPerformanceManager->DeletePerformance( pPerformance ); m_pCutsPanel->InvalidateLayout( true, false ); // Without this, m_nVisibleButtons will be wrong. m_pCutsPanel->OnPerformanceDeleted( m_iSelectedPerformance ); } else { GoBack(); } return; } BaseClass::OnMessage( pParams, hFromPanel ); } void CReplayDetailsPanel::ShowRenderDialog() { ::ReplayUI_ShowRenderDialog( this, m_hReplay, false, m_iSelectedPerformance ); } void CReplayDetailsPanel::FreeMovieFileLock() { m_pPlaybackPanel->FreeMovieMaterial(); } void CReplayDetailsPanel::SetYouTubeStatus( eYouTubeStatus status ) { m_pYouTubeUpload->SetVisible( status == kYouTubeStatus_CouldNotRetrieveInfo || status == kYouTubeStatus_NotUploaded ); m_pYouTubeUpload->SetEnabled( status == kYouTubeStatus_CouldNotRetrieveInfo || status == kYouTubeStatus_NotUploaded ); m_pYouTubeView->SetVisible( !m_pYouTubeUpload->IsVisible() ); m_pYouTubeView->SetEnabled( status == kYouTubeStatus_RetrievedInfo ); m_pYouTubeShareURL->SetEnabled( status == kYouTubeStatus_RetrievedInfo ); } void CReplayDetailsPanel::OnMousePressed( MouseCode code ) { if ( code == MOUSE_LEFT ) { RequestFocus(); } } void CReplayDetailsPanel::OnKeyCodeTyped( KeyCode code ) { if ( code == KEY_DELETE ) { OnCommand( "delete_replayitem" ); } BaseClass::OnKeyCodeTyped( code ); } #endif