Files
UnrealEngine/Engine/Plugins/Chooser/Source/ChooserEditor/Private/ChooserTableEditor.cpp
2025-05-18 13:04:45 +08:00

2664 lines
79 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ChooserTableEditor.h"
#include "Chooser.h"
#include "ChooserDetails.h"
#include "ChooserEditorWidgets.h"
#include "ChooserFindProperties.h"
#include "ChooserTableEditorCommands.h"
#include "ClassViewerFilter.h"
#include "DetailCategoryBuilder.h"
#include "Factories.h"
#include "GraphEditorSettings.h"
#include "IDetailsView.h"
#include "IPropertyAccessEditor.h"
#include "LandscapeRender.h"
#include "ObjectChooserClassFilter.h"
#include "ObjectChooserWidgetFactories.h"
#include "ObjectChooser_Asset.h"
#include "ObjectChooser_Class.h"
#include "PersonaModule.h"
#include "StructUtils/PropertyBag.h"
#include "PropertyCustomizationHelpers.h"
#include "PropertyEditorModule.h"
#include "RandomizeColumn.h"
#include "SAssetDropTarget.h"
#include "SChooserColumnHandle.h"
#include "SClassViewer.h"
#include "ScopedTransaction.h"
#include "SNestedChooserTree.h"
#include "SourceCodeNavigation.h"
#include "StructViewerModule.h"
#include "DragAndDrop/DecoratedDragDropOp.h"
#include "Framework/Docking/TabManager.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Misc/TransactionObjectEvent.h"
#include "Modules/ModuleManager.h"
#include "Styling/AppStyle.h"
#include "SChooserTableRow.h"
#include "Elements/Framework/TypedElementQueryBuilder.h"
#include "Widgets/Docking/SDockTab.h"
#include "Widgets/Input/SComboButton.h"
#include "Widgets/Input/SHyperlink.h"
#include "Widgets/Input/SNumericEntryBox.h"
#include "Widgets/Layout/SScrollBox.h"
#include "Widgets/Layout/SWidgetSwitcher.h"
#include "Widgets/Views/SListView.h"
#include "ToolMenus.h"
#include "UnrealExporter.h"
#include "Exporters/Exporter.h"
#include "HAL/PlatformApplicationMisc.h"
#include "Framework/Commands/GenericCommands.h"
#include "SPositiveActionButton.h"
#define LOCTEXT_NAMESPACE "ChooserEditor"
namespace UE::ChooserEditor
{
const FName FChooserTableEditor::ToolkitFName( TEXT( "ChooserTableEditor" ) );
const FName FChooserTableEditor::PropertiesTabId( TEXT( "ChooserEditor_Properties" ) );
const FName FChooserTableEditor::FindReplaceTabId( TEXT( "ChooserEditor_FindReplace" ) );
const FName FChooserTableEditor::TableTabId( TEXT( "ChooserEditor_Table" ) );
const FName FChooserTableEditor::NestedTablesTreeTabId( TEXT( "ChooserEditor_NestedTables" ) );
constexpr int HistorySize = 16;
void FChooserTableEditor::AddHistory()
{
// remove anything ahead of this in the history, if we had gone back
while (HistoryIndex !=0)
{
History.PopFront();
HistoryIndex--;
}
if (History.Num() >= HistorySize)
{
History.Pop();
}
History.AddFront(GetChooser());
}
bool FChooserTableEditor::CanNavigateBack() const
{
return HistoryIndex < History.Num() - 1;
}
void FChooserTableEditor::NavigateBack()
{
if (HistoryIndex < History.Num() - 1)
{
HistoryIndex++;
SetChooserTableToEdit(History[HistoryIndex], false);
}
}
bool FChooserTableEditor::CanNavigateForward() const
{
return HistoryIndex > 0;
}
void FChooserTableEditor::NavigateForward()
{
if (HistoryIndex > 0)
{
HistoryIndex--;
SetChooserTableToEdit(History[HistoryIndex], false);
}
}
void FChooserTableEditor::SetChooserTableToEdit(UChooserTable* Chooser, bool bApplyToHistory)
{
if (Chooser == GetChooser())
{
return;
}
BreadcrumbTrail->ClearCrumbs();
TArray<UChooserTable*> OuterList;
OuterList.Push(Chooser);
while(OuterList.Last() != GetRootChooser())
{
OuterList.Push(Cast<UChooserTable>(OuterList.Last()->GetOuter()));
}
while(!OuterList.IsEmpty())
{
UChooserTable* Popped = OuterList.Pop();
BreadcrumbTrail->PushCrumb(FText::FromString(Popped->GetName()), Popped);
}
if (bApplyToHistory)
{
AddHistory();
}
RefreshAll();
}
void FChooserTableEditor::PushChooserTableToEdit(UChooserTable* Chooser)
{
BreadcrumbTrail->PushCrumb(FText::FromString(Chooser->GetName()), Chooser);
AddHistory();
RefreshAll();
}
void FChooserTableEditor::PopChooserTableToEdit()
{
if (BreadcrumbTrail->HasCrumbs())
{
BreadcrumbTrail->PopCrumb();
RefreshAll();
}
}
void FChooserTableEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_ChooserTableEditor", "Chooser Table Editor"));
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
InTabManager->RegisterTabSpawner( PropertiesTabId, FOnSpawnTab::CreateSP(this, &FChooserTableEditor::SpawnPropertiesTab) )
.SetDisplayName( LOCTEXT("PropertiesTab", "Details") )
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon("EditorStyle", "LevelEditor.Tabs.Details"));
InTabManager->RegisterTabSpawner( TableTabId, FOnSpawnTab::CreateSP(this, &FChooserTableEditor::SpawnTableTab) )
.SetDisplayName( LOCTEXT("TableTab", "Chooser Table") )
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon("ChooserEditorStyle", "ChooserEditor.ChooserTableIconSmall"));
InTabManager->RegisterTabSpawner( NestedTablesTreeTabId, FOnSpawnTab::CreateSP(this, &FChooserTableEditor::SpawnNestedTablesTreeTab) )
.SetDisplayName( LOCTEXT("NestedTablesTab", "Nested Choosers") )
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon("ChooserEditorStyle", "ChooserEditor.ChooserTableIconSmall"));
InTabManager->RegisterTabSpawner( FindReplaceTabId, FOnSpawnTab::CreateSP(this, &FChooserTableEditor::SpawnFindReplaceTab) )
.SetDisplayName( LOCTEXT("FindReplaceTab", "Find/Replace") )
.SetGroup(WorkspaceMenuCategory.ToSharedRef())
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "Icons.Find"));
}
void FChooserTableEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
InTabManager->UnregisterTabSpawner( TableTabId );
InTabManager->UnregisterTabSpawner( PropertiesTabId );
InTabManager->UnregisterTabSpawner( FindReplaceTabId );
}
const FName FChooserTableEditor::ChooserEditorAppIdentifier( TEXT( "ChooserEditorApp" ) );
FChooserTableEditor::~FChooserTableEditor()
{
if (SelectedColumn)
{
SelectedColumn->RemoveFromRoot();
SelectedColumn = nullptr;
}
for (UObject* SelectedRow : SelectedRows)
{
SelectedRow->ClearFlags(RF_Standalone);
}
SelectedRows.Empty();
FCoreUObjectDelegates::OnObjectsReplaced.RemoveAll(this);
FCoreUObjectDelegates::OnObjectTransacted.RemoveAll(this);
DetailsView.Reset();
}
FName FChooserTableEditor::EditorName = "ChooserTableEditor";
FName FChooserTableEditor::ContextMenuName("ChooserEditorContextMenu"); // todo: for this to actually be extensible this needs to be somewhere public
FName FChooserTableEditor::GetEditorName() const
{
return EditorName;
}
void FChooserTableEditor::MakeDebugTargetMenu(UToolMenu* InToolMenu)
{
static FName SectionName = "Select Debug Target";
InToolMenu->bSearchable = true;
InToolMenu->AddMenuEntry(
SectionName,
FToolMenuEntry::InitMenuEntry(
"None",
LOCTEXT("None", "None"),
LOCTEXT("None Tooltip", "Clear selected debug target"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]()
{
UChooserTable* Chooser = GetRootChooser();
Chooser->ResetDebugTarget();
if (Chooser->GetEnableDebugTesting())
{
Chooser->SetEnableDebugTesting(false);
Chooser->SetDebugTestValuesValid(false);
UpdateTableColumns();
}
}),
FCanExecuteAction()
)
));
InToolMenu->AddMenuEntry(
SectionName,
FToolMenuEntry::InitMenuEntry(
"Manual",
LOCTEXT("Manual Testing", "Manual Testing"),
LOCTEXT("Manual Tooltip", "Test the chooser by manually entering values for each column"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this]()
{
UChooserTable* Chooser = GetRootChooser();
Chooser->ResetDebugTarget();
if (!Chooser->GetEnableDebugTesting())
{
Chooser->SetEnableDebugTesting(true);
Chooser->SetDebugTestValuesValid(true);
UpdateTableColumns();
}
}),
FCanExecuteAction()
)
));
const UChooserTable* Chooser = GetChooser();
Chooser->IterateRecentContextObjects([this, InToolMenu](const FString& ObjectName)
{
InToolMenu->AddMenuEntry(
SectionName,
FToolMenuEntry::InitMenuEntry(
FName(ObjectName),
FText::FromString(ObjectName),
LOCTEXT("Select Object ToolTip", "Select this object as the debug target"),
FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this, ObjectName]()
{
UChooserTable* Chooser = GetRootChooser();
Chooser->SetDebugTarget(ObjectName);
Chooser->SetDebugTestValuesValid(false);
if (!Chooser->GetEnableDebugTesting())
{
Chooser->SetEnableDebugTesting(true);
UpdateTableColumns();
}
}),
FCanExecuteAction()
)
));
}
);
}
TSharedPtr<SWidget> FChooserTableEditor::GenerateRowContextMenu()
{
UToolMenus* ToolMenus = UToolMenus::Get();
FToolMenuContext ToolMenuContext;
InitToolMenuContext(ToolMenuContext);
return ToolMenus->GenerateWidget(ContextMenuName, ToolMenuContext);
}
void FChooserTableEditor::RegisterToolbar()
{
UToolMenus* ToolMenus = UToolMenus::Get();
UToolMenu* ToolBar;
FName ParentName;
const FName MenuName = GetToolMenuToolbarName(ParentName);
if (ToolMenus->IsMenuRegistered(MenuName))
{
ToolBar = ToolMenus->ExtendMenu(MenuName);
}
else
{
ToolBar = UToolMenus::Get()->RegisterMenu(MenuName, ParentName, EMultiBoxType::ToolBar);
}
const FChooserTableEditorCommands& Commands = FChooserTableEditorCommands::Get();
FToolMenuInsert InsertAfterAssetSection("Asset", EToolMenuInsertType::After);
{
FToolMenuSection& Section = ToolBar->AddSection("Chooser", TAttribute<FText>(), InsertAfterAssetSection);
Section.AddEntry(FToolMenuEntry::InitToolBarButton(
Commands.EditChooserSettings,
TAttribute<FText>(),
TAttribute<FText>(),
FSlateIcon("EditorStyle", "FullBlueprintEditor.EditGlobalOptions")));
Section.AddEntry(FToolMenuEntry::InitToolBarButton(Commands.AutoPopulateAll));
Section.AddDynamicEntry("DebuggingCommands", FNewToolMenuSectionDelegate::CreateLambda([](FToolMenuSection& InSection)
{
UChooserEditorToolMenuContext* Context = InSection.FindContext<UChooserEditorToolMenuContext>();
if (Context)
{
if (TSharedPtr<FChooserTableEditor> ChooserEditor = Context->ChooserEditor.Pin())
{
InSection.AddEntry(FToolMenuEntry::InitComboButton( "SelectDebugTarget",
FToolUIActionChoice(),
FNewToolMenuDelegate::CreateSP(ChooserEditor.Get(), &FChooserTableEditor::MakeDebugTargetMenu),
TAttribute<FText>::CreateLambda([Chooser = ChooserEditor->GetRootChooser() ]
{
if (Chooser->HasDebugTarget())
{
return FText::FromString(Chooser->GetDebugTargetName());
}
else
{
return Chooser->GetEnableDebugTesting() ? LOCTEXT("Manual Testing", "Manual Testing") : LOCTEXT("Debug Target", "Debug Target");
}
}),
LOCTEXT("Debug Target Tooltip", "Select an object that has recently been the context object for this chooser to visualize the selection results")));
}
}
}));
}
}
void FChooserTableEditor::RegisterMenus()
{
UToolMenus* ToolMenus = UToolMenus::Get();
const FChooserTableEditorCommands& Commands = FChooserTableEditorCommands::Get();
// Table Context Menu
UToolMenu* ToolMenu;
if (ToolMenus->IsMenuRegistered(ContextMenuName))
{
ToolMenu = ToolMenus->ExtendMenu(ContextMenuName);
}
else
{
ToolMenu = UToolMenus::Get()->RegisterMenu(ContextMenuName, NAME_None, EMultiBoxType::Menu);
}
if (ToolMenu)
{
FToolMenuSection& Section = ToolMenu->AddSection("ChooserEditorContext", TAttribute<FText>());
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FGenericCommands::Get().Copy));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FGenericCommands::Get().Cut));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FGenericCommands::Get().Paste));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FGenericCommands::Get().Duplicate));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(FGenericCommands::Get().Delete));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(Commands.Disable));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(Commands.MoveUp));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(Commands.MoveDown));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(Commands.MoveLeft));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(Commands.MoveRight));
Section.AddEntry(FToolMenuEntry::InitMenuEntry(Commands.AutoPopulateSelection));
Section.AddDynamicEntry("ColumnInputType", FNewToolMenuSectionDelegate::CreateLambda([this] (FToolMenuSection& Section)
{
if (CurrentSelectionType == ESelectionType::Column && SelectedColumn)
{
Section.AddSubMenu("ParameterType", LOCTEXT("Parameter Type", "Parameter Type"),
LOCTEXT("Parameter Type Tooltip", "Change the type of input/output parameter for this column"),
FNewToolMenuChoice(FOnGetContent::CreateLambda([this]()
{
int ColumnIndex = SelectedColumn->Column;
UChooserTable* Chooser = SelectedColumn->Chooser;
FStructViewerInitializationOptions Options;
Options.StructFilter = MakeShared<FStructFilter>(Chooser->ColumnsStructs[ColumnIndex].Get<FChooserColumnBase>().GetInputBaseType());
Options.bAllowViewOptions = false;
Options.bShowNoneOption = false;
Options.NameTypeToDisplay = EStructViewerNameTypeToDisplay::DisplayName;
// Add class filter for columns here
TSharedRef<SWidget> Widget = FModuleManager::LoadModuleChecked<FStructViewerModule>("StructViewer").CreateStructViewer(Options, FOnStructPicked::CreateLambda([this, ColumnIndex](const UScriptStruct* ChosenStruct)
{
const FScopedTransaction Transaction(LOCTEXT("SetColumnInputType", "Set Column Input Type"));
UChooserTable* ChooserTable = GetChooser();
ChooserTable->ColumnsStructs[ColumnIndex].GetMutable<FChooserColumnBase>().SetInputType(ChosenStruct);
ChooserTable->Modify(true);
UpdateTableColumns();
UpdateTableRows();
if (SelectedColumn && SelectedColumn->Column == ColumnIndex)
{
// if this column was selected, reselect to refresh the details widgets
SelectColumn(ChooserTable, ColumnIndex);
}
}));
return Widget;
}))
);
}
}));
}
struct Local
{
static void FillEditMenu(FMenuBuilder& MenuBuilder)
{
MenuBuilder.BeginSection("ChooserEditing", LOCTEXT("Chooser Table Editing", "Chooser Table"));
{
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Copy, NAME_None);
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Cut, NAME_None);
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Paste, NAME_None);
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Duplicate, NAME_None, LOCTEXT("Duplicate Selection", "Duplicate Selection"));
MenuBuilder.AddMenuEntry(FGenericCommands::Get().Delete, NAME_None, LOCTEXT("Delete Selection", "Delete Selection"));
MenuBuilder.AddMenuEntry(FChooserTableEditorCommands::Get().Disable, NAME_None, LOCTEXT("Disable Selection", "Disable Selection"));
MenuBuilder.AddMenuEntry(FChooserTableEditorCommands::Get().RemoveDisabledData, NAME_None);
}
MenuBuilder.EndSection();
}
};
TSharedPtr<FExtender> MenuExtender = MakeShareable(new FExtender);
// Extend the Edit menu
MenuExtender->AddMenuExtension(
"EditHistory",
EExtensionHook::After,
GetToolkitCommands(),
FMenuExtensionDelegate::CreateStatic(&Local::FillEditMenu));
AddMenuExtender(MenuExtender);
}
void FChooserTableEditor::InitToolMenuContext(FToolMenuContext& MenuContext)
{
FAssetEditorToolkit::InitToolMenuContext(MenuContext);
UChooserEditorToolMenuContext* Context = NewObject<UChooserEditorToolMenuContext>();
Context->ChooserEditor = SharedThis(this);
MenuContext.AppendCommandList(GetToolkitCommands());
MenuContext.AddObject(Context);
}
void FChooserTableEditor::SaveAsset_Execute()
{
AutoPopulateAll();
FAssetEditorToolkit::SaveAsset_Execute();
}
void FChooserTableEditor::BindCommands()
{
const FChooserTableEditorCommands& Commands = FChooserTableEditorCommands::Get();
ToolkitCommands->MapAction(
Commands.EditChooserSettings,
FExecuteAction::CreateSP(this, &FChooserTableEditor::SelectRootProperties));
ToolkitCommands->MapAction(
Commands.AutoPopulateAll,
FExecuteAction::CreateSP(this, &FChooserTableEditor::AutoPopulateAll));
ToolkitCommands->MapAction(
Commands.RemoveDisabledData,
FExecuteAction::CreateSP(this, &FChooserTableEditor::RemoveDisabledData));
ToolkitCommands->MapAction(
FGenericCommands::Get().Delete,
FExecuteAction::CreateSP(this, &FChooserTableEditor::DeleteSelection),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::HasSelection)
);
ToolkitCommands->MapAction(
FGenericCommands::Get().Duplicate,
FExecuteAction::CreateSP(this, &FChooserTableEditor::DuplicateSelection),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::HasSelection)
);
ToolkitCommands->MapAction(
Commands.AutoPopulateSelection,
FExecuteAction::CreateSP(this, &FChooserTableEditor::AutoPopulateSelection),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::CanAutoPopulateSelection),
FGetActionCheckState(),FIsActionButtonVisible::CreateSP(this, &FChooserTableEditor::HasSelection)
);
ToolkitCommands->MapAction(
Commands.Disable,
FExecuteAction::CreateSP(this, &FChooserTableEditor::ToggleDisableSelection),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::HasSelection),
FIsActionChecked::CreateSP(this, &FChooserTableEditor::IsSelectionDisabled),
FIsActionButtonVisible::CreateSP(this, &FChooserTableEditor::HasSelection)
);
ToolkitCommands->MapAction(
FGenericCommands::Get().Copy,
FExecuteAction::CreateSP(this, &FChooserTableEditor::CopySelection),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::HasSelection));
ToolkitCommands->MapAction(
FGenericCommands::Get().Cut,
FExecuteAction::CreateSP(this, &FChooserTableEditor::CutSelection),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::HasSelection));
ToolkitCommands->MapAction(
FGenericCommands::Get().Paste,
FExecuteAction::CreateSP(this, &FChooserTableEditor::Paste),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::CanPaste));
ToolkitCommands->MapAction(
Commands.MoveUp,
FExecuteAction::CreateSP(this, &FChooserTableEditor::MoveRowsUp),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::CanMoveRowsUp),
FIsActionChecked(),
FIsActionButtonVisible::CreateSP(this, &FChooserTableEditor::HasRowsSelected)
);
ToolkitCommands->MapAction(
Commands.MoveDown,
FExecuteAction::CreateSP(this, &FChooserTableEditor::MoveRowsDown),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::CanMoveRowsDown),
FIsActionChecked(),
FIsActionButtonVisible::CreateSP(this, &FChooserTableEditor::HasRowsSelected)
);
ToolkitCommands->MapAction(
Commands.MoveLeft,
FExecuteAction::CreateSP(this, &FChooserTableEditor::MoveColumnLeft),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::CanMoveColumnLeft),
FIsActionChecked(),
FIsActionButtonVisible::CreateSP(this, &FChooserTableEditor::HasColumnSelected)
);
ToolkitCommands->MapAction(
Commands.MoveRight,
FExecuteAction::CreateSP(this, &FChooserTableEditor::MoveColumnRight),
FCanExecuteAction::CreateSP(this, &FChooserTableEditor::CanMoveColumnRight),
FIsActionChecked(),
FIsActionButtonVisible::CreateSP(this, &FChooserTableEditor::HasColumnSelected)
);
}
void FChooserTableEditor::OnObjectsTransacted(UObject* Object, const FTransactionObjectEvent& Event)
{
if (UChooserTable* ChooserTable = Cast<UChooserTable>(Object))
{
// if this is the chooser we're editing
if (GetChooser() == ChooserTable)
{
if (CurrentSelectionType == ESelectionType::Rows)
{
// refresh details if we have rows selected
RefreshRowSelectionDetails();
}
}
}
if (UChooserRowDetails* RowDetails = Cast<UChooserRowDetails>(Object))
{
// if this is for the chooser we're editing
if (GetChooser() == RowDetails->Chooser)
{
if (RowDetails->Chooser->ResultsStructs.IsValidIndex(RowDetails->Row))
{
// copy all the values over
TValueOrError<FStructView, EPropertyBagResult> Result = RowDetails->Properties.GetValueStruct("Result", FInstancedStruct::StaticStruct());
if (Result.IsValid())
{
RowDetails->Chooser->ResultsStructs[RowDetails->Row] = Result.GetValue().Get<FInstancedStruct>();
}
int ColumnIndex = 0;
for (FInstancedStruct& ColumnData : RowDetails->Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
Column.SetFromDetails(RowDetails->Properties, ColumnIndex, RowDetails->Row);
ColumnIndex++;
}
TValueOrError<bool, EPropertyBagResult> DisabledResult = RowDetails->Properties.GetValueBool("Disabled");
RowDetails->Chooser->DisabledRows[RowDetails->Row] = DisabledResult.GetValue();
}
else if (RowDetails->Row == ColumnWidget_SpecialIndex_Fallback)
{
TValueOrError<FStructView, EPropertyBagResult> Result = RowDetails->Properties.GetValueStruct("Result", FInstancedStruct::StaticStruct());
if (Result.IsValid())
{
RowDetails->Chooser->FallbackResult = Result.GetValue().Get<FInstancedStruct>();
}
int ColumnIndex = 0;
for (FInstancedStruct& ColumnData : RowDetails->Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
Column.SetFromDetails(RowDetails->Properties, ColumnIndex, RowDetails->Row);
ColumnIndex++;
}
}
RefreshAll();
}
}
}
void FChooserTableEditor::InitEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, const TArray<UObject*>& ObjectsToEdit, FGetDetailsViewObjects GetDetailsViewObjects )
{
UChooserTable* Chooser = Cast<UChooserTable>(ObjectsToEdit[0]);
RootChooser = Chooser->GetRootChooser();
check(RootChooser);
History.Reserve(HistorySize);
BreadcrumbTrail = SNew(SBreadcrumbTrail<UChooserTable*>)
.ButtonStyle(FAppStyle::Get(), "GraphBreadcrumbButton")
.TextStyle(FAppStyle::Get(), "GraphBreadcrumbButtonText")
.ButtonContentPadding( FMargin(4.f, 2.f) )
.DelimiterImage( FAppStyle::GetBrush("BreadcrumbTrail.Delimiter") )
.OnCrumbPushed_Lambda([this](UChooserTable* Table)
{
RefreshAll();
})
.OnCrumbClicked_Lambda([this](UChooserTable* Table)
{
AddHistory();
RefreshAll();
})
.GetCrumbMenuContent_Lambda([this](UChooserTable* Item)
{
return MakeChoosersMenu(Item);
})
;
BreadcrumbTrail->PushCrumb(FText::FromString(RootChooser->GetName()), RootChooser);
AddHistory();
FCoreUObjectDelegates::OnObjectsReplaced.AddSP(this, &FChooserTableEditor::OnObjectsReplaced);
FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked<FPropertyEditorModule>( "PropertyEditor" );
FDetailsViewArgs DetailsViewArgs;
DetailsViewArgs.NotifyHook = this;
DetailsViewArgs.NameAreaSettings = FDetailsViewArgs::HideNameArea;
DetailsView = PropertyEditorModule.CreateDetailView( DetailsViewArgs );
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout( "Standalone_ChooserTableEditor_Layout_v1.6" )
->AddArea
(
FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical)
->Split
(
FTabManager::NewSplitter()
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.7)
->AddTab( TableTabId, ETabState::OpenedTab )
)
->Split
(
FTabManager::NewSplitter()->SetOrientation(Orient_Vertical)
->SetSizeCoefficient(0.3)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.5)
->AddTab( PropertiesTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.5)
->AddTab( NestedTablesTreeTabId, ETabState::OpenedTab )
)
)
)
);
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;
FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, FChooserTableEditor::ChooserEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectsToEdit );
BindCommands();
// todo: should these be triggered once per session by the module?
RegisterToolbar();
RegisterMenus();
RegenerateMenusAndToolbars();
SelectRootProperties();
SetChooserTableToEdit(Chooser);
FAnimAssetFindReplaceConfig FindReplaceConfig;
FindReplaceConfig.InitialProcessorClass = UChooserFindProperties::StaticClass();
FCoreUObjectDelegates::OnObjectTransacted.AddSP(this, &FChooserTableEditor::OnObjectsTransacted);
}
void FChooserTableEditor::FocusWindow(UObject* ObjectToFocusOn)
{
if (UChooserTable* Chooser = Cast<UChooserTable>(ObjectToFocusOn))
{
SetChooserTableToEdit(Chooser);
}
// refresh, even if we set the same chooser we were already editing. (Rewind Debugger double click enables debug testing, which requires recreating the header widgets)
RefreshAll();
FAssetEditorToolkit::FocusWindow(ObjectToFocusOn);
}
FName FChooserTableEditor::GetToolkitFName() const
{
return ToolkitFName;
}
FText FChooserTableEditor::GetBaseToolkitName() const
{
return LOCTEXT("AppLabel", "Chooser Table Editor");
}
void FChooserTableEditor::RefreshAll()
{
if (HeaderRow)
{
// Cache Selection state
ESelectionType CachedSelectionType = CurrentSelectionType;
int SelectedColumnIndex = -1;
UChooserTable* SelectedChooser = nullptr;
TArray<int> CachedSelectedRows;
if (CachedSelectionType == ESelectionType::Column)
{
SelectedColumnIndex = SelectedColumn->Column;
SelectedChooser = SelectedColumn->Chooser;
}
else if (CachedSelectionType == ESelectionType::Rows)
{
if (!SelectedRows.IsEmpty())
{
SelectedChooser = SelectedRows[0]->Chooser;
}
for(const TObjectPtr<UChooserRowDetails>& SelectedRow : SelectedRows)
{
CachedSelectedRows.Add(SelectedRow->Row);
}
}
UpdateTableColumns();
UpdateTableRows();
// reapply cached selection state
if (CachedSelectionType == ESelectionType::Root)
{
SelectRootProperties();
}
else if (CachedSelectionType == ESelectionType::Column)
{
SelectColumn(SelectedChooser, SelectedColumnIndex);
}
else if (CachedSelectionType == ESelectionType::Rows)
{
ClearSelectedRows();
for(int Row : CachedSelectedRows)
{
SelectRow(Row, false);
}
}
}
RefreshNestedChoosers();
}
void FChooserTableEditor::RefreshNestedChoosers()
{
if (NestedChooserTree.IsValid())
{
NestedChooserTree->RefreshAll();
}
}
bool FChooserTableEditor::MatchesContext(const FTransactionContext& InContext, const TArray<TPair<UObject*, FTransactionObjectEvent>>& TransactionObjectContexts) const
{
TArray<UObject*> ContainedObjects;
GetObjectsWithOuter(RootChooser->GetPackage(), ContainedObjects, true);
for(const TPair<UObject*, FTransactionObjectEvent>& Entry : TransactionObjectContexts)
{
if (ContainedObjects.Contains(Entry.Key))
{
return true;
}
}
return false;
}
void FChooserTableEditor::PostUndo(bool bSuccess)
{
RefreshAll();
}
void FChooserTableEditor::PostRedo(bool bSuccess)
{
RefreshAll();
}
void FChooserTableEditor::NotifyPreChange(FProperty* PropertyAboutToChange)
{
}
void FChooserTableEditor::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged)
{
// Called on details panel edits
if (CurrentSelectionType==ESelectionType::Root)
{
// Editing the root in the details panel can change ContextData that means all wigets need to be refreshed
UpdateTableColumns();
UpdateTableRows();
SelectRootProperties();
}
if (CurrentSelectionType==ESelectionType::Column)
{
check(SelectedColumn);
int SelectedColumnIndex = SelectedColumn->Column;
UChooserTable* SelectedColumnChooser = SelectedColumn->Chooser;
// Editing column properties can change the column type, which requires refreshing everything
UpdateTableColumns();
UpdateTableRows();
SelectColumn(SelectedColumnChooser, SelectedColumnIndex);
}
// editing row data should not require any refreshing
}
FText FChooserTableEditor::GetToolkitName() const
{
check( RootChooser );
return FText::FromString(RootChooser->GetName());
}
FText FChooserTableEditor::GetToolkitToolTipText() const
{
check( RootChooser );
return FAssetEditorToolkit::GetToolTipTextForObject(RootChooser);
}
FLinearColor FChooserTableEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor( 0.5f, 0.0f, 0.0f, 0.5f );
}
void FChooserTableEditor::SetPropertyVisibilityDelegate(FIsPropertyVisible InVisibilityDelegate)
{
DetailsView->SetIsPropertyVisibleDelegate(InVisibilityDelegate);
DetailsView->ForceRefresh();
}
void FChooserTableEditor::SetPropertyEditingEnabledDelegate(FIsPropertyEditingEnabled InPropertyEditingDelegate)
{
DetailsView->SetIsPropertyEditingEnabledDelegate(InPropertyEditingDelegate);
DetailsView->ForceRefresh();
}
TSharedRef<SDockTab> FChooserTableEditor::SpawnPropertiesTab( const FSpawnTabArgs& Args )
{
check( Args.GetTabId() == PropertiesTabId );
return SNew(SDockTab)
.Label( LOCTEXT("GenericDetailsTitle", "Details") )
.TabColorScale( GetTabColorScale() )
.OnCanCloseTab_Lambda([]() { return false; })
[
DetailsView.ToSharedRef()
];
}
TSharedRef<SDockTab> FChooserTableEditor::SpawnFindReplaceTab( const FSpawnTabArgs& Args )
{
check( Args.GetTabId() == FindReplaceTabId );
FPersonaModule& PersonaModule = FModuleManager::LoadModuleChecked<FPersonaModule>("Persona");
FAnimAssetFindReplaceConfig Config;
Config.InitialProcessorClass = UChooserFindProperties::StaticClass();
return SNew(SDockTab)
.Label( LOCTEXT("FindReplaceTitle", "Find/Replace") )
.TabColorScale( GetTabColorScale() )
[
PersonaModule.CreateFindReplaceWidget(Config)
];
}
TSharedRef<ITableRow> FChooserTableEditor::GenerateTableRow(TSharedPtr<FChooserTableRow> InItem, const TSharedRef<STableViewBase>& OwnerTable)
{
UChooserTable* Chooser = GetChooser();
return SNew(SChooserTableRow, OwnerTable)
.Entry(InItem).Chooser(Chooser).Editor(this);
}
void FChooserTableEditor::SelectRootProperties()
{
if( DetailsView.IsValid() )
{
// point the details view to the main table
DetailsView->SetObject( GetRootChooser() );
CurrentSelectionType = ESelectionType::Root;
}
}
void FChooserTableEditor::RemoveDisabledData()
{
UChooserTable* Chooser = GetChooser();
const FScopedTransaction Transaction(LOCTEXT("Remove Disabled Data", "Remove Disabled Data"));
Chooser->Modify(true);
Chooser->RemoveDisabledData();
RefreshAll();
}
int FChooserTableEditor::MoveColumn(int SourceIndex, int TargetIndex)
{
UChooserTable* Chooser = GetChooser();
TargetIndex = FMath::Clamp(TargetIndex, 0, Chooser->ColumnsStructs.Num());
if (SourceIndex < 0 || SourceIndex == TargetIndex)
{
return TargetIndex;
}
const FScopedTransaction Transaction(LOCTEXT("Move Row", "Move Row"));
Chooser->Modify(true);
FInstancedStruct ColumnData = Chooser->ColumnsStructs[SourceIndex];
Chooser->ColumnsStructs.RemoveAt(SourceIndex);
if (SourceIndex < TargetIndex)
{
TargetIndex--;
}
if (TargetIndex == Chooser->ColumnsStructs.Num())
{
if (Chooser->ColumnsStructs.Last().GetPtr<FRandomizeColumn>())
{
// never drop after a Randomize Column;
TargetIndex--;
}
}
Chooser->ColumnsStructs.Insert(ColumnData, TargetIndex);
RefreshAll();
return TargetIndex;
}
int FChooserTableEditor::MoveRow(int SourceRowIndex, int TargetRowIndex)
{
UChooserTable* Chooser = GetChooser();
TargetRowIndex = FMath::Min(TargetRowIndex,Chooser->ResultsStructs.Num());
const FScopedTransaction Transaction(LOCTEXT("Move Row", "Move Row"));
Chooser->Modify(true);
for (FInstancedStruct& ColStruct : Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColStruct.GetMutable<FChooserColumnBase>();
Column.MoveRow(SourceRowIndex, TargetRowIndex);
}
FInstancedStruct Result = Chooser->ResultsStructs[SourceRowIndex];
Chooser->ResultsStructs.RemoveAt(SourceRowIndex);
bool bDisabled = Chooser->DisabledRows[SourceRowIndex];
Chooser->DisabledRows.RemoveAt(SourceRowIndex);
if (SourceRowIndex < TargetRowIndex)
{
TargetRowIndex--;
}
Chooser->ResultsStructs.Insert(Result, TargetRowIndex);
Chooser->DisabledRows.Insert(bDisabled, TargetRowIndex);
UpdateTableRows();
return TargetRowIndex;
}
void FChooserTableEditor::SelectRow(int32 RowIndex, bool bClear)
{
if (TSharedPtr<FChooserTableRow>* Row = TableRows.FindByPredicate([RowIndex](const TSharedPtr<FChooserTableRow>& InRow)
{
return InRow->RowIndex == RowIndex;
}))
{
if (!TableView->IsItemSelected(*Row))
{
if (bClear)
{
TableView->ClearSelection();
}
TableView->SetItemSelection(*Row, true, ESelectInfo::OnMouseClick);
}
}
}
void FChooserTableEditor::ClearSelectedRows()
{
SelectedRows.SetNum(0);
TableView->ClearSelection();
SelectRootProperties();
}
bool FChooserTableEditor::IsRowSelected(int32 RowIndex)
{
for(auto& SelectedRow:SelectedRows)
{
if (SelectedRow->Row == RowIndex)
{
return true;
}
}
return false;
}
bool FChooserTableEditor::IsColumnSelected(int32 ColumnIndex)
{
return (CurrentSelectionType == ESelectionType::Column && SelectedColumn && SelectedColumn->Column == ColumnIndex);
}
void FChooserTableEditor::UpdateTableColumns()
{
UChooserTable* Chooser = GetChooser();
HeaderRow->ClearColumns();
HeaderRow->AddColumn(SHeaderRow::Column("Handles")
.DefaultLabel(FText())
.ManualWidth(30));
if (Chooser->ResultType != EObjectChooserResultType::NoPrimaryResult)
{
HeaderRow->AddColumn(SHeaderRow::Column("Result")
.ManualWidth_Lambda([Chooser]() { return Chooser->EditorResultsColumnWidth; } )
.OnWidthChanged_Lambda([Chooser](float NewWidth) { Chooser->EditorResultsColumnWidth = NewWidth; })
.HeaderContent()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.VAlign(VAlign_Top)
[
SNew(STextBlock)
.Text(LOCTEXT("Result", "Result"))
.ToolTipText(LOCTEXT("ResultTooltip", "The Result is the asset which will be returned if a row is selected (or other Chooser to evaluate to get the asset to return"))
]
]);
}
FName ColumnId("ChooserColumn", 1);
int NumColumns = Chooser->ColumnsStructs.Num();
for(int ColumnIndex = 0; ColumnIndex < NumColumns; ColumnIndex++)
{
FChooserColumnBase& Column = Chooser->ColumnsStructs[ColumnIndex].GetMutable<FChooserColumnBase>();
TSharedPtr<SWidget> HeaderWidget = FObjectChooserWidgetFactories::CreateColumnWidget(&Column, Chooser->ColumnsStructs[ColumnIndex].GetScriptStruct(), Chooser->GetRootChooser(), -1);
if (!HeaderWidget.IsValid())
{
HeaderWidget = SNullWidget::NullWidget;
}
HeaderRow->AddColumn(SHeaderRow::FColumn::FArguments()
.ColumnId(ColumnId)
.ManualWidth(Column.EditorColumnWidth)
.ManualWidth_Lambda([&Column]() { return Column.EditorColumnWidth; } )
.OnWidthChanged_Lambda([&Column](float NewWidth) { Column.EditorColumnWidth = NewWidth; })
.HeaderComboVisibility(EHeaderComboVisibility::Ghosted)
.HeaderContent()
[
SNew(SChooserColumnHandle)
.ChooserEditor(this)
.ColumnIndex(ColumnIndex)
.NoDropAfter(Chooser->ColumnsStructs[ColumnIndex].GetPtr<FRandomizeColumn>() != nullptr)
[
HeaderWidget.ToSharedRef()
]
]);
ColumnId.SetNumber(ColumnId.GetNumber() + 1);
}
HeaderRow->AddColumn( SHeaderRow::FColumn::FArguments()
.ColumnId("Add")
.FillWidth(1.0)
.HeaderContent( )
[
SNew(SVerticalBox)
+ SVerticalBox::Slot().AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().MaxWidth(150)
[
CreateColumnComboButton.ToSharedRef()
]
]
]
);
}
void FChooserTableEditor::AddColumn(const UScriptStruct* ColumnType)
{
FSlateApplication::Get().DismissAllMenus();
UChooserTable* Chooser = GetChooser();
const FScopedTransaction Transaction(LOCTEXT("Add Column Transaction", "Add Column"));
Chooser->Modify(true);
FInstancedStruct NewColumn;
NewColumn.InitializeAs(ColumnType);
const FChooserColumnBase& NewColumnRef = NewColumn.Get<FChooserColumnBase>();
int InsertIndex = 0;
if (NewColumnRef.IsRandomizeColumn())
{
// add randomization column at the end (do nothing if there already is one)
InsertIndex = Chooser->ColumnsStructs.Num();
if (InsertIndex == 0 || !Chooser->ColumnsStructs[InsertIndex - 1].Get<FChooserColumnBase>().IsRandomizeColumn())
{
Chooser->ColumnsStructs.Add(NewColumn);
}
}
else if (NewColumnRef.HasOutputs())
{
// add output columns at the end (but before any randomization column)
InsertIndex = Chooser->ColumnsStructs.Num();
if (InsertIndex > 0 && Chooser->ColumnsStructs[InsertIndex - 1].Get<FChooserColumnBase>().IsRandomizeColumn())
{
InsertIndex--;
}
Chooser->ColumnsStructs.Insert(NewColumn, InsertIndex);
}
else
{
// add other columns after the last non-output, non-randomization column
while(InsertIndex < Chooser->ColumnsStructs.Num())
{
const FChooserColumnBase& Column = Chooser->ColumnsStructs[InsertIndex].Get<FChooserColumnBase>();
if (Column.HasOutputs() || Column.IsRandomizeColumn())
{
break;
}
InsertIndex++;
}
Chooser->ColumnsStructs.Insert(NewColumn, InsertIndex);
}
UpdateTableColumns();
UpdateTableRows();
SelectColumn(Chooser, InsertIndex);
}
void FChooserTableEditor::RefreshRowSelectionDetails()
{
for (UObject* SelectedRow : SelectedRows)
{
SelectedRow->ClearFlags(RF_Standalone);
}
SelectedRows.SetNum(0);
UChooserTable* Chooser = GetChooser();
FPropertyBagPropertyDesc ResultPropertyDesc ("Result", EPropertyBagPropertyType::Struct, FInstancedStruct::StaticStruct());
ResultPropertyDesc.MetaData.Add({"ExcludeBaseStruct",""});
ResultPropertyDesc.MetaData.Add({"BaseStruct","/Script/Chooser.ObjectChooserBase"});
// Get the list of objects to edit the details of
TArray<TSharedPtr<FChooserTableRow>> SelectedItems = TableView->GetSelectedItems();
for(TSharedPtr<FChooserTableRow>& SelectedItem : SelectedItems)
{
if (Chooser->ResultsStructs.IsValidIndex(SelectedItem->RowIndex))
{
TObjectPtr<UChooserRowDetails> Selection = NewObject<UChooserRowDetails>();
Selection->Chooser = Chooser;
Selection->Row = SelectedItem->RowIndex;
Selection->SetFlags(RF_Standalone | RF_Transactional);
if (Chooser->ResultType != EObjectChooserResultType::NoPrimaryResult)
{
FInstancedStruct& Result = Chooser->ResultsStructs[SelectedItem->RowIndex];
Selection->Properties.AddProperties({ResultPropertyDesc});
Selection->Properties.SetValueStruct("Result", FConstStructView(FInstancedStruct::StaticStruct(), reinterpret_cast<uint8*>(&Result)));
}
int ColumnIndex = 0;
for (FInstancedStruct& ColumnData : Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
Column.AddToDetails(Selection->Properties, ColumnIndex, SelectedItem->RowIndex);
ColumnIndex++;
}
if (Chooser->DisabledRows.IsValidIndex(SelectedItem->RowIndex))
{
Selection->Properties.AddProperty("Disabled", EPropertyBagPropertyType::Bool);
Selection->Properties.SetValueBool("Disabled", Chooser->DisabledRows[SelectedItem->RowIndex]);
}
SelectedRows.Add(Selection);
}
else if (SelectedItem->RowIndex == ColumnWidget_SpecialIndex_Fallback)
{
TObjectPtr<UChooserRowDetails> Selection = NewObject<UChooserRowDetails>();
Selection->Chooser = Chooser;
Selection->Row = SelectedItem->RowIndex;
Selection->SetFlags(RF_Standalone | RF_Transactional);
if (Chooser->ResultType != EObjectChooserResultType::NoPrimaryResult)
{
FInstancedStruct& Result = Chooser->FallbackResult;
Selection->Properties.AddProperties({ResultPropertyDesc});
Selection->Properties.SetValueStruct("Result", FConstStructView(FInstancedStruct::StaticStruct(), reinterpret_cast<uint8*>(&Result)));
}
int ColumnIndex = 0;
for (FInstancedStruct& ColumnData : Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
if (Column.HasOutputs())
{
Column.AddToDetails(Selection->Properties, ColumnIndex, SelectedItem->RowIndex);
}
ColumnIndex++;
}
SelectedRows.Add(Selection);
}
}
TArray<UObject*> DetailsObjects;
for(auto& Item : SelectedRows)
{
DetailsObjects.Add(Item.Get());
}
if( DetailsView.IsValid() )
{
// Make sure details window is pointing to our object
DetailsView->SetObjects( DetailsObjects );
}
}
void FChooserTableEditor::MakeChoosersMenuRecursive(UObject* Outer, FMenuBuilder& MenuBuilder, const FString& Indent = "")
{
TArray<UObject*> ChildObjects;
GetObjectsWithOuter(Outer, ChildObjects, false);
FString SubIndent = Indent + " ";
for (UObject* Object : ChildObjects)
{
if (UChooserTable* Chooser = Cast<UChooserTable>(Object))
{
if (Chooser == RootChooser || Chooser->GetRootChooser()->NestedChoosers.Contains(Chooser))
{
MenuBuilder.AddMenuEntry( FText::FromString(Indent + Chooser->GetName()), LOCTEXT("Edit Chooser ToolTip", "Browse to this Nested Chooser Table"), FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this, Chooser]()
{
SetChooserTableToEdit(Chooser);
})));
MakeChoosersMenuRecursive(Chooser, MenuBuilder, SubIndent);
}
}
}
}
TSharedRef<SWidget> FChooserTableEditor::MakeChoosersMenu(UObject* RootObject)
{
FMenuBuilder MenuBuilder(true, nullptr);
MakeChoosersMenuRecursive(RootObject, MenuBuilder);
return MenuBuilder.MakeWidget();
}
TSharedRef<SDockTab> FChooserTableEditor::SpawnNestedTablesTreeTab( const FSpawnTabArgs& Args )
{
check( Args.GetTabId() == NestedTablesTreeTabId );
return SNew(SDockTab)
.Label( LOCTEXT("NestedChooserTreeTitle", "Nested Choosers") )
[
SAssignNew(NestedChooserTree, SNestedChooserTree).ChooserEditor(this)
];
}
DECLARE_DELEGATE_OneParam(FCreateStructDelegate, UScriptStruct*);
struct FColumnTypeInfoStruct
{
int SortOrder = 100;
FString Category;
UScriptStruct* Type;
bool operator < (const FColumnTypeInfoStruct& Other) const
{
if (Category == Other.Category)
{
return Type->GetDisplayNameText().ToString() < Other.Type->GetDisplayNameText().ToString();
}
else if (SortOrder == Other.SortOrder)
{
return Category < Other.Category;
}
else
{
return SortOrder < Other.SortOrder;
}
}
};
TSharedRef<SWidget> FChooserTableEditor::MakeCreateColumnMenu()
{
FMenuBuilder MenuBuilder(true, nullptr);
static TArray<FColumnTypeInfoStruct> ColumnTypes;
if (ColumnTypes.IsEmpty())
{
UScriptStruct* BaseType = FChooserColumnBase::StaticStruct();
for (TObjectIterator<UScriptStruct> StructIt; StructIt; ++StructIt)
{
if (*StructIt != BaseType && StructIt->IsChildOf(BaseType))
{
if (!StructIt->HasMetaData("Hidden"))
{
FColumnTypeInfoStruct Info;
Info.Type = *StructIt;
Info.Category = StructIt->HasMetaData("Category") ? StructIt->GetMetaData("Category") : "Other";
if (Info.Category == "Filter")
{
Info.SortOrder = 1;
}
else if (Info.Category == "Scoring")
{
Info.SortOrder = 2;
}
else if (Info.Category == "Output")
{
Info.SortOrder = 3;
}
else if (Info.Category == "Random")
{
Info.SortOrder = 4;
}
ColumnTypes.Add(Info);
}
}
}
ColumnTypes.Sort();
}
FString Section = "";
for(FColumnTypeInfoStruct& Type : ColumnTypes)
{
if (Section != Type.Category)
{
if (Section != "")
{
MenuBuilder.EndSection();
}
Section = Type.Category;
MenuBuilder.BeginSection(FName(Section), FText::FromString(Section));
}
MenuBuilder.AddMenuEntry(Type.Type->GetDisplayNameText(), Type.Type->GetToolTipText(), FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([this, Type]()
{
AddColumn(Type.Type);
}))
);
}
return MenuBuilder.MakeWidget();
}
struct FResultTypeInfoStruct
{
bool ObjectOnly = false;
bool ClassOnly = false;
FString Category;
UScriptStruct* Type = nullptr;
bool operator < (const FResultTypeInfoStruct& Other) const
{
if (Category == Other.Category)
{
return Type->GetDisplayNameText().ToString() < Other.Type->GetDisplayNameText().ToString();
}
else
{
return Category < Other.Category;
}
}
};
void MakeCreateResultMenu(FMenuBuilder& MenuBuilder, EObjectChooserResultType ChooserResultType, FCreateStructDelegate CreateStruct)
{
static TArray<FResultTypeInfoStruct> ResultTypes;
if (ResultTypes.IsEmpty())
{
UScriptStruct* BaseType = FObjectChooserBase::StaticStruct();
for (TObjectIterator<UScriptStruct> StructIt; StructIt; ++StructIt)
{
if (*StructIt != BaseType && StructIt->IsChildOf(BaseType))
{
if (!StructIt->HasMetaData("Hidden"))
{
FResultTypeInfoStruct Info;
Info.Type = *StructIt;
Info.Category = StructIt->HasMetaData("Category") ? StructIt->GetMetaData("Category") : "Other";
if (StructIt->HasMetaData("ResultType"))
{
FString ResultTypeString = StructIt->GetMetaData("ResultType");
Info.ClassOnly = ResultTypeString == "Class";
Info.ObjectOnly = ResultTypeString == "Object";
}
ResultTypes.Add(Info);
}
}
}
ResultTypes.Sort();
}
FString Section = "";
for(FResultTypeInfoStruct& Type : ResultTypes)
{
if (Section != Type.Category)
{
if (Section != "")
{
MenuBuilder.EndSection();
}
Section = Type.Category;
MenuBuilder.BeginSection(FName(Section), FText::FromString(Section));
}
MenuBuilder.AddMenuEntry(Type.Type->GetDisplayNameText(), Type.Type->GetToolTipText(), FSlateIcon(),
FUIAction(
FExecuteAction::CreateLambda([Type, CreateStruct]()
{
CreateStruct.Execute(Type.Type);
}),
FCanExecuteAction::CreateLambda([Type, ChooserResultType]()
{
if (Type.ClassOnly && ChooserResultType == EObjectChooserResultType::ObjectResult)
{
return false;
}
if (Type.ObjectOnly && ChooserResultType == EObjectChooserResultType::ClassResult)
{
return false;
}
return true;
})
)
);
}
}
TSharedRef<SWidget> FChooserTableEditor::MakeCreateRowMenu()
{
FMenuBuilder MenuBuilder(true, nullptr);
UChooserTable* Chooser = GetChooser();
if (!Chooser->FallbackResult.IsValid())
{
if (Chooser->ResultType == EObjectChooserResultType::NoPrimaryResult)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("Add Fallback Output", "Add Fallback Output"),
LOCTEXT("Add Fallback Output Tooltip", "Add a Fallback row to the chooser, which will be used in the case where no other rows passed all filter columns"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this]()
{
UChooserTable* Chooser = GetChooser();
const FScopedTransaction Transaction(LOCTEXT("Add Fallback Row Transaction", "Add Fallback Row"));
Chooser->Modify(true);
// Just construct a dummy result to make sure all rows always have "valid results"
// You can't just leave a null result otherwise rows don't apply their output.
Chooser->FallbackResult.InitializeAs(FClassChooser::StaticStruct());
Chooser->FallbackResult.GetMutable<FClassChooser>().Class = UClass::StaticClass();
UpdateTableRows();
}))
);
}
else
{
MenuBuilder.AddSubMenu(
LOCTEXT("Add Fallback", "Add Fallback Result"),
LOCTEXT("Add Fallback Tooltip", "Add a Fallback row to the chooser, which will be used in the case where no other rows passed all filter columns"),
FNewMenuDelegate::CreateLambda([this](FMenuBuilder& MenuBuilder)
{
UChooserTable* Chooser = GetChooser();
MakeCreateResultMenu(MenuBuilder, Chooser->ResultType, FCreateStructDelegate::CreateLambda([this](UScriptStruct* Type)
{
UChooserTable* Chooser = GetChooser();
const FScopedTransaction Transaction(LOCTEXT("Add Fallback Row Transaction", "Add Fallback Row"));
Chooser->Modify(true);
Chooser->FallbackResult.InitializeAs(Type);
UpdateTableRows();
}));
})
);
}
}
if (Chooser->ResultType == EObjectChooserResultType::NoPrimaryResult)
{
MenuBuilder.AddMenuEntry(
LOCTEXT("Add Output Row", "Add Output Row"),
LOCTEXT("Add Output Row Tooltip", "Add a regular row to the chooser"),
FSlateIcon(),
FUIAction(FExecuteAction::CreateLambda([this]()
{
UChooserTable* Chooser = GetChooser();
const FScopedTransaction Transaction(LOCTEXT("Add Row Transaction", "Add Row"));
Chooser->Modify(true);
// Just construct a dummy result to make sure all rows always have "valid results"
// You can't just leave a null result otherwise rows don't apply their output.
FInstancedStruct& NewResult = Chooser->ResultsStructs.AddDefaulted_GetRef();
NewResult.InitializeAs(FClassChooser::StaticStruct());
NewResult.GetMutable<FClassChooser>().Class = UClass::StaticClass();
UpdateTableRows();
}))
);
}
else
{
MakeCreateResultMenu(MenuBuilder, Chooser->ResultType, FCreateStructDelegate::CreateLambda([this](UScriptStruct* Type)
{
UChooserTable* Chooser = GetChooser();
const FScopedTransaction Transaction(LOCTEXT("Add Row Transaction", "Add Row"));
Chooser->Modify(true);
FInstancedStruct& NewResult = Chooser->ResultsStructs.AddDefaulted_GetRef();
NewResult.InitializeAs(Type);
UpdateTableRows();
}));
}
return MenuBuilder.MakeWidget();
}
TSharedRef<SDockTab> FChooserTableEditor::SpawnTableTab( const FSpawnTabArgs& Args )
{
check( Args.GetTabId() == TableTabId );
UChooserTable* Chooser = GetChooser();
// + button to create new columns
CreateColumnComboButton = SNew(SPositiveActionButton)
.Text(LOCTEXT("Add Column", "Add Column"))
.OnGetMenuContent_Lambda([this]()
{
return MakeCreateColumnMenu();
});
CreateRowComboButton = SNew(SPositiveActionButton)
.Text(LOCTEXT("Add Row", "Add Row"))
.OnGetMenuContent(this, &FChooserTableEditor::MakeCreateRowMenu);
HeaderRow = SNew(SHeaderRow);
UpdateTableRows();
UpdateTableColumns();
TableView = SNew(SListView<TSharedPtr<FChooserTableRow>>)
.OnKeyDownHandler_Lambda([this](const FGeometry&, const FKeyEvent& Event)
{
if (GetToolkitCommands()->ProcessCommandBindings(Event))
{
return FReply::Handled();
}
return FReply::Unhandled();
})
.ListItemsSource(&TableRows)
.OnContextMenuOpening_Raw(this, &FChooserTableEditor::GenerateRowContextMenu)
.OnSelectionChanged_Lambda([this](TSharedPtr<FChooserTableRow>, ESelectInfo::Type SelectInfo)
{
// deselect any selected column
ClearSelectedColumn();
CurrentSelectionType = ESelectionType::Rows;
RefreshRowSelectionDetails();
})
.OnGenerateRow_Raw(this, &FChooserTableEditor::GenerateTableRow)
.HeaderRow(HeaderRow);
TSharedRef<SComboButton> EditChooserTableButton = SNew(SComboButton)
.ButtonStyle(FAppStyle::Get(), "GraphBreadcrumbButton");
EditChooserTableButton->SetOnGetMenuContent(
FOnGetContent::CreateLambda(
[this]()
{
return MakeChoosersMenu(GetRootChooser()->GetPackage());
})
);
return SNew(SDockTab)
.Label( LOCTEXT("ChooserTableTitle", "Chooser Table") )
.TabColorScale( GetTabColorScale() )
.OnCanCloseTab_Lambda([]() { return false; })
[
SNew(SVerticalBox)
+ SVerticalBox::Slot().AutoHeight().Padding(3)
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot().AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "GraphBreadcrumbButton")
.IsEnabled_Raw(this, &FChooserTableEditor::CanNavigateBack)
.OnClicked_Lambda([this]()
{
NavigateBack();
return FReply::Handled();
})
.Content()
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.ArrowLeft"))
]
]
+ SHorizontalBox::Slot().AutoWidth()
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "GraphBreadcrumbButton")
.IsEnabled_Raw(this, &FChooserTableEditor::CanNavigateForward)
.OnClicked_Lambda([this]()
{
NavigateForward();
return FReply::Handled();
})
.Content()
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.ArrowRight") )
]
]
+ SHorizontalBox::Slot().AutoWidth()
[
EditChooserTableButton
]
+ SHorizontalBox::Slot().FillWidth(1)
[
BreadcrumbTrail.ToSharedRef()
]
]
+ SVerticalBox::Slot().FillHeight(1)
[
SNew(SScrollBox).Orientation(Orient_Horizontal)
+ SScrollBox::Slot()
[
TableView.ToSharedRef()
]
]
];
}
void FChooserTableEditor::UpdateTableRows()
{
UChooserTable* Chooser = GetChooser();
int32 NewNum = Chooser->ResultsStructs.Num();
Chooser->DisabledRows.SetNum(NewNum);
// Sync the TableRows array which drives the ui table to match the number of results.
TableRows.SetNum(0, EAllowShrinking::No);
for(int i =0; i < NewNum; i++)
{
TableRows.Add(MakeShared<FChooserTableRow>(i));
}
// Add one at the end, for the Fallback result
if (Chooser->FallbackResult.IsValid())
{
TableRows.Add(MakeShared<FChooserTableRow>(SChooserTableRow::SpecialIndex_Fallback));
}
// Add one at the end, for the "Add Row" control
TableRows.Add(MakeShared<FChooserTableRow>(SChooserTableRow::SpecialIndex_AddRow));
// Make sure each column has the same number of row datas as there are results
for(FInstancedStruct& ColumnData : Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
Column.SetNumRows(NewNum);
}
if (TableView.IsValid())
{
TableView->RebuildList();
}
}
void FChooserTableEditor::OnObjectsReplaced(const TMap<UObject*, UObject*>& ReplacementMap)
{
bool bChangedAny = false;
UObject* ReplacedObject = ReplacementMap.FindRef(RootChooser);
if (ReplacedObject && ReplacedObject != RootChooser)
{
RootChooser = Cast<UChooserTable>(ReplacedObject);
SetChooserTableToEdit(RootChooser);
SelectRootProperties();
}
}
FString FChooserTableEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("WorldCentricTabPrefix", "Chooser Table Asset ").ToString();
}
TSharedRef<FChooserTableEditor> FChooserTableEditor::CreateEditor( const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, UObject* ObjectToEdit, FGetDetailsViewObjects GetDetailsViewObjects )
{
TSharedRef< FChooserTableEditor > NewEditor( new FChooserTableEditor() );
TArray<UObject*> ObjectsToEdit;
ObjectsToEdit.Add( ObjectToEdit );
NewEditor->InitEditor( Mode, InitToolkitHost, ObjectsToEdit, GetDetailsViewObjects );
return NewEditor;
}
TSharedRef<FChooserTableEditor> FChooserTableEditor::CreateEditor( const EToolkitMode::Type Mode, const TSharedPtr< IToolkitHost >& InitToolkitHost, const TArray<UObject*>& ObjectsToEdit, FGetDetailsViewObjects GetDetailsViewObjects )
{
TSharedRef< FChooserTableEditor > NewEditor( new FChooserTableEditor() );
NewEditor->InitEditor( Mode, InitToolkitHost, ObjectsToEdit, GetDetailsViewObjects );
return NewEditor;
}
void FChooserTableEditor::SelectColumn(UChooserTable* ChooserEditor, int Index)
{
ClearSelectedRows();
UChooserTable* Chooser = GetChooser();
if (Index < Chooser->ColumnsStructs.Num())
{
if (SelectedColumn == nullptr)
{
SelectedColumn = NewObject<UChooserColumnDetails>();
SelectedColumn->AddToRoot();
}
SelectedColumn->Chooser = Chooser;
SelectedColumn->Column = Index;
DetailsView->SetObject(SelectedColumn, true);
CurrentSelectionType = ESelectionType::Column;
}
else
{
SelectRootProperties();
}
}
void FChooserTableEditor::ClearSelectedColumn()
{
UChooserTable* Chooser = GetChooser();
if (SelectedColumn != nullptr)
{
SelectedColumn->Column = -1;
if (DetailsView->GetSelectedObjects().Contains(SelectedColumn))
{
SelectRootProperties();
}
}
}
void FChooserTableEditor::DeleteColumn(int Index)
{
const FScopedTransaction Transaction(LOCTEXT("Delete Column Transaction", "Delete Column"));
ClearSelectedColumn();
SelectRootProperties();
UChooserTable* Chooser = GetChooser();
if (Index < Chooser->ColumnsStructs.Num())
{
Chooser->Modify(true);
Chooser->ColumnsStructs.RemoveAt(Index);
UpdateTableColumns();
}
}
int32 FChooserTableEditor::DeleteSelectedRows(int32 RowIndexToRemember)
{
const FScopedTransaction Transaction(LOCTEXT("Delete Row Transaction", "Delete Row"));
return DeleteSelectedRowsInternal(RowIndexToRemember);
}
int32 FChooserTableEditor::DeleteSelectedRowsInternal(int32 RowIndexToRemember)
{
UChooserTable* Chooser = GetChooser();
Chooser->Modify(true);
// delete selected rows.
TArray<uint32> RowsToDelete;
for(auto& SelectedRow:SelectedRows)
{
if (SelectedRow->Row == ColumnWidget_SpecialIndex_Fallback)
{
Chooser->FallbackResult.Reset();
}
else
{
RowsToDelete.Add(SelectedRow->Row);
}
}
SelectedRows.SetNum(0);
SelectRootProperties();
// sort indices in reverse
RowsToDelete.Sort([](int32 A, int32 B){ return A>B; });
for(int32 RowIndex : RowsToDelete)
{
if (RowIndex <= RowIndexToRemember)
{
RowIndexToRemember--;
}
Chooser->ResultsStructs.RemoveAt(RowIndex);
Chooser->DisabledRows.RemoveAt(RowIndex);
}
for(FInstancedStruct& ColumnData : Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
Column.DeleteRows(RowsToDelete);
}
UpdateTableRows();
return RowIndexToRemember;
}
void FChooserTableEditor::MoveRows(int TargetIndex)
{
const FScopedTransaction Transaction(LOCTEXT("Move Row(s)", "Move Row(s)"));
UChooserTable* RowCopy = CopySelectionInternal();
TargetIndex = DeleteSelectedRowsInternal(TargetIndex);
PasteInternal(RowCopy, TargetIndex);
}
void FChooserTableEditor::AutoPopulateColumn(FChooserColumnBase& Column)
{
if (UChooserTable* Chooser = GetChooser())
{
const int RowCount = Chooser->ResultsStructs.Num();
if (Column.AutoPopulates())
{
for (int i = 0; i < RowCount; ++i)
{
if (Chooser->ResultsStructs[i].IsValid())
{
if (UObject* ReferencedObject = Chooser->ResultsStructs[i].Get<FObjectChooserBase>().GetReferencedObject())
{
Column.AutoPopulate(i, ReferencedObject);
}
}
}
}
}
}
void FChooserTableEditor::AutoPopulateRow(int Index)
{
if (UChooserTable* Chooser = GetChooser())
{
if (Chooser->ResultsStructs.IsValidIndex(Index) && Chooser->ResultsStructs[Index].IsValid())
{
if (UObject* ReferencedObject = Chooser->ResultsStructs[Index].Get<FObjectChooserBase>().GetReferencedObject())
{
for (FInstancedStruct& ColumnData : Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
Column.AutoPopulate(Index, ReferencedObject);
}
}
}
}
}
bool FChooserTableEditor::CanAutoPopulateSelection()
{
if (UChooserTable* Chooser = GetChooser())
{
if (CurrentSelectionType == ESelectionType::Column && SelectedColumn)
{
// when a column is selected, return true if that column supports auto populate
if (Chooser->ColumnsStructs.IsValidIndex(SelectedColumn->Column))
{
return Chooser->ColumnsStructs[SelectedColumn->Column].Get<FChooserColumnBase>().AutoPopulates();
}
}
else
{
if (SelectedRows.IsEmpty())
{
return false;
}
// when rows are selected, return true if any column supports auto populate
for (FInstancedStruct& ColumnData : Chooser->ColumnsStructs)
{
const FChooserColumnBase& Column = ColumnData.Get<FChooserColumnBase>();
if (Column.AutoPopulates())
{
return true;
}
}
}
}
return false;
}
void FChooserTableEditor::AutoPopulateSelection()
{
if (UChooserTable* Chooser = GetChooser())
{
const FScopedTransaction Transaction(LOCTEXT("Auto Populate Selection", "Auto Populate Selection"));
Chooser->Modify();
if (HasColumnSelected())
{
if (Chooser->ColumnsStructs.IsValidIndex(SelectedColumn->Column))
{
AutoPopulateColumn(Chooser->ColumnsStructs[SelectedColumn->Column].GetMutable<FChooserColumnBase>());
}
}
else if (HasRowsSelected())
{
for(UChooserRowDetails* RowDetails : SelectedRows)
{
AutoPopulateRow(RowDetails->Row);
}
}
}
}
void FChooserTableEditor::AutoPopulateAll()
{
if (UChooserTable* Chooser = GetChooser())
{
const FScopedTransaction Transaction(LOCTEXT("Auto Populate Chooser", "Auto Populate All"));
Chooser->Modify();
for (FInstancedStruct& ColumnData : Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
AutoPopulateColumn(Column);
}
}
}
bool FChooserTableEditor::HasSelection() const
{
return HasRowsSelected() || HasColumnSelected();
}
bool FChooserTableEditor::HasRowsSelected() const
{
return CurrentSelectionType == ESelectionType::Rows && !SelectedRows.IsEmpty();
}
bool FChooserTableEditor::HasColumnSelected() const
{
const UChooserTable* Chooser = GetChooser();
return CurrentSelectionType == ESelectionType::Column && SelectedColumn && Chooser->ColumnsStructs.IsValidIndex(SelectedColumn->Column);
}
bool FChooserTableEditor::IsSelectionDisabled() const
{
if (const UChooserTable* Chooser = GetChooser())
{
if (HasColumnSelected())
{
if ( Chooser->ColumnsStructs.IsValidIndex(SelectedColumn->Column))
{
const FChooserColumnBase& Column = Chooser->ColumnsStructs[SelectedColumn->Column].Get<FChooserColumnBase>();
return Column.bDisabled;
}
}
else if (HasRowsSelected())
{
bool bSomethingEnabled = false;
for(auto& Row : SelectedRows)
{
if (!Chooser->IsRowDisabled(Row->Row))
{
bSomethingEnabled = true;
break;
}
}
return !bSomethingEnabled;
}
}
return false;
}
void FChooserTableEditor::ToggleDisableSelection()
{
bool bDisabled = IsSelectionDisabled();
if (UChooserTable* Chooser = GetChooser())
{
if (HasColumnSelected())
{
if (Chooser->ColumnsStructs.IsValidIndex(SelectedColumn->Column))
{
FChooserColumnBase& Column = Chooser->ColumnsStructs[SelectedColumn->Column].GetMutable<FChooserColumnBase>();
Column.bDisabled = !Column.bDisabled;
}
}
else if (HasRowsSelected())
{
for (auto& Row : SelectedRows)
{
if (Chooser->DisabledRows.IsValidIndex(Row->Row))
{
Chooser->DisabledRows[Row->Row] = !bDisabled;
}
}
RefreshRowSelectionDetails();
}
}
}
void FChooserTableEditor::DeleteSelection()
{
if (HasColumnSelected())
{
DeleteColumn(SelectedColumn->Column);
}
else if (HasRowsSelected())
{
DeleteSelectedRows();
}
}
void FChooserTableEditor::DuplicateSelection()
{
if (HasRowsSelected())
{
const FScopedTransaction Transaction(LOCTEXT("Duplicate Row(s)", "Duplicate Row(s)"));
UChooserTable* RowCopy = CopySelectionInternal();
int MaxSelectedRow = -1;
for(UChooserRowDetails* SelectedRow : SelectedRows)
{
MaxSelectedRow = FMath::Max(SelectedRow->Row, MaxSelectedRow);
}
PasteInternal(RowCopy, MaxSelectedRow + 1);
}
else if(HasColumnSelected())
{
const FScopedTransaction Transaction(LOCTEXT("Duplicate Column", "Duplicate Column"));
UChooserTable* Chooser = GetChooser();
Chooser->Modify();
FInstancedStruct Column = Chooser->ColumnsStructs[SelectedColumn->Column];
Chooser->ColumnsStructs.Insert(Column,SelectedColumn->Column);
RefreshAll();
}
}
bool FChooserTableEditor::HasFallbackSelected()
{
for(UChooserRowDetails* SelectedRow : SelectedRows)
{
if (SelectedRow->Row == ColumnWidget_SpecialIndex_Fallback)
{
return true;
}
}
return false;
}
bool FChooserTableEditor::CanMoveRowsUp()
{
if (HasRowsSelected())
{
UChooserTable* Chooser = GetChooser();
int MinSelectedRow = Chooser->ResultsStructs.Num();
for(UChooserRowDetails* SelectedRow : SelectedRows)
{
if (SelectedRow->Row != ColumnWidget_SpecialIndex_Fallback)
{
MinSelectedRow = FMath::Min(SelectedRow->Row, MinSelectedRow);
}
}
return MinSelectedRow > 0;
}
return false;
}
void FChooserTableEditor::MoveRowsUp()
{
if (HasRowsSelected())
{
UChooserTable* Chooser = GetChooser();
int MinSelectedRow = Chooser->ResultsStructs.Num();
for(UChooserRowDetails* SelectedRow : SelectedRows)
{
if (SelectedRow->Row != ColumnWidget_SpecialIndex_Fallback)
{
MinSelectedRow = FMath::Min(SelectedRow->Row, MinSelectedRow);
}
}
MoveRows(MinSelectedRow - 1);
}
}
bool FChooserTableEditor::CanMoveRowsDown()
{
if (HasRowsSelected())
{
UChooserTable* Chooser = GetChooser();
int MaxSelectedRow = -1;
for(UChooserRowDetails* SelectedRow : SelectedRows)
{
MaxSelectedRow = FMath::Max(SelectedRow->Row, MaxSelectedRow);
}
return MaxSelectedRow < Chooser->ResultsStructs.Num() - 1;
}
return false;
}
void FChooserTableEditor::MoveRowsDown()
{
if (HasRowsSelected())
{
UChooserTable* Chooser = GetChooser();
int MaxSelectedRow = -1;
for(UChooserRowDetails* SelectedRow : SelectedRows)
{
MaxSelectedRow = FMath::Max(SelectedRow->Row, MaxSelectedRow);
}
MoveRows(MaxSelectedRow + 2);
}
}
bool FChooserTableEditor::CanMoveColumnLeft()
{
if (HasColumnSelected())
{
UChooserTable* Chooser = GetChooser();
if (Chooser->ColumnsStructs[SelectedColumn->Column].GetPtr<FRandomizeColumn>())
{
return false;
}
return SelectedColumn->Column > 0;
}
return false;
}
void FChooserTableEditor::MoveColumnLeft()
{
if (CanMoveColumnLeft())
{
SelectColumn(GetChooser(), MoveColumn(SelectedColumn->Column, SelectedColumn->Column - 1));
}
}
bool FChooserTableEditor::CanMoveColumnRight()
{
if (HasColumnSelected())
{
UChooserTable* Chooser = GetChooser();
if (Chooser->ColumnsStructs[SelectedColumn->Column].GetPtr<FRandomizeColumn>())
{
return false;
}
int NumColumns = Chooser->ColumnsStructs.Num();
if (NumColumns > 0 && Chooser->ColumnsStructs.Last().GetPtr<FRandomizeColumn>())
{
NumColumns--;
}
return (SelectedColumn->Column < NumColumns-1);
}
return false;
}
void FChooserTableEditor::MoveColumnRight()
{
if (CanMoveColumnRight())
{
SelectColumn(GetChooser(), MoveColumn(SelectedColumn->Column, SelectedColumn->Column + 2));
}
}
UChooserTable* DuplicateNestedChooser(UChooserTable* Chooser, UChooserTable* NewOuter)
{
UChooserTable* RootTable = NewOuter->GetRootChooser();
if (TObjectPtr<UChooserTable>* FoundTable = RootTable->NestedChoosers.FindByPredicate([Chooser](UChooserTable* Table)
{
return Table->GetName() == Chooser->GetName();
}))
{
// we already duplicated this table
return *FoundTable;
}
UChooserTable* NewTable = NewObject<UChooserTable>(NewOuter, Chooser->GetFName());
NewTable->ColumnsStructs = Chooser->ColumnsStructs;
NewTable->ResultsStructs = Chooser->ResultsStructs;
NewTable->RootChooser = RootTable;
RootTable->AddNestedChooser(NewTable);
for (FInstancedStruct& ResultData : NewTable->ResultsStructs)
{
if (FNestedChooser* NestedChooser = ResultData.GetMutablePtr<FNestedChooser>())
{
NestedChooser->Chooser = DuplicateNestedChooser(NestedChooser->Chooser, NewOuter);
}
}
return NewTable;
}
UChooserTable* FChooserTableEditor::CopySelectionInternal()
{
UChooserTable* CopyData = NewObject<UChooserTable>(GetTransientPackage());
UChooserTable* Chooser = GetChooser();
// copy context data from root table
CopyData->OutputObjectType = RootChooser->OutputObjectType;
CopyData->ResultType = RootChooser->ResultType;
CopyData->ContextData = RootChooser->ContextData;
if (CurrentSelectionType == ESelectionType::Column)
{
// add selected column including all the cell data
CopyData->ColumnsStructs.Add(Chooser->ColumnsStructs[SelectedColumn->Column]);
}
else if (CurrentSelectionType == ESelectionType::Rows)
{
TArray<UChooserRowDetails*> SelectedRowsCopy = SelectedRows;
Algo::Sort(SelectedRowsCopy, [](UChooserRowDetails *A, UChooserRowDetails *B) { return A->Row<B->Row;});
// copy all columns
CopyData->ColumnsStructs = Chooser->ColumnsStructs;
// clear all column's cell data
for (FInstancedStruct& ColumnData : CopyData->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
Column.SetNumRows(0);
Column.SetNumRows(SelectedRowsCopy.Num());
}
if (SelectedRowsCopy.Num() > 0 && SelectedRowsCopy[0]->Row == ColumnWidget_SpecialIndex_Fallback)
{
SelectedRowsCopy.RemoveAt(0);
CopyData->FallbackResult = Chooser->FallbackResult;
if (FNestedChooser* CopiedNestedChooser = CopyData->FallbackResult.GetMutablePtr<FNestedChooser>())
{
// if the fallback result was a nested chooser, duplicate it
CopiedNestedChooser->Chooser = DuplicateNestedChooser(CopiedNestedChooser->Chooser, CopyData);
}
}
CopyData->ResultsStructs.SetNum(SelectedRowsCopy.Num());
CopyData->DisabledRows.SetNum(SelectedRowsCopy.Num());
// add the selected results and column data
for (int RowIndex = 0; RowIndex < SelectedRowsCopy.Num(); RowIndex++)
{
CopyData->ResultsStructs[RowIndex] = Chooser->ResultsStructs[SelectedRowsCopy[RowIndex]->Row];
if (FNestedChooser* CopiedNestedChooser = CopyData->ResultsStructs[RowIndex].GetMutablePtr<FNestedChooser>())
{
if (CopiedNestedChooser->Chooser)
{
// if the result for this row was a nested chooser (with a valid chooser assigned), duplicate it
CopiedNestedChooser->Chooser = DuplicateNestedChooser(CopiedNestedChooser->Chooser, CopyData);
}
}
CopyData->DisabledRows[RowIndex] = Chooser->DisabledRows[SelectedRowsCopy[RowIndex]->Row];
for (int ColumnIndex = 0; ColumnIndex < CopyData->ColumnsStructs.Num(); ColumnIndex++)
{
FChooserColumnBase& SourceColumn = Chooser->ColumnsStructs[ColumnIndex].GetMutable<FChooserColumnBase>();
FChooserColumnBase& TargetColumn = CopyData->ColumnsStructs[ColumnIndex].GetMutable<FChooserColumnBase>();
TargetColumn.CopyRow(SourceColumn, SelectedRowsCopy[RowIndex]->Row, RowIndex);
}
}
}
return CopyData;
}
void FChooserTableEditor::CopySelection()
{
UChooserTable* CopyData = CopySelectionInternal();
// Clear the mark state for saving.
UnMarkAllObjects(EObjectMark(OBJECTMARK_TagExp | OBJECTMARK_TagImp));
// Export the clipboard to text.
FStringOutputDevice Archive;
const FExportObjectInnerContext Context;
UExporter::ExportToOutputDevice(&Context,CopyData, nullptr, Archive, TEXT("copy"), 0, PPF_ExportsNotFullyQualified | PPF_Copy | PPF_Delimited, false, CopyData->GetOuter());
FPlatformApplicationMisc::ClipboardCopy(*Archive);
}
void FChooserTableEditor::CutSelection()
{
CopySelection();
DeleteSelection();
}
class FChooserClipboardFactory : public FCustomizableTextObjectFactory
{
public:
FChooserClipboardFactory()
: FCustomizableTextObjectFactory(GWarn)
, ClipboardContent(nullptr)
{
}
UChooserTable* ClipboardContent;
protected:
virtual bool CanCreateClass(UClass* InObjectClass, bool& bOmitSubObjs) const override
{
if (InObjectClass->IsChildOf(UChooserTable::StaticClass()))
{
return true;
}
return false;
}
virtual void ProcessConstructedObject(UObject* CreatedObject) override
{
if (CreatedObject->IsA<UChooserTable>())
{
ClipboardContent = CastChecked<UChooserTable>(CreatedObject);
}
}
};
static FString GetColumnName(FChooserColumnBase& Column)
{
if (FChooserParameterBase* InputValue = Column.GetInputValue())
{
return InputValue->GetDebugName();
}
return FString();
}
void FChooserTableEditor::PasteInternal(UChooserTable* PastedContent, int PasteRowIndex)
{
UChooserTable* Chooser = GetChooser();
Chooser->Modify();
if (PastedContent->ResultsStructs.IsEmpty() && !PastedContent->FallbackResult.IsValid())
{
// pasting a column
int InsertColumnIndex = Chooser->ColumnsStructs.Num();
if (CurrentSelectionType == ESelectionType::Column && SelectedColumn)
{
InsertColumnIndex = FMath::Min(InsertColumnIndex, SelectedColumn->Column + 1);
}
if (Chooser->ColumnsStructs.Num() > 0 && Chooser->ColumnsStructs.Num() == InsertColumnIndex)
{
// if were inserting at the end, there is a randomize column, insert new columns before it
if (Chooser->ColumnsStructs.Last().GetPtr<FRandomizeColumn>())
{
InsertColumnIndex--;
}
}
Chooser->ColumnsStructs.Insert(PastedContent->ColumnsStructs, InsertColumnIndex);
SelectColumn(Chooser, InsertColumnIndex);
}
else
{
// pasting rows
int RowsToPaste = PastedContent->ResultsStructs.Num();
// figure out where to start inserting
int InsertIndex = Chooser->ResultsStructs.Num();
if (PasteRowIndex >=0)
{
InsertIndex = PasteRowIndex;
}
else
{
if (SelectedRows.Num() > 0)
{
InsertIndex = SelectedRows[0]->Row;
for(int SelectedRowIndex = 1; SelectedRowIndex < SelectedRows.Num(); SelectedRowIndex ++)
{
InsertIndex = FMath::Max(InsertIndex, SelectedRows[SelectedRowIndex]->Row);
}
if (InsertIndex == ColumnWidget_SpecialIndex_Fallback)
{
// if the only row selected was the fallback, reset insert index to the last row
InsertIndex = Chooser->ResultsStructs.Num();
}
else
{
InsertIndex++;
}
}
}
if (PastedContent->ResultsStructs.Num() > 0)
{
Chooser->ResultsStructs.Insert(PastedContent->ResultsStructs, InsertIndex);
Chooser->DisabledRows.Insert(PastedContent->DisabledRows, InsertIndex);
// Make sure each column has the same number of row datas as there are results
for(FInstancedStruct& ColumnData : Chooser->ColumnsStructs)
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
Column.InsertRows(InsertIndex, RowsToPaste);
}
}
if (PastedContent->FallbackResult.IsValid())
{
// paste fallback result if copy data has one
Chooser->FallbackResult = PastedContent->FallbackResult;
if (FNestedChooser* NestedChooser = Chooser->FallbackResult.GetMutablePtr<FNestedChooser>())
{
// duplicate the nested chooser if the fallback result refers to a nested chooser
NestedChooser->Chooser = DuplicateNestedChooser(NestedChooser->Chooser, Chooser);
}
}
if (!PastedContent->NestedChoosers.IsEmpty())
{
// if there were nested choosers in the copy buffer we have to remap or paste them here
for (int ResultIndex = InsertIndex; ResultIndex < PastedContent->ResultsStructs.Num() + InsertIndex; ResultIndex++)
{
if (FNestedChooser* NestedChooser = Chooser->ResultsStructs[ResultIndex].GetMutablePtr<FNestedChooser>())
{
NestedChooser->Chooser = DuplicateNestedChooser(NestedChooser->Chooser, Chooser);
}
}
}
// try to also paste column data from columns in the paste buffer which match the columns in the current chooser
// -- matching by column type and input value name
// keep track of target columns that have already been matched, to avoid matching multiple source columns with the same target column
TArray<bool> MatchedTargetColumns;
MatchedTargetColumns.SetNum(Chooser->ColumnsStructs.Num());
// keep track of which source columns were matched, so we can add new columns for the unmatched ones after
TArray<bool> MatchedSourceColumns;
MatchedSourceColumns.SetNum(PastedContent->ColumnsStructs.Num());
for(int SourceColumnIndex = 0; SourceColumnIndex < PastedContent->ColumnsStructs.Num(); SourceColumnIndex++)
{
FInstancedStruct& PastedColumnData = PastedContent->ColumnsStructs[SourceColumnIndex];
FChooserColumnBase& PastedColumn = PastedColumnData.GetMutable<FChooserColumnBase>();
FString PastedColumnName = GetColumnName(PastedColumn);
if (FChooserParameterBase* PastedColumnInput = PastedColumn.GetInputValue())
{
for(int TargetColumnIndex = 0; TargetColumnIndex < Chooser->ColumnsStructs.Num(); TargetColumnIndex++)
{
if (!MatchedTargetColumns[TargetColumnIndex])
{
FInstancedStruct& ColumnData = Chooser->ColumnsStructs[TargetColumnIndex];
if (ColumnData.GetScriptStruct() == PastedColumnData.GetScriptStruct())
{
FChooserColumnBase& Column = ColumnData.GetMutable<FChooserColumnBase>();
FString ColumnName = GetColumnName(Column);
if (ColumnName == PastedColumnName)
{
MatchedTargetColumns[TargetColumnIndex] = true;
MatchedSourceColumns[SourceColumnIndex] = true;
// found a match, copy the data over
for (int i = 0; i < RowsToPaste; i++)
{
Column.CopyRow(PastedColumn, i, InsertIndex + i);
}
if (PastedContent->FallbackResult.IsValid())
{
// if the fallback row was copied, paste the fallback data for columns
Column.CopyFallback(PastedColumn);
}
break;
}
}
}
}
}
}
// add new columns for any source columns that were unmatched
int InsertColumnIndex = Chooser->ColumnsStructs.Num();
if (Chooser->ColumnsStructs.Num() > 0)
{
// if there is a randomize column, insert new columns before it
if (Chooser->ColumnsStructs.Last().GetPtr<FRandomizeColumn>())
{
InsertColumnIndex--;
}
}
for(int SourceColumnIndex = 0; SourceColumnIndex < PastedContent->ColumnsStructs.Num(); SourceColumnIndex++)
{
if (!MatchedSourceColumns[SourceColumnIndex])
{
FInstancedStruct& PastedColumnData = PastedContent->ColumnsStructs[SourceColumnIndex];
FChooserColumnBase& PastedColumn = PastedColumnData.GetMutable<FChooserColumnBase>();
// if we couldn't find a match, paste a new column
Chooser->ColumnsStructs.Insert(PastedColumnData, InsertColumnIndex);
FChooserColumnBase& Column = Chooser->ColumnsStructs[InsertColumnIndex].GetMutable<FChooserColumnBase>();
InsertColumnIndex++;
Column.SetNumRows(0);
Column.SetNumRows(Chooser->ResultsStructs.Num());
for (int i = 0; i < RowsToPaste; i++)
{
Column.CopyRow(PastedColumn, i, InsertIndex + i);
}
}
}
RefreshAll();
// select the inserted rows
ClearSelectedRows();
for (int i = 0; i < RowsToPaste; i++)
{
SelectRow(InsertIndex + i, false);
}
if(PastedContent->FallbackResult.IsValid())
{
SelectRow(ColumnWidget_SpecialIndex_Fallback, false);
}
}
RefreshAll();
}
void FChooserTableEditor::Paste()
{
// Get the text from the clipboard.
FString ClipboardText;
FPlatformApplicationMisc::ClipboardPaste(ClipboardText);
FChooserClipboardFactory Factory;
UChooserTable* Chooser = GetChooser();
if (Factory.CanCreateObjectsFromText(ClipboardText))
{
Factory.ProcessBuffer((UObject*)GetTransientPackage(), RF_Transactional, ClipboardText);
if (UChooserTable* PastedContent = Factory.ClipboardContent)
{
FScopedTransaction Transaction(LOCTEXT("Paste Chooser Data", "Paste Chooser Data"));
PasteInternal(PastedContent);
}
}
}
bool FChooserTableEditor::CanPaste() const
{
// Get the text from the clipboard.
FString ClipboardText;
FPlatformApplicationMisc::ClipboardPaste(ClipboardText);
FChooserClipboardFactory Factory;
return Factory.CanCreateObjectsFromText(ClipboardText);
}
void FChooserTableEditor::RegisterWidgets()
{
FObjectChooserWidgetFactories::RegisterWidgetCreator(FAssetChooser::StaticStruct(), CreateAssetWidget);
FObjectChooserWidgetFactories::RegisterWidgetCreator(FSoftAssetChooser::StaticStruct(), CreateSoftAssetWidget);
FObjectChooserWidgetFactories::RegisterWidgetCreator(FClassChooser::StaticStruct(), CreateClassWidget);
FObjectChooserWidgetFactories::RegisterWidgetCreator(FEvaluateChooser::StaticStruct(), CreateEvaluateChooserWidget);
FObjectChooserWidgetFactories::RegisterWidgetCreator(FNestedChooser::StaticStruct(), CreateNestedChooserWidget);
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
PropertyModule.RegisterCustomClassLayout("ChooserTable", FOnGetDetailCustomizationInstance::CreateStatic(&FChooserDetails::MakeInstance));
PropertyModule.RegisterCustomClassLayout("ChooserRowDetails", FOnGetDetailCustomizationInstance::CreateStatic(&FChooserRowDetails::MakeInstance));
PropertyModule.RegisterCustomClassLayout("ChooserColumnDetails", FOnGetDetailCustomizationInstance::CreateStatic(&FChooserColumnDetails::MakeInstance));
}
}
#undef LOCTEXT_NAMESPACE