10967 lines
360 KiB
C++
10967 lines
360 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "BlueprintEditor.h"
|
|
#include "Widgets/Text/STextBlock.h"
|
|
#include "Components/PrimitiveComponent.h"
|
|
#include "Engine/Engine.h"
|
|
#include "Engine/BlueprintGeneratedClass.h"
|
|
#include "Engine/SimpleConstructionScript.h"
|
|
#include "Engine/UserDefinedEnum.h"
|
|
#include "StructUtils/UserDefinedStruct.h"
|
|
#include "Logging/TokenizedMessage.h"
|
|
#include "Misc/PackageName.h"
|
|
#include "AssetRegistry/AssetData.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "Widgets/Layout/SBorder.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Misc/FeedbackContext.h"
|
|
#include "UObject/MetaData.h"
|
|
#include "EdGraph/EdGraph.h"
|
|
#include "Layout/WidgetPath.h"
|
|
#include "Framework/Application/MenuStack.h"
|
|
#include "Framework/Application/SlateApplication.h"
|
|
#include "Widgets/Layout/SSpacer.h"
|
|
#include "Widgets/Images/SImage.h"
|
|
#include "Widgets/Input/SButton.h"
|
|
#include "Styling/AppStyle.h"
|
|
#include "Widgets/Input/SEditableTextBox.h"
|
|
#include "Widgets/Views/STableViewBase.h"
|
|
#include "Widgets/Views/STableRow.h"
|
|
#include "Widgets/Views/SListView.h"
|
|
#include "Dialog/SCustomDialog.h"
|
|
#include "SCheckBoxList.h"
|
|
#include "EdGraph/EdGraphSchema.h"
|
|
#include "EdGraphNode_Comment.h"
|
|
#include "Editor/UnrealEdEngine.h"
|
|
#include "Editor/Transactor.h"
|
|
#include "Settings/EditorExperimentalSettings.h"
|
|
#include "Settings/BlueprintEditorProjectSettings.h"
|
|
#include "GeneralProjectSettings.h"
|
|
#include "Kismet/GameplayStatics.h"
|
|
#include "Components/TimelineComponent.h"
|
|
#include "Engine/LevelStreamingDynamic.h"
|
|
#include "Dialogs/Dialogs.h"
|
|
#include "UnrealEdGlobals.h"
|
|
#include "Kismet2/KismetEditorUtilities.h"
|
|
#include "WorkflowOrientedApp/WorkflowUObjectDocuments.h"
|
|
#include "EdGraphSchema_K2.h"
|
|
#include "K2Node.h"
|
|
#include "EdGraphSchema_K2_Actions.h"
|
|
#include "K2Node_Event.h"
|
|
#include "K2Node_ActorBoundEvent.h"
|
|
#include "K2Node_CallFunction.h"
|
|
#include "K2Node_Variable.h"
|
|
#include "K2Node_CallFunctionOnMember.h"
|
|
#include "K2Node_CallParentFunction.h"
|
|
#include "K2Node_Tunnel.h"
|
|
#include "K2Node_Composite.h"
|
|
#include "K2Node_CustomEvent.h"
|
|
#include "K2Node_ExecutionSequence.h"
|
|
#include "K2Node_FunctionEntry.h"
|
|
#include "K2Node_FunctionResult.h"
|
|
#include "K2Node_Literal.h"
|
|
#include "K2Node_MacroInstance.h"
|
|
#include "K2Node_Select.h"
|
|
#include "K2Node_Switch.h"
|
|
#include "K2Node_SwitchInteger.h"
|
|
#include "K2Node_SwitchName.h"
|
|
#include "K2Node_Timeline.h"
|
|
#include "K2Node_VariableGet.h"
|
|
#include "K2Node_VariableSet.h"
|
|
#include "K2Node_SetFieldsInStruct.h"
|
|
#include "K2Node_Knot.h"
|
|
#include "Engine/LevelScriptBlueprint.h"
|
|
#include "Kismet2/Breakpoint.h"
|
|
#include "ScopedTransaction.h"
|
|
#include "Kismet2/KismetDebugUtilities.h"
|
|
#include "Kismet2/BlueprintEditorUtils.h"
|
|
#include "KismetCompilerModule.h"
|
|
#include "EngineUtils.h"
|
|
#include "EdGraphToken.h"
|
|
#include "Kismet2/CompilerResultsLog.h"
|
|
#include "EdGraphUtilities.h"
|
|
#include "IMessageLogListing.h"
|
|
#include "Logging/MessageLog.h"
|
|
#include "MessageLogModule.h"
|
|
#include "Misc/UObjectToken.h"
|
|
#include "BlueprintEditorCommands.h"
|
|
#include "GraphEditorActions.h"
|
|
#include "SNodePanel.h"
|
|
#include "Widgets/Docking/SDockTab.h"
|
|
#include "EditorClassUtils.h"
|
|
#include "IDocumentation.h"
|
|
#include "BlueprintNodeBinder.h"
|
|
#include "BlueprintFunctionNodeSpawner.h"
|
|
#include "SBlueprintEditorToolbar.h"
|
|
#include "FindInBlueprints.h"
|
|
#include "ImaginaryBlueprintData.h"
|
|
#include "SGraphTitleBar.h"
|
|
#include "Kismet2/Kismet2NameValidators.h"
|
|
#include "Kismet2/DebuggerCommands.h"
|
|
#include "Editor.h"
|
|
#include "IDetailsView.h"
|
|
#include "HAL/PlatformApplicationMisc.h"
|
|
#include "Stats/StatsHierarchical.h"
|
|
#include "BlueprintEditorLibrary.h"
|
|
#include "BlueprintNamespaceHelper.h"
|
|
#include "BlueprintNamespaceUtilities.h"
|
|
|
|
#include "BlueprintEditorTabs.h"
|
|
|
|
#include "ToolMenus.h"
|
|
#include "BlueprintEditorContext.h"
|
|
|
|
#include "Interfaces/IProjectManager.h"
|
|
#include "BlueprintDebugger.h"
|
|
|
|
// Core kismet tabs
|
|
#include "SGraphNode.h"
|
|
#include "SSubobjectBlueprintEditor.h"
|
|
#include "SubobjectDataSubsystem.h"
|
|
#include "SSCSEditorViewport.h"
|
|
#include "SKismetInspector.h"
|
|
#include "SBlueprintPalette.h"
|
|
#include "SBlueprintBookmarks.h"
|
|
#include "SBlueprintActionMenu.h"
|
|
#include "SMyBlueprint.h"
|
|
#include "SReplaceNodeReferences.h"
|
|
// End of core kismet tabs
|
|
|
|
// Debugging
|
|
#include "Debugging/SKismetDebuggingView.h"
|
|
#include "WatchPointViewer.h"
|
|
// End of debugging
|
|
|
|
// Misc diagnostics
|
|
#include "ObjectTools.h"
|
|
// End of misc diagnostics
|
|
|
|
#include "AssetRegistry/AssetRegistryModule.h"
|
|
#include "BlueprintEditorTabFactories.h"
|
|
#include "ClassViewerFilter.h"
|
|
#include "SPinTypeSelector.h"
|
|
#include "Animation/AnimBlueprint.h"
|
|
#include "AnimStateConduitNode.h"
|
|
#include "AnimationGraphSchema.h"
|
|
#include "AnimationGraph.h"
|
|
#include "AnimationStateGraph.h"
|
|
#include "AnimationStateMachineSchema.h"
|
|
#include "AnimationTransitionGraph.h"
|
|
#include "BlueprintEditorModes.h"
|
|
#include "BlueprintEditorSettings.h"
|
|
#include "Settings/EditorProjectSettings.h"
|
|
#include "K2Node_SwitchString.h"
|
|
#include "AnimGraphNode_StateMachineBase.h"
|
|
#include "AnimationStateMachineGraph.h"
|
|
|
|
#include "EngineAnalytics.h"
|
|
#include "AnalyticsEventAttribute.h"
|
|
#include "Interfaces/IAnalyticsProvider.h"
|
|
#include "SourceCodeNavigation.h"
|
|
#include "IHotReload.h"
|
|
|
|
#include "AudioDevice.h"
|
|
|
|
#include "SFixupSelfContextDlg.h"
|
|
|
|
// Blueprint merging
|
|
#include "Widgets/Input/SHyperlink.h"
|
|
#include "Framework/Commands/GenericCommands.h"
|
|
#include "Framework/Notifications/NotificationManager.h"
|
|
#include "Widgets/Notifications/SNotificationList.h"
|
|
|
|
// Focusing related nodes feature
|
|
#include "Framework/MultiBox/MultiBoxBuilder.h"
|
|
#include "Widgets/Input/SNumericEntryBox.h"
|
|
#include "Subsystems/AssetEditorSubsystem.h"
|
|
|
|
#include "BlendSpaceGraph.h"
|
|
#include "AnimationBlendSpaceSampleGraph.h"
|
|
#include "SSubobjectEditor.h"
|
|
#include "BlueprintActionDatabase.h"
|
|
#include "Algo/MinElement.h"
|
|
#include "Editor/EditorEngine.h"
|
|
#include "EditorViewportSelectabilityBridge.h"
|
|
|
|
DEFINE_LOG_CATEGORY_STATIC(LogBlueprintEditor, Log, All);
|
|
|
|
#define LOCTEXT_NAMESPACE "BlueprintEditor"
|
|
|
|
static int32 EnableAutomaticLibraryAssetLoading = 1;
|
|
static FAutoConsoleVariableRef CVarEnableAutomaticLibraryAssetLoading(
|
|
TEXT("bp.EnableAutomaticLibraryAssetLoading"),
|
|
EnableAutomaticLibraryAssetLoading,
|
|
TEXT("Should opening the BP editor load all macro and function library assets or not?\n0: Disable, 1: Enable (defaults to enabled)\nNodes defined in unloaded libraries will not show up in the context menu!"),
|
|
ECVF_Default);
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FSelectionDetailsSummoner
|
|
|
|
FSelectionDetailsSummoner::FSelectionDetailsSummoner(TSharedPtr<class FAssetEditorToolkit> InHostingApp)
|
|
: FWorkflowTabFactory(FBlueprintEditorTabs::DetailsID, InHostingApp)
|
|
{
|
|
TabLabel = LOCTEXT("DetailsView_TabTitle", "Details");
|
|
TabIcon = FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details");
|
|
|
|
bIsSingleton = true;
|
|
|
|
ViewMenuDescription = LOCTEXT("DetailsView_MenuTitle", "Details");
|
|
ViewMenuTooltip = LOCTEXT("DetailsView_ToolTip", "Shows the details view");
|
|
}
|
|
|
|
TSharedRef<SWidget> FSelectionDetailsSummoner::CreateTabBody(const FWorkflowTabSpawnInfo& Info) const
|
|
{
|
|
TSharedPtr<FBlueprintEditor> BlueprintEditorPtr = StaticCastSharedPtr<FBlueprintEditor>(HostingApp.Pin());
|
|
|
|
return BlueprintEditorPtr->GetInspector();
|
|
}
|
|
|
|
TSharedRef<SDockTab> FSelectionDetailsSummoner::SpawnTab(const FWorkflowTabSpawnInfo& Info) const
|
|
{
|
|
TSharedRef<SDockTab> Tab = FWorkflowTabFactory::SpawnTab(Info);
|
|
|
|
TSharedPtr<FBlueprintEditor> BlueprintEditorPtr = StaticCastSharedPtr<FBlueprintEditor>(HostingApp.Pin());
|
|
BlueprintEditorPtr->GetInspector()->SetOwnerTab(Tab);
|
|
|
|
BlueprintEditorPtr->GetInspector()->GetPropertyView()->SetHostTabManager(Info.TabManager);
|
|
|
|
return Tab;
|
|
}
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
// FBlueprintEditor
|
|
|
|
namespace BlueprintEditorImpl
|
|
{
|
|
static const float InstructionFadeDuration = 0.5f;
|
|
|
|
/** Class viewer filter proxy for imported namespace type selectors, controlled by a custom filter option */
|
|
class FImportedClassViewerFilterProxy : public IClassViewerFilter, public TSharedFromThis<FImportedClassViewerFilterProxy>
|
|
{
|
|
public:
|
|
FImportedClassViewerFilterProxy(TSharedPtr<IClassViewerFilter> InClassViewerFilter)
|
|
: ClassViewerFilter(InClassViewerFilter)
|
|
, bIsFilterEnabled(true)
|
|
{
|
|
}
|
|
|
|
// IClassViewerFilter interface
|
|
virtual bool IsClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const UClass* InClass, TSharedRef<FClassViewerFilterFuncs> InFilterFuncs) override
|
|
{
|
|
if (!GetDefault<UBlueprintEditorSettings>()->bEnableNamespaceFilteringFeatures)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bIsFilterEnabled && ClassViewerFilter.IsValid())
|
|
{
|
|
return ClassViewerFilter->IsClassAllowed(InInitOptions, InClass, InFilterFuncs);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual bool IsUnloadedClassAllowed(const FClassViewerInitializationOptions& InInitOptions, const TSharedRef< const IUnloadedBlueprintData > InBlueprint, TSharedRef< FClassViewerFilterFuncs > InFilterFuncs) override
|
|
{
|
|
if (!GetDefault<UBlueprintEditorSettings>()->bEnableNamespaceFilteringFeatures)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bIsFilterEnabled && ClassViewerFilter.IsValid())
|
|
{
|
|
return ClassViewerFilter->IsUnloadedClassAllowed(InInitOptions, InBlueprint, InFilterFuncs);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual void GetFilterOptions(TArray<TSharedRef<FClassViewerFilterOption>>& OutFilterOptions)
|
|
{
|
|
if (!GetDefault<UBlueprintEditorSettings>()->bEnableNamespaceFilteringFeatures)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (!ToggleFilterOption.IsValid())
|
|
{
|
|
ToggleFilterOption = MakeShared<FClassViewerFilterOption>();
|
|
ToggleFilterOption->bEnabled = bIsFilterEnabled;
|
|
ToggleFilterOption->LabelText = LOCTEXT("ClassViewerNamespaceFilterMenuOptionLabel", "Show Only Imported Types");
|
|
ToggleFilterOption->ToolTipText = LOCTEXT("ClassViewerNamespaceFilterMenuOptionToolTip", "Don't include non-imported class types.");
|
|
ToggleFilterOption->OnOptionChanged = FOnClassViewerFilterOptionChanged::CreateSP(this, &FImportedClassViewerFilterProxy::OnFilterOptionChanged);
|
|
}
|
|
|
|
OutFilterOptions.Add(ToggleFilterOption.ToSharedRef());
|
|
}
|
|
|
|
protected:
|
|
void OnFilterOptionChanged(bool bIsEnabled)
|
|
{
|
|
bIsFilterEnabled = bIsEnabled;
|
|
}
|
|
|
|
private:
|
|
/** Imported namespace class viewer filter. */
|
|
TSharedPtr<IClassViewerFilter> ClassViewerFilter;
|
|
|
|
/** Filter option for the class viewer settings menu. */
|
|
TSharedPtr<FClassViewerFilterOption> ToggleFilterOption;
|
|
|
|
/** Whether or not the filter is enabled. */
|
|
bool bIsFilterEnabled;
|
|
};
|
|
|
|
/** Pin type filter proxy for imported namespace type selectors, controlled by a custom filter option */
|
|
class FImportedPinTypeSelectorFilterProxy : public IPinTypeSelectorFilter, public TSharedFromThis<FImportedPinTypeSelectorFilterProxy>
|
|
{
|
|
DECLARE_MULTICAST_DELEGATE(FOnFilterChanged);
|
|
|
|
public:
|
|
FImportedPinTypeSelectorFilterProxy(TSharedPtr<IPinTypeSelectorFilter> InPinTypeSelectorFilter)
|
|
: PinTypeSelectorFilter(InPinTypeSelectorFilter)
|
|
, bIsFilterEnabled(true)
|
|
{
|
|
}
|
|
|
|
// IPinTypeSelectorFilter interface
|
|
virtual bool ShouldShowPinTypeTreeItem(FPinTypeTreeItem InItem) const override
|
|
{
|
|
if (!GetDefault<UBlueprintEditorSettings>()->bEnableNamespaceFilteringFeatures)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
if (bIsFilterEnabled && PinTypeSelectorFilter.IsValid())
|
|
{
|
|
return PinTypeSelectorFilter->ShouldShowPinTypeTreeItem(InItem);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
virtual FDelegateHandle RegisterOnFilterChanged(FSimpleDelegate InOnFilterChanged) override
|
|
{
|
|
return OnFilterChanged.Add(InOnFilterChanged);
|
|
}
|
|
|
|
virtual void UnregisterOnFilterChanged(FDelegateHandle InDelegateHandle) override
|
|
{
|
|
OnFilterChanged.Remove(InDelegateHandle);
|
|
}
|
|
|
|
virtual TSharedPtr<SWidget> GetFilterOptionsWidget() override
|
|
{
|
|
if (!GetDefault<UBlueprintEditorSettings>()->bEnableNamespaceFilteringFeatures)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
if (!FilterOptionsWidget.IsValid())
|
|
{
|
|
SAssignNew(FilterOptionsWidget, SCheckBox)
|
|
.IsChecked(this, &FImportedPinTypeSelectorFilterProxy::IsFilterToggleChecked)
|
|
.OnCheckStateChanged(this, &FImportedPinTypeSelectorFilterProxy::OnToggleFilter)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("PinTypeNamespaceFilterToggleOptionLabel", "Hide Non-Imported Types"))
|
|
];
|
|
}
|
|
|
|
return FilterOptionsWidget;
|
|
}
|
|
|
|
protected:
|
|
ECheckBoxState IsFilterToggleChecked() const
|
|
{
|
|
return bIsFilterEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
|
|
void OnToggleFilter(ECheckBoxState NewState)
|
|
{
|
|
bIsFilterEnabled = (NewState == ECheckBoxState::Checked);
|
|
|
|
// Notify any listeners that the filter has been changed.
|
|
OnFilterChanged.Broadcast();
|
|
}
|
|
|
|
private:
|
|
/** Imported namespace pin type selector filter. */
|
|
TSharedPtr<IPinTypeSelectorFilter> PinTypeSelectorFilter;
|
|
|
|
/** Cached filter options widget. */
|
|
TSharedPtr<SWidget> FilterOptionsWidget;
|
|
|
|
/** Delegate that's called whenever filter options are changed. */
|
|
FOnFilterChanged OnFilterChanged;
|
|
|
|
/** Whether or not the filter is enabled. */
|
|
bool bIsFilterEnabled;
|
|
};
|
|
|
|
/** Pin type filter for asset permissions */
|
|
class FPermissionsPinTypeSelectorFilter : public IPinTypeSelectorFilter, public TSharedFromThis<FPermissionsPinTypeSelectorFilter>
|
|
{
|
|
public:
|
|
FPermissionsPinTypeSelectorFilter(TConstArrayView<UBlueprint*> InBlueprints)
|
|
{
|
|
FAssetReferenceFilterContext Context;
|
|
for (UBlueprint* Blueprint : InBlueprints)
|
|
{
|
|
Context.AddReferencingAsset(FAssetData(Blueprint));
|
|
}
|
|
|
|
AssetReferenceFilter = GEditor->MakeAssetReferenceFilter(Context);
|
|
}
|
|
|
|
// IPinTypeSelectorFilter interface
|
|
virtual bool ShouldShowPinTypeTreeItem(FPinTypeTreeItem InItem) const override
|
|
{
|
|
FTopLevelAssetPath TopLevelAssetPath;
|
|
const FAssetData& AssetData = InItem->GetCachedAssetData();
|
|
if (AssetData.IsValid())
|
|
{
|
|
TopLevelAssetPath = FTopLevelAssetPath(AssetData.PackageName, AssetData.AssetName);
|
|
}
|
|
|
|
// First check pin type permissions
|
|
if (!FBlueprintActionDatabase::IsPinTypeAllowed(InItem->GetPinTypeNoResolve(), TopLevelAssetPath))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Then asset permissions
|
|
if(AssetReferenceFilter.IsValid() && AssetData.IsValid())
|
|
{
|
|
if (!AssetReferenceFilter->PassesFilter(AssetData))
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private:
|
|
/** Filter for asset references */
|
|
TSharedPtr<IAssetReferenceFilter> AssetReferenceFilter;
|
|
};
|
|
|
|
/**
|
|
* Utility function that will check to see if the specified graph has any
|
|
* nodes other than those that come default, pre-placed, in the graph.
|
|
*
|
|
* @param InGraph The graph to check.
|
|
* @return True if the graph has any nodes added by the user, otherwise false.
|
|
*/
|
|
static bool GraphHasUserPlacedNodes(UEdGraph const* InGraph);
|
|
|
|
/**
|
|
* Utility function that will check to see if the specified graph has any
|
|
* nodes that were default, pre-placed, in the graph.
|
|
*
|
|
* @param InGraph The graph to check.
|
|
* @return True if the graph has any pre-placed nodes, otherwise false.
|
|
*/
|
|
static bool GraphHasDefaultNode(UEdGraph const* InGraph);
|
|
|
|
/**
|
|
* Utility function that will set the global save-on-compile setting to the
|
|
* specified value.
|
|
*
|
|
* @param NewSetting The new save-on-compile setting that you want applied.
|
|
*/
|
|
static void SetSaveOnCompileSetting(ESaveOnCompile NewSetting);
|
|
|
|
/**
|
|
* Utility function used to determine what save-on-compile setting should be
|
|
* presented to the user.
|
|
*
|
|
* @param Editor The editor currently querying for the setting value.
|
|
* @param Option The setting to check for.
|
|
* @return False if the option isn't set, or if the save-on-compile is disabled for the blueprint being edited (otherwise true).
|
|
*/
|
|
static bool IsSaveOnCompileOptionSet(TWeakPtr<FBlueprintEditor> Editor, ESaveOnCompile Option);
|
|
|
|
/** Flips the value of the editor's "JumpToNodeErrors" setting. */
|
|
static void ToggleJumpToErrorNodeSetting();
|
|
|
|
/**
|
|
* Utility function that will check to see if the "Jump to Error Nodes"
|
|
* setting is enabled.
|
|
*
|
|
* @return True if UBlueprintEditorSettings::bJumpToNodeErrors is set, otherwise false.
|
|
*/
|
|
static bool IsJumpToErrorNodeOptionSet();
|
|
|
|
/**
|
|
* Searches through a blueprint, looking for the most severe error'ing node.
|
|
*
|
|
* @param Blueprint The blueprint to search through.
|
|
* @param Severity Defines the severity of the error/warning to search for.
|
|
* @return The first node found with the specified error.
|
|
*/
|
|
static UEdGraphNode* FindNodeWithError(UBlueprint* Blueprint, EMessageSeverity::Type Severity = EMessageSeverity::Error);
|
|
|
|
/**
|
|
* Searches through an error log, looking for the most severe error'ing node.
|
|
*
|
|
* @param ErrorLog The error log you want to search through.
|
|
* @param Severity Defines the severity of the error/warning to search for.
|
|
* @return The first node found with the specified error.
|
|
*/
|
|
static UEdGraphNode* FindNodeWithError(FCompilerResultsLog const& ErrorLog, EMessageSeverity::Type Severity = EMessageSeverity::Error);
|
|
}
|
|
|
|
static bool BlueprintEditorImpl::GraphHasUserPlacedNodes(UEdGraph const* InGraph)
|
|
{
|
|
bool bHasUserPlacedNodes = false;
|
|
|
|
for (UEdGraphNode const* Node : InGraph->Nodes)
|
|
{
|
|
if (Node == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (!Node->GetPackage()->GetMetaData().HasValue(Node, FNodeMetadata::DefaultGraphNode))
|
|
{
|
|
bHasUserPlacedNodes = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bHasUserPlacedNodes;
|
|
}
|
|
|
|
static bool BlueprintEditorImpl::GraphHasDefaultNode(UEdGraph const* InGraph)
|
|
{
|
|
bool bHasDefaultNodes = false;
|
|
|
|
for (UEdGraphNode const* Node : InGraph->Nodes)
|
|
{
|
|
if (Node == nullptr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
if (Node->GetPackage()->GetMetaData().HasValue(Node, FNodeMetadata::DefaultGraphNode) && Node->IsNodeEnabled())
|
|
{
|
|
bHasDefaultNodes = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
return bHasDefaultNodes;
|
|
}
|
|
|
|
static void BlueprintEditorImpl::SetSaveOnCompileSetting(ESaveOnCompile NewSetting)
|
|
{
|
|
UBlueprintEditorSettings* Settings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
Settings->SaveOnCompile = NewSetting;
|
|
Settings->SaveConfig();
|
|
}
|
|
|
|
static bool BlueprintEditorImpl::IsSaveOnCompileOptionSet(TWeakPtr<FBlueprintEditor> Editor, ESaveOnCompile Option)
|
|
{
|
|
const UBlueprintEditorSettings* Settings = GetDefault<UBlueprintEditorSettings>();
|
|
|
|
ESaveOnCompile CurrentSetting = Settings->SaveOnCompile;
|
|
if (!Editor.IsValid() || !Editor.Pin()->IsSaveOnCompileEnabled())
|
|
{
|
|
// if save-on-compile is disabled for the blueprint, then we want to
|
|
// show "Never" as being selected
|
|
//
|
|
// @TODO: a tooltip explaining why would be nice too
|
|
CurrentSetting = SoC_Never;
|
|
}
|
|
|
|
return (CurrentSetting == Option);
|
|
}
|
|
|
|
static void BlueprintEditorImpl::ToggleJumpToErrorNodeSetting()
|
|
{
|
|
UBlueprintEditorSettings* Settings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
Settings->bJumpToNodeErrors = !Settings->bJumpToNodeErrors;
|
|
Settings->SaveConfig();
|
|
}
|
|
|
|
static bool BlueprintEditorImpl::IsJumpToErrorNodeOptionSet()
|
|
{
|
|
const UBlueprintEditorSettings* Settings = GetDefault<UBlueprintEditorSettings>();
|
|
return Settings->bJumpToNodeErrors;
|
|
}
|
|
|
|
static UEdGraphNode* BlueprintEditorImpl::FindNodeWithError(UBlueprint* Blueprint, EMessageSeverity::Type Severity/* = EMessageSeverity::Error*/)
|
|
{
|
|
TArray<UEdGraph*> Graphs;
|
|
Blueprint->GetAllGraphs(Graphs);
|
|
|
|
UEdGraphNode* ChoiceNode = nullptr;
|
|
for (UEdGraph* Graph : Graphs)
|
|
{
|
|
for (UEdGraphNode* Node : Graph->Nodes)
|
|
{
|
|
if (Node && Node->bHasCompilerMessage && !Node->ErrorMsg.IsEmpty() && (Node->ErrorType <= Severity))
|
|
{
|
|
if ((ChoiceNode == nullptr) || (ChoiceNode->ErrorType > Node->ErrorType))
|
|
{
|
|
ChoiceNode = Node;
|
|
if (ChoiceNode->ErrorType == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return ChoiceNode;
|
|
}
|
|
|
|
static UEdGraphNode* BlueprintEditorImpl::FindNodeWithError(FCompilerResultsLog const& ErrorLog, EMessageSeverity::Type Severity/* = EMessageSeverity::Error*/)
|
|
{
|
|
UEdGraphNode* ChoiceNode = nullptr;
|
|
for (TWeakObjectPtr<UEdGraphNode> NodePtr : ErrorLog.AnnotatedNodes)
|
|
{
|
|
UEdGraphNode* Node = NodePtr.Get();
|
|
if ((Node != nullptr) && (Node->ErrorType <= Severity))
|
|
{
|
|
if ((ChoiceNode == nullptr) || (Node->ErrorType < ChoiceNode->ErrorType))
|
|
{
|
|
ChoiceNode = Node;
|
|
if (ChoiceNode->ErrorType == 0)
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ChoiceNode;
|
|
}
|
|
|
|
|
|
FName FBlueprintEditor::SelectionState_MyBlueprint(TEXT("MyBlueprint"));
|
|
FName FBlueprintEditor::SelectionState_Components(TEXT("Components"));
|
|
FName FBlueprintEditor::SelectionState_Graph(TEXT("Graph"));
|
|
FName FBlueprintEditor::SelectionState_ClassSettings(TEXT("ClassSettings"));
|
|
FName FBlueprintEditor::SelectionState_ClassDefaults(TEXT("ClassDefaults"));
|
|
|
|
|
|
bool FBlueprintEditor::IsASubGraph( const UEdGraph* GraphPtr )
|
|
{
|
|
if( GraphPtr && GraphPtr->GetOuter() )
|
|
{
|
|
//Check whether the outer is a composite node
|
|
if( GraphPtr->GetOuter()->IsA( UK2Node_Composite::StaticClass() ) )
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/** Util for finding a glyph for a graph */
|
|
const FSlateBrush* FBlueprintEditor::GetGlyphForGraph(const UEdGraph* Graph, bool bInLargeIcon)
|
|
{
|
|
const FSlateBrush* ReturnValue = FAppStyle::GetBrush( bInLargeIcon ? TEXT("GraphEditor.Function_24x") : TEXT("GraphEditor.Function_16x") );
|
|
|
|
check(Graph != nullptr);
|
|
const UEdGraphSchema* Schema = Graph->GetSchema();
|
|
if (Schema != nullptr)
|
|
{
|
|
const EGraphType GraphType = Schema->GetGraphType(Graph);
|
|
switch (GraphType)
|
|
{
|
|
default:
|
|
case GT_StateMachine:
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush( bInLargeIcon ? TEXT("GraphEditor.StateMachine_24x") : TEXT("GraphEditor.StateMachine_16x") );
|
|
}
|
|
break;
|
|
case GT_Function:
|
|
{
|
|
if ( Graph->IsA(UAnimationTransitionGraph::StaticClass()) )
|
|
{
|
|
UObject* GraphOuter = Graph->GetOuter();
|
|
if ( GraphOuter != nullptr && GraphOuter->IsA(UAnimStateConduitNode::StaticClass()) )
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush( bInLargeIcon ? TEXT("GraphEditor.Conduit_24x") : TEXT("GraphEditor.Conduit_16x") );
|
|
}
|
|
else
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush( bInLargeIcon ? TEXT("GraphEditor.Rule_24x") : TEXT("GraphEditor.Rule_16x") );
|
|
}
|
|
}
|
|
else
|
|
{
|
|
//Check for subgraph
|
|
if( IsASubGraph( Graph ) )
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush( bInLargeIcon ? TEXT("GraphEditor.SubGraph_24x") : TEXT("GraphEditor.SubGraph_16x") );
|
|
}
|
|
else
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush( bInLargeIcon ? TEXT("GraphEditor.Function_24x") : TEXT("GraphEditor.Function_16x") );
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case GT_Macro:
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush( bInLargeIcon ? TEXT("GraphEditor.Macro_24x") : TEXT("GraphEditor.Macro_16x") );
|
|
}
|
|
break;
|
|
case GT_Ubergraph:
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush( bInLargeIcon ? TEXT("GraphEditor.EventGraph_24x") : TEXT("GraphEditor.EventGraph_16x") );
|
|
}
|
|
break;
|
|
case GT_Animation:
|
|
{
|
|
if ( Graph->IsA(UAnimationStateGraph::StaticClass()) )
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush( bInLargeIcon ? TEXT("GraphEditor.State_24x") : TEXT("GraphEditor.State_16x") );
|
|
}
|
|
else if ( Graph->IsA(UBlendSpaceGraph::StaticClass()) )
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush(TEXT("BlendSpace.Graph") );
|
|
}
|
|
else if ( Graph->IsA(UAnimationBlendSpaceSampleGraph::StaticClass()) )
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush(TEXT("BlendSpace.SampleGraph") );
|
|
}
|
|
else
|
|
{
|
|
// If it has overriden an interface, show it as a function
|
|
if ( Graph->InterfaceGuid.IsValid() )
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush(bInLargeIcon ? TEXT("GraphEditor.Function_24x") : TEXT("GraphEditor.Function_16x"));
|
|
}
|
|
else
|
|
{
|
|
ReturnValue = FAppStyle::GetBrush(bInLargeIcon ? TEXT("GraphEditor.Animation_24x") : TEXT("GraphEditor.Animation_16x"));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ReturnValue;
|
|
}
|
|
|
|
FSlateBrush const* FBlueprintEditor::GetVarIconAndColor(const UStruct* VarScope, FName VarName, FSlateColor& IconColorOut, FSlateBrush const*& SecondaryBrushOut, FSlateColor& SecondaryColorOut)
|
|
{
|
|
SecondaryBrushOut = nullptr;
|
|
if (VarScope != nullptr)
|
|
{
|
|
FProperty* Property = FindFProperty<FProperty>(VarScope, VarName);
|
|
return GetVarIconAndColorFromProperty(Property, IconColorOut, SecondaryBrushOut, SecondaryColorOut);
|
|
}
|
|
return FAppStyle::GetBrush(TEXT("Kismet.AllClasses.VariableIcon"));
|
|
}
|
|
|
|
FSlateBrush const* FBlueprintEditor::GetVarIconAndColorFromProperty(const FProperty* Property, FSlateColor& IconColorOut, FSlateBrush const*& SecondaryBrushOut, FSlateColor& SecondaryColorOut)
|
|
{
|
|
SecondaryBrushOut = nullptr;
|
|
if (Property != nullptr)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
FEdGraphPinType PinType;
|
|
if (K2Schema->ConvertPropertyToPinType(Property, PinType)) // use schema to get the color
|
|
{
|
|
return GetVarIconAndColorFromPinType(PinType, IconColorOut, SecondaryBrushOut, SecondaryColorOut);
|
|
}
|
|
}
|
|
return FAppStyle::GetBrush(TEXT("Kismet.AllClasses.VariableIcon"));
|
|
}
|
|
|
|
FSlateBrush const* FBlueprintEditor::GetVarIconAndColorFromPinType(const FEdGraphPinType& PinType,
|
|
FSlateColor& IconColorOut, FSlateBrush const*& SecondaryBrushOut, FSlateColor& SecondaryColorOut)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
IconColorOut = K2Schema->GetPinTypeColor(PinType);
|
|
SecondaryBrushOut = FBlueprintEditorUtils::GetSecondaryIconFromPin(PinType);
|
|
SecondaryColorOut = K2Schema->GetSecondaryPinTypeColor(PinType);
|
|
return FBlueprintEditorUtils::GetIconFromPin(PinType);
|
|
}
|
|
|
|
bool FBlueprintEditor::IsInAScriptingMode() const
|
|
{
|
|
return IsModeCurrent(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode) || IsModeCurrent(FBlueprintEditorApplicationModes::BlueprintMacroMode);
|
|
}
|
|
|
|
bool FBlueprintEditor::OnRequestClose(EAssetEditorCloseReason InCloseReason)
|
|
{
|
|
return FWorkflowCentricApplication::OnRequestClose(InCloseReason);
|
|
}
|
|
|
|
void FBlueprintEditor::OnClose()
|
|
{
|
|
// Also close the Find Results tab if we're not in full edit mode.
|
|
TSharedPtr<SDockTab> FindResultsTab = TabManager->FindExistingLiveTab(FBlueprintEditorTabs::FindResultsID);
|
|
if (FindResultsTab.IsValid() && !IsInAScriptingMode())
|
|
{
|
|
FindResultsTab->RequestCloseTab();
|
|
}
|
|
|
|
// Close the Replace References Tab so it doesn't open the next time we do
|
|
TSharedPtr<SDockTab> ReplaceRefsTab = TabManager->FindExistingLiveTab(FBlueprintEditorTabs::ReplaceNodeReferencesID);
|
|
if (ReplaceRefsTab.IsValid())
|
|
{
|
|
ReplaceRefsTab->RequestCloseTab();
|
|
}
|
|
|
|
bEditorMarkedAsClosed = true;
|
|
|
|
FWorkflowCentricApplication::OnClose();
|
|
}
|
|
|
|
bool FBlueprintEditor::InEditingMode() const
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (!FSlateApplication::Get().InKismetDebuggingMode())
|
|
{
|
|
if (!IsPlayInEditorActive())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (Blueprint)
|
|
{
|
|
if (Blueprint->CanAlwaysRecompileWhilePlayingInEditor())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (ModifyDuringPIEStatus == ESafeToModifyDuringPIEStatus::Unknown)
|
|
{
|
|
// Check for project settings or editor preferences that allow this anyways
|
|
ModifyDuringPIEStatus = ESafeToModifyDuringPIEStatus::Safe;
|
|
|
|
auto CheckClassList = [](UClass* TestClass, const TArray<TSoftClassPtr<UObject>>& ClassList)
|
|
{
|
|
for (const TSoftClassPtr<UObject>& SoftBaseClass : ClassList)
|
|
{
|
|
// Safe to call Get() instead of doing a load, as we can't possibly be derived from an unloaded class
|
|
if (UClass* BaseClass = SoftBaseClass.Get())
|
|
{
|
|
if (TestClass->IsChildOf(BaseClass))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
};
|
|
|
|
UClass* TestClass = Blueprint->GeneratedClass;
|
|
if(!TestClass)
|
|
{
|
|
// fall back to user parent class if there's no generated class,
|
|
// this could be the case for newly created blueprints
|
|
TestClass = Blueprint->ParentClass;
|
|
}
|
|
if (TestClass)
|
|
{
|
|
if (CheckClassList(TestClass, GetDefault<UBlueprintEditorSettings>()->BaseClassesToDisallowRecompilingDuringPlayInEditor) ||
|
|
CheckClassList(TestClass, GetDefault<UBlueprintEditorProjectSettings>()->BaseClassesToDisallowRecompilingDuringPlayInEditor))
|
|
{
|
|
ModifyDuringPIEStatus = ESafeToModifyDuringPIEStatus::NotSafe;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ModifyDuringPIEStatus == ESafeToModifyDuringPIEStatus::Safe;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FBlueprintEditor::IsCompilingEnabled() const
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
return Blueprint && Blueprint->BlueprintType != BPTYPE_MacroLibrary && InEditingMode();
|
|
}
|
|
|
|
bool FBlueprintEditor::IsPlayInEditorActive() const
|
|
{
|
|
return GEditor->PlayWorld != nullptr;
|
|
}
|
|
|
|
EVisibility FBlueprintEditor::IsDebuggerVisible() const
|
|
{
|
|
return IsPlayInEditorActive() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
int32 FBlueprintEditor::GetNumberOfSelectedNodes() const
|
|
{
|
|
return GetSelectedNodes().Num();
|
|
}
|
|
|
|
FGraphPanelSelectionSet FBlueprintEditor::GetSelectedNodes() const
|
|
{
|
|
FGraphPanelSelectionSet CurrentSelection;
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
CurrentSelection = FocusedGraphEd->GetSelectedNodes();
|
|
}
|
|
return CurrentSelection;
|
|
}
|
|
|
|
UEdGraphNode* FBlueprintEditor::GetSingleSelectedNode() const
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
return (SelectedNodes.Num() == 1) ? Cast<UEdGraphNode>(*SelectedNodes.CreateConstIterator()) : nullptr;
|
|
}
|
|
|
|
void FBlueprintEditor::AnalyticsTrackNodeEvent(UBlueprint* Blueprint, UEdGraphNode *GraphNode, bool bNodeDelete) const
|
|
{
|
|
if(Blueprint && GraphNode && FEngineAnalytics::IsAvailable())
|
|
{
|
|
// we'd like to see if this was happening in normal blueprint editor or persona
|
|
const FString EditorName = Cast<UAnimBlueprint>(Blueprint) != nullptr ? TEXT("Persona") : TEXT("BlueprintEditor");
|
|
|
|
// Build Node Details
|
|
const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>();
|
|
FString ProjectID = ProjectSettings.ProjectID.ToString();
|
|
TArray<FAnalyticsEventAttribute> NodeAttributes;
|
|
NodeAttributes.Add(FAnalyticsEventAttribute(TEXT("ProjectId"), ProjectID));
|
|
NodeAttributes.Add(FAnalyticsEventAttribute(TEXT("BlueprintId"), Blueprint->GetBlueprintGuid().ToString()));
|
|
TArray<TKeyValuePair<FString, FString>> Attributes;
|
|
|
|
if (UK2Node* K2Node = Cast<UK2Node>(GraphNode))
|
|
{
|
|
K2Node->GetNodeAttributes(Attributes);
|
|
}
|
|
else if (UEdGraphNode_Comment* CommentNode = Cast<UEdGraphNode_Comment>(GraphNode))
|
|
{
|
|
Attributes.Add(TKeyValuePair<FString, FString>(TEXT("Type"), TEXT("Comment")));
|
|
Attributes.Add(TKeyValuePair<FString, FString>(TEXT("Class"), CommentNode->GetClass()->GetName()));
|
|
Attributes.Add(TKeyValuePair<FString, FString>(TEXT("Name"), CommentNode->GetName()));
|
|
}
|
|
if (Attributes.Num() > 0)
|
|
{
|
|
// Build Node Attributes
|
|
for (const TKeyValuePair<FString, FString>& Attribute : Attributes)
|
|
{
|
|
NodeAttributes.Add(FAnalyticsEventAttribute(Attribute.Key, Attribute.Value));
|
|
}
|
|
// Send Analytics event
|
|
FString EventType = bNodeDelete ? FString::Printf(TEXT("Editor.Usage.%s.NodeDeleted"), *EditorName) :
|
|
FString::Printf(TEXT("Editor.Usage.%s.NodeCreated"), *EditorName);
|
|
FEngineAnalytics::GetProvider().RecordEvent(EventType, NodeAttributes);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::AnalyticsTrackCompileEvent(UBlueprint* Blueprint, int32 NumErrors, int32 NumWarnings) const
|
|
{
|
|
if (Blueprint && FEngineAnalytics::IsAvailable())
|
|
{
|
|
// we'd like to see if this was happening in normal blueprint editor or persona
|
|
const FString EditorName = Cast<UAnimBlueprint>(Blueprint) != nullptr ? TEXT("Persona") : TEXT("BlueprintEditor");
|
|
|
|
// Build Node Details
|
|
const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>();
|
|
FString ProjectID = ProjectSettings.ProjectID.ToString();
|
|
|
|
const bool bSuccess = NumErrors == 0;
|
|
TArray<FAnalyticsEventAttribute> Attributes;
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("ProjectId"), ProjectID));
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("BlueprintId"), Blueprint->GetBlueprintGuid().ToString()));
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("Success"), bSuccess? TEXT("True") : TEXT("False")));
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("NumErrors"), FString::FromInt(NumErrors)));
|
|
Attributes.Add(FAnalyticsEventAttribute(TEXT("NumWarnings"), FString::FromInt(NumWarnings)));
|
|
|
|
// Send Analytics event
|
|
FEngineAnalytics::GetProvider().RecordEvent(FString::Printf(TEXT("Editor.Usage.%s.Compile"), *EditorName), Attributes);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::GetPinTypeSelectorFilters(TArray<TSharedPtr<IPinTypeSelectorFilter>>& OutFilters) const
|
|
{
|
|
OutFilters.Add(ImportedPinTypeSelectorFilter);
|
|
OutFilters.Add(PermissionsPinTypeSelectorFilter);
|
|
}
|
|
|
|
void FBlueprintEditor::RefreshEditors(ERefreshBlueprintEditorReason::Type Reason)
|
|
{
|
|
bool bForceFocusOnSelectedNodes = false;
|
|
|
|
if (CurrentUISelection == SelectionState_MyBlueprint)
|
|
{
|
|
// Handled below, here to avoid tripping the ensure
|
|
}
|
|
else if (CurrentUISelection == SelectionState_Components)
|
|
{
|
|
if(SubobjectEditor.IsValid())
|
|
{
|
|
SubobjectEditor->RefreshSelectionDetails();
|
|
}
|
|
}
|
|
else if (CurrentUISelection == SelectionState_Graph)
|
|
{
|
|
bForceFocusOnSelectedNodes = true;
|
|
}
|
|
else if (CurrentUISelection == SelectionState_ClassSettings)
|
|
{
|
|
// No need for a refresh, the Blueprint object didn't change
|
|
}
|
|
else if (CurrentUISelection == SelectionState_ClassDefaults)
|
|
{
|
|
StartEditingDefaults(/*bAutoFocus=*/ false, true);
|
|
}
|
|
|
|
// Remove any tabs are that are pending kill or otherwise invalid UObject pointers.
|
|
DocumentManager->CleanInvalidTabs();
|
|
|
|
//@TODO: Should determine when we need to do the invalid/refresh business and if the graph node selection change
|
|
// under non-compiles is necessary (except when the selection mode is appropriate, as already detected above)
|
|
if (Reason != ERefreshBlueprintEditorReason::BlueprintCompiled)
|
|
{
|
|
DocumentManager->RefreshAllTabs();
|
|
|
|
bForceFocusOnSelectedNodes = true;
|
|
}
|
|
|
|
if (bForceFocusOnSelectedNodes)
|
|
{
|
|
FocusInspectorOnGraphSelection(GetSelectedNodes(), /*bForceRefresh=*/ true);
|
|
}
|
|
|
|
if (ReplaceReferencesWidget.IsValid())
|
|
{
|
|
ReplaceReferencesWidget->Refresh();
|
|
}
|
|
|
|
if (MyBlueprintWidget.IsValid())
|
|
{
|
|
MyBlueprintWidget->Refresh();
|
|
}
|
|
|
|
if(SubobjectEditor.IsValid())
|
|
{
|
|
SubobjectEditor->RefreshComponentTypesList();
|
|
SubobjectEditor->UpdateTree();
|
|
|
|
// Note: Don't pass 'true' here because we don't want the preview actor to be reconstructed until after Blueprint modification is complete.
|
|
UpdateSubobjectPreview();
|
|
}
|
|
|
|
if (BookmarksWidget.IsValid())
|
|
{
|
|
BookmarksWidget->RefreshBookmarksTree();
|
|
}
|
|
|
|
// Note: There is an optimization inside of ShowDetailsForSingleObject() that skips the refresh if the object being selected is the same as the previous object.
|
|
// The SKismetInspector class is shared between both Defaults mode and Components mode, but in Defaults mode the object selected is always going to be the CDO. Given
|
|
// that the selection does not really change, we force it to refresh and skip the optimization. Otherwise, some things may not work correctly in Defaults mode. For
|
|
// example, transform details are customized and the rotation value is cached at customization time; if we don't force refresh here, then after an undo of a previous
|
|
// rotation edit, transform details won't be re-customized and thus the cached rotation value will be stale, resulting in an invalid rotation value on the next edit.
|
|
//@TODO: Probably not always necessary
|
|
RefreshStandAloneDefaultsEditor();
|
|
|
|
// Update associated controls like the function editor
|
|
BroadcastRefresh();
|
|
}
|
|
|
|
void FBlueprintEditor::RefreshMyBlueprint()
|
|
{
|
|
if (MyBlueprintWidget.IsValid())
|
|
{
|
|
MyBlueprintWidget->Refresh();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::RefreshInspector()
|
|
{
|
|
if (Inspector.IsValid())
|
|
{
|
|
Inspector->GetPropertyView()->ForceRefresh();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SetUISelectionState(FName SelectionOwner)
|
|
{
|
|
if ( SelectionOwner != CurrentUISelection )
|
|
{
|
|
ClearSelectionStateFor(CurrentUISelection);
|
|
|
|
CurrentUISelection = SelectionOwner;
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::AddToSelection(UEdGraphNode* InNode)
|
|
{
|
|
FocusedGraphEdPtr.Pin()->SetNodeSelection(InNode, true);
|
|
}
|
|
|
|
void FBlueprintEditor::ClearSelectionStateFor(FName SelectionOwner)
|
|
{
|
|
if ( SelectionOwner == SelectionState_Graph )
|
|
{
|
|
TArray< TSharedPtr<SDockTab> > GraphEditorTabs;
|
|
DocumentManager->FindAllTabsForFactory(GraphEditorTabFactoryPtr, /*out*/ GraphEditorTabs);
|
|
|
|
for (TSharedPtr<SDockTab>& GraphEditorTab : GraphEditorTabs)
|
|
{
|
|
TSharedRef<SGraphEditor> Editor = StaticCastSharedRef<SGraphEditor>((GraphEditorTab)->GetContent());
|
|
|
|
Editor->ClearSelectionSet();
|
|
}
|
|
}
|
|
else if ( SelectionOwner == SelectionState_Components )
|
|
{
|
|
if ( SubobjectEditor.IsValid() )
|
|
{
|
|
SubobjectEditor->ClearSelection();
|
|
}
|
|
}
|
|
else if ( SelectionOwner == SelectionState_MyBlueprint )
|
|
{
|
|
if ( MyBlueprintWidget.IsValid() )
|
|
{
|
|
MyBlueprintWidget->ClearGraphActionMenuSelection();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SummonSearchUI(bool bSetFindWithinBlueprint, FString NewSearchTerms, bool bSelectFirstResult)
|
|
{
|
|
TSharedPtr<SFindInBlueprints> FindResultsToUse;
|
|
|
|
if (bSetFindWithinBlueprint)
|
|
{
|
|
FindResultsToUse = FindResults;
|
|
TabManager->TryInvokeTab(FBlueprintEditorTabs::FindResultsID);
|
|
}
|
|
else
|
|
{
|
|
FindResultsToUse = FFindInBlueprintSearchManager::Get().GetGlobalFindResults();
|
|
}
|
|
|
|
if (FindResultsToUse.IsValid())
|
|
{
|
|
FindResultsToUse->FocusForUse(bSetFindWithinBlueprint, NewSearchTerms, bSelectFirstResult);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SummonFindAndReplaceUI()
|
|
{
|
|
TabManager->TryInvokeTab(FBlueprintEditorTabs::ReplaceNodeReferencesID);
|
|
}
|
|
|
|
void FBlueprintEditor::EnableSubobjectPreview(bool bEnable)
|
|
{
|
|
if(SubobjectViewport.IsValid())
|
|
{
|
|
SubobjectViewport->EnablePreview(bEnable);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::UpdateSubobjectPreview(bool bUpdateNow)
|
|
{
|
|
// refresh widget
|
|
if(SubobjectViewport.IsValid())
|
|
{
|
|
TSharedPtr<SDockTab> OwnerTab = Inspector->GetOwnerTab();
|
|
if ( OwnerTab.IsValid() )
|
|
{
|
|
bUpdateNow &= OwnerTab->IsForeground();
|
|
}
|
|
|
|
// Only request a refresh immediately if the viewport tab is in the foreground.
|
|
SubobjectViewport->RequestRefresh(false, bUpdateNow);
|
|
}
|
|
}
|
|
|
|
UObject* FBlueprintEditor::GetSubobjectEditorObjectContext() const
|
|
{
|
|
// Return the current CDO that was last generated for the class
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if(Blueprint != nullptr && Blueprint->GeneratedClass != nullptr)
|
|
{
|
|
return Blueprint->GeneratedClass->GetDefaultObject();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void FBlueprintEditor::OnSelectionUpdated(const TArray<TSharedPtr<FSubobjectEditorTreeNode>>& SelectedNodes)
|
|
{
|
|
// Check whether the active component visualizer is relevant for the selected components ...
|
|
if (const TSharedPtr<FComponentVisualizer> ComponentVisualizer = GUnrealEd->ComponentVisManager.GetActiveComponentVis())
|
|
{
|
|
bool bClearActiveVisualizer = true;
|
|
for (const FSubobjectEditorTreeNodePtrType& SelectedNode : SelectedNodes)
|
|
{
|
|
const FSubobjectData* const Data = SelectedNode->GetDataSource();
|
|
const UActorComponent* const Component = Data ? Data->FindComponentInstanceInActor(GetPreviewActor()) : nullptr;
|
|
if (Component != nullptr && Component->IsRegistered() && Component == ComponentVisualizer->GetEditedComponent())
|
|
{
|
|
bClearActiveVisualizer = false;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// If the relevant component for the active visualizer is no longer selected, clear the active visualizer.
|
|
if (bClearActiveVisualizer)
|
|
{
|
|
GUnrealEd->ComponentVisManager.ClearActiveComponentVis();
|
|
}
|
|
}
|
|
|
|
if (SubobjectViewport.IsValid())
|
|
{
|
|
SubobjectViewport->OnComponentSelectionChanged();
|
|
}
|
|
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
check(Blueprint != nullptr && Blueprint->SimpleConstructionScript != nullptr);
|
|
|
|
// Update the selection visualization
|
|
AActor* EditorActorInstance = Blueprint->SimpleConstructionScript->GetComponentEditorActorInstance();
|
|
if (EditorActorInstance != nullptr)
|
|
{
|
|
for (UActorComponent* Component : EditorActorInstance->GetComponents())
|
|
{
|
|
if (UPrimitiveComponent* PrimitiveComponent = Cast<UPrimitiveComponent>(Component))
|
|
{
|
|
PrimitiveComponent->PushSelectionToProxy();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Inspector.IsValid())
|
|
{
|
|
// Clear the my blueprints selection
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
SetUISelectionState(FBlueprintEditor::SelectionState_Components);
|
|
}
|
|
|
|
// Convert the selection set to an array of UObject* pointers
|
|
FText InspectorTitle = FText::GetEmpty();
|
|
TArray<UObject*> InspectorObjects;
|
|
bool bShowComponents = true;
|
|
InspectorObjects.Empty(SelectedNodes.Num());
|
|
for (FSubobjectEditorTreeNodePtrType NodePtr : SelectedNodes)
|
|
{
|
|
const FSubobjectData* NodeData = NodePtr ? NodePtr->GetDataSource() : nullptr;
|
|
if (NodeData)
|
|
{
|
|
if (const AActor* Actor = NodeData->GetObject<AActor>())
|
|
{
|
|
if (const AActor* DefaultActor = NodeData->GetObjectForBlueprint<AActor>(GetBlueprintObj()))
|
|
{
|
|
InspectorObjects.Add(const_cast<AActor*>(DefaultActor));
|
|
|
|
FString Title;
|
|
DefaultActor->GetName(Title);
|
|
InspectorTitle = FText::FromString(Title);
|
|
bShowComponents = false;
|
|
|
|
TryInvokingDetailsTab();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
const UActorComponent* EditableComponent = NodeData->GetObjectForBlueprint<UActorComponent>(GetBlueprintObj());
|
|
if (EditableComponent)
|
|
{
|
|
InspectorTitle = FText::FromString(NodePtr->GetDisplayString());
|
|
InspectorObjects.Add(const_cast<UActorComponent*>(EditableComponent));
|
|
}
|
|
|
|
if (SubobjectViewport.IsValid())
|
|
{
|
|
TSharedPtr<SDockTab> OwnerTab = SubobjectViewport->GetOwnerTab();
|
|
if (OwnerTab.IsValid())
|
|
{
|
|
OwnerTab->FlashTab();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update the details panel
|
|
SKismetInspector::FShowDetailsOptions Options(InspectorTitle, true);
|
|
Options.bShowComponents = bShowComponents;
|
|
Inspector->ShowDetailsForObjects(InspectorObjects, Options);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnComponentDoubleClicked(TSharedPtr<FSubobjectEditorTreeNode> Node)
|
|
{
|
|
TSharedPtr<SDockTab> OwnerTab = Inspector->GetOwnerTab();
|
|
if ( OwnerTab.IsValid() )
|
|
{
|
|
GetTabManager()->TryInvokeTab(FBlueprintEditorTabs::SCSViewportID);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnComponentAddedToBlueprint(const FSubobjectData& NewSubobjectData)
|
|
{
|
|
// Determine if we've added to a Blueprint instance that we're editing within this context.
|
|
bool bIsEditingEventTarget = false;
|
|
if (const UBlueprint* TargetBlueprint = NewSubobjectData.GetBlueprint())
|
|
{
|
|
for (UObject* EditorObject : GetEditingObjects())
|
|
{
|
|
if (EditorObject == TargetBlueprint)
|
|
{
|
|
bIsEditingEventTarget = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only handle add events if the editor context includes its targeted Blueprint object.
|
|
if (!bIsEditingEventTarget)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const UObject* NewSubobject = NewSubobjectData.GetObject();
|
|
check(NewSubobject);
|
|
|
|
// Get the default namespace set associated with the new subobject's class. Because we
|
|
// might receive multiple events within a single frame (e.g. dragging multiple component
|
|
// Blueprint class assets into the components tree will result in multiple notifications),
|
|
// we'll add these into the deferred list for now and auto-import them all on the next tick.
|
|
FBlueprintNamespaceUtilities::GetDefaultImportsForObject(NewSubobject->GetClass(), DeferredNamespaceImports);
|
|
}
|
|
|
|
TSharedRef<SWidget> FBlueprintEditor::CreateGraphTitleBarWidget(TSharedRef<FTabInfo> InTabInfo, UEdGraph* InGraph)
|
|
{
|
|
// Create the title bar widget
|
|
return SNew(SGraphTitleBar)
|
|
.EdGraphObj(InGraph)
|
|
.Kismet2(SharedThis(this))
|
|
.OnDifferentGraphCrumbClicked(this, &FBlueprintEditor::OnChangeBreadCrumbGraph)
|
|
.HistoryNavigationWidget(InTabInfo->CreateHistoryNavigationWidget());
|
|
}
|
|
|
|
/** Create new tab for the supplied graph - don't call this directly.*/
|
|
TSharedRef<SGraphEditor> FBlueprintEditor::CreateGraphEditorWidget(TSharedRef<FTabInfo> InTabInfo, UEdGraph* InGraph)
|
|
{
|
|
check((InGraph != nullptr) && IsEditingSingleBlueprint());
|
|
|
|
// No need to regenerate the commands.
|
|
if(!GraphEditorCommands.IsValid())
|
|
{
|
|
GraphEditorCommands = MakeShareable( new FUICommandList );
|
|
{
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().PromoteToVariable,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnPromoteToVariable, true ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanPromoteToVariable, true )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().PromoteToLocalVariable,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnPromoteToVariable, false ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanPromoteToVariable, false )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().SplitStructPin,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnSplitStructPin ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanSplitStructPin )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().RecombineStructPin,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnRecombineStructPin ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanRecombineStructPin )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AddExecutionPin,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAddExecutionPin ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanAddExecutionPin )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().InsertExecutionPinBefore,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnInsertExecutionPinBefore ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanInsertExecutionPin )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().InsertExecutionPinAfter,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnInsertExecutionPinAfter),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanInsertExecutionPin)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().RemoveExecutionPin,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnRemoveExecutionPin ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanRemoveExecutionPin )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().RemoveThisStructVarPin,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnRemoveThisStructVarPin),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanRemoveThisStructVarPin)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().RemoveOtherStructVarPins,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnRemoveOtherStructVarPins),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanRemoveOtherStructVarPins)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().RestoreAllStructVarPins,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnRestoreAllStructVarPins),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanRestoreAllStructVarPins)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().ResetPinToDefaultValue,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnResetPinToDefaultValue),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanResetPinToDefaultValue)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AddOptionPin,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAddOptionPin ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanAddOptionPin )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().RemoveOptionPin,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnRemoveOptionPin ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanRemoveOptionPin )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().ChangePinType,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnChangePinType ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanChangePinType )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AddParentNode,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAddParentNode ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanAddParentNode )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CreateMatchingFunction,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnCreateMatchingFunction ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanCreateMatchingFunction )
|
|
);
|
|
|
|
// Debug actions
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AddBreakpoint,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAddBreakpoint ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanAddBreakpoint ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::CanAddBreakpoint )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().RemoveBreakpoint,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnRemoveBreakpoint ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanRemoveBreakpoint ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::CanRemoveBreakpoint )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().EnableBreakpoint,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnEnableBreakpoint ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanEnableBreakpoint ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::CanEnableBreakpoint )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().DisableBreakpoint,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnDisableBreakpoint ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanDisableBreakpoint ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::CanDisableBreakpoint )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().ToggleBreakpoint,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnToggleBreakpoint ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanToggleBreakpoint ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::CanToggleBreakpoint )
|
|
);
|
|
|
|
// Encapsulation commands
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CollapseNodes,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnCollapseNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanCollapseNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CollapseSelectionToFunction,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnCollapseSelectionToFunction ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanCollapseSelectionToFunction ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewFunctionGraph )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CollapseSelectionToMacro,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnCollapseSelectionToMacro ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanCollapseSelectionToMacro ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewMacroGraph )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().ConvertFunctionToEvent,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnConvertFunctionToEvent),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanConvertFunctionToEvent),
|
|
FIsActionChecked()
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().ConvertEventToFunction,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnConvertEventToFunction),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanConvertEventToFunction),
|
|
FIsActionChecked()
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().PromoteSelectionToFunction,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnPromoteSelectionToFunction ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanPromoteSelectionToFunction ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewFunctionGraph )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().PromoteSelectionToMacro,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnPromoteSelectionToMacro ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanPromoteSelectionToMacro ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewMacroGraph )
|
|
);
|
|
|
|
// Alignment Commands
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesTop,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAlignTop )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesMiddle,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAlignMiddle )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesBottom,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAlignBottom )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesLeft,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAlignLeft )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesCenter,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAlignCenter )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AlignNodesRight,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAlignRight )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().StraightenConnections,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnStraightenConnections )
|
|
);
|
|
|
|
// Distribution Commands
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().DistributeNodesHorizontally,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnDistributeNodesH )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().DistributeNodesVertically,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnDistributeNodesV )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().StackNodesHorizontally,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnStackNodesH)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().StackNodesVertically,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnStackNodesV)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Rename,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnRenameNode ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanRenameNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().ExpandNodes,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnExpandNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanExpandNodes ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::CanExpandNodes )
|
|
);
|
|
|
|
// Editing commands
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().SelectAll,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::SelectAllNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanSelectAllNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Delete,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::DeleteSelectedNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanDeleteNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DeleteAndReconnectNodes,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::DeleteSelectedNodes),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanDeleteNodes)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Copy,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::CopySelectedNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanCopyNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Cut,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::CutSelectedNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanCutNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Paste,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::PasteGeneric ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanPasteGeneric )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGenericCommands::Get().Duplicate,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::DuplicateNodes ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanDuplicateNodes )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().SelectReferenceInLevel,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnSelectReferenceInLevel ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanSelectReferenceInLevel ),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP( this, &FBlueprintEditor::CanSelectReferenceInLevel )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().AssignReferencedActor,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnAssignReferencedActor ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanAssignReferencedActor ) );
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().StartWatchingPin,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnStartWatchingPin ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanStartWatchingPin )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().StopWatchingPin,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnStopWatchingPin ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanStopWatchingPin )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CreateComment,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnCreateComment )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CreateCustomEvent,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnCreateCustomEvent )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().ShowAllPins,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetPinVisibility, SGraphEditor::Pin_Show)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().HideNoConnectionPins,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetPinVisibility, SGraphEditor::Pin_HideNoConnection)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().HideNoConnectionNoDefaultPins,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetPinVisibility, SGraphEditor::Pin_HideNoConnectionNoDefault)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().FindReferences,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnFindReferences, /*bSearchAllBlueprints=*/false, EGetFindReferenceSearchStringFlags::Legacy),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanFindReferences )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().FindReferencesByNameLocal,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnFindReferences, /*bSearchAllBlueprints=*/false, EGetFindReferenceSearchStringFlags::None ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanFindReferences )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().FindReferencesByNameGlobal,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnFindReferences, /*bSearchAllBlueprints=*/true, EGetFindReferenceSearchStringFlags::None ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanFindReferences )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().FindReferencesByClassMemberLocal,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnFindReferences, /*bSearchAllBlueprints=*/false, EGetFindReferenceSearchStringFlags::UseSearchSyntax ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanFindReferences )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().FindReferencesByClassMemberGlobal,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnFindReferences, /*bSearchAllBlueprints=*/true, EGetFindReferenceSearchStringFlags::UseSearchSyntax ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanFindReferences )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().GoToDefinition,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::OnGoToDefinition ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanGoToDefinition )
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().GoToDocumentation,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnGoToDocumentation),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanGoToDocumentation)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().EnableNodes,
|
|
FExecuteAction(),
|
|
FCanExecuteAction(),
|
|
FGetActionCheckState::CreateSP(this, &FBlueprintEditor::GetEnabledCheckBoxStateForSelectedNodes)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DisableNodes,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnSetEnabledStateForSelectedNodes, ENodeEnabledState::Disabled),
|
|
FCanExecuteAction(),
|
|
FGetActionCheckState::CreateSP(this, &FBlueprintEditor::CheckEnabledStateForSelectedNodes, ENodeEnabledState::Disabled)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().EnableNodes_Always,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnSetEnabledStateForSelectedNodes, ENodeEnabledState::Enabled),
|
|
FCanExecuteAction(),
|
|
FGetActionCheckState::CreateSP(this, &FBlueprintEditor::CheckEnabledStateForSelectedNodes, ENodeEnabledState::Enabled)
|
|
);
|
|
|
|
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().EnableNodes_DevelopmentOnly,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnSetEnabledStateForSelectedNodes, ENodeEnabledState::DevelopmentOnly),
|
|
FCanExecuteAction(),
|
|
FGetActionCheckState::CreateSP(this, &FBlueprintEditor::CheckEnabledStateForSelectedNodes, ENodeEnabledState::DevelopmentOnly)
|
|
);
|
|
|
|
OnCreateGraphEditorCommands(GraphEditorCommands);
|
|
}
|
|
}
|
|
|
|
// Create the title bar widget
|
|
TSharedPtr<SWidget> TitleBarWidget = CreateGraphTitleBarWidget(InTabInfo, InGraph);
|
|
|
|
SGraphEditor::FGraphEditorEvents InEvents;
|
|
SetupGraphEditorEvents(InGraph, InEvents);
|
|
|
|
// Append play world commands
|
|
GraphEditorCommands->Append( FPlayWorldCommands::GlobalPlayWorldActions.ToSharedRef() );
|
|
|
|
TSharedRef<SGraphEditor> Editor = SNew(SGraphEditor)
|
|
.AdditionalCommands(GraphEditorCommands)
|
|
.IsEditable(this, &FBlueprintEditor::IsEditable, InGraph)
|
|
.DisplayAsReadOnly(this, &FBlueprintEditor::IsGraphReadOnly, InGraph)
|
|
.TitleBar(TitleBarWidget)
|
|
.Appearance(this, &FBlueprintEditor::GetGraphAppearance, InGraph)
|
|
.GraphToEdit(InGraph)
|
|
.GraphEvents(InEvents)
|
|
.OnNavigateHistoryBack(FSimpleDelegate::CreateSP(this, &FBlueprintEditor::NavigateTab, FDocumentTracker::NavigateBackwards))
|
|
.OnNavigateHistoryForward(FSimpleDelegate::CreateSP(this, &FBlueprintEditor::NavigateTab, FDocumentTracker::NavigateForwards))
|
|
.AssetEditorToolkit(this->AsShared());
|
|
//@TODO: Crashes in command list code during the callback .OnGraphModuleReloaded(FEdGraphEvent::CreateSP(this, &FBlueprintEditor::ChangeOpenGraphInDocumentEditorWidget, WeakParent))
|
|
;
|
|
|
|
OnSetPinVisibility.AddSP(&Editor.Get(), &SGraphEditor::SetPinVisibility);
|
|
|
|
FVector2f ViewOffset = FVector2f::ZeroVector;
|
|
float ZoomAmount = INDEX_NONE;
|
|
|
|
TSharedPtr<SDockTab> ActiveTab = DocumentManager->GetActiveTab();
|
|
if(ActiveTab.IsValid())
|
|
{
|
|
// Check if the graph is already opened in the current tab, if it is we want to start at the same position to stop the graph from jumping around oddly
|
|
TSharedPtr<SGraphEditor> GraphEditor = StaticCastSharedRef<SGraphEditor>(ActiveTab->GetContent());
|
|
|
|
if(GraphEditor.IsValid() && GraphEditor->GetCurrentGraph() == InGraph)
|
|
{
|
|
GraphEditor->GetViewLocation(ViewOffset, ZoomAmount);
|
|
}
|
|
}
|
|
|
|
Editor->SetViewLocation(ViewOffset, ZoomAmount);
|
|
|
|
return Editor;
|
|
}
|
|
|
|
void FBlueprintEditor::SetupGraphEditorEvents(UEdGraph* InGraph, SGraphEditor::FGraphEditorEvents& InEvents)
|
|
{
|
|
InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP( this, &FBlueprintEditor::OnSelectedNodesChanged );
|
|
InEvents.OnDropActors = SGraphEditor::FOnDropActors::CreateSP( this, &FBlueprintEditor::OnGraphEditorDropActor );
|
|
InEvents.OnDropStreamingLevels = SGraphEditor::FOnDropStreamingLevels::CreateSP( this, &FBlueprintEditor::OnGraphEditorDropStreamingLevel );
|
|
InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FBlueprintEditor::OnNodeDoubleClicked);
|
|
InEvents.OnVerifyTextCommit = FOnNodeVerifyTextCommit::CreateSP(this, &FBlueprintEditor::OnNodeVerifyTitleCommit);
|
|
InEvents.OnTextCommitted = FOnNodeTextCommitted::CreateSP(this, &FBlueprintEditor::OnNodeTitleCommitted);
|
|
InEvents.OnSpawnNodeByShortcutAtLocation = SGraphEditor::FOnSpawnNodeByShortcutAtLocation::CreateSP(this, &FBlueprintEditor::OnSpawnGraphNodeByShortcut, InGraph);
|
|
InEvents.OnNodeSpawnedByKeymap = SGraphEditor::FOnNodeSpawnedByKeymap::CreateSP(this, &FBlueprintEditor::OnNodeSpawnedByKeymap );
|
|
InEvents.OnDisallowedPinConnection = SGraphEditor::FOnDisallowedPinConnection::CreateSP(this, &FBlueprintEditor::OnDisallowedPinConnection);
|
|
InEvents.OnDoubleClicked = SGraphEditor::FOnDoubleClicked::CreateSP(this, &FBlueprintEditor::NavigateToParentGraphByDoubleClick);
|
|
|
|
// Custom menu for K2 schemas
|
|
if(InGraph->Schema != nullptr && InGraph->Schema->IsChildOf(UEdGraphSchema_K2::StaticClass()))
|
|
{
|
|
InEvents.OnCreateActionMenuAtLocation = SGraphEditor::FOnCreateActionMenuAtLocation::CreateSP(this, &FBlueprintEditor::OnCreateGraphActionMenu);
|
|
}
|
|
}
|
|
|
|
FGraphAppearanceInfo FBlueprintEditor::GetCurrentGraphAppearance() const
|
|
{
|
|
return GetGraphAppearance(GetFocusedGraph());
|
|
}
|
|
|
|
FGraphAppearanceInfo FBlueprintEditor::GetGraphAppearance(UEdGraph* InGraph) const
|
|
{
|
|
// Create the appearance info
|
|
FGraphAppearanceInfo AppearanceInfo;
|
|
|
|
UBlueprint* Blueprint = (InGraph != nullptr) ? FBlueprintEditorUtils::FindBlueprintForGraph(InGraph) : GetBlueprintObj();
|
|
if (Blueprint != nullptr)
|
|
{
|
|
if (FBlueprintEditorUtils::IsEditorUtilityBlueprint(Blueprint))
|
|
{
|
|
AppearanceInfo.CornerText = LOCTEXT("EditorUtilityAppearanceCornerText", "EDITOR UTILITY");
|
|
}
|
|
else
|
|
{
|
|
switch (Blueprint->BlueprintType)
|
|
{
|
|
case BPTYPE_LevelScript:
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_LevelScript", "LEVEL BLUEPRINT");
|
|
break;
|
|
case BPTYPE_MacroLibrary:
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_Macro", "MACRO");
|
|
break;
|
|
case BPTYPE_Interface:
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_Interface", "INTERFACE");
|
|
break;
|
|
default:
|
|
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_Blueprint", "BLUEPRINT");
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
UEdGraph const* EditingGraph = GetFocusedGraph();
|
|
if (InGraph && BlueprintEditorImpl::GraphHasDefaultNode(InGraph))
|
|
{
|
|
AppearanceInfo.InstructionText = LOCTEXT("AppearanceInstructionText_DefaultGraph", "Drag Off Pins to Create/Connect New Nodes.");
|
|
}
|
|
else // if the graph is empty...
|
|
{
|
|
AppearanceInfo.InstructionText = LOCTEXT("AppearanceInstructionText_EmptyGraph", "Right-Click to Create New Nodes.");
|
|
}
|
|
auto InstructionOpacityDelegate = TAttribute<float>::FGetter::CreateSP(this, &FBlueprintEditor::GetInstructionTextOpacity, InGraph);
|
|
AppearanceInfo.InstructionFade.Bind(InstructionOpacityDelegate);
|
|
|
|
AppearanceInfo.PIENotifyText = GetPIEStatus();
|
|
|
|
return AppearanceInfo;
|
|
}
|
|
|
|
// Open the editor for a given graph
|
|
void FBlueprintEditor::OnChangeBreadCrumbGraph(UEdGraph* InGraph)
|
|
{
|
|
if (InGraph)
|
|
{
|
|
JumpToHyperlink(InGraph, false);
|
|
}
|
|
}
|
|
|
|
FBlueprintEditor::FBlueprintEditor()
|
|
: bSaveIntermediateBuildProducts(false)
|
|
, bIsReparentingBlueprint(false)
|
|
, bPendingDeferredClose(false)
|
|
, CachedNumWarnings(0)
|
|
, CachedNumErrors(0)
|
|
, bRequestedSavingOpenDocumentState(false)
|
|
, bBlueprintModifiedOnOpen (false)
|
|
, PinVisibility(SGraphEditor::Pin_Show)
|
|
, bIsActionMenuContextSensitive(true)
|
|
, CurrentUISelection(NAME_None)
|
|
, bEditorMarkedAsClosed(false)
|
|
, bHideUnrelatedNodes(false)
|
|
, bLockNodeFadeState(false)
|
|
, bSelectRegularNode(false)
|
|
, HasOpenActionMenu(nullptr)
|
|
, InstructionsFadeCountdown(0.f)
|
|
{
|
|
AnalyticsStats.GraphActionMenusNonCtxtSensitiveExecCount = 0;
|
|
AnalyticsStats.GraphActionMenusCtxtSensitiveExecCount = 0;
|
|
AnalyticsStats.GraphActionMenusCancelledCount = 0;
|
|
AnalyticsStats.MyBlueprintNodeDragPlacementCount = 0;
|
|
AnalyticsStats.PaletteNodeDragPlacementCount = 0;
|
|
AnalyticsStats.NodeGraphContextCreateCount = 0;
|
|
AnalyticsStats.NodePinContextCreateCount = 0;
|
|
AnalyticsStats.NodeKeymapCreateCount = 0;
|
|
AnalyticsStats.NodePasteCreateCount = 0;
|
|
|
|
UEditorEngine* Editor = (UEditorEngine*)GEngine;
|
|
if (Editor != nullptr)
|
|
{
|
|
Editor->RegisterForUndo(this);
|
|
}
|
|
|
|
DocumentManager = MakeShareable(new FDocumentTracker);
|
|
}
|
|
|
|
void FBlueprintEditor::EnsureBlueprintIsUpToDate(UBlueprint* BlueprintObj)
|
|
{
|
|
// Purge any nullptr graphs
|
|
FBlueprintEditorUtils::PurgeNullGraphs(BlueprintObj);
|
|
|
|
// Make sure the blueprint is cosmetically up to date
|
|
FKismetEditorUtilities::UpgradeCosmeticallyStaleBlueprint(BlueprintObj);
|
|
|
|
if (FBlueprintEditorUtils::SupportsConstructionScript(BlueprintObj))
|
|
{
|
|
// If we don't have an SCS yet, make it
|
|
if(BlueprintObj->SimpleConstructionScript == nullptr)
|
|
{
|
|
check(nullptr != BlueprintObj->GeneratedClass);
|
|
BlueprintObj->SimpleConstructionScript = NewObject<USimpleConstructionScript>(BlueprintObj->GeneratedClass);
|
|
BlueprintObj->SimpleConstructionScript->SetFlags(RF_Transactional);
|
|
|
|
// Recreate (or create) any widgets that depend on the SCS
|
|
CreateSubobjectEditors();
|
|
}
|
|
|
|
// If we should have a UCS but don't yet, make it
|
|
if (UEdGraph* ExistingUCS = FBlueprintEditorUtils::FindUserConstructionScript(BlueprintObj))
|
|
{
|
|
ExistingUCS->bAllowDeletion = false;
|
|
}
|
|
else if(GetDefault<UBlueprintEditorSettings>()->IsFunctionAllowedForAsset(BlueprintObj, UEdGraphSchema_K2::FN_UserConstructionScript))
|
|
{
|
|
UEdGraph* UCSGraph = FBlueprintEditorUtils::CreateNewGraph(BlueprintObj, UEdGraphSchema_K2::FN_UserConstructionScript, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
|
FBlueprintEditorUtils::AddFunctionGraph(BlueprintObj, UCSGraph, /*bIsUserCreated=*/ false, AActor::StaticClass());
|
|
UCSGraph->bAllowDeletion = false;
|
|
}
|
|
|
|
// Check to see if we have gained a component from our parent (that would require us removing our scene root)
|
|
// (or lost one, which requires adding one)
|
|
if (BlueprintObj->SimpleConstructionScript != nullptr)
|
|
{
|
|
BlueprintObj->SimpleConstructionScript->ValidateSceneRootNodes();
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// If we have an SCS but don't support it, then we remove it
|
|
if (BlueprintObj->SimpleConstructionScript)
|
|
{
|
|
// Remove any SCS variable nodes
|
|
for (USCS_Node* SCS_Node : BlueprintObj->SimpleConstructionScript->GetAllNodes())
|
|
{
|
|
if (SCS_Node)
|
|
{
|
|
FBlueprintEditorUtils::RemoveVariableNodes(BlueprintObj, SCS_Node->GetVariableName());
|
|
}
|
|
}
|
|
|
|
// Remove the SCS object reference
|
|
BlueprintObj->SimpleConstructionScript = nullptr;
|
|
|
|
// Mark the Blueprint as having been structurally modified
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(BlueprintObj);
|
|
}
|
|
|
|
// Allow deleting the UCS if we've somehow changed away from being an actor (e.g., because of C++ reparenting the parent of our native parent)
|
|
if (UEdGraph* ExistingUCS = FBlueprintEditorUtils::FindUserConstructionScript(BlueprintObj))
|
|
{
|
|
ExistingUCS->bAllowDeletion = true;
|
|
}
|
|
}
|
|
|
|
// Make sure that this blueprint is up-to-date with regards to its parent functions
|
|
FBlueprintEditorUtils::ConformCallsToParentFunctions(BlueprintObj);
|
|
|
|
// Make sure that this blueprint is up-to-date with regards to its implemented events
|
|
FBlueprintEditorUtils::ConformImplementedEvents(BlueprintObj);
|
|
|
|
// Make sure that this blueprint is up-to-date with regards to its implemented interfaces
|
|
FBlueprintEditorUtils::ConformImplementedInterfaces(BlueprintObj);
|
|
|
|
// Update old composite nodes(can't do this in PostLoad)
|
|
FBlueprintEditorUtils::UpdateOutOfDateCompositeNodes(BlueprintObj);
|
|
|
|
// Update any nodes which might have dropped their RF_Transactional flag due to copy-n-paste issues
|
|
FBlueprintEditorUtils::UpdateTransactionalFlags(BlueprintObj);
|
|
}
|
|
|
|
struct FLoadObjectsFromAssetRegistryHelper
|
|
{
|
|
template<class TObjectType>
|
|
static void Load(TSet<TWeakObjectPtr<TObjectType>>& Collection)
|
|
{
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
|
|
const double CompileStartTime = FPlatformTime::Seconds();
|
|
|
|
TArray<FAssetData> AssetDatas;
|
|
AssetRegistryModule.Get().GetAssetsByClass(TObjectType::StaticClass()->GetClassPathName(), AssetDatas);
|
|
|
|
for (FAssetData& AssetData : AssetDatas)
|
|
{
|
|
if (AssetData.IsValid())
|
|
{
|
|
FString AssetPath = AssetData.GetObjectPathString();
|
|
// Workaround for UE-178174: AssetRegistry returning unmounted AssetPaths. Test the path for mountedness before loading.
|
|
FString PackagePathString = FPackageName::ObjectPathToPackageName(AssetPath);
|
|
FPackagePath UnusedPackagePath;
|
|
if (FPackagePath::TryFromMountedName(PackagePathString, UnusedPackagePath))
|
|
{
|
|
TObjectType* Object = LoadObject<TObjectType>(nullptr, *AssetPath, nullptr, 0, nullptr);
|
|
if (Object)
|
|
{
|
|
Collection.Add(MakeWeakObjectPtr(Object));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
const double FinishTime = FPlatformTime::Seconds();
|
|
|
|
UE_LOG(LogBlueprint, Log, TEXT("Loading all assets of type: %s took %.2f seconds"), *TObjectType::StaticClass()->GetName(), static_cast<float>(FinishTime - CompileStartTime));
|
|
}
|
|
};
|
|
|
|
void FBlueprintEditor::CommonInitialization(const TArray<UBlueprint*>& InitBlueprints, bool bShouldOpenInDefaultsMode)
|
|
{
|
|
TSharedPtr<FBlueprintEditor> ThisPtr(SharedThis(this));
|
|
|
|
// @todo TabManagement
|
|
DocumentManager->Initialize(ThisPtr);
|
|
|
|
// Register the document factories
|
|
{
|
|
DocumentManager->RegisterDocumentFactory(MakeShareable(new FTimelineEditorSummoner(ThisPtr)));
|
|
|
|
TSharedRef<FDocumentTabFactory> GraphEditorFactory = MakeShareable(new FGraphEditorSummoner(ThisPtr,
|
|
FGraphEditorSummoner::FOnCreateGraphEditorWidget::CreateSP(this, &FBlueprintEditor::CreateGraphEditorWidget)
|
|
));
|
|
|
|
// Also store off a reference to the grapheditor factory so we can find all the tabs spawned by it later.
|
|
GraphEditorTabFactoryPtr = GraphEditorFactory;
|
|
DocumentManager->RegisterDocumentFactory(GraphEditorFactory);
|
|
}
|
|
|
|
// Create a namespace helper to keep track of imports for all BPs being edited.
|
|
ImportedNamespaceHelper = MakeShared<FBlueprintNamespaceHelper>();
|
|
|
|
// Add each Blueprint instance to be edited into the namespace helper's context.
|
|
for (const UBlueprint* BP : InitBlueprints)
|
|
{
|
|
ImportedNamespaceHelper->AddBlueprint(BP);
|
|
}
|
|
|
|
// Create imported namespace type filters for value editing.
|
|
ImportedClassViewerFilter = MakeShared<BlueprintEditorImpl::FImportedClassViewerFilterProxy>(ImportedNamespaceHelper->GetClassViewerFilter());
|
|
ImportedPinTypeSelectorFilter = MakeShared<BlueprintEditorImpl::FImportedPinTypeSelectorFilterProxy>(ImportedNamespaceHelper->GetPinTypeSelectorFilter());
|
|
PermissionsPinTypeSelectorFilter = MakeShared<BlueprintEditorImpl::FPermissionsPinTypeSelectorFilter>(InitBlueprints);
|
|
|
|
// Make sure we know when tabs become active to update details tab
|
|
OnActiveTabChangedDelegateHandle = FGlobalTabmanager::Get()->OnActiveTabChanged_Subscribe( FOnActiveTabChanged::FDelegate::CreateRaw(this, &FBlueprintEditor::OnActiveTabChanged) );
|
|
|
|
if (InitBlueprints.Num() == 1)
|
|
{
|
|
if (!bShouldOpenInDefaultsMode)
|
|
{
|
|
// Load blueprint libraries
|
|
if (ShouldLoadBPLibrariesFromAssetRegistry())
|
|
{
|
|
LoadLibrariesFromAssetRegistry();
|
|
}
|
|
|
|
// Init the action DB for the context menu/palette if not already constructed
|
|
FBlueprintActionDatabase::Get();
|
|
}
|
|
|
|
FLoadObjectsFromAssetRegistryHelper::Load<UUserDefinedEnum>(UserDefinedEnumerators);
|
|
|
|
UBlueprint* InitBlueprint = InitBlueprints[0];
|
|
|
|
// Update the blueprint if required
|
|
EBlueprintStatus OldStatus = InitBlueprint->Status;
|
|
EnsureBlueprintIsUpToDate(InitBlueprint);
|
|
UPackage* BpPackage = InitBlueprint->GetOutermost();
|
|
bBlueprintModifiedOnOpen = (InitBlueprint->Status != OldStatus) && !BpPackage->HasAnyPackageFlags(PKG_NewlyCreated);
|
|
|
|
// Flag the blueprint as having been opened
|
|
InitBlueprint->bIsNewlyCreated = false;
|
|
|
|
// When the blueprint that we are observing changes, it will notify this wrapper widget.
|
|
InitBlueprint->OnChanged().AddSP(this, &FBlueprintEditor::OnBlueprintChanged);
|
|
InitBlueprint->OnCompiled().AddSP(this, &FBlueprintEditor::OnBlueprintCompiled);
|
|
InitBlueprint->OnSetObjectBeingDebugged().AddSP(this, &FBlueprintEditor::HandleSetObjectBeingDebugged);
|
|
}
|
|
|
|
bWasOpenedInDefaultsMode = bShouldOpenInDefaultsMode;
|
|
|
|
CreateDefaultTabContents(InitBlueprints);
|
|
|
|
FCoreUObjectDelegates::OnPreObjectPropertyChanged.AddSP(this, &FBlueprintEditor::OnPreObjectPropertyChanged);
|
|
FCoreUObjectDelegates::OnObjectPropertyChanged.AddSP(this, &FBlueprintEditor::OnPostObjectPropertyChanged);
|
|
|
|
FKismetEditorUtilities::OnBlueprintUnloaded.AddSP(this, &FBlueprintEditor::OnBlueprintUnloaded);
|
|
}
|
|
|
|
void FBlueprintEditor::LoadLibrariesFromAssetRegistry()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintEditor::LoadLibrariesFromAssetRegistry);
|
|
|
|
if (EnableAutomaticLibraryAssetLoading == 0)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if (UBlueprint* BP = GetBlueprintObj())
|
|
{
|
|
const FString UserDeveloperPath = FPackageName::FilenameToLongPackageName( FPaths::GameUserDeveloperDir());
|
|
const FString DeveloperPath = FPackageName::FilenameToLongPackageName( FPaths::GameDevelopersDir() );
|
|
|
|
// Interface blueprints don't show a node context menu anywhere so we can skip library loading
|
|
if (BP->BlueprintType != BPTYPE_Interface)
|
|
{
|
|
// Load the asset registry module
|
|
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
|
|
|
|
// Collect a full list of assets with the specified class
|
|
TArray<FAssetData> AssetData;
|
|
AssetRegistryModule.Get().GetAssetsByClass(UBlueprint::StaticClass()->GetClassPathName(), AssetData);
|
|
|
|
const FName BPTypeName(GET_MEMBER_NAME_STRING_CHECKED(UBlueprint, BlueprintType));
|
|
TArray<const FAssetData*> RelevantAssets;
|
|
const TCHAR* BPMacroTypeStr = TEXT("BPTYPE_MacroLibrary");
|
|
const TCHAR* BPFunctionTypeStr = TEXT("BPTYPE_FunctionLibrary");
|
|
for (const FAssetData& AssetEntry : AssetData)
|
|
{
|
|
const FString AssetBPType = AssetEntry.GetTagValueRef<FString>(BPTypeName);
|
|
|
|
// Only check for Blueprint Macros & Functions in the asset data for loading
|
|
if ((AssetBPType == BPMacroTypeStr) || (AssetBPType == BPFunctionTypeStr))
|
|
{
|
|
RelevantAssets.Add(&AssetEntry);
|
|
}
|
|
}
|
|
|
|
FScopedSlowTask LoadingMacrosAndFunctions(RelevantAssets.Num(), LOCTEXT("LoadingBlueprintAssetData", "Loading Blueprint Asset Data"));
|
|
LoadingMacrosAndFunctions.Visibility = ESlowTaskVisibility::Important; // this function can be very slow, users will benefit from our messages
|
|
const FName BPNamespaceName(GET_MEMBER_NAME_STRING_CHECKED(UBlueprint, BlueprintNamespace));
|
|
|
|
struct FExpensiveObjectRecord
|
|
{
|
|
FExpensiveObjectRecord() : Seconds(0.0) {}
|
|
FExpensiveObjectRecord(double InSeconds, const FName InPath) : Seconds(InSeconds), Path(InPath) {}
|
|
double Seconds;
|
|
FName Path;
|
|
};
|
|
|
|
TArray<FExpensiveObjectRecord> ExpensiveObjects;
|
|
const double MinSecondsToReportExpensiveObject = 0.2;
|
|
const int32 MaxExpensiveObjectsToList = 100;
|
|
int32 NumLibariesLoaded = 0;
|
|
|
|
const double StartTimeAll = FPlatformTime::Seconds();
|
|
for (const FAssetData* AssetEntryPtr : RelevantAssets)
|
|
{
|
|
const FAssetData& AssetEntry = *AssetEntryPtr;
|
|
const FString BlueprintPath = AssetEntry.GetSoftObjectPath().ToString();
|
|
|
|
// See if this passes the namespace check
|
|
bool bAllowLoadBP = !ImportedNamespaceHelper.IsValid() || ImportedNamespaceHelper->IsImportedAsset(AssetEntry);
|
|
|
|
// For blueprints inside developers folder, only allow the ones inside current user's developers folder.
|
|
if (bAllowLoadBP)
|
|
{
|
|
if (BlueprintPath.StartsWith(DeveloperPath) &&
|
|
!BlueprintPath.StartsWith(UserDeveloperPath))
|
|
{
|
|
bAllowLoadBP = false;
|
|
}
|
|
}
|
|
|
|
if (bAllowLoadBP)
|
|
{
|
|
LoadingMacrosAndFunctions.EnterProgressFrame(1.f,
|
|
FText::Format(LOCTEXT("LoadingFuncMacroLib", "Loading Function or Macro library: {0}"), FText::FromName(AssetEntry.AssetName)));
|
|
|
|
++NumLibariesLoaded;
|
|
const double StartTime = FPlatformTime::Seconds();
|
|
|
|
// Load the blueprint
|
|
UBlueprint* BlueprintLibPtr = LoadObject<UBlueprint>(nullptr, *BlueprintPath, nullptr, 0, nullptr);
|
|
if (BlueprintLibPtr)
|
|
{
|
|
StandardLibraries.AddUnique(BlueprintLibPtr);
|
|
}
|
|
|
|
const double ElapsedTime = FPlatformTime::Seconds() - StartTime;
|
|
if (ElapsedTime > MinSecondsToReportExpensiveObject)
|
|
{
|
|
ExpensiveObjects.Add(FExpensiveObjectRecord(ElapsedTime, AssetEntry.PackageName));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ExpensiveObjects.Num() > 0)
|
|
{
|
|
const double TotalSeconds = FPlatformTime::Seconds() - StartTimeAll;
|
|
UE_LOG(LogBlueprintEditor, Log, TEXT("Perf: %.1f total seconds to load all %d blueprint libraries in project. Avoid references to content in blueprint libraries to shorten this time."), TotalSeconds, NumLibariesLoaded);
|
|
|
|
// Log the most expensive objects to load
|
|
Algo::Sort(ExpensiveObjects, [](FExpensiveObjectRecord& A, FExpensiveObjectRecord& B) { return A.Seconds > B.Seconds; });
|
|
for (int32 i=0; i < ExpensiveObjects.Num() && i < MaxExpensiveObjectsToList; ++i)
|
|
{
|
|
FExpensiveObjectRecord& ExpensiveObjectRecord = ExpensiveObjects[i];
|
|
UE_LOG(LogBlueprintEditor, Log, TEXT("Perf: %.1f seconds loading: %s"), ExpensiveObjectRecord.Seconds, *ExpensiveObjectRecord.Path.ToString());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::ImportNamespace(const FString& InNamespace)
|
|
{
|
|
FImportNamespaceExParameters Params;
|
|
Params.NamespacesToImport.Add(InNamespace);
|
|
ImportNamespaceEx(Params);
|
|
}
|
|
|
|
void FBlueprintEditor::ImportNamespaceEx(const FImportNamespaceExParameters& InParams)
|
|
{
|
|
// No auto-import actions if features are disabled.
|
|
const bool bIsAutoImportEnabled = GetDefault<UBlueprintEditorSettings>()->bEnableNamespaceImportingFeatures;
|
|
if (InParams.bIsAutoImport && !bIsAutoImportEnabled)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Exit now if no input was given.
|
|
if (InParams.NamespacesToImport.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
auto AddNamespaceToImportList = [](UBlueprint* InBlueprint, const FString& InNamespace) -> bool
|
|
{
|
|
// No need to include the global namespace (empty string) in the import list.
|
|
if (!InNamespace.IsEmpty() && !InBlueprint->ImportedNamespaces.Contains(InNamespace))
|
|
{
|
|
InBlueprint->Modify();
|
|
InBlueprint->ImportedNamespaces.Add(InNamespace);
|
|
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// Update the imported set for all edited objects.
|
|
bool bWasAddedAsImport = false;
|
|
const auto& EditingObjs = GetEditingObjects();
|
|
for (UObject* EditingObj : EditingObjs)
|
|
{
|
|
if (UBlueprint* BlueprintObj = Cast<UBlueprint>(EditingObj))
|
|
{
|
|
// Add each namespace into the Blueprint's user-facing import set.
|
|
for (const FString& NamespaceToImport : InParams.NamespacesToImport)
|
|
{
|
|
bWasAddedAsImport |= AddNamespaceToImportList(BlueprintObj, NamespaceToImport);
|
|
}
|
|
}
|
|
}
|
|
|
|
auto AddNamespaceToEditorContext = [](TSharedPtr<FBlueprintNamespaceHelper> ImportsHelper, const FString& InNamespace) -> bool
|
|
{
|
|
// Note: The global namespace (empty string) is implicitly included.
|
|
if (!InNamespace.IsEmpty() && !ImportsHelper->IsIncludedInNamespaceList(InNamespace))
|
|
{
|
|
ImportsHelper->AddNamespace(InNamespace);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// Add to the current scope of the Blueprint's editor context. Note that in certain cases, imports may already be associated
|
|
// with the Blueprint, but not yet associated with the editor context (e.g. - auto-import after setting a Blueprint's namespace;
|
|
// we won't add it to the Blueprint's import list, but we still want to add to the editor context and do any post-import actions).
|
|
bool bWasAddedToEditorContext = false;
|
|
if (ImportedNamespaceHelper.IsValid())
|
|
{
|
|
for (const FString& NamespaceToImport : InParams.NamespacesToImport)
|
|
{
|
|
bWasAddedToEditorContext |= AddNamespaceToEditorContext(ImportedNamespaceHelper, NamespaceToImport);
|
|
}
|
|
}
|
|
|
|
if (bWasAddedAsImport || bWasAddedToEditorContext)
|
|
{
|
|
// Load additional libraries that may now be in scope.
|
|
// @todo_namespaces - Make this more targeted - i.e. get/load only those assets tagged w/ the given namespace
|
|
LoadLibrariesFromAssetRegistry();
|
|
|
|
// Refresh class details on an auto-import if visible, since the list of imports has implicitly changed.
|
|
if (InParams.bIsAutoImport && IsDetailsPanelEditingGlobalOptions())
|
|
{
|
|
RefreshInspector();
|
|
}
|
|
|
|
// If bound, execute the post-import callback.
|
|
InParams.OnPostImportCallback.ExecuteIfBound();
|
|
|
|
// Display a notification for auto-import events only if we've added it to the editor context.
|
|
if (InParams.bIsAutoImport && bWasAddedToEditorContext)
|
|
{
|
|
const int32 ImportCount = InParams.NamespacesToImport.Num();
|
|
|
|
FText NotificationText;
|
|
if (ImportCount > 1)
|
|
{
|
|
FFormatNamedArguments FormatArgs;
|
|
FormatArgs.Add(TEXT("ImportCount"), ImportCount);
|
|
NotificationText = FText::Format(LOCTEXT("AutoImportNotification_Multiple", "Imported {ImportCount} namespaces"), FormatArgs);
|
|
}
|
|
else
|
|
{
|
|
NotificationText = FText::Format(LOCTEXT("AutoImportNotification_Single", "Imported namespace \"{0}\""), FText::FromString(InParams.NamespacesToImport.Array()[0]));
|
|
}
|
|
|
|
FNotificationInfo Notification(NotificationText);
|
|
Notification.ExpireDuration = 3.0f;
|
|
FSlateNotificationManager::Get().AddNotification(Notification);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::RemoveNamespace(const FString& InNamespace)
|
|
{
|
|
// Cannot remove the global namespace.
|
|
if (InNamespace.IsEmpty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Update the imported set for all edited objects.
|
|
const auto& EditingObjs = GetEditingObjects();
|
|
for (UObject* EditingObj : EditingObjs)
|
|
{
|
|
if (UBlueprint* BlueprintObj = Cast<UBlueprint>(EditingObj))
|
|
{
|
|
if (BlueprintObj->ImportedNamespaces.Contains(InNamespace))
|
|
{
|
|
BlueprintObj->Modify();
|
|
BlueprintObj->ImportedNamespaces.Remove(InNamespace);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove it from the current scope of the Blueprint's editor context.
|
|
if (ImportedNamespaceHelper.IsValid())
|
|
{
|
|
if (ImportedNamespaceHelper->IsIncludedInNamespaceList(InNamespace))
|
|
{
|
|
ImportedNamespaceHelper->RemoveNamespace(InNamespace);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SelectAndDuplicateNode(UEdGraphNode* InNode)
|
|
{
|
|
check(InNode != nullptr);
|
|
|
|
ClearSelectionStateFor(CurrentUISelection);
|
|
AddToSelection(InNode);
|
|
DuplicateNodes();
|
|
}
|
|
|
|
void FBlueprintEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
//@TODO: Can't we do this sooner?
|
|
DocumentManager->SetTabManager(InTabManager);
|
|
|
|
FWorkflowCentricApplication::RegisterTabSpawners(InTabManager);
|
|
}
|
|
|
|
void FBlueprintEditor::SetCurrentMode(FName NewMode)
|
|
{
|
|
// Clear the selection state when the mode changes.
|
|
SetUISelectionState(NAME_None);
|
|
|
|
OnModeSetData.Broadcast( NewMode );
|
|
FWorkflowCentricApplication::SetCurrentMode(NewMode);
|
|
}
|
|
|
|
void FBlueprintEditor::InitBlueprintEditor(
|
|
const EToolkitMode::Type Mode,
|
|
const TSharedPtr< IToolkitHost >& InitToolkitHost,
|
|
const TArray<UBlueprint*>& InBlueprints,
|
|
bool bShouldOpenInDefaultsMode)
|
|
{
|
|
check(InBlueprints.Num() == 1 || bShouldOpenInDefaultsMode);
|
|
|
|
// TRUE if a single Blueprint is being opened and is marked as newly created
|
|
bool bNewlyCreated = InBlueprints.Num() == 1 && InBlueprints[0]->bIsNewlyCreated;
|
|
|
|
// Load editor settings from disk.
|
|
LoadEditorSettings();
|
|
|
|
TArray< UObject* > Objects;
|
|
for (UBlueprint* Blueprint : InBlueprints)
|
|
{
|
|
// Flag the blueprint as having been opened
|
|
Blueprint->bIsNewlyCreated = false;
|
|
|
|
Objects.Add( Blueprint );
|
|
}
|
|
|
|
if (!Toolbar.IsValid())
|
|
{
|
|
Toolbar = MakeShareable(new FBlueprintEditorToolbar(SharedThis(this)));
|
|
}
|
|
|
|
GetToolkitCommands()->Append(FPlayWorldCommands::GlobalPlayWorldActions.ToSharedRef());
|
|
|
|
CreateDefaultCommands();
|
|
|
|
RegisterMenus();
|
|
|
|
// Initialize the asset editor and spawn nothing (dummy layout)
|
|
const bool bCreateDefaultStandaloneMenu = true;
|
|
const bool bCreateDefaultToolbar = true;
|
|
const FName BlueprintEditorAppName = FName(TEXT("BlueprintEditorApp"));
|
|
InitAssetEditor(Mode, InitToolkitHost, BlueprintEditorAppName, FTabManager::FLayout::NullLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, Objects);
|
|
|
|
CommonInitialization(InBlueprints, bShouldOpenInDefaultsMode);
|
|
|
|
InitalizeExtenders();
|
|
|
|
RegenerateMenusAndToolbars();
|
|
|
|
RegisterApplicationModes(InBlueprints, bShouldOpenInDefaultsMode, bNewlyCreated);
|
|
|
|
// Post-layout initialization
|
|
PostLayoutBlueprintEditorInitialization();
|
|
|
|
// Find and set any instances of this blueprint type if any exists and we are not already editing one
|
|
FBlueprintEditorUtils::FindAndSetDebuggableBlueprintInstances();
|
|
|
|
if ( bNewlyCreated )
|
|
{
|
|
if ( UBlueprint* Blueprint = GetBlueprintObj() )
|
|
{
|
|
if ( Blueprint->BlueprintType == BPTYPE_MacroLibrary )
|
|
{
|
|
NewDocument_OnClick(CGT_NewMacroGraph);
|
|
}
|
|
else if ( Blueprint->BlueprintType == BPTYPE_Interface )
|
|
{
|
|
NewDocument_OnClick(CGT_NewFunctionGraph);
|
|
}
|
|
else if ( Blueprint->BlueprintType == BPTYPE_FunctionLibrary )
|
|
{
|
|
NewDocument_OnClick(CGT_NewFunctionGraph);
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( UBlueprint* Blueprint = GetBlueprintObj() )
|
|
{
|
|
if ( Blueprint->GetClass() == UBlueprint::StaticClass() && Blueprint->BlueprintType == BPTYPE_Normal )
|
|
{
|
|
if ( !bShouldOpenInDefaultsMode )
|
|
{
|
|
GetToolkitCommands()->ExecuteAction(FFullBlueprintEditorCommands::Get().EditClassDefaults.ToSharedRef());
|
|
}
|
|
}
|
|
|
|
// There are upgrade notes, open the log and dump the messages to it
|
|
if (Blueprint->UpgradeNotesLog.IsValid())
|
|
{
|
|
DumpMessagesToCompilerLog(Blueprint->UpgradeNotesLog->Messages, true);
|
|
}
|
|
}
|
|
|
|
// Register for notifications when settings change
|
|
BlueprintEditorSettingsChangedHandle = GetMutableDefault<UBlueprintEditorSettings>()->OnSettingChanged()
|
|
.AddRaw(this, &FBlueprintEditor::OnBlueprintEditorPreferencesChanged);
|
|
BlueprintProjectSettingsChangedHandle = GetMutableDefault<UBlueprintEditorProjectSettings>()->OnSettingChanged()
|
|
.AddRaw(this, &FBlueprintEditor::OnBlueprintProjectSettingsChanged);
|
|
|
|
if (const TSharedPtr<SSCSEditorViewport> Viewport = GetSubobjectViewport())
|
|
{
|
|
ViewportSelectabilityBridge = MakeUnique<FEditorViewportSelectabilityBridge>(Viewport->GetViewportClient());
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::InitToolMenuContext(FToolMenuContext& MenuContext)
|
|
{
|
|
FAssetEditorToolkit::InitToolMenuContext(MenuContext);
|
|
|
|
UBlueprintEditorToolMenuContext* Context = NewObject<UBlueprintEditorToolMenuContext>();
|
|
Context->BlueprintEditor = SharedThis(this);
|
|
MenuContext.AddObject(Context);
|
|
}
|
|
|
|
void FBlueprintEditor::InitalizeExtenders()
|
|
{
|
|
FBlueprintEditorModule* BlueprintEditorModule = &FModuleManager::LoadModuleChecked<FBlueprintEditorModule>("Kismet");
|
|
TSharedPtr<FExtender> CustomExtenders = BlueprintEditorModule->GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects());
|
|
BlueprintEditorModule->OnGatherBlueprintMenuExtensions().Broadcast(CustomExtenders, GetBlueprintObj());
|
|
|
|
AddMenuExtender(CustomExtenders);
|
|
AddToolbarExtender(CustomExtenders);
|
|
}
|
|
|
|
void FBlueprintEditor::RegisterMenus()
|
|
{
|
|
const FName MainMenuName = GetToolMenuName();
|
|
if (!UToolMenus::Get()->IsMenuRegistered(MainMenuName))
|
|
{
|
|
FKismet2Menu::SetupBlueprintEditorMenu(MainMenuName);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::RegisterApplicationModes(const TArray<UBlueprint*>& InBlueprints, bool bShouldOpenInDefaultsMode, bool bNewlyCreated/* = false*/)
|
|
{
|
|
// Newly-created Blueprints will open in Components mode rather than Standard mode
|
|
bool bShouldOpenInComponentsMode = !bShouldOpenInDefaultsMode && bNewlyCreated;
|
|
|
|
// Create the modes and activate one (which will populate with a real layout)
|
|
if ( UBlueprint* SingleBP = GetBlueprintObj() )
|
|
{
|
|
if ( !bShouldOpenInDefaultsMode && FBlueprintEditorUtils::IsInterfaceBlueprint(SingleBP) )
|
|
{
|
|
// Interfaces are only valid in the Interface mode
|
|
AddApplicationMode(
|
|
FBlueprintEditorApplicationModes::BlueprintInterfaceMode,
|
|
MakeShareable(new FBlueprintInterfaceApplicationMode(SharedThis(this))));
|
|
SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintInterfaceMode);
|
|
}
|
|
else if ( SingleBP->BlueprintType == BPTYPE_MacroLibrary )
|
|
{
|
|
// Macro libraries are only valid in the Macro mode
|
|
AddApplicationMode(
|
|
FBlueprintEditorApplicationModes::BlueprintMacroMode,
|
|
MakeShareable(new FBlueprintMacroApplicationMode(SharedThis(this))));
|
|
SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintMacroMode);
|
|
}
|
|
else if ( SingleBP->BlueprintType == BPTYPE_FunctionLibrary )
|
|
{
|
|
AddApplicationMode(
|
|
FBlueprintEditorApplicationModes::StandardBlueprintEditorMode,
|
|
MakeShareable(new FBlueprintEditorUnifiedMode(SharedThis(this), FBlueprintEditorApplicationModes::StandardBlueprintEditorMode, FBlueprintEditorApplicationModes::GetLocalizedMode, CanAccessComponentsMode())));
|
|
SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode);
|
|
}
|
|
else
|
|
{
|
|
if ( bShouldOpenInDefaultsMode )
|
|
{
|
|
// We either have no blueprints or many, open in the defaults mode for multi-editing
|
|
AddApplicationMode(
|
|
FBlueprintEditorApplicationModes::BlueprintDefaultsMode,
|
|
MakeShareable(new FBlueprintDefaultsApplicationMode(SharedThis(this))));
|
|
SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintDefaultsMode);
|
|
}
|
|
else
|
|
{
|
|
AddApplicationMode(
|
|
FBlueprintEditorApplicationModes::StandardBlueprintEditorMode,
|
|
MakeShareable(new FBlueprintEditorUnifiedMode(SharedThis(this), FBlueprintEditorApplicationModes::StandardBlueprintEditorMode, FBlueprintEditorApplicationModes::GetLocalizedMode, CanAccessComponentsMode())));
|
|
SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode);
|
|
|
|
if ( bShouldOpenInComponentsMode && CanAccessComponentsMode() )
|
|
{
|
|
TabManager->TryInvokeTab(FBlueprintEditorTabs::SCSViewportID);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// We either have no blueprints or many, open in the defaults mode for multi-editing
|
|
AddApplicationMode(
|
|
FBlueprintEditorApplicationModes::BlueprintDefaultsMode,
|
|
MakeShareable(new FBlueprintDefaultsApplicationMode(SharedThis(this))));
|
|
SetCurrentMode(FBlueprintEditorApplicationModes::BlueprintDefaultsMode);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::PostRegenerateMenusAndToolbars()
|
|
{
|
|
UBlueprint* BluePrint = GetBlueprintObj();
|
|
|
|
if ( BluePrint && !FBlueprintEditorUtils::IsLevelScriptBlueprint( BluePrint ) )
|
|
{
|
|
// build and attach the menu overlay
|
|
TSharedRef<SHorizontalBox> MenuOverlayBox = SNew(SHorizontalBox)
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.ColorAndOpacity( FSlateColor::UseSubduedForeground() )
|
|
.ShadowOffset( FVector2D::UnitVector )
|
|
.Text(LOCTEXT("BlueprintEditor_ParentClass", "Parent class: "))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SSpacer)
|
|
.Size(FVector2D(2.0f,1.0f))
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(STextBlock)
|
|
.ShadowOffset(FVector2D::UnitVector)
|
|
.Text(this, &FBlueprintEditor::GetParentClassNameText)
|
|
.TextStyle(FAppStyle::Get(), "Common.InheritedFromBlueprintTextStyle")
|
|
.ToolTipText(LOCTEXT("ParentClassToolTip", "The class that the current Blueprint is based on. The parent provides the base definition, which the current Blueprint extends."))
|
|
.Visibility(this, &FBlueprintEditor::GetParentClassNameVisibility)
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
|
|
.OnClicked(this, &FBlueprintEditor::OnFindParentClassInContentBrowserClicked)
|
|
.IsEnabled(this, &FBlueprintEditor::IsParentClassABlueprint)
|
|
.Visibility(this, &FBlueprintEditor::GetFindParentClassVisibility)
|
|
.ToolTipText(LOCTEXT("FindParentInCBToolTip", "Find parent in Content Browser"))
|
|
.ContentPadding(4.0f)
|
|
.ForegroundColor(FSlateColor::UseForeground())
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.Search"))
|
|
]
|
|
]
|
|
+SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
[
|
|
SNew(SButton)
|
|
.VAlign(VAlign_Center)
|
|
.ButtonStyle(FAppStyle::Get(), "HoverHintOnly")
|
|
.OnClicked(this, &FBlueprintEditor::OnEditParentClassClicked)
|
|
.IsEnabled(this, &FBlueprintEditor::IsParentClassAnEditableBlueprint)
|
|
.Visibility(this, &FBlueprintEditor::GetEditParentClassVisibility)
|
|
.ToolTipText(LOCTEXT("EditParentClassToolTip", "Open parent in editor"))
|
|
.ContentPadding(4.0f)
|
|
.ForegroundColor(FSlateColor::UseForeground())
|
|
[
|
|
SNew(SImage)
|
|
.Image(FAppStyle::GetBrush("Icons.Edit"))
|
|
]
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SHyperlink)
|
|
.Style(FAppStyle::Get(), "Common.GotoNativeCodeHyperlink")
|
|
.IsEnabled(this, &FBlueprintEditor::IsNativeParentClassCodeLinkEnabled)
|
|
.Visibility(this, &FBlueprintEditor::GetNativeParentClassButtonsVisibility)
|
|
.OnNavigate(this, &FBlueprintEditor::OnEditParentClassNativeCodeClicked)
|
|
.Text(this, &FBlueprintEditor::GetTextForNativeParentClassHeaderLink)
|
|
.ToolTipText(FText::Format(LOCTEXT("GoToCode_ToolTip", "Click to open this source file in {0}"), FSourceCodeNavigation::GetSelectedSourceCodeIDE()))
|
|
]
|
|
+ SHorizontalBox::Slot()
|
|
.AutoWidth()
|
|
.VAlign(VAlign_Center)
|
|
[
|
|
SNew(SSpacer)
|
|
.Size(FVector2D(8.0f, 1.0f))
|
|
]
|
|
;
|
|
SetMenuOverlay( MenuOverlayBox );
|
|
}
|
|
}
|
|
|
|
FText FBlueprintEditor::GetParentClassNameText() const
|
|
{
|
|
UClass* ParentClass = (GetBlueprintObj() != nullptr) ? GetBlueprintObj()->ParentClass : nullptr;
|
|
return (ParentClass != nullptr) ? ParentClass->GetDisplayNameText() : LOCTEXT("BlueprintEditor_NoParentClass", "None");
|
|
}
|
|
|
|
bool FBlueprintEditor::IsParentClassABlueprint() const
|
|
{
|
|
return FBlueprintEditorUtils::IsParentClassABlueprint(GetBlueprintObj());
|
|
}
|
|
|
|
bool FBlueprintEditor::IsParentClassAnEditableBlueprint() const
|
|
{
|
|
return FBlueprintEditorUtils::IsParentClassAnEditableBlueprint(GetBlueprintObj());
|
|
}
|
|
|
|
EVisibility FBlueprintEditor::GetFindParentClassVisibility() const
|
|
{
|
|
return IsParentClassABlueprint() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility FBlueprintEditor::GetEditParentClassVisibility() const
|
|
{
|
|
return IsParentClassAnEditableBlueprint() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
bool FBlueprintEditor::IsParentClassNative() const
|
|
{
|
|
const UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint != nullptr)
|
|
{
|
|
UClass* ParentClass = Blueprint->ParentClass;
|
|
if (ParentClass != nullptr)
|
|
{
|
|
if (ParentClass->HasAllClassFlags(CLASS_Native))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FBlueprintEditor::IsNativeParentClassCodeLinkEnabled() const
|
|
{
|
|
const UBlueprint* Blueprint = GetBlueprintObj();
|
|
return Blueprint && FSourceCodeNavigation::CanNavigateToClass(Blueprint->ParentClass);
|
|
}
|
|
|
|
EVisibility FBlueprintEditor::GetNativeParentClassButtonsVisibility() const
|
|
{
|
|
return IsNativeParentClassCodeLinkEnabled() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
EVisibility FBlueprintEditor::GetParentClassNameVisibility() const
|
|
{
|
|
return !IsNativeParentClassCodeLinkEnabled() ? EVisibility::Visible : EVisibility::Collapsed;
|
|
}
|
|
|
|
void FBlueprintEditor::OnEditParentClassNativeCodeClicked()
|
|
{
|
|
const UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint)
|
|
{
|
|
FSourceCodeNavigation::NavigateToClass(Blueprint->ParentClass);
|
|
}
|
|
}
|
|
|
|
FText FBlueprintEditor::GetTextForNativeParentClassHeaderLink() const
|
|
{
|
|
// it could be done using FSourceCodeNavigation, but it could be slow
|
|
return FText::FromString(*GetParentClassNameText().ToString());
|
|
}
|
|
|
|
FReply FBlueprintEditor::OnFindParentClassInContentBrowserClicked()
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint)
|
|
{
|
|
UObject* ParentClass = Blueprint->ParentClass;
|
|
if (ParentClass)
|
|
{
|
|
UBlueprintGeneratedClass* ParentBlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>( ParentClass );
|
|
if (ParentBlueprintGeneratedClass)
|
|
{
|
|
TArray<UObject*> ParentObjectList;
|
|
if (ParentBlueprintGeneratedClass->ClassGeneratedBy)
|
|
{
|
|
ParentObjectList.Add(ParentBlueprintGeneratedClass->ClassGeneratedBy);
|
|
}
|
|
else
|
|
{
|
|
ParentObjectList.Add(ParentBlueprintGeneratedClass);
|
|
}
|
|
GEditor->SyncBrowserToObjects(ParentObjectList);
|
|
}
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FReply FBlueprintEditor::OnEditParentClassClicked()
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint)
|
|
{
|
|
UObject* ParentClass = Blueprint->ParentClass;
|
|
if (ParentClass)
|
|
{
|
|
UBlueprintGeneratedClass* ParentBlueprintGeneratedClass = Cast<UBlueprintGeneratedClass>( ParentClass );
|
|
if (ParentBlueprintGeneratedClass)
|
|
{
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(ParentBlueprintGeneratedClass->ClassGeneratedBy);
|
|
|
|
if (UObject* DebugObject = Blueprint->GetObjectBeingDebugged())
|
|
{
|
|
if (UBlueprint* ParentBlueprint = Cast<UBlueprint>(ParentBlueprintGeneratedClass->ClassGeneratedBy))
|
|
{
|
|
ParentBlueprint->SetObjectBeingDebugged(DebugObject);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void FBlueprintEditor::PostLayoutBlueprintEditorInitialization()
|
|
{
|
|
if (UBlueprint* Blueprint = GetBlueprintObj())
|
|
{
|
|
// Refresh the graphs
|
|
RefreshEditors();
|
|
|
|
// EnsureBlueprintIsUpToDate may have updated the blueprint so show notifications to user.
|
|
if (bBlueprintModifiedOnOpen)
|
|
{
|
|
bBlueprintModifiedOnOpen = false;
|
|
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FNotificationInfo Info( NSLOCTEXT("Kismet", "Blueprint Modified", "Blueprint requires updating. Please resave.") );
|
|
Info.Image = FAppStyle::GetBrush(TEXT("Icons.Info"));
|
|
Info.bFireAndForget = true;
|
|
Info.bUseSuccessFailIcons = false;
|
|
Info.ExpireDuration = 5.0f;
|
|
|
|
FocusedGraphEdPtr.Pin()->AddNotification(Info, true);
|
|
}
|
|
|
|
// Fire log message
|
|
FString BlueprintName;
|
|
Blueprint->GetName(BlueprintName);
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("BlueprintName"), FText::FromString( BlueprintName ) );
|
|
LogSimpleMessage( FText::Format( LOCTEXT("Blueprint Modified Long", "Blueprint \"{BlueprintName}\" was updated to fix issues detected on load. Please resave."), Args ) );
|
|
}
|
|
|
|
// Determine if the current "mode" supports invoking the Compiler Results tab.
|
|
const bool bCanInvokeCompilerResultsTab = TabManager->HasTabSpawner(FBlueprintEditorTabs::CompilerResultsID);
|
|
|
|
// If we have a warning/error, open output log if the current mode allows us to invoke it.
|
|
const bool bIsBlueprintInWarningOrErrorState = !Blueprint->IsUpToDate() || (Blueprint->Status == BS_UpToDateWithWarnings);
|
|
if (bIsBlueprintInWarningOrErrorState && bCanInvokeCompilerResultsTab)
|
|
{
|
|
TabManager->TryInvokeTab(FBlueprintEditorTabs::CompilerResultsID);
|
|
}
|
|
else
|
|
{
|
|
// Toolkit modes that don't include this tab may have been incorrectly saved with layout information for restoring it
|
|
// as an "unrecognized" tab, due to having previously invoked it above without checking to see if the layout can open
|
|
// it first. To correct this, we check if the tab was restored from a saved layout here, and close it if not supported.
|
|
TSharedPtr<SDockTab> TabPtr = TabManager->FindExistingLiveTab(FBlueprintEditorTabs::CompilerResultsID);
|
|
if (TabPtr.IsValid() && !bCanInvokeCompilerResultsTab)
|
|
{
|
|
TabPtr->RequestCloseTab();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SetupViewForBlueprintEditingMode()
|
|
{
|
|
// Make sure the defaults tab is pointing to the defaults
|
|
StartEditingDefaults(/*bAutoFocus=*/ true);
|
|
|
|
// Make sure the inspector is always on top
|
|
//@TODO: This is necessary right now because of a bug in restoring layouts not remembering which tab is on top (to get it right initially), but do we want this behavior always?
|
|
TryInvokingDetailsTab();
|
|
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if ((Blueprint != nullptr) && (Blueprint->Status == EBlueprintStatus::BS_Error))
|
|
{
|
|
UBlueprintEditorSettings const* BpEditorSettings = GetDefault<UBlueprintEditorSettings>();
|
|
if (BpEditorSettings->bJumpToNodeErrors)
|
|
{
|
|
if (UEdGraphNode* NodeWithError = BlueprintEditorImpl::FindNodeWithError(Blueprint))
|
|
{
|
|
JumpToNode(NodeWithError, /*bRequestRename =*/false);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FBlueprintEditor::~FBlueprintEditor()
|
|
{
|
|
// Stop watching the settings
|
|
GetMutableDefault<UBlueprintEditorSettings>()->OnSettingChanged().Remove(BlueprintEditorSettingsChangedHandle);
|
|
GetMutableDefault<UBlueprintEditorProjectSettings>()->OnSettingChanged().Remove(BlueprintProjectSettingsChangedHandle);
|
|
|
|
// Clean up the preview
|
|
DestroyPreview();
|
|
|
|
// NOTE: Any tabs that we still have hanging out when destroyed will be cleaned up by FBaseToolkit's destructor
|
|
UEditorEngine* Editor = (UEditorEngine*)GEngine;
|
|
if (Editor)
|
|
{
|
|
Editor->UnregisterForUndo(this);
|
|
}
|
|
|
|
CloseMergeTool();
|
|
|
|
if (GetBlueprintObj())
|
|
{
|
|
GetBlueprintObj()->OnChanged().RemoveAll(this);
|
|
GetBlueprintObj()->OnCompiled().RemoveAll(this);
|
|
GetBlueprintObj()->OnSetObjectBeingDebugged().RemoveAll(this);
|
|
}
|
|
|
|
if (USubobjectDataSubsystem* SubobjectDataSubsystem = USubobjectDataSubsystem::Get())
|
|
{
|
|
SubobjectDataSubsystem->OnNewSubobjectAdded().RemoveAll(this);
|
|
}
|
|
|
|
FCoreUObjectDelegates::OnPreObjectPropertyChanged.RemoveAll(this);
|
|
FCoreUObjectDelegates::OnObjectPropertyChanged.RemoveAll(this);
|
|
|
|
FKismetEditorUtilities::OnBlueprintUnloaded.RemoveAll(this);
|
|
|
|
FGlobalTabmanager::Get()->OnActiveTabChanged_Unsubscribe(OnActiveTabChangedDelegateHandle);
|
|
|
|
if (FEngineAnalytics::IsAvailable())
|
|
{
|
|
const UGeneralProjectSettings& ProjectSettings = *GetDefault<UGeneralProjectSettings>();
|
|
FString ProjectID = ProjectSettings.ProjectID.ToString();
|
|
|
|
TArray<FAnalyticsEventAttribute> BPEditorAttribs;
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("GraphActionMenusExecuted.NonContextSensitive"), AnalyticsStats.GraphActionMenusNonCtxtSensitiveExecCount));
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("GraphActionMenusExecuted.ContextSensitive"), AnalyticsStats.GraphActionMenusCtxtSensitiveExecCount));
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("GraphActionMenusClosed"), AnalyticsStats.GraphActionMenusCancelledCount));
|
|
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("MyBlueprintDragPlacedNodesCreated"), AnalyticsStats.MyBlueprintNodeDragPlacementCount));
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("BlueprintPaletteDragPlacedNodesCreated"), AnalyticsStats.PaletteNodeDragPlacementCount));
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("GraphContextNodesCreated" ), AnalyticsStats.NodeGraphContextCreateCount));
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("GraphPinContextNodesCreated" ), AnalyticsStats.NodePinContextCreateCount));
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("KeymapNodesCreated"), AnalyticsStats.NodeKeymapCreateCount));
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("PastedNodesCreated"), AnalyticsStats.NodePasteCreateCount));
|
|
|
|
BPEditorAttribs.Add(FAnalyticsEventAttribute(TEXT("ProjectId"), ProjectID));
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.BlueprintEditorSummary"), BPEditorAttribs);
|
|
|
|
for (auto Iter = AnalyticsStats.GraphDisallowedPinConnections.CreateConstIterator(); Iter; ++Iter)
|
|
{
|
|
TArray<FAnalyticsEventAttribute> BPEditorPinConnectAttribs;
|
|
BPEditorPinConnectAttribs.Add(FAnalyticsEventAttribute(TEXT("FromPin.Category"), Iter->PinTypeCategoryA));
|
|
BPEditorPinConnectAttribs.Add(FAnalyticsEventAttribute(TEXT("FromPin.IsArray"), Iter->bPinIsArrayA));
|
|
BPEditorPinConnectAttribs.Add(FAnalyticsEventAttribute(TEXT("FromPin.IsReference"), Iter->bPinIsReferenceA));
|
|
BPEditorPinConnectAttribs.Add(FAnalyticsEventAttribute(TEXT("FromPin.IsWeakPointer"), Iter->bPinIsWeakPointerA));
|
|
BPEditorPinConnectAttribs.Add(FAnalyticsEventAttribute(TEXT("ToPin.Category"), Iter->PinTypeCategoryB));
|
|
BPEditorPinConnectAttribs.Add(FAnalyticsEventAttribute(TEXT("ToPin.IsArray"), Iter->bPinIsArrayB));
|
|
BPEditorPinConnectAttribs.Add(FAnalyticsEventAttribute(TEXT("ToPin.IsReference"), Iter->bPinIsReferenceB));
|
|
BPEditorPinConnectAttribs.Add(FAnalyticsEventAttribute(TEXT("ToPin.IsWeakPointer"), Iter->bPinIsWeakPointerB));
|
|
BPEditorPinConnectAttribs.Add(FAnalyticsEventAttribute(TEXT("ProjectId"), ProjectID));
|
|
|
|
FEngineAnalytics::GetProvider().RecordEvent(TEXT("Editor.Usage.BPDisallowedPinConnection"), BPEditorPinConnectAttribs);
|
|
}
|
|
}
|
|
|
|
SaveEditorSettings();
|
|
}
|
|
|
|
void FBlueprintEditor::FocusInspectorOnGraphSelection(const FGraphPanelSelectionSet& NewSelection, bool bForceRefresh)
|
|
{
|
|
// If this graph has selected nodes update the details panel to match.
|
|
if ( NewSelection.Num() > 0 || CurrentUISelection == FBlueprintEditor::SelectionState_Graph )
|
|
{
|
|
SetUISelectionState(FBlueprintEditor::SelectionState_Graph);
|
|
|
|
SKismetInspector::FShowDetailsOptions ShowDetailsOptions;
|
|
ShowDetailsOptions.bForceRefresh = bForceRefresh;
|
|
|
|
Inspector->ShowDetailsForObjects(NewSelection.Array(), ShowDetailsOptions);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::CreateDefaultTabContents(const TArray<UBlueprint*>& InBlueprints)
|
|
{
|
|
UBlueprint* InBlueprint = InBlueprints.Num() == 1 ? InBlueprints[0] : nullptr;
|
|
|
|
// Cache off whether or not this is an interface, since it is used to govern multiple widget's behavior
|
|
const bool bIsInterface = (InBlueprint && InBlueprint->BlueprintType == BPTYPE_Interface);
|
|
const bool bIsMacro = (InBlueprint && InBlueprint->BlueprintType == BPTYPE_MacroLibrary);
|
|
|
|
if (InBlueprint)
|
|
{
|
|
this->BookmarksWidget =
|
|
SNew(SBlueprintBookmarks)
|
|
.EditorContext(SharedThis(this));
|
|
}
|
|
|
|
if (IsEditingSingleBlueprint())
|
|
{
|
|
this->MyBlueprintWidget = SNew(SMyBlueprint, SharedThis(this));
|
|
this->ReplaceReferencesWidget = SNew(SReplaceNodeReferences, SharedThis(this));
|
|
}
|
|
|
|
CompilerResultsListing = FCompilerResultsLog::GetBlueprintMessageLog(InBlueprint);
|
|
CompilerResultsListing->OnMessageTokenClicked().AddSP(this, &FBlueprintEditor::OnLogTokenClicked);
|
|
|
|
FMessageLogModule& MessageLogModule = FModuleManager::LoadModuleChecked<FMessageLogModule>("MessageLog");
|
|
CompilerResults = MessageLogModule.CreateLogListingWidget( CompilerResultsListing.ToSharedRef() );
|
|
FindResults = SNew(SFindInBlueprints, SharedThis(this));
|
|
|
|
this->Inspector =
|
|
SNew(SKismetInspector)
|
|
. HideNameArea(true)
|
|
. ViewIdentifier(FName("BlueprintInspector"))
|
|
. Kismet2(SharedThis(this))
|
|
. OnFinishedChangingProperties( FOnFinishedChangingProperties::FDelegate::CreateSP(this, &FBlueprintEditor::OnFinishedChangingProperties) );
|
|
|
|
if ( InBlueprints.Num() > 0 )
|
|
{
|
|
// Don't show the object name in defaults mode.
|
|
const bool bHideNameArea = bWasOpenedInDefaultsMode;
|
|
|
|
this->DefaultEditor =
|
|
SNew(SKismetInspector)
|
|
. Kismet2(SharedThis(this))
|
|
. ViewIdentifier(FName("BlueprintDefaults"))
|
|
. IsEnabled(!bIsInterface)
|
|
. ShowPublicViewControl(this, &FBlueprintEditor::ShouldShowPublicViewControl)
|
|
. ShowTitleArea(false)
|
|
. HideNameArea(bHideNameArea)
|
|
. OnFinishedChangingProperties( FOnFinishedChangingProperties::FDelegate::CreateSP( this, &FBlueprintEditor::OnFinishedChangingProperties ) );
|
|
}
|
|
|
|
if (InBlueprint &&
|
|
InBlueprint->ParentClass &&
|
|
InBlueprint->ParentClass->IsChildOf(AActor::StaticClass()) &&
|
|
InBlueprint->SimpleConstructionScript )
|
|
{
|
|
CreateSubobjectEditors();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::CreateSubobjectEditors()
|
|
{
|
|
TArray<TSharedRef<IClassViewerFilter>> ClassFilters;
|
|
if (ImportedClassViewerFilter.IsValid())
|
|
{
|
|
ClassFilters.Add(ImportedClassViewerFilter.ToSharedRef());
|
|
}
|
|
|
|
SubobjectEditor = SAssignNew(SubobjectEditor, SSubobjectBlueprintEditor)
|
|
.ObjectContext(this, &FBlueprintEditor::GetSubobjectEditorObjectContext)
|
|
.PreviewActor(this, &FBlueprintEditor::GetPreviewActor)
|
|
.AllowEditing(this, &FBlueprintEditor::InEditingMode)
|
|
.OnSelectionUpdated(this, &FBlueprintEditor::OnSelectionUpdated)
|
|
.OnItemDoubleClicked(this, &FBlueprintEditor::OnComponentDoubleClicked)
|
|
.SubobjectClassListFilters(ClassFilters);
|
|
|
|
LLM_SCOPE_BYNAME(TEXT("BPCreateSubobjectEditorViewportClient"));
|
|
|
|
SubobjectViewport = SAssignNew(SubobjectViewport, SSCSEditorViewport)
|
|
.BlueprintEditor(SharedThis(this));
|
|
|
|
if (USubobjectDataSubsystem* SubobjectDataSubsystem = USubobjectDataSubsystem::Get())
|
|
{
|
|
SubobjectDataSubsystem->OnNewSubobjectAdded().AddSP(this, &FBlueprintEditor::OnComponentAddedToBlueprint);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnLogTokenClicked(const TSharedRef<IMessageToken>& Token)
|
|
{
|
|
if (Token->GetType() == EMessageToken::Object)
|
|
{
|
|
const TSharedRef<FUObjectToken> UObjectToken = StaticCastSharedRef<FUObjectToken>(Token);
|
|
if(UObjectToken->GetObject().IsValid())
|
|
{
|
|
JumpToHyperlink(UObjectToken->GetObject().Get());
|
|
}
|
|
}
|
|
else if (Token->GetType() == EMessageToken::EdGraph)
|
|
{
|
|
const TSharedRef<FEdGraphToken> EdGraphToken = StaticCastSharedRef<FEdGraphToken>(Token);
|
|
const UEdGraphPin* PinBeingReferenced = EdGraphToken->GetPin();
|
|
const UObject* ObjectBeingReferenced = EdGraphToken->GetGraphObject();
|
|
if (PinBeingReferenced)
|
|
{
|
|
JumpToPin(PinBeingReferenced);
|
|
}
|
|
else if(ObjectBeingReferenced)
|
|
{
|
|
JumpToHyperlink(ObjectBeingReferenced);
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Create Default Commands **/
|
|
void FBlueprintEditor::CreateDefaultCommands()
|
|
{
|
|
// Tell Kismet2 how to handle all the UI actions that it can handle
|
|
// @todo: remove this once GraphEditorActions automatically register themselves.
|
|
|
|
FGraphEditorCommands::Register();
|
|
FBlueprintEditorCommands::Register();
|
|
FFullBlueprintEditorCommands::Register();
|
|
FMyBlueprintCommands::Register();
|
|
FBlueprintSpawnNodeCommands::Register();
|
|
|
|
static const FName BpEditorModuleName("Kismet");
|
|
FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::LoadModuleChecked<FBlueprintEditorModule>(BpEditorModuleName);
|
|
ToolkitCommands->Append(BlueprintEditorModule.GetsSharedBlueprintEditorCommands());
|
|
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().Compile,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::Compile),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::IsCompilingEnabled));
|
|
|
|
TWeakPtr<FBlueprintEditor> WeakThisPtr = SharedThis(this);
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().SaveOnCompile_Never,
|
|
FExecuteAction::CreateStatic(&BlueprintEditorImpl::SetSaveOnCompileSetting, (ESaveOnCompile)SoC_Never),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::IsSaveOnCompileEnabled),
|
|
FIsActionChecked::CreateStatic(&BlueprintEditorImpl::IsSaveOnCompileOptionSet, WeakThisPtr, (ESaveOnCompile)SoC_Never)
|
|
);
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().SaveOnCompile_SuccessOnly,
|
|
FExecuteAction::CreateStatic(&BlueprintEditorImpl::SetSaveOnCompileSetting, (ESaveOnCompile)SoC_SuccessOnly),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::IsSaveOnCompileEnabled),
|
|
FIsActionChecked::CreateStatic(&BlueprintEditorImpl::IsSaveOnCompileOptionSet, WeakThisPtr, (ESaveOnCompile)SoC_SuccessOnly)
|
|
);
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().SaveOnCompile_Always,
|
|
FExecuteAction::CreateStatic(&BlueprintEditorImpl::SetSaveOnCompileSetting, (ESaveOnCompile)SoC_Always),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::IsSaveOnCompileEnabled),
|
|
FIsActionChecked::CreateStatic(&BlueprintEditorImpl::IsSaveOnCompileOptionSet, WeakThisPtr, (ESaveOnCompile)SoC_Always)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().JumpToErrorNode,
|
|
FExecuteAction::CreateStatic(&BlueprintEditorImpl::ToggleJumpToErrorNodeSetting),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateStatic(&BlueprintEditorImpl::IsJumpToErrorNodeOptionSet)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().SwitchToScriptingMode,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetCurrentMode, FBlueprintEditorApplicationModes::StandardBlueprintEditorMode),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::IsEditingSingleBlueprint),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsModeCurrent, FBlueprintEditorApplicationModes::StandardBlueprintEditorMode));
|
|
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().SwitchToBlueprintDefaultsMode,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetCurrentMode, FBlueprintEditorApplicationModes::BlueprintDefaultsMode),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsModeCurrent, FBlueprintEditorApplicationModes::BlueprintDefaultsMode));
|
|
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().SwitchToComponentsMode,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetCurrentMode, FBlueprintEditorApplicationModes::BlueprintComponentsMode),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanAccessComponentsMode),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsModeCurrent, FBlueprintEditorApplicationModes::BlueprintComponentsMode));
|
|
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().EditGlobalOptions,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::EditGlobalOptions_Clicked),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsDetailsPanelEditingGlobalOptions));
|
|
|
|
ToolkitCommands->MapAction(
|
|
FFullBlueprintEditorCommands::Get().EditClassDefaults,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::EditClassDefaults_Clicked),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsDetailsPanelEditingClassDefaults));
|
|
|
|
// Edit menu actions
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().FindInBlueprint,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::FindInBlueprint_Clicked),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::IsInAScriptingMode )
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().ReparentBlueprint,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::ReparentBlueprint_Clicked),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FBlueprintEditor::ReparentBlueprint_IsVisible)
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FGenericCommands::Get().Undo,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::UndoGraphAction ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanUndoGraphAction )
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FGenericCommands::Get().Redo,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::RedoGraphAction ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanRedoGraphAction )
|
|
);
|
|
|
|
|
|
// View commands
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().ZoomToWindow,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::ZoomToWindow_Clicked ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanZoomToWindow )
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().ZoomToSelection,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::ZoomToSelection_Clicked ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanZoomToSelection )
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().NavigateToParent,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::NavigateToParentGraph ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanNavigateToParentGraph )
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().NavigateToParentBackspace,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::NavigateToParentGraph ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanNavigateToParentGraph )
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().NavigateToChild,
|
|
FExecuteAction::CreateSP( this, &FBlueprintEditor::NavigateToChildGraph ),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::CanNavigateToChildGraph )
|
|
);
|
|
|
|
ToolkitCommands->MapAction(FGraphEditorCommands::Get().ShowAllPins,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetPinVisibility, SGraphEditor::Pin_Show),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::GetPinVisibility, SGraphEditor::Pin_Show));
|
|
|
|
ToolkitCommands->MapAction(FGraphEditorCommands::Get().HideNoConnectionPins,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetPinVisibility, SGraphEditor::Pin_HideNoConnection),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::GetPinVisibility, SGraphEditor::Pin_HideNoConnection));
|
|
|
|
ToolkitCommands->MapAction(FGraphEditorCommands::Get().HideNoConnectionNoDefaultPins,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetPinVisibility, SGraphEditor::Pin_HideNoConnectionNoDefault),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::GetPinVisibility, SGraphEditor::Pin_HideNoConnectionNoDefault));
|
|
|
|
// Compile
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().CompileBlueprint,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::Compile),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::IsCompilingEnabled)
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().RefreshAllNodes,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::RefreshAllNodes_OnClicked),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::IsInAScriptingMode )
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().DeleteUnusedVariables,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::DeleteUnusedVariables_OnClicked),
|
|
FCanExecuteAction::CreateSP( this, &FBlueprintEditor::IsInAScriptingMode )
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().FindInBlueprints,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::FindInBlueprints_OnClicked)
|
|
);
|
|
|
|
// Debug actions
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().ClearAllBreakpoints,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::ClearAllBreakpoints),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::HasAnyBreakpoints)
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().DisableAllBreakpoints,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::DisableAllBreakpoints),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::HasAnyEnabledBreakpoints)
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().EnableAllBreakpoints,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::EnableAllBreakpoints),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::HasAnyDisabledBreakpoints)
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().ClearAllWatches,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::ClearAllWatches),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::HasAnyWatches)
|
|
);
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().OpenBlueprintDebugger,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OpenBlueprintDebugger),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanOpenBlueprintDebugger)
|
|
);
|
|
|
|
// New document actions
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().AddNewVariable,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnAddNewVariable),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::InEditingMode),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewVariable));
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().AddNewLocalVariable,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnAddNewLocalVariable),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::CanAddNewLocalVariable),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewLocalVariable));
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().AddNewFunction,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::NewDocument_OnClicked, CGT_NewFunctionGraph),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::InEditingMode),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewFunctionGraph));
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().AddNewEventGraph,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::NewDocument_OnClicked, CGT_NewEventGraph),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::InEditingMode),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewEventGraph));
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().AddNewMacroDeclaration,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::NewDocument_OnClicked, CGT_NewMacroGraph),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::InEditingMode),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewMacroGraph));
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().AddNewDelegate,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnAddNewDelegate),
|
|
FCanExecuteAction::CreateSP(this, &FBlueprintEditor::InEditingMode),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FBlueprintEditor::AddNewDelegateIsVisible));
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().FindReferencesFromClass,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnListObjectsReferencedByClass),
|
|
FCanExecuteAction());
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().FindReferencesFromBlueprint,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnListObjectsReferencedByBlueprint),
|
|
FCanExecuteAction());
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().RepairCorruptedBlueprint,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnRepairCorruptedBlueprint),
|
|
FCanExecuteAction());
|
|
|
|
ToolkitCommands->MapAction( FBlueprintEditorCommands::Get().AddNewAnimationLayer,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::NewDocument_OnClicked, CGT_NewAnimationLayer),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked(),
|
|
FIsActionButtonVisible::CreateSP(this, &FBlueprintEditor::NewDocument_IsVisibleForType, CGT_NewAnimationLayer));
|
|
|
|
ToolkitCommands->MapAction(FBlueprintEditorCommands::Get().SaveIntermediateBuildProducts,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::ToggleSaveIntermediateBuildProducts),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::GetSaveIntermediateBuildProducts));
|
|
|
|
ToolkitCommands->MapAction(FBlueprintEditorCommands::Get().BeginBlueprintMerge,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::CreateMergeToolTab),
|
|
FCanExecuteAction());
|
|
|
|
ToolkitCommands->MapAction(FBlueprintEditorCommands::Get().GenerateSearchIndex,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnGenerateSearchIndexForDebugging),
|
|
FCanExecuteAction());
|
|
|
|
ToolkitCommands->MapAction(FBlueprintEditorCommands::Get().DumpCachedIndexData,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnDumpCachedIndexDataForBlueprint),
|
|
FCanExecuteAction());
|
|
|
|
ToolkitCommands->MapAction(FBlueprintEditorCommands::Get().ShowActionMenuItemSignatures,
|
|
FExecuteAction::CreateLambda([]()
|
|
{
|
|
UBlueprintEditorSettings* Settings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
Settings->bShowActionMenuItemSignatures = !Settings->bShowActionMenuItemSignatures;
|
|
Settings->SaveConfig();
|
|
}),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateLambda([]()->bool{ return GetDefault<UBlueprintEditorSettings>()->bShowActionMenuItemSignatures; }));
|
|
|
|
for (int32 QuickJumpIndex = 0; QuickJumpIndex < FGraphEditorCommands::Get().QuickJumpCommands.Num(); ++QuickJumpIndex)
|
|
{
|
|
ToolkitCommands->MapAction(
|
|
FGraphEditorCommands::Get().QuickJumpCommands[QuickJumpIndex].QuickJump,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::OnGraphEditorQuickJump, QuickJumpIndex)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(
|
|
FGraphEditorCommands::Get().QuickJumpCommands[QuickJumpIndex].SetQuickJump,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::SetGraphEditorQuickJump, QuickJumpIndex)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(
|
|
FGraphEditorCommands::Get().QuickJumpCommands[QuickJumpIndex].ClearQuickJump,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::ClearGraphEditorQuickJump, QuickJumpIndex)
|
|
);
|
|
}
|
|
|
|
ToolkitCommands->MapAction(
|
|
FGraphEditorCommands::Get().ClearAllQuickJumps,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::ClearAllGraphEditorQuickJumps)
|
|
);
|
|
|
|
ToolkitCommands->MapAction(
|
|
FBlueprintEditorCommands::Get().ToggleHideUnrelatedNodes,
|
|
FExecuteAction::CreateSP(this, &FBlueprintEditor::ToggleHideUnrelatedNodes),
|
|
FCanExecuteAction(),
|
|
FIsActionChecked::CreateSP(this, &FBlueprintEditor::IsToggleHideUnrelatedNodesChecked),
|
|
FIsActionButtonVisible()
|
|
);
|
|
}
|
|
|
|
void FBlueprintEditor::OnGenerateSearchIndexForDebugging()
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint)
|
|
{
|
|
FString FileLocation = FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir() + TEXT("BlueprintSearchTools"));
|
|
FString FullPath = FString::Printf(TEXT("%s/%s.index.xml"), *FileLocation, *Blueprint->GetName());
|
|
|
|
FArchive* DumpFile = IFileManager::Get().CreateFileWriter(*FullPath);
|
|
if (DumpFile)
|
|
{
|
|
FString JsonOutput = FFindInBlueprintSearchManager::Get().GenerateSearchIndexForDebugging(Blueprint);
|
|
DumpFile->Serialize(TCHAR_TO_ANSI(*JsonOutput), JsonOutput.Len());
|
|
|
|
DumpFile->Close();
|
|
delete DumpFile;
|
|
|
|
UE_LOG(LogFindInBlueprint, Log, TEXT("Wrote search index to %s"), *FullPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnDumpCachedIndexDataForBlueprint()
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint)
|
|
{
|
|
FString FileLocation = FPaths::ConvertRelativePathToFull(FPaths::ProjectIntermediateDir() + TEXT("BlueprintSearchTools"));
|
|
FString FullPath = FString::Printf(TEXT("%s/%s.cache.csv"), *FileLocation, *Blueprint->GetName());
|
|
|
|
FArchive* DumpFile = IFileManager::Get().CreateFileWriter(*FullPath);
|
|
if (DumpFile)
|
|
{
|
|
const FSoftObjectPath AssetPath(Blueprint);
|
|
FSearchData SearchData = FFindInBlueprintSearchManager::Get().GetSearchDataForAssetPath(AssetPath);
|
|
if (SearchData.IsValid() && SearchData.ImaginaryBlueprint.IsValid())
|
|
{
|
|
SearchData.ImaginaryBlueprint->DumpParsedObject(*DumpFile);
|
|
}
|
|
|
|
DumpFile->Close();
|
|
delete DumpFile;
|
|
|
|
UE_LOG(LogFindInBlueprint, Log, TEXT("Wrote cached index data to %s"), *FullPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::FindInBlueprint_Clicked()
|
|
{
|
|
SummonSearchUI(true);
|
|
}
|
|
|
|
void FBlueprintEditor::ReparentBlueprint_Clicked()
|
|
{
|
|
if (!ReparentBlueprint_IsVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<UBlueprint*> Blueprints;
|
|
for (int32 i = 0; i < GetEditingObjects().Num(); ++i)
|
|
{
|
|
UBlueprint* Blueprint = Cast<UBlueprint>(GetEditingObjects()[i]);
|
|
if (Blueprint)
|
|
{
|
|
Blueprints.Add(Blueprint);
|
|
}
|
|
}
|
|
FBlueprintEditorUtils::OpenReparentBlueprintMenu(Blueprints, GetToolkitHost()->GetParentWidget(), FOnClassPicked::CreateSP(this, &FBlueprintEditor::ReparentBlueprint_NewParentChosen));
|
|
}
|
|
|
|
void FBlueprintEditor::ReparentBlueprint_NewParentChosen(UClass* ChosenClass)
|
|
{
|
|
UBlueprint* BlueprintObj = GetBlueprintObj();
|
|
|
|
if ((BlueprintObj != nullptr) && (ChosenClass != nullptr) && (ChosenClass != BlueprintObj->ParentClass))
|
|
{
|
|
// Notify user, about common interfaces
|
|
bool bReparent = true;
|
|
{
|
|
FString CommonInterfacesNames;
|
|
for (const FBPInterfaceDescription& InterdaceDesc : BlueprintObj->ImplementedInterfaces)
|
|
{
|
|
if (ChosenClass->ImplementsInterface(*InterdaceDesc.Interface))
|
|
{
|
|
CommonInterfacesNames += InterdaceDesc.Interface->GetName();
|
|
CommonInterfacesNames += TCHAR('\n');
|
|
}
|
|
}
|
|
if (!CommonInterfacesNames.IsEmpty())
|
|
{
|
|
const FText Title = LOCTEXT("CommonInterfacesTitle", "Common interfaces");
|
|
const FText Message = FText::Format(
|
|
LOCTEXT("ReparentWarning_InterfacesImplemented", "Following interfaces are already implemented. Continue reparenting? \n {0}"),
|
|
FText::FromString(CommonInterfacesNames));
|
|
|
|
FSuppressableWarningDialog::FSetupInfo Info(Message, Title, "Warning_CommonInterfacesWhileReparenting");
|
|
Info.ConfirmText = LOCTEXT("ReparentYesButton", "Reparent");
|
|
Info.CancelText = LOCTEXT("ReparentNoButton", "Cancel");
|
|
|
|
FSuppressableWarningDialog ReparentBlueprintDlg(Info);
|
|
if (ReparentBlueprintDlg.ShowModal() == FSuppressableWarningDialog::Cancel)
|
|
{
|
|
bReparent = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// If the chosen class differs hierarchically from the current class, warn that there may be data loss
|
|
if (bReparent && (!BlueprintObj->ParentClass || !ChosenClass->GetDefaultObject()->IsA(BlueprintObj->ParentClass)))
|
|
{
|
|
const FText Title = LOCTEXT("ReparentTitle", "Reparent Blueprint");
|
|
const FText Message = LOCTEXT("ReparentWarning", "Reparenting this blueprint may cause data loss. Continue reparenting?");
|
|
|
|
// Warn the user that this may result in data loss
|
|
FSuppressableWarningDialog::FSetupInfo Info( Message, Title, "Warning_ReparentTitle" );
|
|
Info.ConfirmText = LOCTEXT("ReparentYesButton", "Reparent");
|
|
Info.CancelText = LOCTEXT("ReparentNoButton", "Cancel");
|
|
Info.CheckBoxText = FText::GetEmpty(); // not suppressible
|
|
|
|
FSuppressableWarningDialog ReparentBlueprintDlg( Info );
|
|
if( ReparentBlueprintDlg.ShowModal() == FSuppressableWarningDialog::Cancel )
|
|
{
|
|
bReparent = false;
|
|
}
|
|
}
|
|
|
|
if ( bReparent )
|
|
{
|
|
// Notify that we are currently reparenting this blueprint so that we get the proper compilation flags
|
|
TGuardValue<bool> GuardValue(bIsReparentingBlueprint, true);
|
|
|
|
const FScopedTransaction Transaction( LOCTEXT("ReparentBlueprint", "Reparent Blueprint") );
|
|
UE_LOG(LogBlueprint, Warning, TEXT("Reparenting blueprint %s from %s to %s..."), *BlueprintObj->GetFullName(), BlueprintObj->ParentClass ? *BlueprintObj->ParentClass->GetName() : TEXT("[None]"), *ChosenClass->GetName());
|
|
|
|
BlueprintObj->Modify();
|
|
if(USimpleConstructionScript* SCS = BlueprintObj->SimpleConstructionScript)
|
|
{
|
|
SCS->Modify();
|
|
|
|
const TArray<USCS_Node*>& AllNodes = SCS->GetAllNodes();
|
|
for(USCS_Node* Node : AllNodes )
|
|
{
|
|
Node->Modify();
|
|
}
|
|
}
|
|
|
|
// Gather the set of default imports with the old parent class set.
|
|
TSet<FString> OldDefaultImports;
|
|
FBlueprintNamespaceUtilities::GetDefaultImportsForObject(BlueprintObj, OldDefaultImports);
|
|
|
|
UClass* OldParentClass = BlueprintObj->ParentClass;
|
|
BlueprintObj->ParentClass = ChosenClass;
|
|
|
|
// Gather the set of default imports with the new parent class set.
|
|
TSet<FString> NewDefaultImports;
|
|
FBlueprintNamespaceUtilities::GetDefaultImportsForObject(BlueprintObj, NewDefaultImports);
|
|
|
|
// Move namespace imports that no longer appear in the default set to the explicit set.
|
|
FImportNamespaceExParameters Params;
|
|
Params.bIsAutoImport = false;
|
|
Params.NamespacesToImport = OldDefaultImports.Difference(NewDefaultImports);
|
|
ImportNamespaceEx(Params);
|
|
|
|
// Ensure that the Blueprint is up-to-date (valid SCS etc.) before compiling
|
|
EnsureBlueprintIsUpToDate(BlueprintObj);
|
|
FBlueprintEditorUtils::RefreshAllNodes(GetBlueprintObj());
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(BlueprintObj);
|
|
|
|
// Changing the parent may change the sparse data used, so mark any current
|
|
// sparse data as requiring a conform post-compile against the new archetype
|
|
if (UBlueprintGeneratedClass* Class = Cast<UBlueprintGeneratedClass>(BlueprintObj->GeneratedClass))
|
|
{
|
|
Class->PrepareToConformSparseClassData(ChosenClass->GetSparseClassDataStruct());
|
|
}
|
|
|
|
Compile();
|
|
|
|
// Ensure that the Blueprint is up-to-date (valid SCS etc.) after compiling (new parent class)
|
|
EnsureBlueprintIsUpToDate(BlueprintObj);
|
|
|
|
if(SubobjectEditor.IsValid())
|
|
{
|
|
SubobjectEditor->UpdateTree();
|
|
}
|
|
}
|
|
}
|
|
|
|
//@TODO: This is probably insufficient as you could reparent the parent instead and we wouldn't get notified
|
|
ModifyDuringPIEStatus = ESafeToModifyDuringPIEStatus::Unknown;
|
|
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
|
|
bool FBlueprintEditor::ReparentBlueprint_IsVisible() const
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint != nullptr)
|
|
{
|
|
// Don't show the reparent option if it's an Interface or we're not in editing mode
|
|
return !FBlueprintEditorUtils::IsInterfaceBlueprint(Blueprint) && InEditingMode() && (BPTYPE_FunctionLibrary != Blueprint->BlueprintType);
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::IsDetailsPanelEditingGlobalOptions() const
|
|
{
|
|
return CurrentUISelection == FBlueprintEditor::SelectionState_ClassSettings;
|
|
}
|
|
|
|
void FBlueprintEditor::EditGlobalOptions_Clicked()
|
|
{
|
|
SetUISelectionState(FBlueprintEditor::SelectionState_ClassSettings);
|
|
|
|
if (bWasOpenedInDefaultsMode)
|
|
{
|
|
RefreshStandAloneDefaultsEditor();
|
|
}
|
|
else
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint != nullptr)
|
|
{
|
|
// Show details for the Blueprint instance we're editing
|
|
Inspector->ShowDetailsForSingleObject(Blueprint);
|
|
|
|
TryInvokingDetailsTab();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::IsDetailsPanelEditingClassDefaults() const
|
|
{
|
|
if (bWasOpenedInDefaultsMode)
|
|
{
|
|
return !IsDetailsPanelEditingGlobalOptions();
|
|
}
|
|
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if ( Blueprint != nullptr )
|
|
{
|
|
if ( Blueprint->GeneratedClass != nullptr )
|
|
{
|
|
UObject* DefaultObject = GetBlueprintObj()->GeneratedClass->GetDefaultObject();
|
|
return Inspector->IsSelected(DefaultObject);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::EditClassDefaults_Clicked()
|
|
{
|
|
StartEditingDefaults(true, true);
|
|
}
|
|
|
|
// Zooming to fit the entire graph
|
|
void FBlueprintEditor::ZoomToWindow_Clicked()
|
|
{
|
|
if (SGraphEditor* GraphEd = FocusedGraphEdPtr.Pin().Get())
|
|
{
|
|
GraphEd->ZoomToFit(/*bOnlySelection=*/ false);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanZoomToWindow() const
|
|
{
|
|
return FocusedGraphEdPtr.IsValid();
|
|
}
|
|
|
|
// Zooming to fit the current selection
|
|
void FBlueprintEditor::ZoomToSelection_Clicked()
|
|
{
|
|
if (SGraphEditor* GraphEd = FocusedGraphEdPtr.Pin().Get())
|
|
{
|
|
GraphEd->ZoomToFit(/*bOnlySelection=*/ true);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanZoomToSelection() const
|
|
{
|
|
return FocusedGraphEdPtr.IsValid();
|
|
}
|
|
|
|
// Navigating into/out of graphs
|
|
void FBlueprintEditor::NavigateToParentGraphByDoubleClick()
|
|
{
|
|
UBlueprintEditorSettings const* Settings = GetDefault<UBlueprintEditorSettings>();
|
|
if (Settings->bDoubleClickNavigatesToParent)
|
|
{
|
|
NavigateToParentGraph();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::NavigateToParentGraph()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
if (UEdGraph* ParentGraph = UEdGraph::GetOuterGraph(FocusedGraphEdPtr.Pin()->GetCurrentGraph()))
|
|
{
|
|
JumpToHyperlink(ParentGraph, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanNavigateToParentGraph() const
|
|
{
|
|
return FocusedGraphEdPtr.IsValid() && UEdGraph::GetOuterGraph(FocusedGraphEdPtr.Pin()->GetCurrentGraph());
|
|
}
|
|
|
|
void FBlueprintEditor::NavigateToChildGraph()
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
UEdGraph* CurrentGraph = FocusedGraphEdPtr.Pin()->GetCurrentGraph();
|
|
|
|
if (CurrentGraph->Nodes.Num() > 0)
|
|
{
|
|
// Display a child jump list
|
|
FMenuBuilder MenuBuilder(true, nullptr);
|
|
MenuBuilder.BeginSection("NavigateToGraph", LOCTEXT("ChildGraphPickerDesc", "Navigate to graph"));
|
|
|
|
TArray<TObjectPtr<UEdGraphNode>> SortedGraphNodes = CurrentGraph->Nodes;
|
|
SortedGraphNodes.Sort([](UEdGraphNode& A, UEdGraphNode& B) {
|
|
FText AName = A.GetNodeTitle(ENodeTitleType::ListView);
|
|
FText BName = B.GetNodeTitle(ENodeTitleType::ListView);
|
|
return AName.CompareToCaseIgnored(BName) < 0; });
|
|
|
|
TObjectPtr<UEdGraphNode> SingleNode;
|
|
int32 NumEntries = 0;
|
|
|
|
for (const TObjectPtr<UEdGraphNode>& Node : SortedGraphNodes)
|
|
{
|
|
// Just calling CanJumpToDefinition isn't enough as it returns true for functions (resulting in a jump
|
|
// to code, which isn't desired).
|
|
UObject* TargetObject = Node->GetJumpTargetForDoubleClick();
|
|
if (TargetObject && Node->CanJumpToDefinition())
|
|
{
|
|
++NumEntries;
|
|
SingleNode = Node;
|
|
MenuBuilder.AddMenuEntry(
|
|
Node->GetNodeTitle(ENodeTitleType::ListView),
|
|
LOCTEXT("ChildGraphPickerTooltip", "Pick the graph to enter"),
|
|
FSlateIcon(),
|
|
FUIAction(
|
|
FExecuteAction::CreateLambda(
|
|
[this, Node]()
|
|
{
|
|
if (Node->CanJumpToDefinition())
|
|
{
|
|
Node->JumpToDefinition();
|
|
}
|
|
// Note that setting the keyboard focus here doesn't work when navigating into a
|
|
// blendspace (i.e. a UEdGraph). Neither does trying to set the focus to
|
|
// Node->DEPRECATED_NodeWidget
|
|
SetKeyboardFocus();
|
|
}),
|
|
FCanExecuteAction()));
|
|
}
|
|
}
|
|
MenuBuilder.EndSection();
|
|
|
|
if (NumEntries > 0)
|
|
{
|
|
// If there is only one entry we could just jump straight to it. However, sometimes that can be a little
|
|
// disorientating if it was not obvious to the user exactly what the targets might be.
|
|
FSlateApplication::Get().PushMenu(
|
|
GetToolkitHost()->GetParentWidget(),
|
|
FWidgetPath(),
|
|
MenuBuilder.MakeWidget(),
|
|
FSlateApplication::Get().GetCursorPos(), // summon location
|
|
FPopupTransitionEffect( FPopupTransitionEffect::TypeInPopup )
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanNavigateToChildGraph() const
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
UEdGraph* CurrentGraph = FocusedGraphEdPtr.Pin()->GetCurrentGraph();
|
|
for (const TObjectPtr<UEdGraphNode>& Node : CurrentGraph->Nodes)
|
|
{
|
|
UObject* TargetObject = Node->GetJumpTargetForDoubleClick();
|
|
if (TargetObject && Node->CanJumpToDefinition())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::SetKeyboardFocus()
|
|
{
|
|
FSlateApplication::Get().SetKeyboardFocus(GetMyBlueprintWidget());
|
|
}
|
|
|
|
TSharedRef<SBlueprintPalette> FBlueprintEditor::GetPalette()
|
|
{
|
|
// Note: construction is deferred until first access; in large-scale projects this can be an expensive widget to construct during editor
|
|
// initialization logic. It's an unnecessary cost if the tab it's hosted in is closed and/or unavailable (e.g. as in the default layout).
|
|
if (!Palette.IsValid())
|
|
{
|
|
SAssignNew(Palette, SBlueprintPalette, SharedThis(this))
|
|
.IsEnabled(this, &FBlueprintEditor::IsFocusedGraphEditable);
|
|
}
|
|
|
|
return Palette.ToSharedRef();
|
|
}
|
|
|
|
bool FBlueprintEditor::TransactionObjectAffectsBlueprint(UObject* InTransactedObject)
|
|
{
|
|
check(InTransactedObject);
|
|
|
|
UBlueprint* BlueprintObj = GetBlueprintObj();
|
|
check(BlueprintObj);
|
|
|
|
return InTransactedObject->GetOutermost() == BlueprintObj->GetOutermost();
|
|
}
|
|
|
|
void FBlueprintEditor::HandleUndoTransaction(const FTransaction* Transaction)
|
|
{
|
|
UBlueprint* BlueprintObj = GetBlueprintObj();
|
|
if (BlueprintObj && Transaction)
|
|
{
|
|
bool bAffectsBlueprint = false;
|
|
const UPackage* BlueprintOutermost = BlueprintObj->GetOutermost();
|
|
|
|
// Look at the transaction this function is responding to, see if any object in it has an outermost of the Blueprint
|
|
TArray<UObject*> TransactionObjects;
|
|
Transaction->GetTransactionObjects(TransactionObjects);
|
|
|
|
for (UObject* Object : TransactionObjects)
|
|
{
|
|
if(TransactionObjectAffectsBlueprint(Object))
|
|
{
|
|
bAffectsBlueprint = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Transaction affects the Blueprint this editor handles, so react as necessary
|
|
if (bAffectsBlueprint)
|
|
{
|
|
// Do not clear the selection on undo for the component tree so that the details panel
|
|
// does not get cleared unnecessarily
|
|
if(CurrentUISelection != SelectionState_Components)
|
|
{
|
|
SetUISelectionState(NAME_None);
|
|
}
|
|
|
|
RefreshEditors();
|
|
|
|
FSlateApplication::Get().DismissAllMenus();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::PostUndo(bool bSuccess)
|
|
{
|
|
if (bSuccess)
|
|
{
|
|
const FTransaction* Transaction = GEditor->Trans->GetTransaction(GEditor->Trans->GetQueueLength() - GEditor->Trans->GetUndoCount());
|
|
HandleUndoTransaction(Transaction);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::PostRedo(bool bSuccess)
|
|
{
|
|
if (bSuccess)
|
|
{
|
|
const FTransaction* Transaction = GEditor->Trans->GetTransaction(GEditor->Trans->GetQueueLength() - GEditor->Trans->GetUndoCount() - 1);
|
|
HandleUndoTransaction(Transaction);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::UndoGraphAction()
|
|
{
|
|
GEditor->UndoTransaction();
|
|
}
|
|
|
|
bool FBlueprintEditor::CanUndoGraphAction() const
|
|
{
|
|
//@TODO: Should probably allow this for BPs that can be edited during PIE (basically returning InEditingMode instead)
|
|
return !IsPlayInEditorActive();
|
|
}
|
|
|
|
void FBlueprintEditor::RedoGraphAction()
|
|
{
|
|
GEditor->RedoTransaction();
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRedoGraphAction() const
|
|
{
|
|
//@TODO: Should probably allow this for BPs that can be edited during PIE (basically returning InEditingMode instead)
|
|
return !IsPlayInEditorActive();
|
|
}
|
|
|
|
void FBlueprintEditor::OnActiveTabChanged(TSharedPtr<SDockTab> PreviouslyActive, TSharedPtr<SDockTab> NewlyActivated)
|
|
{
|
|
}
|
|
|
|
void FBlueprintEditor::OnGraphEditorFocused(const TSharedRef<SGraphEditor>& InGraphEditor)
|
|
{
|
|
// Update the graph editor that is currently focused
|
|
FocusedGraphEdPtr = InGraphEditor;
|
|
InGraphEditor->SetPinVisibility(PinVisibility);
|
|
|
|
// Update the inspector as well, to show selection from the focused graph editor
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
FocusInspectorOnGraphSelection(SelectedNodes, /*bForceRefresh=*/ true);
|
|
|
|
// During undo, garbage graphs can be temporarily brought into focus, ensure that before a refresh of the MyBlueprint window that the graph is owned by a Blueprint
|
|
if ( FocusedGraphEdPtr.IsValid() && MyBlueprintWidget.IsValid() )
|
|
{
|
|
// The focused graph can be garbage as well
|
|
TWeakObjectPtr< UEdGraph > FocusedGraphPtr = FocusedGraphEdPtr.Pin()->GetCurrentGraph();
|
|
UEdGraph* FocusedGraph = FocusedGraphPtr.Get();
|
|
|
|
if ( FocusedGraph != nullptr )
|
|
{
|
|
if ( FBlueprintEditorUtils::FindBlueprintForGraph(FocusedGraph) )
|
|
{
|
|
MyBlueprintWidget->Refresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bHideUnrelatedNodes && SelectedNodes.Num() <= 0)
|
|
{
|
|
ResetAllNodesUnrelatedStates();
|
|
}
|
|
|
|
// If the bookmarks view is active, check whether or not we're restricting the view to the current graph. If we are, update the tree to reflect the focused graph context.
|
|
if (BookmarksWidget.IsValid()
|
|
&& GetDefault<UBlueprintEditorSettings>()->bShowBookmarksForCurrentDocumentOnlyInTab)
|
|
{
|
|
BookmarksWidget->RefreshBookmarksTree();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnGraphEditorBackgrounded(const TSharedRef<SGraphEditor>& InGraphEditor)
|
|
{
|
|
// If the newly active document tab isn't a graph we want to make sure we clear the focused graph pointer.
|
|
// Several other UI reads that, like the MyBlueprints view uses it to determine if it should show the "Local Variable" section.
|
|
FocusedGraphEdPtr = nullptr;
|
|
|
|
if ( MyBlueprintWidget.IsValid() == true )
|
|
{
|
|
MyBlueprintWidget->Refresh();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnGraphEditorDropActor(const TArray< TWeakObjectPtr<AActor> >& Actors, UEdGraph* Graph, const FVector2f& DropLocation)
|
|
{
|
|
// We need to check that the dropped actor is in the right sublevel for the reference
|
|
ULevel* BlueprintLevel = FBlueprintEditorUtils::GetLevelFromBlueprint(GetBlueprintObj());
|
|
|
|
if (BlueprintLevel && FBlueprintEditorUtils::IsLevelScriptBlueprint(GetBlueprintObj()))
|
|
{
|
|
FDeprecateSlateVector2D NodeLocation = DropLocation;
|
|
for (int32 i = 0; i < Actors.Num(); i++)
|
|
{
|
|
AActor* DroppedActor = Actors[i].Get();
|
|
if (DroppedActor&& (DroppedActor->GetLevel() == BlueprintLevel) && !DroppedActor->IsChildActor())
|
|
{
|
|
UK2Node_Literal* ActorRefNode = FEdGraphSchemaAction_K2NewNode::SpawnNode<UK2Node_Literal>(
|
|
Graph,
|
|
NodeLocation,
|
|
EK2NewNodeFlags::SelectNewNode,
|
|
[DroppedActor](UK2Node_Literal* NewInstance)
|
|
{
|
|
NewInstance->SetObjectRef(DroppedActor);
|
|
}
|
|
);
|
|
NodeLocation.Y += UEdGraphSchema_K2::EstimateNodeHeight(ActorRefNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnGraphEditorDropStreamingLevel(const TArray< TWeakObjectPtr<ULevelStreaming> >& Levels, UEdGraph* Graph, const FVector2f& DropLocation)
|
|
{
|
|
UFunction* TargetFunc = UGameplayStatics::StaticClass()->FindFunctionByName(GET_FUNCTION_NAME_CHECKED(UGameplayStatics, GetStreamingLevel));
|
|
check(TargetFunc);
|
|
|
|
for (int32 i = 0; i < Levels.Num(); i++)
|
|
{
|
|
ULevelStreaming* DroppedLevel = Levels[i].Get();
|
|
if (DroppedLevel && DroppedLevel->IsA<ULevelStreamingDynamic>())
|
|
{
|
|
UK2Node_CallFunction* Node = FEdGraphSchemaAction_K2NewNode::SpawnNode<UK2Node_CallFunction>(
|
|
Graph,
|
|
FDeprecateSlateVector2D(DropLocation + (i * FVector2f(0.0f, 80.0f))),
|
|
EK2NewNodeFlags::SelectNewNode,
|
|
[TargetFunc](UK2Node_CallFunction* NewInstance)
|
|
{
|
|
NewInstance->SetFromFunction(TargetFunc);
|
|
}
|
|
);
|
|
|
|
// Set dropped level package name
|
|
UEdGraphPin* PackageNameInputPin = Node->FindPinChecked(TEXT("PackageName"));
|
|
PackageNameInputPin->DefaultValue = DroppedLevel->GetWorldAssetPackageName();
|
|
}
|
|
}
|
|
}
|
|
|
|
FActionMenuContent FBlueprintEditor::OnCreateGraphActionMenu(UEdGraph* InGraph, const FVector2D& InNodePosition, const TArray<UEdGraphPin*>& InDraggedPins, bool bAutoExpand, SGraphEditor::FActionMenuClosed InOnMenuClosed)
|
|
{
|
|
return OnCreateGraphActionMenu(InGraph, UE::Slate::CastToVector2f(InNodePosition), InDraggedPins, bAutoExpand, InOnMenuClosed);
|
|
}
|
|
|
|
FActionMenuContent FBlueprintEditor::OnCreateGraphActionMenu(UEdGraph* InGraph, const FVector2f& InNodePosition, const TArray<UEdGraphPin*>& InDraggedPins, bool bAutoExpand, SGraphEditor::FActionMenuClosed InOnMenuClosed)
|
|
{
|
|
HasOpenActionMenu = InGraph;
|
|
if (!BlueprintEditorImpl::GraphHasUserPlacedNodes(InGraph))
|
|
{
|
|
InstructionsFadeCountdown = BlueprintEditorImpl::InstructionFadeDuration;
|
|
}
|
|
|
|
TSharedRef<SBlueprintActionMenu> ActionMenu =
|
|
SNew(SBlueprintActionMenu, SharedThis(this))
|
|
.GraphObj(InGraph)
|
|
.NewNodePosition(FDeprecateSlateVector2D(InNodePosition))
|
|
.DraggedFromPins(InDraggedPins)
|
|
.AutoExpandActionMenu(bAutoExpand)
|
|
.OnClosedCallback(InOnMenuClosed)
|
|
.OnCloseReason(this, &FBlueprintEditor::OnGraphActionMenuClosed);
|
|
|
|
return FActionMenuContent( ActionMenu, ActionMenu->GetFilterTextBox() );
|
|
}
|
|
|
|
void FBlueprintEditor::OnGraphActionMenuClosed(bool bActionExecuted, bool bContextSensitiveChecked, bool bGraphPinContext)
|
|
{
|
|
if (bActionExecuted)
|
|
{
|
|
bContextSensitiveChecked ? AnalyticsStats.GraphActionMenusCtxtSensitiveExecCount++ : AnalyticsStats.GraphActionMenusNonCtxtSensitiveExecCount++;
|
|
UpdateNodeCreationStats( bGraphPinContext ? ENodeCreateAction::PinContext : ENodeCreateAction::GraphContext );
|
|
}
|
|
else
|
|
{
|
|
AnalyticsStats.GraphActionMenusCancelledCount++;
|
|
}
|
|
|
|
if (UEdGraph* EditingGraph = GetFocusedGraph())
|
|
{
|
|
// if the user didn't place any nodes...
|
|
if (!BlueprintEditorImpl::GraphHasUserPlacedNodes(EditingGraph))
|
|
{
|
|
InstructionsFadeCountdown = 0.0f;
|
|
}
|
|
}
|
|
HasOpenActionMenu = nullptr;
|
|
}
|
|
|
|
void FBlueprintEditor::OnSelectedNodesChangedImpl(const FGraphPanelSelectionSet& NewSelection)
|
|
{
|
|
if ( NewSelection.Num() > 0 )
|
|
{
|
|
SetUISelectionState(FBlueprintEditor::SelectionState_Graph);
|
|
}
|
|
|
|
SKismetInspector::FShowDetailsOptions DetailsOptions;
|
|
DetailsOptions.bForceRefresh = true;
|
|
Inspector->ShowDetailsForObjects(NewSelection.Array(), DetailsOptions);
|
|
|
|
bSelectRegularNode = false;
|
|
for (FGraphPanelSelectionSet::TConstIterator It(NewSelection); It; ++It)
|
|
{
|
|
UEdGraphNode_Comment* SeqNode = Cast<UEdGraphNode_Comment>(*It);
|
|
if (!SeqNode)
|
|
{
|
|
bSelectRegularNode = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bHideUnrelatedNodes && !bLockNodeFadeState)
|
|
{
|
|
ResetAllNodesUnrelatedStates();
|
|
|
|
if ( bSelectRegularNode )
|
|
{
|
|
HideUnrelatedNodes();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnBlueprintChangedImpl(UBlueprint* InBlueprint, bool bIsJustBeingCompiled )
|
|
{
|
|
if (InBlueprint)
|
|
{
|
|
// Notify that the blueprint has been changed (update Content browser, etc)
|
|
InBlueprint->PostEditChange();
|
|
|
|
// Call PostEditChange() on any Actors that are based on this Blueprint
|
|
FBlueprintEditorUtils::PostEditChangeBlueprintActors(InBlueprint);
|
|
|
|
// Refresh the graphs
|
|
ERefreshBlueprintEditorReason::Type Reason = bIsJustBeingCompiled ? ERefreshBlueprintEditorReason::BlueprintCompiled : ERefreshBlueprintEditorReason::UnknownReason;
|
|
RefreshEditors(Reason);
|
|
|
|
// In case objects were deleted, which should close the tab
|
|
if (GetCurrentMode() == FBlueprintEditorApplicationModes::StandardBlueprintEditorMode)
|
|
{
|
|
SaveEditedObjectState();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnBlueprintCompiled(UBlueprint* InBlueprint)
|
|
{
|
|
if( InBlueprint )
|
|
{
|
|
UUnrealEdEngine* EditorEngine = GUnrealEd;
|
|
// GUnrealEd can be nullptr after a hot-reload... this seems like a bigger
|
|
// problem worth investigating (that could affect other systems), but
|
|
// as I cannot repro it a second time (to see if it gets reset soon after),
|
|
// we'll just gaurd here for now and see if we can tie this ensure to any
|
|
// future crash reports
|
|
if (ensure(EditorEngine != nullptr))
|
|
{
|
|
// Compiling will invalidate any cached components in the component visualizer, so clear out active components here
|
|
EditorEngine->ComponentVisManager.ClearActiveComponentVis();
|
|
}
|
|
|
|
// This could be made more efficient by tracking which nodes change
|
|
// their bHasCompilerMessage flag, or immediately updating the error info
|
|
// when we assign the flag:
|
|
TArray<UEdGraph*> Graphs;
|
|
InBlueprint->GetAllGraphs(Graphs);
|
|
for (const UEdGraph* Graph : Graphs)
|
|
{
|
|
for (const UEdGraphNode* Node : Graph->Nodes)
|
|
{
|
|
if (Node)
|
|
{
|
|
TSharedPtr<SGraphNode> Widget = Node->DEPRECATED_NodeWidget.Pin();
|
|
if (Widget.IsValid())
|
|
{
|
|
Widget->RefreshErrorInfo();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
OnBlueprintChangedImpl( InBlueprint, true );
|
|
}
|
|
|
|
void FBlueprintEditor::OnBlueprintUnloaded(UBlueprint* InBlueprint)
|
|
{
|
|
for (UObject* EditingObj : GetEditingObjects())
|
|
{
|
|
if (Cast<UBlueprint>(EditingObj) == InBlueprint)
|
|
{
|
|
// give the editor a chance to open a replacement
|
|
bPendingDeferredClose = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::Compile()
|
|
{
|
|
DECLARE_SCOPE_HIERARCHICAL_COUNTER_FUNC()
|
|
|
|
UBlueprint* BlueprintObj = GetBlueprintObj();
|
|
if (BlueprintObj)
|
|
{
|
|
FMessageLog BlueprintLog("BlueprintLog");
|
|
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("BlueprintName"), FText::FromString(BlueprintObj->GetName()));
|
|
BlueprintLog.NewPage(FText::Format(LOCTEXT("CompilationPageLabel", "Compile {BlueprintName}"), Arguments));
|
|
|
|
FCompilerResultsLog LogResults;
|
|
LogResults.SetSourcePath(BlueprintObj->GetPathName());
|
|
LogResults.BeginEvent(TEXT("Compile"));
|
|
EBlueprintCompileOptions CompileOptions = EBlueprintCompileOptions::None;
|
|
if( bSaveIntermediateBuildProducts )
|
|
{
|
|
CompileOptions |= EBlueprintCompileOptions::SaveIntermediateProducts;
|
|
}
|
|
|
|
if (bIsReparentingBlueprint)
|
|
{
|
|
CompileOptions |= (EBlueprintCompileOptions::UseDeltaSerializationDuringReinstancing | EBlueprintCompileOptions::SkipNewVariableDefaultsDetection);
|
|
}
|
|
|
|
// If compilation is enabled during PIE/simulation, references to the CDO might be held by a script variable.
|
|
// Thus, we set the flag to direct the compiler to allow those references to be replaced during reinstancing.
|
|
if (IsPlayInEditorActive())
|
|
{
|
|
CompileOptions |= EBlueprintCompileOptions::IncludeCDOInReferenceReplacement;
|
|
}
|
|
|
|
FKismetEditorUtilities::CompileBlueprint(BlueprintObj, CompileOptions, &LogResults);
|
|
|
|
LogResults.EndEvent();
|
|
|
|
CachedNumWarnings = LogResults.NumWarnings;
|
|
CachedNumErrors = LogResults.NumErrors;
|
|
|
|
const bool bForceMessageDisplay = (LogResults.NumWarnings > 0 || LogResults.NumErrors > 0) && !BlueprintObj->bIsRegeneratingOnLoad;
|
|
DumpMessagesToCompilerLog(LogResults.Messages, bForceMessageDisplay);
|
|
|
|
UBlueprintEditorSettings const* BpEditorSettings = GetDefault<UBlueprintEditorSettings>();
|
|
if ((LogResults.NumErrors > 0) && BpEditorSettings->bJumpToNodeErrors)
|
|
{
|
|
if (UEdGraphNode* NodeWithError = BlueprintEditorImpl::FindNodeWithError(LogResults))
|
|
{
|
|
JumpToNode(NodeWithError, /*bRequestRename =*/false);
|
|
}
|
|
}
|
|
|
|
if (BlueprintObj->UpgradeNotesLog.IsValid())
|
|
{
|
|
CompilerResultsListing->AddMessages(BlueprintObj->UpgradeNotesLog->Messages);
|
|
}
|
|
|
|
// send record when player clicks compile and send the result
|
|
// this will make sure how the users activity is
|
|
AnalyticsTrackCompileEvent(BlueprintObj, LogResults.NumErrors, LogResults.NumWarnings);
|
|
|
|
RefreshInspector();
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::IsSaveOnCompileEnabled() const
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
bool const bIsLevelScript = (Cast<ULevelScriptBlueprint>(Blueprint) != nullptr);
|
|
|
|
return !bIsLevelScript;
|
|
}
|
|
|
|
FReply FBlueprintEditor::Compile_OnClickWithReply()
|
|
{
|
|
Compile();
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void FBlueprintEditor::RefreshAllNodes_OnClicked()
|
|
{
|
|
FBlueprintEditorUtils::RefreshAllNodes(GetBlueprintObj());
|
|
RefreshEditors();
|
|
Compile();
|
|
}
|
|
|
|
void FBlueprintEditor::DeleteUnusedVariables_OnClicked()
|
|
{
|
|
UBlueprint* BlueprintObj = GetBlueprintObj();
|
|
|
|
// Gather FProperties from this BP and see if we can remove any
|
|
TArray<FProperty*> VariableProperties;
|
|
bool bHasAtLeastOneVariableToCheck = UBlueprintEditorLibrary::GatherUnusedVariables(BlueprintObj, VariableProperties);
|
|
|
|
if (VariableProperties.Num() > 0)
|
|
{
|
|
TSharedRef<SCheckBoxList> CheckBoxList = SNew(SCheckBoxList)
|
|
.ItemHeaderLabel(LOCTEXT("DeleteUnusedVariablesDialog_VariableLabel", "Variable"));
|
|
for (FProperty* Variable : VariableProperties)
|
|
{
|
|
CheckBoxList->AddItem(FText::FromString(UEditorEngine::GetFriendlyName(Variable)), true);
|
|
}
|
|
|
|
TSharedRef<SWidget> DialogContain = SNew(SVerticalBox)
|
|
+ SVerticalBox::Slot().Padding(10)
|
|
.AutoHeight()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("VariableDialog_Message", "These variables are not used in the graph or in other blueprints' graphs.\nThey may be used in other places.\nYou may use 'Find in Blueprint' or the 'Asset Search' to find out if they are referenced elsewhere."))
|
|
.AutoWrapText(true)
|
|
]
|
|
+ SVerticalBox::Slot().FillHeight(0.8)
|
|
[
|
|
CheckBoxList
|
|
];
|
|
|
|
TSharedRef<SCustomDialog> CustomDialog = SNew(SCustomDialog)
|
|
.Title(LOCTEXT("DeleteUnusedVariablesDialog_Title", "Delete Unused Variables"))
|
|
.Icon(FAppStyle::Get().GetBrush("NotificationList.DefaultMessage"))
|
|
.Content()
|
|
[
|
|
DialogContain
|
|
]
|
|
.Buttons(
|
|
{
|
|
SCustomDialog::FButton(LOCTEXT("DeleteUnusedVariablesDialog_ButtonDelete", "Delete")),
|
|
SCustomDialog::FButton(LOCTEXT("DeleteUnusedVariablesDialog_ButtonCancel", "Cancel"))
|
|
});
|
|
|
|
int32 Result = CustomDialog->ShowModal();
|
|
if (Result == 0)
|
|
{
|
|
TArray<FName> VariableNames;
|
|
FString PropertyList;
|
|
VariableNames.Reserve(VariableProperties.Num());
|
|
for (int32 Index = 0; Index < VariableProperties.Num(); ++Index)
|
|
{
|
|
if (CheckBoxList->IsItemChecked(Index))
|
|
{
|
|
VariableNames.Add(VariableProperties[Index]->GetFName());
|
|
if (PropertyList.IsEmpty())
|
|
{
|
|
PropertyList = UEditorEngine::GetFriendlyName(VariableProperties[Index]);
|
|
}
|
|
else
|
|
{
|
|
PropertyList += FString::Printf(TEXT(", %s"), *UEditorEngine::GetFriendlyName(VariableProperties[Index]));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (VariableNames.Num() > 0)
|
|
{
|
|
VariableProperties.Empty(); // Emptying this array because these properties will be deleted and we don't want to keep raw pointers to deleted objects
|
|
FBlueprintEditorUtils::BulkRemoveMemberVariables(BlueprintObj, VariableNames);
|
|
LogSimpleMessage(FText::Format(LOCTEXT("UnusedVariablesDeletedMessage", "The following variable(s) were deleted successfully: {0}."), FText::FromString(PropertyList)));
|
|
}
|
|
else
|
|
{
|
|
LogSimpleMessage(LOCTEXT("NoVariablesSelectedMessage", "No variables were selected for deletion."));
|
|
}
|
|
}
|
|
}
|
|
else if (bHasAtLeastOneVariableToCheck)
|
|
{
|
|
LogSimpleMessage(LOCTEXT("AllVariablesInUseMessage", "All variables are currently in use."));
|
|
}
|
|
else
|
|
{
|
|
LogSimpleMessage(LOCTEXT("NoVariablesToSeeMessage", "No variables to check for."));
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::FindInBlueprints_OnClicked()
|
|
{
|
|
SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode);
|
|
|
|
SummonSearchUI(false);
|
|
}
|
|
|
|
void FBlueprintEditor::ClearAllBreakpoints()
|
|
{
|
|
FKismetDebugUtilities::ClearBreakpoints(GetBlueprintObj());
|
|
}
|
|
|
|
void FBlueprintEditor::DisableAllBreakpoints()
|
|
{
|
|
FKismetDebugUtilities::ForeachBreakpoint(GetBlueprintObj(),
|
|
[](FBlueprintBreakpoint& Breakpoint)
|
|
{
|
|
FKismetDebugUtilities::SetBreakpointEnabled(Breakpoint, false);
|
|
}
|
|
);
|
|
}
|
|
|
|
void FBlueprintEditor::EnableAllBreakpoints()
|
|
{
|
|
FKismetDebugUtilities::ForeachBreakpoint(GetBlueprintObj(),
|
|
[](FBlueprintBreakpoint& Breakpoint)
|
|
{
|
|
FKismetDebugUtilities::SetBreakpointEnabled(Breakpoint, true);
|
|
}
|
|
);
|
|
}
|
|
|
|
void FBlueprintEditor::ClearAllWatches()
|
|
{
|
|
FKismetDebugUtilities::ClearPinWatches(GetBlueprintObj());
|
|
}
|
|
|
|
void FBlueprintEditor::OpenBlueprintDebugger()
|
|
{
|
|
FGlobalTabmanager::Get()->TryInvokeTab(FBlueprintEditorTabs::BlueprintDebuggerID);
|
|
FBlueprintEditorModule& BlueprintEditorModule = FModuleManager::Get().LoadModuleChecked<FBlueprintEditorModule>(TEXT("Kismet"));
|
|
BlueprintEditorModule.GetBlueprintDebugger()->SetDebuggedBlueprint(GetBlueprintObj());
|
|
}
|
|
|
|
bool FBlueprintEditor::CanOpenBlueprintDebugger() const
|
|
{
|
|
// The BP debugger can always be spawned because it will get updated on PIE
|
|
return true;
|
|
}
|
|
|
|
bool FBlueprintEditor::HasAnyBreakpoints() const
|
|
{
|
|
const UBlueprint* Blueprint = GetBlueprintObj();
|
|
if(!Blueprint)
|
|
{
|
|
return false;
|
|
}
|
|
return FKismetDebugUtilities::BlueprintHasBreakpoints(Blueprint);
|
|
}
|
|
|
|
bool FBlueprintEditor::HasAnyEnabledBreakpoints() const
|
|
{
|
|
if (!IsEditingSingleBlueprint()) {return false;}
|
|
|
|
return FKismetDebugUtilities::FindBreakpointByPredicate(
|
|
GetBlueprintObj(),
|
|
[](const FBlueprintBreakpoint& Breakpoint)
|
|
{
|
|
return Breakpoint.IsEnabledByUser();
|
|
}
|
|
) != nullptr;
|
|
}
|
|
|
|
bool FBlueprintEditor::HasAnyDisabledBreakpoints() const
|
|
{
|
|
if (!IsEditingSingleBlueprint()) {return false;}
|
|
|
|
return FKismetDebugUtilities::FindBreakpointByPredicate(
|
|
GetBlueprintObj(),
|
|
[](const FBlueprintBreakpoint& Breakpoint)
|
|
{
|
|
return !Breakpoint.IsEnabledByUser();
|
|
}
|
|
) != nullptr;
|
|
}
|
|
|
|
bool FBlueprintEditor::HasAnyWatches() const
|
|
{
|
|
const UBlueprint* Blueprint = GetBlueprintObj();;
|
|
return Blueprint && FKismetDebugUtilities::BlueprintHasPinWatches(Blueprint);
|
|
}
|
|
|
|
// Jumps to a hyperlinked node, pin, or graph, if it belongs to this blueprint
|
|
void FBlueprintEditor::JumpToHyperlink(const UObject* ObjectReference, bool bRequestRename)
|
|
{
|
|
SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode);
|
|
|
|
if (const UEdGraphNode* Node = Cast<const UEdGraphNode>(ObjectReference))
|
|
{
|
|
if (bRequestRename)
|
|
{
|
|
IsNodeTitleVisible(Node, bRequestRename);
|
|
}
|
|
else
|
|
{
|
|
JumpToNode(Node, false);
|
|
}
|
|
}
|
|
else if (const UEdGraph* Graph = Cast<const UEdGraph>(ObjectReference))
|
|
{
|
|
// Navigating into things should re-use the current tab when it makes sense
|
|
FDocumentTracker::EOpenDocumentCause OpenMode = FDocumentTracker::OpenNewDocument;
|
|
if ((Graph->GetSchema()->GetGraphType(Graph) == GT_Ubergraph) || Cast<UK2Node>(Graph->GetOuter()) || Cast<UEdGraph>(Graph->GetOuter()))
|
|
{
|
|
// Ubergraphs directly reuse the current graph
|
|
OpenMode = FDocumentTracker::NavigatingCurrentDocument;
|
|
}
|
|
else
|
|
{
|
|
// Walk up the outer chain to see if any tabs have a parent of this document open for edit, and if so
|
|
// we should reuse that one and drill in deeper instead
|
|
for (UObject* WalkPtr = const_cast<UEdGraph*>(Graph); WalkPtr != nullptr; WalkPtr = WalkPtr->GetOuter())
|
|
{
|
|
TArray< TSharedPtr<SDockTab> > TabResults;
|
|
if (FindOpenTabsContainingDocument(WalkPtr, /*out*/ TabResults))
|
|
{
|
|
// See if the parent was active
|
|
bool bIsActive = false;
|
|
for (TSharedPtr<SDockTab> Tab : TabResults)
|
|
{
|
|
if (Tab->IsActive())
|
|
{
|
|
bIsActive = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bIsActive)
|
|
{
|
|
OpenMode = FDocumentTracker::NavigatingCurrentDocument;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Force it to open in a new document if shift is pressed
|
|
const bool bIsShiftPressed = FSlateApplication::Get().GetModifierKeys().IsShiftDown();
|
|
if (bIsShiftPressed)
|
|
{
|
|
OpenMode = FDocumentTracker::ForceOpenNewDocument;
|
|
}
|
|
|
|
// Open the document
|
|
OpenDocument(Graph, OpenMode);
|
|
}
|
|
else if (const AActor* ReferencedActor = Cast<const AActor>(ObjectReference))
|
|
{
|
|
// Check if the world is active in the editor. It's possible to open level BPs without formally opening
|
|
// the levels through Find-in-Blueprints
|
|
bool bInOpenWorld = false;
|
|
const TIndirectArray<FWorldContext>& WorldContextList = GEditor->GetWorldContexts();
|
|
const UWorld* ReferencedActorOwningWorld = ReferencedActor->GetWorld();
|
|
for (const FWorldContext& WorldContext : WorldContextList)
|
|
{
|
|
if (WorldContext.World() == ReferencedActorOwningWorld)
|
|
{
|
|
bInOpenWorld = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// Clear the selection even if we couldn't find it, so the existing selection doesn't get mistaken for the desired to be selected actor
|
|
GEditor->SelectNone(false, false);
|
|
|
|
if (bInOpenWorld)
|
|
{
|
|
// Select the in-level actor
|
|
GEditor->SelectActor(const_cast<AActor*>(ReferencedActor), true, true, true);
|
|
|
|
// Point the camera at it
|
|
GUnrealEd->Exec(ReferencedActor->GetWorld(), TEXT("CAMERA ALIGN ACTIVEVIEWPORTONLY"));
|
|
}
|
|
}
|
|
else if(const UFunction* Function = Cast<const UFunction>(ObjectReference))
|
|
{
|
|
UBlueprint* BP = GetBlueprintObj();
|
|
if(BP)
|
|
{
|
|
if (UEdGraph* FunctionGraph = FBlueprintEditorUtils::FindScopeGraph(BP, Function))
|
|
{
|
|
OpenDocument(FunctionGraph, FDocumentTracker::OpenNewDocument);
|
|
}
|
|
}
|
|
}
|
|
else if(const UBlueprintGeneratedClass* Class = Cast<const UBlueprintGeneratedClass>(ObjectReference))
|
|
{
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(Class->ClassGeneratedBy);
|
|
}
|
|
else if (const UTimelineTemplate* Timeline = Cast<const UTimelineTemplate>(ObjectReference))
|
|
{
|
|
OpenDocument(Timeline, FDocumentTracker::OpenNewDocument);
|
|
}
|
|
else if ((ObjectReference != nullptr) && ObjectReference->IsAsset())
|
|
{
|
|
GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->OpenEditorForAsset(const_cast<UObject*>(ObjectReference));
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogBlueprint, Warning, TEXT("Unknown type of hyperlinked object (%s), cannot focus it"), *GetNameSafe(ObjectReference));
|
|
}
|
|
|
|
//@TODO: Hacky way to ensure a message is seen when hitting an exception and doing intraframe debugging
|
|
const FText ExceptionMessage = FKismetDebugUtilities::GetAndClearLastExceptionMessage();
|
|
if (!ExceptionMessage.IsEmpty())
|
|
{
|
|
LogSimpleMessage( ExceptionMessage );
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::JumpToPin(const UEdGraphPin* Pin)
|
|
{
|
|
if (!Pin->IsPendingKill())
|
|
{
|
|
// Open a graph editor and jump to the pin
|
|
TSharedPtr<SGraphEditor> GraphEditor = OpenGraphAndBringToFront(Pin->GetOwningNode()->GetGraph());
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->JumpToPin(Pin);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::AddReferencedObjects( FReferenceCollector& Collector )
|
|
{
|
|
if (GetObjectsCurrentlyBeingEdited()->Num() > 0)
|
|
{
|
|
Collector.AddReferencedObjects(GetEditingObjectPtrs());
|
|
}
|
|
|
|
Collector.AddReferencedObjects(StandardLibraries);
|
|
|
|
UserDefinedEnumerators.Remove(TWeakObjectPtr<UUserDefinedEnum>()); // Remove NULLs
|
|
for (TWeakObjectPtr<UUserDefinedEnum>& ObjectPtr : UserDefinedEnumerators)
|
|
{
|
|
Collector.AddReferencedObject(ObjectPtr);
|
|
}
|
|
|
|
UserDefinedStructures.Remove(TWeakObjectPtr<UUserDefinedStruct>()); // Remove NULLs
|
|
for (TWeakObjectPtr<UUserDefinedStruct>& ObjectPtr : UserDefinedStructures)
|
|
{
|
|
Collector.AddReferencedObject(ObjectPtr);
|
|
}
|
|
}
|
|
|
|
FString FBlueprintEditor::GetReferencerName() const
|
|
{
|
|
return TEXT("FBlueprintEditor");
|
|
}
|
|
|
|
bool FBlueprintEditor::IsNodeTitleVisible(const UEdGraphNode* Node, bool bRequestRename)
|
|
{
|
|
TSharedPtr<SGraphEditor> GraphEditor;
|
|
if(bRequestRename)
|
|
{
|
|
// If we are renaming, the graph will be open already, just grab the tab and it's content and jump to the node.
|
|
TSharedPtr<SDockTab> ActiveTab = DocumentManager->GetActiveTab();
|
|
check(ActiveTab.IsValid());
|
|
GraphEditor = StaticCastSharedRef<SGraphEditor>(ActiveTab->GetContent());
|
|
}
|
|
else
|
|
{
|
|
// Open a graph editor and jump to the node
|
|
GraphEditor = OpenGraphAndBringToFront(Node->GetGraph());
|
|
}
|
|
|
|
bool bVisible = false;
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
bVisible = GraphEditor->IsNodeTitleVisible(Node, bRequestRename);
|
|
}
|
|
return bVisible;
|
|
}
|
|
|
|
void FBlueprintEditor::JumpToNode(const UEdGraphNode* Node, bool bRequestRename)
|
|
{
|
|
TSharedPtr<SGraphEditor> GraphEditor;
|
|
if(bRequestRename)
|
|
{
|
|
// If we are renaming, the graph will be open already, just grab the tab and it's content and jump to the node.
|
|
TSharedPtr<SDockTab> ActiveTab = DocumentManager->GetActiveTab();
|
|
check(ActiveTab.IsValid());
|
|
GraphEditor = StaticCastSharedRef<SGraphEditor>(ActiveTab->GetContent());
|
|
}
|
|
else
|
|
{
|
|
// Open a graph editor and jump to the node
|
|
GraphEditor = OpenGraphAndBringToFront(Node->GetGraph());
|
|
}
|
|
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
GraphEditor->JumpToNode(Node, bRequestRename);
|
|
}
|
|
}
|
|
|
|
UBlueprint* FBlueprintEditor::GetBlueprintObj() const
|
|
{
|
|
return GetEditingObjects().Num() == 1 ? Cast<UBlueprint>(GetEditingObjects()[0]) : nullptr;
|
|
}
|
|
|
|
bool FBlueprintEditor::IsEditingSingleBlueprint() const
|
|
{
|
|
return GetBlueprintObj() != nullptr;
|
|
}
|
|
|
|
FString FBlueprintEditor::GetDocumentationLink() const
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if(Blueprint)
|
|
{
|
|
// Jump to more relevant docs if editing macro library or interface
|
|
if(Blueprint->BlueprintType == BPTYPE_MacroLibrary)
|
|
{
|
|
return TEXT("Engine/Blueprints/UserGuide/Types/MacroLibrary");
|
|
}
|
|
else if (Blueprint->BlueprintType == BPTYPE_Interface)
|
|
{
|
|
return TEXT("Engine/Blueprints/UserGuide/Types/Interface");
|
|
}
|
|
}
|
|
|
|
return FString(TEXT("Engine/Blueprints"));
|
|
}
|
|
|
|
|
|
bool FBlueprintEditor::CanAccessComponentsMode() const
|
|
{
|
|
bool bCanAccess = false;
|
|
|
|
// Ensure that we're editing a Blueprint
|
|
if(IsEditingSingleBlueprint())
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
bCanAccess = FBlueprintEditorUtils::DoesSupportComponents(Blueprint);
|
|
}
|
|
|
|
return bCanAccess;
|
|
}
|
|
|
|
bool FBlueprintEditor::IsEditorClosing() const
|
|
{
|
|
return bEditorMarkedAsClosed;
|
|
}
|
|
|
|
void FBlueprintEditor::RegisterToolbarTab(const TSharedRef<class FTabManager>& InTabManager)
|
|
{
|
|
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
|
|
}
|
|
|
|
void FBlueprintEditor::LogSimpleMessage(const FText& MessageText)
|
|
{
|
|
FNotificationInfo Info( MessageText );
|
|
Info.ExpireDuration = 3.0f;
|
|
Info.bUseLargeFont = false;
|
|
TSharedPtr<SNotificationItem> Notification = FSlateNotificationManager::Get().AddNotification(Info);
|
|
if ( Notification.IsValid() )
|
|
{
|
|
Notification->SetCompletionState( SNotificationItem::CS_Fail );
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::DumpMessagesToCompilerLog(const TArray<TSharedRef<FTokenizedMessage>>& Messages, bool bForceMessageDisplay)
|
|
{
|
|
CompilerResultsListing->ClearMessages();
|
|
|
|
// Note we dont mirror to the output log here as the compiler already does that
|
|
CompilerResultsListing->AddMessages(Messages, false);
|
|
|
|
if (!bEditorMarkedAsClosed && bForceMessageDisplay && GetCurrentMode() == FBlueprintEditorApplicationModes::StandardBlueprintEditorMode)
|
|
{
|
|
TabManager->TryInvokeTab(FBlueprintEditorTabs::CompilerResultsID);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::DoPromoteToVariable(UBlueprint* InBlueprint, UEdGraphPin* InTargetPin, bool bInToMemberVariable, const FVector2D* InOptionalLocation /* = nullptr */)
|
|
{
|
|
TOptional<FVector2f> Location;
|
|
if (InOptionalLocation)
|
|
{
|
|
Location = UE::Slate::CastToVector2f(*InOptionalLocation);
|
|
}
|
|
DoPromoteToVariable2f(InBlueprint, InTargetPin, bInToMemberVariable, Location);
|
|
}
|
|
|
|
void FBlueprintEditor::DoPromoteToVariable2f(UBlueprint* InBlueprint, UEdGraphPin* InTargetPin, bool bInToMemberVariable, TOptional<FVector2f> InOptionalLocation)
|
|
{
|
|
FName PinName = InTargetPin->PinName;
|
|
UEdGraphNode* PinNode = InTargetPin->GetOwningNode();
|
|
check(PinNode);
|
|
UEdGraph* GraphObj = PinNode->GetGraph();
|
|
check(GraphObj);
|
|
|
|
// Used for promoting to local variable
|
|
UEdGraph* FunctionGraph = nullptr;
|
|
|
|
const FScopedTransaction Transaction( bInToMemberVariable? LOCTEXT("PromoteToVariable", "Promote To Variable") : LOCTEXT("PromoteToLocalVariable", "Promote to Local Variable") );
|
|
InBlueprint->Modify();
|
|
GraphObj->Modify();
|
|
|
|
FName VarName;
|
|
bool bWasSuccessful = false;
|
|
FEdGraphPinType NewPinType = InTargetPin->PinType;
|
|
NewPinType.bIsConst = false;
|
|
NewPinType.bIsReference = false;
|
|
NewPinType.bIsWeakPointer = false;
|
|
if (bInToMemberVariable)
|
|
{
|
|
#if WITH_EDITORONLY_DATA
|
|
static const FName NAME_UIMin(TEXT("UIMin"));
|
|
static const FName NAME_UIMax(TEXT("UIMax"));
|
|
static const FName NAME_ClampMin(TEXT("ClampMin"));
|
|
static const FName NAME_ClampMax(TEXT("ClampMax"));
|
|
|
|
FString Meta_UIMin;
|
|
FString Meta_UIMax;
|
|
FString Meta_ClampMin;
|
|
FString Meta_ClampMax;
|
|
|
|
if (const UEdGraphSchema* Schema = InTargetPin->GetSchema())
|
|
{
|
|
// Name the variable to match its target blueprint pin name
|
|
FString IdealVarName = FText::TrimPrecedingAndTrailing(Schema->GetPinDisplayName(InTargetPin)).ToString();
|
|
|
|
// Ignore unnamed return values
|
|
if (IdealVarName == TEXT("Return Value"))
|
|
{
|
|
IdealVarName.Empty();
|
|
}
|
|
|
|
// Ignore names from compact nodes that don't usually display the pin names
|
|
if (const UK2Node* K2Node = Cast<UK2Node>(InTargetPin->GetOwningNode()))
|
|
{
|
|
if (K2Node->ShouldDrawCompact())
|
|
{
|
|
IdealVarName.Empty();
|
|
}
|
|
}
|
|
|
|
// Set the variable name to its ideal name (with an optional numeric suffix if there is a conflict)
|
|
if (!IdealVarName.IsEmpty())
|
|
{
|
|
TSharedPtr<FKismetNameValidator> NameValidator = MakeShareable(new FKismetNameValidator(GetBlueprintObj(), NAME_None));
|
|
if (NameValidator->IsValid(IdealVarName) == EValidatorResult::Ok)
|
|
{
|
|
VarName = FName(*IdealVarName);
|
|
}
|
|
else
|
|
{
|
|
VarName = FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), IdealVarName);
|
|
}
|
|
|
|
if (UEdGraphNode* Node = InTargetPin->GetOwningNode())
|
|
{
|
|
// Extract the target pin's numeric limits so that we can copy them to the new variable
|
|
Meta_UIMin = Node->GetPinMetaData(InTargetPin->PinName, NAME_UIMin);
|
|
Meta_UIMax = Node->GetPinMetaData(InTargetPin->PinName, NAME_UIMax);
|
|
Meta_ClampMin = Node->GetPinMetaData(InTargetPin->PinName, NAME_ClampMin);
|
|
Meta_ClampMax = Node->GetPinMetaData(InTargetPin->PinName, NAME_ClampMax);
|
|
}
|
|
}
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
|
|
if (VarName == NAME_None)
|
|
{
|
|
VarName = FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), TEXT("NewVar"));
|
|
}
|
|
bWasSuccessful = FBlueprintEditorUtils::AddMemberVariable( GetBlueprintObj(), VarName, NewPinType, InTargetPin->GetDefaultAsString() );
|
|
|
|
#if WITH_EDITORONLY_DATA
|
|
if (bWasSuccessful)
|
|
{
|
|
if (!Meta_UIMin.IsEmpty())
|
|
{
|
|
FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, nullptr, NAME_UIMin, Meta_UIMin);
|
|
}
|
|
if (!Meta_UIMax.IsEmpty())
|
|
{
|
|
FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, nullptr, NAME_UIMax, Meta_UIMax);
|
|
}
|
|
if (!Meta_ClampMin.IsEmpty())
|
|
{
|
|
FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, nullptr, NAME_ClampMin, Meta_ClampMin);
|
|
}
|
|
if (!Meta_ClampMax.IsEmpty())
|
|
{
|
|
FBlueprintEditorUtils::SetBlueprintVariableMetaData(GetBlueprintObj(), VarName, nullptr, NAME_ClampMax, Meta_ClampMax);
|
|
}
|
|
}
|
|
#endif // WITH_EDITORONLY_DATA
|
|
}
|
|
else
|
|
{
|
|
ensure(FBlueprintEditorUtils::DoesSupportLocalVariables(GraphObj));
|
|
VarName = FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), TEXT("NewLocalVar"));
|
|
FunctionGraph = FBlueprintEditorUtils::GetTopLevelGraph(GraphObj);
|
|
bWasSuccessful = FBlueprintEditorUtils::AddLocalVariable( GetBlueprintObj(), FunctionGraph, VarName, NewPinType, InTargetPin->GetDefaultAsString() );
|
|
}
|
|
|
|
if (bWasSuccessful)
|
|
{
|
|
// The owning node may have been reconstructed as a result of adding a new variable above, so ensure the pin reference is up-to-date.
|
|
InTargetPin = PinNode->FindPinChecked(PinName);
|
|
|
|
// Create the new setter node
|
|
FEdGraphSchemaAction_K2NewNode NodeInfo;
|
|
|
|
// Create get or set node, depending on whether we clicked on an input or output pin
|
|
UK2Node_Variable* TemplateNode = nullptr;
|
|
if (InTargetPin->Direction == EGPD_Input)
|
|
{
|
|
TemplateNode = NewObject<UK2Node_VariableGet>();
|
|
}
|
|
else
|
|
{
|
|
TemplateNode = NewObject<UK2Node_VariableSet>();
|
|
}
|
|
|
|
if (bInToMemberVariable)
|
|
{
|
|
TemplateNode->VariableReference.SetSelfMember(VarName);
|
|
}
|
|
else
|
|
{
|
|
TemplateNode->VariableReference.SetLocalMember(VarName, FunctionGraph->GetName(), FBlueprintEditorUtils::FindLocalVariableGuidByName(InBlueprint, FunctionGraph, VarName));
|
|
}
|
|
NodeInfo.NodeTemplate = TemplateNode;
|
|
|
|
// Set position of new node to be close to node we clicked on
|
|
FVector2f NewNodePos;
|
|
|
|
if (InOptionalLocation.IsSet())
|
|
{
|
|
NewNodePos = InOptionalLocation.GetValue();
|
|
}
|
|
else
|
|
{
|
|
NewNodePos.X = (InTargetPin->Direction == EGPD_Input) ? PinNode->NodePosX - 200 : PinNode->NodePosX + 400;
|
|
NewNodePos.Y = PinNode->NodePosY;
|
|
}
|
|
|
|
NodeInfo.PerformAction(GraphObj, InTargetPin, NewNodePos, false);
|
|
|
|
RenameNewlyAddedAction(VarName);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnPromoteToVariable(bool bInToMemberVariable)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
UEdGraphPin* TargetPin = FocusedGraphEd->GetGraphPinForMenu();
|
|
|
|
check(IsEditingSingleBlueprint());
|
|
check(GetBlueprintObj()->SkeletonGeneratedClass);
|
|
check(TargetPin);
|
|
|
|
DoPromoteToVariable2f( GetBlueprintObj(), TargetPin, bInToMemberVariable );
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanPromoteToVariable(bool bInToMemberVariable) const
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
bool bCanPromote = false;
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
if (UEdGraphPin* Pin = FocusedGraphEd->GetGraphPinForMenu())
|
|
{
|
|
if (!Pin->bOrphanedPin && (bInToMemberVariable || FBlueprintEditorUtils::DoesSupportLocalVariables(FocusedGraphEd->GetCurrentGraph())))
|
|
{
|
|
bCanPromote = K2Schema->CanPromotePinToVariable(*Pin, bInToMemberVariable);
|
|
}
|
|
}
|
|
}
|
|
|
|
return bCanPromote;
|
|
}
|
|
|
|
void FBlueprintEditor::OnSplitStructPin()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
UEdGraphPin* TargetPin = FocusedGraphEd->GetGraphPinForMenu();
|
|
|
|
check(IsEditingSingleBlueprint());
|
|
check(GetBlueprintObj()->SkeletonGeneratedClass);
|
|
check(TargetPin);
|
|
|
|
const FScopedTransaction Transaction( LOCTEXT("SplitStructPin", "Split Struct Pin") );
|
|
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
K2Schema->SplitPin(TargetPin);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanSplitStructPin() const
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
bool bCanSplit = false;
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
if (UEdGraphPin* Pin = FocusedGraphEd->GetGraphPinForMenu())
|
|
{
|
|
bCanSplit = K2Schema->CanSplitStructPin(*Pin);
|
|
}
|
|
}
|
|
|
|
return bCanSplit;
|
|
}
|
|
|
|
void FBlueprintEditor::OnRecombineStructPin()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
UEdGraphPin* TargetPin = FocusedGraphEd->GetGraphPinForMenu();
|
|
|
|
check(IsEditingSingleBlueprint());
|
|
check(GetBlueprintObj()->SkeletonGeneratedClass);
|
|
check(TargetPin);
|
|
|
|
const FScopedTransaction Transaction( LOCTEXT("RecombineStructPin", "Recombine Struct Pin") );
|
|
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
K2Schema->RecombinePin(TargetPin);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRecombineStructPin() const
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
bool bCanRecombine = false;
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
if (UEdGraphPin* Pin = FocusedGraphEd->GetGraphPinForMenu())
|
|
{
|
|
bCanRecombine = K2Schema->CanRecombineStructPin(*Pin);
|
|
}
|
|
}
|
|
|
|
return bCanRecombine;
|
|
}
|
|
|
|
void FBlueprintEditor::OnAddExecutionPin()
|
|
{
|
|
const FGraphPanelSelectionSet& SelectedNodes = GetSelectedNodes();
|
|
|
|
// Iterate over all nodes, and add the pin
|
|
for (FGraphPanelSelectionSet::TConstIterator It(SelectedNodes); It; ++It)
|
|
{
|
|
UK2Node_ExecutionSequence* SeqNode = Cast<UK2Node_ExecutionSequence>(*It);
|
|
if (SeqNode != nullptr)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("AddExecutionPin", "Add Execution Pin") );
|
|
SeqNode->Modify();
|
|
|
|
SeqNode->AddInputPin();
|
|
|
|
const UEdGraphSchema* Schema = SeqNode->GetSchema();
|
|
Schema->ReconstructNode(*SeqNode);
|
|
}
|
|
else if (UK2Node_Switch* SwitchNode = Cast<UK2Node_Switch>(*It))
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("AddExecutionPin", "Add Execution Pin") );
|
|
SwitchNode->Modify();
|
|
|
|
SwitchNode->AddPinToSwitchNode();
|
|
|
|
const UEdGraphSchema* Schema = SwitchNode->GetSchema();
|
|
Schema->ReconstructNode(*SwitchNode);
|
|
}
|
|
}
|
|
|
|
// Refresh the current graph, so the pins can be updated
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanAddExecutionPin() const
|
|
{
|
|
const FGraphPanelSelectionSet& SelectedNodes = GetSelectedNodes();
|
|
|
|
// Iterate over all nodes, and see if all can have a pin added
|
|
for (FGraphPanelSelectionSet::TConstIterator It(SelectedNodes); It; ++It)
|
|
{
|
|
if (UK2Node_ExecutionSequence* AddPinNode = Cast<UK2Node_ExecutionSequence>(*It))
|
|
{
|
|
if (!AddPinNode->CanAddPin())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FBlueprintEditor::OnInsertExecutionPinBefore()
|
|
{
|
|
OnInsertExecutionPin(EPinInsertPosition::Before);
|
|
}
|
|
|
|
void FBlueprintEditor::OnInsertExecutionPinAfter()
|
|
{
|
|
OnInsertExecutionPin(EPinInsertPosition::After);
|
|
}
|
|
|
|
void FBlueprintEditor::OnInsertExecutionPin(EPinInsertPosition Position)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("InsertExecutionPinBefore", "Insert Execution Pin Before"));
|
|
|
|
UEdGraphPin* SelectedPin = FocusedGraphEd->GetGraphPinForMenu();
|
|
if (SelectedPin)
|
|
{
|
|
UEdGraphNode* OwningNode = SelectedPin->GetOwningNode();
|
|
|
|
if (OwningNode)
|
|
{
|
|
if (UK2Node_ExecutionSequence* SeqNode = Cast<UK2Node_ExecutionSequence>(OwningNode))
|
|
{
|
|
SeqNode->InsertPinIntoExecutionNode(SelectedPin, Position);
|
|
FocusedGraphEd->RefreshNode(*OwningNode);
|
|
|
|
if (UBlueprint* BP = SeqNode->GetBlueprint())
|
|
{
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(BP);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanInsertExecutionPin() const
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
UEdGraphPin* SelectedPin = FocusedGraphEd->GetGraphPinForMenu();
|
|
if (SelectedPin)
|
|
{
|
|
if (UK2Node_ExecutionSequence* ExecutionSequence = Cast<UK2Node_ExecutionSequence>(SelectedPin->GetOwningNode()))
|
|
{
|
|
return ExecutionSequence->CanAddPin();
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnRemoveExecutionPin()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("RemoveExecutionPin", "Remove Execution Pin") );
|
|
|
|
UEdGraphPin* SelectedPin = FocusedGraphEd->GetGraphPinForMenu();
|
|
UEdGraphNode* OwningNode = SelectedPin->GetOwningNode();
|
|
|
|
OwningNode->Modify();
|
|
SelectedPin->Modify();
|
|
|
|
if (UK2Node_ExecutionSequence* SeqNode = Cast<UK2Node_ExecutionSequence>(OwningNode))
|
|
{
|
|
SeqNode->RemovePinFromExecutionNode(SelectedPin);
|
|
}
|
|
else if (UK2Node_Switch* SwitchNode = Cast<UK2Node_Switch>(OwningNode))
|
|
{
|
|
SwitchNode->RemovePinFromSwitchNode(SelectedPin);
|
|
}
|
|
|
|
// Update the graph so that the node will be refreshed
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
|
|
UEdGraph* CurrentGraph = FocusedGraphEd->GetCurrentGraph();
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CurrentGraph);
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRemoveExecutionPin() const
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
if (UEdGraphPin* SelectedPin = FocusedGraphEd->GetGraphPinForMenu())
|
|
{
|
|
UEdGraphNode* OwningNode = SelectedPin->GetOwningNode();
|
|
|
|
if (UK2Node_ExecutionSequence* SeqNode = Cast<UK2Node_ExecutionSequence>(OwningNode))
|
|
{
|
|
return SeqNode->CanRemoveExecutionPin();
|
|
}
|
|
else if (UK2Node_Switch* SwitchNode = Cast<UK2Node_Switch>(OwningNode))
|
|
{
|
|
return SwitchNode->CanRemoveExecutionPin(SelectedPin);
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnRemoveThisStructVarPin()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
UEdGraphPin* SelectedPin = FocusedGraphEd.IsValid() ? FocusedGraphEd->GetGraphPinForMenu() : nullptr;
|
|
UEdGraphNode* OwningNode = SelectedPin ? SelectedPin->GetOwningNodeUnchecked() : nullptr;
|
|
if (UK2Node_SetFieldsInStruct* SetFilestInStructNode = Cast<UK2Node_SetFieldsInStruct>(OwningNode))
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("RemoveThisStructVarPin", "Remove Struct Var Pin"));
|
|
SetFilestInStructNode->Modify();
|
|
SelectedPin->Modify();
|
|
SetFilestInStructNode->RemoveFieldPins(SelectedPin, UK2Node_SetFieldsInStruct::EPinsToRemove::GivenPin);
|
|
|
|
// Update the graph so that the node will be refreshed
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
|
|
UEdGraph* CurrentGraph = FocusedGraphEd->GetCurrentGraph();
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CurrentGraph);
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRemoveThisStructVarPin() const
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
const UEdGraphPin* SelectedPin = FocusedGraphEd.IsValid() ? FocusedGraphEd->GetGraphPinForMenu() : nullptr;
|
|
return UK2Node_SetFieldsInStruct::ShowCustomPinActions(SelectedPin, false);
|
|
}
|
|
|
|
void FBlueprintEditor::OnRemoveOtherStructVarPins()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
UEdGraphPin* SelectedPin = FocusedGraphEd.IsValid() ? FocusedGraphEd->GetGraphPinForMenu() : nullptr;
|
|
UEdGraphNode* OwningNode = SelectedPin ? SelectedPin->GetOwningNodeUnchecked() : nullptr;
|
|
if (UK2Node_SetFieldsInStruct* SetFilestInStructNode = Cast<UK2Node_SetFieldsInStruct>(OwningNode))
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("RemoveOtherStructVarPins", "Remove Other Struct Var Pins"));
|
|
SetFilestInStructNode->Modify();
|
|
SelectedPin->Modify();
|
|
SetFilestInStructNode->RemoveFieldPins(SelectedPin, UK2Node_SetFieldsInStruct::EPinsToRemove::AllOtherPins);
|
|
|
|
// Update the graph so that the node will be refreshed
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
|
|
UEdGraph* CurrentGraph = FocusedGraphEd->GetCurrentGraph();
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CurrentGraph);
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRemoveOtherStructVarPins() const
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
const UEdGraphPin* SelectedPin = FocusedGraphEd.IsValid() ? FocusedGraphEd->GetGraphPinForMenu() : nullptr;
|
|
return UK2Node_SetFieldsInStruct::ShowCustomPinActions(SelectedPin, false);
|
|
}
|
|
|
|
void FBlueprintEditor::OnRestoreAllStructVarPins()
|
|
{
|
|
const FGraphPanelSelectionSet& SelectedNodes = GetSelectedNodes();
|
|
FGraphPanelSelectionSet::TConstIterator It(SelectedNodes);
|
|
UK2Node_SetFieldsInStruct* Node = (!!It) ? Cast<UK2Node_SetFieldsInStruct>(*It) : nullptr;
|
|
if (Node && !Node->AllPinsAreShown())
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("RestoreAllStructVarPins", "Restore all struct var pins"));
|
|
Node->Modify();
|
|
Node->RestoreAllPins();
|
|
|
|
// Refresh the current graph, so the pins can be updated
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
UEdGraph* CurrentGraph = FocusedGraphEd->GetCurrentGraph();
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraphChecked(CurrentGraph);
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint);
|
|
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
}
|
|
|
|
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRestoreAllStructVarPins() const
|
|
{
|
|
const FGraphPanelSelectionSet& SelectedNodes = GetSelectedNodes();
|
|
FGraphPanelSelectionSet::TConstIterator It(SelectedNodes);
|
|
UK2Node_SetFieldsInStruct* Node = (!!It) ? Cast<UK2Node_SetFieldsInStruct>(*It) : nullptr;
|
|
return Node && !Node->AllPinsAreShown();
|
|
}
|
|
|
|
void FBlueprintEditor::OnResetPinToDefaultValue()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
UEdGraphPin* TargetPin = FocusedGraphEd->GetGraphPinForMenu();
|
|
|
|
check(TargetPin);
|
|
|
|
const FScopedTransaction Transaction(LOCTEXT("ResetPinToDefaultValue", "Reset Pin To Default Value"));
|
|
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
TargetPin->Modify();
|
|
K2Schema->ResetPinToAutogeneratedDefaultValue(TargetPin);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanResetPinToDefaultValue() const
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
bool bCanRecombine = false;
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
if (UEdGraphPin* Pin = FocusedGraphEd->GetGraphPinForMenu())
|
|
{
|
|
return !Pin->DoesDefaultValueMatchAutogenerated();
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnAddOptionPin()
|
|
{
|
|
const FGraphPanelSelectionSet& SelectedNodes = GetSelectedNodes();
|
|
|
|
// Iterate over all nodes, and add the pin
|
|
for (FGraphPanelSelectionSet::TConstIterator It(SelectedNodes); It; ++It)
|
|
{
|
|
UK2Node_Select* SeqNode = Cast<UK2Node_Select>(*It);
|
|
if (SeqNode != nullptr)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("AddOptionPin", "Add Option Pin") );
|
|
SeqNode->Modify();
|
|
|
|
SeqNode->AddInputPin();
|
|
|
|
const UEdGraphSchema* Schema = SeqNode->GetSchema();
|
|
Schema->ReconstructNode(*SeqNode);
|
|
}
|
|
}
|
|
|
|
// Refresh the current graph, so the pins can be updated
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanAddOptionPin() const
|
|
{
|
|
const FGraphPanelSelectionSet& SelectedNodes = GetSelectedNodes();
|
|
|
|
// Iterate over all nodes, and see if all can have a pin removed
|
|
for (FGraphPanelSelectionSet::TConstIterator It(SelectedNodes); It; ++It)
|
|
{
|
|
UK2Node_Select* SeqNode = Cast<UK2Node_Select>(*It);
|
|
// There's a bad node so return false
|
|
if (SeqNode == nullptr || !SeqNode->CanAddPin())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FBlueprintEditor::OnRemoveOptionPin()
|
|
{
|
|
const FGraphPanelSelectionSet& SelectedNodes = GetSelectedNodes();
|
|
|
|
// Iterate over all nodes, and add the pin
|
|
for (FGraphPanelSelectionSet::TConstIterator It(SelectedNodes); It; ++It)
|
|
{
|
|
UK2Node_Select* SeqNode = Cast<UK2Node_Select>(*It);
|
|
if (SeqNode != nullptr)
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("RemoveOptionPin", "Remove Option Pin") );
|
|
SeqNode->Modify();
|
|
|
|
SeqNode->RemoveOptionPinToNode();
|
|
|
|
const UEdGraphSchema* Schema = SeqNode->GetSchema();
|
|
Schema->ReconstructNode(*SeqNode);
|
|
}
|
|
}
|
|
|
|
// Refresh the current graph, so the pins can be updated
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRemoveOptionPin() const
|
|
{
|
|
const FGraphPanelSelectionSet& SelectedNodes = GetSelectedNodes();
|
|
|
|
// Iterate over all nodes, and see if all can have a pin removed
|
|
for (FGraphPanelSelectionSet::TConstIterator It(SelectedNodes); It; ++It)
|
|
{
|
|
UK2Node_Select* SeqNode = Cast<UK2Node_Select>(*It);
|
|
// There's a bad node so return false
|
|
if (SeqNode == nullptr || !SeqNode->CanRemoveOptionPinToNode())
|
|
{
|
|
return false;
|
|
}
|
|
// If this node doesn't have at least 3 options return false (need at least 2)
|
|
else
|
|
{
|
|
TArray<UEdGraphPin*> OptionPins;
|
|
SeqNode->GetOptionPins(OptionPins);
|
|
if (OptionPins.Num() <= 2)
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FBlueprintEditor::OnChangePinType()
|
|
{
|
|
if (UEdGraphPin* SelectedPin = GetCurrentlySelectedPin())
|
|
{
|
|
// Grab the root pin, that is what we want to edit
|
|
UEdGraphPin* RootPin = SelectedPin;
|
|
while(RootPin->ParentPin)
|
|
{
|
|
RootPin = RootPin->ParentPin;
|
|
}
|
|
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
// If this is the index node of the select node, we need to use the index list of types
|
|
UK2Node_Select* SelectNode = Cast<UK2Node_Select>(SelectedPin->GetOwningNode());
|
|
if (SelectNode && SelectNode->GetIndexPin() == SelectedPin)
|
|
{
|
|
TSharedRef<SCompoundWidget> PinChange = SNew(SPinTypeSelector, FGetPinTypeTree::CreateUObject(Schema, &UEdGraphSchema_K2::GetVariableTypeTree))
|
|
.TargetPinType(this, &FBlueprintEditor::OnGetPinType, RootPin)
|
|
.OnPinTypeChanged(this, &FBlueprintEditor::OnChangePinTypeFinished, SelectedPin)
|
|
.Schema(Schema)
|
|
.TypeTreeFilter(ETypeTreeFilter::IndexTypesOnly)
|
|
.IsEnabled(true)
|
|
.bAllowArrays(false);
|
|
|
|
PinTypeChangeMenu = FSlateApplication::Get().PushMenu(
|
|
GetToolkitHost()->GetParentWidget(), // Parent widget should be k2 not the menu thats open or it will be closed when the menu is dismissed
|
|
FWidgetPath(),
|
|
PinChange,
|
|
FSlateApplication::Get().GetCursorPos(), // summon location
|
|
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
|
|
);
|
|
}
|
|
else
|
|
{
|
|
TSharedRef<SCompoundWidget> PinChange = SNew(SPinTypeSelector, FGetPinTypeTree::CreateUObject(Schema, &UEdGraphSchema_K2::GetVariableTypeTree))
|
|
.TargetPinType(this, &FBlueprintEditor::OnGetPinType, RootPin)
|
|
.OnPinTypeChanged(this, &FBlueprintEditor::OnChangePinTypeFinished, SelectedPin)
|
|
.Schema(Schema)
|
|
.TypeTreeFilter(ETypeTreeFilter::None)
|
|
.IsEnabled(true)
|
|
.bAllowArrays(false);
|
|
|
|
PinTypeChangeMenu = FSlateApplication::Get().PushMenu(
|
|
GetToolkitHost()->GetParentWidget(), // Parent widget should be k2 not the menu thats open or it will be closed when the menu is dismissed
|
|
FWidgetPath(),
|
|
PinChange,
|
|
FSlateApplication::Get().GetCursorPos(), // summon location
|
|
FPopupTransitionEffect(FPopupTransitionEffect::TypeInPopup)
|
|
);
|
|
}
|
|
}
|
|
}
|
|
|
|
FEdGraphPinType FBlueprintEditor::OnGetPinType(UEdGraphPin* SelectedPin) const
|
|
{
|
|
return SelectedPin->PinType;
|
|
}
|
|
|
|
void FBlueprintEditor::OnChangePinTypeFinished(const FEdGraphPinType& PinType, UEdGraphPin* InSelectedPin)
|
|
{
|
|
if (FBlueprintEditorUtils::IsPinTypeValid(PinType))
|
|
{
|
|
UEdGraphNode* OwningNode = InSelectedPin->GetOwningNode();
|
|
OwningNode->Modify();
|
|
InSelectedPin->PinType = PinType;
|
|
if (UK2Node_Select* SelectNode = Cast<UK2Node_Select>(InSelectedPin->GetOwningNode()))
|
|
{
|
|
SelectNode->ChangePinType(InSelectedPin);
|
|
}
|
|
}
|
|
|
|
if (PinTypeChangeMenu.IsValid())
|
|
{
|
|
PinTypeChangeMenu.Pin()->Dismiss();
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanChangePinType() const
|
|
{
|
|
if (UEdGraphPin* Pin = GetCurrentlySelectedPin())
|
|
{
|
|
if (UK2Node_Select* SelectNode = Cast<UK2Node_Select>(Pin->GetOwningNode()))
|
|
{
|
|
return SelectNode->CanChangePinType(Pin);
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnAddParentNode()
|
|
{
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
if (UEdGraphNode* SelectedObj = GetSingleSelectedNode())
|
|
{
|
|
// Get the function that the event node or function entry represents
|
|
FFunctionFromNodeHelper FunctionFromNode(SelectedObj);
|
|
if (FunctionFromNode.Function && FunctionFromNode.Node)
|
|
{
|
|
UFunction* ValidParent = Schema->GetCallableParentFunction(FunctionFromNode.Function);
|
|
UEdGraph* TargetGraph = FunctionFromNode.Node->GetGraph();
|
|
if (ValidParent && TargetGraph)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("AddParentNode", "Add Parent Node"));
|
|
TargetGraph->Modify();
|
|
|
|
FGraphNodeCreator<UK2Node_CallParentFunction> FunctionNodeCreator(*TargetGraph);
|
|
UK2Node_CallParentFunction* ParentFunctionNode = FunctionNodeCreator.CreateNode();
|
|
ParentFunctionNode->SetFromFunction(ValidParent);
|
|
ParentFunctionNode->AllocateDefaultPins();
|
|
|
|
int32 NodeSizeY = 15;
|
|
if( UK2Node* Node = Cast<UK2Node>(SelectedObj))
|
|
{
|
|
NodeSizeY += Node->DEPRECATED_NodeWidget.IsValid() ? static_cast<int32>(Node->DEPRECATED_NodeWidget.Pin()->GetDesiredSize().Y) : 0;
|
|
}
|
|
ParentFunctionNode->NodePosX = FunctionFromNode.Node->NodePosX;
|
|
ParentFunctionNode->NodePosY = FunctionFromNode.Node->NodePosY + NodeSizeY;
|
|
FunctionNodeCreator.Finalize();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanAddParentNode() const
|
|
{
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
if (UEdGraphNode* SelectedObj = GetSingleSelectedNode())
|
|
{
|
|
// Get the function that the event node or function entry represents
|
|
FFunctionFromNodeHelper FunctionFromNode(SelectedObj);
|
|
if (FunctionFromNode.Function)
|
|
{
|
|
return (Schema->GetCallableParentFunction(FunctionFromNode.Function) != nullptr);
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnCreateMatchingFunction()
|
|
{
|
|
if (UK2Node_CallFunction* SelectedNode = Cast<UK2Node_CallFunction>(GetSingleSelectedNode()))
|
|
{
|
|
FBlueprintEditorUtils::CreateMatchingFunction(SelectedNode, GetDefaultSchemaClass());
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanCreateMatchingFunction() const
|
|
{
|
|
if (NewDocument_IsVisibleForType(CGT_NewFunctionGraph))
|
|
{
|
|
if (const UBlueprint* Blueprint = GetBlueprintObj())
|
|
{
|
|
if (const UK2Node_CallFunction* SelectedNode = Cast<UK2Node_CallFunction>(GetSingleSelectedNode()))
|
|
{
|
|
return FKismetNameValidator(Blueprint).IsValid(SelectedNode->GetFunctionName()) == EValidatorResult::Ok;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnToggleBreakpoint()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UK2Node* SelectedNode = Cast<UK2Node>(*NodeIt);
|
|
if ((SelectedNode != nullptr) && SelectedNode->CanPlaceBreakpoints())
|
|
{
|
|
FBlueprintBreakpoint* ExistingBreakpoint = FKismetDebugUtilities::FindBreakpointForNode(SelectedNode, GetBlueprintObj());
|
|
if (ExistingBreakpoint == nullptr)
|
|
{
|
|
// Add a breakpoint on this node if there isn't one there already
|
|
FKismetDebugUtilities::CreateBreakpoint(GetBlueprintObj(), SelectedNode, /* bIsEnabled = */ true);
|
|
}
|
|
else
|
|
{
|
|
// Remove the breakpoint if it was present
|
|
FKismetDebugUtilities::RemoveBreakpointFromNode(SelectedNode, GetBlueprintObj());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanToggleBreakpoint() const
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UK2Node* SelectedNode = Cast<UK2Node>(*NodeIt);
|
|
if ((SelectedNode != nullptr) && SelectedNode->CanPlaceBreakpoints())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnAddBreakpoint()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UK2Node* SelectedNode = Cast<UK2Node>(*NodeIt);
|
|
if ((SelectedNode != nullptr) && SelectedNode->CanPlaceBreakpoints())
|
|
{
|
|
// Add a breakpoint on this node if there isn't one there already
|
|
const FBlueprintBreakpoint* ExistingBreakpoint = FKismetDebugUtilities::FindBreakpointForNode(SelectedNode, GetBlueprintObj());
|
|
if (ExistingBreakpoint == nullptr)
|
|
{
|
|
FKismetDebugUtilities::CreateBreakpoint(GetBlueprintObj(), SelectedNode, /* bIsEnabled = */ true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanAddBreakpoint() const
|
|
{
|
|
// See if any of the selected nodes are impure, and thus could have a breakpoint set on them
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UK2Node* SelectedNode = Cast<UK2Node>(*NodeIt);
|
|
if ((SelectedNode != nullptr) && SelectedNode->CanPlaceBreakpoints())
|
|
{
|
|
FBlueprintBreakpoint* ExistingBreakpoint = FKismetDebugUtilities::FindBreakpointForNode(SelectedNode, GetBlueprintObj());
|
|
if (ExistingBreakpoint == nullptr)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnRemoveBreakpoint()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* SelectedNode = CastChecked<UEdGraphNode>(*NodeIt);
|
|
|
|
// Remove the breakpoint
|
|
FKismetDebugUtilities::RemoveBreakpointFromNode(SelectedNode, GetBlueprintObj());
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRemoveBreakpoint() const
|
|
{
|
|
// See if any of the selected nodes have a breakpoint set
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* SelectedNode = CastChecked<UEdGraphNode>(*NodeIt);
|
|
if (FKismetDebugUtilities::FindBreakpointForNode(SelectedNode, GetBlueprintObj()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnDisableBreakpoint()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* SelectedNode = CastChecked<UEdGraphNode>(*NodeIt);
|
|
|
|
FKismetDebugUtilities::SetBreakpointEnabled(SelectedNode, GetBlueprintObj(), false);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanDisableBreakpoint() const
|
|
{
|
|
// See if any of the selected nodes have a breakpoint set
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* SelectedNode = CastChecked<UEdGraphNode>(*NodeIt);
|
|
if (FBlueprintBreakpoint* ExistingBreakpoint = FKismetDebugUtilities::FindBreakpointForNode(SelectedNode, GetBlueprintObj()))
|
|
{
|
|
if (ExistingBreakpoint->IsEnabledByUser())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnEnableBreakpoint()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* SelectedNode = CastChecked<UEdGraphNode>(*NodeIt);
|
|
|
|
FKismetDebugUtilities::SetBreakpointEnabled(SelectedNode, GetBlueprintObj(), true);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanEnableBreakpoint() const
|
|
{
|
|
// See if any of the selected nodes have a breakpoint set
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* SelectedNode = CastChecked<UEdGraphNode>(*NodeIt);
|
|
if (FBlueprintBreakpoint* ExistingBreakpoint = FKismetDebugUtilities::FindBreakpointForNode(SelectedNode, GetBlueprintObj()))
|
|
{
|
|
if (!ExistingBreakpoint->IsEnabledByUser())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
namespace CollapseGraphUtils
|
|
{
|
|
/** Helper function to gather any moveable nodes out of a collapsed graph */
|
|
static void GatherMoveableNodes(const UEdGraph* const SourceGraph, TSet<UEdGraphNode*>& OutNodes)
|
|
{
|
|
for (UEdGraphNode* Node : SourceGraph->Nodes)
|
|
{
|
|
// Ignore tunnel nodes and break the links because new ones will be created during the collapse of this node
|
|
if (!Node->IsA<UK2Node_Tunnel>())
|
|
{
|
|
OutNodes.Add(Node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnCollapseNodes()
|
|
{
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
// Does the selection set contain anything that is legal to collapse?
|
|
TSet<UEdGraphNode*> CollapsableNodes;
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*NodeIt))
|
|
{
|
|
if (Schema->CanEncapuslateNode(*SelectedNode))
|
|
{
|
|
CollapsableNodes.Add(SelectedNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collapse them
|
|
if (CollapsableNodes.Num())
|
|
{
|
|
UBlueprint* BlueprintObj = GetBlueprintObj();
|
|
const FScopedTransaction Transaction( FGraphEditorCommands::Get().CollapseNodes->GetDescription() );
|
|
BlueprintObj->Modify();
|
|
|
|
CollapseNodes(CollapsableNodes);
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified( BlueprintObj );
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanCollapseNodes() const
|
|
{
|
|
//@TODO: ANIM: Determine what collapsing nodes means in an animation graph, and add any necessary compiler support for it
|
|
if (IsEditingAnimGraph())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Does the selection set contain anything that is legal to collapse?
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*NodeIt))
|
|
{
|
|
if (Schema->CanEncapuslateNode(*Node))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnCollapseSelectionToFunction()
|
|
{
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
// Does the selection set contain anything that is legal to collapse?
|
|
TSet<UEdGraphNode*> CollapsableNodes;
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*NodeIt))
|
|
{
|
|
if (Schema->CanEncapuslateNode(*SelectedNode))
|
|
{
|
|
CollapsableNodes.Add(SelectedNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collapse them
|
|
if (CollapsableNodes.Num() && CanCollapseSelectionToFunction(CollapsableNodes))
|
|
{
|
|
UBlueprint* BlueprintObj = GetBlueprintObj();
|
|
const FScopedTransaction Transaction( FGraphEditorCommands::Get().CollapseNodes->GetDescription() );
|
|
BlueprintObj->Modify();
|
|
|
|
UEdGraphNode* FunctionNode = nullptr;
|
|
UEdGraph* FunctionGraph = CollapseSelectionToFunction(FocusedGraphEdPtr.Pin(), CollapsableNodes, FunctionNode);
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified( BlueprintObj );
|
|
|
|
RenameNewlyAddedAction(FunctionGraph->GetFName());
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanCollapseSelectionToFunction(TSet<class UEdGraphNode*>& InSelection) const
|
|
{
|
|
bool bBadConnection = false;
|
|
UEdGraphPin* OutputConnection = nullptr;
|
|
UEdGraphPin* InputConnection = nullptr;
|
|
|
|
// Create a function graph
|
|
UEdGraph* FunctionGraph = FBlueprintEditorUtils::CreateNewGraph(GetBlueprintObj(), FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), TEXT("TempGraph")), UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
|
FBlueprintEditorUtils::AddFunctionGraph<UClass>(GetBlueprintObj(), FunctionGraph, /*bIsUserCreated=*/ true, nullptr);
|
|
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
FCompilerResultsLog LogResults;
|
|
LogResults.bAnnotateMentionedNodes = false;
|
|
|
|
UEdGraphNode* InterfaceTemplateNode = nullptr;
|
|
|
|
TArray<UEdGraphPin*> EntryGatewayPins;
|
|
|
|
// Runs through every node and fully validates errors with placing selection in a function graph, reporting all errors.
|
|
for (TSet<UEdGraphNode*>::TConstIterator NodeIt(InSelection); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* Node = *NodeIt;
|
|
|
|
if(!Node->CanPasteHere(FunctionGraph))
|
|
{
|
|
if (UK2Node_CustomEvent* const CustomEvent = Cast<UK2Node_CustomEvent>(Node))
|
|
{
|
|
UEdGraphPin* EventExecPin = K2Schema->FindExecutionPin(*CustomEvent, EGPD_Output);
|
|
check(EventExecPin);
|
|
|
|
if(InterfaceTemplateNode)
|
|
{
|
|
LogResults.Error(*LOCTEXT("TooManyCustomEvents_Error", "Can use @@ as a template for creating the function, can only have a single custom event! Previously found @@").ToString(), CustomEvent, InterfaceTemplateNode);
|
|
}
|
|
else
|
|
{
|
|
// The custom event will be used as the template interface for the function.
|
|
InterfaceTemplateNode = CustomEvent;
|
|
if(InputConnection)
|
|
{
|
|
InputConnection = (EventExecPin->LinkedTo.Num() > 0) ? EventExecPin->LinkedTo[0] : nullptr;
|
|
}
|
|
continue;
|
|
}
|
|
}
|
|
|
|
LogResults.Error(*LOCTEXT("CannotPasteNodeFunction_Error", "@@ cannot be placed in function graph").ToString(), Node);
|
|
bBadConnection = true;
|
|
}
|
|
else
|
|
{
|
|
for (UEdGraphPin* NodePin : Node->Pins)
|
|
{
|
|
if (NodePin->PinType.PinCategory == K2Schema->PC_Exec)
|
|
{
|
|
if (NodePin->LinkedTo.Num() == 0 && NodePin->Direction == EGPD_Input)
|
|
{
|
|
EntryGatewayPins.Add(NodePin);
|
|
}
|
|
else
|
|
{
|
|
for (UEdGraphPin* ConnectedPin : NodePin->LinkedTo)
|
|
{
|
|
if (!InSelection.Contains(ConnectedPin->GetOwningNode()))
|
|
{
|
|
if (NodePin->Direction == EGPD_Input)
|
|
{
|
|
// For input pins, there must be a single connection
|
|
if ((InputConnection == nullptr) || (InputConnection == NodePin))
|
|
{
|
|
EntryGatewayPins.Add(NodePin);
|
|
InputConnection = NodePin;
|
|
}
|
|
else
|
|
{
|
|
// Check if the input connection was linked, report what node it is connected to
|
|
LogResults.Error(*LOCTEXT("TooManyPathsMultipleInput_Error", "Found too many input connections in selection! @@ is connected to @@, previously found @@ connected to @@").ToString(), Node, ConnectedPin->GetOwningNode(), InputConnection->GetOwningNode(), (InputConnection->LinkedTo.Num() > 0) ? InputConnection->LinkedTo[0]->GetOwningNode() : nullptr);
|
|
bBadConnection = true;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// For output pins, as long as they all connect to the same pin, we consider the selection valid for being made into a function
|
|
if ((OutputConnection == nullptr) || (OutputConnection == ConnectedPin))
|
|
{
|
|
OutputConnection = ConnectedPin;
|
|
}
|
|
else
|
|
{
|
|
LogResults.Error(*LOCTEXT("TooManyPathsMultipleOutput_Error", "Found too many output connections in selection! @@ is connected to @@, previously found @@ connected to @@").ToString(), Node, ConnectedPin->GetOwningNode(), OutputConnection->GetOwningNode(), (OutputConnection->LinkedTo.Num() > 0) ? OutputConnection->LinkedTo[0]->GetOwningNode() : nullptr);
|
|
bBadConnection = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bBadConnection && (InputConnection == nullptr) && (EntryGatewayPins.Num() > 1))
|
|
{
|
|
// Too many input gateway pins with no connections.
|
|
LogResults.Error(*LOCTEXT("AmbiguousEntryPaths_Error", "Multiple entry pin possibilities. Unable to convert to a function. Make sure that selection either has only 1 entry pin or exactly 1 entry pin has a connection.").ToString());
|
|
bBadConnection = true;
|
|
}
|
|
|
|
// No need to check for cycling if the selection is invalid anyways.
|
|
if(!bBadConnection && FBlueprintEditorUtils::CheckIfSelectionIsCycling(InSelection, LogResults))
|
|
{
|
|
bBadConnection = true;
|
|
}
|
|
|
|
FMessageLog MessageLog("BlueprintLog");
|
|
MessageLog.NewPage(LOCTEXT("CollapseToFunctionPageLabel", "Collapse to Function"));
|
|
MessageLog.AddMessages(LogResults.Messages);
|
|
MessageLog.Notify(LOCTEXT("CollapseToFunctionError", "Collapsing to Function Failed!"));
|
|
|
|
FBlueprintEditorUtils::RemoveGraph(GetBlueprintObj(), FunctionGraph);
|
|
FunctionGraph->MarkAsGarbage();
|
|
return !bBadConnection;
|
|
}
|
|
|
|
bool FBlueprintEditor::CanCollapseSelectionToFunction() const
|
|
{
|
|
return !IsEditingAnimGraph();
|
|
}
|
|
|
|
void FBlueprintEditor::OnCollapseSelectionToMacro()
|
|
{
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
// Does the selection set contain anything that is legal to collapse?
|
|
TSet<UEdGraphNode*> CollapsableNodes;
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*NodeIt))
|
|
{
|
|
if (Schema->CanEncapuslateNode(*SelectedNode))
|
|
{
|
|
CollapsableNodes.Add(SelectedNode);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Collapse them
|
|
if (CollapsableNodes.Num() && CanCollapseSelectionToMacro(CollapsableNodes))
|
|
{
|
|
UBlueprint* BlueprintObj = GetBlueprintObj();
|
|
const FScopedTransaction Transaction( FGraphEditorCommands::Get().CollapseNodes->GetDescription() );
|
|
BlueprintObj->Modify();
|
|
|
|
UEdGraphNode* MacroNode = nullptr;
|
|
UEdGraph* MacroGraph = CollapseSelectionToMacro(FocusedGraphEdPtr.Pin(), CollapsableNodes, MacroNode);
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified( BlueprintObj );
|
|
|
|
RenameNewlyAddedAction(MacroGraph->GetFName());
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanCollapseSelectionToMacro(TSet<class UEdGraphNode*>& InSelection) const
|
|
{
|
|
// Create a temporary macro graph
|
|
UEdGraph* MacroGraph = FBlueprintEditorUtils::CreateNewGraph(GetBlueprintObj(), FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), TEXT("TempGraph")), UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
|
FBlueprintEditorUtils::AddMacroGraph(GetBlueprintObj(), MacroGraph, /*bIsUserCreated=*/ true, nullptr);
|
|
|
|
// Does the selection set contain anything that is legal to collapse?
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
bool bCollapseAllowed = true;
|
|
FCompilerResultsLog LogResults;
|
|
LogResults.bAnnotateMentionedNodes = false;
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* Node = Cast<UEdGraphNode>(*NodeIt);
|
|
|
|
if(!Node->CanPasteHere(MacroGraph))
|
|
{
|
|
LogResults.Error(*LOCTEXT("CannotPasteNodeMacro_Error", "@@ cannot be placed in macro graph").ToString(), Node);
|
|
bCollapseAllowed = false;
|
|
}
|
|
}
|
|
|
|
FMessageLog MessageLog("BlueprintLog");
|
|
MessageLog.NewPage(LOCTEXT("CollapseToMacroPageLabel", "Collapse to Macro"));
|
|
MessageLog.AddMessages(LogResults.Messages);
|
|
MessageLog.Notify(LOCTEXT("CollapseToMacroError", "Collapsing to Macro Failed!"));
|
|
|
|
FBlueprintEditorUtils::RemoveGraph(GetBlueprintObj(), MacroGraph);
|
|
MacroGraph->MarkAsGarbage();
|
|
return bCollapseAllowed;
|
|
}
|
|
|
|
bool FBlueprintEditor::CanCollapseSelectionToMacro() const
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
if(IsEditingAnimGraph())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
void FBlueprintEditor::OnPromoteSelectionToFunction()
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("ConvertCollapsedGraphToFunction", "Convert Collapse Graph to Function") );
|
|
UBlueprint* BP = GetBlueprintObj();
|
|
BP->Modify();
|
|
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
|
|
// Set of nodes to select when finished
|
|
TSet<UEdGraphNode*> NodesToSelect;
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator It(SelectedNodes); It; ++It)
|
|
{
|
|
if (UK2Node_Composite* CompositeNode = Cast<UK2Node_Composite>(*It))
|
|
{
|
|
// Check if there is only one input and one output connection
|
|
TSet<UEdGraphNode*> NodesInGraph;
|
|
NodesInGraph.Add(CompositeNode);
|
|
|
|
if(CanCollapseSelectionToFunction(NodesInGraph))
|
|
{
|
|
DocumentManager->CleanInvalidTabs();
|
|
|
|
UEdGraph* SourceGraph = CompositeNode->BoundGraph;
|
|
|
|
TSet<UEdGraphNode*> NodesToMove;
|
|
CollapseGraphUtils::GatherMoveableNodes(SourceGraph, /* Out */ NodesToMove);
|
|
|
|
// Remove this node from selection
|
|
FocusedGraphEd->SetNodeSelection(CompositeNode, false);
|
|
|
|
UEdGraphNode* FunctionNode = nullptr;
|
|
CollapseSelectionToFunction(FocusedGraphEd, NodesToMove, FunctionNode);
|
|
NodesToSelect.Add(FunctionNode);
|
|
|
|
// Connect the exec pin of the newly dropped function call node to any previously existing connections
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
UEdGraphPin* ResultExecFunc = K2Schema->FindExecutionPin(*FunctionNode, EGPD_Input);
|
|
UEdGraphPin* OriginalExecPin = K2Schema->FindExecutionPin(*CompositeNode, EGPD_Input);
|
|
|
|
if (ResultExecFunc && OriginalExecPin)
|
|
{
|
|
for (UEdGraphPin* CurPin : OriginalExecPin->LinkedTo)
|
|
{
|
|
// Make a connection if this is an exec pin
|
|
if (CurPin && CurPin->Direction == EGPD_Output && K2Schema->IsExecPin(*CurPin))
|
|
{
|
|
ResultExecFunc->MakeLinkTo(CurPin);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove the old collapsed graph
|
|
FBlueprintEditorUtils::RemoveNode(BP, CompositeNode);
|
|
}
|
|
else
|
|
{
|
|
NodesToSelect.Add(CompositeNode);
|
|
}
|
|
}
|
|
else if(UEdGraphNode* Node = Cast<UEdGraphNode>(*It))
|
|
{
|
|
NodesToSelect.Add(Node);
|
|
}
|
|
}
|
|
|
|
// Select all nodes that should still be part of selection
|
|
for (UEdGraphNode* NodeToSelect : NodesToSelect)
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(NodeToSelect, true);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanPromoteSelectionToFunction() const
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UK2Node_Composite* CompositeNode = Cast<UK2Node_Composite>(*NodeIt))
|
|
{
|
|
// Check if there is only one input and one output connection
|
|
TSet<class UEdGraphNode*> NodesInGraph;
|
|
NodesInGraph.Add(CompositeNode);
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnPromoteSelectionToMacro()
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("ConvertCollapsedGraphToMacro", "Convert Collapse Graph to Macro") );
|
|
UBlueprint* BP = GetBlueprintObj();
|
|
BP->Modify();
|
|
|
|
// Set of nodes to select when finished
|
|
TSet<UEdGraphNode*> NodesToSelect;
|
|
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator It(SelectedNodes); It; ++It)
|
|
{
|
|
if (UK2Node_Composite* CompositeNode = Cast<UK2Node_Composite>(*It))
|
|
{
|
|
TSet<UEdGraphNode*> NodesToMove;
|
|
UEdGraph* SourceGraph = CompositeNode->BoundGraph;
|
|
|
|
// Gather the entry and exit nodes of the collapsed graph so that we can create new macro inputs
|
|
CollapseGraphUtils::GatherMoveableNodes(SourceGraph, /* Out */ NodesToMove);
|
|
|
|
if(CanCollapseSelectionToMacro(NodesToMove))
|
|
{
|
|
DocumentManager->CleanInvalidTabs();
|
|
|
|
// Remove this node from selection
|
|
FocusedGraphEd->SetNodeSelection(CompositeNode, false);
|
|
|
|
UEdGraphNode* MacroNode = nullptr;
|
|
CollapseSelectionToMacro(FocusedGraphEd, NodesToMove, MacroNode);
|
|
NodesToSelect.Add(MacroNode);
|
|
|
|
// Reconnect anything that the original composite node was connected to to this new macro instance
|
|
if (MacroNode)
|
|
{
|
|
// Gather what connections were already on the composite node...
|
|
TMap<FString, TSet<UEdGraphPin*>> OldToNewPinMap;
|
|
FEdGraphUtilities::GetPinConnectionMap(CompositeNode, OldToNewPinMap);
|
|
|
|
for (UEdGraphPin* const NewPin : MacroNode->Pins)
|
|
{
|
|
// Reconnect any new pins here that we can!
|
|
const FString& NewPinName = NewPin->GetName();
|
|
if (OldToNewPinMap.Contains(NewPinName))
|
|
{
|
|
for (UEdGraphPin* OldPin : OldToNewPinMap[NewPinName])
|
|
{
|
|
NewPin->MakeLinkTo(OldPin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FBlueprintEditorUtils::RemoveNode(BP, CompositeNode);
|
|
}
|
|
else
|
|
{
|
|
NodesToSelect.Add(CompositeNode);
|
|
}
|
|
}
|
|
else if(UEdGraphNode* Node = Cast<UEdGraphNode>(*It))
|
|
{
|
|
NodesToSelect.Add(Node);
|
|
}
|
|
}
|
|
|
|
// Select all nodes that should still be part of selection
|
|
for (UEdGraphNode* NodeToSelect : NodesToSelect)
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(NodeToSelect, true);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanPromoteSelectionToMacro() const
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
if(IsEditingAnimGraph())
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
for (UObject* SelectedNode : GetSelectedNodes())
|
|
{
|
|
UK2Node_Composite* CompositeNode = Cast<UK2Node_Composite>(SelectedNode);
|
|
if (CompositeNode && CompositeNode->BoundGraph)
|
|
{
|
|
TSet<class UEdGraphNode*> NodesInGraph;
|
|
|
|
// Collect all the nodes to test if they can be made into a function
|
|
for (UEdGraphNode* Node : CompositeNode->BoundGraph->Nodes)
|
|
{
|
|
// Ignore the tunnel nodes
|
|
if (Node->GetClass() != UK2Node_Tunnel::StaticClass())
|
|
{
|
|
NodesInGraph.Add(Node);
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnExpandNodes()
|
|
{
|
|
const FScopedTransaction Transaction( FGraphEditorCommands::Get().ExpandNodes->GetLabel() );
|
|
GetBlueprintObj()->Modify();
|
|
|
|
TSet<UEdGraphNode*> ExpandedNodes;
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
|
|
// Expand selected nodes into the focused graph context.
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
if(FocusedGraphEd)
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
}
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
ExpandedNodes.Empty();
|
|
bool bExpandedNodesNeedUniqueGuid = true;
|
|
|
|
DocumentManager->CleanInvalidTabs();
|
|
|
|
if (UK2Node_MacroInstance* SelectedMacroInstanceNode = Cast<UK2Node_MacroInstance>(*NodeIt))
|
|
{
|
|
UEdGraph* MacroGraph = SelectedMacroInstanceNode->GetMacroGraph();
|
|
if(MacroGraph)
|
|
{
|
|
// Clone the graph so that we do not delete the original
|
|
UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(MacroGraph, nullptr);
|
|
ExpandNode(SelectedMacroInstanceNode, ClonedGraph, /*inout*/ ExpandedNodes);
|
|
|
|
ClonedGraph->MarkAsGarbage();
|
|
}
|
|
}
|
|
else if (UK2Node_Composite* SelectedCompositeNode = Cast<UK2Node_Composite>(*NodeIt))
|
|
{
|
|
// No need to assign unique GUIDs since the source graph will be removed.
|
|
bExpandedNodesNeedUniqueGuid = false;
|
|
|
|
// Expand the composite node back into the world
|
|
UEdGraph* SourceGraph = SelectedCompositeNode->BoundGraph;
|
|
ExpandNode(SelectedCompositeNode, SourceGraph, /*inout*/ ExpandedNodes);
|
|
|
|
FBlueprintEditorUtils::RemoveGraph(GetBlueprintObj(), SourceGraph, EGraphRemoveFlags::Recompile);
|
|
}
|
|
else if (UK2Node_CallFunction* SelectedCallFunctionNode = Cast<UK2Node_CallFunction>(*NodeIt))
|
|
{
|
|
const UEdGraphNode* ResultEventNode = nullptr;
|
|
UEdGraph* FunctionGraph = SelectedCallFunctionNode->GetFunctionGraph(ResultEventNode);
|
|
|
|
// We should never get here when attempting to expand a call function that calls an event.
|
|
check(!ResultEventNode);
|
|
|
|
if(FunctionGraph)
|
|
{
|
|
// Clone the graph so that we do not delete the original
|
|
UEdGraph* ClonedGraph = FEdGraphUtilities::CloneGraph(FunctionGraph, nullptr);
|
|
ExpandNode(SelectedCallFunctionNode, ClonedGraph, ExpandedNodes);
|
|
|
|
ClonedGraph->MarkAsGarbage();
|
|
}
|
|
}
|
|
UEdGraphNode* SourceNode = CastChecked<UEdGraphNode>(*NodeIt);
|
|
check(SourceNode);
|
|
MoveNodesToAveragePos(ExpandedNodes, FVector2D(SourceNode->NodePosX, SourceNode->NodePosY), bExpandedNodesNeedUniqueGuid);
|
|
}
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj());
|
|
}
|
|
|
|
bool FBlueprintEditor::CanExpandNodes() const
|
|
{
|
|
// Does the selection set contain any composite nodes that are legal to expand?
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (Cast<UK2Node_Composite>(*NodeIt))
|
|
{
|
|
return true;
|
|
}
|
|
else if (UK2Node_MacroInstance* SelectedMacroInstanceNode = Cast<UK2Node_MacroInstance>(*NodeIt))
|
|
{
|
|
return SelectedMacroInstanceNode->GetMacroGraph() != nullptr;
|
|
}
|
|
else if (UK2Node_CallFunction* SelectedCallFunctionNode = Cast<UK2Node_CallFunction>(*NodeIt))
|
|
{
|
|
// If ResultEventNode is non-nullptr, it means it is sourced by an event, we do not want to expand events
|
|
const UEdGraphNode* ResultEventNode = nullptr;
|
|
return SelectedCallFunctionNode->GetFunctionGraph(ResultEventNode) != nullptr && ResultEventNode == nullptr;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::MoveNodesToAveragePos(TSet<UEdGraphNode*>& AverageNodes, FVector2D SourcePos, bool bExpandedNodesNeedUniqueGuid /* = false */) const
|
|
{
|
|
if (AverageNodes.Num() > 0)
|
|
{
|
|
FVector2D AvgNodePosition(0.0f, 0.0f);
|
|
|
|
for (TSet<UEdGraphNode*>::TIterator It(AverageNodes); It; ++It)
|
|
{
|
|
UEdGraphNode* Node = *It;
|
|
AvgNodePosition.X += Node->NodePosX;
|
|
AvgNodePosition.Y += Node->NodePosY;
|
|
}
|
|
|
|
float InvNumNodes = 1.0f / float(AverageNodes.Num());
|
|
AvgNodePosition.X *= InvNumNodes;
|
|
AvgNodePosition.Y *= InvNumNodes;
|
|
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
|
|
for (UEdGraphNode* ExpandedNode : AverageNodes)
|
|
{
|
|
ExpandedNode->NodePosX = static_cast<int32>((ExpandedNode->NodePosX - AvgNodePosition.X) + SourcePos.X);
|
|
ExpandedNode->NodePosY = static_cast<int32>((ExpandedNode->NodePosY - AvgNodePosition.Y) + SourcePos.Y);
|
|
|
|
ExpandedNode->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
|
|
if (bExpandedNodesNeedUniqueGuid)
|
|
{
|
|
ExpandedNode->CreateNewGuid();
|
|
}
|
|
|
|
//Add expanded node to selection
|
|
FocusedGraphEd->SetNodeSelection(ExpandedNode, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanConvertFunctionToEvent() const
|
|
{
|
|
UEdGraphNode* const SelectedNode = GetSingleSelectedNode();
|
|
|
|
if (UK2Node_FunctionEntry* const SelectedCallFunctionNode = Cast<UK2Node_FunctionEntry>(SelectedNode))
|
|
{
|
|
return FBlueprintEditorUtils::IsFunctionConvertableToEvent(GetBlueprintObj(), SelectedCallFunctionNode->FindSignatureFunction());
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnConvertFunctionToEvent()
|
|
{
|
|
UEdGraphNode* SelectedNode = GetSingleSelectedNode();
|
|
|
|
if (UK2Node_FunctionEntry* SelectedCallFunctionNode = Cast<UK2Node_FunctionEntry>(SelectedNode))
|
|
{
|
|
ConvertFunctionIfValid(SelectedCallFunctionNode);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::ConvertFunctionIfValid(UK2Node_FunctionEntry* FuncEntryNode)
|
|
{
|
|
FCompilerResultsLog LogResults;
|
|
FMessageLog MessageLog("BlueprintLog");
|
|
FText SpecificErrorMessage;
|
|
|
|
UBlueprint* BlueprintObj = FuncEntryNode ? FuncEntryNode->GetBlueprint() : nullptr;
|
|
|
|
if(BlueprintObj && FuncEntryNode)
|
|
{
|
|
UFunction* Func = FuncEntryNode->FindSignatureFunction();
|
|
check(Func);
|
|
|
|
// Make sure there are no output parameters
|
|
if (UEdGraphSchema_K2::HasFunctionAnyOutputParameter(Func))
|
|
{
|
|
LogResults.Error(*LOCTEXT("FunctionHasOutput_Error", "A function can only be converted if it does not have any output parameters.").ToString());
|
|
SpecificErrorMessage = LOCTEXT("FunctionHasOutput_Error_Title", "Function cannot have output parameters");
|
|
}
|
|
// Make sure this is not a blueprint/macro function library
|
|
else if (BlueprintObj->BlueprintType == BPTYPE_FunctionLibrary || BlueprintObj->BlueprintType == BPTYPE_MacroLibrary)
|
|
{
|
|
LogResults.Error(*LOCTEXT("BlueprintFunctionLibarary_Error", "Cannot convert functions from blueprint or macro libraries.").ToString());
|
|
SpecificErrorMessage = LOCTEXT("BlueprintFunctionLibarary_Error_Title", "Cannot convert blueprint or macro library functions");
|
|
}
|
|
// Ensure that this is no the construction script
|
|
else if (FuncEntryNode->FunctionReference.GetMemberName() == UEdGraphSchema_K2::FN_UserConstructionScript)
|
|
{
|
|
LogResults.Error(*LOCTEXT("ConvertConstructionScript_Error", "Cannot convert the construction script!").ToString());
|
|
SpecificErrorMessage = LOCTEXT("ConvertConstructionScript_Error_Title", "Cannot convert construction script");
|
|
}
|
|
// Make sure we are not on the animation graph
|
|
else if (IsEditingAnimGraph())
|
|
{
|
|
LogResults.Error(*LOCTEXT("ConvertAnimGraph_Error", "Cannot convert functions on the anim graph!").ToString());
|
|
SpecificErrorMessage = LOCTEXT("ConvertAnimGraph_Error_Title", "Cannot convert on the anim graph");
|
|
}
|
|
else
|
|
{
|
|
ConvertFunctionToEvent(FuncEntryNode);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
LogResults.Error(*LOCTEXT("MultipleNodesSelectred_Error", "Only one node can be selected for conversion!").ToString());
|
|
}
|
|
|
|
// Show the log results if there were any errors
|
|
if (LogResults.NumErrors)
|
|
{
|
|
MessageLog.NewPage(LOCTEXT("OnConvertEventToFunctionLabel", "Convert Event to Function"));
|
|
MessageLog.AddMessages(LogResults.Messages);
|
|
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("SpecificErrorMessage"), SpecificErrorMessage);
|
|
MessageLog.Notify(FText::Format(LOCTEXT("OnConvertEventToFunctionErrorMsg", "Convert Event to Function Failed!\n{SpecificErrorMessage}"), Arguments));
|
|
}
|
|
|
|
// If there are no errors, we succeed and should return true
|
|
return LogResults.NumErrors == 0;
|
|
}
|
|
|
|
void FBlueprintEditor::ConvertFunctionToEvent(UK2Node_FunctionEntry* SelectedCallFunctionNode)
|
|
{
|
|
check(SelectedCallFunctionNode);
|
|
UBlueprint* NodeBP = GetBlueprintObj();
|
|
UEdGraph* FunctionGraph = SelectedCallFunctionNode->GetGraph();
|
|
|
|
// Create a new event node with the old function name
|
|
UFunction* Func = SelectedCallFunctionNode->FindSignatureFunction();
|
|
UEdGraph* EventGraph = FBlueprintEditorUtils::FindEventGraph(NodeBP);
|
|
|
|
if (Func && EventGraph && FunctionGraph)
|
|
{
|
|
const FScopedTransaction Transaction(FGraphEditorCommands::Get().ConvertFunctionToEvent->GetLabel());
|
|
NodeBP->Modify();
|
|
EventGraph->Modify();
|
|
|
|
// Find all return nodes and break their connections
|
|
TArray< UK2Node_FunctionResult*> FunctionReturnNodes;
|
|
FunctionGraph->GetNodesOfClass(FunctionReturnNodes);
|
|
for (UK2Node_FunctionResult* Node : FunctionReturnNodes)
|
|
{
|
|
if (Node)
|
|
{
|
|
Node->BreakAllNodeLinks();
|
|
}
|
|
}
|
|
|
|
// Keep track of the old connections from the entry node
|
|
TMap<FString, TSet<UEdGraphPin*>> PinConnections;
|
|
FEdGraphUtilities::GetPinConnectionMap(SelectedCallFunctionNode, PinConnections);
|
|
|
|
FName EventName = Func->GetFName();
|
|
UClass* const OverrideFuncClass = CastChecked<UClass>(Func->GetOuter())->GetAuthoritativeClass();
|
|
|
|
UK2Node_Event* NewEventNode = nullptr;
|
|
FVector2D SpawnPos = EventGraph->GetGoodPlaceForNewNode();
|
|
|
|
// Was this function implemented in as an override?
|
|
UFunction* ParentFunction = FindUField<UFunction>(NodeBP->ParentClass, EventName);
|
|
bool bIsOverrideFunc = Func->GetSuperFunction() || (ParentFunction != nullptr);
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
// Allow an interface function to be converted in the case where the interface has changed
|
|
// and the function should now be placed as an event (UE-85687)
|
|
if (bIsOverrideFunc || FBlueprintEditorUtils::IsInterfaceFunction(NodeBP, Func))
|
|
{
|
|
NewEventNode = FEdGraphSchemaAction_K2NewNode::SpawnNode<UK2Node_Event>(
|
|
EventGraph,
|
|
SpawnPos,
|
|
EK2NewNodeFlags::SelectNewNode,
|
|
[EventName, OverrideFuncClass](UK2Node_Event* NewInstance)
|
|
{
|
|
NewInstance->EventReference.SetExternalMember(EventName, OverrideFuncClass);
|
|
NewInstance->bOverrideFunction = true;
|
|
}
|
|
);
|
|
}
|
|
else
|
|
{
|
|
// Spawn a new node, we have to do it this way to avoid renaming on creation of the custom event
|
|
UEdGraphNode* NewNode = nullptr;
|
|
NewNode = NewObject<UEdGraphNode>(EventGraph, UK2Node_CustomEvent::StaticClass());
|
|
check(NewNode != nullptr);
|
|
NewNode->CreateNewGuid();
|
|
|
|
NewNode->NodePosX = static_cast<int32>(SpawnPos.X);
|
|
NewNode->NodePosY = static_cast<int32>(SpawnPos.Y);
|
|
|
|
NewNode->SetFlags(RF_Transactional);
|
|
NewNode->AllocateDefaultPins();
|
|
NewNode->PostPlacedNewNode();
|
|
EventGraph->Modify();
|
|
// the FBlueprintMenuActionItem should do the selecting
|
|
EventGraph->AddNode(NewNode, /*bFromUI =*/false, /*bSelectNewNode =*/false);
|
|
|
|
NewEventNode = Cast<UK2Node_CustomEvent>(NewNode);
|
|
NewEventNode->CustomFunctionName = EventName;
|
|
NewEventNode->bOverrideFunction = false;
|
|
|
|
// Add every type of user pin that we need to the new event node
|
|
for (TSharedPtr<FUserPinInfo> Pin : SelectedCallFunctionNode->UserDefinedPins)
|
|
{
|
|
NewEventNode->CreateUserDefinedPin(Pin->PinName, Pin->PinType, Pin->DesiredPinDirection);
|
|
}
|
|
|
|
K2Schema->ReconstructNode(*NewEventNode);
|
|
}
|
|
|
|
// If this function had any local scope variables, we need to convert them to global scope
|
|
if(SelectedCallFunctionNode->LocalVariables.Num() > 0)
|
|
{
|
|
// Find any UK2Node_Variable's that may reference a local variable
|
|
TArray<UK2Node_Variable*> VarNodes;
|
|
FunctionGraph->GetNodesOfClass<UK2Node_Variable>(VarNodes);
|
|
|
|
// Make a globally scoped version of any local variables
|
|
for (const FBPVariableDescription& LocalVar : SelectedCallFunctionNode->LocalVariables)
|
|
{
|
|
// If a variable already exists of this name globally, then we need to use a unique name
|
|
// Only use FindUniqueKismetName if one exists because otherwise it will always add a "_0" to the name
|
|
FName NewVarName = FBlueprintEditorUtils::FindNewVariableIndex(NodeBP, LocalVar.VarName) == INDEX_NONE ?
|
|
LocalVar.VarName : FBlueprintEditorUtils::FindUniqueKismetName(NodeBP, LocalVar.VarName.ToString());
|
|
|
|
if (FBlueprintEditorUtils::AddMemberVariable(NodeBP, NewVarName, LocalVar.VarType, LocalVar.DefaultValue))
|
|
{
|
|
// Upon success, update the variable node's reference members
|
|
const FGuid NewVarGuid = FBlueprintEditorUtils::FindMemberVariableGuidByName(NodeBP, NewVarName);
|
|
|
|
for (UK2Node_Variable* VarNode : VarNodes)
|
|
{
|
|
if (VarNode->GetVarName() == LocalVar.VarName)
|
|
{
|
|
VarNode->Modify();
|
|
VarNode->VariableReference.SetDirect(NewVarName, NewVarGuid, nullptr, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Keep track of any nodes that have been expanded out of the function graph
|
|
TSet<UEdGraphNode*> ExpandedNodes;
|
|
|
|
UEdGraphNode* Entry = nullptr;
|
|
UEdGraphNode* Result = nullptr;
|
|
FunctionGraph->Modify();
|
|
MoveNodesToGraph(MutableView(FunctionGraph->Nodes), EventGraph, ExpandedNodes, &Entry, &Result, false);
|
|
|
|
MoveNodesToAveragePos(ExpandedNodes, FVector2D(SpawnPos.X + 500.0f, SpawnPos.Y));
|
|
|
|
if (Entry)
|
|
{
|
|
Entry->DestroyNode();
|
|
}
|
|
|
|
if (Result)
|
|
{
|
|
Result->DestroyNode();
|
|
}
|
|
|
|
// Connect any pins that need to be set from the old function
|
|
if (NewEventNode)
|
|
{
|
|
// Link the nodes from the original function entry node to the new event node
|
|
FEdGraphUtilities::ReconnectPinMap(NewEventNode, PinConnections);
|
|
FEdGraphUtilities::CopyPinDefaults(SelectedCallFunctionNode, NewEventNode);
|
|
}
|
|
|
|
// Remove the old function graph
|
|
FBlueprintEditorUtils::RemoveGraph(NodeBP, FunctionGraph, EGraphRemoveFlags::Recompile);
|
|
FunctionGraph->MarkAsGarbage();
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(NodeBP);
|
|
|
|
// Do this AFTER removing the function graph so that it's not opened into the existing function graph document tab.
|
|
if (NewEventNode)
|
|
{
|
|
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(NewEventNode, false);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanConvertEventToFunction() const
|
|
{
|
|
// Only allow when only a _single_ event node is selected, not allowed on the anim graph
|
|
UEdGraphNode* SelectedNode = GetSingleSelectedNode();
|
|
if (SelectedNode && SelectedNode->IsA<UK2Node_Event>())
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FBlueprintEditor::ConvertEventIfValid(UK2Node_Event* EventToConv)
|
|
{
|
|
FCompilerResultsLog LogResults;
|
|
FMessageLog MessageLog("BlueprintLog");
|
|
FText SpecificErrorMessage;
|
|
|
|
if(EventToConv)
|
|
{
|
|
UFunction* const Func = FFunctionFromNodeHelper::FunctionFromNode(EventToConv);
|
|
|
|
// Ensure we are not trying to do this on an interface event
|
|
if (FBlueprintEditorUtils::IsInterfaceFunction(GetBlueprintObj(), Func) || EventToConv->IsInterfaceEventNode())
|
|
{
|
|
LogResults.Error(*LOCTEXT("EventIsOnInterface_Error", "Only non-interface events can be converted to functions!").ToString());
|
|
SpecificErrorMessage = LOCTEXT("EventIsOnInterface_Error_Title", "Cannot convert interface events");
|
|
}
|
|
// Make sure that we are not on the animation graph
|
|
else if (IsEditingAnimGraph())
|
|
{
|
|
LogResults.Error(*LOCTEXT("ConvertAnimGraphEvent_Error", "Cannot convert events on the anim graph!").ToString());
|
|
SpecificErrorMessage = LOCTEXT("ConvertAnimGraphEvent_Error_Title", "Cannot convert on the anim graph");
|
|
}
|
|
else
|
|
{
|
|
ConvertEventToFunction(EventToConv);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
SpecificErrorMessage = LOCTEXT("MultipleNodesSelectred_Error_Title", "Only one node can be selected for conversion");
|
|
LogResults.Error(*LOCTEXT("MultipleNodesSelectred_Error", "Only one node can be selected for conversion!").ToString());
|
|
}
|
|
|
|
// Show the log results if there were any errors
|
|
if (LogResults.NumErrors)
|
|
{
|
|
MessageLog.NewPage(LOCTEXT("OnConvertEventToFunctionLabel", "Convert Event to Function"));
|
|
MessageLog.AddMessages(LogResults.Messages);
|
|
|
|
// Format the title node
|
|
FFormatNamedArguments Arguments;
|
|
Arguments.Add(TEXT("SpecificErrorMessage"), SpecificErrorMessage);
|
|
MessageLog.Notify(FText::Format(LOCTEXT("OnConvertEventToFunctionErrorMsg", "Convert Event to Function Failed!\n{SpecificErrorMessage}"), Arguments));
|
|
}
|
|
|
|
// If there are no errors, we succeed and should return true
|
|
return LogResults.NumErrors == 0;
|
|
}
|
|
|
|
void FBlueprintEditor::OnConvertEventToFunction()
|
|
{
|
|
UEdGraphNode* SelectedNode = GetSingleSelectedNode();
|
|
|
|
if (UK2Node_Event* SelectedEventNode = Cast<UK2Node_Event>(SelectedNode))
|
|
{
|
|
ConvertEventIfValid(SelectedEventNode);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::ConvertEventToFunction(UK2Node_Event* SelectedEventNode)
|
|
{
|
|
if (SelectedEventNode)
|
|
{
|
|
const FScopedTransaction Transaction(FGraphEditorCommands::Get().ConvertEventToFunction->GetLabel());
|
|
|
|
UBlueprint* const NodeBP = GetBlueprintObj();
|
|
NodeBP->Modify();
|
|
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
// Refresh this node before getting the pin names to ensure that we get the most up to date ones
|
|
Schema->ReconstructNode(*SelectedEventNode);
|
|
|
|
// Keep track of the old connections from the event node
|
|
TMap<FString, TSet<UEdGraphPin*>> PinConnections;
|
|
FEdGraphUtilities::GetPinConnectionMap(SelectedEventNode, PinConnections);
|
|
|
|
TArray<UEdGraphNode*> CollapsableNodes = GetAllConnectedNodes(SelectedEventNode);
|
|
|
|
UFunction* const FunctionSig = FFunctionFromNodeHelper::FunctionFromNode(SelectedEventNode);
|
|
UEdGraph* const SourceGraph = SelectedEventNode->GetGraph();
|
|
const FName OriginalEventName = SelectedEventNode->GetFunctionName();
|
|
|
|
if (FunctionSig && SourceGraph)
|
|
{
|
|
SourceGraph->Modify();
|
|
|
|
// Check if this is an override function
|
|
UFunction* ParentFunction = FindUField<UFunction>(NodeBP->ParentClass, OriginalEventName);
|
|
const bool bIsOverrideFunc = FunctionSig->GetSuperFunction() || (ParentFunction != nullptr);
|
|
UClass* const OverrideFuncClass = FBlueprintEditorUtils::GetOverrideFunctionClass(NodeBP, OriginalEventName);
|
|
|
|
// Create a new function graph
|
|
// We have to start it out with a temporary graph name because otherwise the flags will get renamed uniquely
|
|
UEdGraph* NewGraph = nullptr;
|
|
static const FString TempFuncGraph = "TEMP_FUNC_GRAPH";
|
|
const FName TempFuncGraphName = FBlueprintEditorUtils::GenerateUniqueGraphName(NodeBP, TempFuncGraph);
|
|
NewGraph = FBlueprintEditorUtils::CreateNewGraph(NodeBP, TempFuncGraphName, SourceGraph->GetClass(), SourceGraph->GetSchema() ? SourceGraph->GetSchema()->GetClass() : Schema->GetClass());
|
|
|
|
if (bIsOverrideFunc)
|
|
{
|
|
FBlueprintEditorUtils::AddFunctionGraph<UClass>(NodeBP, NewGraph, /*bIsUserCreated=*/ false, OverrideFuncClass);
|
|
}
|
|
else
|
|
{
|
|
FBlueprintEditorUtils::AddFunctionGraph<UFunction>(NodeBP, NewGraph, /*bIsUserCreated=*/ true, FunctionSig);
|
|
}
|
|
|
|
// Get the new entry node
|
|
TArray<UK2Node_FunctionEntry*> EntryNodes;
|
|
NewGraph->GetNodesOfClass<UK2Node_FunctionEntry>(EntryNodes);
|
|
check(EntryNodes.Num() > 0);
|
|
UK2Node_FunctionEntry* NewEntryNode = EntryNodes[0];
|
|
|
|
// Reconstruct the pins and add a proper function reference to the parent function
|
|
if (bIsOverrideFunc && NewEntryNode)
|
|
{
|
|
FGuid GraphGuid;
|
|
FBlueprintEditorUtils::GetFunctionGuidFromClassByFieldName(FBlueprintEditorUtils::GetMostUpToDateClass(OverrideFuncClass), OriginalEventName, /* Out */GraphGuid);
|
|
|
|
// Set the external members of this function appropriately
|
|
NewEntryNode->FunctionReference.SetExternalMember(OriginalEventName, SelectedEventNode->EventReference.GetMemberParentClass(), GraphGuid);
|
|
Schema->ReconstructNode(*NewEntryNode);
|
|
}
|
|
|
|
NewGraph->Modify();
|
|
|
|
UEdGraphNode* InvalidNode = nullptr;
|
|
FCompilerResultsLog LogResults;
|
|
|
|
// Check to make sure that this node can actually be put in event graph
|
|
for (UEdGraphNode* const Node : CollapsableNodes)
|
|
{
|
|
if (Node && (!Schema->CanEncapuslateNode(*Node) || !Node->CanPasteHere(NewGraph)))
|
|
{
|
|
// Tell the user why we can't send to function graph
|
|
LogResults.Error(*LOCTEXT("InvalidNode_Error", "@@ cannot be placed in function graph").ToString(), Node);
|
|
InvalidNode = Node;
|
|
}
|
|
}
|
|
|
|
// If this is invalid, then cancel this operation and focus on the node that is invalid
|
|
if (InvalidNode != nullptr)
|
|
{
|
|
FMessageLog MessageLog("BlueprintLog");
|
|
MessageLog.NewPage(LOCTEXT("OnConvertEventToFunctionLabel", "Convert Event to Function"));
|
|
MessageLog.AddMessages(LogResults.Messages);
|
|
MessageLog.Notify(LOCTEXT("OnConvertEventToFunctionError", "Convert Event to Function Failed!"));
|
|
|
|
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(InvalidNode, false);
|
|
|
|
// Get rid of the function graph
|
|
FBlueprintEditorUtils::RemoveGraph(NodeBP, NewGraph);
|
|
NewGraph->MarkAsGarbage();
|
|
return;
|
|
}
|
|
|
|
// Remove any return nodes that may have been added @see UE-78785
|
|
TArray<UK2Node_FunctionResult*> FunctionReturnNodes;
|
|
NewGraph->GetNodesOfClass(FunctionReturnNodes);
|
|
for (UK2Node_FunctionResult* Node : FunctionReturnNodes)
|
|
{
|
|
if (Node)
|
|
{
|
|
Node->DestroyNode();
|
|
}
|
|
}
|
|
|
|
// Break any connections to the delegate pin, because that won't exist
|
|
{
|
|
UEdGraphPin* DelegatePin = SelectedEventNode->FindPin(UK2Node_Event::DelegateOutputName);
|
|
if (DelegatePin)
|
|
{
|
|
DelegatePin->BreakAllPinLinks();
|
|
}
|
|
}
|
|
|
|
// Move the nodes to the new graph
|
|
UEdGraphNode* Entry = nullptr;
|
|
UEdGraphNode* Result = nullptr;
|
|
TSet<UEdGraphNode*> ExpandedNodes;
|
|
MoveNodesToGraph(CollapsableNodes, NewGraph, ExpandedNodes, &Entry, &Result);
|
|
|
|
// Link the new nodes accordingly
|
|
if (NewEntryNode)
|
|
{
|
|
FEdGraphUtilities::ReconnectPinMap(NewEntryNode, PinConnections);
|
|
FEdGraphUtilities::CopyPinDefaults(SelectedEventNode, NewEntryNode);
|
|
|
|
MoveNodesToAveragePos(ExpandedNodes, FVector2D(NewEntryNode->NodePosX + 500.0f, NewEntryNode->NodePosY));
|
|
}
|
|
else
|
|
{
|
|
MoveNodesToAveragePos(ExpandedNodes, NewGraph->GetGoodPlaceForNewNode());
|
|
}
|
|
|
|
FBlueprintEditorUtils::UpdateTransactionalFlags(NodeBP);
|
|
|
|
FVector2D NewFuncCallSpawn( SelectedEventNode->NodePosX, SelectedEventNode->NodePosY );
|
|
|
|
FBlueprintEditorUtils::RemoveNode(NodeBP, SelectedEventNode, /* bDontRecompile= */ true);
|
|
|
|
// Cache the string conversion of the event name
|
|
const FString OriginalEventNameString = OriginalEventName.ToString();
|
|
|
|
// If there is already another object in the same scope with this name, rename it.
|
|
UObject* ExistingObject = StaticFindObject(/*Class=*/ nullptr, NodeBP, *OriginalEventNameString, true);
|
|
if (ExistingObject)
|
|
{
|
|
check(ExistingObject->GetOuter() == NewGraph->GetOuter());
|
|
|
|
ExistingObject->Rename(nullptr, nullptr, REN_DontCreateRedirectors);
|
|
}
|
|
|
|
// Rename the function graph to the original name
|
|
FBlueprintEditorUtils::RenameGraph(NewGraph, *OriginalEventNameString);
|
|
|
|
// If this function is blueprint callable, then spawn a function call node to it in place of the old event
|
|
UFunction* const NewFunction = FindUField<UFunction>(NodeBP->SkeletonGeneratedClass, OriginalEventName);
|
|
if (NewFunction && NewFunction->HasAllFunctionFlags(FUNC_BlueprintCallable))
|
|
{
|
|
IBlueprintNodeBinder::FBindingSet Bindings;
|
|
UEdGraphNode* OutFunctionNode = UBlueprintFunctionNodeSpawner::Create(NewFunction)->Invoke(SourceGraph, Bindings, NewFuncCallSpawn);
|
|
if (NewEntryNode)
|
|
{
|
|
FEdGraphUtilities::CopyPinDefaults(NewEntryNode, OutFunctionNode);
|
|
}
|
|
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(OutFunctionNode, false);
|
|
}
|
|
else if(NewEntryNode)
|
|
{
|
|
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(NewEntryNode, false);
|
|
}
|
|
}
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(NodeBP);
|
|
}
|
|
}
|
|
|
|
TArray<UEdGraphNode*> FBlueprintEditor::GetAllConnectedNodes(UEdGraphNode* const SourceNode) const
|
|
{
|
|
TArray<UEdGraphNode*> OutNodes;
|
|
|
|
if (SourceNode == nullptr)
|
|
{
|
|
return OutNodes;
|
|
}
|
|
|
|
const UEdGraphSchema_K2* Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
TQueue<UEdGraphNode*> Queue;
|
|
Queue.Enqueue(SourceNode);
|
|
|
|
TMap<int32, UEdGraphNode*> VisitedNodes;
|
|
VisitedNodes.Add(SourceNode->GetUniqueID(), SourceNode);
|
|
|
|
while (!Queue.IsEmpty())
|
|
{
|
|
UEdGraphNode* Current = nullptr;
|
|
Queue.Dequeue(Current);
|
|
|
|
if (Current != nullptr)
|
|
{
|
|
// Don't add the original node in the list
|
|
if (Current != SourceNode)
|
|
{
|
|
OutNodes.Add(Current);
|
|
}
|
|
|
|
// For every pin that this node has
|
|
for (UEdGraphPin* CurrentPin : Current->Pins)
|
|
{
|
|
// Look at what pins are connected to that
|
|
for (UEdGraphPin* LinkedPin : CurrentPin->LinkedTo)
|
|
{
|
|
UEdGraphNode* OwningNode = LinkedPin->GetOwningNode();
|
|
|
|
// If we can encapsulate the owner and it has not been visited
|
|
if (OwningNode && !VisitedNodes.Contains(OwningNode->GetUniqueID()))
|
|
{
|
|
// Mark as visited and add to the queue
|
|
VisitedNodes.Add(OwningNode->GetUniqueID(), OwningNode);
|
|
Queue.Enqueue(OwningNode);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return OutNodes;
|
|
}
|
|
|
|
void FBlueprintEditor::OnAlignTop()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnAlignTop();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnAlignMiddle()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnAlignMiddle();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnAlignBottom()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnAlignBottom();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnAlignLeft()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnAlignLeft();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnAlignCenter()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnAlignCenter();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnAlignRight()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnAlignRight();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnStraightenConnections()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnStraightenConnections();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnDistributeNodesH()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnDistributeNodesH();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnDistributeNodesV()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnDistributeNodesV();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnStackNodesH()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnStackNodesH();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnStackNodesV()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->OnStackNodesV();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SelectAllNodes()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->SelectAllNodes();
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanSelectAllNodes() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
void FBlueprintEditor::DeleteSelectedNodes()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const FScopedTransaction Transaction( FGenericCommands::Get().Delete->GetDescription() );
|
|
FocusedGraphEd->GetCurrentGraph()->Modify();
|
|
|
|
bool bNeedToModifyStructurally = false;
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
SetUISelectionState(NAME_None);
|
|
|
|
if(FocusedGraphEd)
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
}
|
|
|
|
// Some nodes have sub-objects that are represented as other tabs.
|
|
// Close them here as a pre-pass before we remove their nodes. If the documents are left open they
|
|
// may reference dangling data and function incorrectly in cases such as FindBlueprintforNodeChecked
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt( SelectedNodes ); NodeIt; ++NodeIt)
|
|
{
|
|
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*NodeIt))
|
|
{
|
|
if (Node->CanUserDeleteNode())
|
|
{
|
|
auto CloseAllDocumentsTab = [this](const UEdGraphNode* InNode)
|
|
{
|
|
TArray<UObject*> NodesToClose;
|
|
GetObjectsWithOuter(InNode, NodesToClose);
|
|
for (UObject* Node : NodesToClose)
|
|
{
|
|
UEdGraph* NodeGraph = Cast<UEdGraph>(Node);
|
|
if (NodeGraph)
|
|
{
|
|
CloseDocumentTab(NodeGraph);
|
|
}
|
|
}
|
|
};
|
|
|
|
|
|
if (Node->GetSubGraphs().Num() > 0)
|
|
{
|
|
CloseAllDocumentsTab(Node);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// Now remove the selected nodes
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt( SelectedNodes ); NodeIt; ++NodeIt)
|
|
{
|
|
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*NodeIt))
|
|
{
|
|
if (Node->CanUserDeleteNode())
|
|
{
|
|
UK2Node* K2Node = Cast<UK2Node>(Node);
|
|
if (K2Node != nullptr && K2Node->NodeCausesStructuralBlueprintChange())
|
|
{
|
|
bNeedToModifyStructurally = true;
|
|
}
|
|
|
|
if (Node->GetSubGraphs().Num() > 0)
|
|
{
|
|
DocumentManager->CleanInvalidTabs();
|
|
}
|
|
else if (UK2Node_Timeline* TimelineNode = Cast<UK2Node_Timeline>(*NodeIt))
|
|
{
|
|
DocumentManager->CleanInvalidTabs();
|
|
}
|
|
AnalyticsTrackNodeEvent( GetBlueprintObj(), Node, true );
|
|
|
|
// If the user is pressing shift then try and reconnect the pins
|
|
if (FSlateApplication::Get().GetModifierKeys().IsShiftDown())
|
|
{
|
|
ReconnectExecPins(K2Node);
|
|
}
|
|
|
|
FBlueprintEditorUtils::RemoveNode(GetBlueprintObj(), Node, true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bNeedToModifyStructurally)
|
|
{
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj());
|
|
}
|
|
else
|
|
{
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprintObj());
|
|
}
|
|
|
|
//@TODO: Reselect items that were not deleted
|
|
}
|
|
|
|
void FBlueprintEditor::ReconnectExecPins(UK2Node* Node)
|
|
{
|
|
if(!Node)
|
|
{
|
|
return;
|
|
}
|
|
|
|
UEdGraphPin* ExecPin = nullptr;
|
|
UEdGraphPin* ThenPin = nullptr;
|
|
|
|
// Get pins for knot nodes or impure nodes only
|
|
if(UK2Node_Knot* KnotNode = Cast<UK2Node_Knot>(Node))
|
|
{
|
|
ExecPin = KnotNode->GetInputPin();
|
|
ThenPin = KnotNode->GetOutputPin();
|
|
}
|
|
else if(!Node->IsNodePure())
|
|
{
|
|
ExecPin = Node->GetExecPin();
|
|
|
|
// Nodes with multiple "then" pins (branch, sequence, foreach, etc) will actually have the FindPin return nullptr
|
|
ThenPin = Node->FindPin(UEdGraphSchema_K2::PN_Then);
|
|
}
|
|
|
|
// We don't want to try and auto connect nodes with multiple outputs,
|
|
// because it's likely the user will not want those connections anyway
|
|
if (ExecPin && ThenPin)
|
|
{
|
|
// Make a connection from every incoming exec pin to every outgoing then pin
|
|
for (UEdGraphPin* const IncomingConnectionPin : ExecPin->LinkedTo)
|
|
{
|
|
if (IncomingConnectionPin)
|
|
{
|
|
for (UEdGraphPin* const ConnectedCompletePin : ThenPin->LinkedTo)
|
|
{
|
|
IncomingConnectionPin->MakeLinkTo(ConnectedCompletePin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanDeleteNodes() const
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
bool bCanUserDeleteNode = false;
|
|
|
|
if(IsEditable(GetFocusedGraph()) && SelectedNodes.Num() > 0)
|
|
{
|
|
for( UObject* NodeObject : SelectedNodes )
|
|
{
|
|
// If any nodes allow deleting, then do not disable the delete option
|
|
UEdGraphNode* Node = Cast<UEdGraphNode>(NodeObject);
|
|
if(Node->CanUserDeleteNode())
|
|
{
|
|
bCanUserDeleteNode = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bCanUserDeleteNode;
|
|
}
|
|
|
|
void FBlueprintEditor::DeleteSelectedDuplicatableNodes()
|
|
{
|
|
// Cache off the old selection
|
|
const FGraphPanelSelectionSet OldSelectedNodes = GetSelectedNodes();
|
|
|
|
// Clear the selection and only select the nodes that can be duplicated
|
|
FGraphPanelSelectionSet CurrentSelection;
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
|
|
FGraphPanelSelectionSet RemainingNodes;
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
|
|
if ((Node != nullptr) && Node->CanDuplicateNode())
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(Node, true);
|
|
}
|
|
else
|
|
{
|
|
RemainingNodes.Add(Node);
|
|
}
|
|
}
|
|
|
|
// Delete the duplicatable nodes
|
|
DeleteSelectedNodes();
|
|
|
|
// Reselect whatever's left from the original selection after the deletion
|
|
FocusedGraphEd->ClearSelectionSet();
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(RemainingNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter))
|
|
{
|
|
FocusedGraphEd->SetNodeSelection(Node, true);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::CutSelectedNodes()
|
|
{
|
|
CopySelectedNodes();
|
|
// Cut should only delete nodes that can be duplicated
|
|
DeleteSelectedDuplicatableNodes();
|
|
}
|
|
|
|
bool FBlueprintEditor::CanCutNodes() const
|
|
{
|
|
return CanCopyNodes() && CanDeleteNodes();
|
|
}
|
|
|
|
void FBlueprintEditor::CopySelectedNodes()
|
|
{
|
|
// Export the selected nodes and place the text on the clipboard
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
FString ExportedText;
|
|
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
if(UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter))
|
|
{
|
|
Node->PrepareForCopying();
|
|
}
|
|
}
|
|
|
|
FEdGraphUtilities::ExportNodesToText(SelectedNodes, /*out*/ ExportedText);
|
|
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
|
|
}
|
|
|
|
bool FBlueprintEditor::CanCopyNodes() const
|
|
{
|
|
// If any of the nodes can be duplicated then we should allow copying
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
|
|
{
|
|
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
|
|
if ((Node != nullptr) && Node->CanDuplicateNode())
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::PasteNodes()
|
|
{
|
|
// Find the graph editor with focus
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
PasteNodesHere(FocusedGraphEd->GetCurrentGraph(), FocusedGraphEd->GetPasteLocation2f());
|
|
|
|
// Dump any temporary pre-compile warnings to the compiler log.
|
|
UBlueprint* BlueprintObj = GetBlueprintObj();
|
|
if (BlueprintObj->PreCompileLog.IsValid())
|
|
{
|
|
DumpMessagesToCompilerLog(BlueprintObj->PreCompileLog->Messages, true);
|
|
}
|
|
}
|
|
|
|
|
|
/**
|
|
* When copying and pasting functions from the LSBP operating on an instance to a class BP,
|
|
* we should automatically transfer the functions from actors to the components
|
|
*/
|
|
struct FUpdatePastedNodes
|
|
{
|
|
TSet<UK2Node_VariableGet*> AddedTargets;
|
|
TSet<UK2Node_CallFunction*> AddedFunctions;
|
|
TSet<UK2Node_Literal*> ReplacedTargets;
|
|
TSet<UK2Node_CallFunctionOnMember*> ReplacedFunctions;
|
|
|
|
UClass* CurrentClass;
|
|
UEdGraph* Graph;
|
|
TSet<UEdGraphNode*>& PastedNodes;
|
|
const UEdGraphSchema_K2* K2Schema;
|
|
|
|
FUpdatePastedNodes(UClass* InCurrentClass, TSet<UEdGraphNode*>& InPastedNodes, UEdGraph* InDestinationGraph)
|
|
: CurrentClass(InCurrentClass)
|
|
, Graph(InDestinationGraph)
|
|
, PastedNodes(InPastedNodes)
|
|
, K2Schema(GetDefault<UEdGraphSchema_K2>())
|
|
{
|
|
check(InCurrentClass && InDestinationGraph && K2Schema);
|
|
}
|
|
|
|
/**
|
|
* Replace UK2Node_CallFunctionOnMember called on actor with a UK2Node_CallFunction.
|
|
* When the blueprint has the member.
|
|
*/
|
|
void ReplaceAll()
|
|
{
|
|
for(UEdGraphNode* PastedNode : PastedNodes)
|
|
{
|
|
if (UK2Node_CallFunctionOnMember* CallOnMember = Cast<UK2Node_CallFunctionOnMember>(PastedNode))
|
|
{
|
|
if (UEdGraphPin* TargetInPin = CallOnMember->FindPin(UEdGraphSchema_K2::PN_Self))
|
|
{
|
|
const UClass* TargetClass = Cast<const UClass>(TargetInPin->PinType.PinSubCategoryObject.Get());
|
|
|
|
const bool bTargetIsNullOrSingleLinked = (TargetInPin->LinkedTo.Num() == 1) ||
|
|
(!TargetInPin->LinkedTo.Num() && !TargetInPin->DefaultObject);
|
|
|
|
const bool bCanCurrentBlueprintReplace = TargetClass
|
|
&& CurrentClass->IsChildOf(TargetClass) // If current class if of the same type, it has the called member
|
|
&& (!CallOnMember->MemberVariableToCallOn.IsSelfContext() && (TargetClass != CurrentClass)) // Make sure the class isn't self, using a explicit check in case the class hasn't been compiled since the member was added
|
|
&& bTargetIsNullOrSingleLinked;
|
|
|
|
if (bCanCurrentBlueprintReplace)
|
|
{
|
|
UEdGraphNode* TargetNode = TargetInPin->LinkedTo.Num() ? TargetInPin->LinkedTo[0]->GetOwningNode() : nullptr;
|
|
UK2Node_Literal* TargetLiteralNode = Cast<UK2Node_Literal>(TargetNode);
|
|
|
|
const bool bPastedNodeShouldBeReplacedWithTarget = TargetLiteralNode
|
|
&& !TargetLiteralNode->GetObjectRef() //The node delivering target actor is invalid
|
|
&& PastedNodes.Contains(TargetLiteralNode);
|
|
const bool bPastedNodeShouldBeReplacedWithoutTarget = !TargetNode || !PastedNodes.Contains(TargetNode);
|
|
|
|
if (bPastedNodeShouldBeReplacedWithTarget || bPastedNodeShouldBeReplacedWithoutTarget)
|
|
{
|
|
Replace(TargetLiteralNode, CallOnMember);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
UpdatePastedCollection();
|
|
}
|
|
|
|
private:
|
|
|
|
void UpdatePastedCollection()
|
|
{
|
|
for (UK2Node_Literal* ReplacedTarget : ReplacedTargets)
|
|
{
|
|
if (ReplacedTarget && ReplacedTarget->GetValuePin() && !ReplacedTarget->GetValuePin()->LinkedTo.Num())
|
|
{
|
|
PastedNodes.Remove(ReplacedTarget);
|
|
Graph->RemoveNode(ReplacedTarget);
|
|
}
|
|
}
|
|
for (UK2Node_CallFunctionOnMember* ReplacedFunction : ReplacedFunctions)
|
|
{
|
|
PastedNodes.Remove(ReplacedFunction);
|
|
Graph->RemoveNode(ReplacedFunction);
|
|
}
|
|
for (UK2Node_VariableGet* AddedTarget : AddedTargets)
|
|
{
|
|
PastedNodes.Add(AddedTarget);
|
|
}
|
|
for (UK2Node_CallFunction* AddedFunction : AddedFunctions)
|
|
{
|
|
PastedNodes.Add(AddedFunction);
|
|
}
|
|
}
|
|
|
|
bool MoveAllLinksExeptSelf(UK2Node* NewNode, UK2Node* OldNode)
|
|
{
|
|
bool bResult = true;
|
|
for(UEdGraphPin* OldPin : OldNode->Pins)
|
|
{
|
|
if(OldPin && (OldPin->PinName != UEdGraphSchema_K2::PN_Self))
|
|
{
|
|
UEdGraphPin* NewPin = NewNode->FindPin(OldPin->PinName);
|
|
if (NewPin)
|
|
{
|
|
if (!K2Schema->MovePinLinks(*OldPin, *NewPin).CanSafeConnect())
|
|
{
|
|
UE_LOG(LogBlueprint, Error, TEXT("FUpdatePastedNodes: Cannot connect pin '%s' node '%s'"),
|
|
*OldPin->PinName.ToString(), *OldNode->GetName());
|
|
bResult = false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogBlueprint, Error, TEXT("FUpdatePastedNodes: Cannot find pin '%s'"), *OldPin->PinName.ToString());
|
|
bResult = false;
|
|
}
|
|
}
|
|
}
|
|
return bResult;
|
|
}
|
|
|
|
void InitializeNewNode(UK2Node* NewNode, UK2Node* OldNode, int32 NodePosX = 0, int32 NodePosY = 0)
|
|
{
|
|
NewNode->NodePosX = OldNode ? OldNode->NodePosX : NodePosX;
|
|
NewNode->NodePosY = OldNode ? OldNode->NodePosY : NodePosY;
|
|
NewNode->SetFlags(RF_Transactional);
|
|
Graph->AddNode(NewNode, false, false);
|
|
NewNode->PostPlacedNewNode();
|
|
NewNode->AllocateDefaultPins();
|
|
}
|
|
|
|
bool Replace(UK2Node_Literal* OldTarget, UK2Node_CallFunctionOnMember* OldCall)
|
|
{
|
|
bool bResult = true;
|
|
check(OldCall);
|
|
|
|
UK2Node_VariableGet* NewTarget = nullptr;
|
|
|
|
const FProperty* Property = OldCall->MemberVariableToCallOn.ResolveMember<FProperty>();
|
|
for (UK2Node_VariableGet* AddedTarget : AddedTargets)
|
|
{
|
|
if (AddedTarget && (Property == AddedTarget->VariableReference.ResolveMember<FProperty>(CurrentClass)))
|
|
{
|
|
NewTarget = AddedTarget;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!NewTarget)
|
|
{
|
|
NewTarget = NewObject<UK2Node_VariableGet>(Graph);
|
|
check(NewTarget);
|
|
NewTarget->SetFromProperty(Property, true, Property->GetOwnerClass());
|
|
AddedTargets.Add(NewTarget);
|
|
const int32 AutoNodeOffsetX = 160;
|
|
InitializeNewNode(NewTarget, OldTarget, OldCall->NodePosX - AutoNodeOffsetX, OldCall->NodePosY);
|
|
}
|
|
|
|
if (OldTarget)
|
|
{
|
|
ReplacedTargets.Add(OldTarget);
|
|
}
|
|
|
|
UK2Node_CallFunction* NewCall = NewObject<UK2Node_CallFunction>(Graph);
|
|
check(NewCall);
|
|
NewCall->SetFromFunction(OldCall->GetTargetFunction());
|
|
InitializeNewNode(NewCall, OldCall);
|
|
AddedFunctions.Add(NewCall);
|
|
|
|
if (!MoveAllLinksExeptSelf(NewCall, OldCall))
|
|
{
|
|
bResult = false;
|
|
}
|
|
|
|
if (NewTarget)
|
|
{
|
|
UEdGraphPin* SelfPin = NewCall->FindPinChecked(UEdGraphSchema_K2::PN_Self);
|
|
if (!K2Schema->TryCreateConnection(SelfPin, NewTarget->GetValuePin()))
|
|
{
|
|
UE_LOG(LogBlueprint, Error, TEXT("FUpdatePastedNodes: Cannot connect new self."));
|
|
bResult = false;
|
|
}
|
|
}
|
|
|
|
OldCall->BreakAllNodeLinks();
|
|
|
|
ReplacedFunctions.Add(OldCall);
|
|
return bResult;
|
|
}
|
|
};
|
|
|
|
void FBlueprintEditor::PasteNodesHere(class UEdGraph* DestinationGraph, const FVector2f& GraphLocation)
|
|
{
|
|
// Find the graph editor with focus
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
// Select the newly pasted stuff
|
|
bool bNeedToModifyStructurally = false;
|
|
{
|
|
const FScopedTransaction Transaction(FGenericCommands::Get().Paste->GetDescription());
|
|
DestinationGraph->Modify();
|
|
|
|
// Clear the selection set (newly pasted stuff will be selected)
|
|
SetUISelectionState(NAME_None);
|
|
|
|
// Grab the text to paste from the clipboard.
|
|
FString TextToImport;
|
|
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
|
|
|
|
// Import the nodes
|
|
TSet<UEdGraphNode*> PastedNodes;
|
|
FEdGraphUtilities::ImportNodesFromText(DestinationGraph, TextToImport, /*out*/ PastedNodes);
|
|
|
|
// Only do this step if we can create functions on the blueprint (i.e. not macro graphs, etc)
|
|
if (NewDocument_IsVisibleForType(CGT_NewFunctionGraph))
|
|
{
|
|
// Spawn Deferred Fixup Modal window if necessary
|
|
TArray<UK2Node_CallFunction*> FixupNodes;
|
|
for (UEdGraphNode* PastedNode : PastedNodes)
|
|
{
|
|
if (UK2Node_CallFunction* Node = Cast<UK2Node_CallFunction>(PastedNode))
|
|
{
|
|
if (Node->FunctionReference.IsSelfContext() && !Node->GetTargetFunction())
|
|
{
|
|
FixupNodes.Add(Node);
|
|
}
|
|
}
|
|
}
|
|
if (FixupNodes.Num() > 0)
|
|
{
|
|
if (!SFixupSelfContextDialog::CreateModal(FixupNodes, Cast<UBlueprint>(DestinationGraph->GetOuter()), this, FixupNodes.Num() != PastedNodes.Num()))
|
|
{
|
|
for (UEdGraphNode* Node : PastedNodes)
|
|
{
|
|
DestinationGraph->RemoveNode(Node);
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Update Paste Analytics
|
|
AnalyticsStats.NodePasteCreateCount += PastedNodes.Num();
|
|
|
|
{
|
|
UBlueprint* Blueprint = FBlueprintEditorUtils::FindBlueprintForGraph(DestinationGraph);
|
|
UClass* CurrentClass = Blueprint ? Blueprint->GeneratedClass : nullptr;
|
|
if (CurrentClass)
|
|
{
|
|
FUpdatePastedNodes ReplaceNodes(CurrentClass, PastedNodes, DestinationGraph);
|
|
ReplaceNodes.ReplaceAll();
|
|
}
|
|
}
|
|
|
|
//Average position of nodes so we can move them while still maintaining relative distances to each other
|
|
FVector2f AvgNodePosition(0.0f, 0.0f);
|
|
|
|
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
|
|
{
|
|
UEdGraphNode* Node = *It;
|
|
AvgNodePosition.X += Node->NodePosX;
|
|
AvgNodePosition.Y += Node->NodePosY;
|
|
}
|
|
|
|
float InvNumNodes = 1.0f / float(PastedNodes.Num());
|
|
AvgNodePosition.X *= InvNumNodes;
|
|
AvgNodePosition.Y *= InvNumNodes;
|
|
|
|
TSet<FString> NamespacesToImport;
|
|
|
|
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
|
|
{
|
|
UEdGraphNode* Node = *It;
|
|
FocusedGraphEd->SetNodeSelection(Node, true);
|
|
|
|
Node->NodePosX = static_cast<int32>((Node->NodePosX - AvgNodePosition.X) + GraphLocation.X);
|
|
Node->NodePosY = static_cast<int32>((Node->NodePosY - AvgNodePosition.Y) + GraphLocation.Y);
|
|
|
|
Node->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
|
|
// Give new node a different Guid from the old one
|
|
Node->CreateNewGuid();
|
|
|
|
// Collect any required imports from node dependencies
|
|
TArray<UStruct*> ExternalDependencies;
|
|
if (Node->HasExternalDependencies(&ExternalDependencies))
|
|
{
|
|
for (const UStruct* ExternalDependency : ExternalDependencies)
|
|
{
|
|
FBlueprintNamespaceUtilities::GetDefaultImportsForObject(ExternalDependency, DeferredNamespaceImports);
|
|
}
|
|
}
|
|
|
|
UK2Node* K2Node = Cast<UK2Node>(Node);
|
|
if ((K2Node != nullptr) && K2Node->NodeCausesStructuralBlueprintChange())
|
|
{
|
|
bNeedToModifyStructurally = true;
|
|
}
|
|
|
|
// For pasted Event nodes, we need to see if there is an already existing node in a ghost state that needs to be cleaned up
|
|
if (UK2Node_Event* EventNode = Cast<UK2Node_Event>(Node))
|
|
{
|
|
// Gather all existing event nodes
|
|
TArray<UK2Node_Event*> ExistingEventNodes;
|
|
FBlueprintEditorUtils::GetAllNodesOfClass<UK2Node_Event>(GetBlueprintObj(), ExistingEventNodes);
|
|
|
|
for (UK2Node_Event* ExistingEventNode : ExistingEventNodes)
|
|
{
|
|
check(ExistingEventNode);
|
|
|
|
const bool bIdenticalNode = (EventNode != ExistingEventNode) && ExistingEventNode->bOverrideFunction && UK2Node_Event::AreEventNodesIdentical(EventNode, ExistingEventNode);
|
|
|
|
// Check if the nodes are identical, if they are we need to delete the original because it is disabled. Identical nodes that are in an enabled state will never make it this far and still be enabled.
|
|
if (bIdenticalNode)
|
|
{
|
|
// Should not have made it to being a pasted node if the pre-existing node wasn't disabled or was otherwise explicitly disabled by the user.
|
|
ensure(ExistingEventNode->IsAutomaticallyPlacedGhostNode());
|
|
|
|
// Destroy the pre-existing node, we do not need it.
|
|
ExistingEventNode->DestroyNode();
|
|
}
|
|
}
|
|
}
|
|
// Log new node created to analytics
|
|
AnalyticsTrackNodeEvent(GetBlueprintObj(), Node, false);
|
|
}
|
|
}
|
|
|
|
if (bNeedToModifyStructurally)
|
|
{
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj());
|
|
}
|
|
else
|
|
{
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprintObj());
|
|
}
|
|
|
|
// Update UI
|
|
FocusedGraphEd->NotifyGraphChanged();
|
|
}
|
|
|
|
void FBlueprintEditor::PasteGeneric()
|
|
{
|
|
if (CanPasteNodes())
|
|
{
|
|
PasteNodes();
|
|
}
|
|
else if (MyBlueprintWidget.IsValid())
|
|
{
|
|
if (MyBlueprintWidget->CanPasteGeneric())
|
|
{
|
|
MyBlueprintWidget->OnPasteGeneric();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanPasteGeneric() const
|
|
{
|
|
if (CanPasteNodes())
|
|
{
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
return MyBlueprintWidget.IsValid() && MyBlueprintWidget->CanPasteGeneric();
|
|
}
|
|
}
|
|
|
|
|
|
bool FBlueprintEditor::CanPasteNodes() const
|
|
{
|
|
// Do not allow pasting into interface Blueprints
|
|
if (GetBlueprintObj()->BlueprintType == BPTYPE_Interface)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Find the graph editor with focus
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
FString ClipboardContent;
|
|
FPlatformApplicationMisc::ClipboardPaste(ClipboardContent);
|
|
|
|
return IsEditable(GetFocusedGraph()) && FEdGraphUtilities::CanImportNodesFromText(FocusedGraphEd->GetCurrentGraph(), ClipboardContent);
|
|
}
|
|
|
|
void FBlueprintEditor::DuplicateNodes()
|
|
{
|
|
// Copy and paste current selection
|
|
CopySelectedNodes();
|
|
PasteNodes();
|
|
}
|
|
|
|
bool FBlueprintEditor::CanDuplicateNodes() const
|
|
{
|
|
return CanCopyNodes() && IsEditable(GetFocusedGraph());
|
|
}
|
|
|
|
void FBlueprintEditor::OnAssignReferencedActor()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
USelection* SelectedActors = GEditor->GetSelectedActors();
|
|
if ( SelectedNodes.Num() > 0 && SelectedActors != nullptr && SelectedActors->Num() == 1 )
|
|
{
|
|
AActor* SelectedActor = Cast<AActor>( SelectedActors->GetSelectedObject(0) );
|
|
if ( SelectedActor != nullptr )
|
|
{
|
|
TArray<UK2Node_ActorBoundEvent*> NodesToAlter;
|
|
|
|
for ( FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt )
|
|
{
|
|
UK2Node_ActorBoundEvent* SelectedNode = Cast<UK2Node_ActorBoundEvent>(*NodeIt);
|
|
if ( SelectedNode != nullptr )
|
|
{
|
|
NodesToAlter.Add( SelectedNode );
|
|
}
|
|
}
|
|
|
|
// only create a transaction if there is a node that is affected.
|
|
if ( NodesToAlter.Num() > 0 )
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("AssignReferencedActor", "Assign referenced Actor") );
|
|
{
|
|
for ( int32 NodeIndex = 0; NodeIndex < NodesToAlter.Num(); NodeIndex++ )
|
|
{
|
|
UK2Node_ActorBoundEvent* CurrentEvent = NodesToAlter[NodeIndex];
|
|
|
|
// Store the node's current state and replace the referenced actor
|
|
CurrentEvent->Modify();
|
|
CurrentEvent->EventOwner = SelectedActor;
|
|
if (!SelectedActor->IsA(CurrentEvent->DelegateOwnerClass))
|
|
{
|
|
CurrentEvent->DelegateOwnerClass = SelectedActor->GetClass();
|
|
}
|
|
CurrentEvent->ReconstructNode();
|
|
}
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(GetBlueprintObj());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanAssignReferencedActor() const
|
|
{
|
|
bool bWouldAssignActors = false;
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
if ( SelectedNodes.Num() > 0 )
|
|
{
|
|
USelection* SelectedActors = GEditor->GetSelectedActors();
|
|
|
|
// If there is only one actor selected and at least one Blueprint graph
|
|
// node is able to receive the assignment then return true.
|
|
if ( SelectedActors != nullptr && SelectedActors->Num() == 1 )
|
|
{
|
|
AActor* SelectedActor = Cast<AActor>( SelectedActors->GetSelectedObject(0) );
|
|
if ( SelectedActor != nullptr )
|
|
{
|
|
for ( FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt )
|
|
{
|
|
UK2Node_ActorBoundEvent* SelectedNode = Cast<UK2Node_ActorBoundEvent>(*NodeIt);
|
|
if ( SelectedNode != nullptr )
|
|
{
|
|
if ( SelectedNode->EventOwner != SelectedActor )
|
|
{
|
|
bWouldAssignActors = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return bWouldAssignActors;
|
|
}
|
|
|
|
void FBlueprintEditor::OnSelectReferenceInLevel()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
TArray<AActor*> ActorsToSelect;
|
|
|
|
// Iterate over all nodes, and select referenced actors.
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UK2Node* SelectedNode = Cast<UK2Node>(*NodeIt);
|
|
AActor* ReferencedActor = (SelectedNode) ? SelectedNode->GetReferencedLevelActor() : nullptr;
|
|
|
|
if (ReferencedActor != nullptr)
|
|
{
|
|
ActorsToSelect.AddUnique(ReferencedActor);
|
|
}
|
|
}
|
|
// If we found any actors to select clear the existing selection, select them and move the camera to show them.
|
|
if( ActorsToSelect.Num() != 0 )
|
|
{
|
|
// First clear the previous selection
|
|
GEditor->GetSelectedActors()->Modify();
|
|
GEditor->SelectNone( false, true );
|
|
|
|
// Now select the actors.
|
|
for (int32 iActor = 0; iActor < ActorsToSelect.Num(); iActor++)
|
|
{
|
|
GEditor->SelectActor(ActorsToSelect[ iActor ], true, true, false);
|
|
}
|
|
|
|
// Execute the command to move camera to the object(s).
|
|
GUnrealEd->Exec_Camera( TEXT("ALIGN ACTIVEVIEWPORTONLY"),*GLog);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanSelectReferenceInLevel() const
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
bool bCanSelectActors = false;
|
|
if (SelectedNodes.Num() > 0)
|
|
{
|
|
// Iterate over all nodes, testing if they're pointing to actors.
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UK2Node* SelectedNode = Cast<UK2Node>(*NodeIt);
|
|
const AActor* ReferencedActor = (SelectedNode) ? SelectedNode->GetReferencedLevelActor() : nullptr;
|
|
|
|
bCanSelectActors = (ReferencedActor != nullptr);
|
|
if (ReferencedActor == nullptr)
|
|
{
|
|
// Bail early if the selected node isn't referencing an actor
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
return bCanSelectActors;
|
|
}
|
|
|
|
// Utility helper to get the currently hovered pin in the currently visible graph, or nullptr if there isn't one
|
|
UEdGraphPin* FBlueprintEditor::GetCurrentlySelectedPin() const
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
return FocusedGraphEd->GetGraphPinForMenu();
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
void FBlueprintEditor::SetDetailsCustomization(TSharedPtr<FDetailsViewObjectFilter> DetailsObjectFilter, TSharedPtr<IDetailRootObjectCustomization> DetailsRootCustomization)
|
|
{
|
|
if (Inspector.IsValid())
|
|
{
|
|
if (TSharedPtr<IDetailsView> DetailsView = Inspector->GetPropertyView())
|
|
{
|
|
DetailsView->SetObjectFilter(DetailsObjectFilter);
|
|
DetailsView->SetRootObjectCustomizationInstance(DetailsRootCustomization);
|
|
DetailsView->ForceRefresh();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SetSubobjectEditorUICustomization(TSharedPtr<ISCSEditorUICustomization> SCSEditorUICustomization)
|
|
{
|
|
if(SubobjectEditor.IsValid())
|
|
{
|
|
SubobjectEditor->SetUICustomization(SCSEditorUICustomization);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::RegisterSCSEditorCustomization(const FName& InComponentName, TSharedPtr<ISCSEditorCustomization> InCustomization)
|
|
{
|
|
SubobjectEditorCustomizations.Add(InComponentName, InCustomization);
|
|
}
|
|
|
|
void FBlueprintEditor::UnregisterSCSEditorCustomization(const FName& InComponentName)
|
|
{
|
|
SubobjectEditorCustomizations.Remove(InComponentName);
|
|
}
|
|
|
|
void FBlueprintEditor::CreateMergeToolTab()
|
|
{
|
|
MergeTool = IMerge::Get().GenerateMergeWidget(*GetBlueprintObj(), SharedThis(this));
|
|
}
|
|
|
|
void FBlueprintEditor::CreateMergeToolTab(const UBlueprint* BaseBlueprint, const UBlueprint* RemoteBlueprint, const FOnMergeResolved& ResolutionCallback)
|
|
{
|
|
OnMergeResolved = ResolutionCallback;
|
|
MergeTool = IMerge::Get().GenerateMergeWidget(BaseBlueprint, RemoteBlueprint, GetBlueprintObj(), ResolutionCallback, SharedThis(this));
|
|
}
|
|
|
|
void FBlueprintEditor::CloseMergeTool()
|
|
{
|
|
TSharedPtr<SDockTab> MergeToolPtr = MergeTool.Pin();
|
|
if( MergeToolPtr.IsValid() )
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
UPackage* BpPackage = (Blueprint == nullptr) ? nullptr : Blueprint->GetOutermost();
|
|
// @TODO: right now crashes the editor on closing of the BP editor
|
|
//OnMergeResolved.ExecuteIfBound(BpPackage, EMergeResult::Unknown);
|
|
OnMergeResolved.Unbind();
|
|
|
|
MergeToolPtr->RequestCloseTab();
|
|
}
|
|
}
|
|
|
|
TArray<TSharedPtr<class FSCSEditorTreeNode> > FBlueprintEditor::GetSelectedSCSEditorTreeNodes() const
|
|
{
|
|
return TArray<TSharedPtr<class FSCSEditorTreeNode>>();
|
|
}
|
|
|
|
TArray<FSubobjectEditorTreeNodePtrType> FBlueprintEditor::GetSelectedSubobjectEditorTreeNodes() const
|
|
{
|
|
TArray<FSubobjectEditorTreeNodePtrType> Nodes;
|
|
if (SubobjectEditor.IsValid())
|
|
{
|
|
Nodes = SubobjectEditor->GetSelectedNodes();
|
|
}
|
|
return Nodes;
|
|
}
|
|
|
|
FSubobjectEditorTreeNodePtrType FBlueprintEditor::FindAndSelectSubobjectEditorTreeNode(const UActorComponent* InComponent, bool IsCntrlDown)
|
|
{
|
|
FSubobjectEditorTreeNodePtrType NodePtr;
|
|
|
|
if (SubobjectEditor.IsValid())
|
|
{
|
|
NodePtr = SubobjectEditor->FindSlateNodeForObject(InComponent);
|
|
if(NodePtr.IsValid())
|
|
{
|
|
SubobjectEditor->SelectNode(NodePtr, IsCntrlDown);
|
|
}
|
|
}
|
|
|
|
return NodePtr;
|
|
}
|
|
|
|
void FBlueprintEditor::OnDisallowedPinConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB)
|
|
{
|
|
FDisallowedPinConnection NewRecord;
|
|
NewRecord.PinTypeCategoryA = PinA->PinType.PinCategory;
|
|
NewRecord.bPinIsArrayA = PinA->PinType.IsArray();
|
|
NewRecord.bPinIsReferenceA = PinA->PinType.bIsReference;
|
|
NewRecord.bPinIsWeakPointerA = PinA->PinType.bIsWeakPointer;
|
|
NewRecord.PinTypeCategoryB = PinB->PinType.PinCategory;
|
|
NewRecord.bPinIsArrayB = PinB->PinType.IsArray();
|
|
NewRecord.bPinIsReferenceB = PinB->PinType.bIsReference;
|
|
NewRecord.bPinIsWeakPointerB = PinB->PinType.bIsWeakPointer;
|
|
AnalyticsStats.GraphDisallowedPinConnections.Add(NewRecord);
|
|
}
|
|
|
|
void FBlueprintEditor::OnStartWatchingPin()
|
|
{
|
|
if (UEdGraphPin* Pin = GetCurrentlySelectedPin())
|
|
{
|
|
// Follow an input back to it's output
|
|
if ((Pin->Direction == EGPD_Input) && (Pin->LinkedTo.Num() > 0))
|
|
{
|
|
Pin = Pin->LinkedTo[0];
|
|
}
|
|
|
|
// Start watching it
|
|
FKismetDebugUtilities::TogglePinWatch(GetBlueprintObj(), Pin);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanStartWatchingPin() const
|
|
{
|
|
if (UEdGraphPin* Pin = GetCurrentlySelectedPin())
|
|
{
|
|
// Follow an input back to it's output
|
|
if ((Pin->Direction == EGPD_Input) && (Pin->LinkedTo.Num() > 0))
|
|
{
|
|
Pin = Pin->LinkedTo[0];
|
|
}
|
|
|
|
return FKismetDebugUtilities::CanWatchPin(GetBlueprintObj(), Pin);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnStopWatchingPin()
|
|
{
|
|
if (UEdGraphPin* Pin = GetCurrentlySelectedPin())
|
|
{
|
|
// Follow an input back to it's output
|
|
if ((Pin->Direction == EGPD_Input) && (Pin->LinkedTo.Num() > 0))
|
|
{
|
|
Pin = Pin->LinkedTo[0];
|
|
}
|
|
|
|
FKismetDebugUtilities::TogglePinWatch(GetBlueprintObj(), Pin);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanStopWatchingPin() const
|
|
{
|
|
if (UEdGraphPin* Pin = GetCurrentlySelectedPin())
|
|
{
|
|
// Follow an input back to it's output
|
|
if ((Pin->Direction == EGPD_Input) && (Pin->LinkedTo.Num() > 0))
|
|
{
|
|
Pin = Pin->LinkedTo[0];
|
|
}
|
|
|
|
return FKismetDebugUtilities::IsPinBeingWatched(GetBlueprintObj(), Pin);
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FBlueprintEditor::CanGoToDefinition() const
|
|
{
|
|
const UEdGraphNode* Node = GetSingleSelectedNode();
|
|
return (Node != nullptr) && Node->CanJumpToDefinition();
|
|
}
|
|
|
|
void FBlueprintEditor::OnGoToDefinition()
|
|
{
|
|
if (UEdGraphNode* SelectedGraphNode = GetSingleSelectedNode())
|
|
{
|
|
OnNodeDoubleClicked(SelectedGraphNode);
|
|
}
|
|
}
|
|
|
|
FString FBlueprintEditor::GetDocLinkForSelectedNode()
|
|
{
|
|
FString DocumentationLink;
|
|
|
|
if (const UEdGraphNode* SelectedGraphNode = GetSingleSelectedNode())
|
|
{
|
|
const FString DocLink = SelectedGraphNode->GetDocumentationLink();
|
|
const FString DocExcerpt = SelectedGraphNode->GetDocumentationExcerptName();
|
|
|
|
if (!DocLink.IsEmpty() && !DocExcerpt.IsEmpty())
|
|
{
|
|
DocumentationLink = FEditorClassUtils::GetDocumentationLinkFromExcerpt(DocLink, DocExcerpt);
|
|
}
|
|
}
|
|
|
|
return DocumentationLink;
|
|
}
|
|
|
|
FString FBlueprintEditor::GetDocLinkBaseUrlForSelectedNode()
|
|
{
|
|
FString DocumentationLinkBaseUrl;
|
|
|
|
if (const UEdGraphNode* SelectedGraphNode = GetSingleSelectedNode())
|
|
{
|
|
const FString DocLink = SelectedGraphNode->GetDocumentationLink();
|
|
const FString DocExcerpt = SelectedGraphNode->GetDocumentationExcerptName();
|
|
|
|
if (!DocLink.IsEmpty() && !DocExcerpt.IsEmpty())
|
|
{
|
|
DocumentationLinkBaseUrl = FEditorClassUtils::GetDocumentationLinkBaseUrlFromExcerpt(DocLink, DocExcerpt);
|
|
}
|
|
}
|
|
|
|
return DocumentationLinkBaseUrl;
|
|
}
|
|
|
|
void FBlueprintEditor::OnGoToDocumentation()
|
|
{
|
|
const FString DocumentationLink = GetDocLinkForSelectedNode();
|
|
const FString DocumentationLinkBaseUrl = GetDocLinkBaseUrlForSelectedNode();
|
|
if (!DocumentationLink.IsEmpty())
|
|
{
|
|
IDocumentation::Get()->Open(DocumentationLink, FDocumentationSourceInfo(TEXT("rightclick_bpnode")), DocumentationLinkBaseUrl);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanGoToDocumentation()
|
|
{
|
|
FString DocumentationLink = GetDocLinkForSelectedNode();
|
|
return !DocumentationLink.IsEmpty();
|
|
}
|
|
|
|
void FBlueprintEditor::OnSetEnabledStateForSelectedNodes(ENodeEnabledState NewState)
|
|
{
|
|
const FScopedTransaction Transaction(LOCTEXT("SetNodeEnabledState", "Set Node Enabled State"));
|
|
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (UObject* SelectedNode : SelectedNodes)
|
|
{
|
|
if (UEdGraphNode* SelectedGraphNode = Cast<UEdGraphNode>(SelectedNode))
|
|
{
|
|
SelectedGraphNode->Modify();
|
|
SelectedGraphNode->SetEnabledState(NewState);
|
|
}
|
|
}
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj());
|
|
}
|
|
|
|
ECheckBoxState FBlueprintEditor::GetEnabledCheckBoxStateForSelectedNodes()
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
ECheckBoxState Result = SelectedNodes.Num() > 0 ? ECheckBoxState::Undetermined : ECheckBoxState::Unchecked;
|
|
for (UObject* SelectedNode : SelectedNodes)
|
|
{
|
|
UEdGraphNode* SelectedGraphNode = Cast<UEdGraphNode>(SelectedNode);
|
|
if(SelectedGraphNode)
|
|
{
|
|
const bool bIsSelectedNodeEnabled = SelectedGraphNode->IsNodeEnabled();
|
|
if(Result == ECheckBoxState::Undetermined)
|
|
{
|
|
Result = bIsSelectedNodeEnabled ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
else if((!bIsSelectedNodeEnabled && Result == ECheckBoxState::Checked)
|
|
|| (bIsSelectedNodeEnabled && Result == ECheckBoxState::Unchecked))
|
|
{
|
|
Result = ECheckBoxState::Undetermined;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
ECheckBoxState FBlueprintEditor::CheckEnabledStateForSelectedNodes(ENodeEnabledState CheckState)
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
ECheckBoxState Result = (SelectedNodes.Num() > 0) ? ECheckBoxState::Undetermined : ECheckBoxState::Unchecked;
|
|
for (UObject* SelectedNode : SelectedNodes)
|
|
{
|
|
if (UEdGraphNode* SelectedGraphNode = Cast<UEdGraphNode>(SelectedNode))
|
|
{
|
|
const ENodeEnabledState NodeState = SelectedGraphNode->GetDesiredEnabledState();
|
|
if (Result == ECheckBoxState::Undetermined)
|
|
{
|
|
Result = (NodeState == CheckState) ? ECheckBoxState::Checked : ECheckBoxState::Unchecked;
|
|
}
|
|
else if ((NodeState != CheckState && Result == ECheckBoxState::Checked) || (NodeState == CheckState && Result == ECheckBoxState::Unchecked))
|
|
{
|
|
Result = ECheckBoxState::Undetermined;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return Result;
|
|
}
|
|
|
|
void FBlueprintEditor::UpdateNodesUnrelatedStatesAfterGraphChange()
|
|
{
|
|
if (bHideUnrelatedNodes && !bLockNodeFadeState && bSelectRegularNode)
|
|
{
|
|
ResetAllNodesUnrelatedStates();
|
|
|
|
HideUnrelatedNodes();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::ResetAllNodesUnrelatedStates()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->ResetAllNodesUnrelatedStates();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::CollectExecDownstreamNodes(UEdGraphNode* CurrentNode, TArray<UEdGraphNode*>& CollectedNodes)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
TArray<UEdGraphPin*> AllPins = CurrentNode->GetAllPins();
|
|
|
|
for (auto& Pin : AllPins)
|
|
{
|
|
if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory == K2Schema->PC_Exec)
|
|
{
|
|
for (auto& Link : Pin->LinkedTo)
|
|
{
|
|
UEdGraphNode* LinkedNode = Cast<UEdGraphNode>(Link->GetOwningNode());
|
|
if (LinkedNode && !CollectedNodes.Contains(LinkedNode))
|
|
{
|
|
CollectedNodes.Add(LinkedNode);
|
|
CollectExecDownstreamNodes( LinkedNode, CollectedNodes );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::CollectExecUpstreamNodes(UEdGraphNode* CurrentNode, TArray<UEdGraphNode*>& CollectedNodes)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
TArray<UEdGraphPin*> AllPins = CurrentNode->GetAllPins();
|
|
|
|
for (auto& Pin : AllPins)
|
|
{
|
|
if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory == K2Schema->PC_Exec)
|
|
{
|
|
for (auto& Link : Pin->LinkedTo)
|
|
{
|
|
UEdGraphNode* LinkedNode = Cast<UEdGraphNode>(Link->GetOwningNode());
|
|
if (LinkedNode && !CollectedNodes.Contains(LinkedNode))
|
|
{
|
|
CollectedNodes.Add(LinkedNode);
|
|
CollectExecUpstreamNodes( LinkedNode, CollectedNodes );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::CollectPureDownstreamNodes(UEdGraphNode* CurrentNode, TArray<UEdGraphNode*>& CollectedNodes)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
TArray<UEdGraphPin*> AllPins = CurrentNode->GetAllPins();
|
|
|
|
for (auto& Pin : AllPins)
|
|
{
|
|
if (Pin->Direction == EGPD_Output && Pin->PinType.PinCategory != K2Schema->PC_Exec)
|
|
{
|
|
for (auto& Link : Pin->LinkedTo)
|
|
{
|
|
UK2Node* LinkedNode = Cast<UK2Node>(Link->GetOwningNode());
|
|
if (LinkedNode && !CollectedNodes.Contains(LinkedNode))
|
|
{
|
|
CollectedNodes.Add(LinkedNode);
|
|
if (LinkedNode->IsNodePure())
|
|
{
|
|
CollectPureDownstreamNodes( LinkedNode, CollectedNodes );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::CollectPureUpstreamNodes(UEdGraphNode* CurrentNode, TArray<UEdGraphNode*>& CollectedNodes)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
TArray<UEdGraphPin*> AllPins = CurrentNode->GetAllPins();
|
|
|
|
for (auto& Pin : AllPins)
|
|
{
|
|
if (Pin->Direction == EGPD_Input && Pin->PinType.PinCategory != K2Schema->PC_Exec)
|
|
{
|
|
for (auto& Link : Pin->LinkedTo)
|
|
{
|
|
UK2Node* LinkedNode = Cast<UK2Node>(Link->GetOwningNode());
|
|
if (LinkedNode && !CollectedNodes.Contains(LinkedNode))
|
|
{
|
|
CollectedNodes.Add(LinkedNode);
|
|
if (LinkedNode->IsNodePure())
|
|
{
|
|
CollectPureUpstreamNodes( LinkedNode, CollectedNodes );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::HideUnrelatedNodes()
|
|
{
|
|
TArray<UEdGraphNode*> NodesToShow;
|
|
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
|
|
TArray<UObject*> ImpureNodes = SelectedNodes.Array().FilterByPredicate([](UObject* Node){
|
|
UK2Node* K2Node = Cast<UK2Node>(Node);
|
|
if (K2Node)
|
|
{
|
|
return !(K2Node->IsNodePure());
|
|
}
|
|
return false;
|
|
});
|
|
|
|
TArray<UObject*> PureNodes = SelectedNodes.Array().FilterByPredicate([](UObject* Node){
|
|
UK2Node* K2Node = Cast<UK2Node>(Node);
|
|
if (K2Node)
|
|
{
|
|
return K2Node->IsNodePure();
|
|
}
|
|
// Treat a node which can't cast to an UK2Node as a pure node (like a document node or a commment node)
|
|
// Make sure all selected nodes are handled
|
|
return true;
|
|
});
|
|
|
|
for (auto Node : ImpureNodes)
|
|
{
|
|
UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(Node);
|
|
|
|
if (SelectedNode)
|
|
{
|
|
NodesToShow.Add(SelectedNode);
|
|
CollectExecDownstreamNodes( SelectedNode, NodesToShow );
|
|
CollectExecUpstreamNodes( SelectedNode, NodesToShow );
|
|
CollectPureDownstreamNodes( SelectedNode, NodesToShow );
|
|
CollectPureUpstreamNodes( SelectedNode, NodesToShow );
|
|
}
|
|
}
|
|
|
|
for (auto Node : PureNodes)
|
|
{
|
|
UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(Node);
|
|
|
|
if (SelectedNode)
|
|
{
|
|
NodesToShow.Add(SelectedNode);
|
|
CollectPureDownstreamNodes( SelectedNode, NodesToShow );
|
|
CollectPureUpstreamNodes( SelectedNode, NodesToShow );
|
|
}
|
|
}
|
|
|
|
TArray<class UEdGraphNode*> AllNodes = FocusedGraphEd->GetCurrentGraph()->Nodes;
|
|
|
|
TArray<UEdGraphNode*> CommentNodes;
|
|
TArray<UEdGraphNode*> RelatedNodes;
|
|
|
|
for (auto& Node : AllNodes)
|
|
{
|
|
if (NodesToShow.Contains(Cast<UEdGraphNode>(Node)))
|
|
{
|
|
Node->SetNodeUnrelated(false);
|
|
RelatedNodes.Add(Node);
|
|
}
|
|
else
|
|
{
|
|
if (UEdGraphNode_Comment* CommentNode = Cast<UEdGraphNode_Comment>(Node))
|
|
{
|
|
CommentNodes.Add(Node);
|
|
}
|
|
else
|
|
{
|
|
Node->SetNodeUnrelated(true);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
FocusedGraphEd->FocusCommentNodes(CommentNodes, RelatedNodes);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::ToggleHideUnrelatedNodes()
|
|
{
|
|
bHideUnrelatedNodes = !bHideUnrelatedNodes;
|
|
|
|
ResetAllNodesUnrelatedStates();
|
|
|
|
if (bHideUnrelatedNodes && bSelectRegularNode)
|
|
{
|
|
HideUnrelatedNodes();
|
|
}
|
|
else
|
|
{
|
|
bLockNodeFadeState = false;
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::IsToggleHideUnrelatedNodesChecked() const
|
|
{
|
|
return bHideUnrelatedNodes == true;
|
|
}
|
|
|
|
bool FBlueprintEditor::ShouldShowToggleHideUnrelatedNodes(bool bIsToolbar) const
|
|
{
|
|
// Only show the toolbar button when not actively debugging, otherwise the debug buttons won't fit
|
|
if (bIsToolbar)
|
|
{
|
|
return !GIntraFrameDebuggingGameThread;
|
|
}
|
|
return GIntraFrameDebuggingGameThread;
|
|
}
|
|
|
|
TSharedRef<SWidget> FBlueprintEditor::MakeHideUnrelatedNodesOptionsMenu()
|
|
{
|
|
const bool bShouldCloseWindowAfterMenuSelection = true;
|
|
FMenuBuilder MenuBuilder( bShouldCloseWindowAfterMenuSelection, GetToolkitCommands() );
|
|
|
|
TSharedRef<SWidget> OptionsHeading = SNew(SBox)
|
|
.Padding(2.0f)
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("HideUnrelatedNodesOptions", "Hide Unrelated Nodes Options"))
|
|
.TextStyle(FAppStyle::Get(), "Menu.Heading")
|
|
]
|
|
];
|
|
|
|
TSharedRef<SWidget> LockNodeStateCheckBox = SNew(SBox)
|
|
[
|
|
SNew(SCheckBox)
|
|
.IsChecked(bLockNodeFadeState ? ECheckBoxState::Checked : ECheckBoxState::Unchecked)
|
|
.OnCheckStateChanged(this, &FBlueprintEditor::OnLockNodeStateCheckStateChanged)
|
|
.Style(FAppStyle::Get(), "Menu.CheckBox")
|
|
.ToolTipText(LOCTEXT("LockNodeStateCheckBoxToolTip", "Lock the current state of all nodes."))
|
|
.Content()
|
|
[
|
|
SNew(SHorizontalBox)
|
|
|
|
+SHorizontalBox::Slot()
|
|
.Padding(2.0f, 0.0f, 0.0f, 0.0f)
|
|
[
|
|
SNew(STextBlock)
|
|
.Text(LOCTEXT("LockNodeState", "Lock Node State"))
|
|
]
|
|
]
|
|
];
|
|
|
|
MenuBuilder.AddWidget(OptionsHeading, FText::GetEmpty(), true);
|
|
|
|
MenuBuilder.AddMenuEntry(FUIAction(), LockNodeStateCheckBox);
|
|
|
|
return MenuBuilder.MakeWidget();
|
|
}
|
|
|
|
void FBlueprintEditor::LoadEditorSettings()
|
|
{
|
|
UBlueprintEditorSettings* LocalSettings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
|
|
if (LocalSettings->bHideUnrelatedNodes)
|
|
{
|
|
ToggleHideUnrelatedNodes();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SaveEditorSettings()
|
|
{
|
|
UBlueprintEditorSettings* LocalSettings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
|
|
LocalSettings->bHideUnrelatedNodes = bHideUnrelatedNodes;
|
|
LocalSettings->SaveConfig();
|
|
}
|
|
|
|
void FBlueprintEditor::OnLockNodeStateCheckStateChanged(ECheckBoxState NewCheckedState)
|
|
{
|
|
bLockNodeFadeState = (NewCheckedState == ECheckBoxState::Checked) ? true : false;
|
|
}
|
|
|
|
void FBlueprintEditor::ToggleSaveIntermediateBuildProducts()
|
|
{
|
|
bSaveIntermediateBuildProducts = !bSaveIntermediateBuildProducts;
|
|
}
|
|
|
|
bool FBlueprintEditor::GetSaveIntermediateBuildProducts() const
|
|
{
|
|
return bSaveIntermediateBuildProducts;
|
|
}
|
|
|
|
void FBlueprintEditor::OnNodeDoubleClicked(UEdGraphNode* Node)
|
|
{
|
|
if (Node->CanJumpToDefinition())
|
|
{
|
|
Node->JumpToDefinition();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::ExtractEventTemplateForFunction(class UK2Node_CustomEvent* InCustomEvent, UEdGraphNode* InGatewayNode, class UK2Node_EditablePinBase* InEntryNode, class UK2Node_EditablePinBase* InResultNode, TSet<UEdGraphNode*>& InCollapsableNodes)
|
|
{
|
|
check(InCustomEvent);
|
|
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
for(UEdGraphPin* Pin : InCustomEvent->Pins)
|
|
{
|
|
if(Pin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
|
|
{
|
|
TArray< UEdGraphPin* > PinLinkList = Pin->LinkedTo;
|
|
for( UEdGraphPin* PinLink : PinLinkList)
|
|
{
|
|
if(!InCollapsableNodes.Contains(PinLink->GetOwningNode()))
|
|
{
|
|
InGatewayNode->Modify();
|
|
Pin->Modify();
|
|
PinLink->Modify();
|
|
|
|
K2Schema->MovePinLinks(*Pin, *K2Schema->FindExecutionPin(*InGatewayNode, EGPD_Output));
|
|
}
|
|
}
|
|
}
|
|
else if(Pin->PinType.PinCategory != UEdGraphSchema_K2::PC_Delegate)
|
|
{
|
|
|
|
TArray< UEdGraphPin* > PinLinkList = Pin->LinkedTo;
|
|
for( UEdGraphPin* PinLink : PinLinkList)
|
|
{
|
|
if(!InCollapsableNodes.Contains(PinLink->GetOwningNode()))
|
|
{
|
|
InGatewayNode->Modify();
|
|
Pin->Modify();
|
|
PinLink->Modify();
|
|
|
|
const FName PortName = *FString::Printf(TEXT("%s_Out"), *Pin->PinName.ToString());
|
|
UEdGraphPin* RemotePortPin = InGatewayNode->FindPin(PortName);
|
|
// For nodes that are connected to the event but not collapsing into the graph, they need to create a pin on the result.
|
|
if(RemotePortPin == nullptr)
|
|
{
|
|
FName UniquePortName = InGatewayNode->CreateUniquePinName(PortName);
|
|
|
|
RemotePortPin = InGatewayNode->CreatePin(Pin->Direction, Pin->PinType, UniquePortName);
|
|
InResultNode->CreateUserDefinedPin(UniquePortName, Pin->PinType, EGPD_Input);
|
|
}
|
|
PinLink->BreakAllPinLinks();
|
|
PinLink->MakeLinkTo(RemotePortPin);
|
|
}
|
|
else
|
|
{
|
|
InEntryNode->Modify();
|
|
|
|
const FName UniquePortName = InGatewayNode->CreateUniquePinName(Pin->PinName);
|
|
InEntryNode->CreateUserDefinedPin(UniquePortName, Pin->PinType, EGPD_Output);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
static void SortPinsByConnectionPosition(TArray<UEdGraphPin*>& Pins)
|
|
{
|
|
// Maps a pin to it's index in the owning node
|
|
TMap<UEdGraphPin*, int32> GetPinIndexCache;
|
|
auto GetPinIndex = [&GetPinIndexCache](const UEdGraphPin* Pin)
|
|
{
|
|
if (!GetPinIndexCache.Contains(Pin))
|
|
{
|
|
const UEdGraphNode* Parent = Pin->GetOwningNode();
|
|
for (int I = 0; I < Parent->Pins.Num(); ++I)
|
|
{
|
|
// since we're already iterating all the siblings,
|
|
// go ahead and store them as well for faster lookup
|
|
GetPinIndexCache.Add(Parent->Pins[I], I);
|
|
}
|
|
}
|
|
return GetPinIndexCache[Pin];
|
|
};
|
|
|
|
// determines whether the Y value of PinA is less than that of PinB
|
|
auto PinYComp = [&GetPinIndex](const UEdGraphPin* PinA, const UEdGraphPin* PinB){
|
|
const float NodeAPosY = PinA->GetOwningNode()->NodePosY;
|
|
const float NodeBPosY = PinB->GetOwningNode()->NodePosY;
|
|
const float NodeAPosX = PinA->GetOwningNode()->NodePosX;
|
|
const float NodeBPosX = PinB->GetOwningNode()->NodePosX;
|
|
|
|
// Exec pins should always appear first
|
|
if (UEdGraphSchema_K2::IsExecPin(*PinA))
|
|
{
|
|
return true;
|
|
}
|
|
if (UEdGraphSchema_K2::IsExecPin(*PinB))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// sort by Y, then X, then Pin index
|
|
if (NodeAPosY != NodeBPosY)
|
|
{
|
|
return NodeAPosY < NodeBPosY;
|
|
}
|
|
if (NodeAPosX != NodeBPosX)
|
|
{
|
|
return NodeAPosX < NodeBPosX;
|
|
}
|
|
return GetPinIndex(PinA) < GetPinIndex(PinB);
|
|
};
|
|
|
|
Pins.Sort([&PinYComp](const UEdGraphPin& PinA, const UEdGraphPin& PinB)
|
|
{
|
|
// since we care more about making the calling convention pretty
|
|
// and less about making the internal implementation pretty, we'll
|
|
// sort by linked pins instead of the gateway pins directly.
|
|
return PinYComp(*Algo::MinElement(PinA.LinkedTo, PinYComp), *Algo::MinElement(PinB.LinkedTo, PinYComp));
|
|
});
|
|
}
|
|
|
|
void FBlueprintEditor::CollapseNodesIntoGraph(UEdGraphNode* InGatewayNode, UK2Node_EditablePinBase* InEntryNode, UK2Node_EditablePinBase* InResultNode, UEdGraph* InSourceGraph, UEdGraph* InDestinationGraph, TSet<UEdGraphNode*>& InCollapsableNodes, bool bCanDiscardEmptyReturnNode, bool bCanHaveWeakObjPtrParam)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
// Keep track of the statistics of the node positions so the new nodes can be located reasonably well
|
|
int32 SumNodeX = 0;
|
|
int32 SumNodeY = 0;
|
|
int32 MinNodeX = std::numeric_limits<int32>::max();
|
|
int32 MinNodeY = std::numeric_limits<int32>::max();
|
|
int32 MaxNodeX = std::numeric_limits<int32>::min();
|
|
int32 MaxNodeY = std::numeric_limits<int32>::min();
|
|
|
|
UEdGraphNode* InterfaceTemplateNode = nullptr;
|
|
|
|
// If our return node only contains an exec pin, then we don't need to add it
|
|
// This helps to mitigate cases where it is unclear which exec pins should be connected to the return node
|
|
bool bDiscardReturnNode = true;
|
|
|
|
// For collapsing to functions can use a single event as a template for the function. This event MUST be deleted at the end, and the pins pre-generated.
|
|
if (InGatewayNode->GetClass() == UK2Node_CallFunction::StaticClass())
|
|
{
|
|
for (UEdGraphNode* Node : InCollapsableNodes)
|
|
{
|
|
if (UK2Node_CustomEvent* const CustomEvent = Cast<UK2Node_CustomEvent>(Node))
|
|
{
|
|
check(!InterfaceTemplateNode);
|
|
|
|
InterfaceTemplateNode = CustomEvent;
|
|
InterfaceTemplateNode->Modify();
|
|
|
|
ExtractEventTemplateForFunction(CustomEvent, InGatewayNode, InEntryNode, InResultNode, InCollapsableNodes);
|
|
|
|
FString GraphName = FBlueprintEditorUtils::GenerateUniqueGraphName(GetBlueprintObj(), CustomEvent->GetNodeTitle(ENodeTitleType::ListView).ToString()).ToString();
|
|
FBlueprintEditorUtils::RenameGraph(InDestinationGraph, GraphName);
|
|
|
|
// Remove the node, it has no place in the new graph
|
|
InCollapsableNodes.Remove(Node);
|
|
|
|
// Also break all links so that we don't try to reconnect to it from the new graph
|
|
Node->BreakAllNodeLinks();
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
TArray<UEdGraphPin*> GatewayPins;
|
|
|
|
// Move the nodes over, which may create cross-graph references that we need fix up ASAP
|
|
for (TSet<UEdGraphNode*>::TConstIterator NodeIt(InCollapsableNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* Node = *NodeIt;
|
|
Node->Modify();
|
|
|
|
// Update stats
|
|
SumNodeX += Node->NodePosX;
|
|
SumNodeY += Node->NodePosY;
|
|
MinNodeX = FMath::Min<int32>(MinNodeX, Node->NodePosX);
|
|
MinNodeY = FMath::Min<int32>(MinNodeY, Node->NodePosY);
|
|
MaxNodeX = FMath::Max<int32>(MaxNodeX, Node->NodePosX);
|
|
MaxNodeY = FMath::Max<int32>(MaxNodeY, Node->NodePosY);
|
|
|
|
// Move the node over
|
|
InSourceGraph->Nodes.Remove(Node);
|
|
InDestinationGraph->Nodes.Add(Node);
|
|
Node->Rename(/*NewName=*/ nullptr, /*NewOuter=*/ InDestinationGraph);
|
|
|
|
// Move the sub-graph to the new graph
|
|
if (UK2Node_Composite* Composite = Cast<UK2Node_Composite>(Node))
|
|
{
|
|
InSourceGraph->SubGraphs.Remove(Composite->BoundGraph);
|
|
InDestinationGraph->SubGraphs.Add(Composite->BoundGraph);
|
|
}
|
|
|
|
TArray<UEdGraphPin*> OutputGatewayExecPins;
|
|
|
|
// Find cross-graph links
|
|
for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
|
|
{
|
|
UEdGraphPin* LocalPin = Node->Pins[PinIndex];
|
|
|
|
bool bIsGatewayPin = false;
|
|
if(LocalPin->LinkedTo.Num())
|
|
{
|
|
for (int32 LinkIndex = 0; LinkIndex < LocalPin->LinkedTo.Num(); ++LinkIndex)
|
|
{
|
|
UEdGraphPin* TrialPin = LocalPin->LinkedTo[LinkIndex];
|
|
if (!InCollapsableNodes.Contains(TrialPin->GetOwningNode()))
|
|
{
|
|
bIsGatewayPin = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// If the pin has no links but is an exec pin and this is a function graph, then it is a gateway pin
|
|
else if (InGatewayNode->GetClass() == UK2Node_CallFunction::StaticClass() && K2Schema->IsExecPin(*LocalPin))
|
|
{
|
|
if (LocalPin->Direction == EGPD_Input)
|
|
{
|
|
// Connect the gateway pin to the node, there is no remote pin to hook up because the exec pin was not originally connected
|
|
LocalPin->Modify();
|
|
UK2Node_EditablePinBase* LocalPort = InEntryNode;
|
|
UEdGraphPin* LocalPortPin = LocalPort->Pins[0];
|
|
LocalPin->MakeLinkTo(LocalPortPin);
|
|
}
|
|
else
|
|
{
|
|
OutputGatewayExecPins.Add(LocalPin);
|
|
}
|
|
}
|
|
|
|
if (bIsGatewayPin)
|
|
{
|
|
GatewayPins.Add(LocalPin);
|
|
}
|
|
}
|
|
|
|
if (OutputGatewayExecPins.Num() > 0)
|
|
{
|
|
UEdGraphPin* LocalResultPortPin = K2Schema->FindExecutionPin(*InResultNode, EGPD_Input);
|
|
|
|
// If the Result Node already contains links, then we don't need to make these connections as the intended connections have already been
|
|
// transferred from original graph.
|
|
if (LocalResultPortPin != nullptr && LocalResultPortPin->LinkedTo.Num() == 0)
|
|
{
|
|
// TODO: Some of these pins may not necessarily be terminal pins. We should prompt the user to choose which of these connections should
|
|
// be made to the return node.
|
|
for (UEdGraphPin* LocalPin : OutputGatewayExecPins)
|
|
{
|
|
// Connect the gateway pin to the node, there is no remote pin to hook up because the exec pin was not originally connected
|
|
LocalPin->Modify();
|
|
LocalPin->MakeLinkTo(LocalResultPortPin);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// put all exec pins first, then sort by Y, then X, then Pin index
|
|
SortPinsByConnectionPosition(GatewayPins);
|
|
|
|
// Thunk cross-graph links thru the gateway
|
|
for (UEdGraphPin* LocalPin : GatewayPins)
|
|
{
|
|
// Local port is either the entry or the result node in the collapsed graph
|
|
// Remote port is the node placed in the source graph
|
|
UK2Node_EditablePinBase* LocalPort = (LocalPin->Direction == EGPD_Input) ? InEntryNode : InResultNode;
|
|
|
|
// Add a new pin to the entry/exit node and to the composite node
|
|
UEdGraphPin* LocalPortPin = nullptr;
|
|
UEdGraphPin* RemotePortPin = nullptr;
|
|
|
|
// Function graphs have a single exec path through them, so only one exec pin for input and another for output. In this fashion, they must not be handled by name.
|
|
if (InGatewayNode->GetClass() == UK2Node_CallFunction::StaticClass() && LocalPin->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec)
|
|
{
|
|
LocalPortPin = LocalPort->Pins[0];
|
|
RemotePortPin = K2Schema->FindExecutionPin(*InGatewayNode, (LocalPortPin->Direction == EGPD_Input)? EGPD_Output : EGPD_Input);
|
|
}
|
|
else
|
|
{
|
|
// If there is a custom event being used as a template, we must check to see if any connected pins have already been built
|
|
if (InterfaceTemplateNode && LocalPin->Direction == EGPD_Input)
|
|
{
|
|
// Find the pin on the entry node, we will use that pin's name to find the pin on the remote port
|
|
UEdGraphPin* EntryNodePin = InEntryNode->FindPin(LocalPin->LinkedTo[0]->PinName);
|
|
if(EntryNodePin)
|
|
{
|
|
LocalPin->BreakAllPinLinks();
|
|
LocalPin->MakeLinkTo(EntryNodePin);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (LocalPin->LinkedTo[0]->GetOwningNode() != InEntryNode)
|
|
{
|
|
const FName UniquePortName = InGatewayNode->CreateUniquePinName(LocalPin->PinName);
|
|
|
|
if (!RemotePortPin && !LocalPortPin)
|
|
{
|
|
if (LocalPin->Direction == EGPD_Output)
|
|
{
|
|
bDiscardReturnNode = false;
|
|
}
|
|
|
|
FEdGraphPinType PinType = LocalPin->PinType;
|
|
if (PinType.bIsWeakPointer && !PinType.IsContainer() && !bCanHaveWeakObjPtrParam)
|
|
{
|
|
PinType.bIsWeakPointer = false;
|
|
}
|
|
RemotePortPin = InGatewayNode->CreatePin(LocalPin->Direction, PinType, UniquePortName);
|
|
LocalPortPin = LocalPort->CreateUserDefinedPin(UniquePortName, PinType, (LocalPin->Direction == EGPD_Input) ? EGPD_Output : EGPD_Input);
|
|
}
|
|
}
|
|
}
|
|
|
|
check(LocalPortPin);
|
|
check(RemotePortPin);
|
|
|
|
LocalPin->Modify();
|
|
|
|
// Route the links
|
|
for (int32 LinkIndex = 0; LinkIndex < LocalPin->LinkedTo.Num(); ++LinkIndex)
|
|
{
|
|
UEdGraphPin* RemotePin = LocalPin->LinkedTo[LinkIndex];
|
|
RemotePin->Modify();
|
|
|
|
if (!InCollapsableNodes.Contains(RemotePin->GetOwningNode()) && RemotePin->GetOwningNode() != InEntryNode && RemotePin->GetOwningNode() != InResultNode)
|
|
{
|
|
// Fix up the remote pin
|
|
RemotePin->LinkedTo.Remove(LocalPin);
|
|
// When given a composite node, we could possibly be given a pin with a different outer
|
|
// which is bad! Then there would be a pin connecting to itself and cause an ensure
|
|
if (RemotePin->GetOwningNode()->GetOuter() == RemotePortPin->GetOwningNode()->GetOuter())
|
|
{
|
|
RemotePin->MakeLinkTo(RemotePortPin);
|
|
}
|
|
|
|
// The Entry Node only supports a single link, so if we made links above
|
|
// we need to break them now, to make room for the new link.
|
|
if (LocalPort == InEntryNode)
|
|
{
|
|
LocalPortPin->BreakAllPinLinks();
|
|
}
|
|
|
|
// Fix up the local pin
|
|
LocalPin->LinkedTo.Remove(RemotePin);
|
|
--LinkIndex;
|
|
LocalPin->MakeLinkTo(LocalPortPin);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Reposition the newly created nodes
|
|
const int32 NumNodes = InCollapsableNodes.Num();
|
|
|
|
// Remove the template node if one was used for generating the function
|
|
if (InterfaceTemplateNode)
|
|
{
|
|
if (NumNodes == 0)
|
|
{
|
|
SumNodeX = InterfaceTemplateNode->NodePosX;
|
|
SumNodeY = InterfaceTemplateNode->NodePosY;
|
|
}
|
|
|
|
FBlueprintEditorUtils::RemoveNode(GetBlueprintObj(), InterfaceTemplateNode);
|
|
}
|
|
|
|
// Using the result pin, we will ensure that there is a path through the function by checking if it is connected. If it is not, link it to the entry node.
|
|
if (UEdGraphPin* ResultExecFunc = K2Schema->FindExecutionPin(*InResultNode, EGPD_Input))
|
|
{
|
|
if (ResultExecFunc->LinkedTo.Num() == 0)
|
|
{
|
|
K2Schema->FindExecutionPin(*InEntryNode, EGPD_Output)->MakeLinkTo(K2Schema->FindExecutionPin(*InResultNode, EGPD_Input));
|
|
}
|
|
}
|
|
|
|
if (UK2Node_FunctionEntry* EntryNode = Cast<UK2Node_FunctionEntry>(InEntryNode))
|
|
{
|
|
// If the source entry node is threadsafe, then our new entry node should also be threadsafe.
|
|
// It's implied that the source graph only has threadsafe nodes.
|
|
// This ensures that our newly collapsed graph can be still be used in the source graph.
|
|
|
|
EntryNode->MetaData.bThreadSafe = K2Schema->IsGraphMarkedThreadSafe(InSourceGraph);
|
|
}
|
|
|
|
const int32 CenterX = (NumNodes == 0) ? SumNodeX : SumNodeX / NumNodes;
|
|
const int32 CenterY = (NumNodes == 0) ? SumNodeY : SumNodeY / NumNodes;
|
|
const int32 MinusOffsetX = 160; //@TODO: Random magic numbers
|
|
const int32 PlusOffsetX = 300;
|
|
|
|
// Put the gateway node at the center of the empty space in the old graph
|
|
InGatewayNode->NodePosX = CenterX;
|
|
InGatewayNode->NodePosY = CenterY;
|
|
InGatewayNode->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
|
|
// Put the entry and exit nodes on either side of the nodes in the new graph
|
|
//@TODO: Should we recenter the whole ensemble?
|
|
if (NumNodes != 0)
|
|
{
|
|
InEntryNode->NodePosX = MinNodeX - MinusOffsetX;
|
|
InEntryNode->NodePosY = CenterY;
|
|
InEntryNode->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
|
|
InResultNode->NodePosX = MaxNodeX + PlusOffsetX;
|
|
InResultNode->NodePosY = CenterY;
|
|
InResultNode->SnapToGrid(SNodePanel::GetSnapGridSize());
|
|
}
|
|
|
|
if (bCanDiscardEmptyReturnNode && bDiscardReturnNode)
|
|
{
|
|
InResultNode->DestroyNode();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::CollapseNodes(TSet<UEdGraphNode*>& InCollapsableNodes)
|
|
{
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd.IsValid())
|
|
{
|
|
return;
|
|
}
|
|
|
|
UEdGraph* SourceGraph = FocusedGraphEd->GetCurrentGraph();
|
|
SourceGraph->Modify();
|
|
|
|
// Create the composite node that will serve as the gateway into the subgraph
|
|
UK2Node_Composite* GatewayNode = nullptr;
|
|
{
|
|
GatewayNode = FEdGraphSchemaAction_K2NewNode::SpawnNode<UK2Node_Composite>(SourceGraph, FVector2D(0,0), EK2NewNodeFlags::SelectNewNode);
|
|
GatewayNode->bCanRenameNode = true;
|
|
check(GatewayNode);
|
|
}
|
|
|
|
FName GraphName;
|
|
GraphName = FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), TEXT("CollapseGraph"));
|
|
|
|
// Rename the graph to the correct name
|
|
UEdGraph* DestinationGraph = GatewayNode->BoundGraph;
|
|
TSharedPtr<INameValidatorInterface> NameValidator = MakeShareable(new FKismetNameValidator(GetBlueprintObj(), GraphName));
|
|
FBlueprintEditorUtils::RenameGraphWithSuggestion(DestinationGraph, NameValidator, GraphName.ToString());
|
|
|
|
CollapseNodesIntoGraph(GatewayNode, GatewayNode->GetInputSink(), GatewayNode->GetOutputSource(), SourceGraph, DestinationGraph, InCollapsableNodes, false, true);
|
|
}
|
|
|
|
UEdGraph* FBlueprintEditor::CollapseSelectionToFunction(TSharedPtr<SGraphEditor> InRootGraph, TSet<UEdGraphNode*>& InCollapsableNodes, UEdGraphNode*& OutFunctionNode)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = InRootGraph;
|
|
if (!FocusedGraphEd.IsValid())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UEdGraph* SourceGraph = FocusedGraphEd->GetCurrentGraph();
|
|
SourceGraph->Modify();
|
|
|
|
UEdGraph* NewGraph = nullptr;
|
|
|
|
FName DocumentName = FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), TEXT("NewFunction"));
|
|
|
|
NewGraph = FBlueprintEditorUtils::CreateNewGraph(GetBlueprintObj(), DocumentName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
|
FBlueprintEditorUtils::AddFunctionGraph<UClass>(GetBlueprintObj(), NewGraph, /*bIsUserCreated=*/ true, nullptr);
|
|
|
|
TArray<UK2Node_FunctionEntry*> EntryNodes;
|
|
NewGraph->GetNodesOfClass(EntryNodes);
|
|
UK2Node_FunctionEntry* EntryNode = EntryNodes[0];
|
|
UK2Node_FunctionResult* ResultNode = nullptr;
|
|
|
|
// Create Result
|
|
FGraphNodeCreator<UK2Node_FunctionResult> ResultNodeCreator(*NewGraph);
|
|
UK2Node_FunctionResult* FunctionResult = ResultNodeCreator.CreateNode();
|
|
|
|
const UEdGraphSchema_K2* Schema = Cast<UEdGraphSchema_K2>(FunctionResult->GetSchema());
|
|
FunctionResult->NodePosX = EntryNode->NodePosX + EntryNode->NodeWidth + 256;
|
|
FunctionResult->NodePosY = EntryNode->NodePosY;
|
|
|
|
ResultNodeCreator.Finalize();
|
|
|
|
ResultNode = FunctionResult;
|
|
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
|
|
// make temp list builder
|
|
FGraphActionListBuilderBase TempListBuilder;
|
|
TempListBuilder.OwnerOfTemporaries = NewObject<UEdGraph>(GetBlueprintObj());
|
|
TempListBuilder.OwnerOfTemporaries->SetFlags(RF_Transient);
|
|
|
|
IBlueprintNodeBinder::FBindingSet Bindings;
|
|
OutFunctionNode = UBlueprintFunctionNodeSpawner::Create(FindUField<UFunction>(GetBlueprintObj()->SkeletonGeneratedClass, DocumentName))->Invoke(SourceGraph, Bindings, FVector2D::ZeroVector);
|
|
|
|
check(OutFunctionNode);
|
|
|
|
CollapseNodesIntoGraph(OutFunctionNode, EntryNode, ResultNode, SourceGraph, NewGraph, InCollapsableNodes, true, false);
|
|
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(GetBlueprintObj());
|
|
OutFunctionNode->ReconstructNode();
|
|
|
|
return NewGraph;
|
|
}
|
|
|
|
UEdGraph* FBlueprintEditor::CollapseSelectionToMacro(TSharedPtr<SGraphEditor> InRootGraph, TSet<UEdGraphNode*>& InCollapsableNodes, UEdGraphNode*& OutMacroNode)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = InRootGraph;
|
|
if (!FocusedGraphEd.IsValid())
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
UBlueprint* BP = GetBlueprintObj();
|
|
|
|
UEdGraph* SourceGraph = FocusedGraphEd->GetCurrentGraph();
|
|
SourceGraph->Modify();
|
|
|
|
UEdGraph* DestinationGraph = nullptr;
|
|
|
|
FName DocumentName = FBlueprintEditorUtils::FindUniqueKismetName(BP, TEXT("NewMacro"));
|
|
|
|
DestinationGraph = FBlueprintEditorUtils::CreateNewGraph(BP, DocumentName, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
|
FBlueprintEditorUtils::AddMacroGraph(BP, DestinationGraph, /*bIsUserCreated=*/ true, nullptr);
|
|
|
|
UK2Node_MacroInstance* GatewayNode = FEdGraphSchemaAction_K2NewNode::SpawnNode<UK2Node_MacroInstance>(
|
|
SourceGraph,
|
|
FVector2D(0.0f, 0.0f),
|
|
EK2NewNodeFlags::None,
|
|
[DestinationGraph](UK2Node_MacroInstance* NewInstance)
|
|
{
|
|
NewInstance->SetMacroGraph(DestinationGraph);
|
|
}
|
|
);
|
|
|
|
TArray<UK2Node_Tunnel*> TunnelNodes;
|
|
GatewayNode->GetMacroGraph()->GetNodesOfClass(TunnelNodes);
|
|
|
|
UK2Node_EditablePinBase* InputSink = nullptr;
|
|
UK2Node_EditablePinBase* OutputSink = nullptr;
|
|
|
|
// Retrieve the tunnel nodes to use them to match up pin links that connect to the gateway.
|
|
for (UK2Node_Tunnel* Node : TunnelNodes)
|
|
{
|
|
if (Node->IsEditable())
|
|
{
|
|
if (Node->bCanHaveOutputs)
|
|
{
|
|
InputSink = Node;
|
|
}
|
|
else if (Node->bCanHaveInputs)
|
|
{
|
|
OutputSink = Node;
|
|
}
|
|
}
|
|
}
|
|
|
|
CollapseNodesIntoGraph(GatewayNode, InputSink, OutputSink, SourceGraph, DestinationGraph, InCollapsableNodes, /* bCanDiscardEmptyReturnNode */ false, /* bCanHaveWeakObjPtrParam */ false);
|
|
|
|
OutMacroNode = GatewayNode;
|
|
OutMacroNode->ReconstructNode();
|
|
|
|
return DestinationGraph;
|
|
}
|
|
|
|
void FBlueprintEditor::ExpandNode(UEdGraphNode* InNodeToExpand, UEdGraph* InSourceGraph, TSet<UEdGraphNode*>& OutExpandedNodes)
|
|
{
|
|
UEdGraph* DestinationGraph = InNodeToExpand->GetGraph();
|
|
UEdGraph* SourceGraph = InSourceGraph;
|
|
check(SourceGraph);
|
|
|
|
// Mark all edited objects so they will appear in the transaction record if needed.
|
|
DestinationGraph->Modify();
|
|
SourceGraph->Modify();
|
|
InNodeToExpand->Modify();
|
|
|
|
UEdGraphNode* Entry = nullptr;
|
|
UEdGraphNode* Result = nullptr;
|
|
|
|
const bool bIsCollapsedGraph = InNodeToExpand->IsA<UK2Node_Composite>();
|
|
|
|
MoveNodesToGraph(MutableView(SourceGraph->Nodes), DestinationGraph, OutExpandedNodes, &Entry, &Result, bIsCollapsedGraph);
|
|
|
|
UEdGraphPin* OutputExecPinReconnect = nullptr;
|
|
if(UK2Node_CallFunction* CallFunction = Cast<UK2Node_CallFunction>(InNodeToExpand))
|
|
{
|
|
UEdGraphPin* ThenPin = CallFunction->GetThenPin();
|
|
if (ThenPin && ThenPin->LinkedTo.Num())
|
|
{
|
|
OutputExecPinReconnect = ThenPin->LinkedTo[0];
|
|
}
|
|
}
|
|
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
K2Schema->CollapseGatewayNode(Cast<UK2Node>(InNodeToExpand), Entry, Result, nullptr, &OutExpandedNodes);
|
|
|
|
if(Entry)
|
|
{
|
|
Entry->DestroyNode();
|
|
}
|
|
|
|
if(Result)
|
|
{
|
|
Result->DestroyNode();
|
|
}
|
|
|
|
// Make sure any subgraphs get propagated appropriately
|
|
if (SourceGraph->SubGraphs.Num() > 0)
|
|
{
|
|
DestinationGraph->SubGraphs.Append(SourceGraph->SubGraphs);
|
|
SourceGraph->SubGraphs.Empty();
|
|
}
|
|
|
|
// Remove the gateway node and source graph
|
|
InNodeToExpand->DestroyNode();
|
|
|
|
// This should be set for function nodes, all expanded nodes should connect their output exec pins to the original pin.
|
|
if(OutputExecPinReconnect)
|
|
{
|
|
for (TSet<UEdGraphNode*>::TConstIterator NodeIt(OutExpandedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* Node = *NodeIt;
|
|
for (int32 PinIndex = 0; PinIndex < Node->Pins.Num(); ++PinIndex)
|
|
{
|
|
// Only hookup output exec pins that do not have a connection
|
|
if(Node->Pins[PinIndex]->PinType.PinCategory == UEdGraphSchema_K2::PC_Exec && Node->Pins[PinIndex]->Direction == EGPD_Output && Node->Pins[PinIndex]->LinkedTo.Num() == 0)
|
|
{
|
|
Node->Pins[PinIndex]->MakeLinkTo(OutputExecPinReconnect);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::MoveNodesToGraph(TArray<UEdGraphNode*>& SourceNodes, UEdGraph* DestinationGraph, TSet<UEdGraphNode*>& OutExpandedNodes, UEdGraphNode** OutEntry, UEdGraphNode** OutResult, const bool bIsCollapsedGraph)
|
|
{
|
|
// Move the nodes over, remembering any that are boundary nodes
|
|
while (SourceNodes.Num())
|
|
{
|
|
UEdGraphNode* Node = SourceNodes.Pop();
|
|
UEdGraph* OriginalGraph = Node->GetGraph();
|
|
|
|
Node->Modify();
|
|
OriginalGraph->Modify();
|
|
Node->Rename(/*NewName=*/ nullptr, /*NewOuter=*/ DestinationGraph, REN_DontCreateRedirectors);
|
|
|
|
// Remove the node from the original graph
|
|
OriginalGraph->RemoveNode(Node, false);
|
|
|
|
// We do not check CanPasteHere when determining CanCollapseNodes, unlike CanCollapseSelectionToFunction/Macro,
|
|
// so when expanding a collapsed graph we don't want to check the CanPasteHere function:
|
|
if (!bIsCollapsedGraph && !Node->CanPasteHere(DestinationGraph))
|
|
{
|
|
Node->BreakAllNodeLinks();
|
|
continue;
|
|
}
|
|
|
|
// Successfully added the node to the graph, we may need to remove flags
|
|
if (Node->HasAllFlags(RF_Transient) && !DestinationGraph->HasAllFlags(RF_Transient))
|
|
{
|
|
Node->SetFlags(RF_Transactional);
|
|
Node->ClearFlags(RF_Transient);
|
|
TArray<UObject*> Subobjects;
|
|
GetObjectsWithOuter(Node, Subobjects);
|
|
for (UObject* Subobject : Subobjects)
|
|
{
|
|
Subobject->ClearFlags(RF_Transient);
|
|
Subobject->SetFlags(RF_Transactional);
|
|
}
|
|
}
|
|
|
|
DestinationGraph->AddNode(Node, /* bFromUI */ false, /* bSelectNewNode */ false);
|
|
|
|
if(UK2Node_Composite* Composite = Cast<UK2Node_Composite>(Node))
|
|
{
|
|
OriginalGraph->SubGraphs.Remove(Composite->BoundGraph);
|
|
DestinationGraph->SubGraphs.Add(Composite->BoundGraph);
|
|
}
|
|
|
|
// Want to test exactly against tunnel, we shouldn't collapse embedded collapsed
|
|
// nodes or macros, only the tunnels in/out of the collapsed graph
|
|
if (Node->GetClass() == UK2Node_Tunnel::StaticClass())
|
|
{
|
|
UK2Node_Tunnel* TunnelNode = Cast<UK2Node_Tunnel>(Node);
|
|
if (TunnelNode->bCanHaveOutputs)
|
|
{
|
|
*OutEntry = Node;
|
|
}
|
|
else if (TunnelNode->bCanHaveInputs)
|
|
{
|
|
*OutResult = Node;
|
|
}
|
|
}
|
|
else if (Node->GetClass() == UK2Node_FunctionEntry::StaticClass())
|
|
{
|
|
*OutEntry = Node;
|
|
}
|
|
else if (Node->GetClass() == UK2Node_FunctionResult::StaticClass())
|
|
{
|
|
*OutResult = Node;
|
|
}
|
|
else
|
|
{
|
|
OutExpandedNodes.Add(Node);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SaveEditedObjectState()
|
|
{
|
|
check(IsEditingSingleBlueprint());
|
|
|
|
// Clear currently edited documents
|
|
GetBlueprintObj()->LastEditedDocuments.Empty();
|
|
|
|
// Ask all open documents to save their state, which will update LastEditedDocuments
|
|
DocumentManager->SaveAllState();
|
|
}
|
|
|
|
void FBlueprintEditor::RequestSaveEditedObjectState()
|
|
{
|
|
bRequestedSavingOpenDocumentState = true;
|
|
}
|
|
|
|
void FBlueprintEditor::GetBoundsForNode(const UEdGraphNode* InNode, class FSlateRect& OutRect, float InPadding) const
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->GetBoundsForNode(InNode, OutRect, InPadding);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::GetViewBookmark(FGuid& BookmarkId)
|
|
{
|
|
BookmarkId.Invalidate();
|
|
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->GetViewBookmark(BookmarkId);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::GetViewLocation(FVector2D& Location, float& ZoomAmount)
|
|
{
|
|
Location = FVector2D::ZeroVector;
|
|
ZoomAmount = 0.0f;
|
|
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FVector2f TempLocation;
|
|
FocusedGraphEdPtr.Pin()->GetViewLocation(TempLocation, ZoomAmount);
|
|
Location = FVector2D(TempLocation);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SetViewLocation(const FVector2D& Location, float ZoomAmount, const FGuid& BookmarkId)
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->SetViewLocation(UE::Slate::CastToVector2f(Location), ZoomAmount, BookmarkId);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::Tick(float DeltaTime)
|
|
{
|
|
PreviewScene.UpdateCaptureContents();
|
|
|
|
// Create or update the Blueprint actor instance in the preview scene
|
|
if ( GetPreviewActor() == nullptr )
|
|
{
|
|
UpdatePreviewActor(GetBlueprintObj(), true);
|
|
}
|
|
|
|
if (bRequestedSavingOpenDocumentState)
|
|
{
|
|
bRequestedSavingOpenDocumentState = false;
|
|
|
|
SaveEditedObjectState();
|
|
}
|
|
|
|
if (InstructionsFadeCountdown > 0.f)
|
|
{
|
|
InstructionsFadeCountdown -= DeltaTime;
|
|
}
|
|
|
|
if (bPendingDeferredClose)
|
|
{
|
|
IAssetEditorInstance* EditorInst = GEditor->GetEditorSubsystem<UAssetEditorSubsystem>()->FindEditorForAsset(GetBlueprintObj(), /*bFocusIfOpen =*/false);
|
|
check(EditorInst != nullptr);
|
|
EditorInst->CloseWindow(EAssetEditorCloseReason::AssetUnloadingOrInvalid);
|
|
}
|
|
else
|
|
{
|
|
// Auto-import any namespaces we've collected as a result of compound events that may have occurred within this frame.
|
|
if (!DeferredNamespaceImports.IsEmpty())
|
|
{
|
|
FImportNamespaceExParameters Params;
|
|
Params.NamespacesToImport = MoveTemp(DeferredNamespaceImports);
|
|
ImportNamespaceEx(Params);
|
|
|
|
// Assert that this was reset by the move above.
|
|
check(DeferredNamespaceImports.IsEmpty());
|
|
}
|
|
}
|
|
}
|
|
TStatId FBlueprintEditor::GetStatId() const
|
|
{
|
|
RETURN_QUICK_DECLARE_CYCLE_STAT(FBlueprintEditor, STATGROUP_Tickables);
|
|
}
|
|
|
|
|
|
void FBlueprintEditor::OnStartEditingDefaultsClicked()
|
|
{
|
|
StartEditingDefaults(/*bAutoFocus=*/ true);
|
|
}
|
|
|
|
void FBlueprintEditor::OnListObjectsReferencedByClass()
|
|
{
|
|
ObjectTools::ShowReferencedObjs(GetBlueprintObj()->GeneratedClass);
|
|
}
|
|
|
|
void FBlueprintEditor::OnListObjectsReferencedByBlueprint()
|
|
{
|
|
ObjectTools::ShowReferencedObjs(GetBlueprintObj());
|
|
}
|
|
|
|
void FBlueprintEditor::OnRepairCorruptedBlueprint()
|
|
{
|
|
IKismetCompilerInterface& Compiler = FModuleManager::LoadModuleChecked<IKismetCompilerInterface>(KISMET_COMPILER_MODULENAME);
|
|
Compiler.RecoverCorruptedBlueprint(GetBlueprintObj());
|
|
}
|
|
|
|
void FBlueprintEditor::StartEditingDefaults(bool bAutoFocus, bool bForceRefresh)
|
|
{
|
|
SetUISelectionState(FBlueprintEditor::SelectionState_ClassDefaults);
|
|
|
|
if (IsEditingSingleBlueprint())
|
|
{
|
|
if (GetBlueprintObj()->GeneratedClass != nullptr)
|
|
{
|
|
if (SubobjectEditor.IsValid() && GetBlueprintObj()->GeneratedClass->IsChildOf<AActor>())
|
|
{
|
|
SubobjectEditor->SelectRoot();
|
|
}
|
|
else
|
|
{
|
|
UObject* DefaultObject = GetBlueprintObj()->GeneratedClass->GetDefaultObject();
|
|
|
|
// Update the details panel
|
|
FString Title;
|
|
DefaultObject->GetName(Title);
|
|
SKismetInspector::FShowDetailsOptions Options(FText::FromString(Title), bForceRefresh);
|
|
Options.bShowComponents = false;
|
|
|
|
Inspector->ShowDetailsForSingleObject(DefaultObject, Options);
|
|
|
|
if ( bAutoFocus )
|
|
{
|
|
TryInvokingDetailsTab();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
RefreshStandAloneDefaultsEditor();
|
|
}
|
|
|
|
void FBlueprintEditor::RefreshStandAloneDefaultsEditor()
|
|
{
|
|
// Update the details panel
|
|
SKismetInspector::FShowDetailsOptions Options(FText::GetEmpty(), true);
|
|
|
|
TArray<UObject*> DefaultObjects;
|
|
for ( int32 i = 0; i < GetEditingObjects().Num(); ++i )
|
|
{
|
|
if (UBlueprint* Blueprint = Cast<UBlueprint>(GetEditingObjects()[i]))
|
|
{
|
|
if (CurrentUISelection == FBlueprintEditor::SelectionState_ClassSettings)
|
|
{
|
|
DefaultObjects.Add(Blueprint);
|
|
}
|
|
else if (Blueprint->GeneratedClass)
|
|
{
|
|
DefaultObjects.Add(Blueprint->GeneratedClass->GetDefaultObject());
|
|
}
|
|
}
|
|
}
|
|
|
|
if ( DefaultObjects.Num() && DefaultEditor.IsValid() )
|
|
{
|
|
DefaultEditor->ShowDetailsForObjects(DefaultObjects);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::RenameNewlyAddedAction(FName InActionName)
|
|
{
|
|
TabManager->TryInvokeTab(FBlueprintEditorTabs::MyBlueprintID);
|
|
TryInvokingDetailsTab(/*Flash*/false);
|
|
|
|
if (MyBlueprintWidget.IsValid())
|
|
{
|
|
// Force a refresh immediately, the item has to be present in the list for the rename requests to be successful.
|
|
MyBlueprintWidget->Refresh();
|
|
MyBlueprintWidget->SelectItemByName(InActionName,ESelectInfo::OnMouseClick);
|
|
MyBlueprintWidget->OnRequestRenameOnActionNode();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnAddNewVariable()
|
|
{
|
|
const FScopedTransaction Transaction( LOCTEXT("AddVariable", "Add Variable") );
|
|
|
|
FName VarName = FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), TEXT("NewVar"));
|
|
|
|
bool bSuccess = MyBlueprintWidget.IsValid() && FBlueprintEditorUtils::AddMemberVariable(GetBlueprintObj(), VarName, MyBlueprintWidget->GetLastPinTypeUsed());
|
|
|
|
if(!bSuccess)
|
|
{
|
|
LogSimpleMessage( LOCTEXT("AddVariable_Error", "Adding new variable failed.") );
|
|
}
|
|
else
|
|
{
|
|
RenameNewlyAddedAction(VarName);
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanAddNewLocalVariable() const
|
|
{
|
|
if (InEditingMode())
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (!FocusedGraphEd.IsValid())
|
|
{
|
|
return false;
|
|
}
|
|
|
|
UEdGraph* TargetGraph = FBlueprintEditorUtils::GetTopLevelGraph(FocusedGraphEd->GetCurrentGraph());
|
|
return TargetGraph->GetSchema()->GetGraphType(TargetGraph) == GT_Function;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnAddNewLocalVariable()
|
|
{
|
|
if (!CanAddNewLocalVariable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Find the top level graph to place the local variables into
|
|
UEdGraph* TargetGraph = FBlueprintEditorUtils::GetTopLevelGraph(FocusedGraphEdPtr.Pin()->GetCurrentGraph());
|
|
check(TargetGraph->GetSchema()->GetGraphType(TargetGraph) == GT_Function);
|
|
|
|
FName VarName = FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), TEXT("NewLocalVar"), FindUField<UFunction>(GetBlueprintObj()->SkeletonGeneratedClass, TargetGraph->GetFName()));
|
|
|
|
bool bSuccess = MyBlueprintWidget.IsValid() && FBlueprintEditorUtils::AddLocalVariable(GetBlueprintObj(), TargetGraph, VarName, MyBlueprintWidget->GetLastPinTypeUsed());
|
|
|
|
if(!bSuccess)
|
|
{
|
|
LogSimpleMessage( LOCTEXT("AddLocalVariable_Error", "Adding new local variable failed.") );
|
|
}
|
|
else
|
|
{
|
|
RenameNewlyAddedAction(VarName);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnPasteNewLocalVariable(const FBPVariableDescription& VariableDescription)
|
|
{
|
|
if (!CanAddNewLocalVariable())
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Find the top level graph to place the local variables into
|
|
UEdGraph* TargetGraph = FBlueprintEditorUtils::GetTopLevelGraph(FocusedGraphEdPtr.Pin()->GetCurrentGraph());
|
|
check(TargetGraph->GetSchema()->GetGraphType(TargetGraph) == GT_Function);
|
|
|
|
bool bSuccess = MyBlueprintWidget.IsValid() && FBlueprintEditorUtils::AddLocalVariable(GetBlueprintObj(), TargetGraph, VariableDescription.VarName, VariableDescription.VarType, VariableDescription.DefaultValue);
|
|
|
|
if(!bSuccess)
|
|
{
|
|
LogSimpleMessage( LOCTEXT("PasteLocalVariable_Error", "Pasting new local variable failed.") );
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnAddNewDelegate()
|
|
{
|
|
if (!AddNewDelegateIsVisible())
|
|
{
|
|
return;
|
|
}
|
|
|
|
const UEdGraphSchema_K2* K2Schema = GetDefault<UEdGraphSchema_K2>();
|
|
check(nullptr != K2Schema);
|
|
UBlueprint* const Blueprint = GetBlueprintObj();
|
|
check(nullptr != Blueprint);
|
|
|
|
FName Name = FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), TEXT("NewEventDispatcher"));
|
|
|
|
|
|
const FScopedTransaction Transaction( LOCTEXT("AddNewDelegate", "Add New Event Dispatcher") );
|
|
Blueprint->Modify();
|
|
|
|
FEdGraphPinType DelegateType;
|
|
DelegateType.PinCategory = UEdGraphSchema_K2::PC_MCDelegate;
|
|
const bool bVarCreatedSuccess = FBlueprintEditorUtils::AddMemberVariable(Blueprint, Name, DelegateType);
|
|
if(!bVarCreatedSuccess)
|
|
{
|
|
LogSimpleMessage( LOCTEXT("AddDelegateVariable_Error", "Adding new delegate variable failed.") );
|
|
return;
|
|
}
|
|
|
|
UEdGraph* const NewGraph = FBlueprintEditorUtils::CreateNewGraph(Blueprint, Name, UEdGraph::StaticClass(), UEdGraphSchema_K2::StaticClass());
|
|
if(!NewGraph)
|
|
{
|
|
FBlueprintEditorUtils::RemoveMemberVariable(Blueprint, Name);
|
|
LogSimpleMessage( LOCTEXT("AddDelegateVariable_Error", "Adding new delegate variable failed.") );
|
|
return;
|
|
}
|
|
|
|
NewGraph->bEditable = false;
|
|
|
|
K2Schema->CreateDefaultNodesForGraph(*NewGraph);
|
|
K2Schema->CreateFunctionGraphTerminators(*NewGraph, (UClass*)nullptr);
|
|
K2Schema->AddExtraFunctionFlags(NewGraph, (FUNC_BlueprintCallable|FUNC_BlueprintEvent|FUNC_Public));
|
|
K2Schema->MarkFunctionEntryAsEditable(NewGraph, true);
|
|
|
|
Blueprint->DelegateSignatureGraphs.Add(NewGraph);
|
|
FBlueprintEditorUtils::MarkBlueprintAsStructurallyModified(Blueprint);
|
|
|
|
RenameNewlyAddedAction(Name);
|
|
}
|
|
|
|
void FBlueprintEditor::NewDocument_OnClicked(ECreatedDocumentType GraphType)
|
|
{
|
|
if (!NewDocument_IsVisibleForType(GraphType))
|
|
{
|
|
return;
|
|
}
|
|
|
|
FText DocumentNameText;
|
|
bool bResetMyBlueprintFilter = false;
|
|
|
|
switch (GraphType)
|
|
{
|
|
case CGT_NewFunctionGraph:
|
|
DocumentNameText = LOCTEXT("NewDocFuncName", "NewFunction");
|
|
bResetMyBlueprintFilter = true;
|
|
break;
|
|
case CGT_NewEventGraph:
|
|
DocumentNameText = LOCTEXT("NewDocEventGraphName", "NewEventGraph");
|
|
bResetMyBlueprintFilter = true;
|
|
break;
|
|
case CGT_NewMacroGraph:
|
|
DocumentNameText = LOCTEXT("NewDocMacroName", "NewMacro");
|
|
bResetMyBlueprintFilter = true;
|
|
break;
|
|
case CGT_NewAnimationLayer:
|
|
DocumentNameText = LOCTEXT("NewDocAnimationLayerName", "NewAnimationLayer");
|
|
bResetMyBlueprintFilter = true;
|
|
break;
|
|
default:
|
|
DocumentNameText = LOCTEXT("NewDocNewName", "NewDocument");
|
|
break;
|
|
}
|
|
|
|
FName DocumentName = FName(*DocumentNameText.ToString());
|
|
|
|
// Make sure the new name is valid
|
|
DocumentName = FBlueprintEditorUtils::FindUniqueKismetName(GetBlueprintObj(), DocumentNameText.ToString());
|
|
|
|
check(IsEditingSingleBlueprint());
|
|
|
|
const FScopedTransaction Transaction( LOCTEXT("AddNewFunction", "Add New Function") );
|
|
GetBlueprintObj()->Modify();
|
|
|
|
UEdGraph* NewGraph = nullptr;
|
|
|
|
if (GraphType == CGT_NewFunctionGraph)
|
|
{
|
|
NewGraph = FBlueprintEditorUtils::CreateNewGraph(GetBlueprintObj(), DocumentName, UEdGraph::StaticClass(), GetDefaultSchemaClass());
|
|
FBlueprintEditorUtils::AddFunctionGraph<UClass>(GetBlueprintObj(), NewGraph, /*bIsUserCreated=*/ true, nullptr);
|
|
}
|
|
else if (GraphType == CGT_NewMacroGraph)
|
|
{
|
|
NewGraph = FBlueprintEditorUtils::CreateNewGraph(GetBlueprintObj(), DocumentName, UEdGraph::StaticClass(), GetDefaultSchemaClass());
|
|
FBlueprintEditorUtils::AddMacroGraph(GetBlueprintObj(), NewGraph, /*bIsUserCreated=*/ true, nullptr);
|
|
}
|
|
else if (GraphType == CGT_NewEventGraph)
|
|
{
|
|
NewGraph = FBlueprintEditorUtils::CreateNewGraph(GetBlueprintObj(), DocumentName, UEdGraph::StaticClass(), GetDefaultSchemaClass());
|
|
FBlueprintEditorUtils::AddUbergraphPage(GetBlueprintObj(), NewGraph);
|
|
}
|
|
else if (GraphType == CGT_NewAnimationLayer)
|
|
{
|
|
//@TODO: ANIMREFACTOR: This code belongs in Persona, not in BlueprintEditor
|
|
NewGraph = FBlueprintEditorUtils::CreateNewGraph(GetBlueprintObj(), DocumentName, UAnimationGraph::StaticClass(), UAnimationGraphSchema::StaticClass());
|
|
FBlueprintEditorUtils::AddDomainSpecificGraph(GetBlueprintObj(), NewGraph);
|
|
}
|
|
else
|
|
{
|
|
ensureMsgf(false, TEXT("GraphType is invalid") );
|
|
}
|
|
|
|
// Now open the new graph
|
|
if (NewGraph)
|
|
{
|
|
OpenDocument(NewGraph, FDocumentTracker::OpenNewDocument);
|
|
|
|
RenameNewlyAddedAction(DocumentName);
|
|
}
|
|
else
|
|
{
|
|
LogSimpleMessage( LOCTEXT("AddDocument_Error", "Adding new document failed.") );
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::NewDocument_IsVisibleForType(ECreatedDocumentType GraphType) const
|
|
{
|
|
switch (GraphType)
|
|
{
|
|
case CGT_NewVariable:
|
|
return (GetBlueprintObj()->BlueprintType != BPTYPE_FunctionLibrary)
|
|
&& (GetBlueprintObj()->BlueprintType != BPTYPE_Interface)
|
|
&& (GetBlueprintObj()->BlueprintType != BPTYPE_MacroLibrary);
|
|
case CGT_NewFunctionGraph:
|
|
{
|
|
if(UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(GetBlueprintObj()))
|
|
{
|
|
return (GetBlueprintObj()->BlueprintType != BPTYPE_Interface);
|
|
}
|
|
else
|
|
{
|
|
return (GetBlueprintObj()->BlueprintType != BPTYPE_MacroLibrary);
|
|
}
|
|
}
|
|
case CGT_NewMacroGraph:
|
|
return (GetBlueprintObj()->BlueprintType == BPTYPE_MacroLibrary) || (GetBlueprintObj()->BlueprintType == BPTYPE_Normal) || (GetBlueprintObj()->BlueprintType == BPTYPE_LevelScript);
|
|
case CGT_NewAnimationLayer:
|
|
{
|
|
if(UAnimBlueprint* AnimBlueprint = Cast<UAnimBlueprint>(GetBlueprintObj()))
|
|
{
|
|
UAnimBlueprint* RootBlueprint = UAnimBlueprint::FindRootAnimBlueprint(AnimBlueprint);
|
|
if(RootBlueprint == nullptr)
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
break;
|
|
case CGT_NewEventGraph:
|
|
return FBlueprintEditorUtils::DoesSupportEventGraphs(GetBlueprintObj());
|
|
case CGT_NewLocalVariable:
|
|
return FBlueprintEditorUtils::DoesSupportLocalVariables(GetFocusedGraph())
|
|
&& IsFocusedGraphEditable();
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
TSubclassOf<UEdGraphSchema> FBlueprintEditor::GetDefaultSchemaClass() const
|
|
{
|
|
return UEdGraphSchema_K2::StaticClass();
|
|
}
|
|
|
|
bool FBlueprintEditor::AddNewDelegateIsVisible() const
|
|
{
|
|
const UBlueprint* Blueprint = GetBlueprintObj();
|
|
return (nullptr != Blueprint)
|
|
&& (Blueprint->BlueprintType != BPTYPE_Interface)
|
|
&& (Blueprint->BlueprintType != BPTYPE_MacroLibrary)
|
|
&& (Blueprint->BlueprintType != BPTYPE_FunctionLibrary);
|
|
}
|
|
|
|
void FBlueprintEditor::NotifyPreChange(FProperty* PropertyAboutToChange)
|
|
{
|
|
// this only delivers message to the "FOCUSED" one, not every one
|
|
// internally it will only deliver the message to the selected node, not all nodes
|
|
FString PropertyName = PropertyAboutToChange->GetName();
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->NotifyPrePropertyChange(PropertyName);
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::NotifyPostChange(const FPropertyChangedEvent& PropertyChangedEvent, FProperty* PropertyThatChanged)
|
|
{
|
|
FString PropertyName = PropertyThatChanged->GetName();
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
FocusedGraphEdPtr.Pin()->NotifyPostPropertyChange(PropertyChangedEvent, PropertyName);
|
|
}
|
|
|
|
if (IsEditingSingleBlueprint())
|
|
{
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
UPackage* BlueprintPackage = Blueprint->GetOutermost();
|
|
|
|
// if any of the objects being edited are in our package, mark us as dirty
|
|
bool bPropertyInBlueprint = false;
|
|
for (int32 ObjectIndex = 0; ObjectIndex < PropertyChangedEvent.GetNumObjectsBeingEdited(); ++ObjectIndex)
|
|
{
|
|
const UObject* Object = PropertyChangedEvent.GetObjectBeingEdited(ObjectIndex);
|
|
if (Object && Object->GetOutermost() == BlueprintPackage)
|
|
{
|
|
bPropertyInBlueprint = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (bPropertyInBlueprint)
|
|
{
|
|
// Note: if change type is "interactive," hold off on applying the change (e.g. this will occur if the user is scrubbing a spinbox value; we don't want to apply the change until the mouse is released, for performance reasons)
|
|
if (PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
|
|
{
|
|
FBlueprintEditorUtils::MarkBlueprintAsModified(Blueprint, PropertyChangedEvent);
|
|
|
|
// Call PostEditChange() on any Actors that might be based on this Blueprint
|
|
FBlueprintEditorUtils::PostEditChangeBlueprintActors(Blueprint);
|
|
}
|
|
|
|
// Force updates to occur immediately during interactive mode (otherwise the preview won't refresh because it won't be ticking)
|
|
UpdateSubobjectPreview(PropertyChangedEvent.ChangeType == EPropertyChangeType::Interactive);
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::ShouldShowPublicViewControl() const
|
|
{
|
|
// In defaults-only mode, hide the "Public View" checkbox when Class Settings is selected into the Details view.
|
|
return !bWasOpenedInDefaultsMode || CurrentUISelection != FBlueprintEditor::SelectionState_ClassSettings;
|
|
}
|
|
|
|
void FBlueprintEditor::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
FName PropertyName = PropertyChangedEvent.GetPropertyName();
|
|
FName MemberPropertyName = PropertyChangedEvent.MemberProperty ? PropertyChangedEvent.MemberProperty->GetFName() : PropertyName;
|
|
|
|
//@TODO: This code does not belong here (might not even be necessary anymore as they seem to have PostEditChangeProperty impls now)!
|
|
if ((PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_Switch, bHasDefaultPin)) ||
|
|
(PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_SwitchInteger, StartIndex)) ||
|
|
(PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_SwitchString, PinNames)) ||
|
|
(PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_SwitchName, PinNames)) ||
|
|
(PropertyName == GET_MEMBER_NAME_CHECKED(UK2Node_SwitchString, bIsCaseSensitive)))
|
|
{
|
|
DocumentManager->RefreshAllTabs();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnPreObjectPropertyChanged(UObject* InObject, const FEditPropertyChain& EditPropertyChain)
|
|
{
|
|
if (GetDefault<UBlueprintEditorSettings>()->bDoNotMarkAllInstancesDirtyOnDefaultValueChange)
|
|
{
|
|
// Determine which inspector widget is currently in use.
|
|
TSharedPtr<SKismetInspector> CurrentInspectorWidget;
|
|
if (GetCurrentMode() == FBlueprintEditorApplicationModes::BlueprintDefaultsMode)
|
|
{
|
|
CurrentInspectorWidget = DefaultEditor;
|
|
}
|
|
else
|
|
{
|
|
CurrentInspectorWidget = Inspector;
|
|
}
|
|
|
|
// Note: While we could rely on our notify hook to determine whether an incoming change belongs to this
|
|
// context, NotifyPreChange() may end up getting called after this event is broadcast (e.g. from inside
|
|
// of a details customization). So it's not safe to assume they will get called in any specific order.
|
|
// It is, however, safe to assume this will get called for an archetype/CDO prior to change propagation.
|
|
if (CurrentInspectorWidget.IsValid())
|
|
{
|
|
check(InObject);
|
|
|
|
auto IsSubobjectOfAnySelectedObject = [](const UObject* InObject, const TArray<TWeakObjectPtr<UObject>>& SelectedObjects)
|
|
{
|
|
for (const TWeakObjectPtr<UObject>& SelectedObject : SelectedObjects)
|
|
{
|
|
if (SelectedObject.IsValid() && InObject->IsInOuter(SelectedObject.Get()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
};
|
|
|
|
// If we're modifying an object that's selected into our property editor context (e.g. the Blueprint CDO, or an SCS
|
|
// component template), set up change propagation so that instances do not always mark their outer package as dirty.
|
|
bool bIsObjectSelectedForEditing = false;
|
|
const TArray<TWeakObjectPtr<UObject>>& SelectedObjects = CurrentInspectorWidget->GetSelectedObjects();
|
|
if (SelectedObjects.Contains(InObject) || IsSubobjectOfAnySelectedObject(InObject, SelectedObjects))
|
|
{
|
|
bIsObjectSelectedForEditing = true;
|
|
}
|
|
else if (IsEditingSingleBlueprint())
|
|
{
|
|
// Resolve the property that's about to be changed.
|
|
FProperty* PropertyAboutToChange = nullptr;
|
|
if (FEditPropertyChain::TDoubleLinkedListNode* PropertyNode = EditPropertyChain.GetActiveNode())
|
|
{
|
|
PropertyAboutToChange = PropertyNode->GetValue();
|
|
}
|
|
|
|
// When actions from the "My Blueprint" tab are selected (e.g. class variable actions), a property "wrapper"
|
|
// object is selected to the inspector view instead, and the details customization will then pass the object
|
|
// instance that's about to be changed (e.g. the Blueprint CDO) as the input parameter to this delegate.
|
|
if (PropertyAboutToChange)
|
|
{
|
|
const UBlueprint* BlueprintContext = GetBlueprintObj();
|
|
check(BlueprintContext);
|
|
|
|
// Redirect to the skeleton class if the property's owner class was generated by the Blueprint that's
|
|
// associated with this editing context. For details customization purposes, the property that's wrapped
|
|
// belongs to the skeleton class and not the actual generated class (see SMyBlueprint::CollectAllActions).
|
|
const UClass* OwnerClass = PropertyAboutToChange->GetOwnerClass();
|
|
if (OwnerClass && OwnerClass->ClassGeneratedBy == BlueprintContext && BlueprintContext->SkeletonGeneratedClass)
|
|
{
|
|
PropertyAboutToChange = BlueprintContext->SkeletonGeneratedClass->FindPropertyByName(PropertyAboutToChange->GetFName());
|
|
if (PropertyAboutToChange && SelectedObjects.Contains(PropertyAboutToChange->GetUPropertyWrapper()) && InObject == OwnerClass->GetDefaultObject())
|
|
{
|
|
bIsObjectSelectedForEditing = true;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bIsObjectSelectedForEditing)
|
|
{
|
|
InObject->SetEditChangePropagationFlags(EEditChangePropagationFlags::OnlyMarkRealignedInstancesAsDirty);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnPostObjectPropertyChanged(UObject* InObject, FPropertyChangedEvent& PropertyChangedEvent)
|
|
{
|
|
check(InObject);
|
|
|
|
// Reset propagation flags so that any changes external to our editing context will use the standard (default) behavior.
|
|
InObject->SetEditChangePropagationFlags(EEditChangePropagationFlags::None);
|
|
}
|
|
|
|
FName FBlueprintEditor::GetToolkitFName() const
|
|
{
|
|
return FName("BlueprintEditor");
|
|
}
|
|
|
|
FName FBlueprintEditor::GetContextFromBlueprintType(EBlueprintType InType)
|
|
{
|
|
switch (InType)
|
|
{
|
|
default:
|
|
case BPTYPE_Normal:
|
|
return FName("BlueprintEditor");
|
|
case BPTYPE_MacroLibrary:
|
|
return FName("BlueprintEditor.MacroLibrary");
|
|
case BPTYPE_Interface:
|
|
return FName("BlueprintEditor.Interface");
|
|
case BPTYPE_LevelScript:
|
|
return FName("BlueprintEditor.LevelScript");
|
|
}
|
|
}
|
|
|
|
FName FBlueprintEditor::GetToolkitContextFName() const
|
|
{
|
|
if(GetBlueprintObj())
|
|
{
|
|
return GetContextFromBlueprintType(GetBlueprintObj()->BlueprintType);
|
|
}
|
|
|
|
return FName("BlueprintEditor");
|
|
}
|
|
|
|
FText FBlueprintEditor::GetBaseToolkitName() const
|
|
{
|
|
return LOCTEXT( "AppLabel", "Blueprint Editor" );
|
|
}
|
|
|
|
FText FBlueprintEditor::GetToolkitName() const
|
|
{
|
|
const auto& EditingObjs = GetEditingObjects();
|
|
|
|
if( IsEditingSingleBlueprint() )
|
|
{
|
|
if (FBlueprintEditorUtils::IsLevelScriptBlueprint(GetBlueprintObj()))
|
|
{
|
|
const FString& LevelName = FPackageName::GetShortFName( GetBlueprintObj()->GetOutermost()->GetFName().GetPlainNameString() ).GetPlainNameString();
|
|
return FText::FromString(LevelName);
|
|
}
|
|
else
|
|
{
|
|
return FText::FromString(GetBlueprintObj()->GetName());
|
|
}
|
|
}
|
|
|
|
TSubclassOf< UObject > SharedParentClass = nullptr;
|
|
|
|
for (UObject* EditingObj : EditingObjs)
|
|
{
|
|
UBlueprint* Blueprint = Cast<UBlueprint>(EditingObj);
|
|
check( Blueprint );
|
|
|
|
// Initialize with the class of the first object we encounter.
|
|
if( *SharedParentClass == nullptr )
|
|
{
|
|
SharedParentClass = Blueprint->ParentClass;
|
|
}
|
|
|
|
// If we've encountered an object that's not a subclass of the current best baseclass,
|
|
// climb up a step in the class hierarchy.
|
|
while( !Blueprint->ParentClass->IsChildOf( SharedParentClass ) )
|
|
{
|
|
SharedParentClass = SharedParentClass->GetSuperClass();
|
|
}
|
|
}
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("NumberOfObjects"), EditingObjs.Num() );
|
|
Args.Add( TEXT("ObjectName"), FText::FromString( SharedParentClass->GetName() ) );
|
|
return FText::Format( NSLOCTEXT("KismetEditor", "ToolkitTitle_UniqueLayerName", "{NumberOfObjects} {ClassName} - Class Defaults"), Args );
|
|
}
|
|
|
|
FText FBlueprintEditor::GetToolkitToolTipText() const
|
|
{
|
|
const auto& EditingObjs = GetEditingObjects();
|
|
|
|
if( IsEditingSingleBlueprint() )
|
|
{
|
|
if (FBlueprintEditorUtils::IsLevelScriptBlueprint(GetBlueprintObj()))
|
|
{
|
|
const FString& LevelName = FPackageName::GetShortFName( GetBlueprintObj()->GetOutermost()->GetFName().GetPlainNameString() ).GetPlainNameString();
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("LevelName"), FText::FromString( LevelName ) );
|
|
return FText::Format( NSLOCTEXT("KismetEditor", "LevelScriptAppToolTip", "{LevelName} - Level Blueprint Editor"), Args );
|
|
}
|
|
else
|
|
{
|
|
return FAssetEditorToolkit::GetToolTipTextForObject( GetBlueprintObj() );
|
|
}
|
|
}
|
|
|
|
TSubclassOf< UObject > SharedParentClass = nullptr;
|
|
|
|
for (UObject* EditingObj : EditingObjs)
|
|
{
|
|
UBlueprint* Blueprint = Cast<UBlueprint>(EditingObj);
|
|
check( Blueprint );
|
|
|
|
// Initialize with the class of the first object we encounter.
|
|
if( *SharedParentClass == nullptr )
|
|
{
|
|
SharedParentClass = Blueprint->ParentClass;
|
|
}
|
|
|
|
// If we've encountered an object that's not a subclass of the current best baseclass,
|
|
// climb up a step in the class hierarchy.
|
|
while( !Blueprint->ParentClass->IsChildOf( SharedParentClass ) )
|
|
{
|
|
SharedParentClass = SharedParentClass->GetSuperClass();
|
|
}
|
|
}
|
|
|
|
FFormatNamedArguments Args;
|
|
Args.Add( TEXT("NumberOfObjects"), EditingObjs.Num() );
|
|
Args.Add( TEXT("ClassName"), FText::FromString( SharedParentClass->GetName() ) );
|
|
return FText::Format( NSLOCTEXT("KismetEditor", "ToolkitTitle_UniqueLayerName", "{NumberOfObjects} {ClassName} - Class Defaults"), Args );
|
|
}
|
|
|
|
FLinearColor FBlueprintEditor::GetWorldCentricTabColorScale() const
|
|
{
|
|
if ((IsEditingSingleBlueprint()) && FBlueprintEditorUtils::IsLevelScriptBlueprint(GetBlueprintObj()))
|
|
{
|
|
return FLinearColor( 0.0f, 0.2f, 0.3f, 0.5f );
|
|
}
|
|
else
|
|
{
|
|
return FLinearColor( 0.0f, 0.0f, 0.3f, 0.5f );
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::IsBlueprintEditor() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FString FBlueprintEditor::GetWorldCentricTabPrefix() const
|
|
{
|
|
check(IsEditingSingleBlueprint());
|
|
|
|
if (FBlueprintEditorUtils::IsLevelScriptBlueprint(GetBlueprintObj()))
|
|
{
|
|
return NSLOCTEXT("KismetEditor", "WorldCentricTabPrefix_LevelScript", "Script ").ToString();
|
|
}
|
|
else
|
|
{
|
|
return NSLOCTEXT("KismetEditor", "WorldCentricTabPrefix_Blueprint", "Blueprint ").ToString();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::VariableListWasUpdated()
|
|
{
|
|
StartEditingDefaults(/*bAutoFocus=*/ false);
|
|
}
|
|
|
|
bool FBlueprintEditor::GetBoundsForSelectedNodes(class FSlateRect& Rect, float Padding)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
return FocusedGraphEd->GetBoundsForSelectedNodes(Rect, Padding);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnRenameNode()
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if(FocusedGraphEd.IsValid())
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*NodeIt);
|
|
if (SelectedNode != nullptr && SelectedNode->GetCanRenameNode())
|
|
{
|
|
FKismetEditorUtilities::BringKismetToFocusAttentionOnObject(SelectedNode, true);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRenameNodes() const
|
|
{
|
|
if (IsEditable(GetFocusedGraph()))
|
|
{
|
|
if (const UEdGraphNode* SelectedNode = GetSingleSelectedNode())
|
|
{
|
|
return SelectedNode->GetCanRenameNode();
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool FBlueprintEditor::OnNodeVerifyTitleCommit(const FText& NewText, UEdGraphNode* NodeBeingChanged, FText& OutErrorMessage)
|
|
{
|
|
bool bValid(false);
|
|
|
|
if (NodeBeingChanged && NodeBeingChanged->GetCanRenameNode())
|
|
{
|
|
// Clear off any existing error message
|
|
NodeBeingChanged->ErrorMsg.Empty();
|
|
NodeBeingChanged->bHasCompilerMessage = false;
|
|
|
|
if (!NameEntryValidator.IsValid())
|
|
{
|
|
NameEntryValidator = FNameValidatorFactory::MakeValidator(NodeBeingChanged);
|
|
}
|
|
|
|
EValidatorResult VResult = NameEntryValidator->IsValid(NewText.ToString(), true);
|
|
if (VResult == EValidatorResult::Ok)
|
|
{
|
|
bValid = true;
|
|
}
|
|
else if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
EValidatorResult Valid = NameEntryValidator->IsValid(NewText.ToString(), false);
|
|
|
|
NodeBeingChanged->bHasCompilerMessage = true;
|
|
NodeBeingChanged->ErrorMsg = NameEntryValidator->GetErrorString(NewText.ToString(), Valid);
|
|
NodeBeingChanged->ErrorType = EMessageSeverity::Error;
|
|
}
|
|
}
|
|
NameEntryValidator.Reset();
|
|
|
|
return bValid;
|
|
}
|
|
|
|
void FBlueprintEditor::OnNodeTitleCommitted(const FText& NewText, ETextCommit::Type CommitInfo, UEdGraphNode* NodeBeingChanged)
|
|
{
|
|
if (NodeBeingChanged)
|
|
{
|
|
const FScopedTransaction Transaction( NSLOCTEXT( "K2_RenameNode", "RenameNode", "Rename Node" ) );
|
|
NodeBeingChanged->Modify();
|
|
NodeBeingChanged->OnRenameNode(NewText.ToString());
|
|
if (BookmarksWidget.IsValid())
|
|
{
|
|
BookmarksWidget->RefreshBookmarksTree();
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/////////////////////////////////////////////////////
|
|
|
|
void FBlueprintEditor::OnEditTabClosed(TSharedRef<SDockTab> Tab)
|
|
{
|
|
// Update the edited object state
|
|
if (GetBlueprintObj())
|
|
{
|
|
SaveEditedObjectState();
|
|
}
|
|
}
|
|
|
|
// Tries to open the specified graph and bring it's document to the front
|
|
TSharedPtr<SGraphEditor> FBlueprintEditor::OpenGraphAndBringToFront(UEdGraph* Graph, bool bSetFocus)
|
|
{
|
|
if (!IsValid(Graph))
|
|
{
|
|
return TSharedPtr<SGraphEditor>();
|
|
}
|
|
|
|
// First, switch back to standard mode
|
|
SetCurrentMode(FBlueprintEditorApplicationModes::StandardBlueprintEditorMode);
|
|
|
|
// This will either reuse an existing tab or spawn a new one
|
|
TSharedPtr<SDockTab> TabWithGraph = OpenDocument(Graph, FDocumentTracker::OpenNewDocument);
|
|
if (TabWithGraph.IsValid())
|
|
{
|
|
|
|
// We know that the contents of the opened tabs will be a graph editor.
|
|
TSharedRef<SGraphEditor> NewGraphEditor = StaticCastSharedRef<SGraphEditor>(TabWithGraph->GetContent());
|
|
|
|
// Handover the keyboard focus to the new graph editor widget.
|
|
if (bSetFocus)
|
|
{
|
|
NewGraphEditor->CaptureKeyboard();
|
|
}
|
|
|
|
return NewGraphEditor;
|
|
}
|
|
else
|
|
{
|
|
return TSharedPtr<SGraphEditor>();
|
|
}
|
|
}
|
|
|
|
TSharedPtr<SDockTab> FBlueprintEditor::OpenDocument(const UObject* DocumentID, FDocumentTracker::EOpenDocumentCause Cause)
|
|
{
|
|
TSharedRef<FTabPayload_UObject> Payload = FTabPayload_UObject::Make(DocumentID);
|
|
return DocumentManager->OpenDocument(Payload, Cause);
|
|
}
|
|
|
|
void FBlueprintEditor::NavigateTab(FDocumentTracker::EOpenDocumentCause InCause)
|
|
{
|
|
OpenDocument(nullptr, InCause);
|
|
}
|
|
|
|
void FBlueprintEditor::CloseDocumentTab(const UObject* DocumentID)
|
|
{
|
|
TSharedRef<FTabPayload_UObject> Payload = FTabPayload_UObject::Make(DocumentID);
|
|
DocumentManager->CloseTab(Payload);
|
|
}
|
|
|
|
// Finds any open tabs containing the specified document and adds them to the specified array; returns true if at least one is found
|
|
bool FBlueprintEditor::FindOpenTabsContainingDocument(const UObject* DocumentID, /*inout*/ TArray< TSharedPtr<SDockTab> >& Results)
|
|
{
|
|
int32 StartingCount = Results.Num();
|
|
|
|
TSharedRef<FTabPayload_UObject> Payload = FTabPayload_UObject::Make(DocumentID);
|
|
|
|
DocumentManager->FindMatchingTabs( Payload, /*inout*/ Results);
|
|
|
|
// Did we add anything new?
|
|
return (StartingCount != Results.Num());
|
|
}
|
|
|
|
void FBlueprintEditor::RestoreEditedObjectState()
|
|
{
|
|
check(IsEditingSingleBlueprint());
|
|
|
|
UBlueprint* Blueprint = GetBlueprintObj();
|
|
if (Blueprint->LastEditedDocuments.Num() == 0)
|
|
{
|
|
if (FBlueprintEditorUtils::SupportsConstructionScript(Blueprint))
|
|
{
|
|
Blueprint->LastEditedDocuments.Add(FBlueprintEditorUtils::FindUserConstructionScript(Blueprint));
|
|
}
|
|
|
|
if (Blueprint->SupportsEventGraphs())
|
|
{
|
|
Blueprint->LastEditedDocuments.Add(FBlueprintEditorUtils::FindEventGraph(Blueprint));
|
|
}
|
|
}
|
|
|
|
TSet<FSoftObjectPath> PathsToRemove;
|
|
for (int32 i = 0; i < Blueprint->LastEditedDocuments.Num(); i++)
|
|
{
|
|
if (UObject* Obj = Blueprint->LastEditedDocuments[i].EditedObjectPath.ResolveObject())
|
|
{
|
|
if(UEdGraph* Graph = Cast<UEdGraph>(Obj))
|
|
{
|
|
if (FBlueprintEditorUtils::IsEventGraph(Graph) && !Blueprint->SupportsEventGraphs())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
struct LocalStruct
|
|
{
|
|
static TSharedPtr<SDockTab> OpenGraphTree(FBlueprintEditor* InBlueprintEditor, UEdGraph* InGraph)
|
|
{
|
|
FDocumentTracker::EOpenDocumentCause OpenCause = FDocumentTracker::QuickNavigateCurrentDocument;
|
|
|
|
for (UObject* OuterObject = InGraph->GetOuter(); OuterObject; OuterObject = OuterObject->GetOuter())
|
|
{
|
|
if (OuterObject->IsA<UBlueprint>())
|
|
{
|
|
// reached up to the blueprint for the graph, we are done climbing the tree
|
|
OpenCause = FDocumentTracker::RestorePreviousDocument;
|
|
break;
|
|
}
|
|
else if(UEdGraph* OuterGraph = Cast<UEdGraph>(OuterObject))
|
|
{
|
|
// Found another graph, open it up
|
|
OpenGraphTree(InBlueprintEditor, OuterGraph);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return InBlueprintEditor->OpenDocument(InGraph, OpenCause);
|
|
}
|
|
};
|
|
TSharedPtr<SDockTab> TabWithGraph = LocalStruct::OpenGraphTree(this, Graph);
|
|
if (TabWithGraph.IsValid())
|
|
{
|
|
TSharedRef<SGraphEditor> GraphEditor = StaticCastSharedRef<SGraphEditor>(TabWithGraph->GetContent());
|
|
GraphEditor->SetViewLocation(UE::Slate::CastToVector2f(Blueprint->LastEditedDocuments[i].SavedViewOffset), Blueprint->LastEditedDocuments[i].SavedZoomAmount);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
TSharedPtr<SDockTab> TabWithGraph = OpenDocument(Obj, FDocumentTracker::RestorePreviousDocument);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
PathsToRemove.Add(Blueprint->LastEditedDocuments[i].EditedObjectPath);
|
|
}
|
|
}
|
|
|
|
// Older assets may have neglected to clean up this array when referenced objects were deleted, so
|
|
// we'll check for that now. This is done to ensure we don't store invalid object paths indefinitely.
|
|
if (PathsToRemove.Num() > 0)
|
|
{
|
|
Blueprint->LastEditedDocuments.RemoveAll([&PathsToRemove](const FEditedDocumentInfo& Entry)
|
|
{
|
|
return PathsToRemove.Contains(Entry.EditedObjectPath);
|
|
});
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanRecompileModules()
|
|
{
|
|
// We're not able to recompile if a compile is already in progress!
|
|
return !IHotReloadModule::Get().IsCurrentlyCompiling();
|
|
}
|
|
|
|
void FBlueprintEditor::OnCreateComment()
|
|
{
|
|
TSharedPtr<SGraphEditor> GraphEditor = FocusedGraphEdPtr.Pin();
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
if (UEdGraph* Graph = GraphEditor->GetCurrentGraph())
|
|
{
|
|
if (const UEdGraphSchema* Schema = Graph->GetSchema())
|
|
{
|
|
if (Schema->IsA(UEdGraphSchema_K2::StaticClass()))
|
|
{
|
|
FEdGraphSchemaAction_K2AddComment CommentAction;
|
|
CommentAction.PerformAction(Graph, nullptr, GraphEditor->GetPasteLocation2f());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::OnCreateCustomEvent()
|
|
{
|
|
TSharedPtr<SGraphEditor> GraphEditor = FocusedGraphEdPtr.Pin();
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
if (UEdGraph* Graph = GraphEditor->GetCurrentGraph())
|
|
{
|
|
if (const UEdGraphSchema* Schema = Graph->GetSchema())
|
|
{
|
|
if (Schema->IsA(UEdGraphSchema_K2::StaticClass()))
|
|
{
|
|
// BlueprintEventNodeSpawner seems better but we'll use FEdGraphSchemaAction_K2AddCustomEvent
|
|
FEdGraphSchemaAction_K2AddCustomEvent EventAction;
|
|
EventAction.NodeTemplate = NewObject<UK2Node_CustomEvent>();
|
|
EventAction.PerformAction(Graph, nullptr, GraphEditor->GetPasteLocation2f());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SetPinVisibility(SGraphEditor::EPinVisibility Visibility)
|
|
{
|
|
PinVisibility = Visibility;
|
|
OnSetPinVisibility.Broadcast(PinVisibility);
|
|
}
|
|
|
|
void FBlueprintEditor::OnFindReferences(bool bSearchAllBlueprints, const EGetFindReferenceSearchStringFlags Flags)
|
|
{
|
|
TSharedPtr<SGraphEditor> GraphEditor = FocusedGraphEdPtr.Pin();
|
|
if (GraphEditor.IsValid())
|
|
{
|
|
const FGraphPanelSelectionSet SelectedNodes = GraphEditor->GetSelectedNodes();
|
|
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
|
|
{
|
|
if (UEdGraphNode* SelectedNode = Cast<UEdGraphNode>(*NodeIt))
|
|
{
|
|
FString SearchTerm = SelectedNode->GetFindReferenceSearchString(Flags);
|
|
if (!SearchTerm.IsEmpty())
|
|
{
|
|
// Start the search
|
|
const bool bSetFindWithinBlueprint = !bSearchAllBlueprints;
|
|
SummonSearchUI(bSetFindWithinBlueprint, SearchTerm);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FBlueprintEditor::CanFindReferences()
|
|
{
|
|
return GetSingleSelectedNode() != nullptr;
|
|
}
|
|
|
|
AActor* FBlueprintEditor::GetPreviewActor() const
|
|
{
|
|
UBlueprint* PreviewBlueprint = GetBlueprintObj();
|
|
|
|
// Note: The weak ptr can become stale if the actor is reinstanced due to a Blueprint change, etc. In that
|
|
// case we look to see if we can find the new instance in the preview world and then update the weak ptr.
|
|
if ( PreviewActorPtr.IsStale(true) && PreviewBlueprint )
|
|
{
|
|
UWorld* PreviewWorld = PreviewScene.GetWorld();
|
|
for ( TActorIterator<AActor> It(PreviewWorld); It; ++It )
|
|
{
|
|
AActor* Actor = *It;
|
|
if ( !Actor->IsPendingKillPending()
|
|
&& Actor->GetClass()->ClassGeneratedBy == PreviewBlueprint )
|
|
{
|
|
PreviewActorPtr = Actor;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
return PreviewActorPtr.Get();
|
|
}
|
|
|
|
void FBlueprintEditor::UpdatePreviewActor(UBlueprint* InBlueprint, bool bInForceFullUpdate/* = false*/)
|
|
{
|
|
// If the components mode isn't available there's no reason to update the preview actor.
|
|
if ( !CanAccessComponentsMode() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
|
|
// Signal that we're going to be constructing editor components
|
|
if ( InBlueprint != nullptr && InBlueprint->SimpleConstructionScript != nullptr )
|
|
{
|
|
InBlueprint->SimpleConstructionScript->BeginEditorComponentConstruction();
|
|
}
|
|
|
|
UBlueprint* PreviewBlueprint = GetBlueprintObj();
|
|
|
|
// If the Blueprint is changing
|
|
if ( InBlueprint != PreviewBlueprint || bInForceFullUpdate )
|
|
{
|
|
// Destroy the previous actor instance
|
|
DestroyPreview();
|
|
|
|
// Save the Blueprint we're creating a preview for
|
|
PreviewBlueprint = InBlueprint;
|
|
|
|
// Spawn a new preview actor based on the Blueprint's generated class if it's Actor-based
|
|
if ( PreviewBlueprint && PreviewBlueprint->GeneratedClass && PreviewBlueprint->GeneratedClass->IsChildOf(AActor::StaticClass()) )
|
|
{
|
|
FVector SpawnLocation = FVector::ZeroVector;
|
|
FRotator SpawnRotation = FRotator::ZeroRotator;
|
|
|
|
// Spawn an Actor based on the Blueprint's generated class
|
|
FActorSpawnParameters SpawnInfo;
|
|
SpawnInfo.SpawnCollisionHandlingOverride = ESpawnActorCollisionHandlingMethod::AlwaysSpawn;
|
|
SpawnInfo.bNoFail = true;
|
|
SpawnInfo.ObjectFlags = RF_Transient|RF_Transactional;
|
|
|
|
{
|
|
FMakeClassSpawnableOnScope TemporarilySpawnable(PreviewBlueprint->GeneratedClass);
|
|
PreviewActorPtr = PreviewActor = PreviewScene.GetWorld()->SpawnActor(PreviewBlueprint->GeneratedClass, &SpawnLocation, &SpawnRotation, SpawnInfo);
|
|
}
|
|
|
|
check(PreviewActor);
|
|
|
|
// Ensure that the actor is visible
|
|
if ( PreviewActor->IsHidden() )
|
|
{
|
|
PreviewActor->SetHidden(false);
|
|
PreviewActor->MarkComponentsRenderStateDirty();
|
|
}
|
|
|
|
// Prevent any audio from playing as a result of spawning
|
|
if (FAudioDeviceHandle AudioDevice = GEngine->GetMainAudioDevice())
|
|
{
|
|
AudioDevice->Flush(PreviewScene.GetWorld());
|
|
}
|
|
|
|
// Set the reference to the preview actor for component editing purposes
|
|
if ( PreviewBlueprint->SimpleConstructionScript != nullptr )
|
|
{
|
|
PreviewBlueprint->SimpleConstructionScript->SetComponentEditorActorInstance(PreviewActor);
|
|
}
|
|
}
|
|
}
|
|
else if ( PreviewActor )
|
|
{
|
|
PreviewActor->ReregisterAllComponents();
|
|
PreviewActor->RerunConstructionScripts();
|
|
}
|
|
|
|
// Signal that we're done constructing editor components
|
|
if ( InBlueprint != nullptr && InBlueprint->SimpleConstructionScript != nullptr )
|
|
{
|
|
InBlueprint->SimpleConstructionScript->EndEditorComponentConstruction();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::DestroyPreview()
|
|
{
|
|
// If the components mode isn't available there's no reason to delete the preview actor.
|
|
if ( !CanAccessComponentsMode() )
|
|
{
|
|
return;
|
|
}
|
|
|
|
AActor* PreviewActor = GetPreviewActor();
|
|
if ( PreviewActor != nullptr )
|
|
{
|
|
check(PreviewScene.GetWorld());
|
|
PreviewScene.GetWorld()->EditorDestroyActor(PreviewActor, false);
|
|
}
|
|
|
|
UBlueprint* PreviewBlueprint = GetBlueprintObj();
|
|
|
|
if ( PreviewBlueprint != nullptr )
|
|
{
|
|
if ( PreviewBlueprint->SimpleConstructionScript != nullptr
|
|
&& PreviewActor == PreviewBlueprint->SimpleConstructionScript->GetComponentEditorActorInstance() )
|
|
{
|
|
// Ensure that all editable component references are cleared
|
|
PreviewBlueprint->SimpleConstructionScript->ClearEditorComponentReferences();
|
|
|
|
// Clear the reference to the preview actor instance
|
|
PreviewBlueprint->SimpleConstructionScript->SetComponentEditorActorInstance(nullptr);
|
|
}
|
|
|
|
PreviewBlueprint = nullptr;
|
|
}
|
|
|
|
PreviewActorPtr = nullptr;
|
|
}
|
|
|
|
|
|
FReply FBlueprintEditor::OnSpawnGraphNodeByShortcut(FInputChord InChord, const FVector2f& InPosition, UEdGraph* InGraph)
|
|
{
|
|
PRAGMA_DISABLE_DEPRECATION_WARNINGS
|
|
return OnSpawnGraphNodeByShortcut(InChord, FVector2D(InPosition), InGraph);
|
|
PRAGMA_ENABLE_DEPRECATION_WARNINGS
|
|
}
|
|
|
|
FReply FBlueprintEditor::OnSpawnGraphNodeByShortcut(FInputChord InChord, const FVector2D& InPosition, UEdGraph* InGraph)
|
|
{
|
|
UEdGraph* Graph = InGraph;
|
|
if (Graph == nullptr)
|
|
{
|
|
return FReply::Handled();
|
|
}
|
|
|
|
FScopedTransaction Transaction(LOCTEXT("AddNode", "Add Node"));
|
|
|
|
TArray<UEdGraphNode*> OutNodes;
|
|
FVector2D NodeSpawnPos = InPosition;
|
|
FBlueprintSpawnNodeCommands::Get().GetGraphActionByChord(InChord, InGraph, NodeSpawnPos, OutNodes);
|
|
|
|
TSet<const UEdGraphNode*> NodesToSelect;
|
|
|
|
for (UEdGraphNode* CurrentNode : OutNodes)
|
|
{
|
|
NodesToSelect.Add(CurrentNode);
|
|
}
|
|
|
|
// Do not change node selection if no actions were performed
|
|
if(OutNodes.Num() > 0)
|
|
{
|
|
Graph->SelectNodeSet(NodesToSelect, /*bFromUI =*/true);
|
|
}
|
|
else
|
|
{
|
|
Transaction.Cancel();
|
|
}
|
|
|
|
return FReply::Handled();
|
|
}
|
|
|
|
void FBlueprintEditor::OnNodeSpawnedByKeymap()
|
|
{
|
|
UpdateNodeCreationStats( ENodeCreateAction::Keymap );
|
|
}
|
|
|
|
void FBlueprintEditor::UpdateNodeCreationStats( const ENodeCreateAction::Type CreateAction )
|
|
{
|
|
switch( CreateAction )
|
|
{
|
|
case ENodeCreateAction::MyBlueprintDragPlacement:
|
|
AnalyticsStats.MyBlueprintNodeDragPlacementCount++;
|
|
break;
|
|
case ENodeCreateAction::PaletteDragPlacement:
|
|
AnalyticsStats.PaletteNodeDragPlacementCount++;
|
|
break;
|
|
case ENodeCreateAction::GraphContext:
|
|
AnalyticsStats.NodeGraphContextCreateCount++;
|
|
break;
|
|
case ENodeCreateAction::PinContext:
|
|
AnalyticsStats.NodePinContextCreateCount++;
|
|
break;
|
|
case ENodeCreateAction::Keymap:
|
|
AnalyticsStats.NodeKeymapCreateCount++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
TSharedPtr<ISCSEditorCustomization> FBlueprintEditor::CustomizeSubobjectEditor(const USceneComponent* InComponentToCustomize) const
|
|
{
|
|
check(InComponentToCustomize);
|
|
const TSharedPtr<ISCSEditorCustomization>* FoundCustomization = SubobjectEditorCustomizations.Find(InComponentToCustomize->GetClass()->GetFName());
|
|
if(FoundCustomization)
|
|
{
|
|
return *FoundCustomization;
|
|
}
|
|
|
|
return TSharedPtr<ISCSEditorCustomization>();
|
|
}
|
|
|
|
FText FBlueprintEditor::GetPIEStatus() const
|
|
{
|
|
UBlueprint* CurrentBlueprint = GetBlueprintObj();
|
|
UWorld *DebugWorld = nullptr;
|
|
ENetMode NetMode = NM_Standalone;
|
|
if (CurrentBlueprint)
|
|
{
|
|
DebugWorld = CurrentBlueprint->GetWorldBeingDebugged();
|
|
if (DebugWorld)
|
|
{
|
|
NetMode = DebugWorld->GetNetMode();
|
|
}
|
|
else
|
|
{
|
|
UObject* ObjOuter = CurrentBlueprint->GetObjectBeingDebugged();
|
|
while(DebugWorld == nullptr && ObjOuter != nullptr)
|
|
{
|
|
ObjOuter = ObjOuter->GetOuter();
|
|
DebugWorld = Cast<UWorld>(ObjOuter);
|
|
}
|
|
|
|
if (DebugWorld)
|
|
{
|
|
// Redirect through streaming levels to find the owning world; this ensures that we always use the appropriate NetMode for the context string below.
|
|
if (DebugWorld->PersistentLevel != nullptr && DebugWorld->PersistentLevel->OwningWorld != nullptr)
|
|
{
|
|
DebugWorld = DebugWorld->PersistentLevel->OwningWorld;
|
|
}
|
|
|
|
NetMode = DebugWorld->GetNetMode();
|
|
}
|
|
}
|
|
}
|
|
|
|
if (NetMode == NM_ListenServer || NetMode == NM_DedicatedServer)
|
|
{
|
|
return LOCTEXT("PIEStatusServerSimulating", "SERVER - SIMULATING");
|
|
}
|
|
else if (NetMode == NM_Client)
|
|
{
|
|
FWorldContext* PIEContext = GEngine->GetWorldContextFromWorld(DebugWorld);
|
|
if (PIEContext && PIEContext->PIEInstance > 1)
|
|
{
|
|
return FText::Format(LOCTEXT("PIEStatusClientSimulatingFormat", "CLIENT {0} - SIMULATING"), FText::AsNumber(PIEContext->PIEInstance - 1));
|
|
}
|
|
|
|
return LOCTEXT("PIEStatusClientSimulating", "CLIENT - SIMULATING");
|
|
}
|
|
|
|
return LOCTEXT("PIEStatusSimulating", "SIMULATING");
|
|
}
|
|
|
|
bool FBlueprintEditor::IsEditingAnimGraph() const
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
if (UEdGraph* CurrentGraph = FocusedGraphEdPtr.Pin()->GetCurrentGraph())
|
|
{
|
|
if (CurrentGraph->Schema->IsChildOf(UAnimationGraphSchema::StaticClass()) || (CurrentGraph->Schema == UAnimationStateMachineSchema::StaticClass()))
|
|
{
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
UEdGraph* FBlueprintEditor::GetFocusedGraph() const
|
|
{
|
|
if (FocusedGraphEdPtr.IsValid())
|
|
{
|
|
if (UEdGraph* Graph = FocusedGraphEdPtr.Pin()->GetCurrentGraph())
|
|
{
|
|
if (IsValid(Graph))
|
|
{
|
|
return Graph;
|
|
}
|
|
}
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
bool FBlueprintEditor::IsEditable(UEdGraph* InGraph) const
|
|
{
|
|
return InEditingMode() && !FBlueprintEditorUtils::IsGraphReadOnly(InGraph);
|
|
}
|
|
|
|
bool FBlueprintEditor::IsGraphReadOnly(UEdGraph* InGraph) const
|
|
{
|
|
return FBlueprintEditorUtils::IsGraphReadOnly(InGraph);
|
|
}
|
|
|
|
float FBlueprintEditor::GetInstructionTextOpacity(UEdGraph* InGraph) const
|
|
{
|
|
UBlueprintEditorSettings const* Settings = GetDefault<UBlueprintEditorSettings>();
|
|
if ((InGraph == nullptr) || !IsEditable(InGraph) || FBlueprintEditorUtils::IsGraphReadOnly(InGraph) || !Settings->bShowGraphInstructionText)
|
|
{
|
|
return 0.0f;
|
|
}
|
|
else if ((InstructionsFadeCountdown > 0.0f) || (HasOpenActionMenu == InGraph))
|
|
{
|
|
return InstructionsFadeCountdown / BlueprintEditorImpl::InstructionFadeDuration;
|
|
}
|
|
else if (BlueprintEditorImpl::GraphHasUserPlacedNodes(InGraph))
|
|
{
|
|
return 0.0f;
|
|
}
|
|
return 1.0f;
|
|
}
|
|
|
|
FText FBlueprintEditor::GetGraphDisplayName(const UEdGraph* Graph)
|
|
{
|
|
return FLocalKismetCallbacks::GetGraphDisplayName(Graph);
|
|
}
|
|
|
|
|
|
FText FBlueprintEditor::GetGraphDecorationString(UEdGraph* InGraph) const
|
|
{
|
|
return FText::GetEmpty();
|
|
}
|
|
|
|
bool FBlueprintEditor::IsGraphInCurrentBlueprint(const UEdGraph* InGraph) const
|
|
{
|
|
bool bEditable = true;
|
|
|
|
UBlueprint* EditingBP = GetBlueprintObj();
|
|
if(EditingBP)
|
|
{
|
|
TArray<UEdGraph*> Graphs;
|
|
EditingBP->GetAllGraphs(Graphs);
|
|
bEditable &= Graphs.Contains(InGraph);
|
|
}
|
|
|
|
return bEditable;
|
|
}
|
|
|
|
bool FBlueprintEditor::IsFocusedGraphEditable() const
|
|
{
|
|
UEdGraph* FocusedGraph = GetFocusedGraph();
|
|
if (FocusedGraph != nullptr)
|
|
{
|
|
return IsEditable(FocusedGraph);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
void FBlueprintEditor::TryInvokingDetailsTab(bool bFlash)
|
|
{
|
|
if ( TabManager->HasTabSpawner(FBlueprintEditorTabs::DetailsID) )
|
|
{
|
|
TSharedPtr<SDockTab> BlueprintTab = FGlobalTabmanager::Get()->GetMajorTabForTabManager(TabManager.ToSharedRef());
|
|
|
|
// We don't want to force this tab into existence when the blueprint editor isn't in the foreground and actively
|
|
// being interacted with. So we make sure the window it's in is focused and the tab is in the foreground.
|
|
if ( BlueprintTab.IsValid() && BlueprintTab->IsForeground() )
|
|
{
|
|
TSharedPtr<SWindow> ParentWindow = BlueprintTab->GetParentWindow();
|
|
if ( ParentWindow.IsValid() && ParentWindow->HasFocusedDescendants() )
|
|
{
|
|
if ( !Inspector.IsValid() || !Inspector->GetOwnerTab().IsValid() || Inspector->GetOwnerTab()->GetDockArea().IsValid() )
|
|
{
|
|
// Show the details panel if it doesn't exist.
|
|
TabManager->TryInvokeTab(FBlueprintEditorTabs::DetailsID);
|
|
|
|
if ( bFlash )
|
|
{
|
|
TSharedPtr<SDockTab> OwnerTab = Inspector->GetOwnerTab();
|
|
if ( OwnerTab.IsValid() )
|
|
{
|
|
OwnerTab->FlashTab();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SelectGraphActionItemByName(const FName& ItemName, ESelectInfo::Type SelectInfo, int32 SectionId, bool bIsCategory)
|
|
{
|
|
if (MyBlueprintWidget.IsValid() && Inspector.IsValid())
|
|
{
|
|
// Select Item in "My Blueprint"
|
|
MyBlueprintWidget->SelectItemByName(ItemName, SelectInfo, SectionId, bIsCategory);
|
|
|
|
// Find associated variable
|
|
if (FEdGraphSchemaAction_K2Var* SelectedVar = MyBlueprintWidget->SelectionAsVar())
|
|
{
|
|
if (FProperty* SelectedProperty = SelectedVar->GetProperty())
|
|
{
|
|
// Update Details Panel
|
|
Inspector->ShowDetailsForSingleObject(SelectedProperty->GetUPropertyWrapper());
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FBPEditorBookmarkNode* FBlueprintEditor::AddBookmark(const FText& DisplayName, const FEditedDocumentInfo& BookmarkInfo, bool bSharedBookmark)
|
|
{
|
|
FBPEditorBookmarkNode* NewNode = nullptr;
|
|
|
|
if (bSharedBookmark)
|
|
{
|
|
if (UBlueprint* Blueprint = GetBlueprintObj())
|
|
{
|
|
NewNode = new(Blueprint->BookmarkNodes) FBPEditorBookmarkNode;
|
|
NewNode->NodeGuid = FGuid::NewGuid();
|
|
NewNode->DisplayName = DisplayName;
|
|
|
|
Blueprint->Modify();
|
|
Blueprint->Bookmarks.Add(NewNode->NodeGuid, BookmarkInfo);
|
|
}
|
|
}
|
|
else if(UBlueprintEditorSettings* LocalSettings = GetMutableDefault<UBlueprintEditorSettings>())
|
|
{
|
|
NewNode = new(LocalSettings->BookmarkNodes) FBPEditorBookmarkNode;
|
|
NewNode->NodeGuid = FGuid::NewGuid();
|
|
NewNode->DisplayName = DisplayName;
|
|
|
|
LocalSettings->Bookmarks.Add(NewNode->NodeGuid, BookmarkInfo);
|
|
LocalSettings->SaveConfig();
|
|
}
|
|
|
|
if (NewNode && BookmarksWidget.IsValid())
|
|
{
|
|
BookmarksWidget->RefreshBookmarksTree();
|
|
}
|
|
|
|
return NewNode;
|
|
}
|
|
|
|
void FBlueprintEditor::RenameBookmark(const FGuid& BookmarkNodeId, const FText& NewName)
|
|
{
|
|
bool bFoundSharedBookmark = false;
|
|
if (UBlueprint* Blueprint = GetBlueprintObj())
|
|
{
|
|
for (FBPEditorBookmarkNode& BookmarkNode : Blueprint->BookmarkNodes)
|
|
{
|
|
if (BookmarkNode.NodeGuid == BookmarkNodeId)
|
|
{
|
|
Blueprint->Modify();
|
|
BookmarkNode.DisplayName = NewName;
|
|
|
|
bFoundSharedBookmark = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bFoundSharedBookmark)
|
|
{
|
|
UBlueprintEditorSettings* LocalSettings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
for (FBPEditorBookmarkNode& BookmarkNode : LocalSettings->BookmarkNodes)
|
|
{
|
|
if (BookmarkNode.NodeGuid == BookmarkNodeId)
|
|
{
|
|
BookmarkNode.DisplayName = NewName;
|
|
LocalSettings->SaveConfig();
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (BookmarksWidget.IsValid())
|
|
{
|
|
BookmarksWidget->RefreshBookmarksTree();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::RemoveBookmark(const FGuid& BookmarkNodeId, bool bRefreshUI)
|
|
{
|
|
bool bFoundSharedBookmark = false;
|
|
if (UBlueprint* Blueprint = GetBlueprintObj())
|
|
{
|
|
for (int32 i = 0; i < Blueprint->BookmarkNodes.Num(); ++i)
|
|
{
|
|
const FBPEditorBookmarkNode& BookmarkNode = Blueprint->BookmarkNodes[i];
|
|
if (BookmarkNode.NodeGuid == BookmarkNodeId)
|
|
{
|
|
Blueprint->Modify();
|
|
Blueprint->BookmarkNodes.RemoveAtSwap(i);
|
|
FEditedDocumentInfo BookmarkInfo = Blueprint->Bookmarks.FindAndRemoveChecked(BookmarkNodeId);
|
|
|
|
FGuid CurrentBookmarkId;
|
|
GetViewBookmark(CurrentBookmarkId);
|
|
if (CurrentBookmarkId == BookmarkNodeId)
|
|
{
|
|
SetViewLocation(BookmarkInfo.SavedViewOffset, BookmarkInfo.SavedZoomAmount);
|
|
}
|
|
|
|
bFoundSharedBookmark = true;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!bFoundSharedBookmark)
|
|
{
|
|
UBlueprintEditorSettings* LocalSettings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
for (int32 i = 0; i < LocalSettings->BookmarkNodes.Num(); ++i)
|
|
{
|
|
const FBPEditorBookmarkNode& BookmarkNode = LocalSettings->BookmarkNodes[i];
|
|
if (BookmarkNode.NodeGuid == BookmarkNodeId)
|
|
{
|
|
LocalSettings->BookmarkNodes.RemoveAtSwap(i);
|
|
FEditedDocumentInfo BookmarkInfo = LocalSettings->Bookmarks.FindAndRemoveChecked(BookmarkNodeId);
|
|
LocalSettings->SaveConfig();
|
|
|
|
FGuid CurrentBookmarkId;
|
|
GetViewBookmark(CurrentBookmarkId);
|
|
if (CurrentBookmarkId == BookmarkNodeId)
|
|
{
|
|
SetViewLocation(BookmarkInfo.SavedViewOffset, BookmarkInfo.SavedZoomAmount);
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (bRefreshUI && BookmarksWidget.IsValid())
|
|
{
|
|
BookmarksWidget->RefreshBookmarksTree();
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::SetGraphEditorQuickJump(int32 QuickJumpIndex)
|
|
{
|
|
TSharedPtr<SGraphEditor> FocusedGraphEd = FocusedGraphEdPtr.Pin();
|
|
if (FocusedGraphEd.IsValid())
|
|
{
|
|
if (UEdGraph* GraphObject = FocusedGraphEd->GetCurrentGraph())
|
|
{
|
|
UBlueprintEditorSettings* LocalSettings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
FEditedDocumentInfo& QuickJumpInfo = LocalSettings->GraphEditorQuickJumps.FindOrAdd(QuickJumpIndex);
|
|
|
|
QuickJumpInfo.EditedObjectPath = GraphObject;
|
|
FVector2f TempViewOffset = UE::Slate::CastToVector2f(QuickJumpInfo.SavedViewOffset);
|
|
FocusedGraphEd->GetViewLocation(TempViewOffset, QuickJumpInfo.SavedZoomAmount);
|
|
QuickJumpInfo.SavedViewOffset = FDeprecateSlateVector2D(TempViewOffset);
|
|
|
|
LocalSettings->SaveConfig();
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::ClearGraphEditorQuickJump(int32 QuickJumpIndex)
|
|
{
|
|
UBlueprintEditorSettings* LocalSettings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
LocalSettings->GraphEditorQuickJumps.Remove(QuickJumpIndex);
|
|
LocalSettings->SaveConfig();
|
|
}
|
|
|
|
void FBlueprintEditor::OnGraphEditorQuickJump(int32 QuickJumpIndex)
|
|
{
|
|
const UBlueprintEditorSettings* LocalSettings = GetDefault<UBlueprintEditorSettings>();
|
|
if (const FEditedDocumentInfo* QuickJumpInfo = LocalSettings->GraphEditorQuickJumps.Find(QuickJumpIndex))
|
|
{
|
|
if (UObject* EditedObject = QuickJumpInfo->EditedObjectPath.TryLoad())
|
|
{
|
|
TSharedPtr<IBlueprintEditor> IBlueprintEditorPtr = FKismetEditorUtilities::GetIBlueprintEditorForObject(EditedObject, true);
|
|
if (IBlueprintEditorPtr.IsValid())
|
|
{
|
|
IBlueprintEditorPtr->FocusWindow();
|
|
TSharedPtr<SGraphEditor> GraphEditorPtr = IBlueprintEditorPtr->OpenGraphAndBringToFront(Cast<UEdGraph>(EditedObject));
|
|
if (GraphEditorPtr.IsValid())
|
|
{
|
|
GraphEditorPtr->SetViewLocation(UE::Slate::CastToVector2f(QuickJumpInfo->SavedViewOffset), QuickJumpInfo->SavedZoomAmount);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FBlueprintEditor::ClearAllGraphEditorQuickJumps()
|
|
{
|
|
UBlueprintEditorSettings* LocalSettings = GetMutableDefault<UBlueprintEditorSettings>();
|
|
LocalSettings->GraphEditorQuickJumps.Empty();
|
|
LocalSettings->SaveConfig();
|
|
}
|
|
|
|
bool FBlueprintEditor::IsNonImportedObject(const UObject* InObject) const
|
|
{
|
|
if (ImportedNamespaceHelper.IsValid() && !ImportedNamespaceHelper->IsImportedObject(InObject))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FBlueprintEditor::IsNonImportedObject(const FSoftObjectPath& InObject) const
|
|
{
|
|
if (ImportedNamespaceHelper.IsValid() && !ImportedNamespaceHelper->IsImportedObject(InObject))
|
|
{
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FBlueprintEditor::OnBlueprintProjectSettingsChanged(UObject*, struct FPropertyChangedEvent&)
|
|
{
|
|
ModifyDuringPIEStatus = ESafeToModifyDuringPIEStatus::Unknown;
|
|
}
|
|
|
|
void FBlueprintEditor::OnBlueprintEditorPreferencesChanged(UObject*, struct FPropertyChangedEvent&)
|
|
{
|
|
ModifyDuringPIEStatus = ESafeToModifyDuringPIEStatus::Unknown;
|
|
}
|
|
|
|
bool FBlueprintEditor::AreEventGraphsAllowed() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool FBlueprintEditor::AreMacrosAllowed() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
bool FBlueprintEditor::AreDelegatesAllowed() const
|
|
{
|
|
return true;
|
|
}
|
|
|
|
FEditorViewportSelectabilityBridge* FBlueprintEditor::GetViewportSelectabilityBridge()
|
|
{
|
|
return ViewportSelectabilityBridge.Get();
|
|
}
|
|
|
|
/////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////
|
|
/////////////////////////////////////////////////////
|
|
|
|
UBlueprint* UBlueprintEditorToolMenuContext::GetBlueprintObj() const
|
|
{
|
|
return BlueprintEditor.IsValid() ? BlueprintEditor.Pin()->GetBlueprintObj() : nullptr;
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|