// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "ContentBrowserPCH.h" #include "CollectionViewTypes.h" #include "CollectionContextMenu.h" #include "ObjectTools.h" #include "SourcesViewWidgets.h" #include "ContentBrowserModule.h" #include "SExpandableArea.h" #define LOCTEXT_NAMESPACE "ContentBrowser" void SCollectionView::Construct( const FArguments& InArgs ) { OnCollectionSelected = InArgs._OnCollectionSelected; bAllowCollectionButtons = InArgs._AllowCollectionButtons; bAllowRightClickMenu = InArgs._AllowRightClickMenu; FCollectionManagerModule& CollectionManagerModule = FModuleManager::LoadModuleChecked(TEXT("CollectionManager")); CollectionManagerModule.Get().OnCollectionCreated().AddSP( this, &SCollectionView::HandleCollectionCreated ); CollectionManagerModule.Get().OnCollectionRenamed().AddSP( this, &SCollectionView::HandleCollectionRenamed ); CollectionManagerModule.Get().OnCollectionDestroyed().AddSP( this, &SCollectionView::HandleCollectionDestroyed ); Commands = TSharedPtr< FUICommandList >(new FUICommandList); CollectionContextMenu = MakeShareable(new FCollectionContextMenu( SharedThis(this) )); CollectionContextMenu->BindCommands(Commands); FOnContextMenuOpening CollectionListContextMenuOpening; if ( InArgs._AllowContextMenu ) { CollectionListContextMenuOpening = FOnContextMenuOpening::CreateSP( this, &SCollectionView::MakeCollectionListContextMenu ); } PreventSelectionChangedDelegateCount = 0; TSharedRef< SWidget > HeaderContent = SNew(SHorizontalBox) +SHorizontalBox::Slot() .VAlign(VAlign_Center) .FillWidth(1.f) [ SNew(STextBlock) .Font( FEditorStyle::GetFontStyle("ContentBrowser.SourceTitleFont") ) .Text( LOCTEXT("CollectionsListTitle", "Collections") ) ] +SHorizontalBox::Slot() .AutoWidth() .VAlign(VAlign_Bottom) [ SNew(SButton) .ToolTipText(LOCTEXT("AddCollectionButtonTooltip", "Add a collection.")) .OnClicked(this, &SCollectionView::MakeAddCollectionMenu) .ContentPadding(0) .Visibility(this, &SCollectionView::GetAddCollectionButtonVisibility) [ SNew(SImage) .Image( FEditorStyle::GetBrush("ContentBrowser.AddCollectionButtonIcon") ) ] ]; TSharedRef< SWidget > BodyContent = SNew(SVerticalBox) // Separator +SVerticalBox::Slot() .AutoHeight() [ SNew(SSeparator) ] // Collections list +SVerticalBox::Slot() .FillHeight(1.f) [ SAssignNew(CollectionListPtr, SListView< TSharedPtr >) .ListItemsSource(&CollectionItems) .OnGenerateRow( this, &SCollectionView::GenerateCollectionRow ) .ItemHeight(18) .SelectionMode(ESelectionMode::Multi) .OnSelectionChanged(this, &SCollectionView::CollectionSelectionChanged) .OnContextMenuOpening( CollectionListContextMenuOpening ) .OnItemScrolledIntoView(this, &SCollectionView::CollectionItemScrolledIntoView) .ClearSelectionOnClick(false) .Visibility(this, &SCollectionView::GetCollectionListVisibility) ]; TSharedPtr< SWidget > Content; if ( InArgs._AllowCollapsing ) { Content = SAssignNew(CollectionsExpandableAreaPtr, SExpandableArea) .MaxHeight(200) .BorderImage( FEditorStyle::GetBrush("NoBorder") ) .HeaderContent() [ HeaderContent ] .BodyContent() [ BodyContent ]; } else { Content = SNew( SVerticalBox ) +SVerticalBox::Slot() .AutoHeight() [ HeaderContent ] +SVerticalBox::Slot() .AutoHeight() [ BodyContent ]; } ChildSlot [ Content.ToSharedRef() ]; UpdateCollectionItems(); } void SCollectionView::HandleCollectionCreated( const FCollectionNameType& Collection ) { UpdateCollectionItems(); CollectionListPtr->RequestListRefresh(); } void SCollectionView::HandleCollectionRenamed( const FCollectionNameType& OriginalCollection, const FCollectionNameType& NewCollection ) { UpdateCollectionItems(); CollectionListPtr->RequestListRefresh(); } void SCollectionView::HandleCollectionDestroyed( const FCollectionNameType& Collection ) { UpdateCollectionItems(); CollectionListPtr->RequestListRefresh(); } void SCollectionView::UpdateCollectionItems() { CollectionItems.Empty(); // Load the collection manager module FCollectionManagerModule& CollectionManagerModule = FModuleManager::LoadModuleChecked(TEXT("CollectionManager")); // Get collections of all types for (int32 TypeIdx = 0; TypeIdx < ECollectionShareType::CST_All; ++TypeIdx) { ECollectionShareType::Type CollectionType = ECollectionShareType::Type(TypeIdx); //Never display system collections if ( CollectionType == ECollectionShareType::CST_System ) { continue; } TArray CollectionNames; CollectionManagerModule.Get().GetCollectionNames(CollectionType, CollectionNames); for (int32 CollectionIdx = 0; CollectionIdx < CollectionNames.Num(); ++CollectionIdx) { const FName& CollectionName = CollectionNames[CollectionIdx]; CollectionItems.Add( MakeShareable(new FCollectionItem(CollectionName.ToString(), CollectionType)) ); } } CollectionItems.Sort( FCollectionItem::FCompareFCollectionItemByName() ); } void SCollectionView::SetSelectedCollections(const TArray& CollectionsToSelect) { // Prevent the selection changed delegate since the invoking code requested it FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) ); // Expand the collections area if we are indeed selecting at least one collection if ( CollectionsToSelect.Num() > 0 && CollectionsExpandableAreaPtr.IsValid() ) { CollectionsExpandableAreaPtr->SetExpanded(true); } // Clear the selection to start, then add the selected paths as they are found CollectionListPtr->ClearSelection(); for ( auto CollectionIt = CollectionItems.CreateConstIterator(); CollectionIt; ++CollectionIt ) { const TSharedPtr& Item = *CollectionIt; for ( auto SelectionIt = CollectionsToSelect.CreateConstIterator(); SelectionIt; ++SelectionIt ) { const FCollectionNameType& Selection = *SelectionIt; if ( Item->CollectionName == Selection.Name.ToString() && Item->CollectionType == Selection.Type ) { CollectionListPtr->SetItemSelection(Item, true); CollectionListPtr->RequestScrollIntoView(Item); } } } } 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 CollectionListPtr->ClearSelection(); } TArray SCollectionView::GetSelectedCollections() const { TArray RetArray; TArray> Items = CollectionListPtr->GetSelectedItems(); for ( int32 ItemIdx = 0; ItemIdx < Items.Num(); ++ItemIdx ) { const TSharedPtr& Item = Items[ItemIdx]; new(RetArray) FCollectionNameType(FName(*Item->CollectionName), Item->CollectionType); } return RetArray; } 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) ); CollectionListPtr->ClearSelection(); for ( auto HistoryIt = History.SourcesData.Collections.CreateConstIterator(); HistoryIt; ++HistoryIt) { FString Name = (*HistoryIt).Name.ToString(); ECollectionShareType::Type Type = (*HistoryIt).Type; for ( auto CollectionIt = CollectionItems.CreateConstIterator(); CollectionIt; ++CollectionIt) { if ( (*CollectionIt)->CollectionName == Name && (*CollectionIt)->CollectionType == Type ) { CollectionListPtr->SetItemSelection(*CollectionIt, true); break; } } } } void SCollectionView::SaveSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString) const { FString SelectedCollectionsString; TArray< TSharedPtr > CollectionSelectedItems = CollectionListPtr->GetSelectedItems(); for ( auto CollectionIt = CollectionSelectedItems.CreateConstIterator(); CollectionIt; ++CollectionIt ) { if ( SelectedCollectionsString.Len() > 0 ) { SelectedCollectionsString += TEXT(","); } const TSharedPtr& Collection = *CollectionIt; SelectedCollectionsString += Collection->CollectionName + TEXT("?") + FString::FromInt(Collection->CollectionType); } const bool IsCollectionsExpanded = CollectionsExpandableAreaPtr.IsValid() ? CollectionsExpandableAreaPtr->IsExpanded() : true; GConfig->SetBool(*IniSection, *(SettingsString + TEXT(".CollectionsExpanded")), IsCollectionsExpanded, IniFilename); GConfig->SetString(*IniSection, *(SettingsString + TEXT(".SelectedCollections")), *SelectedCollectionsString, IniFilename); } void SCollectionView::LoadSettings(const FString& IniFilename, const FString& IniSection, const FString& SettingsString) { // Collection expansion state bool bCollectionsExpanded = false; if ( CollectionsExpandableAreaPtr.IsValid() && GConfig->GetBool(*IniSection, *(SettingsString + TEXT(".CollectionsExpanded")), bCollectionsExpanded, IniFilename) ) { CollectionsExpandableAreaPtr->SetExpanded(bCollectionsExpanded); } // Selected Collections FString SelectedCollectionsString; TArray NewSelectedCollections; if ( GConfig->GetString(*IniSection, *(SettingsString + TEXT(".SelectedCollections")), SelectedCollectionsString, IniFilename) ) { TArray NewSelectedCollectionStrings; SelectedCollectionsString.ParseIntoArray(NewSelectedCollectionStrings, TEXT(","), /*bCullEmpty*/true); for ( auto CollectionIt = NewSelectedCollectionStrings.CreateConstIterator(); CollectionIt; ++CollectionIt ) { FString CollectionName; FString CollectionTypeString; if ( (*CollectionIt).Split(TEXT("?"), &CollectionName, &CollectionTypeString) ) { int32 CollectionType = FCString::Atoi(*CollectionTypeString); if ( CollectionType >= 0 && CollectionType < ECollectionShareType::CST_All ) { new(NewSelectedCollections) FCollectionNameType(FName(*CollectionName), ECollectionShareType::Type(CollectionType) ); } } } } if ( NewSelectedCollections.Num() > 0 ) { // Select the collections SetSelectedCollections(NewSelectedCollections); CollectionSelectionChanged( TSharedPtr(), ESelectInfo::Direct ); } } FReply SCollectionView::OnKeyDown( const FGeometry& MyGeometry, const FKeyEvent& InKeyEvent ) { if( Commands->ProcessCommandBindings( InKeyEvent ) ) { return FReply::Handled(); } return FReply::Unhandled(); } bool SCollectionView::ShouldAllowSelectionChangedDelegate() const { return PreventSelectionChangedDelegateCount == 0; } FReply SCollectionView::MakeAddCollectionMenu() { // Get all menu extenders for this context menu from the content browser module FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); TArray MenuExtenderDelegates = ContentBrowserModule.GetAllCollectionViewContextMenuExtenders(); TArray> Extenders; for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) { if (MenuExtenderDelegates[i].IsBound()) { Extenders.Add(MenuExtenderDelegates[i].Execute()); } } TSharedPtr MenuExtender = FExtender::Combine(Extenders); FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, NULL, MenuExtender, true); CollectionContextMenu->UpdateProjectSourceControl(); CollectionContextMenu->MakeNewCollectionSubMenu(MenuBuilder); FSlateApplication::Get().PushMenu( AsShared(), MenuBuilder.MakeWidget(), FSlateApplication::Get().GetCursorPos(), FPopupTransitionEffect( FPopupTransitionEffect::TopMenu ) ); return FReply::Handled(); } EVisibility SCollectionView::GetAddCollectionButtonVisibility() const { return (bAllowCollectionButtons && ( !CollectionsExpandableAreaPtr.IsValid() || CollectionsExpandableAreaPtr->IsExpanded() ) ) ? EVisibility::Visible : EVisibility::Collapsed; } void SCollectionView::CreateCollectionItem( ECollectionShareType::Type CollectionType ) { if ( ensure(CollectionType != ECollectionShareType::CST_All) ) { FCollectionManagerModule& CollectionManagerModule = FModuleManager::Get().LoadModuleChecked("CollectionManager"); const FName BaseCollectionName = *LOCTEXT("NewCollectionName", "NewCollection").ToString(); FName CollectionName; CollectionManagerModule.Get().CreateUniqueCollectionName(BaseCollectionName, CollectionType, CollectionName); TSharedPtr NewItem = MakeShareable(new FCollectionItem(CollectionName.ToString(), CollectionType)); // 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; CollectionItems.Add( NewItem ); CollectionItems.Sort( FCollectionItem::FCompareFCollectionItemByName() ); CollectionListPtr->RequestScrollIntoView(NewItem); CollectionListPtr->SetSelection( NewItem ); } } void SCollectionView::RenameCollectionItem( const TSharedPtr& ItemToRename ) { if ( ensure(ItemToRename.IsValid()) ) { ItemToRename->bRenaming = true; CollectionListPtr->RequestScrollIntoView(ItemToRename); } } void SCollectionView::RemoveCollectionItems( const TArray>& ItemsToRemove ) { TArray> SelectedItems = CollectionListPtr->GetSelectedItems(); // Remove all the items, while keeping track of the number of selected items that were removed. int32 LastSelectedItemIdx = INDEX_NONE; int32 NumSelectedItemsRemoved = 0; for (int32 RemoveIdx = 0; RemoveIdx < ItemsToRemove.Num(); ++RemoveIdx) { const TSharedPtr& ItemToRemove = ItemsToRemove[RemoveIdx]; int32 ItemIdx = INDEX_NONE; if ( CollectionItems.Find(ItemToRemove, ItemIdx) ) { if ( SelectedItems.Contains(ItemToRemove) ) { NumSelectedItemsRemoved++; LastSelectedItemIdx = ItemIdx; } CollectionItems.RemoveAt(ItemIdx); } } FScopedPreventSelectionChangedDelegate DelegatePrevention(SharedThis(this)); CollectionListPtr->ClearSelection(); // If we removed all the selected items and there is at least one other item, select it if ( LastSelectedItemIdx > INDEX_NONE && NumSelectedItemsRemoved > 0 && NumSelectedItemsRemoved >= SelectedItems.Num() && CollectionItems.Num() > 1 ) { // The last selected item idx will refer to the next item in the list now that it has been removed. // If the removed item was the last item in the list, select the new last item const int32 NewSelectedItemIdx = FMath::Min(LastSelectedItemIdx, CollectionItems.Num() - 1); CollectionListPtr->SetSelection(CollectionItems[NewSelectedItemIdx]); } // Refresh the list CollectionListPtr->RequestListRefresh(); } EVisibility SCollectionView::GetCollectionListVisibility() const { return CollectionItems.Num() > 0 ? EVisibility::Visible : EVisibility::Collapsed; } TSharedRef SCollectionView::GenerateCollectionRow( TSharedPtr CollectionItem, const TSharedRef& OwnerTable ) { check(CollectionItem.IsValid()); TSharedPtr< STableRow< TSharedPtr > > TableRow = SNew( STableRow< TSharedPtr >, OwnerTable ); TableRow->SetContent ( SNew(SCollectionListItem) .ParentWidget(SharedThis(this)) .CollectionItem(CollectionItem) .OnNameChangeCommit(this, &SCollectionView::CollectionNameChangeCommit) .OnVerifyRenameCommit(this, &SCollectionView::CollectionVerifyRenameCommit) .OnAssetsDragDropped(this, &SCollectionView::CollectionAssetsDropped) .IsSelected( TableRow.Get(), &STableRow< TSharedPtr >::IsSelectedExclusively ) .IsReadOnly(this, &SCollectionView::IsCollectionNotRenamable) ); return TableRow.ToSharedRef(); } TSharedPtr SCollectionView::MakeCollectionListContextMenu() { if ( !bAllowRightClickMenu ) { return NULL; } return CollectionContextMenu->MakeCollectionListContextMenu(Commands); } void SCollectionView::CollectionSelectionChanged( TSharedPtr< FCollectionItem > CollectionItem, ESelectInfo::Type /*SelectInfo*/ ) { if ( ShouldAllowSelectionChangedDelegate() && OnCollectionSelected.IsBound() ) { if ( CollectionItem.IsValid() ) { OnCollectionSelected.Execute(FCollectionNameType(FName(*CollectionItem->CollectionName), CollectionItem->CollectionType)); } else { OnCollectionSelected.Execute(FCollectionNameType(NAME_None, ECollectionShareType::CST_All)); } } } void SCollectionView::CollectionAssetsDropped(const TArray& AssetList, const TSharedPtr& CollectionItem, FText& OutMessage) { TArray ObjectPaths; for ( auto AssetIt = AssetList.CreateConstIterator(); AssetIt; ++AssetIt ) { ObjectPaths.Add((*AssetIt).ObjectPath); } FCollectionManagerModule& CollectionManagerModule = FModuleManager::Get().LoadModuleChecked("CollectionManager"); int32 NumAdded = 0; if ( CollectionManagerModule.Get().AddToCollection(FName(*CollectionItem->CollectionName), CollectionItem->CollectionType, ObjectPaths, &NumAdded) ) { FFormatNamedArguments Args; Args.Add( TEXT("Number"), NumAdded ); OutMessage = FText::Format( LOCTEXT("CollectionAssetsAdded", "Added {Number} asset(s)"), Args ); } else { OutMessage = CollectionManagerModule.Get().GetLastError(); } } void SCollectionView::CollectionItemScrolledIntoView( TSharedPtr CollectionItem, const TSharedPtr& Widget ) { if ( CollectionItem->bRenaming && Widget.IsValid() && Widget->GetContent().IsValid() ) { CollectionItem->OnRenamedRequestEvent.Broadcast(); } } bool SCollectionView::IsCollectionNotRenamable() const { CollectionContextMenu->UpdateProjectSourceControl(); return !CollectionContextMenu->CanRenameSelectedCollections(); } bool SCollectionView::CollectionNameChangeCommit( const TSharedPtr< FCollectionItem >& CollectionItem, const FString& NewName, bool bChangeConfirmed, FText& OutWarningMessage ) { // There should only ever be one item selected when renaming check(CollectionListPtr->GetNumItemsSelected() == 1); FCollectionManagerModule& CollectionManagerModule = FModuleManager::Get().LoadModuleChecked(TEXT("CollectionManager")); ECollectionShareType::Type CollectionType = CollectionItem->CollectionType; // If new name is empty, set it back to the original name FString NewNameFinal( NewName.IsEmpty() ? CollectionItem->CollectionName : NewName ); if ( CollectionItem->bNewCollection ) { CollectionItem->bNewCollection = false; // If we can canceled the name change when creating a new asset, we want to silently remove it if ( !bChangeConfirmed ) { CollectionItems.Remove(CollectionItem); CollectionListPtr->RequestListRefresh(); return false; } if ( !CollectionManagerModule.Get().CreateCollection(FName(*NewNameFinal), CollectionType) ) { // Failed to add the collection, remove it from the list CollectionItems.Remove(CollectionItem); CollectionListPtr->RequestListRefresh(); OutWarningMessage = FText::Format( LOCTEXT("CreateCollectionFailed", "Failed to create the collection. {0}"), CollectionManagerModule.Get().GetLastError()); return false; } } else { // If the old name is the same as the new name, just early exit here. if ( CollectionItem->CollectionName == NewNameFinal ) { return true; } // Otherwise perform the rename if ( !CollectionManagerModule.Get().RenameCollection(FName(*CollectionItem->CollectionName), CollectionType, FName(*NewNameFinal), CollectionType) ) { // Failed to rename the collection OutWarningMessage = FText::Format( LOCTEXT("RenameCollectionFailed", "Failed to rename the collection. {0}"), CollectionManagerModule.Get().GetLastError()); return false; } } // Make sure we are sorted CollectionItems.Sort( FCollectionItem::FCompareFCollectionItemByName() ); // 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 FindCollectionItemPredicate = [CollectionType, &NewNameFinal] (const TSharedPtr& Item) { return ( Item->CollectionType == CollectionType && Item->CollectionName == NewNameFinal ); }; auto NewCollectionItemPtr = CollectionItems.FindByPredicate( FindCollectionItemPredicate ); // Reselect the path to notify that the selection has changed { FScopedPreventSelectionChangedDelegate DelegatePrevention( SharedThis(this) ); CollectionListPtr->ClearSelection(); } // Set the selection if (NewCollectionItemPtr) { const auto& NewCollectionItem = *NewCollectionItemPtr; CollectionListPtr->RequestScrollIntoView(NewCollectionItem); CollectionListPtr->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 == NewName) { return true; } FCollectionManagerModule& CollectionManagerModule = FModuleManager::Get().LoadModuleChecked(TEXT("CollectionManager")); if (CollectionManagerModule.Get().CollectionExists(*NewName, ECollectionShareType::CST_All)) { // This collection already exists, inform the user and continue OutErrorMessage = FText::Format(LOCTEXT("RenameCollectionAlreadyExists", "A collection already exists with the name '{0}'."), FText::FromString(NewName)); // Return false to indicate that the user should enter a new name return false; } return true; } #undef LOCTEXT_NAMESPACE