Team Fortress 2 Source Code as on 22/4/2020
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.

483 lines
13 KiB

  1. //========= Copyright Valve Corporation, All rights reserved. ============//
  2. //
  3. //=======================================================================================//
  4. #include "cbase.h"
  5. #if defined( REPLAY_ENABLED )
  6. #include "replaybrowserlistpanel.h"
  7. #include "ienginevgui.h"
  8. #include "vgui/ISurface.h"
  9. #include "vgui/IInput.h"
  10. #include "vgui/IVGui.h"
  11. #include "vgui_controls/ScrollBar.h"
  12. #include "vgui_controls/ScrollBarSlider.h"
  13. #include "replaybrowserlistitempanel.h"
  14. #include "replaybrowserpreviewpanel.h"
  15. #include "replaybrowserbasepage.h"
  16. #include "replay/ireplaymoviemanager.h"
  17. #include "replay/ireplaymanager.h"
  18. #include "replaybrowsermainpanel.h"
  19. // memdbgon must be the last include file in a .cpp file!!!
  20. #include <tier0/memdbgon.h>
  21. //-----------------------------------------------------------------------------
  22. extern IClientReplayContext *g_pClientReplayContext;
  23. extern IReplayMovieManager *g_pReplayMovieManager;
  24. extern const char *GetMapDisplayName( const char *mapName );
  25. //-----------------------------------------------------------------------------
  26. DECLARE_BUILD_FACTORY( CReplayListPanel );
  27. //-----------------------------------------------------------------------------
  28. #define MAX_MOVIE_THUMBNAILS 12 // The remaining movies will be put into a single collection
  29. //-----------------------------------------------------------------------------
  30. CReplayListPanel::CReplayListPanel( Panel *pParent, const char *pName )
  31. : BaseClass( pParent, pName ),
  32. m_pPrevHoverPanel( NULL ),
  33. m_pPreviewPanel( NULL )
  34. {
  35. ivgui()->AddTickSignal( GetVPanel(), 10 );
  36. m_pBorderArrowImg = new ImagePanel( this, "ArrowImage" );
  37. // Add replays and movies collections, which will contain all replays & movies.
  38. m_pReplaysCollection = new CReplayThumbnailCollection( this, "ReplayThumbnailCollection", GetReplayItemManager() );
  39. m_pMoviesCollection = new CMovieThumbnailCollection( this, "MovieThumbnailCollection", GetReplayMovieItemManager(), true );
  40. m_vecCollections.AddToTail( m_pReplaysCollection );
  41. m_vecCollections.AddToTail( m_pMoviesCollection );
  42. m_wszFilter[0] = L' ';
  43. m_wszFilter[1] = NULL;
  44. }
  45. CReplayListPanel::~CReplayListPanel()
  46. {
  47. ivgui()->RemoveTickSignal( GetVPanel() );
  48. }
  49. void CReplayListPanel::ApplySchemeSettings( vgui::IScheme *pScheme )
  50. {
  51. BaseClass::ApplySchemeSettings( pScheme );
  52. LoadControlSettings( "resource/ui/replaybrowser/replaylistpanel.res", "GAME" );
  53. #if !defined( TF_CLIENT_DLL )
  54. SetPaintBorderEnabled( false );
  55. #endif
  56. MoveScrollBarToTop();
  57. vgui::ScrollBar *pScrollBar = dynamic_cast< vgui::ScrollBar * >( FindChildByName( "PanelListPanelVScroll" ) );
  58. pScrollBar->SetScrollbarButtonsVisible( false );
  59. Color clrButtonColor = GetSchemeColor( "Yellow", Color( 255, 255, 255, 255 ), pScheme );
  60. Color clrBgColor = GetSchemeColor( "TanDark", Color( 255, 255, 255, 255 ), pScheme );
  61. const int nWidth = XRES( 5 );
  62. pScrollBar->SetSize( nWidth, GetTall() );
  63. pScrollBar->GetSlider()->SetSize( nWidth, GetTall() );
  64. }
  65. void CReplayListPanel::PerformLayout()
  66. {
  67. BaseClass::PerformLayout();
  68. }
  69. void CReplayListPanel::OnMouseWheeled(int delta)
  70. {
  71. if ( !GetScrollbar()->IsVisible() )
  72. return;
  73. BaseClass::OnMouseWheeled( delta );
  74. }
  75. void CReplayListPanel::SetupBorderArrow( bool bLeft )
  76. {
  77. m_pBorderArrowImg->SetVisible( true );
  78. m_pBorderArrowImg->SetImage( bLeft ? "replay/replay_balloon_arrow_left" : "replay/replay_balloon_arrow_right" );
  79. m_pBorderArrowImg->SetZPos( 1000 );
  80. m_pBorderArrowImg->GetImage()->GetContentSize( m_aBorderArrowDims[0], m_aBorderArrowDims[1] );
  81. m_pBorderArrowImg->SetSize( m_aBorderArrowDims[0], m_aBorderArrowDims[1] );
  82. }
  83. void CReplayListPanel::ClearPreviewPanel()
  84. {
  85. if ( m_pPreviewPanel )
  86. {
  87. m_pPreviewPanel->MarkForDeletion();
  88. m_pPreviewPanel = NULL;
  89. }
  90. }
  91. void CReplayListPanel::ApplyFilter( const wchar_t *pFilterText )
  92. {
  93. Q_wcsncpy( m_wszFilter, pFilterText, sizeof( m_wszFilter ) );
  94. V_wcslower( m_wszFilter );
  95. m_pReplaysCollection->RemoveAll();
  96. m_pMoviesCollection->RemoveAll();
  97. m_pPrevHoverPanel = NULL;
  98. ClearPreviewPanel();
  99. RemoveAll();
  100. AddReplaysToList();
  101. FOR_EACH_VEC( m_vecCollections, i )
  102. {
  103. m_vecCollections[i]->OnUpdated();
  104. }
  105. InvalidateLayout();
  106. }
  107. void CReplayListPanel::OnTick()
  108. {
  109. if ( !enginevgui->IsGameUIVisible() )
  110. return;
  111. CReplayBrowserPanel *pReplayBrowser = ReplayUI_GetBrowserPanel();
  112. if ( !pReplayBrowser || !pReplayBrowser->IsVisible() )
  113. return;
  114. int x,y;
  115. vgui::input()->GetCursorPos(x, y);
  116. // If the deletion confirmation dialog is up
  117. if ( vgui::input()->GetAppModalSurface() )
  118. {
  119. ClearPreviewPanel();
  120. // Hide the preview arrow
  121. m_pBorderArrowImg->SetVisible( false );
  122. return;
  123. }
  124. CReplayBrowserThumbnail *pOverPanel = FindThumbnailAtCursor( x, y );
  125. if ( m_pPrevHoverPanel != pOverPanel )
  126. {
  127. if ( m_pPrevHoverPanel )
  128. {
  129. OnItemPanelExited( m_pPrevHoverPanel );
  130. }
  131. m_pPrevHoverPanel = pOverPanel;
  132. if ( m_pPrevHoverPanel )
  133. {
  134. OnItemPanelEntered( m_pPrevHoverPanel );
  135. }
  136. }
  137. }
  138. void CReplayListPanel::OnItemPanelEntered( vgui::Panel *pPanel )
  139. {
  140. CReplayBrowserThumbnail *pThumbnail = dynamic_cast< CReplayBrowserThumbnail * >( pPanel );
  141. if ( IsVisible() && pThumbnail && pThumbnail->IsVisible() )
  142. {
  143. ClearPreviewPanel();
  144. // Determine which type of preview panel to display
  145. IReplayItemManager *pItemManager;
  146. IQueryableReplayItem *pReplayItem = FindReplayItem( pThumbnail->m_hReplayItem, &pItemManager );
  147. AssertMsg( pReplayItem, "Why is this happening?" );
  148. if ( !pReplayItem )
  149. return;
  150. if ( pReplayItem->IsItemAMovie() )
  151. {
  152. m_pPreviewPanel = new CReplayPreviewPanelBase( this, pReplayItem->GetItemHandle(), pItemManager );
  153. }
  154. else
  155. {
  156. m_pPreviewPanel = new CReplayPreviewPanelSlideshow( this, pReplayItem->GetItemHandle(), pItemManager );
  157. }
  158. m_pPreviewPanel->InvalidateLayout( true, true );
  159. int x,y;
  160. pThumbnail->GetPosRelativeToAncestor( this, x, y );
  161. int nXPos, nYPos;
  162. int nOffset = XRES( 1 );
  163. nXPos = ( x > GetWide()/2 ) ? ( x - m_pPreviewPanel->GetWide() - nOffset ) : ( x + pThumbnail->GetWide() + nOffset );
  164. nYPos = y + ( pThumbnail->GetTall() - m_pPreviewPanel->GetTall() ) / 2;
  165. // Make sure the popup stays onscreen.
  166. if ( nXPos < 0 )
  167. {
  168. nXPos = 0;
  169. }
  170. else if ( (nXPos + m_pPreviewPanel->GetWide()) > GetWide() )
  171. {
  172. nXPos = GetWide() - m_pPreviewPanel->GetWide();
  173. }
  174. if ( nYPos < 0 )
  175. {
  176. nYPos = 0;
  177. }
  178. else if ( (nYPos + m_pPreviewPanel->GetTall()) > GetTall() )
  179. {
  180. // Move it up as much as we can without it going below the bottom
  181. nYPos = GetTall() - m_pPreviewPanel->GetTall();
  182. }
  183. // Setup the balloon's arrow
  184. bool bLeftArrow = x < (GetWide() / 2);
  185. SetupBorderArrow( bLeftArrow ); // Sets proper image and caches image dims in m_aBorderArrowDims
  186. int nArrowXPos, nArrowYPos;
  187. const int nPreviewBorderWidth = 2; // Should be just big enough to cover the preview's border width
  188. if ( bLeftArrow )
  189. {
  190. // Setup the arrow along the left-hand side
  191. nArrowXPos = nXPos - m_aBorderArrowDims[0] + nPreviewBorderWidth;
  192. }
  193. else
  194. {
  195. nArrowXPos = nXPos + m_pPreviewPanel->GetWide() - nPreviewBorderWidth;
  196. }
  197. nArrowYPos = MIN( nYPos + m_pPreviewPanel->GetTall() - m_pBorderArrowImg->GetTall() * 2, y + ( pThumbnail->m_pScreenshotThumb->GetTall() - m_aBorderArrowDims[1] ) / 2 );
  198. m_pBorderArrowImg->SetPos( nArrowXPos, nArrowYPos );
  199. m_pPreviewPanel->SetPos( nXPos, nYPos );
  200. m_pPreviewPanel->SetVisible( true );
  201. surface()->PlaySound( "replay\\replaypreviewpopup.wav" );
  202. }
  203. }
  204. void CReplayListPanel::OnItemPanelExited( vgui::Panel *pPanel )
  205. {
  206. CReplayBrowserThumbnail *pThumbnail = dynamic_cast < CReplayBrowserThumbnail * > ( pPanel );
  207. if ( pThumbnail && IsVisible() && m_pPreviewPanel )
  208. {
  209. m_pBorderArrowImg->SetVisible( false );
  210. ClearPreviewPanel();
  211. }
  212. }
  213. CBaseThumbnailCollection *CReplayListPanel::FindOrAddReplayThumbnailCollection( const IQueryableReplayItem *pItem, IReplayItemManager *pItemManager )
  214. {
  215. Assert( pItem );
  216. if ( pItem->IsItemAMovie() )
  217. {
  218. return m_pMoviesCollection;
  219. }
  220. return m_pReplaysCollection;
  221. }
  222. void CReplayListPanel::AddReplaysToList()
  223. {
  224. // Cache off list item pointers into a temp list for processing
  225. CUtlLinkedList< IQueryableReplayItem *, int > lstMovies;
  226. CUtlLinkedList< IQueryableReplayItem *, int > lstReplays;
  227. // Add all replays to a replays list
  228. g_pReplayManager->GetReplaysAsQueryableItems( lstReplays );
  229. // Add all movies to a movies list
  230. g_pReplayMovieManager->GetMoviesAsQueryableItems( lstMovies );
  231. // Go through all movies, and add them to the proper collection, based on date
  232. FOR_EACH_LL( lstMovies, i )
  233. {
  234. if ( PassesFilter( lstMovies[ i ] ) )
  235. {
  236. AddReplayItem( lstMovies[ i ]->GetItemHandle() );
  237. }
  238. }
  239. // Add any replays to the "temporary replays" collection
  240. FOR_EACH_LL( lstReplays, i )
  241. {
  242. if ( PassesFilter( lstReplays[ i ] ) )
  243. {
  244. m_pReplaysCollection->AddReplay( lstReplays[ i ] );
  245. }
  246. }
  247. // Add all collection panels to the list panel
  248. FOR_EACH_VEC( m_vecCollections, i )
  249. {
  250. AddItem( NULL, m_vecCollections[ i ] );
  251. }
  252. }
  253. void CReplayListPanel::RemoveCollection( CBaseThumbnailCollection *pCollection )
  254. {
  255. // Never remove our two base collections. If they have no entries, they display messages instead.
  256. if ( pCollection == m_pMoviesCollection || pCollection == m_pReplaysCollection )
  257. return;
  258. // Find the item and remove it
  259. int i = FirstItem();
  260. while ( i != InvalidItemID() )
  261. {
  262. if ( GetItemPanel( i ) == pCollection )
  263. {
  264. int nNextI = NextItem( i );
  265. RemoveItem( i );
  266. i = nNextI;
  267. }
  268. else
  269. {
  270. i = NextItem( i );
  271. }
  272. }
  273. // Remove our own cached ptr
  274. i = m_vecCollections.Find( pCollection );
  275. if ( i != m_vecCollections.InvalidIndex() )
  276. {
  277. m_vecCollections.Remove( i );
  278. }
  279. }
  280. CReplayBrowserThumbnail *CReplayListPanel::FindThumbnailAtCursor( int x, int y )
  281. {
  282. // Is the cursor hovering over any of the thumbnails?
  283. FOR_EACH_VEC( m_vecCollections, i )
  284. {
  285. CBaseThumbnailCollection *pCollection = m_vecCollections[ i ];
  286. if ( pCollection->IsWithin( x, y ) )
  287. {
  288. FOR_EACH_VEC( pCollection->m_vecRows, j )
  289. {
  290. CReplayBrowserThumbnailRow *pRow = pCollection->m_vecRows[ j ];
  291. if ( pRow->IsWithin( x, y ) )
  292. {
  293. FOR_EACH_VEC( pRow->m_vecThumbnails, k )
  294. {
  295. CReplayBrowserThumbnail *pThumbnail = pRow->m_vecThumbnails[ k ];
  296. if ( pThumbnail->IsWithin( x, y ) )
  297. {
  298. return pThumbnail;
  299. }
  300. }
  301. }
  302. }
  303. }
  304. }
  305. return NULL;
  306. }
  307. #if defined( WIN32 )
  308. #define Q_wcstok( text, delimiters, context ) wcstok( text, delimiters ); context;
  309. #elif defined( OSX ) || defined( LINUX )
  310. #define Q_wcstok( text, delimiters, context ) wcstok( text, delimiters, context )
  311. #endif
  312. bool CReplayListPanel::PassesFilter( IQueryableReplayItem *pItem )
  313. {
  314. CGenericClassBasedReplay *pReplay = ToGenericClassBasedReplay( pItem->GetItemReplay() );
  315. if ( !pReplay )
  316. return false;
  317. wchar_t wszSearchableText[1024] = L"";
  318. wchar_t wszTemp[256];
  319. // title
  320. const wchar_t *pTitle = pItem->GetItemTitle();
  321. V_wcscat_safe( wszSearchableText, pTitle );
  322. V_wcscat_safe( wszSearchableText, L" " );
  323. // map
  324. const char *pMapName = GetMapDisplayName( pReplay->m_szMapName );
  325. g_pVGuiLocalize->ConvertANSIToUnicode( pMapName, wszTemp, sizeof( wszTemp ) );
  326. V_wcscat_safe( wszSearchableText, wszTemp );
  327. V_wcscat_safe( wszSearchableText, L" " );
  328. // player class
  329. g_pVGuiLocalize->ConvertANSIToUnicode( pReplay->GetPlayerClass(), wszTemp, sizeof( wszTemp ) );
  330. V_wcscat_safe( wszSearchableText, wszTemp );
  331. V_wcscat_safe( wszSearchableText, L" " );
  332. // killer name
  333. if ( pReplay->WasKilled() )
  334. {
  335. g_pVGuiLocalize->ConvertANSIToUnicode( pReplay->GetKillerName(), wszTemp, sizeof( wszTemp ) );
  336. V_wcscat_safe( wszSearchableText, wszTemp );
  337. V_wcscat_safe( wszSearchableText, L" " );
  338. }
  339. // lower case
  340. V_wcslower( wszSearchableText );
  341. wchar_t wszFilter[256];
  342. Q_wcsncpy( wszFilter, m_wszFilter, sizeof( wszFilter ) );
  343. bool bPasses = true;
  344. wchar_t seps[] = L" ";
  345. wchar_t *last = NULL;
  346. wchar_t *token = Q_wcstok( wszFilter, seps, &last );
  347. while ( token && bPasses )
  348. {
  349. bPasses &= wcsstr( wszSearchableText, token ) != NULL;
  350. token = Q_wcstok( NULL, seps, &last );
  351. }
  352. return bPasses;
  353. }
  354. void CReplayListPanel::AddReplayItem( ReplayItemHandle_t hItem )
  355. {
  356. IReplayItemManager *pItemManager;
  357. const IQueryableReplayItem *pItem = FindReplayItem( hItem, &pItemManager );
  358. if ( !pItem )
  359. return;
  360. // Find or add the collection
  361. CBaseThumbnailCollection *pCollection = FindOrAddReplayThumbnailCollection( pItem, pItemManager );
  362. // Add the replay
  363. pCollection->AddReplay( pItem );
  364. }
  365. void CReplayListPanel::CleanupUIForReplayItem( ReplayItemHandle_t hReplayItem )
  366. {
  367. IReplayItemManager *pItemManager;
  368. const IQueryableReplayItem *pReplayItem = FindReplayItem( hReplayItem, &pItemManager ); AssertValidReadPtr( pReplayItem );
  369. CBaseThumbnailCollection *pCollection = NULL;
  370. FOR_EACH_VEC( m_vecCollections, i )
  371. {
  372. CBaseThumbnailCollection *pCurCollection = m_vecCollections[ i ];
  373. if ( pCurCollection->FindReplayItemThumbnailRow( pReplayItem ) )
  374. {
  375. pCollection = pCurCollection;
  376. break;
  377. }
  378. }
  379. // Find the collection associated with the given replay - NOTE: we pass false here for the "bAddIfNotFound" param
  380. if ( !pCollection )
  381. {
  382. AssertMsg( 0, "REPLAY: Should have found collection while attempting to delete a replay from the browser." );
  383. return;
  384. }
  385. // Clear the previous hover pointer to avoid a potential crash where we've got a stale ptr
  386. m_pPrevHoverPanel = NULL;
  387. // Clear out the preview panel if it exists and is for the given replay necessary
  388. if ( m_pPreviewPanel && m_pPreviewPanel->GetReplayHandle() == pReplayItem->GetItemReplayHandle() )
  389. {
  390. ClearPreviewPanel();
  391. m_pBorderArrowImg->SetVisible( false );
  392. }
  393. pCollection->CleanupUIForReplayItem( hReplayItem );
  394. }
  395. #endif