Files
UnrealEngine/Engine/Source/Editor/Kismet/Private/BlueprintEditorViewportContextMenuExtender.cpp
2025-05-18 13:04:45 +08:00

299 lines
9.6 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "Containers/Array.h"
#include "Containers/EnumAsByte.h"
#include "Containers/UnrealString.h"
#include "CreateBlueprintFromActorDialog.h"
#include "Delegates/Delegate.h"
#include "Editor.h"
#include "Editor/EditorEngine.h"
#include "Engine/Blueprint.h"
#include "Framework/Commands/UIAction.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "Framework/MultiBox/MultiBoxExtender.h"
#include "GameFramework/Actor.h"
#include "HAL/PlatformMath.h"
#include "Internationalization/Internationalization.h"
#include "Internationalization/Text.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Misc/AssertionMacros.h"
#include "Selection.h"
#include "Styling/AppStyle.h"
#include "Subsystems/AssetEditorSubsystem.h"
#include "Templates/Casts.h"
#include "Templates/SharedPointer.h"
#include "Textures/SlateIcon.h"
#include "Trace/Detail/Channel.h"
#include "UObject/Class.h"
#include "UObject/Object.h"
#include "UObject/WeakObjectPtr.h"
#include "UObject/WeakObjectPtrTemplates.h"
class FUICommandList;
DEFINE_LOG_CATEGORY_STATIC(LogViewportBlueprintMenu, Log, All);
#define LOCTEXT_NAMESPACE "LevelViewportContextMenuBlueprints"
/** Blueprint class info for context menu */
struct FMenuBlueprintClass
{
/** Name of the class */
FString Name;
/** Blueprint for a kismet graph */
TWeakObjectPtr<UBlueprint> Blueprint;
};
/**
* Called to edit code for the specified function symbol name
*
* @param Blueprint Blueprint to edit code for
*/
void EditKismetCodeFor( TWeakObjectPtr<UBlueprint> BlueprintRef )
{
// Navigate to this function (implemented in Kismet 2)!
if (UBlueprint* Blueprint = BlueprintRef.Get())
{
// Open the blueprint
// @todo toolkit major: Needs world-centric support (pass in LevelEditor. See FLevelEditorActionCallbacks::OpenLevelBlueprint)
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset( Blueprint );
}
else
{
UE_LOG(LogViewportBlueprintMenu, Warning, TEXT("Failed to find blueprint"));
}
}
/**
* Fills in a sub-menu that shows all of the classes that can be edited
*
* @param MenuBuilder The sub-menu we're building up
*/
void FillEditCodeMenu( class FMenuBuilder& MenuBuilder, TArray< FMenuBlueprintClass > Classes)
{
for( int32 CurClassIndex = 0; CurClassIndex < Classes.Num(); ++CurClassIndex )
{
FMenuBlueprintClass& CurClass = Classes[ CurClassIndex ];
FText LabelName = FText::FromString( CurClass.Name );
const FText ToolTipName = LOCTEXT("EditCodeMenu_ClassToolTip", "Opens this Blueprint in the Blueprint Editor");
FUIAction UIAction;
UIAction.ExecuteAction.BindStatic(
&EditKismetCodeFor,
CurClass.Blueprint );
MenuBuilder.AddMenuEntry( LabelName, ToolTipName, FSlateIcon(), UIAction );
}
}
/**
* Called to recompile the out of date blueprint for the current selection set
*/
void RecompileOutOfDateKismetForSelection()
{
int32 BlueprintFailures = 0;
// Run thru all selected actors, looking for out of date blueprints
FSelectionIterator SelectedActorItr( GEditor->GetSelectedActorIterator() );
for ( ; SelectedActorItr; ++SelectedActorItr)
{
AActor* CurrentActor = Cast<AActor>(*SelectedActorItr);
UBlueprint* Blueprint = Cast<UBlueprint>(CurrentActor->GetClass()->ClassGeneratedBy);
if ((Blueprint != NULL) && (!Blueprint->IsUpToDate()))
{
FKismetEditorUtilities::CompileBlueprint(Blueprint);
if (Blueprint->Status == BS_Error)
{
++BlueprintFailures;
}
}
}
if (BlueprintFailures)
{
UE_LOG(LogViewportBlueprintMenu, Warning, TEXT("%d blueprints failed to be recompiled"), BlueprintFailures);
}
}
/**
* Gathers all blueprints for the actors in question, outputting them to the classes array
*/
void GatherBlueprintsForActors( TArray< AActor* >& Actors, TArray< FMenuBlueprintClass >& Classes )
{
struct Local
{
static void AddBlueprint( TArray< FMenuBlueprintClass >& InClasses, const FString& ClassName, UBlueprint* Blueprint = NULL )
{
check( !ClassName.IsEmpty() );
// Check to see if we already have this class name in our list
FMenuBlueprintClass* FoundClass = NULL;
for( int32 CurClassIndex = 0; CurClassIndex < InClasses.Num(); ++CurClassIndex )
{
FMenuBlueprintClass& CurClass = InClasses[ CurClassIndex ];
if( CurClass.Name == ClassName )
{
FoundClass = &CurClass;
break;
}
}
// Add a new class to our list if we need to
if( FoundClass == NULL )
{
FoundClass = new( InClasses ) FMenuBlueprintClass();
FoundClass->Name = ClassName;
FoundClass->Blueprint = Blueprint;
}
else
{
check(FoundClass->Blueprint.Get() == Blueprint);
}
}
};
for( TArray< AActor* >::TIterator It( Actors ); It; ++It )
{
AActor* Actor = static_cast<AActor*>( *It );
checkSlow( Actor->IsA(AActor::StaticClass()) );
// Grab the class of this actor
UClass* ActorClass = Actor->GetClass();
check( ActorClass != NULL );
// Walk the inheritance hierarchy for this class
for( UClass* CurClass = ActorClass; CurClass != NULL; CurClass = CurClass->GetSuperClass() )
{
if (UBlueprint* Blueprint = Cast<UBlueprint>(CurClass->ClassGeneratedBy))
{
// Class was created by a blueprint, so don't offer C++ editing of functions declared in it
// Instead offer to edit the events and graphs of the blueprint
Local::AddBlueprint( Classes, CurClass->GetName(), Blueprint );
}
}
}
}
/**
* Fills the Blueprint menu with extra options
*/
void FillBlueprintOptions(FMenuBuilder& MenuBuilder, TArray<AActor*> SelectedActors)
{
// Gather Blueprint classes for this actor
TArray< FMenuBlueprintClass > BlueprintClasses;
GatherBlueprintsForActors( SelectedActors, BlueprintClasses );
MenuBuilder.BeginSection("ActorBlueprint", LOCTEXT("BlueprintsHeading", "Blueprints") );
// Adds the "Create Blueprint..." menu option if valid.
{
int NumBlueprintableActors = 0;
bool IsBlueprintBased = BlueprintClasses.Num() > 1;
if(!BlueprintClasses.Num())
{
for(auto It(SelectedActors.CreateIterator());It;++It)
{
AActor* Actor = *It;
if( FKismetEditorUtilities::CanCreateBlueprintOfClass(Actor->GetClass()))
{
NumBlueprintableActors++;
}
}
}
const bool bCanHarvestComponentsForBlueprint = (!IsBlueprintBased && (NumBlueprintableActors > 0));
if(bCanHarvestComponentsForBlueprint)
{
AActor* ActorOverride = nullptr;
FUIAction CreateBlueprintAction( FExecuteAction::CreateStatic( &FCreateBlueprintFromActorDialog::OpenDialog, ECreateBlueprintFromActorMode::Harvest, ActorOverride, true ) );
MenuBuilder.AddMenuEntry(LOCTEXT("CreateBlueprint", "Create Blueprint..."), LOCTEXT("CreateBlueprint_Tooltip", "Harvest Components from Selected Actors and create Blueprint"), FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.HarvestBlueprintFromActors"), CreateBlueprintAction);
}
}
// Check to see if we have any classes with functions to display
if( BlueprintClasses.Num() > 0 )
{
{
UBlueprint* FirstBlueprint = BlueprintClasses[0].Blueprint.Get();
// Determine if the selected objects that have blueprints are all of the same class, and if they are all up to date
bool bAllAreSameType = true;
bool bAreAnyNotUpToDate = false;
for (int32 ClassIndex = 0; ClassIndex < BlueprintClasses.Num(); ++ClassIndex)
{
UBlueprint* CurrentBlueprint = BlueprintClasses[ClassIndex].Blueprint.Get();
bAllAreSameType = bAllAreSameType && (CurrentBlueprint == FirstBlueprint);
if (CurrentBlueprint != NULL)
{
bAreAnyNotUpToDate |= !CurrentBlueprint->IsUpToDate();
}
}
// For a single selected class, we show a top level item (saves 2 clicks); otherwise we show the full hierarchy
if (bAllAreSameType && (FirstBlueprint != NULL))
{
// Shortcut to edit the blueprint directly, saves two clicks
FUIAction UIAction;
UIAction.ExecuteAction.BindStatic(
&EditKismetCodeFor,
/*Blueprint=*/ MakeWeakObjectPtr(FirstBlueprint) );
const FText Label = LOCTEXT("EditBlueprint", "Edit Blueprint");
const FText Description = FText::Format( LOCTEXT("EditBlueprint_ToolTip", "Opens {0} in the Blueprint editor"), FText::FromString( FirstBlueprint->GetName() ) );
MenuBuilder.AddMenuEntry( Label, Description, FSlateIcon(), UIAction );
}
else
{
// More than one type of blueprint is selected, so add a sub-menu for "Edit Kismet Code"
MenuBuilder.AddSubMenu(
LOCTEXT("EditBlueprintSubMenu", "Edit Blueprint"),
LOCTEXT("EditBlueprintSubMenu_ToolTip", "Shows Blueprints that can be opened for editing"),
FNewMenuDelegate::CreateStatic( &FillEditCodeMenu, BlueprintClasses ) );
}
// For any that aren't up to date, we offer a compile blueprints button
if (bAreAnyNotUpToDate)
{
// Shortcut to edit the blueprint directly, saves two clicks
FUIAction UIAction;
UIAction.ExecuteAction.BindStatic(&RecompileOutOfDateKismetForSelection);
const FText Label = LOCTEXT("CompileOutOfDateBPs", "Compile Out-of-Date Blueprints");
const FText Description = LOCTEXT("CompileOutOfDateBPs_ToolTip", "Compiles out-of-date blueprints for selected actors");
MenuBuilder.AddMenuEntry( Label, Description, FSlateIcon(), UIAction );
}
}
}
MenuBuilder.EndSection();
}
/**
* Extends the level viewport context menu with blueprint-specific menu items
*/
TSharedRef<FExtender> ExtendLevelViewportContextMenuForBlueprints(const TSharedRef<FUICommandList> CommandList, TArray<AActor*> SelectedActors)
{
TSharedPtr<FExtender> Extender = MakeShareable(new FExtender);
Extender->AddMenuExtension("LevelViewportEdit", EExtensionHook::Before, CommandList,
FMenuExtensionDelegate::CreateStatic(&FillBlueprintOptions, SelectedActors));
return Extender.ToSharedRef();
}
#undef LOCTEXT_NAMESPACE