888 lines
26 KiB
C++
888 lines
26 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "SStatsViewer.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Misc/ConfigCacheIni.h"
|
|
#include "Misc/App.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Widgets/SBoxPanel.h"
|
|
#include "Textures/SlateIcon.h"
|
|
#include "Framework/Commands/UIAction.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Input/SComboButton.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "StatsViewerModule.h"
|
|
#include "PropertyEditorModule.h"
|
|
#include "IPropertyTableRow.h"
|
|
#include "IPropertyTableColumn.h"
|
|
#include "IPropertyTableCell.h"
|
|
#include "StatsPageManager.h"
|
|
#include "IPropertyTableCustomColumn.h"
|
|
#include "ObjectHyperlinkColumn.h"
|
|
#include "StatsCustomColumn.h"
|
|
#include "ActorArrayHyperlinkColumn.h"
|
|
#include "StatsViewerUtils.h"
|
|
#include "Widgets/Input/SSearchBox.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogStatsViewer, Log, All);
|
|
|
|
#define LOCTEXT_NAMESPACE "Editor.StatsViewer"
|
|
|
|
namespace StatsViewerConstants
|
|
{
|
|
/** Delay (in seconds) after a new character is entered into the search box to wait before updating the list (to give them time to enter a whole string instead of useless updating every time a char is put in) **/
|
|
static const float SearchTextUpdateDelay = 0.5f;
|
|
|
|
/** Stat viewer config file section name */
|
|
static const FString ConfigSectionName = "StatsViewer";
|
|
}
|
|
|
|
namespace StatsViewerMetadata
|
|
{
|
|
static const FName ColumnWidth( "ColumnWidth" );
|
|
static const FName SortMode( "SortMode" );
|
|
}
|
|
|
|
void SStatsViewer::Construct( const FArguments& InArgs )
|
|
{
|
|
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>( "PropertyEditor" );
|
|
|
|
StatsPageManagerPtr = InArgs._StatsPageManager;
|
|
|
|
// create empty property table
|
|
PropertyTable = PropertyEditorModule.CreatePropertyTable();
|
|
PropertyTable->SetIsUserAllowedToChangeRoot( false );
|
|
PropertyTable->SetOrientation( EPropertyTableOrientation::AlignPropertiesInColumns );
|
|
PropertyTable->SetShowRowHeader( false );
|
|
PropertyTable->SetShowObjectName( false );
|
|
|
|
// we want to customize some columns
|
|
TArray< TSharedRef< IPropertyTableCustomColumn > > CustomColumns;
|
|
FStatsPageManager& StatsPageManager = GetStatsPageManager();
|
|
for( int32 PageIndex = 0; PageIndex < StatsPageManager.NumPages(); PageIndex++ )
|
|
{
|
|
TSharedRef<IStatsPage> StatsPage = StatsPageManager.GetPageByIndex( PageIndex );
|
|
TArray< TSharedRef< IPropertyTableCustomColumn > > PagesCustomColumns;
|
|
StatsPage->GetCustomColumns(PagesCustomColumns);
|
|
if(PagesCustomColumns.Num() > 0)
|
|
{
|
|
CustomColumns.Append(PagesCustomColumns);
|
|
}
|
|
}
|
|
|
|
CustomColumns.Add( MakeShareable(new FObjectHyperlinkColumn) );
|
|
CustomColumns.Add( MakeShareable(new FActorArrayHyperlinkColumn) );
|
|
CustomColumns.Add( CustomColumn );
|
|
|
|
ChildSlot
|
|
[
|
|
SNew( SVerticalBox )
|
|
+SVerticalBox::Slot()
|
|
.Padding(FMargin(0.0f, 0.0f, 0.0f, 4.0f))
|
|
.AutoHeight()
|
|
[
|
|
SNew( SBorder )
|
|
.BorderImage( FAppStyle::GetBrush("ToolPanel.GroupBorder") )
|
|
.Padding(4.0f)
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(0.0f)
|
|
[
|
|
SNew( SComboButton )
|
|
.ContentPadding(3.f)
|
|
.OnGetMenuContent( this, &SStatsViewer::OnGetDisplayMenuContent )
|
|
.ButtonContent()
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( this, &SStatsViewer::OnGetDisplayMenuLabel )
|
|
.ToolTipText( LOCTEXT( "DisplayedStatistic_Tooltip", "Choose the statistics to display" ) )
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(0.0f)
|
|
[
|
|
SNew( SButton )
|
|
.Visibility( this, &SStatsViewer::OnGetStatsVisibility )
|
|
.ContentPadding(3.f)
|
|
.OnClicked( this, &SStatsViewer::OnRefreshClicked )
|
|
.Content()
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "Refresh", "Refresh" ) )
|
|
.ToolTipText( LOCTEXT( "Refresh_Tooltip", "Refresh the displayed statistics" ) )
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(0.0f)
|
|
[
|
|
SNew( SButton )
|
|
.Visibility( this, &SStatsViewer::OnGetStatsVisibility )
|
|
.ContentPadding(3.f)
|
|
.OnClicked( this, &SStatsViewer::OnExportClicked )
|
|
.Content()
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( LOCTEXT( "Export", "Export" ) )
|
|
.ToolTipText( LOCTEXT( "Export_Tooltip", "Export the displayed statistics to a CSV file" ) )
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(0.0f)
|
|
[
|
|
SAssignNew( CustomContent, SBorder )
|
|
.BorderImage( FAppStyle::GetBrush("NoBorder") )
|
|
.Padding(0.0f)
|
|
.Visibility( this, &SStatsViewer::OnGetStatsVisibility )
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.Padding(0.0f)
|
|
.HAlign(HAlign_Right)
|
|
[
|
|
SAssignNew( CustomFilter, SBorder )
|
|
.BorderImage( FAppStyle::GetBrush("NoBorder") )
|
|
.Padding(0.0f)
|
|
]
|
|
]
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.Padding(FMargin(0.0f, 0.0f, 0.0f, 4.0f))
|
|
.FillHeight(1.0f)
|
|
[
|
|
SNew( SBorder )
|
|
.BorderImage( FAppStyle::GetBrush("ToolPanel.GroupBorder") )
|
|
.Visibility( this, &SStatsViewer::OnGetStatsVisibility )
|
|
.Padding(4.0f)
|
|
[
|
|
PropertyEditorModule.CreatePropertyTableWidget( PropertyTable.ToSharedRef(), CustomColumns )
|
|
]
|
|
]
|
|
+SVerticalBox::Slot()
|
|
.Padding(FMargin(0.0f, 0.0f, 0.0f, 4.0f))
|
|
.AutoHeight()
|
|
[
|
|
SNew( SBorder )
|
|
.BorderImage( FAppStyle::GetBrush("ToolPanel.GroupBorder") )
|
|
.Visibility( this, &SStatsViewer::OnGetStatsVisibility )
|
|
.Padding(4.0f)
|
|
[
|
|
SNew( SHorizontalBox )
|
|
+SHorizontalBox::Slot()
|
|
.FillWidth(1.0f)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SAssignNew( FilterTextBoxWidget, SSearchBox )
|
|
.HintText( LOCTEXT( "FilterDisplayedStatistics", "Filter Displayed Statistics" ) )
|
|
.ToolTipText( LOCTEXT( "FilterDisplayedStatistics_Tooltip", "Type here to filter displayed statistics" ) )
|
|
.OnTextChanged( this, &SStatsViewer::OnFilterTextChanged )
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.Padding(2, 1, 0, 0)
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew( SComboButton )
|
|
.Visibility( this, &SStatsViewer::OnGetStatsVisibility )
|
|
.ContentPadding(2.f)
|
|
.OnGetMenuContent( this, &SStatsViewer::OnGetFilterMenuContent )
|
|
.ButtonContent()
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( this, &SStatsViewer::OnGetFilterComboButtonLabel )
|
|
.ToolTipText( LOCTEXT( "FilterColumnToUse_Tooltip", "Choose the statistic to filter when searching" ) )
|
|
]
|
|
]
|
|
]
|
|
]
|
|
];
|
|
|
|
// Display stats page from previous stat viewer instance
|
|
if (!CurrentStats.IsValid())
|
|
{
|
|
TSharedPtr<IStatsPage> InitialStatsPage;
|
|
FString DisplayedStatsPageName;
|
|
FString ConfigKey = TEXT("DisplayedStatsPageName");
|
|
if (StatsPageManager.GetName() != NAME_None)
|
|
{
|
|
ConfigKey += TEXT("_") + StatsPageManager.GetName().ToString();
|
|
}
|
|
|
|
if (GConfig->GetString(*StatsViewerConstants::ConfigSectionName, *ConfigKey, DisplayedStatsPageName, GEditorPerProjectIni))
|
|
{
|
|
InitialStatsPage = StatsPageManager.GetPage(FName(*DisplayedStatsPageName));
|
|
}
|
|
|
|
if(!InitialStatsPage.IsValid())
|
|
{
|
|
// Default to primitive stats if no config data exists yet
|
|
InitialStatsPage = StatsPageManager.GetPage(EStatsPage::PrimitiveStats);
|
|
}
|
|
|
|
SetDisplayedStats(InitialStatsPage.ToSharedRef());
|
|
}
|
|
}
|
|
|
|
SStatsViewer::SStatsViewer() :
|
|
bNeedsRefresh( false ),
|
|
bNeedsRefreshForFilterChange( false ),
|
|
CurrentObjectSetIndex( 0 ),
|
|
CurrentFilterIndex( 0 ),
|
|
CustomColumn( new FStatsCustomColumn )
|
|
{
|
|
}
|
|
|
|
SStatsViewer::~SStatsViewer()
|
|
{
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
CurrentStats->OnHide();
|
|
}
|
|
}
|
|
|
|
/** Helper function to get the string of a cell as it is being presented to the user */
|
|
static FString GetCellString( const TSharedPtr<IPropertyTableCell> Cell, bool bGetRawValue = false )
|
|
{
|
|
FString String = TEXT("");
|
|
|
|
// we dont want to search the full object path if this is an object, so
|
|
// we use the displayed name we would get from our asset hyperlink column
|
|
TSharedPtr< IPropertyHandle > PropertyHandle = Cell->GetPropertyHandle();
|
|
if( PropertyHandle.IsValid() )
|
|
{
|
|
UObject* Object = NULL;
|
|
if( PropertyHandle->GetValue( Object ) == FPropertyAccess::Success )
|
|
{
|
|
if( Object != NULL )
|
|
{
|
|
String = StatsViewerUtils::GetAssetName( Object ).ToString();
|
|
}
|
|
}
|
|
}
|
|
|
|
// not an object, but maybe supported
|
|
if( FStatsCustomColumn::SupportsProperty(PropertyHandle->GetProperty()) )
|
|
{
|
|
String = FStatsCustomColumn::GetPropertyAsText(PropertyHandle, bGetRawValue).ToString();
|
|
}
|
|
|
|
// still no name? will have to default to the 'value as string'
|
|
if(String.Len() == 0)
|
|
{
|
|
String = Cell->GetValueAsString();
|
|
}
|
|
|
|
return String;
|
|
}
|
|
|
|
void SStatsViewer::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
|
|
{
|
|
// check if we need to switch pages - i.e. if a page wants to be shown
|
|
FStatsPageManager& StatsPageManager = GetStatsPageManager();
|
|
for( int32 PageIndex = 0; PageIndex < StatsPageManager.NumPages(); PageIndex++ )
|
|
{
|
|
TSharedRef<IStatsPage> StatsPage = StatsPageManager.GetPageByIndex( PageIndex );
|
|
if( StatsPage->IsShowPending() )
|
|
{
|
|
SetDisplayedStats( StatsPage );
|
|
StatsPage->Show( false );
|
|
}
|
|
}
|
|
|
|
// check if we have timed out after typing something into the search filter
|
|
bool bTimerActive = SearchTextUpdateTimer >= 0.0f;
|
|
SearchTextUpdateTimer -= InDeltaTime;
|
|
if( bTimerActive && SearchTextUpdateTimer < 0.0f )
|
|
{
|
|
bNeedsRefreshForFilterChange = true;
|
|
}
|
|
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
if( CurrentStats->IsRefreshPending() )
|
|
{
|
|
bNeedsRefresh = true;
|
|
CurrentStats->Refresh( false );
|
|
}
|
|
}
|
|
|
|
if( bNeedsRefresh || bNeedsRefreshForFilterChange)
|
|
{
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
if (bNeedsRefresh)
|
|
{
|
|
// Flag all the current stat objects for death
|
|
for (auto Iter = LastGeneratedObjectList.CreateIterator(); Iter; Iter++)
|
|
{
|
|
if ((*Iter).IsValid())
|
|
{
|
|
(*Iter)->RemoveFromRoot();
|
|
}
|
|
}
|
|
|
|
// Generate new set of objects
|
|
LastGeneratedObjectList.Empty();
|
|
CurrentObjects.Empty();
|
|
CurrentStats->Generate(CurrentObjects);
|
|
|
|
// Backup list for future use (see bNeedsRefreshForFilterChange)
|
|
LastGeneratedObjectList = CurrentObjects;
|
|
}
|
|
else if (bNeedsRefreshForFilterChange)
|
|
{
|
|
// For a filter change, recycle last generated object list
|
|
CurrentObjects = LastGeneratedObjectList;
|
|
}
|
|
|
|
// clear the map of total strings
|
|
CustomColumn->TotalsMap.Empty();
|
|
|
|
// plug objects into table
|
|
PropertyTable->SetObjects(CurrentObjects);
|
|
|
|
// freeze & resize columns & sort if required
|
|
const TArray< TSharedRef< IPropertyTableColumn > >& Columns = PropertyTable->GetColumns();
|
|
for (int32 ColumnIndex = 0; ColumnIndex < Columns.Num(); ++ColumnIndex)
|
|
{
|
|
TSharedRef< IPropertyTableColumn > Column = Columns[ColumnIndex];
|
|
if (Columns[ColumnIndex]->GetDataSource()->IsValid())
|
|
{
|
|
TSharedPtr< FPropertyPath > PropertyPath = Column->GetDataSource()->AsPropertyPath();
|
|
const FPropertyInfo& PropertyInfo = PropertyPath->GetRootProperty();
|
|
const FString& ColumnWidthString = PropertyInfo.Property->GetMetaData(StatsViewerMetadata::ColumnWidth);
|
|
const float ColumnWidth = ColumnWidthString.Len() > 0 ? FCString::Atof(*ColumnWidthString) : 100.0f;
|
|
Column->SetWidth(ColumnWidth);
|
|
|
|
const FString& SortModeString = PropertyInfo.Property->GetMetaData(StatsViewerMetadata::SortMode);
|
|
if (SortModeString.Len() > 0)
|
|
{
|
|
EColumnSortMode::Type SortType = SortModeString == TEXT("Ascending") ? EColumnSortMode::Ascending : EColumnSortMode::Descending;
|
|
PropertyTable->SortByColumn(Column, SortType, EColumnSortPriority::Primary);
|
|
}
|
|
}
|
|
|
|
Column->SetFrozen(true);
|
|
}
|
|
|
|
// Cull objects using filter - this is currently a bit of a hack, as we need to modify the source data
|
|
// rather than the view of that data (i.e. the property table).
|
|
// @todo: Fix this once the property table has filtering.
|
|
if(FilterText.Len() > 0)
|
|
{
|
|
TArray< TSharedRef< IPropertyTableRow > >& Rows = PropertyTable->GetRows();
|
|
for( int32 RowIndex = 0; RowIndex < Rows.Num(); RowIndex++ )
|
|
{
|
|
bool bFoundMatchingCell = false;
|
|
|
|
int32 ColumnIndex = 0;
|
|
for( TSharedPtr< IPropertyTableCell > Cell = PropertyTable->GetFirstCellInRow(Rows[RowIndex]); Cell.IsValid(); Cell = PropertyTable->GetNextCellInRow(Cell.ToSharedRef()), ColumnIndex++ )
|
|
{
|
|
if( CurrentFilterIndex == ColumnIndex )
|
|
{
|
|
FString String = GetCellString( Cell );
|
|
|
|
if( String.Contains(FilterText) )
|
|
{
|
|
bFoundMatchingCell = true;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( !bFoundMatchingCell )
|
|
{
|
|
CurrentObjects.Remove( Rows[RowIndex]->GetDataSource()->AsUObject() );
|
|
}
|
|
}
|
|
}
|
|
|
|
// generate totals from the objects that are filtered
|
|
CurrentStats->GenerateTotals( CurrentObjects, CustomColumn->TotalsMap );
|
|
|
|
// Re-plug objects into table as we may have removed some
|
|
// note: this currently also allows us to properly set up the UI as the 'frozen'
|
|
// flag is not taken into account when building the table at first. Setting the
|
|
// same set of objects again here allows us to remove the combo button & 'remove' menu
|
|
// from the column header.
|
|
PropertyTable->SetObjects( CurrentObjects );
|
|
|
|
PropertyTable->RequestRefresh();
|
|
}
|
|
|
|
bNeedsRefresh = false;
|
|
bNeedsRefreshForFilterChange = false;
|
|
}
|
|
}
|
|
|
|
void SStatsViewer::Refresh()
|
|
{
|
|
bNeedsRefresh = true;
|
|
}
|
|
|
|
TSharedPtr< IPropertyTable > SStatsViewer::GetPropertyTable()
|
|
{
|
|
return PropertyTable;
|
|
}
|
|
|
|
FReply SStatsViewer::OnRefreshClicked()
|
|
{
|
|
Refresh();
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply SStatsViewer::OnExportClicked()
|
|
{
|
|
if( !CurrentStats.IsValid() || CurrentObjects.Num() == 0 )
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
|
|
// MEssage to disply on completion
|
|
FText Message;
|
|
bool bSuccessful = false;
|
|
|
|
// CSV: Human-readable spreadsheet format.
|
|
FString CSVFilename = FPaths::ProjectLogDir();
|
|
CSVFilename /= CurrentStats->GetName().ToString();
|
|
CSVFilename /= GWorld->GetOutermost()->GetName();
|
|
CSVFilename /= FString::Printf(
|
|
TEXT("%s-%i-%s.csv"),
|
|
FApp::GetProjectName(),
|
|
FEngineVersion::Current().GetChangelist(),
|
|
*FDateTime::Now().ToString() );
|
|
|
|
// Create the CSV (can't use ToNumber or FormatIntToHumanReadable as it'll break the CSV!)
|
|
FArchive* CSVFile = IFileManager::Get().CreateFileWriter( *CSVFilename );
|
|
if( CSVFile )
|
|
{
|
|
const FString Delimiter = TEXT(",");
|
|
|
|
// write out header row
|
|
{
|
|
FString HeaderRow;
|
|
const TArray< TSharedRef< IPropertyTableColumn > >& Columns = PropertyTable->GetColumns();
|
|
for( int32 ColumnIndex = 0; ColumnIndex < Columns.Num(); ++ColumnIndex )
|
|
{
|
|
TSharedRef< IPropertyTableColumn > Column = Columns[ColumnIndex];
|
|
if( Columns[ColumnIndex]->GetDataSource()->IsValid() )
|
|
{
|
|
TSharedPtr< FPropertyPath > PropertyPath = Column->GetDataSource()->AsPropertyPath();
|
|
const FPropertyInfo& PropertyInfo = PropertyPath->GetRootProperty();
|
|
const TWeakFieldPtr< const FProperty > Property = PropertyInfo.Property;
|
|
FString Name = UEditorEngine::GetFriendlyName(Property.Get());
|
|
Name.ReplaceInline( *Delimiter, TEXT(" ") );
|
|
HeaderRow += Name + Delimiter;
|
|
}
|
|
}
|
|
HeaderRow += LINE_TERMINATOR;
|
|
CSVFile->Serialize( TCHAR_TO_ANSI( *HeaderRow ), HeaderRow.Len() );
|
|
}
|
|
|
|
// write out data
|
|
{
|
|
FString Data;
|
|
TArray< TSharedRef< IPropertyTableRow > >& Rows = PropertyTable->GetRows();
|
|
for( int32 RowIndex = 0; RowIndex < Rows.Num(); ++RowIndex )
|
|
{
|
|
TSharedRef< IPropertyTableRow > Row = Rows[RowIndex];
|
|
for( TSharedPtr< IPropertyTableCell > Cell = PropertyTable->GetFirstCellInRow(Row); Cell.IsValid(); Cell = PropertyTable->GetNextCellInRow(Cell.ToSharedRef()) )
|
|
{
|
|
FString CellData = GetCellString( Cell , true );
|
|
CellData.ReplaceInline( *Delimiter, TEXT(" ") );
|
|
Data += CellData + Delimiter;
|
|
}
|
|
Data += LINE_TERMINATOR;
|
|
}
|
|
CSVFile->Serialize( TCHAR_TO_ANSI( *Data ), Data.Len() );
|
|
}
|
|
|
|
// write out totals, if any
|
|
{
|
|
FString Total;
|
|
const TArray< TSharedRef< IPropertyTableColumn > >& Columns = PropertyTable->GetColumns();
|
|
for( int32 ColumnIndex = 0; ColumnIndex < Columns.Num(); ++ColumnIndex )
|
|
{
|
|
TSharedRef< IPropertyTableColumn > Column = Columns[ColumnIndex];
|
|
if( Columns[ColumnIndex]->GetDataSource()->IsValid() )
|
|
{
|
|
TSharedPtr< FPropertyPath > PropertyPath = Column->GetDataSource()->AsPropertyPath();
|
|
const FPropertyInfo& PropertyInfo = PropertyPath->GetRootProperty();
|
|
const FString& ShowTotal = PropertyInfo.Property->GetMetaData(TEXT("ShowTotal"));
|
|
if( ShowTotal.Len() > 0 )
|
|
{
|
|
FText* TotalText = CustomColumn->TotalsMap.Find( PropertyInfo.Property->GetNameCPP() );
|
|
if( TotalText != NULL )
|
|
{
|
|
FString TotalString = TotalText->ToString();
|
|
TotalString.ReplaceInline( *Delimiter, TEXT(" ") );
|
|
Total += TotalString;
|
|
}
|
|
}
|
|
}
|
|
|
|
Total += Delimiter;
|
|
}
|
|
Total += LINE_TERMINATOR;
|
|
CSVFile->Serialize( TCHAR_TO_ANSI( *Total ), Total.Len() );
|
|
}
|
|
|
|
// Close and delete archive.
|
|
CSVFile->Close();
|
|
delete CSVFile;
|
|
CSVFile = NULL;
|
|
|
|
Message = LOCTEXT("ExportMessage", "Wrote statistics to file");
|
|
bSuccessful = true;
|
|
}
|
|
else
|
|
{
|
|
Message = LOCTEXT("ExportErrorMessage", "Could not write statistics to file");
|
|
bSuccessful = false;
|
|
}
|
|
|
|
struct Local
|
|
{
|
|
static void NavigateToExportedFile( FString InCSVFilename, bool bInSuccessful )
|
|
{
|
|
InCSVFilename = FPaths::ConvertRelativePathToFull(InCSVFilename);
|
|
if (bInSuccessful)
|
|
{
|
|
FPlatformProcess::LaunchFileInDefaultExternalApplication(*InCSVFilename);
|
|
}
|
|
else
|
|
{
|
|
FPlatformProcess::ExploreFolder(*FPaths::GetPath(InCSVFilename));
|
|
}
|
|
}
|
|
};
|
|
|
|
FNotificationInfo Info(Message);
|
|
Info.Hyperlink = FSimpleDelegate::CreateStatic(&Local::NavigateToExportedFile, CSVFilename, false);
|
|
Info.HyperlinkText = FText::FromString( CSVFilename );
|
|
Info.bUseLargeFont = false;
|
|
Info.bFireAndForget = true;
|
|
Info.ExpireDuration = 8.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Info);
|
|
|
|
UE_LOG( LogStatsViewer, Log, TEXT("%s %s"), *Message.ToString(), *CSVFilename );
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FStatsPageManager& SStatsViewer::GetStatsPageManager() const
|
|
{
|
|
// We can use either provided page manager or use the default (global) one.
|
|
return StatsPageManagerPtr.IsValid() ? *StatsPageManagerPtr : FStatsPageManager::Get();
|
|
}
|
|
|
|
int32 SStatsViewer::GetObjectSetIndex() const
|
|
{
|
|
return CurrentObjectSetIndex;
|
|
}
|
|
|
|
void SStatsViewer::SwitchAndFilterPage(int32 Page, const FString& FilterValue, const FString& FilterProperty)
|
|
{
|
|
FStatsPageManager& Manager = FStatsPageManager::Get();
|
|
if(Page < Manager.NumPages())
|
|
{
|
|
TSharedRef<IStatsPage> StatsPage = FStatsPageManager::Get().GetPage(Page);
|
|
SetDisplayedStats(StatsPage);
|
|
|
|
int32 FilterIndex = -1;
|
|
if(FilterProperty.Len())
|
|
{
|
|
int32 ColumnIndex = 0;
|
|
for (TFieldIterator<FProperty> PropertyIter( CurrentStats->GetEntryClass(), EFieldIteratorFlags::IncludeSuper ); PropertyIter; ++PropertyIter )
|
|
{
|
|
FProperty* Property = *PropertyIter;
|
|
if( Property->HasAnyPropertyFlags(CPF_AssetRegistrySearchable) )
|
|
{
|
|
FString FilterName = Property->GetDisplayNameText().ToString();
|
|
if( FilterName.Len() == 0 )
|
|
{
|
|
FilterName = UEditorEngine::GetFriendlyName(Property);
|
|
}
|
|
if(FilterName == FilterProperty)
|
|
{
|
|
FilterIndex = ColumnIndex;
|
|
break;
|
|
}
|
|
ColumnIndex++;
|
|
}
|
|
}
|
|
}
|
|
if(FilterIndex >= 0)
|
|
{
|
|
FilterTextBoxWidget->SetText(FText::FromString(FilterValue));
|
|
SetSearchFilter(FilterIndex);
|
|
}
|
|
Refresh();
|
|
}
|
|
}
|
|
void SStatsViewer::OnFilterTextChanged( const FText& InFilterText )
|
|
{
|
|
FilterText = InFilterText.ToString();
|
|
SearchTextUpdateTimer = StatsViewerConstants::SearchTextUpdateDelay;
|
|
}
|
|
|
|
FText SStatsViewer::OnGetDisplayMenuLabel() const
|
|
{
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
return CurrentStats->GetDisplayName();
|
|
}
|
|
|
|
return LOCTEXT( "NoDisplayedStatistic", "Display" );
|
|
}
|
|
|
|
FText SStatsViewer::OnGetObjectSetMenuLabel() const
|
|
{
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
return FText::FromString( CurrentStats->GetObjectSetName(CurrentObjectSetIndex) );
|
|
}
|
|
|
|
return LOCTEXT( "NoDisplayedObjectSet", "Objects" );
|
|
}
|
|
|
|
TSharedRef<SWidget> SStatsViewer::OnGetDisplayMenuContent() const
|
|
{
|
|
FMenuBuilder MenuBuilder(true, NULL);
|
|
FStatsPageManager& StatsPageManager = GetStatsPageManager();
|
|
|
|
for( int32 PageIndex = 0; PageIndex < StatsPageManager.NumPages(); PageIndex++ )
|
|
{
|
|
TSharedRef<IStatsPage> StatsPage = StatsPageManager.GetPageByIndex( PageIndex );
|
|
MenuBuilder.AddMenuEntry(
|
|
StatsPage->GetDisplayName(),
|
|
StatsPage->GetToolTip(),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP( const_cast<SStatsViewer*>(this), &SStatsViewer::SetDisplayedStats, StatsPage ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SStatsViewer::AreStatsDisplayed, StatsPage )
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
}
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
TSharedRef<SWidget> SStatsViewer::OnGetObjectSetMenuContent() const
|
|
{
|
|
FMenuBuilder MenuBuilder(true, NULL);
|
|
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
for( int32 ObjectSetIndex = 0; ObjectSetIndex < CurrentStats->GetObjectSetCount(); ++ObjectSetIndex )
|
|
{
|
|
MenuBuilder.AddMenuEntry(
|
|
FText::FromString( CurrentStats->GetObjectSetName( ObjectSetIndex ) ),
|
|
FText::FromString( CurrentStats->GetObjectSetToolTip( ObjectSetIndex ) ),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP( const_cast<SStatsViewer*>(this), &SStatsViewer::SetObjectSet, ObjectSetIndex ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SStatsViewer::IsObjectSetSelected, ObjectSetIndex )
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
}
|
|
}
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
TSharedRef<SWidget> SStatsViewer::OnGetFilterMenuContent() const
|
|
{
|
|
FMenuBuilder MenuBuilder(true, NULL);
|
|
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
int32 ColumnIndex = 0;
|
|
for (TFieldIterator<FProperty> PropertyIter( CurrentStats->GetEntryClass(), EFieldIteratorFlags::IncludeSuper ); PropertyIter; ++PropertyIter )
|
|
{
|
|
FProperty* Property = *PropertyIter;
|
|
if( Property->HasAnyPropertyFlags(CPF_AssetRegistrySearchable) )
|
|
{
|
|
FText FilterName = Property->GetDisplayNameText();
|
|
if( FilterName.IsEmpty() )
|
|
{
|
|
FilterName = FText::AsCultureInvariant(UEditorEngine::GetFriendlyName(Property));
|
|
}
|
|
|
|
FText FilterDesc = Property->GetToolTipText();
|
|
if( FilterDesc.IsEmpty() )
|
|
{
|
|
FilterDesc = FText::AsCultureInvariant(UEditorEngine::GetFriendlyName(Property));
|
|
}
|
|
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("FilterName"), FilterName);
|
|
Arguments.Add(TEXT("FilterDesc"), FilterDesc);
|
|
|
|
MenuBuilder.AddMenuEntry(
|
|
FilterName,
|
|
FText::Format( LOCTEXT( "FilterMenuEntry_Tooltip", "Search statistics by {FilterName}.\n{FilterDesc}" ), Arguments ),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateSP( const_cast<SStatsViewer*>(this), &SStatsViewer::SetSearchFilter, ColumnIndex ),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP( this, &SStatsViewer::IsSearchFilterSelected, ColumnIndex )
|
|
),
|
|
NAME_None,
|
|
EUserInterfaceActionType::RadioButton
|
|
);
|
|
|
|
++ColumnIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
EVisibility SStatsViewer::OnGetObjectSetsVisibility() const
|
|
{
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
return CurrentStats->GetObjectSetCount() > 1 ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
return CurrentStats.IsValid() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility SStatsViewer::OnGetStatsVisibility() const
|
|
{
|
|
return CurrentStats.IsValid() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
void SStatsViewer::SetDisplayedStats( TSharedRef<IStatsPage> StatsPage )
|
|
{
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
CurrentStats->OnHide();
|
|
}
|
|
|
|
CurrentStats = StatsPage;
|
|
GConfig->SetString(*StatsViewerConstants::ConfigSectionName, TEXT("DisplayedStatsPageName"), *StatsPage->GetName().ToString(), GEditorPerProjectIni);
|
|
CurrentStats->OnShow( SharedThis(this) );
|
|
CurrentObjectSetIndex = CurrentStats->GetSelectedObjectSet();
|
|
CurrentFilterIndex = 0;
|
|
FilterTextBoxWidget->SetText( FText::FromString(TEXT("")) );
|
|
|
|
// set custom widget, if any
|
|
TSharedPtr<SWidget> CustomContentWidget = CurrentStats->GetCustomWidget( SharedThis(this) );
|
|
{
|
|
if(CustomContentWidget.IsValid())
|
|
{
|
|
CustomContent->SetContent( CustomContentWidget.ToSharedRef() );
|
|
CustomContent->SetVisibility( EVisibility::Visible );
|
|
}
|
|
else
|
|
{
|
|
CustomContent->SetVisibility( EVisibility::Collapsed );
|
|
}
|
|
}
|
|
|
|
// set custom filter, if any
|
|
TSharedPtr<SWidget> CustomFilterWidget = CurrentStats->GetCustomFilter( SharedThis(this) );
|
|
{
|
|
if (!CustomFilterWidget.IsValid())
|
|
{
|
|
CustomFilterWidget = SNew( SComboButton )
|
|
.Visibility( this, &SStatsViewer::OnGetObjectSetsVisibility )
|
|
.ContentPadding(3.f)
|
|
.OnGetMenuContent( this, &SStatsViewer::OnGetObjectSetMenuContent )
|
|
.ButtonContent()
|
|
[
|
|
SNew( STextBlock )
|
|
.Text( this, &SStatsViewer::OnGetObjectSetMenuLabel )
|
|
.ToolTipText( LOCTEXT( "DisplayedObjects_Tooltip", "Choose the objects whose statistics you want to display" ) )
|
|
];
|
|
}
|
|
|
|
CustomFilter->SetContent(CustomFilterWidget.ToSharedRef());
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
bool SStatsViewer::AreStatsDisplayed( TSharedRef<IStatsPage> StatsPage ) const
|
|
{
|
|
return CurrentStats == StatsPage;
|
|
}
|
|
|
|
FText SStatsViewer::OnGetFilterComboButtonLabel() const
|
|
{
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
int32 ColumnIndex = 0;
|
|
for (TFieldIterator<FProperty> PropertyIter( CurrentStats->GetEntryClass(), EFieldIteratorFlags::IncludeSuper ); PropertyIter; ++PropertyIter )
|
|
{
|
|
if( PropertyIter->HasAnyPropertyFlags(CPF_AssetRegistrySearchable) )
|
|
{
|
|
if( ColumnIndex == CurrentFilterIndex )
|
|
{
|
|
FString FilterName = PropertyIter->GetDisplayNameText().ToString();
|
|
if( FilterName.Len() == 0 )
|
|
{
|
|
FilterName = UEditorEngine::GetFriendlyName(*PropertyIter);
|
|
}
|
|
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("FilterName"), FText::FromString(FilterName));
|
|
return FText::Format( LOCTEXT( "FilterSelected", "Filter: {FilterName}" ), Arguments );
|
|
}
|
|
++ColumnIndex;
|
|
}
|
|
}
|
|
}
|
|
|
|
return LOCTEXT( "Filter", "Filter" );
|
|
}
|
|
|
|
void SStatsViewer::SetObjectSet( int32 InSetIndex )
|
|
{
|
|
CurrentObjectSetIndex = InSetIndex;
|
|
if( CurrentStats.IsValid() )
|
|
{
|
|
CurrentStats->SetSelectedObjectSet( InSetIndex );
|
|
}
|
|
|
|
Refresh();
|
|
}
|
|
|
|
bool SStatsViewer::IsObjectSetSelected( int32 InSetIndex ) const
|
|
{
|
|
return CurrentObjectSetIndex == InSetIndex;
|
|
}
|
|
|
|
void SStatsViewer::SetSearchFilter( int32 InFilterIndex )
|
|
{
|
|
CurrentFilterIndex = InFilterIndex;
|
|
|
|
bNeedsRefreshForFilterChange = true;
|
|
}
|
|
|
|
bool SStatsViewer::IsSearchFilterSelected( int32 InFilterIndex ) const
|
|
{
|
|
return CurrentFilterIndex == InFilterIndex;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|