You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
The various commands that depend on a valid selection (eg: Copy, Duplicate, etc) need to use a FCanExecuteAction delegate to ensure that a row is currently highlighted. For now, we can just use CanEditTable for that check. The Jira only calls out the toolbar as having this issue, but we need to add FCanExecuteAction to the commands as well. This ensures that these commands are disabled if they're ever added to a menu in the future. #jira UE-62702 #rb michael.noland [CL 15989996 by dave jones2 in ue5-main branch]
1406 lines
41 KiB
C++
1406 lines
41 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DataTableEditor.h"
|
|
#include "DataTableEditorModule.h"
|
|
#include "Dom/JsonObject.h"
|
|
#include "Editor.h"
|
|
#include "EditorStyleSet.h"
|
|
#include "Fonts/FontMeasure.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Commands/GenericCommands.h"
|
|
#include "Framework/Layout/Overscroll.h"
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "IDocumentation.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Modules/ModuleManager.h"
|
|
#include "Policies/PrettyJsonPrintPolicy.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "SDataTableListViewRow.h"
|
|
#include "Serialization/JsonReader.h"
|
|
#include "Serialization/JsonSerializer.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "Widgets/Input/SSearchBox.h"
|
|
#include "Widgets/Layout/SScrollBar.h"
|
|
#include "Widgets/Layout/SScrollBox.h"
|
|
#include "Widgets/SToolTip.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Widgets/Views/SListView.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Input/SHyperlink.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
#include "Widgets/Layout/SSeparator.h"
|
|
#include "SourceCodeNavigation.h"
|
|
#include "PropertyEditorModule.h"
|
|
#include "UObject/StructOnScope.h"
|
|
#include "Toolkits/GlobalEditorCommonCommands.h"
|
|
#include "Engine/DataTable.h"
|
|
#include "Subsystems/AssetEditorSubsystem.h"
|
|
|
|
#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(FEditorStyle::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(FEditorStyle::GetStyleSetName(), "AssetEditor.ReimportAsset"));
|
|
|
|
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(FEditorStyle::GetStyleSetName(), "DataTableEditor.Add"));
|
|
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(FEditorStyle::GetStyleSetName(), "DataTableEditor.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(FEditorStyle::GetStyleSetName(), "DataTableEditor.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(FEditorStyle::GetStyleSetName(), "DataTableEditor.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(FEditorStyle::GetStyleSetName(), "DataTableEditor.Remove"));
|
|
}
|
|
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 = FEditorStyle::GetWidgetStyle<FTextBlockStyle>("DataTableEditor.CellText");
|
|
const float CellPadding = 10.0f;
|
|
|
|
for (const FDataTableEditorRowListViewDataPtr& RowData : AvailableRows)
|
|
{
|
|
const float RowNumberWidth = 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 = FEditorStyle::GetWidgetStyle<FTextBlockStyle>("DataTableEditor.CellText");
|
|
static const float CellPadding = 10.0f;
|
|
|
|
for (const FDataTableEditorRowListViewDataPtr& RowData : AvailableRows)
|
|
{
|
|
const float RowNameWidth = 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(FEditorStyle::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(FEditorStyle::Get(), "Common.GotoNativeCodeHyperlink")
|
|
.OnNavigate(this, &FDataTableEditor::OnEditDataTableStructClicked)
|
|
.Text(FText::FromName(DataTable->GetRowStructName()))
|
|
.ToolTipText(LOCTEXT("DataTableRowToolTip", "Open the struct used for each row in this data table"))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.ButtonStyle(FEditorStyle::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(FEditorStyle::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)
|
|
.Icon(FEditorStyle::GetBrush("DataTableEditor.Tabs.Properties"))
|
|
.Label(LOCTEXT("RowEditorTitle", "Row Editor"))
|
|
.TabColorScale(GetTabColorScale())
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(2)
|
|
.VAlign(VAlign_Top)
|
|
.HAlign(HAlign_Fill)
|
|
.BorderImage(FEditorStyle::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)
|
|
.Icon( FEditorStyle::GetBrush("DataTableEditor.Tabs.Properties") )
|
|
.Label( LOCTEXT("DataTableTitle", "Data Table") )
|
|
.TabColorScale( GetTabColorScale() )
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(2)
|
|
.BorderImage( FEditorStyle::GetBrush( "ToolPanel.GroupBorder" ) )
|
|
[
|
|
DataTableTabWidget.ToSharedRef()
|
|
]
|
|
];
|
|
}
|
|
|
|
TSharedRef<SDockTab> FDataTableEditor::SpawnTab_DataTableDetails(const FSpawnTabArgs& Args)
|
|
{
|
|
check(Args.GetTabId().TabType == DataTableDetailsTabId);
|
|
|
|
PropertyView->SetObject(GetEditableDataTable());
|
|
|
|
return SNew(SDockTab)
|
|
.Icon(FEditorStyle::GetBrush("DataTableEditor.Tabs.Properties"))
|
|
.Label(LOCTEXT("DataTableDetails", "Data Table Details"))
|
|
.TabColorScale(GetTabColorScale())
|
|
[
|
|
SNew(SBorder)
|
|
.Padding(2)
|
|
.BorderImage(FEditorStyle::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
|