Files
UnrealEngineUWP/Engine/Source/Editor/ContentBrowser/Private/AssetContextMenu.cpp

1723 lines
51 KiB
C++
Raw Normal View History

// 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<SAssetView>& InAssetView)
: AssetView(InAssetView)
, bAtLeastOneNonRedirectorSelected(false)
, bCanExecuteSCCCheckOut(false)
, bCanExecuteSCCOpenForAdd(false)
, bCanExecuteSCCCheckIn(false)
, bCanExecuteSCCHistory(false)
, bCanExecuteSCCRevert(false)
, bCanExecuteSCCSync(false)
{
}
TSharedRef<SWidget> FAssetContextMenu::MakeContextMenu(const TArray<FAssetData>& 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<FContentBrowserModule>( TEXT("ContentBrowser") );
TArray<FContentBrowserMenuExtender_SelectedAssets> MenuExtenderDelegates = ContentBrowserModule.GetAllAssetViewContextMenuExtenders();
TArray<TSharedPtr<FExtender>> Extenders;
for (int32 i = 0; i < MenuExtenderDelegates.Num(); ++i)
{
if (MenuExtenderDelegates[i].IsBound())
{
Extenders.Add(MenuExtenderDelegates[i].Execute(SelectedAssets));
}
}
TSharedPtr<FExtender> 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 )
)
);
Folder Rename can now be done in the Content Browser Path View #ttp 342267 - Editor Usability: Select a folder in Content Browser and pressing F2 to rename it, triggers a rename of an actor or asset instead! #branch UE4 SContentBrowser Added functions for Can/Execute Rename/Delete which forward to the active context menu Moved Rename/Delete bind commands to the content browser, so it can forward the request to the correct context menu (this was necessary as it wasn't possible to bind to both context menus without the code complaining about it already being bound) Modified GetFolderContextMenu to take an extra param to indicate whether it was the path view or asset view which called it - it then uses this to clear the asset view selection when the path view context menu is requested - we can then use the selection information to determine which context menu was opened later on when processing rename/delete (ideally it should clear when the path view recieves focus, but that's harder to determine). FAssetContextMenu Made Can/Execute Rename/Delete public - so they can be called by the Content Browser Removed BindCommands as it's no longer needed Removed condition for adding Rename to the menu, as it now always is and is greyed out if not possible. FPathContextMenu Added delegate for when rename folder is requested - the same as FAssetContextMenu Added handling for whether folder renaming is possible, and executing. Removed condition for adding Rename to the menu, as it now always is and is greyed out if not possible. Fixed issue with Delete not mapping to keyboard shortcut SPathView Added function to rename folder (similar to SAssetView) Fixed issue with VerifyFolderNameChanged not generating the correct path when checking to see if a folder exists (it was previous just checking to old path - which would always return true). Modified FolderNameChanged to take 'OldPath' as a param (inline with the delegate change). This function is called when a folder is created or renamed... if it's the latter it has to handle moving the contents of the folder to the new location, which it could only do if it knew where the previous location was (again, similar to SAssetView) SAssetTreeItem Added extra param to FOnNameChanged (OldPath) - so we can move assets when a folder is renamed FTreeItem Renamed variable bNewFolder to bNamingFolder - as it's true whenever the folder is being named, not just when it's new reviewed by Thomas.Sarkanen [CL 2239648 by Andrew Brown in Main branch]
2014-08-01 05:51:26 -04:00
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<FAssetToolsModule>("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<UBlueprint>();
if (bIsBlueprint)
{
FString* ParentClassPath = SelectedAssets[0].TagsAndValues.Find(GET_MEMBER_NAME_CHECKED(UBlueprint,ParentClass));
if (ParentClassPath)
{
SelectedClass = FindObject<UClass>(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<FString> ObjectPaths;
for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx)
{
ObjectPaths.Add(SelectedAssets[AssetIdx].ObjectPath.ToString());
}
TArray<UObject*> SelectedObjects;
if ( ContentBrowserUtils::LoadAssetsIfNeeded(ObjectPaths, SelectedObjects) )
{
// Load the asset tools module
FAssetToolsModule& AssetToolsModule = FModuleManager::LoadModuleChecked<FAssetToolsModule>(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<UObject*>& 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<UObject> >& 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<UObject*>* Referencers = ReferenceGraph.Find(AssetToFind);
if (Referencers)
{
for(TSet<UObject*>::TConstIterator SetIt(*Referencers); SetIt; ++SetIt)
{
Generate(*SetIt, OutObjects);
}
}
}
};
void FAssetContextMenu::ExecuteFindAssetInWorld()
{
TArray<UObject*> 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<UObject> > 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<AActor>(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<UObject*> ObjectsForPropertiesMenu;
const bool SkipRedirectors = true;
GetSelectedAssets(ObjectsForPropertiesMenu, SkipRedirectors);
if ( ObjectsForPropertiesMenu.Num() > 0 )
{
FAssetEditorManager::Get().OpenEditorForAssets( ObjectsForPropertiesMenu );
}
}
void FAssetContextMenu::ExecutePropertyMatrix()
{
TArray<UObject*> ObjectsForPropertiesMenu;
const bool SkipRedirectors = true;
GetSelectedAssets(ObjectsForPropertiesMenu, SkipRedirectors);
if ( ObjectsForPropertiesMenu.Num() > 0 )
{
FPropertyEditorModule& PropertyEditorModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>( "PropertyEditor" );
PropertyEditorModule.CreatePropertyEditorToolkit( EToolkitMode::Standalone, TSharedPtr<IToolkitHost>(), ObjectsForPropertiesMenu );
}
}
void FAssetContextMenu::ExecuteSaveAsset()
{
TArray<UPackage*> 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<FAssetToolsModule>("AssetTools");
FRevisionInfo CurrentRevision;
CurrentRevision.Revision = -1;
AssetToolsModule.Get().DiffAssets(FirstObjectSelected, SecondObjectSelected, CurrentRevision, CurrentRevision);
}
}
}
void FAssetContextMenu::ExecuteDuplicate()
{
TArray<UObject*> ObjectsToDuplicate;
const bool SkipRedirectors = true;
GetSelectedAssets(ObjectsToDuplicate, SkipRedirectors);
if ( ObjectsToDuplicate.Num() == 1 )
{
OnDuplicateRequested.ExecuteIfBound(ObjectsToDuplicate[0]);
}
else if ( ObjectsToDuplicate.Num() > 1 )
{
TArray<UObject*> NewObjects;
ObjectTools::DuplicateObjects(ObjectsToDuplicate, TEXT(""), TEXT(""), /*bOpenDialog=*/false, &NewObjects);
TArray<FAssetData> AssetsToSync;
for ( auto ObjIt = NewObjects.CreateConstIterator(); ObjIt; ++ObjIt )
{
new(AssetsToSync) FAssetData(*ObjIt);
}
// Sync to asset tree
if ( NewObjects.Num() > 0 )
{
OnFindInAssetTreeRequested.ExecuteIfBound(AssetsToSync);
}
}
}
Folder Rename can now be done in the Content Browser Path View #ttp 342267 - Editor Usability: Select a folder in Content Browser and pressing F2 to rename it, triggers a rename of an actor or asset instead! #branch UE4 SContentBrowser Added functions for Can/Execute Rename/Delete which forward to the active context menu Moved Rename/Delete bind commands to the content browser, so it can forward the request to the correct context menu (this was necessary as it wasn't possible to bind to both context menus without the code complaining about it already being bound) Modified GetFolderContextMenu to take an extra param to indicate whether it was the path view or asset view which called it - it then uses this to clear the asset view selection when the path view context menu is requested - we can then use the selection information to determine which context menu was opened later on when processing rename/delete (ideally it should clear when the path view recieves focus, but that's harder to determine). FAssetContextMenu Made Can/Execute Rename/Delete public - so they can be called by the Content Browser Removed BindCommands as it's no longer needed Removed condition for adding Rename to the menu, as it now always is and is greyed out if not possible. FPathContextMenu Added delegate for when rename folder is requested - the same as FAssetContextMenu Added handling for whether folder renaming is possible, and executing. Removed condition for adding Rename to the menu, as it now always is and is greyed out if not possible. Fixed issue with Delete not mapping to keyboard shortcut SPathView Added function to rename folder (similar to SAssetView) Fixed issue with VerifyFolderNameChanged not generating the correct path when checking to see if a folder exists (it was previous just checking to old path - which would always return true). Modified FolderNameChanged to take 'OldPath' as a param (inline with the delegate change). This function is called when a folder is created or renamed... if it's the latter it has to handle moving the contents of the folder to the new location, which it could only do if it knew where the previous location was (again, similar to SAssetView) SAssetTreeItem Added extra param to FOnNameChanged (OldPath) - so we can move assets when a folder is renamed FTreeItem Renamed variable bNewFolder to bNamingFolder - as it's true whenever the folder is being named, not just when it's new reviewed by Thomas.Sarkanen [CL 2239648 by Andrew Brown in Main branch]
2014-08-01 05:51:26 -04:00
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]);
}
}
Folder Rename can now be done in the Content Browser Path View #ttp 342267 - Editor Usability: Select a folder in Content Browser and pressing F2 to rename it, triggers a rename of an actor or asset instead! #branch UE4 SContentBrowser Added functions for Can/Execute Rename/Delete which forward to the active context menu Moved Rename/Delete bind commands to the content browser, so it can forward the request to the correct context menu (this was necessary as it wasn't possible to bind to both context menus without the code complaining about it already being bound) Modified GetFolderContextMenu to take an extra param to indicate whether it was the path view or asset view which called it - it then uses this to clear the asset view selection when the path view context menu is requested - we can then use the selection information to determine which context menu was opened later on when processing rename/delete (ideally it should clear when the path view recieves focus, but that's harder to determine). FAssetContextMenu Made Can/Execute Rename/Delete public - so they can be called by the Content Browser Removed BindCommands as it's no longer needed Removed condition for adding Rename to the menu, as it now always is and is greyed out if not possible. FPathContextMenu Added delegate for when rename folder is requested - the same as FAssetContextMenu Added handling for whether folder renaming is possible, and executing. Removed condition for adding Rename to the menu, as it now always is and is greyed out if not possible. Fixed issue with Delete not mapping to keyboard shortcut SPathView Added function to rename folder (similar to SAssetView) Fixed issue with VerifyFolderNameChanged not generating the correct path when checking to see if a folder exists (it was previous just checking to old path - which would always return true). Modified FolderNameChanged to take 'OldPath' as a param (inline with the delegate change). This function is called when a folder is created or renamed... if it's the latter it has to handle moving the contents of the folder to the new location, which it could only do if it knew where the previous location was (again, similar to SAssetView) SAssetTreeItem Added extra param to FOnNameChanged (OldPath) - so we can move assets when a folder is renamed FTreeItem Renamed variable bNewFolder to bNamingFolder - as it's true whenever the folder is being named, not just when it's new reviewed by Thomas.Sarkanen [CL 2239648 by Andrew Brown in Main branch]
2014-08-01 05:51:26 -04:00
void FAssetContextMenu::ExecuteDelete()
{
TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets();
if(AssetViewSelectedAssets.Num() > 0)
{
TArray<FAssetData> 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<UObject*> 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<FName> PackageNames;
for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx)
{
PackageNames.Add(SelectedAssets[AssetIdx].PackageName);
}
FAssetToolsModule& AssetToolsModule = FModuleManager::Get().LoadModuleChecked<FAssetToolsModule>("AssetTools");
AssetToolsModule.Get().MigratePackages( PackageNames );
}
void FAssetContextMenu::ExecuteShowReferenceViewer()
{
TArray<FName> 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<UObject*> ObjectsToExport;
const bool SkipRedirectors = false;
GetSelectedAssets(ObjectsToExport, SkipRedirectors);
if ( ObjectsToExport.Num() > 0 )
{
ObjectTools::ExportObjects(ObjectsToExport, /*bPromptForEachFileName=*/true);
}
}
void FAssetContextMenu::ExecuteBulkExport()
{
TArray<UObject*> 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<FName> AssetsToRemove;
for (auto AssetIt = SelectedAssets.CreateConstIterator(); AssetIt; ++AssetIt)
{
AssetsToRemove.Add((*AssetIt).ObjectPath);
}
if ( AssetsToRemove.Num() > 0 )
{
FCollectionManagerModule& CollectionManagerModule = FModuleManager::LoadModuleChecked<FCollectionManagerModule>("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<FString> PackageNames;
GetSelectedPackageNames(PackageNames);
ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create<FUpdateStatus>(), SourceControlHelpers::PackageFilenames(PackageNames), EConcurrency::Asynchronous);
}
void FAssetContextMenu::ExecuteSCCCheckOut()
{
TArray<UPackage*> PackagesToCheckOut;
GetSelectedPackages(PackagesToCheckOut);
if ( PackagesToCheckOut.Num() > 0 )
{
// Update the source control status of all potentially relevant packages
ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create<FUpdateStatus>(), PackagesToCheckOut);
// Now check them out
FEditorFileUtils::CheckoutPackages(PackagesToCheckOut);
}
}
void FAssetContextMenu::ExecuteSCCOpenForAdd()
{
TArray<FString> PackageNames;
GetSelectedPackageNames(PackageNames);
ISourceControlProvider& SourceControlProvider = ISourceControlModule::Get().GetProvider();
TArray<FString> PackagesToAdd;
TArray<UPackage*> 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<UPackage*> 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<FMarkForAdd>(), SourceControlHelpers::PackageFilenames(PackagesToAdd));
}
}
void FAssetContextMenu::ExecuteSCCCheckIn()
{
TArray<FString> PackageNames;
GetSelectedPackageNames(PackageNames);
TArray<UPackage*> 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<FString> PackageNames;
GetSelectedPackageNames(PackageNames);
FSourceControlWindows::DisplayRevisionHistory(SourceControlHelpers::PackageFilenames(PackageNames));
}
void FAssetContextMenu::ExecuteSCCDiffAgainstDepot() const
{
// Load the asset registry module
FAssetToolsModule& AssetToolsModule = FModuleManager::GetModuleChecked<FAssetToolsModule>("AssetTools");
// Iterate over each selected asset
for(int32 AssetIdx=0; AssetIdx<SelectedAssets.Num(); AssetIdx++)
{
// Get the actual asset (will load it)
const FAssetData& AssetData = SelectedAssets[AssetIdx];
UObject* CurrentObject = AssetData.GetAsset();
if( CurrentObject )
{
const FString PackagePath = AssetData.PackageName.ToString();
const FString PackageName = AssetData.AssetName.ToString();
AssetToolsModule.Get().DiffAgainstDepot( CurrentObject, PackagePath, PackageName );
}
}
}
void FAssetContextMenu::ExecuteSCCRevert()
{
TArray<FString> PackageNames;
GetSelectedPackageNames(PackageNames);
FSourceControlWindows::PromptForRevert(PackageNames);
}
void FAssetContextMenu::ExecuteSCCSync()
{
TArray<FString> PackageNames;
GetSelectedPackageNames(PackageNames);
TArray<FString> PackageFileNames = SourceControlHelpers::PackageFilenames(PackageNames);
TArray<UPackage*> Packages;
GetSelectedPackages(Packages);
FText ErrorMessage;
PackageTools::UnloadPackages(Packages, ErrorMessage);
if(!ErrorMessage.IsEmpty())
{
FMessageDialog::Open( EAppMsgType::Ok, ErrorMessage );
}
else
{
ISourceControlModule::Get().GetProvider().Execute(ISourceControlOperation::Create<FSync>(), PackageFileNames);
for( TArray<FString>::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<UActorComponent> > 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<UObject*> 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<UObject*> CompatibleObjects;
return FConsolidateToolWindow::DetermineAssetCompatibility(ProposedObjects, CompatibleObjects);
}
return false;
}
bool FAssetContextMenu::CanExecuteSaveAsset() const
{
TArray<UPackage*> 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<FString>& OutPackageNames) const
{
for (int32 AssetIdx = 0; AssetIdx < SelectedAssets.Num(); ++AssetIdx)
{
OutPackageNames.Add(SelectedAssets[AssetIdx].PackageName.ToString());
}
}
void FAssetContextMenu::GetSelectedPackages(TArray<UPackage*>& 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<int32> 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<SWindow> 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<int32>)
.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<int32> 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<int32> FAssetContextMenu::GetChunkIDSelection() const
{
return ChunkIDSelected;
}
void FAssetContextMenu::OnChunkIDAssignChanged(int32 NewChunkID)
{
ChunkIDSelected = NewChunkID;
}
FReply FAssetContextMenu::OnChunkIDAssignCommit(TSharedPtr<SWindow> Window)
{
TArray< FAssetData > AssetViewSelectedAssets = AssetView.Pin()->GetSelectedAssets();
for (const auto& SelectedAsset : AssetViewSelectedAssets)
{
UPackage* Package = FindPackage(NULL, *SelectedAsset.PackageName.ToString());
if (Package)
{
TArray<int32> CurrentChunks = Package->GetChunkIDs();
CurrentChunks.AddUnique(ChunkIDSelected);
Package->SetChunkIDs(CurrentChunks);
Package->SetDirtyFlag(true);
}
}
Window->RequestDestroyWindow();
return FReply::Handled();
}
FReply FAssetContextMenu::OnChunkIDAssignCancel(TSharedPtr<SWindow> 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<int32> CurrentChunks = Package->GetChunkIDs();
CurrentChunks.Find(ChunkID, FoundIndex);
if (FoundIndex != INDEX_NONE)
{
CurrentChunks.RemoveAt(FoundIndex);
Package->SetChunkIDs(CurrentChunks);
Package->SetDirtyFlag(true);
}
}
}
}
#undef LOCTEXT_NAMESPACE