Files
UnrealEngineUWP/Engine/Source/Editor/DataTableEditor/Private/SRowEditor.cpp
fred kimberley 4c0c49b2f5 Added better warning messages for when users try to rename rows with invalid names in the row editor.
Updated the editable text layout to prevent it from calling OnTextCommitted delegates when the text has not been changed. This fixes a bug where committing a text change by pressing enter can call the delegate twice.

#jira UE-63407
#rb Marc.Audy
[FYI] Chris.Gagnon

#ROBOMERGE-OWNER: ryan.vance
#ROBOMERGE-AUTHOR: fred.kimberley
#ROBOMERGE-SOURCE: CL 4962191 in //UE4/Release-4.22/... via CL 4968530
#ROBOMERGE-BOT: DEVVR (Main -> Dev-VR)

[CL 5030972 by fred kimberley in Dev-VR branch]
2019-02-16 05:56:04 -05:00

642 lines
16 KiB
C++

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "SRowEditor.h"
#include "Modules/ModuleManager.h"
#include "Widgets/Layout/SSpacer.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SEditableTextBox.h"
#include "Widgets/Input/SButton.h"
#include "EditorStyleSet.h"
#include "UObject/StructOnScope.h"
#include "Misc/MessageDialog.h"
#include "PropertyEditorModule.h"
#include "IStructureDetailsView.h"
#include "IDetailsView.h"
#define LOCTEXT_NAMESPACE "SRowEditor"
class FStructFromDataTable : public FStructOnScope
{
TWeakObjectPtr<UDataTable> DataTable;
FName RowName;
public:
FStructFromDataTable(UDataTable* InDataTable, FName InRowName)
: FStructOnScope()
, DataTable(InDataTable)
, RowName(InRowName)
{}
virtual uint8* GetStructMemory() override
{
return (DataTable.IsValid() && !RowName.IsNone()) ? DataTable->FindRowUnchecked(RowName) : nullptr;
}
virtual const uint8* GetStructMemory() const override
{
return (DataTable.IsValid() && !RowName.IsNone()) ? DataTable->FindRowUnchecked(RowName) : nullptr;
}
virtual const UScriptStruct* GetStruct() const override
{
return DataTable.IsValid() ? DataTable->GetRowStruct() : nullptr;
}
virtual UPackage* GetPackage() const override
{
return DataTable.IsValid() ? DataTable->GetOutermost() : nullptr;
}
virtual void SetPackage(UPackage* InPackage) override
{
}
virtual bool IsValid() const override
{
return !RowName.IsNone()
&& DataTable.IsValid()
&& DataTable->GetRowStruct()
&& DataTable->FindRowUnchecked(RowName);
}
virtual void Destroy() override
{
DataTable = nullptr;
RowName = NAME_None;
}
FName GetRowName() const
{
return RowName;
}
};
SRowEditor::SRowEditor()
: SCompoundWidget()
{
}
SRowEditor::~SRowEditor()
{
}
void SRowEditor::NotifyPreChange( UProperty* PropertyAboutToChange )
{
check(DataTable.IsValid());
DataTable->Modify();
FDataTableEditorUtils::BroadcastPreChange(DataTable.Get(), FDataTableEditorUtils::EDataTableChangeInfo::RowData);
}
void SRowEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, UProperty* PropertyThatChanged )
{
check(DataTable.IsValid());
FDataTableEditorUtils::BroadcastPostChange(DataTable.Get(), FDataTableEditorUtils::EDataTableChangeInfo::RowData);
DataTable->MarkPackageDirty();
}
void SRowEditor::PreChange(const class UUserDefinedStruct* Struct, FStructureEditorUtils::EStructureEditorChangeInfo Info)
{
if (Struct && (GetScriptStruct() == Struct))
{
CleanBeforeChange();
}
}
void SRowEditor::PostChange(const class UUserDefinedStruct* Struct, FStructureEditorUtils::EStructureEditorChangeInfo Info)
{
if (Struct && (GetScriptStruct() == Struct))
{
RefreshNameList();
Restore();
}
}
void SRowEditor::PreChange(const UDataTable* Changed, FDataTableEditorUtils::EDataTableChangeInfo Info)
{
if ((Changed == DataTable.Get()) && (FDataTableEditorUtils::EDataTableChangeInfo::RowList == Info))
{
CleanBeforeChange();
}
}
void SRowEditor::PostChange(const UDataTable* Changed, FDataTableEditorUtils::EDataTableChangeInfo Info)
{
if ((Changed == DataTable.Get()) && (FDataTableEditorUtils::EDataTableChangeInfo::RowList == Info))
{
RefreshNameList();
Restore();
}
}
void SRowEditor::CleanBeforeChange()
{
if (StructureDetailsView.IsValid())
{
StructureDetailsView->SetStructureData(NULL);
}
if (CurrentRow.IsValid())
{
CurrentRow->Destroy();
CurrentRow.Reset();
}
}
void SRowEditor::RefreshNameList()
{
CachedRowNames.Empty();
if (DataTable.IsValid())
{
auto RowNames = DataTable->GetRowNames();
for (auto RowName : RowNames)
{
CachedRowNames.Add(MakeShareable(new FName(RowName)));
}
}
}
void SRowEditor::Restore()
{
if (!SelectedName.IsValid() || !SelectedName->IsNone())
{
if (SelectedName.IsValid())
{
auto CurrentName = *SelectedName;
SelectedName = NULL;
for (auto Element : CachedRowNames)
{
if (*Element == CurrentName)
{
SelectedName = Element;
break;
}
}
}
if (!SelectedName.IsValid() && CachedRowNames.Num() && CachedRowNames[0].IsValid())
{
SelectedName = CachedRowNames[0];
}
if (RowComboBox.IsValid())
{
RowComboBox->SetSelectedItem(SelectedName);
}
}
else
{
if (RowComboBox.IsValid())
{
RowComboBox->ClearSelection();
}
}
auto FinalName = SelectedName.IsValid() ? *SelectedName : NAME_None;
CurrentRow = MakeShareable(new FStructFromDataTable(DataTable.Get(), FinalName));
if (StructureDetailsView.IsValid())
{
StructureDetailsView->SetStructureData(CurrentRow);
}
RowSelectedCallback.ExecuteIfBound(FinalName);
}
UScriptStruct* SRowEditor::GetScriptStruct() const
{
return DataTable.IsValid() ? DataTable->RowStruct : nullptr;
}
FName SRowEditor::GetCurrentName() const
{
return SelectedName.IsValid() ? *SelectedName : NAME_None;
}
FText SRowEditor::GetCurrentNameAsText() const
{
return FText::FromName(GetCurrentName());
}
FString SRowEditor::GetStructureDisplayName() const
{
const auto Struct = DataTable.IsValid() ? DataTable->GetRowStruct() : nullptr;
return Struct
? Struct->GetDisplayNameText().ToString()
: LOCTEXT("Error_UnknownStruct", "Error: Unknown Struct").ToString();
}
TSharedRef<SWidget> SRowEditor::OnGenerateWidget(TSharedPtr<FName> InItem)
{
return SNew(STextBlock).Text(FText::FromName(InItem.IsValid() ? *InItem : NAME_None));
}
void SRowEditor::OnSelectionChanged(TSharedPtr<FName> InItem, ESelectInfo::Type InSeletionInfo)
{
if (InItem.IsValid() && InItem != SelectedName)
{
CleanBeforeChange();
SelectedName = InItem;
Restore();
if (RenameTextBox.IsValid())
{
// refresh name, in case of a pending rename action
RenameTextBox->SetText(TAttribute<FText>(this, &SRowEditor::GetCurrentNameAsText));
}
}
}
void SRowEditor::SelectRow(FName InName)
{
TSharedPtr<FName> NewSelectedName;
for (auto Name : CachedRowNames)
{
if (Name.IsValid() && (*Name == InName))
{
NewSelectedName = Name;
}
}
if (!NewSelectedName.IsValid())
{
NewSelectedName = MakeShareable(new FName(InName));
}
OnSelectionChanged(NewSelectedName, ESelectInfo::Direct);
}
void SRowEditor::HandleUndoRedo()
{
RefreshNameList();
Restore();
}
FReply SRowEditor::OnAddClicked()
{
if (DataTable.IsValid())
{
FName NewName = DataTableUtils::MakeValidName(TEXT("NewRow"));
const TArray<FName> ExisitngNames = DataTable->GetRowNames();
while (ExisitngNames.Contains(NewName))
{
NewName.SetNumber(NewName.GetNumber() + 1);
}
FDataTableEditorUtils::AddRow(DataTable.Get(), NewName);
SelectRow(NewName);
}
return FReply::Handled();
}
FReply SRowEditor::OnRemoveClicked()
{
if (DataTable.IsValid())
{
const FName RowToRemove = GetCurrentName();
const int32 RowToRemoveIndex = CachedRowNames.IndexOfByPredicate([&](const TSharedPtr<FName>& InRowName) -> bool
{
return *InRowName == RowToRemove;
});
if (FDataTableEditorUtils::RemoveRow(DataTable.Get(), RowToRemove))
{
// Try and keep the same row index selected
const int32 RowIndexToSelect = FMath::Clamp(RowToRemoveIndex, 0, CachedRowNames.Num() - 1);
if (CachedRowNames.IsValidIndex(RowIndexToSelect))
{
SelectRow(*CachedRowNames[RowIndexToSelect]);
}
}
}
return FReply::Handled();
}
FReply SRowEditor::OnMoveRowClicked(FDataTableEditorUtils::ERowMoveDirection MoveDirection)
{
if (DataTable.IsValid())
{
const FName RowToMove = GetCurrentName();
FDataTableEditorUtils::MoveRow(DataTable.Get(), RowToMove, MoveDirection);
}
return FReply::Handled();
}
FReply SRowEditor::OnMoveToExtentClicked(FDataTableEditorUtils::ERowMoveDirection MoveDirection)
{
if (DataTable.IsValid())
{
// We move by the row map size, as FDataTableEditorUtils::MoveRow will automatically clamp this as appropriate
const FName RowToMove = GetCurrentName();
FDataTableEditorUtils::MoveRow(DataTable.Get(), RowToMove, MoveDirection, DataTable->GetRowMap().Num());
}
return FReply::Handled();
}
void SRowEditor::OnRowRenamed(const FText& Text, ETextCommit::Type CommitType)
{
if (!GetCurrentNameAsText().EqualTo(Text) && DataTable.IsValid())
{
if (Text.IsEmptyOrWhitespace() || !FName::IsValidXName(Text.ToString(), INVALID_NAME_CHARACTERS))
{
// popup an error dialog here
const FText Message = FText::Format(LOCTEXT("InvalidRowName", "'{0}' is not a valid row name"), Text);
FMessageDialog::Open(EAppMsgType::Ok, Message);
return;
}
const FName NewName = DataTableUtils::MakeValidName(Text.ToString());
if (NewName == NAME_None)
{
// popup an error dialog here
const FText Message = FText::Format(LOCTEXT("InvalidRowName", "'{0}' is not a valid row name"), Text);
FMessageDialog::Open(EAppMsgType::Ok, Message);
return;
}
for (auto Name : CachedRowNames)
{
if (Name.IsValid() && (*Name == NewName))
{
//the name already exists
// popup an error dialog here
const FText Message = FText::Format(LOCTEXT("DuplicateRowName", "'{0}' is already used as a row name in this table"), Text);
FMessageDialog::Open(EAppMsgType::Ok, Message);
return;
}
}
const FName OldName = GetCurrentName();
FDataTableEditorUtils::RenameRow(DataTable.Get(), OldName, NewName);
SelectRow(NewName);
}
}
FReply SRowEditor::OnResetToDefaultClicked()
{
if (DataTable.IsValid() && SelectedName.IsValid())
{
FDataTableEditorUtils::ResetToDefault(DataTable.Get(), *SelectedName.Get());
}
return FReply::Handled();
}
EVisibility SRowEditor::GetResetToDefaultVisibility() const
{
EVisibility VisibleState = EVisibility::Collapsed;
if (DataTable.IsValid() && SelectedName.IsValid())
{
if (FDataTableEditorUtils::DiffersFromDefault(DataTable.Get(), *SelectedName.Get()))
{
VisibleState = EVisibility::Visible;
}
}
return VisibleState;
}
void SRowEditor::Construct(const FArguments& InArgs, UDataTable* Changed)
{
ConstructInternal(Changed);
}
void SRowEditor::ConstructInternal(UDataTable* Changed)
{
DataTable = Changed;
{
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
FDetailsViewArgs ViewArgs;
ViewArgs.bAllowSearch = false;
ViewArgs.bHideSelectionTip = false;
ViewArgs.bShowActorLabel = false;
ViewArgs.NotifyHook = this;
FStructureDetailsViewArgs StructureViewArgs;
StructureViewArgs.bShowObjects = false;
StructureViewArgs.bShowAssets = true;
StructureViewArgs.bShowClasses = true;
StructureViewArgs.bShowInterfaces = false;
StructureDetailsView = PropertyModule.CreateStructureDetailView(ViewArgs, StructureViewArgs, CurrentRow, LOCTEXT("RowValue", "Row Value"));
}
RefreshNameList();
Restore();
const float ButtonWidth = 85.0f;
ChildSlot
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
[
SNew(SHorizontalBox)
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
.ForegroundColor(FSlateColor::UseForeground())
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.OnClicked(this, &SRowEditor::OnAddClicked)
.IsEnabled(this, &SRowEditor::IsAddRowEnabled)
.ToolTipText(LOCTEXT("AddRowTooltip", "Add a new row to the data table"))
[
SNew(SImage)
.Image(FEditorStyle::Get().GetBrush("Plus"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
.ForegroundColor(FSlateColor::UseForeground())
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.OnClicked(this, &SRowEditor::OnRemoveClicked)
.IsEnabled(this, &SRowEditor::IsRemoveRowEnabled)
.ToolTipText(LOCTEXT("RemoveRowTooltip", "Remove the currently selected row from the data table"))
[
SNew(SImage)
.Image(FEditorStyle::Get().GetBrush("Cross"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
[
SNew(SBox)
.WidthOverride(2 * ButtonWidth)
.ToolTipText(LOCTEXT("SelectedRowTooltip", "Select a row to edit"))
[
SAssignNew(RowComboBox, SComboBox<TSharedPtr<FName>>)
.OptionsSource(&CachedRowNames)
.OnSelectionChanged(this, &SRowEditor::OnSelectionChanged)
.OnGenerateWidget(this, &SRowEditor::OnGenerateWidget)
.Content()
[
SNew(STextBlock).Text(this, &SRowEditor::GetCurrentNameAsText)
]
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
[
SNew(SButton)
.OnClicked(this, &SRowEditor::OnResetToDefaultClicked)
.Visibility(this, &SRowEditor::GetResetToDefaultVisibility)
.ContentPadding(FMargin(5.f, 0.f))
.ToolTipText(LOCTEXT("ResetToDefaultToolTip", "Reset to Default"))
.ButtonStyle(FEditorStyle::Get(), "NoBorder")
.ForegroundColor(FSlateColor::UseForeground())
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.Content()
[
SNew(SImage)
.Image(FEditorStyle::GetBrush("PropertyWindow.DiffersFromDefault"))
]
]
+ SHorizontalBox::Slot()
[
SNew(SSpacer)
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
.VAlign(EVerticalAlignment::VAlign_Center)
[
SNew(SBox)
.HAlign(HAlign_Right)
.Visibility(this, &SRowEditor::GetRenameVisibility)
[
SNew(STextBlock).Text(LOCTEXT("RowNameLabel", "Row Name:"))
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
[
SNew(SBox)
.WidthOverride(2 * ButtonWidth)
.Visibility(this, &SRowEditor::GetRenameVisibility)
[
SAssignNew(RenameTextBox, SEditableTextBox)
.Text(this, &SRowEditor::GetCurrentNameAsText)
.OnTextCommitted(this, &SRowEditor::OnRowRenamed)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
.ForegroundColor(FSlateColor::UseForeground())
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.OnClicked(this, &SRowEditor::OnMoveRowClicked, FDataTableEditorUtils::ERowMoveDirection::Up)
.IsEnabled(this, &SRowEditor::IsMoveRowUpEnabled)
.ToolTipText(LOCTEXT("MoveUpTooltip", "Move the currently selected row up by one in the data table"))
[
SNew(STextBlock)
.Font(FEditorStyle::Get().GetFontStyle("FontAwesome.14"))
.Text(FText::FromString(FString(TEXT("\xf106"))) /*fa-angle-up*/)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
.ForegroundColor(FSlateColor::UseForeground())
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.OnClicked(this, &SRowEditor::OnMoveRowClicked, FDataTableEditorUtils::ERowMoveDirection::Down)
.IsEnabled(this, &SRowEditor::IsMoveRowDownEnabled)
.ToolTipText(LOCTEXT("MoveDownTooltip", "Move the currently selected row down by one in the data table"))
[
SNew(STextBlock)
.Font(FEditorStyle::Get().GetFontStyle("FontAwesome.14"))
.Text(FText::FromString(FString(TEXT("\xf107"))) /*fa-angle-down*/)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
.ForegroundColor(FSlateColor::UseForeground())
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.OnClicked(this, &SRowEditor::OnMoveToExtentClicked, FDataTableEditorUtils::ERowMoveDirection::Up)
.IsEnabled(this, &SRowEditor::IsMoveRowUpEnabled)
.ToolTipText(LOCTEXT("MoveToTopTooltip", "Move the currently selected row to the top of the data table"))
[
SNew(STextBlock)
.Font(FEditorStyle::Get().GetFontStyle("FontAwesome.14"))
.Text(FText::FromString(FString(TEXT("\xf102"))) /*fa-angle-double-up*/)
]
]
+ SHorizontalBox::Slot()
.AutoWidth()
.Padding(2)
[
SNew(SButton)
.ButtonStyle(FEditorStyle::Get(), "HoverHintOnly")
.ForegroundColor(FSlateColor::UseForeground())
.HAlign(HAlign_Center)
.VAlign(VAlign_Center)
.OnClicked(this, &SRowEditor::OnMoveToExtentClicked, FDataTableEditorUtils::ERowMoveDirection::Down)
.IsEnabled(this, &SRowEditor::IsMoveRowDownEnabled)
.ToolTipText(LOCTEXT("MoveToBottomTooltip", "Move the currently selected row to the bottom of the data table"))
[
SNew(STextBlock)
.Font(FEditorStyle::Get().GetFontStyle("FontAwesome.14"))
.Text(FText::FromString(FString(TEXT("\xf103"))) /*fa-angle-double-down*/)
]
]
]
+ SVerticalBox::Slot()
[
StructureDetailsView->GetWidget().ToSharedRef()
]
];
}
bool SRowEditor::IsMoveRowUpEnabled() const
{
return true;
}
bool SRowEditor::IsMoveRowDownEnabled() const
{
return true;
}
bool SRowEditor::IsAddRowEnabled() const
{
return true;
}
bool SRowEditor::IsRemoveRowEnabled() const
{
return true;
}
EVisibility SRowEditor::GetRenameVisibility() const
{
return EVisibility::Visible;
}
#undef LOCTEXT_NAMESPACE