You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
This change consists of multiple changes: Core: - Deprecation of ANY_PACKAGE macro. Added ANY_PACKAGE_DEPRECATED macro which can still be used for backwards compatibility purposes (only used in CoreUObject) - Deprecation of StaticFindObjectFast* functions that take bAnyPackage parameter - Added UStruct::GetStructPathName function that returns FTopLevelAssetPath representing the path name (package + object FName, super quick compared to UObject::GetPathName) + wrapper UClass::GetClassPathName to make it look better when used with UClasses - Added (Static)FindFirstObject* functions that find a first object given its Name (no Outer). These functions are used in places I consider valid to do global UObject (UClass) lookups like parsing command line parameters / checking for unique object names - Added static UClass::TryFindType function which serves a similar purpose as FindFirstObject however it's going to throw a warning (with a callstack / maybe ensure in the future?) if short class name is provided. This function is used in places that used to use short class names but now should have been converted to use path names to catch any potential regressions and or edge cases I missed. - Added static UClass::TryConvertShortNameToPathName utility function - Added static UClass::TryFixShortClassNameExportPath utility function - Object text export paths will now also include class path (Texture2D'/Game/Textures/Grass.Grass' -> /Script/Engine.Texture2D'/Game/Textures/Grass.Grass') - All places that manually generated object export paths for objects will now use FObjectPropertyBase::GetExportPath - Added a new startup test that checks for short type names in UClass/FProperty MetaData values AssetRegistry: - Deprecated any member variables (FAssetData / FARFilter) or functions that use FNames to represent class names and replaced them with FTopLevelAssetPath - Added new member variables and new function overloads that use FTopLevelAssetPath to represent class names - This also applies to a few other modules' APIs to match AssetRegistry changes Everything else: - Updated code that used ANY_PACKAGE (depending on the use case) to use FindObject(nullptr, PathToObject), UClass::TryFindType (used when path name is expected, warns if it's a short name) or FindFirstObject (usually for finding types based on user input but there's been a few legitimate use cases not related to user input) - Updated code that used AssetRegistry API to use FTopLevelAssetPaths and USomeClass::StaticClass()->GetClassPathName() instead of GetFName() - Updated meta data and hardcoded FindObject(ANY_PACKAGE, "EEnumNameOrClassName") calls to use path names #jira UE-99463 #rb many.people [FYI] Marcus.Wassmer #preflight 629248ec2256738f75de9b32 #codereviewnumbers 20320742, 20320791, 20320799, 20320756, 20320809, 20320830, 20320840, 20320846, 20320851, 20320863, 20320780, 20320765, 20320876, 20320786 #ROBOMERGE-OWNER: robert.manuszewski #ROBOMERGE-AUTHOR: robert.manuszewski #ROBOMERGE-SOURCE: CL 20430220 via CL 20433854 via CL 20435474 via CL 20435484 #ROBOMERGE-BOT: UE5 (Release-Engine-Staging -> Main) (v949-20362246) [CL 20448496 by robert manuszewski in ue5-main branch]
1067 lines
33 KiB
C++
1067 lines
33 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "DataTableEditorUtils.h"
|
|
#include "UObject/UObjectHash.h"
|
|
#include "UObject/UObjectIterator.h"
|
|
#include "Styling/SlateTypes.h"
|
|
#include "Fonts/FontMeasure.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Framework/Application/SlateUser.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Engine/UserDefinedStruct.h"
|
|
#include "Misc/StringUtility.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "K2Node_GetDataTableRow.h"
|
|
#include "Input/Reply.h"
|
|
#include "Widgets/Input/SComboButton.h"
|
|
#include "Widgets/Input/SEditableTextBox.h"
|
|
#include "Widgets/Views/SListView.h"
|
|
#include "Widgets/Input/SComboBox.h"
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "DetailWidgetRow.h"
|
|
#include "Editor.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "DataTableEditorUtils"
|
|
|
|
/** Combobox that allows selecting a struct row for a data table. Based off of SSearchableComboBox */
|
|
class SDataTableStructComboBox : public SComboButton
|
|
{
|
|
public:
|
|
/** Type of list used for showing menu options. */
|
|
typedef SListView< TSharedPtr<FString> > SComboListType;
|
|
/** Delegate type used to generate widgets that represent Options */
|
|
typedef typename TSlateDelegates< TSharedPtr<FString> >::FOnGenerateWidget FOnGenerateWidget;
|
|
typedef typename TSlateDelegates< TSharedPtr<FString> >::FOnSelectionChanged FOnSelectionChanged;
|
|
DECLARE_DELEGATE_OneParam(FOnFillComboBoxStrings, TArray<TSharedPtr<FString>>&);
|
|
|
|
SLATE_BEGIN_ARGS(SDataTableStructComboBox)
|
|
: _Content()
|
|
, _ComboBoxStyle(&FCoreStyle::Get().GetWidgetStyle< FComboBoxStyle >("ComboBox"))
|
|
, _ButtonStyle(nullptr)
|
|
, _ItemStyle(&FAppStyle::Get().GetWidgetStyle<FTableRowStyle>("ComboBox.Row"))
|
|
, _ContentPadding(_ComboBoxStyle->ContentPadding)
|
|
, _ForegroundColor(FSlateColor::UseStyle())
|
|
, _OnStructSelected()
|
|
, _InitiallySelectedItem(nullptr)
|
|
, _Method()
|
|
, _MaxListHeight(450.0f)
|
|
, _HasDownArrow(true)
|
|
{}
|
|
|
|
/** Slot for this button's content (optional) */
|
|
SLATE_DEFAULT_SLOT(FArguments, Content)
|
|
|
|
SLATE_STYLE_ARGUMENT(FComboBoxStyle, ComboBoxStyle)
|
|
|
|
/** The visual style of the button part of the combo box (overrides ComboBoxStyle) */
|
|
SLATE_STYLE_ARGUMENT(FButtonStyle, ButtonStyle)
|
|
|
|
SLATE_STYLE_ARGUMENT(FTableRowStyle, ItemStyle)
|
|
|
|
SLATE_ATTRIBUTE(FMargin, ContentPadding)
|
|
SLATE_ATTRIBUTE(FSlateColor, ForegroundColor)
|
|
|
|
SLATE_EVENT(FDataTableEditorUtils::FOnDataTableStructSelected, OnStructSelected)
|
|
|
|
/** The custom scrollbar to use in the ListView */
|
|
SLATE_ARGUMENT(TSharedPtr<SScrollBar>, CustomScrollbar)
|
|
|
|
/** The option that should be selected when the combo box is first created */
|
|
SLATE_ARGUMENT(TSharedPtr<FString>, InitiallySelectedItem)
|
|
|
|
SLATE_ARGUMENT(TOptional<EPopupMethod>, Method)
|
|
|
|
/** The max height of the combo box menu */
|
|
SLATE_ARGUMENT(float, MaxListHeight)
|
|
|
|
/**
|
|
* When false, the down arrow is not generated and it is up to the API consumer
|
|
* to make their own visual hint that this is a drop down.
|
|
*/
|
|
SLATE_ARGUMENT(bool, HasDownArrow)
|
|
|
|
SLATE_END_ARGS()
|
|
|
|
/**
|
|
* Construct the widget from a declaration
|
|
*
|
|
* @param InArgs Declaration from which to construct the combo box
|
|
*/
|
|
void Construct(const FArguments& InArgs);
|
|
|
|
void ClearSelection();
|
|
|
|
void SetSelectedItem(TSharedPtr<FString> InSelectedItem);
|
|
|
|
/** @return the item currently selected by the combo box. */
|
|
TSharedPtr<FString> GetSelectedItem();
|
|
|
|
/**
|
|
* Requests a list refresh after updating options
|
|
* Call SetSelectedItem to update the selected item if required
|
|
* @see SetSelectedItem
|
|
*/
|
|
void RefreshOptions();
|
|
|
|
/** Returns the asset data for a specific string, or null if not found */
|
|
const FAssetData* FindAssetDataForString(TSharedPtr<FString> StringOption) const;
|
|
|
|
/** Returns struct from AssetData, possibly loading it */
|
|
UScriptStruct* GetOrLoadStruct(const FAssetData* AssetData);
|
|
|
|
private:
|
|
|
|
/** Generate a row for the InItem in the combo box's list (passed in as OwnerTable). Do this by calling the user-specified OnGenerateWidget */
|
|
TSharedRef<ITableRow> GenerateMenuItemRow(TSharedPtr<FString> InItem, const TSharedRef<STableViewBase>& OwnerTable);
|
|
|
|
/** Called if the menu is closed */
|
|
void OnMenuOpenChanged(bool bOpen);
|
|
|
|
/** Invoked when the selection in the list changes */
|
|
void OnSelectionChanged_Internal(TSharedPtr<FString> ProposedSelection, ESelectInfo::Type SelectInfo);
|
|
|
|
/** Invoked when the search text changes */
|
|
void OnSearchTextChanged(const FText& ChangedText);
|
|
|
|
/** Text to display inside box */
|
|
FText GetSelectedText() const;
|
|
|
|
/** Show tooltip text for a specific option */
|
|
FText GetTooltipText(TSharedPtr<FString> StringOption);
|
|
|
|
/** Handle clicking on the content menu */
|
|
virtual FReply OnButtonClicked() override;
|
|
|
|
/** The item style to use. */
|
|
const FTableRowStyle* ItemStyle;
|
|
|
|
private:
|
|
/** Delegate that is invoked when the selected item in the combo box changes */
|
|
FDataTableEditorUtils::FOnDataTableStructSelected OnStructSelected;
|
|
/** The padding around each menu row */
|
|
FMargin MenuRowPadding;
|
|
/** The item currently selected in the combo box */
|
|
TSharedPtr<FString> SelectedItem;
|
|
/** The search field used for the combox box's contents */
|
|
TSharedPtr< SEditableTextBox > SearchField;
|
|
/** The ListView that we pop up; visualized the available options. */
|
|
TSharedPtr< SComboListType > ComboListView;
|
|
/** The Scrollbar used in the ListView. */
|
|
TSharedPtr< SScrollBar > CustomScrollbar;
|
|
|
|
/** List of names to show in combo box, there is a 1:1 mapping to PossibleStructs */
|
|
TArray< TSharedPtr<FString> > CurrentOptions;
|
|
/** List of AssetData representing rows */
|
|
TArray<FAssetData> PossibleStructs;
|
|
};
|
|
|
|
void SDataTableStructComboBox::Construct(const FArguments& InArgs)
|
|
{
|
|
check(InArgs._ComboBoxStyle);
|
|
|
|
ItemStyle = InArgs._ItemStyle;
|
|
|
|
MenuRowPadding = InArgs._ComboBoxStyle->MenuRowPadding;
|
|
|
|
// Work out which values we should use based on whether we were given an override, or should use the style's version
|
|
const FComboButtonStyle& OurComboButtonStyle = InArgs._ComboBoxStyle->ComboButtonStyle;
|
|
const FButtonStyle* const OurButtonStyle = InArgs._ButtonStyle ? InArgs._ButtonStyle : &OurComboButtonStyle.ButtonStyle;
|
|
|
|
this->OnStructSelected = InArgs._OnStructSelected;
|
|
|
|
CustomScrollbar = InArgs._CustomScrollbar;
|
|
|
|
TSharedRef<SWidget> ComboBoxMenuContent =
|
|
SNew(SBox)
|
|
.MaxDesiredHeight(InArgs._MaxListHeight)
|
|
[
|
|
SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot()
|
|
.AutoHeight()
|
|
[
|
|
SAssignNew(this->SearchField, SEditableTextBox)
|
|
.HintText(LOCTEXT("Search", "Search"))
|
|
.OnTextChanged(this, &SDataTableStructComboBox::OnSearchTextChanged)
|
|
]
|
|
|
|
+ SVerticalBox::Slot()
|
|
[
|
|
SAssignNew(this->ComboListView, SComboListType)
|
|
.ListItemsSource(&CurrentOptions)
|
|
.OnGenerateRow(this, &SDataTableStructComboBox::GenerateMenuItemRow)
|
|
.OnSelectionChanged(this, &SDataTableStructComboBox::OnSelectionChanged_Internal)
|
|
.SelectionMode(ESelectionMode::Single)
|
|
.ExternalScrollbar(InArgs._CustomScrollbar)
|
|
]
|
|
];
|
|
|
|
// Set up content
|
|
TSharedPtr<SWidget> ButtonContent = InArgs._Content.Widget;
|
|
if (InArgs._Content.Widget == SNullWidget::NullWidget)
|
|
{
|
|
SAssignNew(ButtonContent, STextBlock)
|
|
.Text(this, &SDataTableStructComboBox::GetSelectedText);
|
|
}
|
|
|
|
SComboButton::Construct(SComboButton::FArguments()
|
|
.ComboButtonStyle(&OurComboButtonStyle)
|
|
.ButtonStyle(OurButtonStyle)
|
|
.Method(InArgs._Method)
|
|
.ButtonContent()
|
|
[
|
|
ButtonContent.ToSharedRef()
|
|
]
|
|
.MenuContent()
|
|
[
|
|
ComboBoxMenuContent
|
|
]
|
|
.HasDownArrow(InArgs._HasDownArrow)
|
|
.ContentPadding(InArgs._ContentPadding)
|
|
.ForegroundColor(InArgs._ForegroundColor)
|
|
.OnMenuOpenChanged(this, &SDataTableStructComboBox::OnMenuOpenChanged)
|
|
.IsFocusable(true)
|
|
);
|
|
|
|
|
|
// Better to select search field so you can type right away
|
|
SetMenuContentWidgetToFocus(SearchField);
|
|
|
|
// Refresh options now
|
|
RefreshOptions();
|
|
|
|
// Need to establish the selected item at point of construction so its available for querying
|
|
// NB: If you need a selection to fire use SetItemSelection rather than setting an IntiallySelectedItem
|
|
SelectedItem = InArgs._InitiallySelectedItem;
|
|
if (TListTypeTraits<TSharedPtr<FString>>::IsPtrValid(SelectedItem))
|
|
{
|
|
ComboListView->Private_SetItemSelection(SelectedItem, true);
|
|
}
|
|
}
|
|
|
|
void SDataTableStructComboBox::ClearSelection()
|
|
{
|
|
ComboListView->ClearSelection();
|
|
}
|
|
|
|
void SDataTableStructComboBox::SetSelectedItem(TSharedPtr<FString> InSelectedItem)
|
|
{
|
|
if (TListTypeTraits<TSharedPtr<FString>>::IsPtrValid(InSelectedItem))
|
|
{
|
|
ComboListView->SetSelection(InSelectedItem);
|
|
}
|
|
else
|
|
{
|
|
ComboListView->ClearSelection();
|
|
}
|
|
}
|
|
|
|
TSharedPtr<FString> SDataTableStructComboBox::GetSelectedItem()
|
|
{
|
|
return SelectedItem;
|
|
}
|
|
|
|
FText SDataTableStructComboBox::GetSelectedText() const
|
|
{
|
|
if (SelectedItem.IsValid())
|
|
{
|
|
return FText::FromString(*SelectedItem);
|
|
}
|
|
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
FText SDataTableStructComboBox::GetTooltipText(TSharedPtr<FString> StringOption)
|
|
{
|
|
const FAssetData* FoundAsset = FindAssetDataForString(StringOption);
|
|
|
|
if (FoundAsset)
|
|
{
|
|
return FText::FromString(FoundAsset->PackageName.ToString());
|
|
}
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
void SDataTableStructComboBox::RefreshOptions()
|
|
{
|
|
if (PossibleStructs.Num() == 0)
|
|
{
|
|
FDataTableEditorUtils::GetPossibleStructAssetData(PossibleStructs);
|
|
|
|
CurrentOptions.Reset();
|
|
for (const FAssetData& FoundStruct : PossibleStructs)
|
|
{
|
|
CurrentOptions.Add(MakeShareable(new FString(FoundStruct.AssetName.ToString())));
|
|
}
|
|
}
|
|
|
|
if (!ComboListView->IsPendingRefresh())
|
|
{
|
|
ComboListView->RequestListRefresh();
|
|
}
|
|
}
|
|
|
|
const FAssetData* SDataTableStructComboBox::FindAssetDataForString(TSharedPtr<FString> StringOption) const
|
|
{
|
|
check(CurrentOptions.Num() == PossibleStructs.Num());
|
|
for (int32 i = 0; i < CurrentOptions.Num(); i++)
|
|
{
|
|
if (StringOption == CurrentOptions[i])
|
|
{
|
|
return &PossibleStructs[i];
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
UScriptStruct* SDataTableStructComboBox::GetOrLoadStruct(const FAssetData* AssetData)
|
|
{
|
|
if (!AssetData)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
return Cast<UScriptStruct>(AssetData->GetAsset());
|
|
}
|
|
|
|
TSharedRef<ITableRow> SDataTableStructComboBox::GenerateMenuItemRow(TSharedPtr<FString> InItem, const TSharedRef<STableViewBase>& OwnerTable)
|
|
{
|
|
FString SearchToken = SearchField->GetText().ToString().ToLower();
|
|
EVisibility WidgetVisibility = EVisibility::Visible;
|
|
if (!SearchToken.IsEmpty())
|
|
{
|
|
if (InItem->ToLower().Find(SearchToken) < 0)
|
|
{
|
|
WidgetVisibility = EVisibility::Collapsed;
|
|
}
|
|
}
|
|
|
|
TAttribute<FText> OnGetToolTip = TAttribute<FText>::Create(TAttribute<FText>::FGetter::CreateSP(this, &SDataTableStructComboBox::GetTooltipText, InItem));
|
|
|
|
return SNew(SComboRow<TSharedPtr<FString>>, OwnerTable)
|
|
.Style(ItemStyle)
|
|
.Visibility(WidgetVisibility)
|
|
.Padding(MenuRowPadding)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(FText::FromString(*InItem))
|
|
.ToolTipText(OnGetToolTip)
|
|
];
|
|
}
|
|
|
|
void SDataTableStructComboBox::OnMenuOpenChanged(bool bOpen)
|
|
{
|
|
if (bOpen == false)
|
|
{
|
|
if (TListTypeTraits<TSharedPtr<FString>>::IsPtrValid(SelectedItem))
|
|
{
|
|
// Ensure the ListView selection is set back to the last committed selection
|
|
ComboListView->SetSelection(SelectedItem, ESelectInfo::OnNavigation);
|
|
ComboListView->RequestScrollIntoView(SelectedItem, 0);
|
|
}
|
|
|
|
// Set focus back to ComboBox for users focusing the ListView that just closed
|
|
TSharedRef<SWidget> ThisRef = AsShared();
|
|
FSlateApplication::Get().ForEachUser([&ThisRef](FSlateUser& User) {
|
|
if (User.HasFocusedDescendants(ThisRef))
|
|
{
|
|
User.SetFocus(ThisRef, EFocusCause::SetDirectly);
|
|
}
|
|
});
|
|
}
|
|
}
|
|
|
|
void SDataTableStructComboBox::OnSelectionChanged_Internal(TSharedPtr<FString> ProposedSelection, ESelectInfo::Type SelectInfo)
|
|
{
|
|
// Ensure that the proposed selection is different
|
|
if (SelectInfo != ESelectInfo::OnNavigation)
|
|
{
|
|
// Ensure that the proposed selection is different from selected
|
|
if (ProposedSelection != SelectedItem)
|
|
{
|
|
SelectedItem = ProposedSelection;
|
|
|
|
UScriptStruct* SelectedStruct = GetOrLoadStruct(FindAssetDataForString(SelectedItem));
|
|
|
|
OnStructSelected.ExecuteIfBound(SelectedStruct);
|
|
|
|
}
|
|
// close combo even if user reselected item
|
|
this->SetIsOpen(false);
|
|
}
|
|
}
|
|
|
|
void SDataTableStructComboBox::OnSearchTextChanged(const FText& ChangedText)
|
|
{
|
|
FString SearchToken = ChangedText.ToString().ToLower();
|
|
for (int32 i = 0; i < CurrentOptions.Num(); i++)
|
|
{
|
|
TSharedPtr<ITableRow> Row = ComboListView->WidgetFromItem(CurrentOptions[i]);
|
|
if (Row)
|
|
{
|
|
if (SearchToken.IsEmpty())
|
|
{
|
|
Row->AsWidget()->SetVisibility(EVisibility::Visible);
|
|
}
|
|
else if (CurrentOptions[i]->ToLower().Find(SearchToken) >= 0)
|
|
{
|
|
Row->AsWidget()->SetVisibility(EVisibility::Visible);
|
|
}
|
|
else
|
|
{
|
|
Row->AsWidget()->SetVisibility(EVisibility::Collapsed);
|
|
}
|
|
}
|
|
}
|
|
|
|
ComboListView->RequestListRefresh();
|
|
|
|
SelectedItem = TSharedPtr< FString >();
|
|
}
|
|
|
|
FReply SDataTableStructComboBox::OnButtonClicked()
|
|
{
|
|
// if user clicked to close the combo menu
|
|
if (this->IsOpen())
|
|
{
|
|
// Re-select first selected item, just in case it was selected by navigation previously
|
|
TArray<TSharedPtr<FString>> SelectedItems = ComboListView->GetSelectedItems();
|
|
if (SelectedItems.Num() > 0)
|
|
{
|
|
OnSelectionChanged_Internal(SelectedItems[0], ESelectInfo::Direct);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SearchField->SetText(FText::GetEmpty());
|
|
RefreshOptions();
|
|
}
|
|
|
|
return SComboButton::OnButtonClicked();
|
|
}
|
|
|
|
const FString FDataTableEditorUtils::VariableTypesTooltipDocLink = TEXT("Shared/Editor/Blueprint/VariableTypes");
|
|
|
|
TSharedRef<SWidget> FDataTableEditorUtils::MakeRowStructureComboBox(FOnDataTableStructSelected OnSelected)
|
|
{
|
|
TSharedRef<SDataTableStructComboBox> ComboBox = SNew(SDataTableStructComboBox)
|
|
.OnStructSelected(OnSelected);
|
|
|
|
return ComboBox;
|
|
}
|
|
|
|
FDataTableEditorUtils::FDataTableEditorManager& FDataTableEditorUtils::FDataTableEditorManager::Get()
|
|
{
|
|
static TSharedRef< FDataTableEditorManager > EditorManager(new FDataTableEditorManager());
|
|
return *EditorManager;
|
|
}
|
|
|
|
bool FDataTableEditorUtils::RemoveRow(UDataTable* DataTable, FName Name)
|
|
{
|
|
bool bResult = false;
|
|
if (DataTable && DataTable->RowStruct)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("RemoveDataTableRow", "Remove Data Table Row"));
|
|
|
|
BroadcastPreChange(DataTable, EDataTableChangeInfo::RowList);
|
|
DataTable->Modify();
|
|
uint8* RowData = nullptr;
|
|
const bool bRemoved = DataTable->GetNonConstRowMap().RemoveAndCopyValue(Name, RowData);
|
|
if (bRemoved && RowData)
|
|
{
|
|
DataTable->RowStruct->DestroyStruct(RowData);
|
|
FMemory::Free(RowData);
|
|
bResult = true;
|
|
|
|
// Compact the map so that a subsequent add goes at the end of the table
|
|
DataTable->GetNonConstRowMap().CompactStable();
|
|
}
|
|
BroadcastPostChange(DataTable, EDataTableChangeInfo::RowList);
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
uint8* FDataTableEditorUtils::AddRow(UDataTable* DataTable, FName RowName)
|
|
{
|
|
if (!DataTable || (RowName == NAME_None) || (DataTable->GetRowMap().Find(RowName) != nullptr) || !DataTable->RowStruct)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("AddDataTableRow", "Add Data Table Row"));
|
|
|
|
BroadcastPreChange(DataTable, EDataTableChangeInfo::RowList);
|
|
DataTable->Modify();
|
|
// Allocate data to store information, using UScriptStruct to know its size
|
|
uint8* RowData = (uint8*)FMemory::Malloc(DataTable->RowStruct->GetStructureSize());
|
|
DataTable->RowStruct->InitializeStruct(RowData);
|
|
// And be sure to call DestroyScriptStruct later
|
|
|
|
// Add to row map
|
|
DataTable->AddRowInternal(RowName, RowData);
|
|
BroadcastPostChange(DataTable, EDataTableChangeInfo::RowList);
|
|
return RowData;
|
|
}
|
|
|
|
uint8* FDataTableEditorUtils::AddRowAboveOrBelowSelection(UDataTable* DataTable, const FName& RowName, const FName& NewRowName, ERowInsertionPosition InsertPosition)
|
|
{
|
|
if (!DataTable || (NewRowName == NAME_None) || (DataTable->GetRowMap().Find(NewRowName) != nullptr) || !DataTable->RowStruct)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("AddDataTableRowAboveBelow", "Add Data Table Row Above or Below"));
|
|
|
|
TArray<FName> OrderedRowNames;
|
|
DataTable->GetRowMap().GenerateKeyArray(OrderedRowNames);
|
|
|
|
int32 CurrentRowIndex = OrderedRowNames.IndexOfByKey(RowName);
|
|
if (CurrentRowIndex == INDEX_NONE)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (InsertPosition == ERowInsertionPosition::Below)
|
|
{
|
|
CurrentRowIndex += 1;
|
|
}
|
|
|
|
OrderedRowNames.Insert(NewRowName, CurrentRowIndex);
|
|
|
|
// Build a name -> index map as the KeySort will hit this a lot
|
|
TMap<FName, int32> NamesToNewIndex;
|
|
for (int32 NameIndex = 0; NameIndex < OrderedRowNames.Num(); ++NameIndex)
|
|
{
|
|
NamesToNewIndex.Add(OrderedRowNames[NameIndex], NameIndex);
|
|
}
|
|
|
|
|
|
BroadcastPreChange(DataTable, EDataTableChangeInfo::RowList);
|
|
|
|
DataTable->Modify();
|
|
|
|
// Allocate data to store information, using UScriptStruct to know its size
|
|
uint8* RowData = (uint8*)FMemory::Malloc(DataTable->RowStruct->GetStructureSize());
|
|
|
|
// And be sure to call DestroyScriptStruct later
|
|
DataTable->RowStruct->InitializeStruct(RowData);
|
|
|
|
// Add to row map
|
|
DataTable->AddRowInternal(NewRowName, RowData);
|
|
|
|
// Re-sort the map keys to match the new order
|
|
DataTable->GetNonConstRowMap().KeySort([&NamesToNewIndex](const FName& One, const FName& Two) -> bool
|
|
{
|
|
const int32 OneIndex = NamesToNewIndex.FindRef(One);
|
|
const int32 TwoIndex = NamesToNewIndex.FindRef(Two);
|
|
return OneIndex < TwoIndex;
|
|
});
|
|
|
|
BroadcastPostChange(DataTable, EDataTableChangeInfo::RowList);
|
|
|
|
return RowData;
|
|
}
|
|
|
|
|
|
uint8* FDataTableEditorUtils::DuplicateRow(UDataTable* DataTable, FName SourceRowName, FName RowName)
|
|
{
|
|
if (!DataTable || (SourceRowName == NAME_None) || !DataTable->RowMap.Contains(SourceRowName) || DataTable->RowMap.Contains(RowName) || !DataTable->RowStruct)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("DuplicateDataTableRow", "Duplicate Data Table Row"));
|
|
|
|
BroadcastPreChange(DataTable, EDataTableChangeInfo::RowList);
|
|
DataTable->Modify();
|
|
|
|
// Allocate data to store information, using UScriptStruct to know its size
|
|
uint8* OldRowData = *DataTable->RowMap.Find(SourceRowName);
|
|
uint8* NewRowData = (uint8*)FMemory::Malloc(DataTable->RowStruct->GetStructureSize());
|
|
|
|
DataTable->RowStruct->InitializeStruct(NewRowData);
|
|
DataTable->RowStruct->CopyScriptStruct(NewRowData, OldRowData);
|
|
|
|
// Add to row map
|
|
DataTable->RowMap.Add(RowName, NewRowData);
|
|
BroadcastPostChange(DataTable, EDataTableChangeInfo::RowList);
|
|
return NewRowData;
|
|
}
|
|
|
|
bool FDataTableEditorUtils::RenameRow(UDataTable* DataTable, FName OldName, FName NewName)
|
|
{
|
|
bool bResult = false;
|
|
if (DataTable)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("RenameDataTableRow", "Rename Data Table Row"));
|
|
|
|
BroadcastPreChange(DataTable, EDataTableChangeInfo::RowList);
|
|
DataTable->Modify();
|
|
|
|
uint8* RowData = nullptr;
|
|
const bool bValidnewName = (NewName != NAME_None) && !DataTable->GetRowMap().Find(NewName);
|
|
const bool bRemoved = bValidnewName && DataTable->GetNonConstRowMap().RemoveAndCopyValue(OldName, RowData);
|
|
if (bRemoved)
|
|
{
|
|
DataTable->GetNonConstRowMap().FindOrAdd(NewName) = RowData;
|
|
bResult = true;
|
|
}
|
|
BroadcastPostChange(DataTable, EDataTableChangeInfo::RowList);
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
bool FDataTableEditorUtils::MoveRow(UDataTable* DataTable, FName RowName, ERowMoveDirection Direction, int32 NumRowsToMoveBy)
|
|
{
|
|
if (!DataTable)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Our maps are ordered which is why we can get away with this
|
|
// If we ever change our map implementation, we'll need to preserve this order information in a separate array and
|
|
// make sure that order dependent code (such as exporting and the data table viewer) use that when dealing with rows
|
|
// This may also require making RowMap private and fixing up all the existing code that references it directly
|
|
TArray<FName> OrderedRowNames;
|
|
DataTable->GetRowMap().GenerateKeyArray(OrderedRowNames);
|
|
|
|
const int32 CurrentRowIndex = OrderedRowNames.IndexOfByKey(RowName);
|
|
if (CurrentRowIndex == INDEX_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Calculate our new row index, clamped to the available rows
|
|
int32 NewRowIndex = INDEX_NONE;
|
|
switch(Direction)
|
|
{
|
|
case ERowMoveDirection::Up:
|
|
NewRowIndex = FMath::Clamp(CurrentRowIndex - NumRowsToMoveBy, 0, OrderedRowNames.Num() - 1);
|
|
break;
|
|
|
|
case ERowMoveDirection::Down:
|
|
NewRowIndex = FMath::Clamp(CurrentRowIndex + NumRowsToMoveBy, 0, OrderedRowNames.Num() - 1);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (NewRowIndex == INDEX_NONE)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (CurrentRowIndex == NewRowIndex)
|
|
{
|
|
// Nothing to do, but not an error
|
|
return true;
|
|
}
|
|
|
|
// Swap the order around as requested
|
|
OrderedRowNames.RemoveAt(CurrentRowIndex, 1, false);
|
|
OrderedRowNames.Insert(RowName, NewRowIndex);
|
|
|
|
// Build a name -> index map as the KeySort will hit this a lot
|
|
TMap<FName, int32> NamesToNewIndex;
|
|
for (int32 NameIndex = 0; NameIndex < OrderedRowNames.Num(); ++NameIndex)
|
|
{
|
|
NamesToNewIndex.Add(OrderedRowNames[NameIndex], NameIndex);
|
|
}
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("MoveDataTableRow", "Move Data Table Row"));
|
|
|
|
BroadcastPreChange(DataTable, EDataTableChangeInfo::RowList);
|
|
DataTable->Modify();
|
|
|
|
// Re-sort the map keys to match the new order
|
|
DataTable->GetNonConstRowMap().KeySort([&NamesToNewIndex](const FName& One, const FName& Two) -> bool
|
|
{
|
|
const int32 OneIndex = NamesToNewIndex.FindRef(One);
|
|
const int32 TwoIndex = NamesToNewIndex.FindRef(Two);
|
|
return OneIndex < TwoIndex;
|
|
});
|
|
|
|
BroadcastPostChange(DataTable, EDataTableChangeInfo::RowList);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FDataTableEditorUtils::SelectRow(const UDataTable* DataTable, FName RowName)
|
|
{
|
|
for (auto Listener : FDataTableEditorManager::Get().GetListeners())
|
|
{
|
|
static_cast<INotifyOnDataTableChanged*>(Listener)->SelectionChange(DataTable, RowName);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
bool FDataTableEditorUtils::DiffersFromDefault(UDataTable* DataTable, FName RowName)
|
|
{
|
|
bool bDiffers = false;
|
|
|
|
if (DataTable && DataTable->GetRowMap().Contains(RowName))
|
|
{
|
|
uint8* RowData = DataTable->GetRowMap()[RowName];
|
|
|
|
if (const UUserDefinedStruct* UDStruct = Cast<const UUserDefinedStruct>(DataTable->RowStruct))
|
|
{
|
|
return !UDStruct->CompareScriptStruct(RowData, UDStruct->GetDefaultInstance(), PPF_None);
|
|
}
|
|
}
|
|
|
|
return bDiffers;
|
|
}
|
|
|
|
bool FDataTableEditorUtils::ResetToDefault(UDataTable* DataTable, FName RowName)
|
|
{
|
|
bool bResult = false;
|
|
|
|
if (DataTable && DataTable->GetRowMap().Contains(RowName))
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("ResetDataTableRowToDefault", "Reset Data Table Row to Default Values"));
|
|
|
|
BroadcastPreChange(DataTable, EDataTableChangeInfo::RowData);
|
|
DataTable->Modify();
|
|
|
|
uint8* RowData = DataTable->GetRowMap()[RowName];
|
|
|
|
if (const UUserDefinedStruct* UDStruct = Cast<const UUserDefinedStruct>(DataTable->RowStruct))
|
|
{
|
|
UDStruct->InitializeDefaultValue(RowData);
|
|
bResult = true;
|
|
}
|
|
|
|
BroadcastPostChange(DataTable, EDataTableChangeInfo::RowData);
|
|
}
|
|
|
|
return bResult;
|
|
}
|
|
|
|
void FDataTableEditorUtils::BroadcastPreChange(UDataTable* DataTable, EDataTableChangeInfo Info)
|
|
{
|
|
FDataTableEditorManager::Get().PreChange(DataTable, Info);
|
|
}
|
|
|
|
void FDataTableEditorUtils::BroadcastPostChange(UDataTable* DataTable, EDataTableChangeInfo Info)
|
|
{
|
|
if (DataTable && (EDataTableChangeInfo::RowList == Info))
|
|
{
|
|
for (TObjectIterator<UK2Node_GetDataTableRow> It(RF_Transient | RF_ClassDefaultObject, /** bIncludeDerivedClasses */ true, /** InternalExcludeFlags */ EInternalObjectFlags::Garbage); It; ++It)
|
|
{
|
|
It->OnDataTableRowListChanged(DataTable);
|
|
}
|
|
}
|
|
FDataTableEditorManager::Get().PostChange(DataTable, Info);
|
|
DataTable->OnDataTableChanged().Broadcast();
|
|
}
|
|
|
|
void FDataTableEditorUtils::CacheDataTableForEditing(const UDataTable* DataTable, TArray<FDataTableEditorColumnHeaderDataPtr>& OutAvailableColumns, TArray<FDataTableEditorRowListViewDataPtr>& OutAvailableRows)
|
|
{
|
|
if (!DataTable || !DataTable->RowStruct)
|
|
{
|
|
OutAvailableColumns.Empty();
|
|
OutAvailableRows.Empty();
|
|
return;
|
|
}
|
|
|
|
CacheDataForEditing(DataTable->RowStruct, DataTable->GetRowMap(), OutAvailableColumns, OutAvailableRows);
|
|
}
|
|
|
|
void FDataTableEditorUtils::CacheDataForEditing(const UScriptStruct* RowStruct, const TMap<FName, uint8*>& RowMap, TArray<FDataTableEditorColumnHeaderDataPtr>& OutAvailableColumns, TArray<FDataTableEditorRowListViewDataPtr>& OutAvailableRows)
|
|
{
|
|
TArray<FDataTableEditorColumnHeaderDataPtr> OldColumns = OutAvailableColumns;
|
|
TArray<FDataTableEditorRowListViewDataPtr> OldRows = OutAvailableRows;
|
|
|
|
// First build array of properties
|
|
TArray<const FProperty*> StructProps;
|
|
for (TFieldIterator<const FProperty> It(RowStruct); It; ++It)
|
|
{
|
|
const FProperty* Prop = *It;
|
|
check(Prop);
|
|
if (!Prop->HasMetaData(FName(TEXT("HideFromDataTableEditorColumn"))))
|
|
{
|
|
StructProps.Add(Prop);
|
|
}
|
|
}
|
|
|
|
TSharedRef<FSlateFontMeasure> FontMeasure = FSlateApplication::Get().GetRenderer()->GetFontMeasureService();
|
|
const FTextBlockStyle& CellTextStyle = FAppStyle::GetWidgetStyle<FTextBlockStyle>("DataTableEditor.CellText");
|
|
static const float CellPadding = 10.0f;
|
|
|
|
// Populate the column data
|
|
OutAvailableColumns.Reset(StructProps.Num());
|
|
for (int32 Index = 0; Index < StructProps.Num(); ++Index)
|
|
{
|
|
const FProperty* Prop = StructProps[Index];
|
|
const FText PropertyDisplayName = DataTableUtils::GetPropertyDisplayName(Prop, FName::NameToDisplayString(Prop->GetName(), Prop->IsA<FBoolProperty>()));
|
|
|
|
FDataTableEditorColumnHeaderDataPtr CachedColumnData;
|
|
|
|
// If at all possible, attempt to reuse previous columns if their data has not changed
|
|
if (Index >= OldColumns.Num() || OldColumns[Index]->ColumnId != Prop->GetFName() || !OldColumns[Index]->DisplayName.EqualTo(PropertyDisplayName))
|
|
{
|
|
CachedColumnData = MakeShareable(new FDataTableEditorColumnHeaderData());
|
|
CachedColumnData->ColumnId = Prop->GetFName();
|
|
CachedColumnData->DisplayName = PropertyDisplayName;
|
|
CachedColumnData->Property = Prop;
|
|
}
|
|
else
|
|
{
|
|
CachedColumnData = OldColumns[Index];
|
|
|
|
// Need to update property hard pointer in case it got reconstructed
|
|
CachedColumnData->Property = Prop;
|
|
}
|
|
|
|
CachedColumnData->DesiredColumnWidth = FontMeasure->Measure(CachedColumnData->DisplayName, CellTextStyle.Font).X + CellPadding;
|
|
|
|
OutAvailableColumns.Add(CachedColumnData);
|
|
}
|
|
|
|
// Populate the row data
|
|
OutAvailableRows.Reset(RowMap.Num());
|
|
int32 Index = 0;
|
|
for (auto RowIt = RowMap.CreateConstIterator(); RowIt; ++RowIt, ++Index)
|
|
{
|
|
FText RowName = FText::FromName(RowIt->Key);
|
|
FDataTableEditorRowListViewDataPtr CachedRowData;
|
|
|
|
// If at all possible, attempt to reuse previous rows if their data has not changed.
|
|
if (Index >= OldRows.Num() || OldRows[Index]->RowId != RowIt->Key || !OldRows[Index]->DisplayName.EqualTo(RowName))
|
|
{
|
|
CachedRowData = MakeShareable(new FDataTableEditorRowListViewData());
|
|
CachedRowData->RowId = RowIt->Key;
|
|
CachedRowData->DisplayName = RowName;
|
|
CachedRowData->CellData.Reserve(StructProps.Num());
|
|
}
|
|
else
|
|
{
|
|
CachedRowData = OldRows[Index];
|
|
CachedRowData->CellData.Reset(StructProps.Num());
|
|
}
|
|
|
|
CachedRowData->DesiredRowHeight = FontMeasure->GetMaxCharacterHeight(CellTextStyle.Font);
|
|
CachedRowData->RowNum = Index + 1;
|
|
|
|
// Always rebuild cell data
|
|
{
|
|
const uint8* RowData = RowIt.Value();
|
|
for (int32 ColumnIndex = 0; ColumnIndex < StructProps.Num(); ++ColumnIndex)
|
|
{
|
|
const FProperty* Prop = StructProps[ColumnIndex];
|
|
FDataTableEditorColumnHeaderDataPtr CachedColumnData = OutAvailableColumns[ColumnIndex];
|
|
|
|
const FText CellText = DataTableUtils::GetPropertyValueAsText(Prop, RowData);
|
|
CachedRowData->CellData.Add(CellText);
|
|
|
|
const FVector2D CellTextSize = FontMeasure->Measure(CellText, CellTextStyle.Font);
|
|
|
|
CachedRowData->DesiredRowHeight = FMath::Max(CachedRowData->DesiredRowHeight, CellTextSize.Y);
|
|
|
|
const float CellWidth = CellTextSize.X + CellPadding;
|
|
CachedColumnData->DesiredColumnWidth = FMath::Max(CachedColumnData->DesiredColumnWidth, CellWidth);
|
|
}
|
|
}
|
|
|
|
OutAvailableRows.Add(CachedRowData);
|
|
}
|
|
}
|
|
|
|
TArray<UScriptStruct*> FDataTableEditorUtils::GetPossibleStructs()
|
|
{
|
|
TArray< UScriptStruct* > RowStructs;
|
|
|
|
// Make combo of table rowstruct options
|
|
for (TObjectIterator<UScriptStruct> It; It; ++It)
|
|
{
|
|
UScriptStruct* Struct = *It;
|
|
if (IsValidTableStruct(Struct))
|
|
{
|
|
RowStructs.Add(Struct);
|
|
}
|
|
}
|
|
|
|
RowStructs.Sort();
|
|
|
|
return RowStructs;
|
|
}
|
|
|
|
void FDataTableEditorUtils::GetPossibleStructAssetData(TArray<FAssetData>& StructAssets)
|
|
{
|
|
StructAssets.Reset();
|
|
|
|
// Make combo of table rowstruct options
|
|
for (TObjectIterator<UScriptStruct> It; It; ++It)
|
|
{
|
|
UScriptStruct* Struct = *It;
|
|
if (IsValidTableStruct(Struct))
|
|
{
|
|
StructAssets.Add(FAssetData(Struct));
|
|
}
|
|
}
|
|
|
|
// Now get unloaded ones
|
|
const FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
TArray<FAssetData> AssetData;
|
|
AssetRegistryModule.Get().GetAssetsByClass(UUserDefinedStruct::StaticClass()->GetClassPathName(), AssetData);
|
|
|
|
for (int32 AssetIndex = 0; AssetIndex < AssetData.Num(); ++AssetIndex)
|
|
{
|
|
const FAssetData& Asset = AssetData[AssetIndex];
|
|
if (Asset.IsValid() && !Asset.IsAssetLoaded())
|
|
{
|
|
StructAssets.Add(Asset);
|
|
}
|
|
}
|
|
|
|
StructAssets.Sort([](const FAssetData& A, const FAssetData& B) { return A.AssetName.LexicalLess(B.AssetName); });
|
|
}
|
|
|
|
bool FDataTableEditorUtils::IsValidTableStruct(const UScriptStruct* Struct)
|
|
{
|
|
const UScriptStruct* TableRowStruct = FTableRowBase::StaticStruct();
|
|
|
|
// If a child of the table row struct base, but not itself
|
|
const bool bBasedOnTableRowBase = TableRowStruct && Struct->IsChildOf(TableRowStruct) && (Struct != TableRowStruct);
|
|
const bool bUDStruct = Struct->IsA<UUserDefinedStruct>();
|
|
const bool bValidStruct = (Struct->GetOutermost() != GetTransientPackage());
|
|
|
|
return (bBasedOnTableRowBase || bUDStruct) && bValidStruct;
|
|
}
|
|
|
|
void FDataTableEditorUtils::AddSearchForReferencesContextMenu(FDetailWidgetRow& RowNameDetailWidget, FExecuteAction SearchForReferencesAction)
|
|
{
|
|
if (SearchForReferencesAction.IsBound() && FEditorDelegates::OnOpenReferenceViewer.IsBound())
|
|
{
|
|
RowNameDetailWidget.AddCustomContextMenuAction(FUIAction(SearchForReferencesAction),
|
|
NSLOCTEXT("FDataTableRowUtils", "FDataTableRowUtils_SearchForReferences", "Find Row References"),
|
|
NSLOCTEXT("FDataTableRowUtils", "FDataTableRowUtils_SearchForReferencesTooltip", "Find assets that reference this Row"),
|
|
FSlateIcon());
|
|
}
|
|
}
|
|
|
|
FText FDataTableEditorUtils::GetHandleShortDescription(const UObject* TableAsset, FName RowName)
|
|
{
|
|
FText TableNameText = LOCTEXT("Description_None", "None");
|
|
FText RowNameText = TableNameText;
|
|
const int32 MaxChars = 15;
|
|
FString More = TEXT("...");
|
|
|
|
if (!TableAsset && RowName.IsNone())
|
|
{
|
|
// Just display None on it's own
|
|
return TableNameText;
|
|
}
|
|
|
|
if (TableAsset)
|
|
{
|
|
FString TempString = TableAsset->GetName();
|
|
|
|
// Chop off end if needed
|
|
if (TempString.Len() > MaxChars)
|
|
{
|
|
TempString.LeftInline(MaxChars - More.Len());
|
|
TempString.Append(More);
|
|
}
|
|
|
|
TableNameText = FText::AsCultureInvariant(TempString);
|
|
}
|
|
|
|
if (!RowName.IsNone())
|
|
{
|
|
FString TempString = RowName.ToString();
|
|
|
|
// Show right side if too long, usually more important
|
|
if (TempString.Len() > MaxChars)
|
|
{
|
|
TempString.RightInline(MaxChars - More.Len());
|
|
TempString.InsertAt(0, More);
|
|
}
|
|
|
|
RowNameText = FText::AsCultureInvariant(TempString);
|
|
}
|
|
|
|
return FText::Format(LOCTEXT("HandlePreviewFormat", "{0}[{1}]"), TableNameText, RowNameText);
|
|
}
|
|
|
|
FText FDataTableEditorUtils::GetRowTypeInfoTooltipText(FDataTableEditorColumnHeaderDataPtr ColumnHeaderDataPtr)
|
|
{
|
|
if (ColumnHeaderDataPtr.IsValid())
|
|
{
|
|
const FProperty* Property = ColumnHeaderDataPtr->Property;
|
|
if (Property)
|
|
{
|
|
const FFieldClass* PropertyClass = Property->GetClass();
|
|
const FStructProperty* StructProp = CastField<const FStructProperty>(Property);
|
|
if (StructProp)
|
|
{
|
|
FString TypeName = FName::NameToDisplayString(Property->GetCPPType(), Property->IsA<FBoolProperty>());
|
|
if (TypeName.Len())
|
|
{
|
|
// If type name starts with F and another capital letter, assume standard naming and remove F in the string shown to the user
|
|
if (TypeName.StartsWith("F", ESearchCase::CaseSensitive) && TypeName.Len() > 1 && FChar::IsUpper(TypeName.GetCharArray()[1]))
|
|
{
|
|
TypeName.RemoveFromStart("F");
|
|
}
|
|
return FText::FromString(TypeName);
|
|
}
|
|
}
|
|
if (PropertyClass)
|
|
{
|
|
return FText::FromString(PropertyClass->GetDescription());
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
FString FDataTableEditorUtils::GetRowTypeTooltipDocExcerptName(FDataTableEditorColumnHeaderDataPtr ColumnHeaderDataPtr)
|
|
{
|
|
if (ColumnHeaderDataPtr.IsValid())
|
|
{
|
|
const FProperty* Property = ColumnHeaderDataPtr->Property;
|
|
if (Property)
|
|
{
|
|
const FStructProperty* StructProp = CastField<const FStructProperty>(Property);
|
|
if (StructProp)
|
|
{
|
|
if (StructProp->Struct == TBaseStructure<FSoftObjectPath>::Get())
|
|
{
|
|
return "SoftObject";
|
|
}
|
|
if (StructProp->Struct == TBaseStructure<FSoftClassPath>::Get())
|
|
{
|
|
return "SoftClass";
|
|
}
|
|
FString TypeName = FName::NameToDisplayString(Property->GetCPPType(), Property->IsA<FBoolProperty>());
|
|
if (TypeName.Len())
|
|
{
|
|
// If type name starts with F and another capital letter, assume standard naming and remove F to match the doc excerpt name
|
|
if (TypeName.StartsWith("F", ESearchCase::CaseSensitive) && TypeName.Len() > 1 && FChar::IsUpper(TypeName.GetCharArray()[1]))
|
|
{
|
|
TypeName.RemoveFromStart("F");
|
|
}
|
|
return TypeName;
|
|
}
|
|
}
|
|
const FFieldClass* PropertyClass = Property->GetClass();
|
|
if (PropertyClass)
|
|
{
|
|
if (PropertyClass == FStrProperty::StaticClass())
|
|
{
|
|
return "String";
|
|
}
|
|
FString PropertyClassName = PropertyClass->GetName();
|
|
PropertyClassName.RemoveFromEnd("Property");
|
|
return PropertyClassName;
|
|
}
|
|
}
|
|
}
|
|
|
|
return "";
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|