Files
UnrealEngineUWP/Engine/Source/Editor/ContentBrowser/Private/SCollectionView.cpp
patrick laflamme 878d1ae127 Instrumented source control upcate callbacks with with cpu scopes for monitoring performances.
Optimized Collection code invoked at each source control update to avoid comparing large set to detect if the in-memory collection is different from the collection on disk.

#rb Julien.StJean
#preflight 6346e2cf663dafe4162e8c9e

[CL 22505265 by patrick laflamme in ue5-main branch]
2022-10-13 11:24:42 -04:00

1590 lines
57 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SCollectionView.h"
#include "AssetRegistry/AssetData.h"
#include "CollectionAssetManagement.h"
#include "CollectionContextMenu.h"
#include "CollectionManagerModule.h"
#include "CollectionViewTypes.h"
#include "CollectionViewUtils.h"
#include "ContentBrowserDelegates.h"
#include "ContentBrowserModule.h"
#include "ContentBrowserPluginFilters.h"
#include "ContentBrowserUtils.h"
#include "CoreGlobals.h"
#include "DragAndDrop/AssetDragDropOp.h"
#include "DragAndDrop/CollectionDragDropOp.h"
#include "Framework/Application/MenuStack.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/Commands/UICommandList.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "Framework/SlateDelegates.h"
#include "Framework/Views/ITypedTableView.h"
#include "GenericPlatform/ICursor.h"
#include "HistoryManager.h"
#include "ICollectionManager.h"
#include "ISourceControlModule.h"
#include "ISourceControlProvider.h"
#include "ISourceControlState.h"
#include "Input/DragAndDrop.h"
#include "Input/Events.h"
#include "InputCoreTypes.h"
#include "Layout/Children.h"
#include "Layout/Geometry.h"
#include "Layout/Margin.h"
#include "Layout/SlateRect.h"
#include "Layout/WidgetPath.h"
#include "Math/Color.h"
#include "Math/Vector2D.h"
#include "Misc/CString.h"
#include "Misc/ConfigCacheIni.h"
#include "Misc/TextFilterUtils.h"
#include "Modules/ModuleManager.h"
#include "ProfilingDebugging/CpuProfilerTrace.h"
#include "SAssetTagItem.h"
#include "SlotBase.h"
#include "SourcesData.h"
#include "SourcesSearch.h"
#include "SourcesViewWidgets.h"
#include "Styling/AppStyle.h"
#include "Styling/ISlateStyle.h"
#include "Styling/SlateColor.h"
#include "Templates/Tuple.h"
#include "Textures/SlateIcon.h"
#include "UObject/NameTypes.h"
#include "UObject/UnrealNames.h"
#include "Widgets/Images/SImage.h"
#include "Widgets/Input/SButton.h"
#include "Widgets/Input/SCheckBox.h"
#include "Widgets/Layout/SBorder.h"
#include "Widgets/SBoxPanel.h"
#include "Widgets/SNullWidget.h"
#include "Widgets/SOverlay.h"
#include "Widgets/Views/ITableRow.h"
#include "Widgets/Views/STableRow.h"
class SWidget;
struct FSlateBrush;
#define LOCTEXT_NAMESPACE "ContentBrowser"
namespace CollectionViewFilter
{
void GetBasicStrings(const FCollectionItem& InCollection, TArray<FString>& OutBasicStrings)
{
OutBasicStrings.Add(InCollection.CollectionName.ToString());
}
bool TestComplexExpression(const FCollectionItem& InCollection, const FName& InKey, const FTextFilterString& InValue, ETextFilterComparisonOperation InComparisonOperation, ETextFilterTextComparisonMode InTextComparisonMode)
{
static const FName NameKeyName = "Name";
static const FName TypeKeyName = "Type";
// Handle the collection name
if (InKey == NameKeyName)
{
// Names can only work with Equal or NotEqual type tests
if (InComparisonOperation != ETextFilterComparisonOperation::Equal && InComparisonOperation != ETextFilterComparisonOperation::NotEqual)
{
return false;
}
const bool bIsMatch = TextFilterUtils::TestBasicStringExpression(InCollection.CollectionName.ToString(), InValue, InTextComparisonMode);
return (InComparisonOperation == ETextFilterComparisonOperation::Equal) ? bIsMatch : !bIsMatch;
}
// Handle the collection type
if (InKey == TypeKeyName)
{
// Types can only work with Equal or NotEqual type tests
if (InComparisonOperation != ETextFilterComparisonOperation::Equal && InComparisonOperation != ETextFilterComparisonOperation::NotEqual)
{
return false;
}
const bool bIsMatch = TextFilterUtils::TestBasicStringExpression(ECollectionShareType::ToString(InCollection.CollectionType), InValue, InTextComparisonMode);
return (InComparisonOperation == ETextFilterComparisonOperation::Equal) ? bIsMatch : !bIsMatch;
}
return false;
}
} // namespace CollectionViewFilter
void SCollectionView::Construct( const FArguments& InArgs )
{
OnCollectionSelected = InArgs._OnCollectionSelected;
bAllowCollectionButtons = InArgs._AllowCollectionButtons;
bAllowRightClickMenu = InArgs._AllowRightClickMenu;
bAllowCollectionDrag = InArgs._AllowCollectionDrag;
bAllowExternalSearch = false;
bDraggedOver = false;
bQueueCollectionItemsUpdate = false;
bQueueSCCRefresh = true;
IsDocked = InArgs._IsDocked;
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
CollectionManagerModule.Get().OnCollectionCreated().AddSP( this, &SCollectionView::HandleCollectionCreated );
CollectionManagerModule.Get().OnCollectionRenamed().AddSP( this, &SCollectionView::HandleCollectionRenamed );
CollectionManagerModule.Get().OnCollectionReparented().AddSP( this, &SCollectionView::HandleCollectionReparented );
CollectionManagerModule.Get().OnCollectionDestroyed().AddSP( this, &SCollectionView::HandleCollectionDestroyed );
CollectionManagerModule.Get().OnCollectionUpdated().AddSP( this, &SCollectionView::HandleCollectionUpdated );
CollectionManagerModule.Get().OnAssetsAddedToCollection().AddSP( this, &SCollectionView::HandleAssetsAddedToCollection );
CollectionManagerModule.Get().OnAssetsRemovedFromCollection().AddSP( this, &SCollectionView::HandleAssetsRemovedFromCollection );
ISourceControlModule::Get().RegisterProviderChanged(FSourceControlProviderChanged::FDelegate::CreateSP(this, &SCollectionView::HandleSourceControlProviderChanged));
SourceControlStateChangedDelegateHandle = ISourceControlModule::Get().GetProvider().RegisterSourceControlStateChanged_Handle(FSourceControlStateChanged::FDelegate::CreateSP(this, &SCollectionView::HandleSourceControlStateChanged));
Commands = TSharedPtr< FUICommandList >(new FUICommandList);
CollectionContextMenu = MakeShareable(new FCollectionContextMenu( SharedThis(this) ));
CollectionContextMenu->BindCommands(Commands);
CollectionItemTextFilter = MakeShareable(new FCollectionItemTextFilter(
FCollectionItemTextFilter::FItemToStringArray::CreateStatic(&CollectionViewFilter::GetBasicStrings),
FCollectionItemTextFilter::FItemTestComplexExpression::CreateStatic(&CollectionViewFilter::TestComplexExpression)
));
CollectionItemTextFilter->OnChanged().AddSP(this, &SCollectionView::UpdateFilteredCollectionItems);
if ( InArgs._AllowQuickAssetManagement )
{
QuickAssetManagement = MakeShareable(new FCollectionAssetManagement());
}
FOnContextMenuOpening CollectionListContextMenuOpening;
if ( InArgs._AllowContextMenu )
{
CollectionListContextMenuOpening = FOnContextMenuOpening::CreateSP( this, &SCollectionView::MakeCollectionTreeContextMenu );
}
SearchPtr = InArgs._ExternalSearch;
if (SearchPtr)
{
SearchPtr->OnSearchChanged().AddSP(this, &SCollectionView::SetCollectionsSearchFilterText);
}
ExternalSearchPtr = InArgs._ExternalSearch;
TitleContent = SNew(SHorizontalBox);
PreventSelectionChangedDelegateCount = 0;
TSharedRef< SWidget > HeaderContent = SNew(SHorizontalBox)
.Visibility(this, &SCollectionView::GetHeaderVisibility)
+ SHorizontalBox::Slot()
.FillWidth(1.0f)
.Padding(0.0f)
[
TitleContent.ToSharedRef()
]
+SHorizontalBox::Slot()
.AutoWidth()
.VAlign(VAlign_Center)
.Padding(2.0f, 0.0f, 0.0f, 0.0f)
[
SNew(SButton)
.ButtonStyle(FAppStyle::Get(), "SimpleButton")
.ToolTipText(LOCTEXT("AddCollectionButtonTooltip", "Add a collection."))
.OnClicked(this, &SCollectionView::OnAddCollectionClicked)
.ContentPadding( FMargin(2, 2) )
.Visibility(bAllowCollectionButtons ? EVisibility::Visible : EVisibility::Collapsed)
[
SNew(SImage)
.Image(FAppStyle::Get().GetBrush("Icons.PlusCircle"))
.ColorAndOpacity(FSlateColor::UseForeground())
]
];
TSharedRef< SWidget > BodyContent = SNew(SVerticalBox)
// Collections tree
+SVerticalBox::Slot()
.FillHeight(1.f)
[
SAssignNew(CollectionTreePtr, STreeView< TSharedPtr<FCollectionItem> >)
.TreeItemsSource(&VisibleRootCollectionItems)
.OnGenerateRow(this, &SCollectionView::GenerateCollectionRow)
.OnGetChildren(this, &SCollectionView::GetCollectionItemChildren)
.ItemHeight(18)
.SelectionMode(ESelectionMode::Multi)
.OnSelectionChanged(this, &SCollectionView::CollectionSelectionChanged)
.OnContextMenuOpening(CollectionListContextMenuOpening)
.OnItemScrolledIntoView(this, &SCollectionView::CollectionItemScrolledIntoView)
.ClearSelectionOnClick(false)
.Visibility(this, &SCollectionView::GetCollectionTreeVisibility)
];
ChildSlot
[
SNew(SOverlay)
// Main content
+SOverlay::Slot()
[
SNew(SVerticalBox)
+ SVerticalBox::Slot()
.AutoHeight()
.Padding(FMargin(12.0f, 0.0f, 0.0f, 0.0f))
[
HeaderContent
]
+ SVerticalBox::Slot()
[
BodyContent
]
]
// Drop target overlay
+SOverlay::Slot()
[
SNew(SBorder)
.Padding(0)
.Visibility(EVisibility::HitTestInvisible)
.BorderImage(this, &SCollectionView::GetCollectionViewDropTargetBorder)
.BorderBackgroundColor(FLinearColor::Yellow)
[
SNullWidget::NullWidget
]
]
];
UpdateCollectionItems();
}
void SCollectionView::HandleCollectionCreated( const FCollectionNameType& Collection )
{
bQueueCollectionItemsUpdate = true;
}
void SCollectionView::HandleCollectionRenamed( const FCollectionNameType& OriginalCollection, const FCollectionNameType& NewCollection )
{
bQueueCollectionItemsUpdate = true;
// Rename the item in-place so we can maintain its expansion and selection states correctly once the view is refreshed on the next Tick
TSharedPtr<FCollectionItem> CollectionItem = AvailableCollections.FindRef(OriginalCollection);
if (CollectionItem.IsValid())
{
CollectionItem->CollectionName = NewCollection.Name;
CollectionItem->CollectionType = NewCollection.Type;
AvailableCollections.Remove(OriginalCollection);
AvailableCollections.Add(NewCollection, CollectionItem);
}
}
void SCollectionView::HandleCollectionReparented( const FCollectionNameType& Collection, const TOptional<FCollectionNameType>& OldParent, const TOptional<FCollectionNameType>& NewParent )
{
bQueueCollectionItemsUpdate = true;
}
void SCollectionView::HandleCollectionDestroyed( const FCollectionNameType& Collection )
{
bQueueCollectionItemsUpdate = true;
}
void SCollectionView::HandleCollectionUpdated( const FCollectionNameType& Collection )
{
TSharedPtr<FCollectionItem> CollectionItemToUpdate = AvailableCollections.FindRef(Collection);
if (CollectionItemToUpdate.IsValid())
{
bQueueSCCRefresh = true;
CollectionItemToUpdate->CollectionColor = CollectionViewUtils::ResolveColor(Collection.Name, Collection.Type);
UpdateCollectionItemStatus(CollectionItemToUpdate.ToSharedRef());
}
}
void SCollectionView::HandleAssetsAddedToCollection( const FCollectionNameType& Collection, TConstArrayView<FSoftObjectPath> AssetsAdded )
{
HandleCollectionUpdated(Collection);
}
void SCollectionView::HandleAssetsRemovedFromCollection( const FCollectionNameType& Collection, TConstArrayView<FSoftObjectPath> AssetsRemoved )
{
HandleCollectionUpdated(Collection);
}
void SCollectionView::HandleSourceControlProviderChanged(ISourceControlProvider& OldProvider, ISourceControlProvider& NewProvider)
{
OldProvider.UnregisterSourceControlStateChanged_Handle(SourceControlStateChangedDelegateHandle);
SourceControlStateChangedDelegateHandle = NewProvider.RegisterSourceControlStateChanged_Handle(FSourceControlStateChanged::FDelegate::CreateSP(this, &SCollectionView::HandleSourceControlStateChanged));
bQueueSCCRefresh = true;
HandleSourceControlStateChanged();
}
void SCollectionView::HandleSourceControlStateChanged()
{
TRACE_CPUPROFILER_EVENT_SCOPE(SCollectionView::HandleSourceControlStateChanged);
// Update the status of each collection
for (const auto& AvailableCollectionInfo : AvailableCollections)
{
UpdateCollectionItemStatus(AvailableCollectionInfo.Value.ToSharedRef());
}
}
void SCollectionView::UpdateCollectionItemStatus( const TSharedRef<FCollectionItem>& CollectionItem )
{
TRACE_CPUPROFILER_EVENT_SCOPE(SCollectionView::UpdateCollectionItemStatus);
int32 NewObjectCount = 0;
TOptional<ECollectionItemStatus> NewStatus;
// Check IsModuleAvailable as we might be in the process of shutting down, and were notified due to the SCC provider being nulled out...
if (FCollectionManagerModule::IsModuleAvailable())
{
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
FCollectionStatusInfo StatusInfo;
if (CollectionManagerModule.Get().GetCollectionStatusInfo(CollectionItem->CollectionName, CollectionItem->CollectionType, StatusInfo))
{
NewObjectCount = StatusInfo.NumObjects;
// Test the SCC state first as this should take priority when reporting the status back to the user
if (StatusInfo.bUseSCC)
{
if (StatusInfo.SCCState.IsValid() && StatusInfo.SCCState->IsSourceControlled())
{
if (StatusInfo.SCCState->IsCheckedOutOther())
{
NewStatus = ECollectionItemStatus::IsCheckedOutByAnotherUser;
}
else if (StatusInfo.SCCState->IsConflicted())
{
NewStatus = ECollectionItemStatus::IsConflicted;
}
else if (!StatusInfo.SCCState->IsCurrent())
{
NewStatus = ECollectionItemStatus::IsOutOfDate;
}
else if (StatusInfo.SCCState->IsModified())
{
NewStatus = ECollectionItemStatus::HasLocalChanges;
}
}
else
{
NewStatus = ECollectionItemStatus::IsMissingSCCProvider;
}
}
// Not set by the SCC status, so check just use the local state
if (!NewStatus.IsSet())
{
if (StatusInfo.bIsDirty)
{
NewStatus = ECollectionItemStatus::HasLocalChanges;
}
else if (StatusInfo.bIsEmpty)
{
NewStatus = ECollectionItemStatus::IsUpToDateAndEmpty;
}
else
{
NewStatus = ECollectionItemStatus::IsUpToDateAndPopulated;
}
}
}
}
CollectionItem->NumObjects = NewObjectCount;
CollectionItem->CurrentStatus = NewStatus.Get(ECollectionItemStatus::IsUpToDateAndEmpty);
}
void SCollectionView::UpdateCollectionItems()
{
struct FGatherCollectionItems
{
FGatherCollectionItems()
: CollectionManagerModule(FCollectionManagerModule::GetModule())
{
}
void GatherCollectionItems(FAvailableCollectionsMap& OutAvailableCollections)
{
OutAvailableCollections.Reset();
TArray<FCollectionNameType> RootCollections;
CollectionManagerModule.Get().GetRootCollections(RootCollections);
ProcessGatheredCollectionsAndRecurse(RootCollections, nullptr, OutAvailableCollections);
}
void GatherChildCollectionItems(const TSharedPtr<FCollectionItem>& InParentCollectionItem, FAvailableCollectionsMap& OutAvailableCollections)
{
TArray<FCollectionNameType> ChildCollections;
CollectionManagerModule.Get().GetChildCollections(InParentCollectionItem->CollectionName, InParentCollectionItem->CollectionType, ChildCollections);
ProcessGatheredCollectionsAndRecurse(ChildCollections, InParentCollectionItem, OutAvailableCollections);
}
void ProcessGatheredCollectionsAndRecurse(const TArray<FCollectionNameType>& InCollections, const TSharedPtr<FCollectionItem>& InParentCollectionItem, FAvailableCollectionsMap& OutAvailableCollections)
{
for (const FCollectionNameType& Collection : InCollections)
{
// Never display system collections
if (Collection.Type == ECollectionShareType::CST_System)
{
continue;
}
TSharedRef<FCollectionItem> CollectionItem = MakeShareable(new FCollectionItem(Collection.Name, Collection.Type));
OutAvailableCollections.Add(Collection, CollectionItem);
CollectionManagerModule.Get().GetCollectionStorageMode(Collection.Name, Collection.Type, CollectionItem->StorageMode);
CollectionItem->CollectionColor = CollectionViewUtils::ResolveColor(Collection.Name, Collection.Type);
SCollectionView::UpdateCollectionItemStatus(CollectionItem);
if (InParentCollectionItem.IsValid())
{
// Fixup the parent and child pointers
InParentCollectionItem->ChildCollections.Add(CollectionItem);
CollectionItem->ParentCollection = InParentCollectionItem;
}
// Recurse
GatherChildCollectionItems(CollectionItem, OutAvailableCollections);
}
}
FCollectionManagerModule& CollectionManagerModule;
};
// Backup the current selection and expansion state of our collections
// We're about to re-create the tree, so we'll need to re-apply this again afterwards
TArray<FCollectionNameType> SelectedCollections;
TArray<FCollectionNameType> ExpandedCollections;
{
const auto SelectedCollectionItems = CollectionTreePtr->GetSelectedItems();
SelectedCollections.Reserve(SelectedCollectionItems.Num());
for (const TSharedPtr<FCollectionItem>& SelectedCollectionItem : SelectedCollectionItems)
{
SelectedCollections.Add(FCollectionNameType(SelectedCollectionItem->CollectionName, SelectedCollectionItem->CollectionType));
}
}
{
TSet<TSharedPtr<FCollectionItem>> ExpandedCollectionItems;
CollectionTreePtr->GetExpandedItems(ExpandedCollectionItems);
ExpandedCollections.Reserve(ExpandedCollectionItems.Num());
for (const TSharedPtr<FCollectionItem>& ExpandedCollectionItem : ExpandedCollectionItems)
{
ExpandedCollections.Add(FCollectionNameType(ExpandedCollectionItem->CollectionName, ExpandedCollectionItem->CollectionType));
}
}
FGatherCollectionItems GatherCollectionItems;
GatherCollectionItems.GatherCollectionItems(AvailableCollections);
UpdateFilteredCollectionItems();
// Restore selection and expansion
SetSelectedCollections(SelectedCollections, false);
SetExpandedCollections(ExpandedCollections);
bQueueSCCRefresh = true;
}
void SCollectionView::UpdateFilteredCollectionItems()
{
VisibleCollections.Reset();
VisibleRootCollectionItems.Reset();
auto AddVisibleCollection = [&](const TSharedPtr<FCollectionItem>& InCollectionItem)
{
VisibleCollections.Add(FCollectionNameType(InCollectionItem->CollectionName, InCollectionItem->CollectionType));
if (!InCollectionItem->ParentCollection.IsValid())
{
VisibleRootCollectionItems.AddUnique(InCollectionItem);
}
};
auto AddVisibleCollectionRecursive = [&](const TSharedPtr<FCollectionItem>& InCollectionItem)
{
TSharedPtr<FCollectionItem> CollectionItemToAdd = InCollectionItem;
do
{
AddVisibleCollection(CollectionItemToAdd);
CollectionItemToAdd = CollectionItemToAdd->ParentCollection.Pin();
}
while(CollectionItemToAdd.IsValid());
};
// Do we have an active filter to test against?
if (CollectionItemTextFilter->GetRawFilterText().IsEmpty())
{
// No filter, just mark everything as visible
for (const auto& AvailableCollectionInfo : AvailableCollections)
{
AddVisibleCollection(AvailableCollectionInfo.Value);
}
}
else
{
TArray<TSharedRef<FCollectionItem>> CollectionsToExpandTo;
// Test everything against the filter - a visible child needs to make sure its parents are also marked as visible
for (const auto& AvailableCollectionInfo : AvailableCollections)
{
const TSharedPtr<FCollectionItem>& CollectionItem = AvailableCollectionInfo.Value;
if (CollectionItemTextFilter->PassesFilter(*CollectionItem))
{
AddVisibleCollectionRecursive(CollectionItem);
CollectionsToExpandTo.Add(CollectionItem.ToSharedRef());
}
}
// Make sure all matching items have their parents expanded so they can be seen
for (const TSharedRef<FCollectionItem>& CollectionItem : CollectionsToExpandTo)
{
ExpandParentItems(CollectionItem);
}
}
VisibleRootCollectionItems.Sort(FCollectionItem::FCompareFCollectionItemByName());
CollectionTreePtr->RequestTreeRefresh();
}
void SCollectionView::SetCollectionsSearchFilterText( const FText& InSearchText, TArray<FText>& OutErrors )
{
CollectionItemTextFilter->SetRawFilterText( InSearchText );
const FText ErrorText = CollectionItemTextFilter->GetFilterErrorText();
if (!ErrorText.IsEmpty())
{
OutErrors.Add(ErrorText);
}
}
FText SCollectionView::GetCollectionsSearchFilterText() const
{
return CollectionItemTextFilter->GetRawFilterText();
}
void SCollectionView::SetSelectedCollections(const TArray<FCollectionNameType>& CollectionsToSelect, const bool bEnsureVisible)
{
// Prevent the selection changed delegate since the invoking code requested it
FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) );
// Clear the selection to start, then add the selected items as they are found
CollectionTreePtr->ClearSelection();
for (const FCollectionNameType& CollectionToSelect : CollectionsToSelect)
{
TSharedPtr<FCollectionItem> CollectionItemToSelect = AvailableCollections.FindRef(CollectionToSelect);
if (CollectionItemToSelect.IsValid())
{
if (bEnsureVisible)
{
ExpandParentItems(CollectionItemToSelect.ToSharedRef());
CollectionTreePtr->RequestScrollIntoView(CollectionItemToSelect);
}
CollectionTreePtr->SetItemSelection(CollectionItemToSelect, true);
// If the selected collection doesn't pass our current filter, we need to clear it
if (bEnsureVisible && !CollectionItemTextFilter->PassesFilter(*CollectionItemToSelect))
{
SearchPtr->ClearSearch();
}
}
}
}
void SCollectionView::SetExpandedCollections(const TArray<FCollectionNameType>& CollectionsToExpand)
{
// Clear the expansion to start, then add the expanded items as they are found
CollectionTreePtr->ClearExpandedItems();
for (const FCollectionNameType& CollectionToExpand : CollectionsToExpand)
{
TSharedPtr<FCollectionItem> CollectionItemToExpand = AvailableCollections.FindRef(CollectionToExpand);
if (CollectionItemToExpand.IsValid())
{
CollectionTreePtr->SetItemExpansion(CollectionItemToExpand, true);
}
}
}
void SCollectionView::ClearSelection()
{
// Prevent the selection changed delegate since the invoking code requested it
FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) );
// Clear the selection to start, then add the selected paths as they are found
CollectionTreePtr->ClearSelection();
}
TArray<FCollectionNameType> SCollectionView::GetSelectedCollections() const
{
TArray<FCollectionNameType> RetArray;
TArray<TSharedPtr<FCollectionItem>> Items = CollectionTreePtr->GetSelectedItems();
for ( int32 ItemIdx = 0; ItemIdx < Items.Num(); ++ItemIdx )
{
const TSharedPtr<FCollectionItem>& Item = Items[ItemIdx];
RetArray.Add(FCollectionNameType(Item->CollectionName, Item->CollectionType));
}
return RetArray;
}
void SCollectionView::SetSelectedAssetPaths(const TArray<FSoftObjectPath>& SelectedAssets)
{
if ( QuickAssetManagement.IsValid() )
{
QuickAssetManagement->SetCurrentAssetPaths(SelectedAssets);
}
}
void SCollectionView::ApplyHistoryData( const FHistoryData& History )
{
// Prevent the selection changed delegate because it would add more history when we are just setting a state
FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) );
CollectionTreePtr->ClearSelection();
for (const FCollectionNameType& HistoryCollection : History.SourcesData.Collections)
{
TSharedPtr<FCollectionItem> CollectionHistoryItem = AvailableCollections.FindRef(HistoryCollection);
if (CollectionHistoryItem.IsValid())
{
ExpandParentItems(CollectionHistoryItem.ToSharedRef());
CollectionTreePtr->RequestScrollIntoView(CollectionHistoryItem);
CollectionTreePtr->SetItemSelection(CollectionHistoryItem, true);
}
}
}
void SCollectionView::SaveSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString) const
{
auto SaveCollectionsArrayToIni = [&](const FString& InSubKey, const TArray<TSharedPtr<FCollectionItem>>& InCollectionItems)
{
FString CollectionsString;
for (const TSharedPtr<FCollectionItem>& CollectionItem : InCollectionItems)
{
if (CollectionsString.Len() > 0)
{
CollectionsString += TEXT(",");
}
CollectionsString += CollectionItem->CollectionName.ToString();
CollectionsString += TEXT("?");
CollectionsString += FString::FromInt(CollectionItem->CollectionType);
}
GConfig->SetString(*IniSection, *(SettingsString + InSubKey), *CollectionsString, IniFilename);
};
SaveCollectionsArrayToIni(TEXT(".SelectedCollections"), CollectionTreePtr->GetSelectedItems());
{
TSet<TSharedPtr<FCollectionItem>> ExpandedCollectionItems;
CollectionTreePtr->GetExpandedItems(ExpandedCollectionItems);
SaveCollectionsArrayToIni(TEXT(".ExpandedCollections"), ExpandedCollectionItems.Array());
}
}
void SCollectionView::LoadSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString)
{
auto LoadCollectionsArrayFromIni = [&](const FString& InSubKey) -> TArray<FCollectionNameType>
{
TArray<FCollectionNameType> RetCollectionsArray;
FString CollectionsArrayString;
if (GConfig->GetString(*IniSection, *(SettingsString + InSubKey), CollectionsArrayString, IniFilename))
{
TArray<FString> CollectionStrings;
CollectionsArrayString.ParseIntoArray(CollectionStrings, TEXT(","), /*bCullEmpty*/true);
for (const FString& CollectionString : CollectionStrings)
{
FString CollectionName;
FString CollectionTypeString;
if (CollectionString.Split(TEXT("?"), &CollectionName, &CollectionTypeString))
{
const int32 CollectionType = FCString::Atoi(*CollectionTypeString);
if (CollectionType >= 0 && CollectionType < ECollectionShareType::CST_All)
{
RetCollectionsArray.Add(FCollectionNameType(FName(*CollectionName), ECollectionShareType::Type(CollectionType)));
}
}
}
}
return RetCollectionsArray;
};
// Selected Collections
TArray<FCollectionNameType> NewSelectedCollections = LoadCollectionsArrayFromIni(TEXT(".SelectedCollections"));
if (NewSelectedCollections.Num() > 0)
{
SetSelectedCollections(NewSelectedCollections);
const TArray<TSharedPtr<FCollectionItem>> SelectedCollectionItems = CollectionTreePtr->GetSelectedItems();
if (SelectedCollectionItems.Num() > 0)
{
CollectionSelectionChanged(SelectedCollectionItems[0], ESelectInfo::Direct);
}
}
// Expanded Collections
TArray<FCollectionNameType> NewExpandedCollections = LoadCollectionsArrayFromIni(TEXT(".ExpandedCollections"));
if (NewExpandedCollections.Num() > 0)
{
SetExpandedCollections(NewExpandedCollections);
}
}
void SCollectionView::Tick( const FGeometry& AllottedGeometry, const double InCurrentTime, const float InDeltaTime )
{
SCompoundWidget::Tick(AllottedGeometry, InCurrentTime, InDeltaTime);
if (bQueueCollectionItemsUpdate)
{
bQueueCollectionItemsUpdate = false;
UpdateCollectionItems();
}
if (bQueueSCCRefresh && FCollectionManagerModule::IsModuleAvailable() && ISourceControlModule::Get().IsEnabled())
{
bQueueSCCRefresh = false;
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
TArray<FString> CollectionFilesToRefresh;
for (const auto& AvailableCollectionInfo : AvailableCollections)
{
FCollectionStatusInfo StatusInfo;
if (CollectionManagerModule.Get().GetCollectionStatusInfo(AvailableCollectionInfo.Value->CollectionName, AvailableCollectionInfo.Value->CollectionType, StatusInfo))
{
if (StatusInfo.bUseSCC && StatusInfo.SCCState.IsValid() && StatusInfo.SCCState->IsSourceControlled())
{
CollectionFilesToRefresh.Add(StatusInfo.SCCState->GetFilename());
}
}
}
if (CollectionFilesToRefresh.Num() > 0)
{
ISourceControlModule::Get().QueueStatusUpdate(CollectionFilesToRefresh);
}
}
}
FReply SCollectionView::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent )
{
if( Commands->ProcessCommandBindings( InKeyEvent ) )
{
return FReply::Handled();
}
return FReply::Unhandled();
}
void SCollectionView::OnDragEnter( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
ValidateDragDropOnCollectionTree(MyGeometry, DragDropEvent, bDraggedOver); // updates bDraggedOver
}
void SCollectionView::OnDragLeave( const FDragDropEvent& DragDropEvent )
{
bDraggedOver = false;
}
FReply SCollectionView::OnDragOver( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
ValidateDragDropOnCollectionTree(MyGeometry, DragDropEvent, bDraggedOver); // updates bDraggedOver
return (bDraggedOver) ? FReply::Handled() : FReply::Unhandled();
}
FReply SCollectionView::OnDrop( const FGeometry& MyGeometry, const FDragDropEvent& DragDropEvent )
{
if (ValidateDragDropOnCollectionTree(MyGeometry, DragDropEvent, bDraggedOver)) // updates bDraggedOver
{
bDraggedOver = false;
return HandleDragDropOnCollectionTree(MyGeometry, DragDropEvent);
}
if (bDraggedOver)
{
// We were able to handle this operation, but could not due to another error - still report this drop as handled so it doesn't fall through to other widgets
bDraggedOver = false;
return FReply::Handled();
}
return FReply::Unhandled();
}
void SCollectionView::MakeSaveDynamicCollectionMenu(FText InQueryString, FSimpleDelegate OnSaveSearchClicked)
{
// Get all menu extenders for this context menu from the content browser module
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>( TEXT("ContentBrowser") );
TArray<FContentBrowserMenuExtender> MenuExtenderDelegates = ContentBrowserModule.GetAllCollectionViewContextMenuExtenders();
TArray<TSharedPtr<FExtender>> Extenders;
for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i)
{
if (MenuExtenderDelegates[i].IsBound())
{
Extenders.Add(MenuExtenderDelegates[i].Execute());
}
}
TSharedPtr<FExtender> MenuExtender = FExtender::Combine(Extenders);
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, NULL, MenuExtender, true);
CollectionContextMenu->UpdateProjectSourceControl();
if(OnSaveSearchClicked.IsBound())
{
MakeSaveSearchMenu(MenuBuilder, OnSaveSearchClicked);
}
CollectionContextMenu->MakeSaveDynamicCollectionSubMenu(MenuBuilder, InQueryString);
FWidgetPath WidgetPath;
if (FSlateApplication::Get().GeneratePathToWidgetUnchecked(AsShared(), WidgetPath, EVisibility::All)) // since the collection window can be hidden, we need to manually search the path with a EVisibility::All instead of the default EVisibility::Visible
{
FSlateApplication::Get().PushMenu(
AsShared(),
WidgetPath,
MenuBuilder.MakeWidget(),
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect(FPopupTransitionEffect::TopMenu)
);
}
}
void SCollectionView::MakeSaveSearchMenu(FMenuBuilder& InMenuBuilder, FSimpleDelegate OnSaveSearchClicked) const
{
InMenuBuilder.BeginSection("ContentBrowserSaveSearch", LOCTEXT("ContentBrowserCreateFilterMenuHeading", "Create Filter"));
InMenuBuilder.AddMenuEntry(
LOCTEXT("ContentBrowserSaveAsCustomFilter", "Save as Custom Filter"),
LOCTEXT("ContentBrowserSaveAsCustomFilterTooltip", "Save the current search text as a custom filter in the filter bar"),
FSlateIcon(),
FUIAction(OnSaveSearchClicked)
);
InMenuBuilder.EndSection();
}
FReply SCollectionView::OnAddCollectionClicked()
{
MakeAddCollectionMenu(AsShared());
return FReply::Handled();
}
bool SCollectionView::ShouldAllowSelectionChangedDelegate() const
{
return PreventSelectionChangedDelegateCount == 0;
}
void SCollectionView::MakeAddCollectionMenu(TSharedRef<SWidget> MenuParent)
{
// Get all menu extenders for this context menu from the content browser module
FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked<FContentBrowserModule>( TEXT("ContentBrowser") );
TArray<FContentBrowserMenuExtender> MenuExtenderDelegates = ContentBrowserModule.GetAllCollectionViewContextMenuExtenders();
TArray<TSharedPtr<FExtender>> Extenders;
for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i)
{
if (MenuExtenderDelegates[i].IsBound())
{
Extenders.Add(MenuExtenderDelegates[i].Execute());
}
}
TSharedPtr<FExtender> MenuExtender = FExtender::Combine(Extenders);
FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, NULL, MenuExtender, true);
CollectionContextMenu->UpdateProjectSourceControl();
CollectionContextMenu->MakeNewCollectionSubMenu(MenuBuilder, ECollectionStorageMode::Static, SCollectionView::FCreateCollectionPayload());
FSlateApplication::Get().PushMenu(
MenuParent,
FWidgetPath(),
MenuBuilder.MakeWidget(),
FSlateApplication::Get().GetCursorPos(),
FPopupTransitionEffect( FPopupTransitionEffect::TopMenu )
);
}
void SCollectionView::CreateCollectionItem( ECollectionShareType::Type CollectionType, ECollectionStorageMode::Type StorageMode, const FCreateCollectionPayload& InCreationPayload )
{
if ( ensure(CollectionType != ECollectionShareType::CST_All) )
{
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
const FName BaseCollectionName = *LOCTEXT("NewCollectionName", "NewCollection").ToString();
FName CollectionName;
CollectionManagerModule.Get().CreateUniqueCollectionName(BaseCollectionName, ECollectionShareType::CST_All, CollectionName);
TSharedPtr<FCollectionItem> NewItem = MakeShareable(new FCollectionItem(CollectionName, CollectionType));
NewItem->StorageMode = StorageMode;
// Adding a new collection now, so clear any filter we may have applied
SearchPtr->ClearSearch();
if ( InCreationPayload.ParentCollection.IsSet() )
{
TSharedPtr<FCollectionItem> ParentCollectionItem = AvailableCollections.FindRef(InCreationPayload.ParentCollection.GetValue());
if ( ParentCollectionItem.IsValid() )
{
ParentCollectionItem->ChildCollections.Add(NewItem);
NewItem->ParentCollection = ParentCollectionItem;
// Make sure the parent is expanded so we can see its newly added child item
CollectionTreePtr->SetItemExpansion(ParentCollectionItem, true);
}
}
// Mark the new collection for rename and that it is new so it will be created upon successful rename
NewItem->bRenaming = true;
NewItem->bNewCollection = true;
NewItem->OnCollectionCreatedEvent = InCreationPayload.OnCollectionCreatedEvent;
AvailableCollections.Add( FCollectionNameType(NewItem->CollectionName, NewItem->CollectionType), NewItem );
UpdateFilteredCollectionItems();
CollectionTreePtr->RequestScrollIntoView(NewItem);
CollectionTreePtr->SetSelection( NewItem );
}
}
void SCollectionView::RenameCollectionItem( const TSharedPtr<FCollectionItem>& ItemToRename )
{
if ( ensure(ItemToRename.IsValid()) )
{
ItemToRename->bRenaming = true;
CollectionTreePtr->RequestScrollIntoView(ItemToRename);
}
}
void SCollectionView::DeleteCollectionItems( const TArray<TSharedPtr<FCollectionItem>>& ItemsToDelete )
{
if (ItemsToDelete.Num() == 0)
{
return;
}
// Before we delete anything (as this will trigger a tree update) we need to work out what our new selection should be in the case that
// all of the selected items are removed
const TArray<TSharedPtr<FCollectionItem>> PreviouslySelectedItems = CollectionTreePtr->GetSelectedItems();
// Get the first selected item that will be deleted so we can find a suitable new selection
TSharedPtr<FCollectionItem> FirstSelectedItemDeleted;
for (const auto& ItemToDelete : ItemsToDelete)
{
if (PreviouslySelectedItems.Contains(ItemToDelete))
{
FirstSelectedItemDeleted = ItemToDelete;
break;
}
}
// Build up an array of potential new selections (in the case that we're deleting everything that's selected)
// Earlier items should be considered first, we base this list on the first selected item that will be deleted, and include previous siblings, and then all parents and roots
TArray<FCollectionNameType> PotentialNewSelections;
if (FirstSelectedItemDeleted.IsValid())
{
TSharedPtr<FCollectionItem> RootSelectedItemDeleted = FirstSelectedItemDeleted;
TSharedPtr<FCollectionItem> ParentCollectionItem = FirstSelectedItemDeleted->ParentCollection.Pin();
if (ParentCollectionItem.IsValid())
{
// Add all the siblings until we find the item that will be deleted
for (const auto& ChildItemWeakPtr : ParentCollectionItem->ChildCollections)
{
TSharedPtr<FCollectionItem> ChildItem = ChildItemWeakPtr.Pin();
if (ChildItem.IsValid())
{
if (ChildItem == FirstSelectedItemDeleted)
{
break;
}
// We add siblings as the start, as the closest sibling should be the first match
PotentialNewSelections.Insert(FCollectionNameType(ChildItem->CollectionName, ChildItem->CollectionType), 0);
}
}
// Now add this parent, and all other parents too
do
{
PotentialNewSelections.Add(FCollectionNameType(ParentCollectionItem->CollectionName, ParentCollectionItem->CollectionType));
RootSelectedItemDeleted = ParentCollectionItem;
ParentCollectionItem = ParentCollectionItem->ParentCollection.Pin();
}
while (ParentCollectionItem.IsValid());
}
if (RootSelectedItemDeleted.IsValid())
{
// Add all the root level items before this one
const int32 InsertionPoint = PotentialNewSelections.Num();
for (const auto& RootItem : VisibleRootCollectionItems)
{
if (RootItem == RootSelectedItemDeleted)
{
break;
}
// Add each root item at the insertion point, as the closest item should be a better match
PotentialNewSelections.Insert(FCollectionNameType(RootItem->CollectionName, RootItem->CollectionType), InsertionPoint);
}
}
}
// Delete all given collections
int32 NumSelectedItemsDeleted = 0;
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
for (const TSharedPtr<FCollectionItem>& ItemToDelete : ItemsToDelete)
{
if (CollectionManagerModule.Get().DestroyCollection(ItemToDelete->CollectionName, ItemToDelete->CollectionType))
{
if (PreviouslySelectedItems.Contains(ItemToDelete))
{
++NumSelectedItemsDeleted;
}
}
else
{
// Display a warning
const FVector2D& CursorPos = FSlateApplication::Get().GetCursorPos();
FSlateRect MessageAnchor(CursorPos.X, CursorPos.Y, CursorPos.X, CursorPos.Y);
ContentBrowserUtils::DisplayMessage(
FText::Format( LOCTEXT("CollectionDestroyFailed", "Failed to destroy collection. {0}"), CollectionManagerModule.Get().GetLastError() ),
MessageAnchor,
CollectionTreePtr.ToSharedRef()
);
}
}
// DestroyCollection will have triggered a notification that will have updated the tree, we now need to apply a suitable selection...
// Did this delete change the list of selected items?
if (NumSelectedItemsDeleted > 0 || PreviouslySelectedItems.Num() == 0)
{
// If we removed everything that was selected, we need to try and find a suitable replacement...
if (NumSelectedItemsDeleted >= PreviouslySelectedItems.Num() && VisibleCollections.Num() > 1)
{
// Include the first visible item as an absolute last resort should everything else suitable have been removed from the tree
PotentialNewSelections.Add(*VisibleCollections.CreateConstIterator());
// Check the potential new selections array and try and select the first one that's still visible in the tree
TArray<FCollectionNameType> NewItemSelection;
for (const FCollectionNameType& PotentialNewSelection : PotentialNewSelections)
{
if (VisibleCollections.Contains(PotentialNewSelection))
{
NewItemSelection.Add(PotentialNewSelection);
break;
}
}
SetSelectedCollections(NewItemSelection, true);
}
// Broadcast the new selection
const TArray<TSharedPtr<FCollectionItem>> UpdatedSelectedItems = CollectionTreePtr->GetSelectedItems();
CollectionSelectionChanged((UpdatedSelectedItems.Num() > 0) ? UpdatedSelectedItems[0] : nullptr, ESelectInfo::Direct);
}
}
EVisibility SCollectionView::GetCollectionTreeVisibility() const
{
return AvailableCollections.Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed;
}
EVisibility SCollectionView::GetHeaderVisibility() const
{
return IsDocked.Get() ? EVisibility::Collapsed : EVisibility::SelfHitTestInvisible;
}
const FSlateBrush* SCollectionView::GetCollectionViewDropTargetBorder() const
{
return bDraggedOver ? FAppStyle::GetBrush("ContentBrowser.CollectionTreeDragDropBorder") : FAppStyle::GetBrush("NoBorder");
}
TSharedRef<ITableRow> SCollectionView::GenerateCollectionRow( TSharedPtr<FCollectionItem> CollectionItem, const TSharedRef<STableViewBase>& OwnerTable )
{
check(CollectionItem.IsValid());
// Only bind the check box callbacks if we're allowed to show check boxes
TAttribute<bool> IsCollectionCheckBoxEnabledAttribute;
TAttribute<ECheckBoxState> IsCollectionCheckedAttribute;
FOnCheckStateChanged OnCollectionCheckStateChangedDelegate;
if ( QuickAssetManagement.IsValid() )
{
// Can only manage assets for static collections
if (CollectionItem->StorageMode == ECollectionStorageMode::Static)
{
IsCollectionCheckBoxEnabledAttribute.Bind(TAttribute<bool>::FGetter::CreateSP(this, &SCollectionView::IsCollectionCheckBoxEnabled, CollectionItem));
IsCollectionCheckedAttribute.Bind(TAttribute<ECheckBoxState>::FGetter::CreateSP(this, &SCollectionView::IsCollectionChecked, CollectionItem));
OnCollectionCheckStateChangedDelegate.BindSP(this, &SCollectionView::OnCollectionCheckStateChanged, CollectionItem);
}
else
{
IsCollectionCheckBoxEnabledAttribute.Bind(TAttribute<bool>::FGetter::CreateLambda([]{ return false; }));
IsCollectionCheckedAttribute.Bind(TAttribute<ECheckBoxState>::FGetter::CreateLambda([]{ return ECheckBoxState::Unchecked; }));
}
}
TSharedPtr< SAssetTagItemTableRow< TSharedPtr<FCollectionItem> > > TableRow = SNew( SAssetTagItemTableRow< TSharedPtr<FCollectionItem> >, OwnerTable )
.OnDragDetected(this, &SCollectionView::OnCollectionDragDetected);
TSharedPtr<SCollectionTreeItem> CollectionTreeItem;
TableRow->SetContent
(
SAssignNew(CollectionTreeItem, SCollectionTreeItem)
.ParentWidget(SharedThis(this))
.CollectionItem(CollectionItem)
.OnNameChangeCommit(this, &SCollectionView::CollectionNameChangeCommit)
.OnVerifyRenameCommit(this, &SCollectionView::CollectionVerifyRenameCommit)
.OnValidateDragDrop(this, &SCollectionView::ValidateDragDropOnCollectionItem)
.OnHandleDragDrop(this, &SCollectionView::HandleDragDropOnCollectionItem)
.IsSelected(TableRow.Get(), &STableRow< TSharedPtr<FCollectionItem> >::IsSelectedExclusively)
.IsReadOnly(this, &SCollectionView::IsCollectionNameReadOnly)
.HighlightText(this, &SCollectionView::GetCollectionsSearchFilterText)
.IsCheckBoxEnabled(IsCollectionCheckBoxEnabledAttribute)
.IsCollectionChecked(IsCollectionCheckedAttribute)
.OnCollectionCheckStateChanged(OnCollectionCheckStateChangedDelegate)
);
TableRow->SetIsDropTarget(MakeAttributeSP(CollectionTreeItem.Get(), &SCollectionTreeItem::IsDraggedOver));
return TableRow.ToSharedRef();
}
void SCollectionView::GetCollectionItemChildren( TSharedPtr<FCollectionItem> InParentItem, TArray< TSharedPtr<FCollectionItem> >& OutChildItems ) const
{
for (const auto& ChildItemWeakPtr : InParentItem->ChildCollections)
{
TSharedPtr<FCollectionItem> ChildItem = ChildItemWeakPtr.Pin();
if (ChildItem.IsValid() && VisibleCollections.Contains(FCollectionNameType(ChildItem->CollectionName, ChildItem->CollectionType)))
{
OutChildItems.Add(ChildItem);
}
}
OutChildItems.Sort(FCollectionItem::FCompareFCollectionItemByName());
}
FReply SCollectionView::OnCollectionDragDetected(const FGeometry& Geometry, const FPointerEvent& MouseEvent)
{
if (bAllowCollectionDrag && MouseEvent.IsMouseButtonDown(EKeys::LeftMouseButton))
{
const TArray<FCollectionNameType> SelectedCollections = GetSelectedCollections();
if (SelectedCollections.Num() > 0)
{
TSharedRef<FCollectionDragDropOp> DragDropOp = FCollectionDragDropOp::New(SelectedCollections);
CurrentCollectionDragDropOp = DragDropOp;
return FReply::Handled().BeginDragDrop(DragDropOp);
}
}
return FReply::Unhandled();
}
bool SCollectionView::ValidateDragDropOnCollectionTree(const FGeometry& Geometry, const FDragDropEvent& DragDropEvent, bool& OutIsKnownDragOperation)
{
OutIsKnownDragOperation = false;
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return false;
}
if (Operation->IsOfType<FCollectionDragDropOp>())
{
OutIsKnownDragOperation = true;
return true;
}
return false;
}
FReply SCollectionView::HandleDragDropOnCollectionTree(const FGeometry& Geometry, const FDragDropEvent& DragDropEvent)
{
// Should have already called ValidateDragDropOnCollectionTree prior to calling this...
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
check(Operation.IsValid());
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
if (Operation->IsOfType<FCollectionDragDropOp>())
{
TSharedPtr<FCollectionDragDropOp> DragDropOp = StaticCastSharedPtr<FCollectionDragDropOp>(Operation);
// Reparent all of the collections in the drag drop so that they are root level items
for (const FCollectionNameType& NewChildCollection : DragDropOp->Collections)
{
if (!CollectionManagerModule.Get().ReparentCollection(
NewChildCollection.Name, NewChildCollection.Type,
NAME_None, ECollectionShareType::CST_All
))
{
ContentBrowserUtils::DisplayMessage(CollectionManagerModule.Get().GetLastError(), Geometry.GetLayoutBoundingRect(), SharedThis(this));
}
}
return FReply::Handled();
}
return FReply::Unhandled();
}
bool SCollectionView::ValidateDragDropOnCollectionItem(TSharedRef<FCollectionItem> CollectionItem, const FGeometry& Geometry, const FDragDropEvent& DragDropEvent, bool& OutIsKnownDragOperation)
{
OutIsKnownDragOperation = false;
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
if (!Operation.IsValid())
{
return false;
}
bool bIsValidDrag = false;
TOptional<EMouseCursor::Type> NewDragCursor;
if (Operation->IsOfType<FCollectionDragDropOp>())
{
TSharedPtr<FCollectionDragDropOp> DragDropOp = StaticCastSharedPtr<FCollectionDragDropOp>(Operation);
OutIsKnownDragOperation = true;
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
bIsValidDrag = true;
for (const FCollectionNameType& PotentialChildCollection : DragDropOp->Collections)
{
bIsValidDrag = CollectionManagerModule.Get().IsValidParentCollection(
PotentialChildCollection.Name, PotentialChildCollection.Type,
CollectionItem->CollectionName, CollectionItem->CollectionType
);
if (!bIsValidDrag)
{
DragDropOp->SetToolTip(CollectionManagerModule.Get().GetLastError(), FAppStyle::GetBrush(TEXT("Graph.ConnectorFeedback.Error")));
break;
}
}
// If we are dragging over a child collection item, then this view as a whole should not be marked as dragged over
bDraggedOver = false;
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
OutIsKnownDragOperation = true;
bIsValidDrag = DragDropOp->HasAssets();
}
// Set the default slashed circle if this drag is invalid and a drag operation hasn't set NewDragCursor to something custom
if (!bIsValidDrag && !NewDragCursor.IsSet())
{
NewDragCursor = EMouseCursor::SlashedCircle;
}
Operation->SetCursorOverride(NewDragCursor);
return bIsValidDrag;
}
FReply SCollectionView::HandleDragDropOnCollectionItem(TSharedRef<FCollectionItem> CollectionItem, const FGeometry& Geometry, const FDragDropEvent& DragDropEvent)
{
// Should have already called ValidateDragDropOnCollectionItem prior to calling this...
TSharedPtr<FDragDropOperation> Operation = DragDropEvent.GetOperation();
check(Operation.IsValid());
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
if (Operation->IsOfType<FCollectionDragDropOp>())
{
TSharedPtr<FCollectionDragDropOp> DragDropOp = StaticCastSharedPtr<FCollectionDragDropOp>(Operation);
// Make sure our drop item is marked as expanded so that we'll be able to see the newly added children
CollectionTreePtr->SetItemExpansion(CollectionItem, true);
// Reparent all of the collections in the drag drop so that they are our immediate children
for (const FCollectionNameType& NewChildCollection : DragDropOp->Collections)
{
if (!CollectionManagerModule.Get().ReparentCollection(
NewChildCollection.Name, NewChildCollection.Type,
CollectionItem->CollectionName, CollectionItem->CollectionType
))
{
ContentBrowserUtils::DisplayMessage(CollectionManagerModule.Get().GetLastError(), Geometry.GetLayoutBoundingRect(), SharedThis(this));
}
}
return FReply::Handled();
}
else if (Operation->IsOfType<FAssetDragDropOp>())
{
TSharedPtr<FAssetDragDropOp> DragDropOp = StaticCastSharedPtr<FAssetDragDropOp>(Operation);
const TArray<FAssetData>& DroppedAssets = DragDropOp->GetAssets();
TArray<FSoftObjectPath> ObjectPaths;
ObjectPaths.Reserve(DroppedAssets.Num());
for (const FAssetData& AssetData : DroppedAssets)
{
ObjectPaths.Add(AssetData.GetSoftObjectPath());
}
int32 NumAdded = 0;
FText Message;
PRAGMA_DISABLE_DEPRECATION_WARNINGS
if (CollectionManagerModule.Get().AddToCollection(CollectionItem->CollectionName, CollectionItem->CollectionType, UE::SoftObjectPath::Private::ConvertSoftObjectPaths(ObjectPaths), &NumAdded))
PRAGMA_ENABLE_DEPRECATION_WARNINGS
{
if (DroppedAssets.Num() == 1)
{
FFormatNamedArguments Args;
Args.Add(TEXT("AssetName"), FText::FromName(DroppedAssets[0].AssetName));
Args.Add(TEXT("CollectionName"), FText::FromName(CollectionItem->CollectionName));
Message = FText::Format(LOCTEXT("CollectionAssetAdded", "Added {AssetName} to {CollectionName}"), Args);
}
else
{
FFormatNamedArguments Args;
Args.Add(TEXT("Number"), NumAdded);
Args.Add(TEXT("CollectionName"), FText::FromName(CollectionItem->CollectionName));
Message = FText::Format(LOCTEXT("CollectionAssetsAdded", "Added {Number} asset(s) to {CollectionName}"), Args);
}
}
else
{
Message = CollectionManagerModule.Get().GetLastError();
}
// Added items to the collection or failed. Either way, display the message.
ContentBrowserUtils::DisplayMessage(Message, Geometry.GetLayoutBoundingRect(), SharedThis(this));
return FReply::Handled();
}
return FReply::Unhandled();
}
void SCollectionView::ExpandParentItems(const TSharedRef<FCollectionItem>& InCollectionItem)
{
for (TSharedPtr<FCollectionItem> CollectionItemToExpand = InCollectionItem->ParentCollection.Pin();
CollectionItemToExpand.IsValid();
CollectionItemToExpand = CollectionItemToExpand->ParentCollection.Pin()
)
{
CollectionTreePtr->SetItemExpansion(CollectionItemToExpand, true);
}
}
TSharedPtr<SWidget> SCollectionView::MakeCollectionTreeContextMenu()
{
if ( !bAllowRightClickMenu )
{
return NULL;
}
return CollectionContextMenu->MakeCollectionTreeContextMenu(Commands);
}
bool SCollectionView::IsCollectionCheckBoxEnabled( TSharedPtr<FCollectionItem> CollectionItem ) const
{
return QuickAssetManagement.IsValid() && QuickAssetManagement->IsCollectionEnabled(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
}
ECheckBoxState SCollectionView::IsCollectionChecked( TSharedPtr<FCollectionItem> CollectionItem ) const
{
if ( QuickAssetManagement.IsValid() )
{
return QuickAssetManagement->GetCollectionCheckState(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
}
return ECheckBoxState::Unchecked;
}
void SCollectionView::OnCollectionCheckStateChanged( ECheckBoxState NewState, TSharedPtr<FCollectionItem> CollectionItem )
{
if ( QuickAssetManagement.IsValid() )
{
FCollectionNameType CollectionKey(CollectionItem->CollectionName, CollectionItem->CollectionType);
if (QuickAssetManagement->GetCollectionCheckState(CollectionKey) == ECheckBoxState::Checked)
{
QuickAssetManagement->RemoveCurrentAssetsFromCollection(CollectionKey);
}
else
{
QuickAssetManagement->AddCurrentAssetsToCollection(CollectionKey);
}
}
}
void SCollectionView::CollectionSelectionChanged( TSharedPtr< FCollectionItem > CollectionItem, ESelectInfo::Type /*SelectInfo*/ )
{
if ( ShouldAllowSelectionChangedDelegate() && OnCollectionSelected.IsBound() )
{
if ( CollectionItem.IsValid() )
{
OnCollectionSelected.Execute(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
}
else
{
OnCollectionSelected.Execute(FCollectionNameType(NAME_None, ECollectionShareType::CST_All));
}
}
}
void SCollectionView::CollectionItemScrolledIntoView( TSharedPtr<FCollectionItem> CollectionItem, const TSharedPtr<ITableRow>& Widget )
{
if ( CollectionItem->bRenaming && Widget.IsValid() && Widget->GetContent().IsValid() )
{
CollectionItem->OnRenamedRequestEvent.Broadcast();
}
}
bool SCollectionView::IsCollectionNameReadOnly() const
{
// We can't rename collections while they're being dragged
TSharedPtr<FCollectionDragDropOp> DragDropOp = CurrentCollectionDragDropOp.Pin();
if (DragDropOp.IsValid())
{
TArray<TSharedPtr<FCollectionItem>> SelectedCollectionItems = CollectionTreePtr->GetSelectedItems();
for (const auto& SelectedCollectionItem : SelectedCollectionItems)
{
if (DragDropOp->Collections.Contains(FCollectionNameType(SelectedCollectionItem->CollectionName, SelectedCollectionItem->CollectionType)))
{
return true;
}
}
}
CollectionContextMenu->UpdateProjectSourceControl();
return !CollectionContextMenu->CanRenameSelectedCollections();
}
bool SCollectionView::CollectionNameChangeCommit( const TSharedPtr< FCollectionItem >& CollectionItem, const FString& NewName, bool bChangeConfirmed, FText& OutWarningMessage )
{
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
// If new name is empty, set it back to the original name
const FName NewNameFinal( NewName.IsEmpty() ? CollectionItem->CollectionName : FName(*NewName) );
if ( CollectionItem->bNewCollection )
{
CollectionItem->bNewCollection = false;
// Cache this here as CreateCollection will invalidate the current parent pointer
TOptional<FCollectionNameType> NewCollectionParentKey;
TSharedPtr<FCollectionItem> ParentCollectionItem = CollectionItem->ParentCollection.Pin();
if ( ParentCollectionItem.IsValid() )
{
NewCollectionParentKey = FCollectionNameType(ParentCollectionItem->CollectionName, ParentCollectionItem->CollectionType);
}
// If we canceled the name change when creating a new asset, we want to silently remove it
if ( !bChangeConfirmed )
{
AvailableCollections.Remove(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
UpdateFilteredCollectionItems();
return false;
}
if ( !CollectionManagerModule.Get().CreateCollection(NewNameFinal, CollectionItem->CollectionType, CollectionItem->StorageMode) )
{
// Failed to add the collection, remove it from the list
AvailableCollections.Remove(FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType));
UpdateFilteredCollectionItems();
OutWarningMessage = FText::Format( LOCTEXT("CreateCollectionFailed", "Failed to create the collection. {0}"), CollectionManagerModule.Get().GetLastError());
return false;
}
// Since we're really adding a new collection (as our placeholder item is currently transient), we don't get a rename event from the collections manager
// We'll spoof one here that so that our placeholder tree item is updated with the final name - this will preserve its expansion and selection state
HandleCollectionRenamed(
FCollectionNameType(CollectionItem->CollectionName, CollectionItem->CollectionType),
FCollectionNameType(NewNameFinal, CollectionItem->CollectionType)
);
if ( NewCollectionParentKey.IsSet() )
{
// Try and set the parent correctly (if this fails for any reason, the collection will still be added, but will just appear at the root)
CollectionManagerModule.Get().ReparentCollection(NewNameFinal, CollectionItem->CollectionType, NewCollectionParentKey->Name, NewCollectionParentKey->Type);
}
// Notify anything that cares that this collection has been created now
if ( CollectionItem->OnCollectionCreatedEvent.IsBound())
{
CollectionItem->OnCollectionCreatedEvent.Execute(FCollectionNameType(NewNameFinal, CollectionItem->CollectionType));
CollectionItem->OnCollectionCreatedEvent.Unbind();
}
}
else
{
// If the old name is the same as the new name, just early exit here.
if ( CollectionItem->CollectionName == NewNameFinal )
{
return true;
}
// If the new name doesn't pass our current filter, we need to clear it
if ( !CollectionItemTextFilter->PassesFilter( FCollectionItem(NewNameFinal, CollectionItem->CollectionType) ) )
{
SearchPtr->ClearSearch();
}
// Otherwise perform the rename
if ( !CollectionManagerModule.Get().RenameCollection(CollectionItem->CollectionName, CollectionItem->CollectionType, NewNameFinal, CollectionItem->CollectionType) )
{
// Failed to rename the collection
OutWarningMessage = FText::Format( LOCTEXT("RenameCollectionFailed", "Failed to rename the collection. {0}"), CollectionManagerModule.Get().GetLastError());
return false;
}
}
// At this point CollectionItem is no longer a member of the CollectionItems list (as the list is repopulated by
// UpdateCollectionItems, which is called by a broadcast from CollectionManagerModule::RenameCollection, above).
// So search again for the item by name and type.
auto NewCollectionItemPtr = AvailableCollections.Find( FCollectionNameType(NewNameFinal, CollectionItem->CollectionType) );
// Reselect the path to notify that the selection has changed
{
FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) );
CollectionTreePtr->ClearSelection();
}
// Set the selection
if (NewCollectionItemPtr)
{
const auto& NewCollectionItem = *NewCollectionItemPtr;
CollectionTreePtr->RequestScrollIntoView(NewCollectionItem);
CollectionTreePtr->SetItemSelection(NewCollectionItem, true);
}
return true;
}
bool SCollectionView::CollectionVerifyRenameCommit(const TSharedPtr< FCollectionItem >& CollectionItem, const FString& NewName, const FSlateRect& MessageAnchor, FText& OutErrorMessage)
{
// If the new name is the same as the old name, consider this to be unchanged, and accept it.
if (CollectionItem->CollectionName.ToString() == NewName)
{
return true;
}
FCollectionManagerModule& CollectionManagerModule = FCollectionManagerModule::GetModule();
if (!CollectionManagerModule.Get().IsValidCollectionName(NewName, ECollectionShareType::CST_All))
{
OutErrorMessage = CollectionManagerModule.Get().GetLastError();
return false;
}
return true;
}
#undef LOCTEXT_NAMESPACE