Files
UnrealEngine/Engine/Source/Editor/DataTableEditor/Private/DataTableEditor.cpp
2025-05-18 13:04:45 +08:00

1449 lines
42 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "DataTableEditor.h"
#include "AssetRegistry/AssetData.h"
#include "Containers/Map.h"
#include "CoreGlobals.h"
#include "DataTableEditorModule.h"
#include "DataTableUtils.h"
#include "DetailsViewArgs.h"
#include "Dom/JsonObject.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "Engine/DataTable.h"
#include "StructUtils/UserDefinedStruct.h"
#include "Fonts/FontMeasure.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/GenericCommands.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/Docking/TabManager.h"
#include "Framework/Layout/Overscroll.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/Notifications/NotificationManager.h"
#include "Framework/Text/TextLayout.h"
#include "Framework/Views/ITypedTableView.h"
#include "HAL/PlatformApplicationMisc.h"
#include "IDetailsView.h"
#include "IDocumentation.h"
#include "Internationalization/Internationalization.h"
#include "Layout/BasicLayoutWidgetSlot.h"
#include "Layout/Margin.h"
#include "Layout/Visibility.h"
#include "Math/ColorList.h"
#include "Math/UnrealMathSSE.h"
#include "Math/Vector2D.h"
#include "Misc/AssertionMacros.h"
#include "Misc/Attribute.h"
#include "Misc/FeedbackContext.h"
#include "Misc/FileHelper.h"
#include "Misc/Paths.h"
#include "Modules/ModuleManager.h"
#include "Policies/PrettyJsonPrintPolicy.h"
#include "PropertyEditorModule.h"
#include "Rendering/SlateRenderer.h"
#include "SDataTableListViewRow.h"
#include "SRowEditor.h"
#include "ScopedTransaction.h"
#include "Serialization/JsonReader.h"
#include "Serialization/JsonSerializer.h"
#include "Serialization/JsonWriter.h"
#include "SlotBase.h"
#include "SourceCodeNavigation.h"
#include "Styling/AppStyle.h"
#include "Styling/SlateTypes.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Templates/Casts.h"
#include "Templates/TypeHash.h"
#include "Textures/SlateIcon.h"
#include "Toolkits/AssetEditorToolkit.h"
#include "UObject/Class.h"
#include "UObject/ObjectMacros.h"
#include "UObject/ObjectPtr.h"
#include "UObject/PropertyPortFlags.h"
#include "UObject/TopLevelAssetPath.h"
#include "UObject/UObjectBaseUtility.h"
#include "Widgets/DeclarativeSyntaxSupport.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SHyperlink.h"
#include "Widgets/Input/SSearchBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/Layout/SBox.h"
#include "Widgets/Layout/SScrollBar.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Notifications/SNotificationList.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/SToolTip.h"
#include "Widgets/Text/STextBlock.h"
#include "Widgets/Views/SListView.h"
class ITableRow;
class STableViewBase;
class SWidget;
#define LOCTEXT_NAMESPACE "DataTableEditor"
const FName FDataTableEditor::DataTableTabId("DataTableEditor_DataTable");
const FName FDataTableEditor::DataTableDetailsTabId("DataTableEditor_DataTableDetails");
const FName FDataTableEditor::RowEditorTabId("DataTableEditor_RowEditor");
const FName FDataTableEditor::RowNameColumnId("RowName");
const FName FDataTableEditor::RowNumberColumnId("RowNumber");
const FName FDataTableEditor::RowDragDropColumnId("RowDragDrop");
class SDataTableModeSeparator : public SBorder
{
public:
SLATE_BEGIN_ARGS(SDataTableModeSeparator) {}
SLATE_END_ARGS()
void Construct(const FArguments& InArg)
{
SBorder::Construct(
SBorder::FArguments()
.BorderImage(FAppStyle::GetBrush("BlueprintEditor.PipelineSeparator"))
.Padding(0.0f)
);
}
// SWidget interface
virtual FVector2D ComputeDesiredSize(float) const override
{
const float Height = 20.0f;
const float Thickness = 16.0f;
return FVector2D(Thickness, Height);
}
// End of SWidget interface
};
void FDataTableEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_Data Table Editor", "Data Table Editor"));
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
CreateAndRegisterDataTableTab(InTabManager);
CreateAndRegisterDataTableDetailsTab(InTabManager);
CreateAndRegisterRowEditorTab(InTabManager);
}
void FDataTableEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
InTabManager->UnregisterTabSpawner(DataTableTabId);
InTabManager->UnregisterTabSpawner(DataTableDetailsTabId);
InTabManager->UnregisterTabSpawner(RowEditorTabId);
DataTableTabWidget.Reset();
RowEditorTabWidget.Reset();
}
void FDataTableEditor::CreateAndRegisterDataTableTab(const TSharedRef<class FTabManager>& InTabManager)
{
DataTableTabWidget = CreateContentBox();
InTabManager->RegisterTabSpawner(DataTableTabId, FOnSpawnTab::CreateSP(this, &FDataTableEditor::SpawnTab_DataTable))
.SetDisplayName(LOCTEXT("DataTableTab", "Data Table"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef());
}
void FDataTableEditor::CreateAndRegisterDataTableDetailsTab(const TSharedRef<class FTabManager>& InTabManager)
{
FPropertyEditorModule & EditModule = FModuleManager::Get().GetModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsViewArgs.bHideSelectionTip = true;
PropertyView = EditModule.CreateDetailView(DetailsViewArgs);
InTabManager->RegisterTabSpawner(DataTableDetailsTabId, FOnSpawnTab::CreateSP(this, &FDataTableEditor::SpawnTab_DataTableDetails))
.SetDisplayName(LOCTEXT("DataTableDetailsTab", "Data Table Details"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef());
}
void FDataTableEditor::CreateAndRegisterRowEditorTab(const TSharedRef<class FTabManager>& InTabManager)
{
RowEditorTabWidget = CreateRowEditorBox();
InTabManager->RegisterTabSpawner(RowEditorTabId, FOnSpawnTab::CreateSP(this, &FDataTableEditor::SpawnTab_RowEditor))
.SetDisplayName(LOCTEXT("RowEditorTab", "Row Editor"))
.SetGroup(WorkspaceMenuCategory.ToSharedRef());
}
FDataTableEditor::FDataTableEditor()
: RowNameColumnWidth(0)
, RowNumberColumnWidth(0)
, HighlightedVisibleRowIndex(INDEX_NONE)
, SortMode(EColumnSortMode::Ascending)
{
}
FDataTableEditor::~FDataTableEditor()
{
GEditor->UnregisterForUndo(this);
UDataTable* Table = GetEditableDataTable();
if (Table)
{
SaveLayoutData();
}
}
void FDataTableEditor::PostUndo(bool bSuccess)
{
HandleUndoRedo();
}
void FDataTableEditor::PostRedo(bool bSuccess)
{
HandleUndoRedo();
}
void FDataTableEditor::HandleUndoRedo()
{
const UDataTable* Table = GetDataTable();
if (Table)
{
HandlePostChange();
CallbackOnDataTableUndoRedo.ExecuteIfBound();
}
}
void FDataTableEditor::PreChange(const class UUserDefinedStruct* Struct, FStructureEditorUtils::EStructureEditorChangeInfo Info)
{
}
void FDataTableEditor::PostChange(const class UUserDefinedStruct* Struct, FStructureEditorUtils::EStructureEditorChangeInfo Info)
{
const UDataTable* Table = GetDataTable();
if (Struct && Table && (Table->GetRowStruct() == Struct))
{
HandlePostChange();
}
}
void FDataTableEditor::SelectionChange(const UDataTable* Changed, FName RowName)
{
const UDataTable* Table = GetDataTable();
if (Changed == Table)
{
const bool bSelectionChanged = HighlightedRowName != RowName;
SetHighlightedRow(RowName);
if (bSelectionChanged)
{
CallbackOnRowHighlighted.ExecuteIfBound(HighlightedRowName);
}
}
}
void FDataTableEditor::PreChange(const UDataTable* Changed, FDataTableEditorUtils::EDataTableChangeInfo Info)
{
}
void FDataTableEditor::PostChange(const UDataTable* Changed, FDataTableEditorUtils::EDataTableChangeInfo Info)
{
UDataTable* Table = GetEditableDataTable();
if (Changed == Table)
{
// Don't need to notify the DataTable about changes, that's handled before this
HandlePostChange();
}
}
const UDataTable* FDataTableEditor::GetDataTable() const
{
return Cast<const UDataTable>(GetEditingObject());
}
void FDataTableEditor::HandlePostChange()
{
// We need to cache and restore the selection here as RefreshCachedDataTable will re-create the list view items
const FName CachedSelection = HighlightedRowName;
HighlightedRowName = NAME_None;
RefreshCachedDataTable(CachedSelection, true/*bUpdateEvenIfValid*/);
}
void FDataTableEditor::InitDataTableEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UDataTable* Table )
{
TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout( "Standalone_DataTableEditor_Layout_v6" )
->AddArea
(
FTabManager::NewPrimaryArea()->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewStack()
->AddTab(DataTableTabId, ETabState::OpenedTab)
->AddTab(DataTableDetailsTabId, ETabState::OpenedTab)
->SetForegroundTab(DataTableTabId)
)
->Split
(
FTabManager::NewStack()
->AddTab(RowEditorTabId, ETabState::OpenedTab)
)
);
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;
FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, FDataTableEditorModule::DataTableEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, Table );
FDataTableEditorModule& DataTableEditorModule = FModuleManager::LoadModuleChecked<FDataTableEditorModule>( "DataTableEditor" );
AddMenuExtender(DataTableEditorModule.GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
TSharedPtr<FExtender> ToolbarExtender = DataTableEditorModule.GetToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects());
ExtendToolbar(ToolbarExtender);
AddToolbarExtender(ToolbarExtender);
RegenerateMenusAndToolbars();
// Support undo/redo
GEditor->RegisterForUndo(this);
// @todo toolkit world centric editing
/*// Setup our tool's layout
if( IsWorldCentricAssetEditor() )
{
const FString TabInitializationPayload(TEXT("")); // NOTE: Payload not currently used for table properties
SpawnToolkitTab( DataTableTabId, TabInitializationPayload, EToolkitTabSpot::Details );
}*/
// asset editor commands here
ToolkitCommands->MapAction(FGenericCommands::Get().Copy, FExecuteAction::CreateSP(this, &FDataTableEditor::CopySelectedRow), FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanEditTable));
ToolkitCommands->MapAction(FGenericCommands::Get().Paste, FExecuteAction::CreateSP(this, &FDataTableEditor::PasteOnSelectedRow), FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanEditTable));
ToolkitCommands->MapAction(FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP(this, &FDataTableEditor::DuplicateSelectedRow), FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanEditTable));
ToolkitCommands->MapAction(FGenericCommands::Get().Rename, FExecuteAction::CreateSP(this, &FDataTableEditor::RenameSelectedRowCommand), FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanEditTable));
ToolkitCommands->MapAction(FGenericCommands::Get().Delete, FExecuteAction::CreateSP(this, &FDataTableEditor::DeleteSelectedRow), FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanEditTable));
}
bool FDataTableEditor::CanEditRows() const
{
return true;
}
FName FDataTableEditor::GetToolkitFName() const
{
return FName("DataTableEditor");
}
FString FDataTableEditor::GetDocumentationLink() const
{
return FString(TEXT("Gameplay/DataDriven"));
}
void FDataTableEditor::OnAddClicked()
{
UDataTable* Table = GetEditableDataTable();
if (Table)
{
FName NewName = DataTableUtils::MakeValidName(TEXT("NewRow"));
while (Table->GetRowMap().Contains(NewName))
{
NewName.SetNumber(NewName.GetNumber() + 1);
}
FDataTableEditorUtils::AddRow(Table, NewName);
FDataTableEditorUtils::SelectRow(Table, NewName);
SetDefaultSort();
}
}
void FDataTableEditor::OnRemoveClicked()
{
DeleteSelectedRow();
}
FReply FDataTableEditor::OnMoveRowClicked(FDataTableEditorUtils::ERowMoveDirection MoveDirection)
{
UDataTable* Table = GetEditableDataTable();
if (Table)
{
FDataTableEditorUtils::MoveRow(Table, HighlightedRowName, MoveDirection);
}
return FReply::Handled();
}
FReply FDataTableEditor::OnMoveToExtentClicked(FDataTableEditorUtils::ERowMoveDirection MoveDirection)
{
UDataTable* Table = GetEditableDataTable();
if (Table)
{
// We move by the row map size, as FDataTableEditorUtils::MoveRow will automatically clamp this as appropriate
FDataTableEditorUtils::MoveRow(Table, HighlightedRowName, MoveDirection, Table->GetRowMap().Num());
FDataTableEditorUtils::SelectRow(Table, HighlightedRowName);
SetDefaultSort();
}
return FReply::Handled();
}
void FDataTableEditor::OnCopyClicked()
{
UDataTable* Table = GetEditableDataTable();
if (Table)
{
CopySelectedRow();
}
}
void FDataTableEditor::OnPasteClicked()
{
UDataTable* Table = GetEditableDataTable();
if (Table)
{
PasteOnSelectedRow();
}
}
void FDataTableEditor::OnDuplicateClicked()
{
UDataTable* Table = GetEditableDataTable();
if (Table)
{
DuplicateSelectedRow();
}
}
bool FDataTableEditor::CanEditTable() const
{
return HighlightedRowName != NAME_None;
}
void FDataTableEditor::SetDefaultSort()
{
SortMode = EColumnSortMode::Ascending;
SortByColumn = FDataTableEditor::RowNumberColumnId;
}
EColumnSortMode::Type FDataTableEditor::GetColumnSortMode(const FName ColumnId) const
{
if (SortByColumn != ColumnId)
{
return EColumnSortMode::None;
}
return SortMode;
}
void FDataTableEditor::OnColumnSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type InSortMode)
{
int32 ColumnIndex;
SortMode = InSortMode;
SortByColumn = ColumnId;
for (ColumnIndex = 0; ColumnIndex < AvailableColumns.Num(); ++ColumnIndex)
{
if (AvailableColumns[ColumnIndex]->ColumnId == ColumnId)
{
break;
}
}
if (AvailableColumns.IsValidIndex(ColumnIndex))
{
if (InSortMode == EColumnSortMode::Ascending)
{
VisibleRows.Sort([ColumnIndex](const FDataTableEditorRowListViewDataPtr& first, const FDataTableEditorRowListViewDataPtr& second)
{
int32 Result = (first->CellData[ColumnIndex].ToString()).Compare(second->CellData[ColumnIndex].ToString());
if (!Result)
{
return first->RowNum < second->RowNum;
}
return Result < 0;
});
}
else if (InSortMode == EColumnSortMode::Descending)
{
VisibleRows.Sort([ColumnIndex](const FDataTableEditorRowListViewDataPtr& first, const FDataTableEditorRowListViewDataPtr& second)
{
int32 Result = (first->CellData[ColumnIndex].ToString()).Compare(second->CellData[ColumnIndex].ToString());
if (!Result)
{
return first->RowNum > second->RowNum;
}
return Result > 0;
});
}
}
CellsListView->RequestListRefresh();
}
void FDataTableEditor::OnColumnNumberSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type InSortMode)
{
SortMode = InSortMode;
SortByColumn = ColumnId;
if (InSortMode == EColumnSortMode::Ascending)
{
VisibleRows.Sort([](const FDataTableEditorRowListViewDataPtr& first, const FDataTableEditorRowListViewDataPtr& second)
{
return first->RowNum < second->RowNum;
});
}
else if (InSortMode == EColumnSortMode::Descending)
{
VisibleRows.Sort([](const FDataTableEditorRowListViewDataPtr& first, const FDataTableEditorRowListViewDataPtr& second)
{
return first->RowNum > second->RowNum;
});
}
CellsListView->RequestListRefresh();
}
void FDataTableEditor::OnColumnNameSortModeChanged(const EColumnSortPriority::Type SortPriority, const FName& ColumnId, const EColumnSortMode::Type InSortMode)
{
SortMode = InSortMode;
SortByColumn = ColumnId;
if (InSortMode == EColumnSortMode::Ascending)
{
VisibleRows.Sort([](const FDataTableEditorRowListViewDataPtr& first, const FDataTableEditorRowListViewDataPtr& second)
{
return (first->DisplayName).ToString() < (second->DisplayName).ToString();
});
}
else if (InSortMode == EColumnSortMode::Descending)
{
VisibleRows.Sort([](const FDataTableEditorRowListViewDataPtr& first, const FDataTableEditorRowListViewDataPtr& second)
{
return (first->DisplayName).ToString() > (second->DisplayName).ToString();
});
}
CellsListView->RequestListRefresh();
}
void FDataTableEditor::OnEditDataTableStructClicked()
{
const UDataTable* DataTable = GetDataTable();
if (DataTable)
{
const UScriptStruct* ScriptStruct = DataTable->GetRowStruct();
if (ScriptStruct)
{
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(ScriptStruct->GetPathName());
if (FSourceCodeNavigation::CanNavigateToStruct(ScriptStruct))
{
FSourceCodeNavigation::NavigateToStruct(ScriptStruct);
}
}
}
}
void FDataTableEditor::ExtendToolbar(TSharedPtr<FExtender> Extender)
{
Extender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
GetToolkitCommands(),
FToolBarExtensionDelegate::CreateSP(this, &FDataTableEditor::FillToolbar)
);
}
void FDataTableEditor::FillToolbar(FToolBarBuilder& ToolbarBuilder)
{
ToolbarBuilder.BeginSection("DataTableCommands");
{
ToolbarBuilder.AddToolBarButton(
FUIAction(
FExecuteAction::CreateSP(this, &FDataTableEditor::Reimport_Execute),
FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanReimport)),
NAME_None,
LOCTEXT("ReimportText", "Reimport"),
LOCTEXT("ReimportTooltip", "Reimport this DataTable"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Reimport"));
ToolbarBuilder.AddSeparator();
ToolbarBuilder.AddToolBarButton(
FUIAction(FExecuteAction::CreateSP(this, &FDataTableEditor::OnAddClicked)),
NAME_None,
LOCTEXT("AddIconText", "Add"),
LOCTEXT("AddRowToolTip", "Add a new row to the Data Table"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Plus"));
ToolbarBuilder.AddToolBarButton(
FUIAction(
FExecuteAction::CreateSP(this, &FDataTableEditor::OnCopyClicked),
FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanEditTable)),
NAME_None,
LOCTEXT("CopyIconText", "Copy"),
LOCTEXT("CopyToolTip", "Copy the currently selected row"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCommands.Copy"));
ToolbarBuilder.AddToolBarButton(
FUIAction(
FExecuteAction::CreateSP(this, &FDataTableEditor::OnPasteClicked),
FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanEditTable)),
NAME_None,
LOCTEXT("PasteIconText", "Paste"),
LOCTEXT("PasteToolTip", "Paste on the currently selected row"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "GenericCommands.Paste"));
ToolbarBuilder.AddToolBarButton(
FUIAction(
FExecuteAction::CreateSP(this, &FDataTableEditor::OnDuplicateClicked),
FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanEditTable)),
NAME_None,
LOCTEXT("DuplicateIconText", "Duplicate"),
LOCTEXT("DuplicateToolTip", "Duplicate the currently selected row"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Duplicate"));
ToolbarBuilder.AddToolBarButton(
FUIAction(
FExecuteAction::CreateSP(this, &FDataTableEditor::OnRemoveClicked),
FCanExecuteAction::CreateSP(this, &FDataTableEditor::CanEditTable)),
NAME_None,
LOCTEXT("RemoveRowIconText", "Remove"),
LOCTEXT("RemoveRowToolTip", "Remove the currently selected row from the Data Table"),
FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Delete"));
}
ToolbarBuilder.EndSection();
}
UDataTable* FDataTableEditor::GetEditableDataTable() const
{
return Cast<UDataTable>(GetEditingObject());
}
FText FDataTableEditor::GetBaseToolkitName() const
{
return LOCTEXT( "AppLabel", "DataTable Editor" );
}
FString FDataTableEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("WorldCentricTabPrefix", "DataTable ").ToString();
}
FLinearColor FDataTableEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor( 0.0f, 0.0f, 0.2f, 0.5f );
}
FSlateColor FDataTableEditor::GetRowTextColor(FName RowName) const
{
if (RowName == HighlightedRowName)
{
return FSlateColor(FColorList::Orange);
}
return FSlateColor::UseForeground();
}
FText FDataTableEditor::GetCellText(FDataTableEditorRowListViewDataPtr InRowDataPointer, int32 ColumnIndex) const
{
if (InRowDataPointer.IsValid() && ColumnIndex < InRowDataPointer->CellData.Num())
{
return InRowDataPointer->CellData[ColumnIndex];
}
return FText();
}
FText FDataTableEditor::GetCellToolTipText(FDataTableEditorRowListViewDataPtr InRowDataPointer, int32 ColumnIndex) const
{
FText TooltipText;
if (ColumnIndex < AvailableColumns.Num())
{
TooltipText = AvailableColumns[ColumnIndex]->DisplayName;
}
if (InRowDataPointer.IsValid() && ColumnIndex < InRowDataPointer->CellData.Num())
{
TooltipText = FText::Format(LOCTEXT("ColumnRowNameFmt", "{0}: {1}"), TooltipText, InRowDataPointer->CellData[ColumnIndex]);
}
return TooltipText;
}
float FDataTableEditor::GetRowNumberColumnWidth() const
{
return RowNumberColumnWidth;
}
void FDataTableEditor::RefreshRowNumberColumnWidth()
{
TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
const FTextBlockStyle& CellTextStyle = FAppStyle::GetWidgetStyle<FTextBlockStyle>("DataTableEditor.CellText");
const float CellPadding = 10.0f;
for (const FDataTableEditorRowListViewDataPtr& RowData : AvailableRows)
{
const float RowNumberWidth = (float)FontMeasure->Measure(FString::FromInt(RowData->RowNum), CellTextStyle.Font).X + CellPadding;
RowNumberColumnWidth = FMath::Max(RowNumberColumnWidth, RowNumberWidth);
}
}
void FDataTableEditor::OnRowNumberColumnResized(const float NewWidth)
{
RowNumberColumnWidth = NewWidth;
}
float FDataTableEditor::GetRowNameColumnWidth() const
{
return RowNameColumnWidth;
}
void FDataTableEditor::RefreshRowNameColumnWidth()
{
TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
const FTextBlockStyle& CellTextStyle = FAppStyle::GetWidgetStyle<FTextBlockStyle>("DataTableEditor.CellText");
static const float CellPadding = 10.0f;
for (const FDataTableEditorRowListViewDataPtr& RowData : AvailableRows)
{
const float RowNameWidth = (float)FontMeasure->Measure(RowData->DisplayName, CellTextStyle.Font).X + CellPadding;
RowNameColumnWidth = FMath::Max(RowNameColumnWidth, RowNameWidth);
}
}
float FDataTableEditor::GetColumnWidth(const int32 ColumnIndex) const
{
if (ColumnWidths.IsValidIndex(ColumnIndex))
{
return ColumnWidths[ColumnIndex].CurrentWidth;
}
return 0.0f;
}
void FDataTableEditor::OnColumnResized(const float NewWidth, const int32 ColumnIndex)
{
if (ColumnWidths.IsValidIndex(ColumnIndex))
{
FColumnWidth& ColumnWidth = ColumnWidths[ColumnIndex];
ColumnWidth.bIsAutoSized = false;
ColumnWidth.CurrentWidth = NewWidth;
// Update the persistent column widths in the layout data
{
if (!LayoutData.IsValid())
{
LayoutData = MakeShareable(new FJsonObject());
}
TSharedPtr<FJsonObject> LayoutColumnWidths;
if (!LayoutData->HasField(TEXT("ColumnWidths")))
{
LayoutColumnWidths = MakeShareable(new FJsonObject());
LayoutData->SetObjectField(TEXT("ColumnWidths"), LayoutColumnWidths);
}
else
{
LayoutColumnWidths = LayoutData->GetObjectField(TEXT("ColumnWidths"));
}
const FString& ColumnName = AvailableColumns[ColumnIndex]->ColumnId.ToString();
LayoutColumnWidths->SetNumberField(ColumnName, NewWidth);
}
}
}
void FDataTableEditor::OnRowNameColumnResized(const float NewWidth)
{
RowNameColumnWidth = NewWidth;
}
void FDataTableEditor::LoadLayoutData()
{
LayoutData.Reset();
const UDataTable* Table = GetDataTable();
if (!Table)
{
return;
}
const FString LayoutDataFilename = FPaths::ProjectSavedDir() / TEXT("AssetData") / TEXT("DataTableEditorLayout") / Table->GetName() + TEXT(".json");
FString JsonText;
if (FFileHelper::LoadFileToString(JsonText, *LayoutDataFilename))
{
TSharedRef< TJsonReader<TCHAR> > JsonReader = TJsonReaderFactory<TCHAR>::Create(JsonText);
FJsonSerializer::Deserialize(JsonReader, LayoutData);
}
}
void FDataTableEditor::SaveLayoutData()
{
const UDataTable* Table = GetDataTable();
if (!Table || !LayoutData.IsValid())
{
return;
}
const FString LayoutDataFilename = FPaths::ProjectSavedDir() / TEXT("AssetData") / TEXT("DataTableEditorLayout") / Table->GetName() + TEXT(".json");
FString JsonText;
TSharedRef< TJsonWriter< TCHAR, TPrettyJsonPrintPolicy<TCHAR> > > JsonWriter = TJsonWriterFactory< TCHAR, TPrettyJsonPrintPolicy<TCHAR> >::Create(&JsonText);
if (FJsonSerializer::Serialize(LayoutData.ToSharedRef(), JsonWriter))
{
FFileHelper::SaveStringToFile(JsonText, *LayoutDataFilename);
}
}
TSharedRef<ITableRow> FDataTableEditor::MakeRowWidget(FDataTableEditorRowListViewDataPtr InRowDataPtr, const TSharedRef<STableViewBase>& OwnerTable)
{
return
SNew(SDataTableListViewRow, OwnerTable)
.DataTableEditor(SharedThis(this))
.RowDataPtr(InRowDataPtr)
.IsEditable(CanEditRows());
}
TSharedRef<SWidget> FDataTableEditor::MakeCellWidget(FDataTableEditorRowListViewDataPtr InRowDataPtr, const int32 InRowIndex, const FName& InColumnId)
{
int32 ColumnIndex = 0;
for (; ColumnIndex < AvailableColumns.Num(); ++ColumnIndex)
{
const FDataTableEditorColumnHeaderDataPtr& ColumnData = AvailableColumns[ColumnIndex];
if (ColumnData->ColumnId == InColumnId)
{
break;
}
}
// Valid column ID?
if (AvailableColumns.IsValidIndex(ColumnIndex) && InRowDataPtr->CellData.IsValidIndex(ColumnIndex))
{
return SNew(SBox)
.Padding(FMargin(4, 2, 4, 2))
[
SNew(STextBlock)
.TextStyle(FAppStyle::Get(), "DataTableEditor.CellText")
.ColorAndOpacity(this, &FDataTableEditor::GetRowTextColor, InRowDataPtr->RowId)
.Text(this, &FDataTableEditor::GetCellText, InRowDataPtr, ColumnIndex)
.HighlightText(this, &FDataTableEditor::GetFilterText)
.ToolTipText(this, &FDataTableEditor::GetCellToolTipText, InRowDataPtr, ColumnIndex)
];
}
return SNullWidget::NullWidget;
}
void FDataTableEditor::OnRowSelectionChanged(FDataTableEditorRowListViewDataPtr InNewSelection, ESelectInfo::Type InSelectInfo)
{
const bool bSelectionChanged = !InNewSelection.IsValid() || InNewSelection->RowId != HighlightedRowName;
const FName NewRowName = (InNewSelection.IsValid()) ? InNewSelection->RowId : NAME_None;
SetHighlightedRow(NewRowName);
if (bSelectionChanged)
{
CallbackOnRowHighlighted.ExecuteIfBound(HighlightedRowName);
}
}
void FDataTableEditor::CopySelectedRow()
{
UDataTable* TablePtr = Cast<UDataTable>(GetEditingObject());
uint8* RowPtr = TablePtr ? TablePtr->GetRowMap().FindRef(HighlightedRowName) : nullptr;
if (!RowPtr || !TablePtr->RowStruct)
return;
FString ClipboardValue;
TablePtr->RowStruct->ExportText(ClipboardValue, RowPtr, RowPtr, TablePtr, PPF_Copy, nullptr);
FPlatformApplicationMisc::ClipboardCopy(*ClipboardValue);
}
void FDataTableEditor::PasteOnSelectedRow()
{
UDataTable* TablePtr = Cast<UDataTable>(GetEditingObject());
uint8* RowPtr = TablePtr ? TablePtr->GetRowMap().FindRef(HighlightedRowName) : nullptr;
if (!RowPtr || !TablePtr->RowStruct)
return;
const FScopedTransaction Transaction(LOCTEXT("PasteDataTableRow", "Paste Data Table Row"));
TablePtr->Modify();
FString ClipboardValue;
FPlatformApplicationMisc::ClipboardPaste(ClipboardValue);
FDataTableEditorUtils::BroadcastPreChange(TablePtr, FDataTableEditorUtils::EDataTableChangeInfo::RowData);
const TCHAR* Result = TablePtr->RowStruct->ImportText(*ClipboardValue, RowPtr, TablePtr, PPF_Copy, GWarn, GetPathNameSafe(TablePtr->RowStruct));
TablePtr->HandleDataTableChanged(HighlightedRowName);
TablePtr->MarkPackageDirty();
FDataTableEditorUtils::BroadcastPostChange(TablePtr, FDataTableEditorUtils::EDataTableChangeInfo::RowData);
if (Result == nullptr)
{
FNotificationInfo Info(LOCTEXT("FailedPaste", "Failed to paste row"));
FSlateNotificationManager::Get().AddNotification(Info);
}
}
void FDataTableEditor::DuplicateSelectedRow()
{
UDataTable* TablePtr = Cast<UDataTable>(GetEditingObject());
FName NewName = HighlightedRowName;
if (NewName == NAME_None || TablePtr == nullptr)
{
return;
}
const TArray<FName> ExistingNames = TablePtr->GetRowNames();
while (ExistingNames.Contains(NewName))
{
NewName.SetNumber(NewName.GetNumber() + 1);
}
FDataTableEditorUtils::DuplicateRow(TablePtr, HighlightedRowName, NewName);
FDataTableEditorUtils::SelectRow(TablePtr, NewName);
}
void FDataTableEditor::RenameSelectedRowCommand()
{
UDataTable* TablePtr = Cast<UDataTable>(GetEditingObject());
FName NewName = HighlightedRowName;
if (NewName == NAME_None || TablePtr == nullptr)
{
return;
}
if (VisibleRows.IsValidIndex(HighlightedVisibleRowIndex))
{
TSharedPtr< SDataTableListViewRow > RowWidget = StaticCastSharedPtr< SDataTableListViewRow >(CellsListView->WidgetFromItem(VisibleRows[HighlightedVisibleRowIndex]));
RowWidget->SetRowForRename();
}
}
void FDataTableEditor::DeleteSelectedRow()
{
if (UDataTable* Table = GetEditableDataTable())
{
// We must perform this before removing the row
const int32 RowToRemoveIndex = VisibleRows.IndexOfByPredicate([&](const FDataTableEditorRowListViewDataPtr& InRowName) -> bool
{
return InRowName->RowId == HighlightedRowName;
});
// Remove row
if (FDataTableEditorUtils::RemoveRow(Table, HighlightedRowName))
{
// Try and keep the same row index selected
const int32 RowIndexToSelect = FMath::Clamp(RowToRemoveIndex, 0, VisibleRows.Num() - 1);
if (VisibleRows.IsValidIndex(RowIndexToSelect))
{
FDataTableEditorUtils::SelectRow(Table, VisibleRows[RowIndexToSelect]->RowId);
}
// Refresh list. Otherwise, the removed row would still appear in the screen until the next list refresh. An
// analog of CellsListView->RequestListRefresh() also occurs inside FDataTableEditorUtils::SelectRow
else
{
CellsListView->RequestListRefresh();
}
}
}
}
FText FDataTableEditor::GetFilterText() const
{
return ActiveFilterText;
}
void FDataTableEditor::OnFilterTextChanged(const FText& InFilterText)
{
ActiveFilterText = InFilterText;
UpdateVisibleRows();
}
void FDataTableEditor::OnFilterTextCommitted(const FText& NewText, ETextCommit::Type CommitInfo)
{
if (CommitInfo == ETextCommit::OnCleared)
{
SearchBoxWidget->SetText(FText::GetEmpty());
OnFilterTextChanged(FText::GetEmpty());
}
}
void FDataTableEditor::PostRegenerateMenusAndToolbars()
{
const UDataTable* DataTable = GetDataTable();
if (DataTable)
{
const UUserDefinedStruct* UDS = Cast<const UUserDefinedStruct>(DataTable->GetRowStruct());
// build and attach the menu overlay
TSharedRef<SHorizontalBox> MenuOverlayBox = SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(STextBlock)
.ColorAndOpacity(FSlateColor::UseSubduedForeground())
.ShadowOffset(FVector2D::UnitVector)
.Text(LOCTEXT("DataTableEditor_RowStructType", "Row Type: "))
]
+ SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
[
SNew(SHyperlink)
.Style(FAppStyle::Get(), "Common.GotoNativeCodeHyperlink")
.OnNavigate(this, &FDataTableEditor::OnEditDataTableStructClicked)
.Text(FText::FromName(DataTable->GetRowStructPathName().GetAssetName()))
.ToolTipText(LOCTEXT("DataTableRowToolTip", "Open the struct used for each row in this data table"))
]
+ SHorizontalBox::Slot()
.AutoWidth()
[
SNew(SButton)
.VAlign(VAlign_Center)
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
.OnClicked(this, &FDataTableEditor::OnFindRowInContentBrowserClicked)
.Visibility(UDS ? EVisibility::Visible : EVisibility::Collapsed)
.ToolTipText(LOCTEXT("FindRowInCBToolTip", "Find struct in Content Browser"))
.ContentPadding(4.0f)
.ForegroundColor(FSlateColor::UseForeground())
[
SNew(SImage)
.Image(FAppStyle::GetBrush("Icons.Search"))
]
];
SetMenuOverlay(MenuOverlayBox);
}
}
FReply FDataTableEditor::OnFindRowInContentBrowserClicked()
{
const UDataTable* DataTable = GetDataTable();
if(DataTable)
{
TArray<FAssetData> ObjectsToSync;
ObjectsToSync.Add(FAssetData(DataTable->GetRowStruct()));
GEditor->SyncBrowserToObjects(ObjectsToSync);
}
return FReply::Handled();
}
void FDataTableEditor::RefreshCachedDataTable(const FName InCachedSelection, const bool bUpdateEvenIfValid)
{
UDataTable* Table = GetEditableDataTable();
TArray<FDataTableEditorColumnHeaderDataPtr> PreviousColumns = AvailableColumns;
FDataTableEditorUtils::CacheDataTableForEditing(Table, AvailableColumns, AvailableRows);
// Update the desired width of the row names and numbers column
// This prevents it growing or shrinking as you scroll the list view
RefreshRowNumberColumnWidth();
RefreshRowNameColumnWidth();
// Setup the default auto-sized columns
ColumnWidths.SetNum(AvailableColumns.Num());
for (int32 ColumnIndex = 0; ColumnIndex < AvailableColumns.Num(); ++ColumnIndex)
{
const FDataTableEditorColumnHeaderDataPtr& ColumnData = AvailableColumns[ColumnIndex];
FColumnWidth& ColumnWidth = ColumnWidths[ColumnIndex];
ColumnWidth.CurrentWidth = FMath::Clamp(ColumnData->DesiredColumnWidth, 10.0f, 400.0f); // Clamp auto-sized columns to a reasonable limit
}
// Load the persistent column widths from the layout data
{
const TSharedPtr<FJsonObject>* LayoutColumnWidths = nullptr;
if (LayoutData.IsValid() && LayoutData->TryGetObjectField(TEXT("ColumnWidths"), LayoutColumnWidths))
{
for(int32 ColumnIndex = 0; ColumnIndex < AvailableColumns.Num(); ++ColumnIndex)
{
const FDataTableEditorColumnHeaderDataPtr& ColumnData = AvailableColumns[ColumnIndex];
double LayoutColumnWidth = 0.0f;
if ((*LayoutColumnWidths)->TryGetNumberField(ColumnData->ColumnId.ToString(), LayoutColumnWidth))
{
FColumnWidth& ColumnWidth = ColumnWidths[ColumnIndex];
ColumnWidth.bIsAutoSized = false;
ColumnWidth.CurrentWidth = static_cast<float>(LayoutColumnWidth);
}
}
}
}
if (PreviousColumns != AvailableColumns)
{
ColumnNamesHeaderRow->ClearColumns();
if (CanEditRows())
{
ColumnNamesHeaderRow->AddColumn(
SHeaderRow::Column(RowDragDropColumnId)
[
SNew(SBox)
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
.ToolTip(IDocumentation::Get()->CreateToolTip(
LOCTEXT("DataTableRowHandleTooltip", "Drag Drop Handles"),
nullptr,
*FDataTableEditorUtils::VariableTypesTooltipDocLink,
TEXT("DataTableRowHandle")))
[
SNew(STextBlock)
.Text(FText::GetEmpty())
]
]
);
}
ColumnNamesHeaderRow->AddColumn(
SHeaderRow::Column(RowNumberColumnId)
.SortMode(this, &FDataTableEditor::GetColumnSortMode, RowNumberColumnId)
.OnSort(this, &FDataTableEditor::OnColumnNumberSortModeChanged)
.ManualWidth(this, &FDataTableEditor::GetRowNumberColumnWidth)
.OnWidthChanged(this, &FDataTableEditor::OnRowNumberColumnResized)
[
SNew(SBox)
.VAlign(VAlign_Fill)
.HAlign(HAlign_Fill)
.ToolTip(IDocumentation::Get()->CreateToolTip(
LOCTEXT("DataTableRowIndexTooltip", "Row Index"),
nullptr,
*FDataTableEditorUtils::VariableTypesTooltipDocLink,
TEXT("DataTableRowIndex")))
[
SNew(STextBlock)
.Text(FText::GetEmpty())
]
]
);
ColumnNamesHeaderRow->AddColumn(
SHeaderRow::Column(RowNameColumnId)
.DefaultLabel(LOCTEXT("DataTableRowName", "Row Name"))
.ManualWidth(this, &FDataTableEditor::GetRowNameColumnWidth)
.OnWidthChanged(this, &FDataTableEditor::OnRowNameColumnResized)
.SortMode(this, &FDataTableEditor::GetColumnSortMode, RowNameColumnId)
.OnSort(this, &FDataTableEditor::OnColumnNameSortModeChanged)
);
for (int32 ColumnIndex = 0; ColumnIndex < AvailableColumns.Num(); ++ColumnIndex)
{
const FDataTableEditorColumnHeaderDataPtr& ColumnData = AvailableColumns[ColumnIndex];
ColumnNamesHeaderRow->AddColumn(
SHeaderRow::Column(ColumnData->ColumnId)
.DefaultLabel(ColumnData->DisplayName)
.ManualWidth(TAttribute<float>::Create(TAttribute<float>::FGetter::CreateSP(this, &FDataTableEditor::GetColumnWidth, ColumnIndex)))
.OnWidthChanged(this, &FDataTableEditor::OnColumnResized, ColumnIndex)
.SortMode(this, &FDataTableEditor::GetColumnSortMode, ColumnData->ColumnId)
.OnSort(this, &FDataTableEditor::OnColumnSortModeChanged)
[
SNew(SBox)
.Padding(FMargin(0, 4, 0, 4))
.VAlign(VAlign_Fill)
.ToolTip(IDocumentation::Get()->CreateToolTip(FDataTableEditorUtils::GetRowTypeInfoTooltipText(ColumnData), nullptr, *FDataTableEditorUtils::VariableTypesTooltipDocLink, FDataTableEditorUtils::GetRowTypeTooltipDocExcerptName(ColumnData)))
[
SNew(STextBlock)
.Justification(ETextJustify::Center)
.Text(ColumnData->DisplayName)
]
]
);
}
}
UpdateVisibleRows(InCachedSelection, bUpdateEvenIfValid);
if (PropertyView.IsValid())
{
PropertyView->SetObject(Table);
}
}
void FDataTableEditor::UpdateVisibleRows(const FName InCachedSelection, const bool bUpdateEvenIfValid)
{
if (ActiveFilterText.IsEmptyOrWhitespace())
{
VisibleRows = AvailableRows;
}
else
{
VisibleRows.Empty(AvailableRows.Num());
const FString& ActiveFilterString = ActiveFilterText.ToString();
for (const FDataTableEditorRowListViewDataPtr& RowData : AvailableRows)
{
bool bPassesFilter = false;
if (RowData->DisplayName.ToString().Contains(ActiveFilterString))
{
bPassesFilter = true;
}
else
{
for (const FText& CellText : RowData->CellData)
{
if (CellText.ToString().Contains(ActiveFilterString))
{
bPassesFilter = true;
break;
}
}
}
if (bPassesFilter)
{
VisibleRows.Add(RowData);
}
}
}
CellsListView->RequestListRefresh();
RestoreCachedSelection(InCachedSelection, bUpdateEvenIfValid);
}
void FDataTableEditor::RestoreCachedSelection(const FName InCachedSelection, const bool bUpdateEvenIfValid)
{
// Validate the requested selection to see if it matches a known row
bool bSelectedRowIsValid = false;
if (!InCachedSelection.IsNone())
{
bSelectedRowIsValid = VisibleRows.ContainsByPredicate([&InCachedSelection](const FDataTableEditorRowListViewDataPtr& RowData) -> bool
{
return RowData->RowId == InCachedSelection;
});
}
// Apply the new selection (if required)
if (!bSelectedRowIsValid)
{
SetHighlightedRow((VisibleRows.Num() > 0) ? VisibleRows[0]->RowId : NAME_None);
CallbackOnRowHighlighted.ExecuteIfBound(HighlightedRowName);
}
else if (bUpdateEvenIfValid)
{
SetHighlightedRow(InCachedSelection);
CallbackOnRowHighlighted.ExecuteIfBound(HighlightedRowName);
}
}
TSharedRef<SVerticalBox> FDataTableEditor::CreateContentBox()
{
TSharedRef<SScrollBar> HorizontalScrollBar = SNew(SScrollBar)
.Orientation(Orient_Horizontal)
.Thickness(FVector2D(12.0f, 12.0f));
TSharedRef<SScrollBar> VerticalScrollBar = SNew(SScrollBar)
.Orientation(Orient_Vertical)
.Thickness(FVector2D(12.0f, 12.0f));
ColumnNamesHeaderRow = SNew(SHeaderRow);
CellsListView = SNew(SListView<FDataTableEditorRowListViewDataPtr>)
.ListItemsSource(&VisibleRows)
.HeaderRow(ColumnNamesHeaderRow)
.OnGenerateRow(this, &FDataTableEditor::MakeRowWidget)
.OnSelectionChanged(this, &FDataTableEditor::OnRowSelectionChanged)
.ExternalScrollbar(VerticalScrollBar)
.ConsumeMouseWheel(EConsumeMouseWheel::Always)
.SelectionMode(ESelectionMode::Single)
.AllowOverscroll(EAllowOverscroll::No);
LoadLayoutData();
RefreshCachedDataTable();
return SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
[
SAssignNew(SearchBoxWidget, SSearchBox)
.InitialText(this, &FDataTableEditor::GetFilterText)
.OnTextChanged(this, &FDataTableEditor::OnFilterTextChanged)
.OnTextCommitted(this, &FDataTableEditor::OnFilterTextCommitted)
]
]
+SVerticalBox::Slot()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
[
SNew(SScrollBox)
.Orientation(Orient_Horizontal)
.ExternalScrollbar(HorizontalScrollBar)
+SScrollBox::Slot()
[
CellsListView.ToSharedRef()
]
]
+SHorizontalBox::Slot()
.AutoWidth()
[
VerticalScrollBar
]
]
+SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+SHorizontalBox::Slot()
[
HorizontalScrollBar
]
];
}
TSharedRef<SWidget> FDataTableEditor::CreateRowEditorBox()
{
UDataTable* Table = Cast<UDataTable>(GetEditingObject());
// Support undo/redo
if (Table)
{
Table->SetFlags(RF_Transactional);
}
auto RowEditor = SNew(SRowEditor, Table);
RowEditor->RowSelectedCallback.BindSP(this, &FDataTableEditor::SetHighlightedRow);
CallbackOnRowHighlighted.BindSP(RowEditor, &SRowEditor::SelectRow);
CallbackOnDataTableUndoRedo.BindSP(RowEditor, &SRowEditor::HandleUndoRedo);
return RowEditor;
}
TSharedRef<SRowEditor> FDataTableEditor::CreateRowEditor(UDataTable* Table)
{
return SNew(SRowEditor, Table);
}
TSharedRef<SDockTab> FDataTableEditor::SpawnTab_RowEditor(const FSpawnTabArgs& Args)
{
check(Args.GetTabId().TabType == RowEditorTabId);
return SNew(SDockTab)
.Label(LOCTEXT("RowEditorTitle", "Row Editor"))
.TabColorScale(GetTabColorScale())
[
SNew(SBorder)
.Padding(2)
.VAlign(VAlign_Top)
.HAlign(HAlign_Fill)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
RowEditorTabWidget.ToSharedRef()
]
];
}
TSharedRef<SDockTab> FDataTableEditor::SpawnTab_DataTable( const FSpawnTabArgs& Args )
{
check( Args.GetTabId().TabType == DataTableTabId );
UDataTable* Table = Cast<UDataTable>(GetEditingObject());
// Support undo/redo
if (Table)
{
Table->SetFlags(RF_Transactional);
}
return SNew(SDockTab)
.Label( LOCTEXT("DataTableTitle", "Data Table") )
.TabColorScale( GetTabColorScale() )
[
SNew(SBorder)
.Padding(2)
.BorderImage( FAppStyle::GetBrush( "ToolPanel.GroupBorder" ) )
[
DataTableTabWidget.ToSharedRef()
]
];
}
TSharedRef<SDockTab> FDataTableEditor::SpawnTab_DataTableDetails(const FSpawnTabArgs& Args)
{
check(Args.GetTabId().TabType == DataTableDetailsTabId);
PropertyView->SetObject(GetEditableDataTable());
return SNew(SDockTab)
.Label(LOCTEXT("DataTableDetails", "Data Table Details"))
.TabColorScale(GetTabColorScale())
[
SNew(SBorder)
.Padding(2)
.BorderImage(FAppStyle::GetBrush("ToolPanel.GroupBorder"))
[
PropertyView.ToSharedRef()
]
];
}
void FDataTableEditor::SetHighlightedRow(FName Name)
{
if (Name == HighlightedRowName)
{
return;
}
if (Name.IsNone())
{
HighlightedRowName = NAME_None;
CellsListView->ClearSelection();
HighlightedVisibleRowIndex = INDEX_NONE;
}
else
{
HighlightedRowName = Name;
FDataTableEditorRowListViewDataPtr* NewSelectionPtr = NULL;
for (HighlightedVisibleRowIndex = 0; HighlightedVisibleRowIndex < VisibleRows.Num(); ++HighlightedVisibleRowIndex)
{
if (VisibleRows[HighlightedVisibleRowIndex]->RowId == Name)
{
NewSelectionPtr = &(VisibleRows[HighlightedVisibleRowIndex]);
break;
}
}
// Synchronize the list views
if (NewSelectionPtr)
{
CellsListView->SetSelection(*NewSelectionPtr);
CellsListView->RequestScrollIntoView(*NewSelectionPtr);
}
else
{
CellsListView->ClearSelection();
}
}
}
#undef LOCTEXT_NAMESPACE