// Copyright Epic Games, Inc. All Rights Reserved. #include "DataTableCustomization.h" #include "AssetRegistry/AssetData.h" #include "Containers/Map.h" #include "DataTableEditorUtils.h" #include "Delegates/Delegate.h" #include "DetailWidgetRow.h" #include "Editor.h" #include "Engine/DataTable.h" #include "Fonts/SlateFontInfo.h" #include "Framework/Commands/UIAction.h" #include "HAL/Platform.h" #include "HAL/PlatformCrt.h" #include "IDetailChildrenBuilder.h" #include "Internationalization/Internationalization.h" #include "Internationalization/Text.h" #include "Misc/Attribute.h" #include "PropertyCustomizationHelpers.h" #include "PropertyEditorModule.h" #include "PropertyHandle.h" #include "Templates/Casts.h" #include "UObject/Class.h" #include "UObject/Object.h" #include "Widgets/DeclarativeSyntaxSupport.h" #include "Widgets/Text/STextBlock.h" class SToolTip; #define LOCTEXT_NAMESPACE "FDataTableCustomizationLayout" void FDataTableCustomizationLayout::CustomizeHeader(TSharedRef InStructPropertyHandle, class FDetailWidgetRow& HeaderRow, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { this->StructPropertyHandle = InStructPropertyHandle; if (StructPropertyHandle->HasMetaData(TEXT("RowType"))) { const FString& RowType = StructPropertyHandle->GetMetaData(TEXT("RowType")); RowTypeFilter = FName(*RowType); RowFilterStruct = UClass::TryFindTypeSlow(RowType); } FSimpleDelegate OnDataTableChangedDelegate = FSimpleDelegate::CreateSP(this, &FDataTableCustomizationLayout::OnDataTableChanged); StructPropertyHandle->SetOnPropertyValueChanged(OnDataTableChangedDelegate); HeaderRow .NameContent() [ InStructPropertyHandle->CreatePropertyNameWidget() ]; FDataTableEditorUtils::AddSearchForReferencesContextMenu(HeaderRow, FExecuteAction::CreateSP(this, &FDataTableCustomizationLayout::OnSearchForReferences)); } void FDataTableCustomizationLayout::CustomizeChildren(TSharedRef InStructPropertyHandle, class IDetailChildrenBuilder& StructBuilder, IPropertyTypeCustomizationUtils& StructCustomizationUtils) { /** Get all the existing property handles */ DataTablePropertyHandle = InStructPropertyHandle->GetChildHandle("DataTable"); RowNamePropertyHandle = InStructPropertyHandle->GetChildHandle("RowName"); const FName PropertyName = InStructPropertyHandle->GetProperty()->GetFName(); if (DataTablePropertyHandle->IsValidHandle() && RowNamePropertyHandle->IsValidHandle()) { /** Setup Change callback */ FSimpleDelegate OnDataTableChangedDelegate = FSimpleDelegate::CreateSP(this, &FDataTableCustomizationLayout::OnDataTableChanged); DataTablePropertyHandle->SetOnPropertyValueChanged(OnDataTableChangedDelegate); /** Construct a asset picker widget with a custom filter */ StructBuilder.AddCustomRow(LOCTEXT("DataTable_TableName", "Data Table")) .RowTag(PropertyName) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("DataTable_TableName", "Data Table")) .Font(StructCustomizationUtils.GetRegularFont()) ] .ValueContent() .MaxDesiredWidth(0.0f) // don't constrain the combo button width [ SNew(SObjectPropertyEntryBox) .PropertyHandle(DataTablePropertyHandle) .AllowedClass(UDataTable::StaticClass()) .OnShouldFilterAsset(this, &FDataTableCustomizationLayout::ShouldFilterAsset) ]; FPropertyComboBoxArgs ComboArgs(RowNamePropertyHandle, FOnGetPropertyComboBoxStrings::CreateSP(this, &FDataTableCustomizationLayout::OnGetRowStrings), FOnGetPropertyComboBoxValue::CreateSP(this, &FDataTableCustomizationLayout::OnGetRowValueString)); ComboArgs.ShowSearchForItemCount = 1; /** Construct a combo box widget to select from a list of valid options */ StructBuilder.AddCustomRow(LOCTEXT("DataTable_RowName", "Row Name")) .RowTag(PropertyName) .NameContent() [ SNew(STextBlock) .Text(LOCTEXT("DataTable_RowName", "Row Name")) .Font(StructCustomizationUtils.GetRegularFont()) ] .ValueContent() .MaxDesiredWidth(0.0f) // don't constrain the combo button width [ PropertyCustomizationHelpers::MakePropertyComboBox(ComboArgs) ]; } } bool FDataTableCustomizationLayout::GetCurrentValue(UDataTable*& OutDataTable, FName& OutName) const { if (RowNamePropertyHandle.IsValid() && RowNamePropertyHandle->IsValidHandle() && DataTablePropertyHandle.IsValid() && DataTablePropertyHandle->IsValidHandle()) { // If either handle is multiple value or failure, fail UObject* SourceDataTable = nullptr; if (DataTablePropertyHandle->GetValue(SourceDataTable) == FPropertyAccess::Success) { OutDataTable = Cast(SourceDataTable); if (RowNamePropertyHandle->GetValue(OutName) == FPropertyAccess::Success) { return true; } } } return false; } void FDataTableCustomizationLayout::OnSearchForReferences() { UDataTable* DataTable; FName RowName; if (GetCurrentValue(DataTable, RowName) && DataTable) { TArray AssetIdentifiers; AssetIdentifiers.Add(FAssetIdentifier(DataTable, RowName)); FEditorDelegates::OnOpenReferenceViewer.Broadcast(AssetIdentifiers, FReferenceViewerParams()); } } FString FDataTableCustomizationLayout::OnGetRowValueString() const { if (!RowNamePropertyHandle.IsValid() || !RowNamePropertyHandle->IsValidHandle()) { return FString(); } FName RowNameValue; const FPropertyAccess::Result RowResult = RowNamePropertyHandle->GetValue(RowNameValue); if (RowResult == FPropertyAccess::Success) { if (RowNameValue.IsNone()) { return LOCTEXT("DataTable_None", "None").ToString(); } return RowNameValue.ToString(); } else if (RowResult == FPropertyAccess::Fail) { return LOCTEXT("DataTable_None", "None").ToString(); } else { return LOCTEXT("MultipleValues", "Multiple Values").ToString(); } } void FDataTableCustomizationLayout::OnGetRowStrings(TArray< TSharedPtr >& OutStrings, TArray>& OutToolTips, TArray& OutRestrictedItems) const { UDataTable* DataTable = nullptr; FName IgnoredRowName; // Ignore return value as we will show rows if table is the same but row names are multiple values GetCurrentValue(DataTable, IgnoredRowName); TArray AllRowNames; if (DataTable != nullptr) { for (TMap::TConstIterator Iterator(DataTable->GetRowMap()); Iterator; ++Iterator) { AllRowNames.Add(Iterator.Key()); } // Sort the names alphabetically. AllRowNames.Sort(FNameLexicalLess()); } for (const FName& RowName : AllRowNames) { OutStrings.Add(MakeShared(RowName.ToString())); OutRestrictedItems.Add(false); } } void FDataTableCustomizationLayout::OnDataTableChanged() { UDataTable* CurrentTable; FName OldName; // Clear name on table change if no longer valid if (GetCurrentValue(CurrentTable, OldName)) { if (!CurrentTable || !CurrentTable->FindRowUnchecked(OldName)) { RowNamePropertyHandle->SetValue(FName()); } } } bool FDataTableCustomizationLayout::ShouldFilterAsset(const struct FAssetData& AssetData) { if (!RowTypeFilter.IsNone()) { static const FName RowStructureTagName("RowStructure"); FString RowStructure; if (AssetData.GetTagValue(RowStructureTagName, RowStructure)) { if (RowStructure == RowTypeFilter.ToString()) { return false; } // This is slow, but at the moment we don't have an alternative to the short struct name search UScriptStruct* RowStruct = UClass::TryFindTypeSlow(RowStructure); if (RowStruct && RowFilterStruct && RowStruct->IsChildOf(RowFilterStruct)) { return false; } } return true; } return false; } #undef LOCTEXT_NAMESPACE