// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved. #include "EnvironmentQueryEditorPrivatePCH.h" #include "GraphEditorActions.h" #include "ScopedTransaction.h" #include "EdGraphUtilities.h" #include "EnvironmentQuery/EnvQuery.h" #include "EnvironmentQuery/EnvQueryOption.h" #include "EnvironmentQuery/EnvQueryGenerator.h" #include "Toolkits/IToolkitHost.h" #include "Editor/WorkspaceMenuStructure/Public/WorkspaceMenuStructureModule.h" #include "Editor/PropertyEditor/Public/PropertyEditorModule.h" #include "Editor/PropertyEditor/Public/IDetailsView.h" #include "SDockTab.h" #include "GenericCommands.h" #define LOCTEXT_NAMESPACE "EnvironmentQueryEditor" const FName FEnvironmentQueryEditor::EQSUpdateGraphTabId( TEXT( "EnvironmentQueryEditor_UpdateGraph" ) ); const FName FEnvironmentQueryEditor::EQSPropertiesTabId( TEXT( "EnvironmentQueryEditor_Properties" ) ); void FEnvironmentQueryEditor::RegisterTabSpawners(const TSharedRef& TabManager) { WorkspaceMenuCategory = TabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_EnvironmentQueryEditor", "Environment Query Editor")); auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef(); FAssetEditorToolkit::RegisterTabSpawners(TabManager); TabManager->RegisterTabSpawner( EQSUpdateGraphTabId, FOnSpawnTab::CreateSP(this, &FEnvironmentQueryEditor::SpawnTab_UpdateGraph) ) .SetDisplayName( NSLOCTEXT("EnvironmentQueryEditor", "Graph", "Graph") ) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "GraphEditor.EventGraph_16x")); TabManager->RegisterTabSpawner( EQSPropertiesTabId, FOnSpawnTab::CreateSP(this, &FEnvironmentQueryEditor::SpawnTab_Properties) ) .SetDisplayName( NSLOCTEXT("EnvironmentQueryEditor", "PropertiesTab", "Details" ) ) .SetGroup(WorkspaceMenuCategoryRef) .SetIcon(FSlateIcon(FEditorStyle::GetStyleSetName(), "LevelEditor.Tabs.Details")); } void FEnvironmentQueryEditor::UnregisterTabSpawners(const TSharedRef& TabManager) { FAssetEditorToolkit::UnregisterTabSpawners(TabManager); TabManager->UnregisterTabSpawner( EQSPropertiesTabId ); TabManager->UnregisterTabSpawner( EQSUpdateGraphTabId ); } FEnvironmentQueryEditor::FEnvironmentQueryEditor() : IEnvironmentQueryEditor() { UEditorEngine* Editor = (UEditorEngine*)GEngine; if (Editor != NULL) { Editor->RegisterForUndo(this); } } FEnvironmentQueryEditor::~FEnvironmentQueryEditor() { UEditorEngine* Editor = (UEditorEngine*)GEngine; if (Editor) { Editor->UnregisterForUndo( this ); } } void FEnvironmentQueryEditor::InitEnvironmentQueryEditor( const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UEnvQuery* InScript ) { Query = InScript; check(Query != NULL); TSharedRef StandaloneDefaultLayout = FTabManager::NewLayout( "Standalone_EnvironmentQuery_Layout" ) ->AddArea ( FTabManager::NewPrimaryArea() ->SetOrientation(Orient_Vertical) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.1f) ->AddTab(GetToolbarTabId(), ETabState::OpenedTab) ->SetHideTabWell( true ) ) ->Split ( FTabManager::NewSplitter() ->SetOrientation(Orient_Horizontal) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.7f) ->AddTab( EQSUpdateGraphTabId, ETabState::OpenedTab ) ) ->Split ( FTabManager::NewStack() ->SetSizeCoefficient(0.3f) ->AddTab( EQSPropertiesTabId, ETabState::OpenedTab ) ) ) ); const bool bCreateDefaultStandaloneMenu = true; const bool bCreateDefaultToolbar = true; FAssetEditorToolkit::InitAssetEditor( Mode, InitToolkitHost, FEnvironmentQueryEditorModule::EnvironmentQueryEditorAppIdentifier, StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, Query ); FEnvironmentQueryEditorModule& EnvironmentQueryEditorModule = FModuleManager::LoadModuleChecked( "EnvironmentQueryEditor" ); AddMenuExtender(EnvironmentQueryEditorModule.GetMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects())); RegenerateMenusAndToolbars(); // Update BT asset data based on saved graph to have correct data in editor TSharedPtr UpdateGraphEditor = UpdateGraphEdPtr.Pin(); if (UpdateGraphEditor.IsValid() && UpdateGraphEditor->GetCurrentGraph() != NULL) { //let's find root node UEnvironmentQueryGraph* EQSGraph = Cast(UpdateGraphEditor->GetCurrentGraph()); EQSGraph->UpdateAsset(); } } FName FEnvironmentQueryEditor::GetToolkitFName() const { return FName("Environment Query"); } FText FEnvironmentQueryEditor::GetBaseToolkitName() const { return NSLOCTEXT("EnvironmentQueryEditor", "AppLabel", "EnvironmentQuery"); } FString FEnvironmentQueryEditor::GetWorldCentricTabPrefix() const { return NSLOCTEXT("EnvironmentQueryEditor", "WorldCentricTabPrefix", "EnvironmentQuery ").ToString(); } FLinearColor FEnvironmentQueryEditor::GetWorldCentricTabColorScale() const { return FLinearColor( 0.0f, 0.0f, 0.2f, 0.5f ); } /** Create new tab for the supplied graph - don't call this directly, call SExplorer->FindTabForGraph.*/ TSharedRef FEnvironmentQueryEditor::CreateGraphEditorWidget(UEdGraph* InGraph) { check(InGraph != NULL); GraphEditorCommands = MakeShareable( new FUICommandList ); { // Editing commands GraphEditorCommands->MapAction( FGenericCommands::Get().SelectAll, FExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::SelectAllNodes ), FCanExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::CanSelectAllNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Delete, FExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::DeleteSelectedNodes ), FCanExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::CanDeleteNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Copy, FExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::CopySelectedNodes ), FCanExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::CanCopyNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Cut, FExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::CutSelectedNodes ), FCanExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::CanCutNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Paste, FExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::PasteNodes ), FCanExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::CanPasteNodes ) ); GraphEditorCommands->MapAction( FGenericCommands::Get().Duplicate, FExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::DuplicateNodes ), FCanExecuteAction::CreateSP( this, &FEnvironmentQueryEditor::CanDuplicateNodes ) ); } // Create the appearance info FGraphAppearanceInfo AppearanceInfo; AppearanceInfo.CornerText = NSLOCTEXT("EnvironmentQueryEditor", "AppearanceCornerText", "ENVIRONMENT QUERY"); SGraphEditor::FGraphEditorEvents InEvents; InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FEnvironmentQueryEditor::OnSelectedNodesChanged); // Make title bar TSharedRef TitleBarWidget = SNew(SBorder) .BorderImage( FEditorStyle::GetBrush( TEXT("Graph.TitleBackground") ) ) .HAlign(HAlign_Fill) [ SNew(SHorizontalBox) +SHorizontalBox::Slot() .HAlign(HAlign_Center) .FillWidth(1.f) [ SNew(STextBlock) .Text(NSLOCTEXT("EnvironmentQueryEditor", "TheQueryGraphLabel", "Query Graph")) .TextStyle( FEditorStyle::Get(), TEXT("GraphBreadcrumbButtonText") ) ] ]; // Make full graph editor return SNew(SGraphEditor) .AdditionalCommands(GraphEditorCommands) .Appearance(AppearanceInfo) .TitleBar(TitleBarWidget) .GraphToEdit(InGraph) .GraphEvents(InEvents); } TSharedRef FEnvironmentQueryEditor::SpawnTab_UpdateGraph( const FSpawnTabArgs& Args ) { check( Args.GetTabId().TabType == EQSUpdateGraphTabId ); if (Query->EdGraph == NULL) { UEnvironmentQueryGraph* MyGraph = NewObject(Query, NAME_None, RF_Transactional); MyGraph->MarkVersion(); Query->EdGraph = MyGraph; // let's read data from BT script and generate nodes const UEdGraphSchema* Schema = Query->EdGraph->GetSchema(); Schema->CreateDefaultNodesForGraph(*Query->EdGraph); } // Conversion should be removed are a while UEnvironmentQueryGraph* QueryGraph = Cast(Query->EdGraph); QueryGraph->UpdateVersion(); QueryGraph->CalculateAllWeights(); TSharedRef UpdateGraphEditor = CreateGraphEditorWidget(Query->EdGraph); UpdateGraphEdPtr = UpdateGraphEditor; // Keep pointer to editor return SNew(SDockTab) .Label( NSLOCTEXT("EnvironmentQueryEditor", "UpdateGraph", "Update Graph") ) .TabColorScale( GetTabColorScale() ) [ UpdateGraphEditor ]; } TSharedRef FEnvironmentQueryEditor::SpawnTab_Properties(const FSpawnTabArgs& Args) { check( Args.GetTabId() == EQSPropertiesTabId ); CreateInternalWidgets(); TSharedRef SpawnedTab = SNew(SDockTab) .Icon( FEditorStyle::GetBrush("SoundClassEditor.Tabs.Properties") ) .Label( NSLOCTEXT("EnvironmentQueryEditor", "SoundClassPropertiesTitle", "Details" ) ) [ DetailsView.ToSharedRef() ]; return SpawnedTab; } void FEnvironmentQueryEditor::OnSelectedNodesChanged(const TSet& NewSelection) { TArray Selection; if(NewSelection.Num()) { for(TSet::TConstIterator SetIt(NewSelection);SetIt;++SetIt) { UEnvironmentQueryGraphNode* GraphNode = CastChecked(*SetIt); if (GraphNode) { if (GraphNode->IsA(UEnvironmentQueryGraphNode_Root::StaticClass())) { Selection.Add(GraphNode); } else if (GraphNode->IsA(UEnvironmentQueryGraphNode_Option::StaticClass())) { UEnvQueryOption* QueryOption = Cast(GraphNode->NodeInstance); if (QueryOption) { Selection.Add(QueryOption->Generator); } } else { Selection.Add(GraphNode->NodeInstance); } } } } if (Selection.Num() == 1) { DetailsView->SetObjects(Selection); } else { DetailsView->SetObject(NULL); } } void FEnvironmentQueryEditor::CreateInternalWidgets() { FPropertyEditorModule& PropertyEditorModule = FModuleManager::GetModuleChecked( "PropertyEditor" ); const FDetailsViewArgs DetailsViewArgs( false, false, true, FDetailsViewArgs::ObjectsUseNameArea, false ); DetailsView = PropertyEditorModule.CreateDetailView( DetailsViewArgs ); DetailsView->SetObject( NULL ); DetailsView->OnFinishedChangingProperties().AddSP(this, &FEnvironmentQueryEditor::OnFinishedChangingProperties); } void FEnvironmentQueryEditor::SaveAsset_Execute() { // modify BT asset TSharedPtr UpdateGraphEditor = UpdateGraphEdPtr.Pin(); if (UpdateGraphEditor.IsValid() && UpdateGraphEditor->GetCurrentGraph() != NULL) { //let's find root node UEnvironmentQueryGraph* EdGraph = Cast(UpdateGraphEditor->GetCurrentGraph()); EdGraph->UpdateAsset(); } // save it IEnvironmentQueryEditor::SaveAsset_Execute(); } void FEnvironmentQueryEditor::OnFinishedChangingProperties(const FPropertyChangedEvent& PropertyChangedEvent) { if (PropertyChangedEvent.Property) { FGraphPanelSelectionSet CurrentSelection = GetSelectedNodes(); if (CurrentSelection.Num() == 1) { for (FGraphPanelSelectionSet::TConstIterator It(CurrentSelection); It; ++It) { UEnvironmentQueryGraphNode_Test* TestNode = Cast(*It); if (TestNode && TestNode->ParentNode) { TestNode->ParentNode->CalculateWeights(); break; } } } } } void FEnvironmentQueryEditor::PostUndo(bool bSuccess) { if (bSuccess) { // Clear selection, to avoid holding refs to nodes that go away TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (CurrentGraphEditor.IsValid()) { CurrentGraphEditor->ClearSelectionSet(); CurrentGraphEditor->NotifyGraphChanged(); } FSlateApplication::Get().DismissAllMenus(); } } void FEnvironmentQueryEditor::PostRedo(bool bSuccess) { if (bSuccess) { // Clear selection, to avoid holding refs to nodes that go away TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (CurrentGraphEditor.IsValid()) { CurrentGraphEditor->ClearSelectionSet(); CurrentGraphEditor->NotifyGraphChanged(); } FSlateApplication::Get().DismissAllMenus(); } } FGraphPanelSelectionSet FEnvironmentQueryEditor::GetSelectedNodes() const { FGraphPanelSelectionSet CurrentSelection; TSharedPtr FocusedGraphEd = UpdateGraphEdPtr.Pin(); if (FocusedGraphEd.IsValid()) { CurrentSelection = FocusedGraphEd->GetSelectedNodes(); } return CurrentSelection; } void FEnvironmentQueryEditor::SelectAllNodes() { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (CurrentGraphEditor.IsValid()) { CurrentGraphEditor->SelectAllNodes(); } } bool FEnvironmentQueryEditor::CanSelectAllNodes() const { return true; } void FEnvironmentQueryEditor::DeleteSelectedNodes() { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (!CurrentGraphEditor.IsValid()) { return; } const FScopedTransaction Transaction( FGenericCommands::Get().Delete->GetDescription() ); CurrentGraphEditor->GetCurrentGraph()->Modify(); const FGraphPanelSelectionSet SelectedNodes = CurrentGraphEditor->GetSelectedNodes(); CurrentGraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator NodeIt( SelectedNodes ); NodeIt; ++NodeIt) { if (UEdGraphNode* Node = Cast(*NodeIt)) { if (Node->CanUserDeleteNode()) { Node->Modify(); Node->DestroyNode(); } } } } bool FEnvironmentQueryEditor::CanDeleteNodes() const { // If any of the nodes can be deleted then we should allow deleting const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if (Node && Node->CanUserDeleteNode()) { return true; } } return false; } void FEnvironmentQueryEditor::DeleteSelectedDuplicatableNodes() { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (!CurrentGraphEditor.IsValid()) { return; } const FGraphPanelSelectionSet OldSelectedNodes = CurrentGraphEditor->GetSelectedNodes(); CurrentGraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter) { UEdGraphNode* Node = Cast(*SelectedIter); if (Node && Node->CanDuplicateNode()) { CurrentGraphEditor->SetNodeSelection(Node, true); } } // Delete the duplicatable nodes DeleteSelectedNodes(); CurrentGraphEditor->ClearSelectionSet(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter) { if (UEdGraphNode* Node = Cast(*SelectedIter)) { CurrentGraphEditor->SetNodeSelection(Node, true); } } } void FEnvironmentQueryEditor::CutSelectedNodes() { CopySelectedNodes(); DeleteSelectedDuplicatableNodes(); } bool FEnvironmentQueryEditor::CanCutNodes() const { return CanCopyNodes() && CanDeleteNodes(); } void FEnvironmentQueryEditor::CopySelectedNodes() { // Export the selected nodes and place the text on the clipboard FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); TArray SubNodes; FString ExportedText; int32 CopySubNodeIndex = 0; for (FGraphPanelSelectionSet::TIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEnvironmentQueryGraphNode_Option* OptionNode = Cast(*SelectedIter); UEnvironmentQueryGraphNode_Test* TestNode = Cast(*SelectedIter); UEnvironmentQueryGraphNode* Node = OptionNode ? (UEnvironmentQueryGraphNode*)OptionNode : (UEnvironmentQueryGraphNode*)TestNode; if (OptionNode == NULL && TestNode == NULL) { SelectedIter.RemoveCurrent(); continue; } Node->PrepareForCopying(); Node->CopySubNodeIndex = CopySubNodeIndex; // append all subnodes for selection if (OptionNode) { for (int32 i = 0; i < OptionNode->Tests.Num(); i++) { OptionNode->Tests[i]->CopySubNodeIndex = CopySubNodeIndex; SubNodes.Add(OptionNode->Tests[i]); } } CopySubNodeIndex++; } for (int32 i = 0; i < SubNodes.Num(); i++) { SelectedNodes.Add(SubNodes[i]); SubNodes[i]->PrepareForCopying(); } FEdGraphUtilities::ExportNodesToText(SelectedNodes, ExportedText); FPlatformMisc::ClipboardCopy(*ExportedText); for (FGraphPanelSelectionSet::TIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEnvironmentQueryGraphNode* Node = Cast(*SelectedIter); Node->PostCopyNode(); } } bool FEnvironmentQueryEditor::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(*SelectedIter); if (Node && Node->CanDuplicateNode()) { return true; } } return false; } void FEnvironmentQueryEditor::PasteNodes() { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (CurrentGraphEditor.IsValid()) { PasteNodesHere(CurrentGraphEditor->GetPasteLocation()); } } void FEnvironmentQueryEditor::PasteNodesHere(const FVector2D& Location) { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (!CurrentGraphEditor.IsValid()) { return; } // Undo/Redo support const FScopedTransaction Transaction( FGenericCommands::Get().Paste->GetDescription() ); UEnvironmentQueryGraph* EdGraph = Cast(CurrentGraphEditor->GetCurrentGraph()); EdGraph->Modify(); EdGraph->LockUpdates(); // Find currently selected option to paste all new tests into UEnvironmentQueryGraphNode_Option* SelectedOption = NULL; bool bHasMultipleOptionsSelected = false; const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes(); for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter) { UEnvironmentQueryGraphNode_Option* Node = Cast(*SelectedIter); UEnvironmentQueryGraphNode_Test* TestNode = Cast(*SelectedIter); if (TestNode) { Node = TestNode->ParentNode; } if (Node) { if (SelectedOption == NULL) { SelectedOption = Node; } else { bHasMultipleOptionsSelected = true; break; } } } // Clear the selection set (newly pasted stuff will be selected) CurrentGraphEditor->ClearSelectionSet(); // Grab the text to paste from the clipboard. FString TextToImport; FPlatformMisc::ClipboardPaste(TextToImport); // Import the nodes TSet PastedNodes; FEdGraphUtilities::ImportNodesFromText(EdGraph, TextToImport, /*out*/ PastedNodes); //Average position of nodes so we can move them while still maintaining relative distances to each other FVector2D AvgNodePosition(0.0f,0.0f); for (TSet::TIterator It(PastedNodes); It; ++It) { UEnvironmentQueryGraphNode* BTNode = Cast(*It); if (BTNode && !BTNode->IsSubNode()) { AvgNodePosition.X += BTNode->NodePosX; AvgNodePosition.Y += BTNode->NodePosY; } } if (PastedNodes.Num() > 0) { float InvNumNodes = 1.0f/float(PastedNodes.Num()); AvgNodePosition.X *= InvNumNodes; AvgNodePosition.Y *= InvNumNodes; } bool bPastedOptionNode = false; TMap ParentMap; for (TSet::TIterator It(PastedNodes); It; ++It) { UEnvironmentQueryGraphNode* PasteNode = Cast(*It); if (PasteNode && !PasteNode->IsSubNode()) { bPastedOptionNode = true; // Select the newly pasted stuff CurrentGraphEditor->SetNodeSelection(PasteNode, true); PasteNode->NodePosX = (PasteNode->NodePosX - AvgNodePosition.X) + Location.X ; PasteNode->NodePosY = (PasteNode->NodePosY - AvgNodePosition.Y) + Location.Y ; PasteNode->SnapToGrid(16); // Give new node a different Guid from the old one PasteNode->CreateNewGuid(); UEnvironmentQueryGraphNode_Option* PasteOption = Cast(PasteNode); if (PasteOption) { PasteOption->Tests.Reset(); ParentMap.Add(PasteOption->CopySubNodeIndex, PasteOption); } } } for (TSet::TIterator It(PastedNodes); It; ++It) { UEnvironmentQueryGraphNode_Test* PasteTest = Cast(*It); if (PasteTest) { PasteTest->NodePosX = 0; PasteTest->NodePosY = 0; // Give new node a different Guid from the old one PasteTest->CreateNewGuid(); // remove subnode from graph, it will be referenced from parent node PasteTest->DestroyNode(); PasteTest->ParentNode = ParentMap.FindRef(PasteTest->CopySubNodeIndex); if (PasteTest->ParentNode) { PasteTest->ParentNode->Tests.Add(PasteTest); } else if (!bHasMultipleOptionsSelected && !bPastedOptionNode && SelectedOption) { PasteTest->ParentNode = SelectedOption; SelectedOption->Tests.Add(PasteTest); SelectedOption->Modify(); } } } // Update UI EdGraph->UnlockUpdates(); CurrentGraphEditor->NotifyGraphChanged(); Query->PostEditChange(); Query->MarkPackageDirty(); } bool FEnvironmentQueryEditor::CanPasteNodes() const { TSharedPtr CurrentGraphEditor = UpdateGraphEdPtr.Pin(); if (!CurrentGraphEditor.IsValid()) { return false; } FString ClipboardContent; FPlatformMisc::ClipboardPaste(ClipboardContent); return FEdGraphUtilities::CanImportNodesFromText(CurrentGraphEditor->GetCurrentGraph(), ClipboardContent); } void FEnvironmentQueryEditor::DuplicateNodes() { CopySelectedNodes(); PasteNodes(); } bool FEnvironmentQueryEditor::CanDuplicateNodes() const { return CanCopyNodes(); } #undef LOCTEXT_NAMESPACE