// Copyright 1998-2014 Epic Games, Inc. All Rights Reserved. #include "ContentBrowserPCH.h" #include "AssetViewTypes.h" #include "AssetContextMenu.h" #include "ContentBrowserModule.h" #include "Editor/UnrealEd/Public/ObjectTools.h" #include "Editor/UnrealEd/Public/PackageTools.h" #include "Editor/UnrealEd/Public/FileHelpers.h" #include "Editor/PropertyEditor/Public/PropertyEditorModule.h" #include "Toolkits/AssetEditorManager.h" #include "Toolkits/ToolkitManager.h" #include "ConsolidateWindow.h" #include "ReferenceViewer.h" #include "ReferencedAssetsUtils.h" #include "ISourceControlModule.h" #include "ISourceControlRevision.h" #include "SourceControlWindows.h" #include "KismetEditorUtilities.h" #include "AssetToolsModule.h" #include "ComponentAssetBroker.h" #include "SNumericEntryBox.h" #include "SourceCodeNavigation.h" #include "IDocumentation.h" #include "EditorClassUtils.h" #define LOCTEXT_NAMESPACE "ContentBrowser" FAssetContextMenu::FAssetContextMenu(const TWeakPtr& InAssetView) : AssetView(InAssetView) , bAtLeastOneNonRedirectorSelected(false) , bCanExecuteSCCCheckOut(false) , bCanExecuteSCCOpenForAdd(false) , bCanExecuteSCCCheckIn(false) , bCanExecuteSCCHistory(false) , bCanExecuteSCCRevert(false) , bCanExecuteSCCSync(false) { } TSharedRef FAssetContextMenu::MakeContextMenu(const TArray& InSelectedAssets, const FSourcesData& InSourcesData, TSharedPtr< FUICommandList > InCommandList) { SelectedAssets = InSelectedAssets; SourcesData = InSourcesData; // Cache any vars that are used in determining if you can execute any actions. // Useful for actions whose "CanExecute" will not change or is expensive to calculate. CacheCanExecuteVars(); // Get all menu extenders for this context menu from the content browser module FContentBrowserModule& ContentBrowserModule = FModuleManager::GetModuleChecked( TEXT("ContentBrowser") ); TArray MenuExtenderDelegates = ContentBrowserModule.GetAllAssetViewContextMenuExtenders(); TArray> Extenders; for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i) { if (MenuExtenderDelegates[i].IsBound()) { Extenders.Add(MenuExtenderDelegates[i].Execute(SelectedAssets)); } } TSharedPtr MenuExtender = FExtender::Combine(Extenders); FMenuBuilder MenuBuilder(/*bInShouldCloseWindowAfterMenuSelection=*/true, InCommandList, MenuExtender); // Only add something if at least one asset is selected if ( SelectedAssets.Num() ) { // Add any type-specific context menu options AddAssetTypeMenuOptions(MenuBuilder); // Add quick access to common commands. AddCommonMenuOptions(MenuBuilder); // Add reference options AddReferenceMenuOptions(MenuBuilder); // Add documentation options AddDocumentationMenuOptions(MenuBuilder); // Add source control options AddSourceControlMenuOptions(MenuBuilder); // Add collection options AddCollectionMenuOptions(MenuBuilder); } return MenuBuilder.MakeWidget(); } void FAssetContextMenu::SetOnFindInAssetTreeRequested(const FOnFindInAssetTreeRequested& InOnFindInAssetTreeRequested) { OnFindInAssetTreeRequested = InOnFindInAssetTreeRequested; } void FAssetContextMenu::SetOnRenameRequested(const FOnRenameRequested& InOnRenameRequested) { OnRenameRequested = InOnRenameRequested; } void FAssetContextMenu::SetOnRenameFolderRequested(const FOnRenameFolderRequested& InOnRenameFolderRequested) { OnRenameFolderRequested = InOnRenameFolderRequested; } void FAssetContextMenu::SetOnDuplicateRequested(const FOnDuplicateRequested& InOnDuplicateRequested) { OnDuplicateRequested = InOnDuplicateRequested; } void FAssetContextMenu::SetOnAssetViewRefreshRequested(const FOnAssetViewRefreshRequested& InOnAssetViewRefreshRequested) { OnAssetViewRefreshRequested = InOnAssetViewRefreshRequested; } bool FAssetContextMenu::AddCommonMenuOptions(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("AssetContextActions", LOCTEXT("AssetActionsMenuHeading", "Asset Actions")); { MenuBuilder.AddMenuEntry( LOCTEXT("SyncToAssetTree", "Find in Asset Tree"), LOCTEXT("SyncToAssetTreeTooltip", "Selects the folder in the asset tree containing this asset."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSyncToAssetTree ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSyncToAssetTree ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("FindAssetInWorld", "Select Actors Using This Asset"), LOCTEXT("FindAssetInWorldTooltip", "Selects all actors referencing this asset."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteFindAssetInWorld ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteFindAssetInWorld ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("Properties", "Details..."), LOCTEXT("PropertiesTooltip", "Opens the details for the selected assets."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteProperties ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteProperties ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("PropertyMatrix", "Property Matrix..."), LOCTEXT("PropertyMatrixTooltip", "Opens the property matrix editor for the selected assets."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecutePropertyMatrix ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteProperties ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("Duplicate", "Create Copy"), LOCTEXT("DuplicateTooltip", "Create a copy of the selected assets."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteDuplicate ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteDuplicate ) ) ); MenuBuilder.AddMenuEntry( FGenericCommands::Get().Rename, NAME_None, LOCTEXT("Rename", "Rename"), LOCTEXT("RenameTooltip", "Rename the selected asset.") ); MenuBuilder.AddMenuEntry( FGenericCommands::Get().Delete, NAME_None, LOCTEXT("Delete", "Delete"), LOCTEXT("DeleteTooltip", "Delete the selected assets.") ); if ( CanExecuteConsolidate() ) { MenuBuilder.AddMenuEntry( LOCTEXT("ReplaceReferences", "Replace References"), LOCTEXT("ConsolidateTooltip", "Replace references to the selected assets."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteConsolidate ) ) ); } MenuBuilder.AddMenuEntry( LOCTEXT("Export", "Export..."), LOCTEXT("ExportTooltip", "Export the selected assets to file."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteExport ) ) ); if (SelectedAssets.Num() > 1) { MenuBuilder.AddMenuEntry( LOCTEXT("BulkExport", "Bulk Export..."), LOCTEXT("BulkExportTooltip", "Export the selected assets to file in the selected directory"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteBulkExport ) ) ); } MenuBuilder.AddMenuEntry( LOCTEXT("MigrateAsset", "Migrate..."), LOCTEXT("MigrateAssetTooltip", "Copies all selected assets and their dependencies to another game"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteMigrateAsset ) ) ); MenuBuilder.AddMenuEntry( ContentBrowserUtils::GetExploreFolderText(), LOCTEXT("FindInExplorerTooltip", "Finds this asset on disk"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteFindInExplorer ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteFindInExplorer ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("CreateBlueprintUsing", "Create Blueprint Using..."), LOCTEXT("CreateBlueprintUsingTooltip", "Create a new Blueprint and add this asset to it"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteCreateBlueprintUsing), FCanExecuteAction::CreateSP(this, &FAssetContextMenu::CanExecuteCreateBlueprintUsing) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("SaveAsset", "Save"), LOCTEXT("SaveAssetTooltip", "Saves the asset to file."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSaveAsset ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSaveAsset ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("AssignAssetChunk", "Assign to Chunk..."), LOCTEXT("AssignAssetChunkTooltip", "Assign this asset to a specific Chunk"), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteAssignChunkID) ) ); MenuBuilder.AddSubMenu( LOCTEXT("RemoveAssetFromChunk", "Remove from Chunk..."), LOCTEXT("RemoveAssetFromChunkTooltip", "Removed an asset from a Chunk it's assigned to."), FNewMenuDelegate::CreateRaw(this, &FAssetContextMenu::MakeChunkIDListMenu) ); MenuBuilder.AddMenuEntry( LOCTEXT("RemoveAllChunkAssignments", "Remove from all Chunks"), LOCTEXT("RemoveAllChunkAssignmentsTooltip", "Removed an asset from all Chunks it's assigned to."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteRemoveAllChunkID) ) ); if (CanExecuteDiffSelected()) { MenuBuilder.AddMenuEntry( LOCTEXT("DiffSelected", "Diff Selected"), LOCTEXT("DiffSelectedTooltip", "Diff the two assets that you have selected."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteDiffSelected) ) ); } FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked("AssetTools"); if ( SelectedAssets.Num() == 1 && AssetToolsModule.Get().AssetUsesGenericThumbnail(SelectedAssets[0]) ) { MenuBuilder.AddMenuEntry( LOCTEXT("CaptureThumbnail", "Capture Thumbnail"), LOCTEXT("CaptureThumbnailTooltip", "Captures a thumbnail from the active viewport."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteCaptureThumbnail ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteCaptureThumbnail ) ) ); } if ( CanClearCustomThumbnails() ) { MenuBuilder.AddMenuEntry( LOCTEXT("ClearCustomThumbnail", "Clear Thumbnail"), LOCTEXT("ClearCustomThumbnailTooltip", "Clears all custom thumbnails for selected assets."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteClearThumbnail ) ) ); } } MenuBuilder.EndSection(); return true; } bool FAssetContextMenu::AddReferenceMenuOptions(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("AssetContextReferences", LOCTEXT("ReferencesMenuHeading", "References")); { MenuBuilder.AddMenuEntry( LOCTEXT("CopyReference", "Copy Reference"), LOCTEXT("CopyReferenceTooltip", "Copies reference paths for the selected assets to the clipboard."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteCopyReference ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("ReferenceViewer", "Reference Viewer..."), LOCTEXT("ReferenceViewerTooltip", "Shows a graph of references for this asset."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteShowReferenceViewer ) ) ); } MenuBuilder.EndSection(); return true; } bool FAssetContextMenu::AddDocumentationMenuOptions(FMenuBuilder& MenuBuilder) { bool bAddedOption = false; // Objects must be loaded for this operation... for now UClass* SelectedClass = (SelectedAssets.Num() > 0 ? SelectedAssets[0].GetClass() : nullptr); for (const FAssetData& AssetData : SelectedAssets) { if (SelectedClass != AssetData.GetClass()) { SelectedClass = nullptr; break; } } // Go to C++ Code if( SelectedClass != nullptr ) { // Blueprints are special. We won't link to C++ and for documentation we'll use the class it is generated from const bool bIsBlueprint = SelectedClass->IsChildOf(); if (bIsBlueprint) { FString* ParentClassPath = SelectedAssets[0].TagsAndValues.Find(GET_MEMBER_NAME_CHECKED(UBlueprint,ParentClass)); if (ParentClassPath) { SelectedClass = FindObject(nullptr,**ParentClassPath); } } if ( !bIsBlueprint && FSourceCodeNavigation::IsCompilerAvailable() ) { FString ClassHeaderPath; if( FSourceCodeNavigation::FindClassHeaderPath( SelectedClass, ClassHeaderPath ) && IFileManager::Get().FileSize( *ClassHeaderPath ) != INDEX_NONE ) { bAddedOption = true; const FString CodeFileName = FPaths::GetCleanFilename( *ClassHeaderPath ); MenuBuilder.BeginSection( "ActorCode", LOCTEXT("ActorCodeHeading", "C++") ); { MenuBuilder.AddMenuEntry( FText::Format( LOCTEXT("GoToCodeForActor", "Open {0}"), FText::FromString( CodeFileName ) ), FText::Format( LOCTEXT("GoToCodeForActor_ToolTip", "Opens the header file for this actor ({0}) in a code editing program"), FText::FromString( CodeFileName ) ), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteGoToCodeForAsset, SelectedClass ) ) ); } MenuBuilder.EndSection(); } } const FString DocumentationLink = FEditorClassUtils::GetDocumentationLink(SelectedClass); if (!DocumentationLink.IsEmpty()) { bAddedOption = true; MenuBuilder.BeginSection( "ActorDocumentation", LOCTEXT("ActorDocsHeading", "Documentation") ); { MenuBuilder.AddMenuEntry( LOCTEXT("GoToDocsForActor", "View Documentation"), LOCTEXT("GoToDocsForActor_ToolTip", "Click to open documentation for this actor"), FSlateIcon(FEditorStyle::GetStyleSetName(), "HelpIcon.Hovered" ), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteGoToDocsForAsset, SelectedClass ) ) ); } MenuBuilder.EndSection(); } } return bAddedOption; } bool FAssetContextMenu::AddAssetTypeMenuOptions(FMenuBuilder& MenuBuilder) { bool bAnyTypeOptions = false; // Objects must be loaded for this operation... for now TArray ObjectPaths; for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { ObjectPaths.Add(SelectedAssets[AssetIdx].ObjectPath.ToString()); } TArray SelectedObjects; if ( ContentBrowserUtils::LoadAssetsIfNeeded(ObjectPaths, SelectedObjects) ) { // Load the asset tools module FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked(TEXT("AssetTools")); bAnyTypeOptions = AssetToolsModule.Get().GetAssetActions(SelectedObjects, MenuBuilder, /*bIncludeHeading=*/true); } return bAnyTypeOptions; } bool FAssetContextMenu::AddSourceControlMenuOptions(FMenuBuilder& MenuBuilder) { MenuBuilder.BeginSection("AssetContextSourceControl", LOCTEXT("AssetSCCOptionsMenuHeading", "Source Control")); if ( ISourceControlModule::Get().IsEnabled() ) { if( CanExecuteSCCSync() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCSync", "Sync"), LOCTEXT("SCCSyncTooltip", "Updates the item to the latest version in source control."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCSync ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCSync ) ) ); } if ( CanExecuteSCCCheckOut() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCCheckOut", "Check Out"), LOCTEXT("SCCCheckOutTooltip", "Checks out the selected asset from source control."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCCheckOut ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCCheckOut ) ) ); } if ( CanExecuteSCCOpenForAdd() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCOpenForAdd", "Mark For Add"), LOCTEXT("SCCOpenForAddTooltip", "Adds the selected asset to source control."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCOpenForAdd ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCOpenForAdd ) ) ); } if ( CanExecuteSCCCheckIn() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCCheckIn", "Check In"), LOCTEXT("SCCCheckInTooltip", "Checks in the selected asset to source control."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCCheckIn ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCCheckIn ) ) ); } MenuBuilder.AddMenuEntry( LOCTEXT("SCCRefresh", "Refresh"), LOCTEXT("SCCRefreshTooltip", "Updates the source control status of the asset."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCRefresh ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCRefresh ) ) ); if( CanExecuteSCCHistory() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCHistory", "History"), LOCTEXT("SCCHistoryTooltip", "Displays the source control revision history of the selected asset."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCHistory ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCHistory ) ) ); MenuBuilder.AddMenuEntry( LOCTEXT("SCCDiffAgainstDepot", "Diff Against Depot"), LOCTEXT("SCCDiffAgainstDepotTooltip", "Look at differences between your version of the asset and that in source control."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCDiffAgainstDepot ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCDiffAgainstDepot ) ) ); } if( CanExecuteSCCRevert() ) { MenuBuilder.AddMenuEntry( LOCTEXT("SCCRevert", "Revert"), LOCTEXT("SCCRevertTooltip", "Reverts the asset to the state it was before it was checked out."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteSCCRevert ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteSCCRevert ) ) ); } } else { MenuBuilder.AddMenuEntry( LOCTEXT("SCCConnectToSourceControl", "Connect To Source Control"), LOCTEXT("SCCConnectToSourceControlTooltip", "Connect to source control to allow source control operations to be performed on content and levels."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteEnableSourceControl ), FCanExecuteAction() ) ); } MenuBuilder.EndSection(); return true; } bool FAssetContextMenu::AddCollectionMenuOptions(FMenuBuilder& MenuBuilder) { // "Remove from collection" (only display option if exactly one collection is selected) if ( SourcesData.Collections.Num() == 1 ) { MenuBuilder.BeginSection("AssetContextCollections", LOCTEXT("AssetCollectionOptionsMenuHeading", "Collections")); { MenuBuilder.AddMenuEntry( LOCTEXT("RemoveFromCollection", "Remove From Collection"), LOCTEXT("RemoveFromCollection_ToolTip", "Removes the selected asset from the current collection."), FSlateIcon(), FUIAction( FExecuteAction::CreateSP( this, &FAssetContextMenu::ExecuteRemoveFromCollection ), FCanExecuteAction::CreateSP( this, &FAssetContextMenu::CanExecuteRemoveFromCollection ) ) ); } MenuBuilder.EndSection(); return true; } return false; } void FAssetContextMenu::ExecuteSyncToAssetTree() { OnFindInAssetTreeRequested.ExecuteIfBound(SelectedAssets); } void FAssetContextMenu::ExecuteFindInExplorer() { for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { const UObject* Asset = SelectedAssets[AssetIdx].GetAsset(); if (Asset) { FAssetData AssetData(Asset); const bool bIsWorldAsset = (AssetData.AssetClass == UWorld::StaticClass()->GetFName()); const FString Extension = bIsWorldAsset ? FPackageName::GetMapPackageExtension() : FPackageName::GetAssetPackageExtension(); const FString FilePath = FPackageName::LongPackageNameToFilename(AssetData.PackageName.ToString(), Extension); const FString FullFilePath = FPaths::ConvertRelativePathToFull(FilePath); FPlatformProcess::ExploreFolder(*FullFilePath); } } } void FAssetContextMenu::ExecuteCreateBlueprintUsing() { if(SelectedAssets.Num() == 1) { UObject* Asset = SelectedAssets[0].GetAsset(); FKismetEditorUtilities::CreateBlueprintUsingAsset(Asset, true); } } void FAssetContextMenu::GetSelectedAssets(TArray& Assets, bool SkipRedirectors) { for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { if (SkipRedirectors && (SelectedAssets[AssetIdx].AssetClass == UObjectRedirector::StaticClass()->GetFName())) { // Don't operate on Redirectors continue; } UObject* Object = SelectedAssets[AssetIdx].GetAsset(); if (Object) { Assets.Add(Object); } } } /** Generates a reference graph of the world and can then find actors referencing specified objects */ struct WorldReferenceGenerator : public FFindReferencedAssets { void BuildReferencingData() { MarkAllObjects(); const int32 MaxRecursionDepth = 0; const bool bIncludeClasses = true; const bool bIncludeDefaults = false; const bool bReverseReferenceGraph = true; UWorld* World = GWorld; // Generate the reference graph for the world FReferencedAssets* WorldReferencer = new(Referencers)FReferencedAssets(World); FFindAssetsArchive(World, WorldReferencer->AssetList, &ReferenceGraph, MaxRecursionDepth, bIncludeClasses, bIncludeDefaults, bReverseReferenceGraph); // Also include all the streaming levels in the results for (int32 LevelIndex = 0; LevelIndex < World->StreamingLevels.Num(); ++LevelIndex) { ULevelStreaming* StreamingLevel = World->StreamingLevels[LevelIndex]; if( StreamingLevel != NULL ) { ULevel* Level = StreamingLevel->GetLoadedLevel(); if( Level != NULL ) { // Generate the reference graph for each streamed in level FReferencedAssets* LevelReferencer = new(Referencers) FReferencedAssets(Level); FFindAssetsArchive(Level, LevelReferencer->AssetList, &ReferenceGraph, MaxRecursionDepth, bIncludeClasses, bIncludeDefaults, bReverseReferenceGraph); } } } } void MarkAllObjects() { // Mark all objects so we don't get into an endless recursion for (FObjectIterator It; It; ++It) { It->Mark(OBJECTMARK_TagExp); } } void Generate( const UObject* AssetToFind, TArray< TWeakObjectPtr >& OutObjects ) { // Don't examine visited objects if (!AssetToFind->HasAnyMarks(OBJECTMARK_TagExp)) { return; } AssetToFind->UnMark(OBJECTMARK_TagExp); // Return once we find a parent object that is an actor if (AssetToFind->IsA(AActor::StaticClass())) { OutObjects.Add(AssetToFind); return; } // Transverse the reference graph looking for actor objects TSet* Referencers = ReferenceGraph.Find(AssetToFind); if (Referencers) { for(TSet::TConstIterator SetIt(*Referencers); SetIt; ++SetIt) { Generate(*SetIt, OutObjects); } } } }; void FAssetContextMenu::ExecuteFindAssetInWorld() { TArray AssetsToFind; const bool SkipRedirectors = true; GetSelectedAssets(AssetsToFind, SkipRedirectors); const bool NoteSelectionChange = true; const bool DeselectBSPSurfs = true; const bool WarnAboutManyActors = false; GEditor->SelectNone(NoteSelectionChange, DeselectBSPSurfs, WarnAboutManyActors); if (AssetsToFind.Num() > 0) { const bool ShowProgressDialog = true; GWarn->BeginSlowTask(NSLOCTEXT("AssetContextMenu", "FindAssetInWorld", "Finding actors that use this asset..."), ShowProgressDialog); CollectGarbage(GARBAGE_COLLECTION_KEEPFLAGS); TArray< TWeakObjectPtr > OutObjects; WorldReferenceGenerator ObjRefGenerator; ObjRefGenerator.BuildReferencingData(); for (int32 AssetIdx = 0; AssetIdx < AssetsToFind.Num(); ++AssetIdx) { ObjRefGenerator.MarkAllObjects(); ObjRefGenerator.Generate(AssetsToFind[AssetIdx], OutObjects); } if (OutObjects.Num() > 0) { const bool InSelected = true; const bool Notify = false; // Select referencing actors for (int32 ActorIdx = 0; ActorIdx < OutObjects.Num(); ++ActorIdx) { GEditor->SelectActor(CastChecked(OutObjects[ActorIdx].Get()), InSelected, Notify); } GEditor->NoteSelectionChange(); } else { FNotificationInfo Info(LOCTEXT("NoReferencingActorsFound", "No actors found.")); Info.ExpireDuration = 3.0f; FSlateNotificationManager::Get().AddNotification(Info); } GWarn->EndSlowTask(); } } void FAssetContextMenu::ExecuteProperties() { TArray ObjectsForPropertiesMenu; const bool SkipRedirectors = true; GetSelectedAssets(ObjectsForPropertiesMenu, SkipRedirectors); if ( ObjectsForPropertiesMenu.Num() > 0 ) { FAssetEditorManager::Get().OpenEditorForAssets( ObjectsForPropertiesMenu ); } } void FAssetContextMenu::ExecutePropertyMatrix() { TArray ObjectsForPropertiesMenu; const bool SkipRedirectors = true; GetSelectedAssets(ObjectsForPropertiesMenu, SkipRedirectors); if ( ObjectsForPropertiesMenu.Num() > 0 ) { FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked( "PropertyEditor" ); PropertyEditorModule.CreatePropertyEditorToolkit( EToolkitMode::Standalone, TSharedPtr(), ObjectsForPropertiesMenu ); } } void FAssetContextMenu::ExecuteSaveAsset() { TArray PackagesToSave; GetSelectedPackages(PackagesToSave); TArray< UPackage* > PackagesWithExternalRefs; FString PackageNames; if( PackageTools::CheckForReferencesToExternalPackages( &PackagesToSave, &PackagesWithExternalRefs ) ) { for(int32 PkgIdx = 0; PkgIdx < PackagesWithExternalRefs.Num(); ++PkgIdx) { PackageNames += FString::Printf(TEXT("%s\n"), *PackagesWithExternalRefs[ PkgIdx ]->GetName()); } bool bProceed = EAppReturnType::Yes == FMessageDialog::Open( EAppMsgType::YesNo, FText::Format( NSLOCTEXT("UnrealEd", "Warning_ExternalPackageRef", "The following assets have references to external assets: \n{0}\nExternal assets won't be found when in a game and all references will be broken. Proceed?"), FText::FromString(PackageNames) ) ); if(!bProceed) { return; } } const bool bCheckDirty = false; const bool bPromptToSave = false; const FEditorFileUtils::EPromptReturnCode Return = FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave); //return Return == FEditorFileUtils::EPromptReturnCode::PR_Success; } void FAssetContextMenu::ExecuteDiffSelected() const { if (SelectedAssets.Num() >= 2) { UObject* FirstObjectSelected = SelectedAssets[0].GetAsset(); UObject* SecondObjectSelected = SelectedAssets[1].GetAsset(); if ((FirstObjectSelected != NULL) && (SecondObjectSelected != NULL)) { // Load the asset registry module FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); FRevisionInfo CurrentRevision; CurrentRevision.Revision = -1; AssetToolsModule.Get().DiffAssets(FirstObjectSelected, SecondObjectSelected, CurrentRevision, CurrentRevision); } } } void FAssetContextMenu::ExecuteDuplicate() { TArray ObjectsToDuplicate; const bool SkipRedirectors = true; GetSelectedAssets(ObjectsToDuplicate, SkipRedirectors); if ( ObjectsToDuplicate.Num() == 1 ) { OnDuplicateRequested.ExecuteIfBound(ObjectsToDuplicate[0]); } else if ( ObjectsToDuplicate.Num() > 1 ) { TArray NewObjects; ObjectTools::DuplicateObjects(ObjectsToDuplicate, TEXT(""), TEXT(""), /*bOpenDialog=*/false, &NewObjects); TArray AssetsToSync; for ( auto ObjIt = NewObjects.CreateConstIterator(); ObjIt; ++ObjIt ) { new(AssetsToSync) FAssetData(*ObjIt); } // Sync to asset tree if ( NewObjects.Num() > 0 ) { OnFindInAssetTreeRequested.ExecuteIfBound(AssetsToSync); } } } void FAssetContextMenu::ExecuteRename() { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); TArray< FString > SelectedFolders = AssetView.Pin()->GetSelectedFolders(); if ( AssetViewSelectedAssets.Num() == 1 && SelectedFolders.Num() == 0 ) { // Don't operate on Redirectors if ( AssetViewSelectedAssets[0].AssetClass != UObjectRedirector::StaticClass()->GetFName() ) { OnRenameRequested.ExecuteIfBound(AssetViewSelectedAssets[0]); } } if ( AssetViewSelectedAssets.Num() == 0 && SelectedFolders.Num() == 1 ) { OnRenameFolderRequested.ExecuteIfBound(SelectedFolders[0]); } } void FAssetContextMenu::ExecuteDelete() { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); if(AssetViewSelectedAssets.Num() > 0) { TArray AssetsToDelete; for( auto AssetIt = AssetViewSelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { const FAssetData& AssetData = *AssetIt; if( AssetData.AssetClass == UObjectRedirector::StaticClass()->GetFName() ) { // Don't operate on Redirectors continue; } AssetsToDelete.Add( AssetData ); } if ( AssetsToDelete.Num() > 0 ) { ObjectTools::DeleteAssets( AssetsToDelete ); } } TArray< FString > SelectedFolders = AssetView.Pin()->GetSelectedFolders(); if(SelectedFolders.Num() > 0) { FText Prompt; if ( SelectedFolders.Num() == 1 ) { Prompt = FText::Format(LOCTEXT("FolderDeleteConfirm_Single", "Delete folder '{0}'?"), FText::FromString(SelectedFolders[0])); } else { Prompt = FText::Format(LOCTEXT("FolderDeleteConfirm_Multiple", "Delete {0} folders?"), FText::AsNumber(SelectedFolders.Num())); } // Spawn a confirmation dialog since this is potentially a highly destructive operation ContentBrowserUtils::DisplayConfirmationPopup( Prompt, LOCTEXT("FolderDeleteConfirm_Yes", "Delete"), LOCTEXT("FolderDeleteConfirm_No", "Cancel"), AssetView.Pin().ToSharedRef(), FOnClicked::CreateSP( this, &FAssetContextMenu::ExecuteDeleteFolderConfirmed )); } } FReply FAssetContextMenu::ExecuteDeleteFolderConfirmed() { TArray< FString > SelectedFolders = AssetView.Pin()->GetSelectedFolders(); if(SelectedFolders.Num() > 0) { ContentBrowserUtils::DeleteFolders(SelectedFolders); } return FReply::Handled(); } void FAssetContextMenu::ExecuteConsolidate() { TArray ObjectsToConsolidate; const bool SkipRedirectors = true; GetSelectedAssets(ObjectsToConsolidate, SkipRedirectors); if ( ObjectsToConsolidate.Num() > 0 ) { FConsolidateToolWindow::AddConsolidationObjects( ObjectsToConsolidate ); } } void FAssetContextMenu::ExecuteCaptureThumbnail() { FViewport* Viewport = GEditor->GetActiveViewport(); if ( ensure(GCurrentLevelEditingViewportClient) && ensure(Viewport) ) { //have to re-render the requested viewport FLevelEditorViewportClient* OldViewportClient = GCurrentLevelEditingViewportClient; //remove selection box around client during render GCurrentLevelEditingViewportClient = NULL; Viewport->Draw(); ContentBrowserUtils::CaptureThumbnailFromViewport(Viewport, SelectedAssets); //redraw viewport to have the yellow highlight again GCurrentLevelEditingViewportClient = OldViewportClient; Viewport->Draw(); } } void FAssetContextMenu::ExecuteClearThumbnail() { ContentBrowserUtils::ClearCustomThumbnails(SelectedAssets); } void FAssetContextMenu::ExecuteMigrateAsset() { // Get a list of package names for input into MigratePackages TArray PackageNames; for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { PackageNames.Add(SelectedAssets[AssetIdx].PackageName); } FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked("AssetTools"); AssetToolsModule.Get().MigratePackages( PackageNames ); } void FAssetContextMenu::ExecuteShowReferenceViewer() { TArray PackageNames; for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { PackageNames.Add(AssetIt->PackageName); } if ( PackageNames.Num() > 0 ) { IReferenceViewerModule::Get().InvokeReferenceViewerTab(PackageNames); } } void FAssetContextMenu::ExecuteGoToCodeForAsset(UClass* SelectedClass) { if (SelectedClass) { FString ClassHeaderPath; if( FSourceCodeNavigation::FindClassHeaderPath( SelectedClass, ClassHeaderPath ) && IFileManager::Get().FileSize( *ClassHeaderPath ) != INDEX_NONE ) { const FString AbsoluteHeaderPath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*ClassHeaderPath); FSourceCodeNavigation::OpenSourceFile( AbsoluteHeaderPath ); } } } void FAssetContextMenu::ExecuteGoToDocsForAsset(UClass* SelectedClass) { if (SelectedClass) { FString DocumentationLink = FEditorClassUtils::GetDocumentationLink(SelectedClass); if (!DocumentationLink.IsEmpty()) { IDocumentation::Get()->Open( DocumentationLink ); } } } void FAssetContextMenu::ExecuteCopyReference() { ContentBrowserUtils::CopyAssetReferencesToClipboard(SelectedAssets); } void FAssetContextMenu::ExecuteExport() { TArray ObjectsToExport; const bool SkipRedirectors = false; GetSelectedAssets(ObjectsToExport, SkipRedirectors); if ( ObjectsToExport.Num() > 0 ) { ObjectTools::ExportObjects(ObjectsToExport, /*bPromptForEachFileName=*/true); } } void FAssetContextMenu::ExecuteBulkExport() { TArray ObjectsToExport; const bool SkipRedirectors = false; GetSelectedAssets(ObjectsToExport, SkipRedirectors); if ( ObjectsToExport.Num() > 0 ) { ObjectTools::ExportObjects(ObjectsToExport, /*bPromptForEachFileName=*/false); } } void FAssetContextMenu::ExecuteRemoveFromCollection() { if ( ensure(SourcesData.Collections.Num() == 1) ) { TArray AssetsToRemove; for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) { AssetsToRemove.Add((*AssetIt).ObjectPath); } if ( AssetsToRemove.Num() > 0 ) { FCollectionManagerModule& CollectionManagerModule = FModuleManager::LoadModuleChecked("CollectionManager"); FName CollectionName = SourcesData.Collections[0].Name; ECollectionShareType::Type CollectionType = SourcesData.Collections[0].Type; CollectionManagerModule.Get().RemoveFromCollection(CollectionName, CollectionType, AssetsToRemove); OnAssetViewRefreshRequested.ExecuteIfBound(); } } } void FAssetContextMenu::ExecuteSCCRefresh() { TArray PackageNames; GetSelectedPackageNames(PackageNames); ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create(), SourceControlHelpers::PackageFilenames(PackageNames), EConcurrency::Asynchronous); } void FAssetContextMenu::ExecuteSCCCheckOut() { TArray PackagesToCheckOut; GetSelectedPackages(PackagesToCheckOut); if ( PackagesToCheckOut.Num() > 0 ) { // Update the source control status of all potentially relevant packages ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create(), PackagesToCheckOut); // Now check them out FEditorFileUtils::CheckoutPackages(PackagesToCheckOut); } } void FAssetContextMenu::ExecuteSCCOpenForAdd() { TArray PackageNames; GetSelectedPackageNames(PackageNames); ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); TArray PackagesToAdd; TArray PackagesToSave; for ( auto PackageIt = PackageNames.CreateConstIterator(); PackageIt; ++PackageIt ) { FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(SourceControlHelpers::PackageFilename(*PackageIt), EStateCacheUsage::Use); if ( SourceControlState.IsValid() && !SourceControlState->IsSourceControlled() ) { PackagesToAdd.Add(*PackageIt); // Make sure the file actually exists on disk before adding it FString Filename; if ( !FPackageName::DoesPackageExist(*PackageIt, NULL, &Filename) ) { UPackage* Package = FindPackage(NULL, **PackageIt); if ( Package ) { PackagesToSave.Add(Package); } } } } if ( PackagesToAdd.Num() > 0 ) { // If any of the packages are new, save them now if ( PackagesToSave.Num() > 0 ) { const bool bCheckDirty = false; const bool bPromptToSave = false; TArray FailedPackages; const FEditorFileUtils::EPromptReturnCode Return = FEditorFileUtils::PromptForCheckoutAndSave(PackagesToSave, bCheckDirty, bPromptToSave, &FailedPackages); if(FailedPackages.Num() > 0) { // don't try and add files that failed to save - remove them from the list for(auto FailedPackageIt = FailedPackages.CreateConstIterator(); FailedPackageIt; FailedPackageIt++) { PackagesToAdd.Remove((*FailedPackageIt)->GetName()); } } } SourceControlProvider.Execute(ISourceControlOperation::Create(), SourceControlHelpers::PackageFilenames(PackagesToAdd)); } } void FAssetContextMenu::ExecuteSCCCheckIn() { TArray PackageNames; GetSelectedPackageNames(PackageNames); TArray Packages; GetSelectedPackages(Packages); // Prompt the user to ask if they would like to first save any dirty packages they are trying to check-in const FEditorFileUtils::EPromptReturnCode UserResponse = FEditorFileUtils::PromptForCheckoutAndSave( Packages, true, true ); // If the user elected to save dirty packages, but one or more of the packages failed to save properly OR if the user // canceled out of the prompt, don't follow through on the check-in process const bool bShouldProceed = ( UserResponse == FEditorFileUtils::EPromptReturnCode::PR_Success || UserResponse == FEditorFileUtils::EPromptReturnCode::PR_Declined ); if ( bShouldProceed ) { FSourceControlWindows::PromptForCheckin(PackageNames); } else { // If a failure occurred, alert the user that the check-in was aborted. This warning shouldn't be necessary if the user cancelled // from the dialog, because they obviously intended to cancel the whole operation. if ( UserResponse == FEditorFileUtils::EPromptReturnCode::PR_Failure ) { FMessageDialog::Open( EAppMsgType::Ok, NSLOCTEXT("UnrealEd", "SCC_Checkin_Aborted", "Check-in aborted as a result of save failure.") ); } } } void FAssetContextMenu::ExecuteSCCHistory() { TArray PackageNames; GetSelectedPackageNames(PackageNames); FSourceControlWindows::DisplayRevisionHistory(SourceControlHelpers::PackageFilenames(PackageNames)); } void FAssetContextMenu::ExecuteSCCDiffAgainstDepot() const { // Load the asset registry module FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked("AssetTools"); // Iterate over each selected asset for(int32 AssetIdx=0; AssetIdx PackageNames; GetSelectedPackageNames(PackageNames); FSourceControlWindows::PromptForRevert(PackageNames); } void FAssetContextMenu::ExecuteSCCSync() { TArray PackageNames; GetSelectedPackageNames(PackageNames); TArray PackageFileNames = SourceControlHelpers::PackageFilenames(PackageNames); TArray Packages; GetSelectedPackages(Packages); FText ErrorMessage; PackageTools::UnloadPackages(Packages, ErrorMessage); if(!ErrorMessage.IsEmpty()) { FMessageDialog::Open( EAppMsgType::Ok, ErrorMessage ); } else { ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create(), PackageFileNames); for( TArray::TConstIterator PackageIter( PackageNames ); PackageIter; ++PackageIter ) { PackageTools::LoadPackage(*PackageIter); } } } void FAssetContextMenu::ExecuteEnableSourceControl() { ISourceControlModule::Get().ShowLoginDialog(FSourceControlLoginClosed(), ELoginWindowMode::Modeless); } bool FAssetContextMenu::CanExecuteSyncToAssetTree() const { return SelectedAssets.Num() > 0; } bool FAssetContextMenu::CanExecuteFindInExplorer() const { return SelectedAssets.Num() > 0; } bool FAssetContextMenu::CanExecuteCreateBlueprintUsing() const { // Only work if you have a single asset selected if(SelectedAssets.Num() == 1) { UObject* Asset = SelectedAssets[0].GetAsset(); // See if we know how to make a component from this asset TArray< TSubclassOf > ComponentClassList = FComponentAssetBrokerage::GetComponentsForAsset(Asset); return (ComponentClassList.Num() > 0); } return false; } bool FAssetContextMenu::CanExecuteFindAssetInWorld() const { return bAtLeastOneNonRedirectorSelected; } bool FAssetContextMenu::CanExecuteProperties() const { return bAtLeastOneNonRedirectorSelected; } bool FAssetContextMenu::CanExecutePropertyMatrix() const { return bAtLeastOneNonRedirectorSelected; } bool FAssetContextMenu::CanExecuteDuplicate() const { return bAtLeastOneNonRedirectorSelected; } bool FAssetContextMenu::CanExecuteRename() const { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); TArray< FString > SelectedFolders = AssetView.Pin()->GetSelectedFolders(); const bool bOneAssetSelected = AssetViewSelectedAssets.Num() == 1 && SelectedFolders.Num() == 0 && AssetViewSelectedAssets[0].AssetClass != UObjectRedirector::StaticClass()->GetFName(); const bool bOneFolderSelected = AssetViewSelectedAssets.Num() == 0 && SelectedFolders.Num() == 1; return (bOneAssetSelected || bOneFolderSelected) && !AssetView.Pin()->IsThumbnailEditMode(); } bool FAssetContextMenu::CanExecuteDelete() const { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); TArray< FString > SelectedFolders = AssetView.Pin()->GetSelectedFolders(); return AssetViewSelectedAssets.Num() > 0 || SelectedFolders.Num() > 0; } bool FAssetContextMenu::CanExecuteRemoveFromCollection() const { return SourcesData.Collections.Num() == 1; } bool FAssetContextMenu::CanExecuteSCCRefresh() const { return ISourceControlModule::Get().IsEnabled(); } bool FAssetContextMenu::CanExecuteSCCCheckOut() const { return bCanExecuteSCCCheckOut; } bool FAssetContextMenu::CanExecuteSCCOpenForAdd() const { return bCanExecuteSCCOpenForAdd; } bool FAssetContextMenu::CanExecuteSCCCheckIn() const { return bCanExecuteSCCCheckIn; } bool FAssetContextMenu::CanExecuteSCCHistory() const { return bCanExecuteSCCHistory; } bool FAssetContextMenu::CanExecuteSCCDiffAgainstDepot() const { return bCanExecuteSCCHistory; } bool FAssetContextMenu::CanExecuteSCCRevert() const { return bCanExecuteSCCRevert; } bool FAssetContextMenu::CanExecuteSCCSync() const { return bCanExecuteSCCSync; } bool FAssetContextMenu::CanExecuteConsolidate() const { TArray ProposedObjects; for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { // Don't load assets here. Only operate on already loaded assets. if ( SelectedAssets[AssetIdx].IsAssetLoaded() ) { UObject* Object = SelectedAssets[AssetIdx].GetAsset(); if ( Object ) { ProposedObjects.Add(Object); } } } if ( ProposedObjects.Num() > 0 ) { TArray CompatibleObjects; return FConsolidateToolWindow::DetermineAssetCompatibility(ProposedObjects, CompatibleObjects); } return false; } bool FAssetContextMenu::CanExecuteSaveAsset() const { TArray Packages; GetSelectedPackages(Packages); // only enabled if at least one selected package is loaded at all for (int32 PackageIdx = 0; PackageIdx < Packages.Num(); ++PackageIdx) { if ( Packages[PackageIdx] != NULL ) { return true; } } return false; } bool FAssetContextMenu::CanExecuteDiffSelected() const { bool bCanDiffSelected = false; if (SelectedAssets.Num() == 2) { FAssetData const& FirstSelection = SelectedAssets[0]; FAssetData const& SecondSelection = SelectedAssets[1]; if (FirstSelection.AssetClass != SecondSelection.AssetClass) { bCanDiffSelected = false; } else { bCanDiffSelected = (FirstSelection.AssetClass == UBlueprint::StaticClass()->GetFName()); } } return bCanDiffSelected; } bool FAssetContextMenu::CanExecuteCaptureThumbnail() const { return GCurrentLevelEditingViewportClient != NULL; } bool FAssetContextMenu::CanClearCustomThumbnails() const { for ( auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt ) { if ( ContentBrowserUtils::AssetHasCustomThumbnail(*AssetIt) ) { return true; } } return false; } void FAssetContextMenu::CacheCanExecuteVars() { bAtLeastOneNonRedirectorSelected = false; bCanExecuteSCCCheckOut = false; bCanExecuteSCCOpenForAdd = false; bCanExecuteSCCCheckIn = false; bCanExecuteSCCHistory = false; bCanExecuteSCCRevert = false; bCanExecuteSCCSync = false; for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt) { const FAssetData& AssetData = *AssetIt; if ( !AssetData.IsValid() ) { continue; } if ( !bAtLeastOneNonRedirectorSelected && AssetData.AssetClass != UObjectRedirector::StaticClass()->GetFName() ) { bAtLeastOneNonRedirectorSelected = true; } ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider(); if ( ISourceControlModule::Get().IsEnabled() ) { // Check the SCC state for each package in the selected paths FSourceControlStatePtr SourceControlState = SourceControlProvider.GetState(SourceControlHelpers::PackageFilename(AssetData.PackageName.ToString()), EStateCacheUsage::Use); if(SourceControlState.IsValid()) { if ( SourceControlState->CanCheckout() ) { bCanExecuteSCCCheckOut = true; } if ( !SourceControlState->IsSourceControlled() && SourceControlState->CanAdd() ) { bCanExecuteSCCOpenForAdd = true; } else if( SourceControlState->IsSourceControlled() && !SourceControlState->IsAdded() ) { bCanExecuteSCCHistory = true; } if(!SourceControlState->IsCurrent()) { bCanExecuteSCCSync = true; } if ( SourceControlState->CanCheckIn() ) { bCanExecuteSCCCheckIn = true; bCanExecuteSCCRevert = true; } } } if ( bAtLeastOneNonRedirectorSelected && bCanExecuteSCCCheckOut && bCanExecuteSCCOpenForAdd && bCanExecuteSCCCheckIn && bCanExecuteSCCHistory && bCanExecuteSCCRevert && bCanExecuteSCCSync ) { // All options are available, no need to keep iterating break; } } } void FAssetContextMenu::GetSelectedPackageNames(TArray& OutPackageNames) const { for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { OutPackageNames.Add(SelectedAssets[AssetIdx].PackageName.ToString()); } } void FAssetContextMenu::GetSelectedPackages(TArray& OutPackages) const { for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx) { UPackage* Package = FindPackage(NULL, *SelectedAssets[AssetIdx].PackageName.ToString()); if ( Package ) { OutPackages.Add(Package); } } } void FAssetContextMenu::MakeChunkIDListMenu(FMenuBuilder& MenuBuilder) { TArray FoundChunks; TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); for (const auto& SelectedAsset : AssetViewSelectedAssets) { UPackage* Package = FindPackage(NULL, *SelectedAsset.PackageName.ToString()); if (Package) { for (auto ChunkID : Package->GetChunkIDs()) { FoundChunks.AddUnique(ChunkID); } } } for (auto ChunkID : FoundChunks) { MenuBuilder.AddMenuEntry( FText::Format(LOCTEXT("PackageChunk", "Chunk {0}"), FText::AsNumber(ChunkID)), FText(), FSlateIcon(), FUIAction( FExecuteAction::CreateSP(this, &FAssetContextMenu::ExecuteRemoveChunkID, ChunkID) ) ); } } void FAssetContextMenu::ExecuteAssignChunkID() { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); auto AssetViewPtr = AssetView.Pin(); if (AssetViewSelectedAssets.Num() > 0 && AssetViewPtr.IsValid()) { // Determine the position of the window so that it will spawn near the mouse, but not go off the screen. const FVector2D CursorPos = FSlateApplication::Get().GetCursorPos(); FSlateRect Anchor(CursorPos.X, CursorPos.Y, CursorPos.X, CursorPos.Y); FVector2D AdjustedSummonLocation = FSlateApplication::Get().CalculatePopupWindowPosition(Anchor, SColorPicker::DEFAULT_WINDOW_SIZE, Orient_Horizontal); TSharedPtr Window = SNew(SWindow) .AutoCenter(EAutoCenter::None) .ScreenPosition(AdjustedSummonLocation) .SupportsMaximize(false) .SupportsMinimize(false) .SizingRule(ESizingRule::Autosized) .Title(LOCTEXT("WindowHeader", "Enter Chunk ID")); Window->SetContent( SNew(SVerticalBox) + SVerticalBox::Slot() .FillHeight(1.0f) .HAlign(HAlign_Fill) .VAlign(VAlign_Top) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(STextBlock) .Text(LOCTEXT("MeshPaint_LabelStrength", "Chunk ID")) ] + SHorizontalBox::Slot() .FillWidth(2.0f) .HAlign(HAlign_Fill) .VAlign(VAlign_Center) [ SNew(SNumericEntryBox) .AllowSpin(true) .MinSliderValue(0) .MaxSliderValue(300) .MinValue(0) .MaxValue(300) .Value(this, &FAssetContextMenu::GetChunkIDSelection) .OnValueChanged(this, &FAssetContextMenu::OnChunkIDAssignChanged) ] ] + SVerticalBox::Slot() .FillHeight(1.0f) .HAlign(HAlign_Fill) .VAlign(VAlign_Bottom) [ SNew(SHorizontalBox) + SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Right) .VAlign(VAlign_Center) [ SNew(SButton) .Text(LOCTEXT("ChunkIDAssign_Yes", "OK")) .OnClicked(this, &FAssetContextMenu::OnChunkIDAssignCommit, Window) ] + SHorizontalBox::Slot() .FillWidth(1.0f) .HAlign(HAlign_Left) .VAlign(VAlign_Center) [ SNew(SButton) .Text(LOCTEXT("ChunkIDAssign_No", "Cancel")) .OnClicked(this, &FAssetContextMenu::OnChunkIDAssignCancel, Window) ] ] ); ChunkIDSelected = 0; FSlateApplication::Get().AddModalWindow(Window.ToSharedRef(), AssetViewPtr); } } void FAssetContextMenu::ExecuteRemoveAllChunkID() { TArray EmptyChunks; TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); for (const auto& SelectedAsset : AssetViewSelectedAssets) { UPackage* Package = FindPackage(NULL, *SelectedAsset.PackageName.ToString()); if (Package) { Package->SetChunkIDs(EmptyChunks); Package->SetDirtyFlag(true); } } } TOptional FAssetContextMenu::GetChunkIDSelection() const { return ChunkIDSelected; } void FAssetContextMenu::OnChunkIDAssignChanged(int32 NewChunkID) { ChunkIDSelected = NewChunkID; } FReply FAssetContextMenu::OnChunkIDAssignCommit(TSharedPtr Window) { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); for (const auto& SelectedAsset : AssetViewSelectedAssets) { UPackage* Package = FindPackage(NULL, *SelectedAsset.PackageName.ToString()); if (Package) { TArray CurrentChunks = Package->GetChunkIDs(); CurrentChunks.AddUnique(ChunkIDSelected); Package->SetChunkIDs(CurrentChunks); Package->SetDirtyFlag(true); } } Window->RequestDestroyWindow(); return FReply::Handled(); } FReply FAssetContextMenu::OnChunkIDAssignCancel(TSharedPtr Window) { Window->RequestDestroyWindow(); return FReply::Handled(); } void FAssetContextMenu::ExecuteRemoveChunkID(int32 ChunkID) { TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets(); for (const auto& SelectedAsset : AssetViewSelectedAssets) { UPackage* Package = FindPackage(NULL, *SelectedAsset.PackageName.ToString()); if (Package) { int32 FoundIndex; TArray CurrentChunks = Package->GetChunkIDs(); CurrentChunks.Find(ChunkID, FoundIndex); if (FoundIndex != INDEX_NONE) { CurrentChunks.RemoveAt(FoundIndex); Package->SetChunkIDs(CurrentChunks); Package->SetDirtyFlag(true); } } } } #undef LOCTEXT_NAMESPACE