// Copyright Epic Games, Inc. All Rights Reserved. /*============================================================================= SoundCueGraphSchema.cpp =============================================================================*/ #include "SoundCueGraph/SoundCueGraphSchema.h" #include "AssetRegistry/AssetData.h" #include "ClassViewerFilter.h" #include "Containers/EnumAsByte.h" #include "Containers/Set.h" #include "Delegates/Delegate.h" #include "EdGraph/EdGraph.h" #include "EdGraph/EdGraphNode.h" #include "EdGraph/EdGraphPin.h" #include "EdGraphNode_Comment.h" #include "Editor.h" #include "Editor/EditorEngine.h" #include "GraphEditor.h" #include "GraphEditorActions.h" #include "Internationalization/Internationalization.h" #include "Layout/SlateRect.h" #include "Misc/AssertionMacros.h" #include "ScopedTransaction.h" #include "Selection.h" #include "Sound/DialogueTypes.h" #include "Sound/DialogueWave.h" #include "Sound/SoundCue.h" #include "Sound/SoundNode.h" #include "Sound/SoundNodeDialoguePlayer.h" #include "Sound/SoundNodeWavePlayer.h" #include "Sound/SoundWave.h" #include "SoundCueEditorUtilities.h" #include "SSoundCuePalette.h" #include "SoundCueGraph/SoundCueGraph.h" #include "SoundCueGraph/SoundCueGraphNode.h" #include "SoundCueGraph/SoundCueGraphNode_Root.h" #include "Templates/Casts.h" #include "ToolMenu.h" #include "ToolMenuSection.h" #include "UObject/Class.h" class FString; #define LOCTEXT_NAMESPACE "SoundCueSchema" ///////////////////////////////////////////////////// // FSoundCueGraphSchemaAction_NewNode UEdGraphNode* FSoundCueGraphSchemaAction_NewNode::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2f& Location, bool bSelectNewNode/* = true*/) { check(SoundNodeClass); USoundCue* SoundCue = CastChecked(ParentGraph)->GetSoundCue(); const FScopedTransaction Transaction( LOCTEXT("SoundCueEditorNewSoundNode", "Sound Cue Editor: New Sound Node") ); ParentGraph->Modify(); SoundCue->Modify(); USoundNode* NewNode = SoundCue->ConstructSoundNode(SoundNodeClass, bSelectNewNode); // If this node allows >0 children but by default has zero - create a connector for starters if (NewNode->GetMaxChildNodes() > 0 && NewNode->ChildNodes.Num() == 0) { NewNode->CreateStartingConnectors(); } // Attempt to connect inputs to selected nodes, unless we're already dragging from a single output if (FromPin == NULL || FromPin->Direction == EGPD_Input) { ConnectToSelectedNodes(NewNode, ParentGraph); } NewNode->GraphNode->NodePosX = Location.X; NewNode->GraphNode->NodePosY = Location.Y; NewNode->GraphNode->AutowireNewNode(FromPin); SoundCue->PostEditChange(); SoundCue->MarkPackageDirty(); return NewNode->GraphNode; } void FSoundCueGraphSchemaAction_NewNode::ConnectToSelectedNodes(USoundNode* NewNode, class UEdGraph* ParentGraph) const { // only connect if node can have many children if (NewNode->GetMaxChildNodes() > 1) { const FGraphPanelSelectionSet SelectedNodes = FSoundCueEditorUtilities::GetSelectedNodes(ParentGraph); TArray SortedNodes; for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt) { USoundCueGraphNode* SelectedNode = Cast(*NodeIt); if (SelectedNode) { // Sort the nodes by y position bool bInserted = false; for (int32 Index = 0; Index < SortedNodes.Num(); ++Index) { if (SortedNodes[Index]->GraphNode->NodePosY > SelectedNode->NodePosY) { SortedNodes.Insert(SelectedNode->SoundNode, Index); bInserted = true; break; } } if (!bInserted) { SortedNodes.Add(SelectedNode->SoundNode); } } } if (SortedNodes.Num() > 1) { CastChecked(NewNode->GraphNode->GetSchema())->TryConnectNodes(SortedNodes, NewNode); } } } ///////////////////////////////////////////////////// // FSoundCueGraphSchemaAction_NewFromSelected UEdGraphNode* FSoundCueGraphSchemaAction_NewFromSelected::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2f& Location, bool bSelectNewNode/* = true*/) { USoundCue* SoundCue = CastChecked(ParentGraph)->GetSoundCue(); const FScopedTransaction Transaction( LOCTEXT("SoundCueEditorNewFromSelection", "Sound Cue Editor: New From Selection") ); ParentGraph->Modify(); SoundCue->Modify(); UEdGraphNode* CreatedNode = NULL; FDeprecateSlateVector2D WaveStartLocation = Location; if (SoundNodeClass) { // If we will create another node, move wave nodes out of the way. WaveStartLocation.X -= 200.0f; } TArray SelectedWaves; TArray SelectedDialogues; TArray CreatedPlayers; GEditor->GetSelectedObjects()->GetSelectedObjects(SelectedWaves); GEditor->GetSelectedObjects()->GetSelectedObjects(SelectedDialogues); FSoundCueEditorUtilities::CreateWaveContainers(SelectedWaves, SoundCue, CreatedPlayers, WaveStartLocation); FSoundCueEditorUtilities::CreateDialogueContainers(SelectedDialogues, SoundCue, CreatedPlayers, WaveStartLocation); if (SoundNodeClass) { USoundNode* NewNode = SoundCue->ConstructSoundNode(SoundNodeClass, bSelectNewNode); UEdGraphNode* NewGraphNode = NewNode->GraphNode; const USoundCueGraphSchema* NewSchema = CastChecked(NewGraphNode->GetSchema()); // If this node allows >0 children but by default has zero - create a connector for starters if (NewNode->GetMaxChildNodes() > 0 && NewNode->ChildNodes.Num() == 0) { NewNode->CreateStartingConnectors(); } NewSchema->TryConnectNodes(CreatedPlayers, NewNode); NewGraphNode->NodePosX = Location.X; NewGraphNode->NodePosY = Location.Y; CreatedNode = NewNode->GraphNode; } else { if (CreatedPlayers.Num() > 0) { CreatedNode = CreatedPlayers[0]->GraphNode; } } if (CreatedNode) { CreatedNode->AutowireNewNode(FromPin); } SoundCue->PostEditChange(); SoundCue->MarkPackageDirty(); return CreatedNode; } ///////////////////////////////////////////////////// // FSoundCueGraphSchemaAction_NewComment UEdGraphNode* FSoundCueGraphSchemaAction_NewComment::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2f& Location, bool bSelectNewNode/* = true*/) { // Add menu item for creating comment boxes UEdGraphNode_Comment* CommentTemplate = NewObject(); FVector2f SpawnLocation = Location; FSlateRect Bounds; if (FSoundCueEditorUtilities::GetBoundsForSelectedNodes(ParentGraph, Bounds, 50.0f)) { CommentTemplate->SetBounds(Bounds); SpawnLocation.X = CommentTemplate->NodePosX; SpawnLocation.Y = CommentTemplate->NodePosY; } return FEdGraphSchemaAction_NewNode::SpawnNodeFromTemplate(ParentGraph, CommentTemplate, SpawnLocation); } ///////////////////////////////////////////////////// // FSoundCueGraphSchemaAction_Paste UEdGraphNode* FSoundCueGraphSchemaAction_Paste::PerformAction(class UEdGraph* ParentGraph, UEdGraphPin* FromPin, const FVector2f& Location, bool bSelectNewNode/* = true*/) { FSoundCueEditorUtilities::PasteNodesHere(ParentGraph, FDeprecateSlateVector2D(Location)); return NULL; } ///////////////////////////////////////////////////// // USoundCueGraphSchema USoundCueGraphSchema::USoundCueGraphSchema(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) { } void USoundCueGraphSchema::UpdateSoundNodeList(const SSoundCuePalette::FSoundNodeFilterData& FilterData) { check(FilterData.InitOptions.IsValid()); check(FilterData.ClassFilter.IsValid()); check(FilterData.FilterFuncs.IsValid()); AllowedSoundNodes.Empty(); TArray SoundNodeClasses; GetDerivedClasses(USoundNode::StaticClass(), SoundNodeClasses, true); SoundNodeClasses.Sort(); for(TSubclassOf SoundNodeClass : SoundNodeClasses) { if(!SoundNodeClass->HasAnyClassFlags(CLASS_Abstract) && FilterData.ClassFilter->IsClassAllowed(*FilterData.InitOptions, SoundNodeClass, FilterData.FilterFuncs.ToSharedRef())) { AllowedSoundNodes.Add(SoundNodeClass); } } } bool USoundCueGraphSchema::ConnectionCausesLoop(const UEdGraphPin* InputPin, const UEdGraphPin* OutputPin) const { USoundCueGraphNode* InputNode = Cast(InputPin->GetOwningNode()); if (InputNode) { // Only nodes representing SoundNodes have outputs USoundCueGraphNode* OutputNode = CastChecked(OutputPin->GetOwningNode()); if (OutputNode->SoundNode) { // Grab all child nodes. We can't just test the output because // the loop could happen from any additional child nodes. TArray Nodes; OutputNode->SoundNode->GetAllNodes(Nodes); // If our test input is in that set, return true. return Nodes.Contains(InputNode->SoundNode); } } // Simple connection to root node return false; } void USoundCueGraphSchema::GetPaletteActions(FGraphActionMenuBuilder& ActionMenuBuilder) const { GetAllSoundNodeActions(ActionMenuBuilder, false); GetCommentAction(ActionMenuBuilder); } void USoundCueGraphSchema::TryConnectNodes(const TArray& OutputNodes, USoundNode* InputNode) const { for (int32 Index = 0; Index < OutputNodes.Num(); Index++) { if ( Index < InputNode->GetMaxChildNodes() ) { USoundCueGraphNode* GraphNode = CastChecked(InputNode->GetGraphNode()); if (Index >= GraphNode->GetInputCount()) { GraphNode->CreateInputPin(); } TryCreateConnection(GraphNode->GetInputPin(Index), CastChecked(OutputNodes[Index]->GetGraphNode())->GetOutputPin() ); } } } void USoundCueGraphSchema::GetGraphContextActions(FGraphContextMenuBuilder& ContextMenuBuilder) const { GetAllSoundNodeActions(ContextMenuBuilder, true); GetCommentAction(ContextMenuBuilder, ContextMenuBuilder.CurrentGraph); if (!ContextMenuBuilder.FromPin && FSoundCueEditorUtilities::CanPasteNodes(ContextMenuBuilder.CurrentGraph)) { TSharedPtr NewAction( new FSoundCueGraphSchemaAction_Paste(FText::GetEmpty(), LOCTEXT("PasteHereAction", "Paste here"), FText::GetEmpty(), 0) ); ContextMenuBuilder.AddAction( NewAction ); } } void USoundCueGraphSchema::GetContextMenuActions(class UToolMenu* Menu, class UGraphNodeContextMenuContext* Context) const { if (Context->Node) { const USoundCueGraphNode* SoundGraphNode = Cast(Context->Node); { FToolMenuSection& Section = Menu->AddSection("SoundCueGraphSchemaNodeActions", LOCTEXT("NodeActionsMenuHeader", "Node Actions")); Section.AddMenuEntry(FGraphEditorCommands::Get().BreakNodeLinks); } } Super::GetContextMenuActions(Menu, Context); } void USoundCueGraphSchema::CreateDefaultNodesForGraph(UEdGraph& Graph) const { const int32 RootNodeHeightOffset = -58; // Create the result node FGraphNodeCreator NodeCreator(Graph); USoundCueGraphNode_Root* ResultRootNode = NodeCreator.CreateNode(); ResultRootNode->NodePosY = RootNodeHeightOffset; NodeCreator.Finalize(); SetNodeMetaData(ResultRootNode, FNodeMetadata::DefaultGraphNode); } const FPinConnectionResponse USoundCueGraphSchema::CanCreateConnection(const UEdGraphPin* PinA, const UEdGraphPin* PinB) const { // Make sure the pins are not on the same node if (PinA->GetOwningNode() == PinB->GetOwningNode()) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionSameNode", "Both are on the same node")); } // Compare the directions const UEdGraphPin* InputPin = NULL; const UEdGraphPin* OutputPin = NULL; if (!CategorizePinsByDirection(PinA, PinB, /*out*/ InputPin, /*out*/ OutputPin)) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionIncompatible", "Directions are not compatible")); } if (ConnectionCausesLoop(InputPin, OutputPin)) { return FPinConnectionResponse(CONNECT_RESPONSE_DISALLOW, LOCTEXT("ConnectionLoop", "Connection would cause loop")); } // Break existing connections on inputs only - multiple output connections are acceptable if (InputPin->LinkedTo.Num() > 0) { ECanCreateConnectionResponse ReplyBreakOutputs; if (InputPin == PinA) { ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_A; } else { ReplyBreakOutputs = CONNECT_RESPONSE_BREAK_OTHERS_B; } return FPinConnectionResponse(ReplyBreakOutputs, LOCTEXT("ConnectionReplace", "Replace existing connections")); } return FPinConnectionResponse(CONNECT_RESPONSE_MAKE, TEXT("")); } bool USoundCueGraphSchema::TryCreateConnection(UEdGraphPin* PinA, UEdGraphPin* PinB) const { bool bModified = UEdGraphSchema::TryCreateConnection(PinA, PinB); if (bModified) { CastChecked(PinA->GetOwningNode()->GetGraph())->GetSoundCue()->CompileSoundNodesFromGraphNodes(); } return bModified; } bool USoundCueGraphSchema::ShouldHidePinDefaultValue(UEdGraphPin* Pin) const { return true; } FLinearColor USoundCueGraphSchema::GetPinTypeColor(const FEdGraphPinType& PinType) const { return FLinearColor::White; } void USoundCueGraphSchema::BreakNodeLinks(UEdGraphNode& TargetNode) const { Super::BreakNodeLinks(TargetNode); CastChecked(TargetNode.GetGraph())->GetSoundCue()->CompileSoundNodesFromGraphNodes(); } void USoundCueGraphSchema::BreakPinLinks(UEdGraphPin& TargetPin, bool bSendsNodeNotifcation) const { const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "GraphEd_BreakPinLinks", "Break Pin Links") ); Super::BreakPinLinks(TargetPin, bSendsNodeNotifcation); // if this would notify the node then we need to compile the SoundCue if (bSendsNodeNotifcation) { CastChecked(TargetPin.GetOwningNode()->GetGraph())->GetSoundCue()->CompileSoundNodesFromGraphNodes(); } } void USoundCueGraphSchema::GetAssetsGraphHoverMessage(const TArray& Assets, const UEdGraph* HoverGraph, FString& OutTooltipText, bool& OutOkIcon) const { OutOkIcon = false; for (int32 AssetIdx = 0; AssetIdx < Assets.Num(); ++AssetIdx) { // As soon as one of the items is a sound wave, say we can drag it on... we actually eat only the sound waves. USoundWave* SoundWav = Cast(Assets[AssetIdx].GetAsset()); if (SoundWav) { OutOkIcon = true; break; } } } void USoundCueGraphSchema::DroppedAssetsOnGraph(const TArray& Assets, const FVector2f& GraphPosition, UEdGraph* Graph) const { ////////////////////////////////////////////////////////////////////////// // Handle dropped USoundWaves TArray Waves; for (int32 AssetIdx = 0; AssetIdx < Assets.Num(); ++AssetIdx) { USoundWave* SoundWav = Cast(Assets[AssetIdx].GetAsset()); if (SoundWav) { Waves.Add(SoundWav); } } if (Waves.Num() > 0) { const FScopedTransaction Transaction( LOCTEXT("SoundCueEditorDropWave", "Sound Cue Editor: Drag and Drop Sound Wave") ); USoundCueGraph* SoundCueGraph = CastChecked(Graph); USoundCue* SoundCue = SoundCueGraph->GetSoundCue(); SoundCueGraph->Modify(); TArray CreatedPlayers; FSoundCueEditorUtilities::CreateWaveContainers(Waves, SoundCue, CreatedPlayers, FDeprecateSlateVector2D(GraphPosition)); } ////////////////////////////////////////////////////////////////////////// // Handle dropped UDialogueWaves TArray Dialogues; for (int32 AssetIdx = 0; AssetIdx < Assets.Num(); ++AssetIdx) { UDialogueWave* DialogueWave = Cast(Assets[AssetIdx].GetAsset()); if (DialogueWave) { Dialogues.Add(DialogueWave); } } if (Dialogues.Num() > 0) { const FScopedTransaction Transaction(LOCTEXT("SoundCueEditorDropDialogue", "Sound Cue Editor: Drag and Drop Dialogue Wave")); USoundCueGraph* SoundCueGraph = CastChecked(Graph); USoundCue* SoundCue = SoundCueGraph->GetSoundCue(); SoundCueGraph->Modify(); TArray CreatedPlayers; FSoundCueEditorUtilities::CreateDialogueContainers(Dialogues, SoundCue, CreatedPlayers, FDeprecateSlateVector2D(GraphPosition)); } } void USoundCueGraphSchema::DroppedAssetsOnNode(const TArray& Assets, const FVector2f& GraphPosition, UEdGraphNode* Node) const { // Currently, drag and drop is only supported for dropping on sound cue graph nodes, and in particular, sound wave players and sound dialogue players. if (!Node->IsA()) { return; } USoundCueGraphNode* SoundCueGraphNode = CastChecked(Node); USoundCueGraph* SoundCueGraph = CastChecked(Node->GetGraph()); USoundCue* SoundCue = SoundCueGraph->GetSoundCue(); TArray Waves; TArray Dialogues; for (int32 AssetIdx = 0; AssetIdx < Assets.Num(); ++AssetIdx) { USoundWave* SoundWav = Cast(Assets[AssetIdx].GetAsset()); if (SoundWav) { Waves.Add(SoundWav); } else { UDialogueWave* Dialogue = Cast(Assets[AssetIdx].GetAsset()); if (Dialogue) { Dialogues.Add(Dialogue); } } } USoundNodeWavePlayer* SoundNodeWavePlayer = Cast(SoundCueGraphNode->SoundNode); if (SoundNodeWavePlayer != nullptr) { if (Waves.Num() > 0) { if (Waves.Num() >= 1) { SoundCueGraph->Modify(); SoundNodeWavePlayer->SetSoundWave(Waves[0]); } for (int32 Index = 1; Index < Waves.Num(); Index++) { TArray CreatedPlayers; FSoundCueEditorUtilities::CreateWaveContainers(Waves, SoundCue, CreatedPlayers, FDeprecateSlateVector2D(GraphPosition)); } } else if (Dialogues.Num() > 0) { TArray CreatedPlayers; FSoundCueEditorUtilities::CreateDialogueContainers(Dialogues, SoundCue, CreatedPlayers, FDeprecateSlateVector2D(GraphPosition)); if (CreatedPlayers.Num() > 0) { USoundNode* OldNode = SoundCueGraphNode->SoundNode; SoundCueGraphNode->SetSoundNode(CreatedPlayers[0]); // Make sure SoundCue is updated to match graph SoundCue->CompileSoundNodesFromGraphNodes(); // Remove this node from the SoundCue's list of all SoundNodes SoundCue->AllNodes.Remove(OldNode); SoundCue->MarkPackageDirty(); } } } USoundNodeDialoguePlayer* SoundNodeDialoguePlayer = Cast(SoundCueGraphNode->SoundNode); if (SoundNodeDialoguePlayer != nullptr) { if (Dialogues.Num() > 0) { if (Dialogues.Num() >= 1) { SoundCueGraph->Modify(); SoundNodeDialoguePlayer->SetDialogueWave(Dialogues[0]); if (Dialogues[0]->ContextMappings.Num() == 1) { SoundNodeDialoguePlayer->DialogueWaveParameter.Context.Speaker = Dialogues[0]->ContextMappings[0].Context.Speaker; SoundNodeDialoguePlayer->DialogueWaveParameter.Context.Targets = Dialogues[0]->ContextMappings[0].Context.Targets; } } for (int32 Index = 1; Index < Waves.Num(); Index++) { TArray CreatedPlayers; FSoundCueEditorUtilities::CreateDialogueContainers(Dialogues, SoundCue, CreatedPlayers, FDeprecateSlateVector2D(GraphPosition)); } } else if (Waves.Num() > 0) { TArray CreatedPlayers; FSoundCueEditorUtilities::CreateWaveContainers(Waves, SoundCue, CreatedPlayers, FDeprecateSlateVector2D(GraphPosition)); if (CreatedPlayers.Num() > 0) { USoundNode* OldNode = SoundCueGraphNode->SoundNode; SoundCueGraphNode->SetSoundNode(CreatedPlayers[0]); // Make sure SoundCue is updated to match graph SoundCue->CompileSoundNodesFromGraphNodes(); // Remove this node from the SoundCue's list of all SoundNodes SoundCue->AllNodes.Remove(OldNode); SoundCue->MarkPackageDirty(); } } } SoundCueGraph->NotifyGraphChanged(); } void USoundCueGraphSchema::GetAllSoundNodeActions(FGraphActionMenuBuilder& ActionMenuBuilder, bool bShowSelectedActions) const { FText SelectedItemText; bool IsSoundWaveSelected = false; bool IsDialogueWaveSelected = false; if (bShowSelectedActions) { FEditorDelegates::LoadSelectedAssetsIfNeeded.Broadcast(); // Get display text for any items that may be selected if (ActionMenuBuilder.FromPin == NULL) { TArray SelectedWavs; TArray SelectedDialogues; GEditor->GetSelectedObjects()->GetSelectedObjects(SelectedWavs); GEditor->GetSelectedObjects()->GetSelectedObjects(SelectedDialogues); int32 TotalWavs = SelectedWavs.Num() + SelectedDialogues.Num() ; if (TotalWavs > 1) { SelectedItemText = LOCTEXT("MultipleWAVsSelected", "Multiple WAVs"); } else if (SelectedWavs.Num() == 1) { SelectedItemText = FText::FromString(SelectedWavs[0]->GetName()); IsSoundWaveSelected = true; } else if (SelectedDialogues.Num() == 1) { SelectedItemText = FText::FromString(SelectedDialogues[0]->GetName()); IsDialogueWaveSelected = true; } } else { USoundWave* SelectedWave = GEditor->GetSelectedObjects()->GetTop(); if (SelectedWave && ActionMenuBuilder.FromPin->Direction == EGPD_Input) { SelectedItemText = FText::FromString(SelectedWave->GetName()); IsSoundWaveSelected = true; } else { UDialogueWave* SelectedDialogue = GEditor->GetSelectedObjects()->GetTop(); if (SelectedDialogue && ActionMenuBuilder.FromPin->Direction == EGPD_Input) { SelectedItemText = FText::FromString(SelectedDialogue->GetName()); IsDialogueWaveSelected = true; } } } bShowSelectedActions = !SelectedItemText.IsEmpty(); } for (TSubclassOf SoundNodeClass : AllowedSoundNodes) { const USoundNode* SoundNode = SoundNodeClass->GetDefaultObject(); // when dragging from an output pin you can create anything but a wave player if (!ActionMenuBuilder.FromPin || ActionMenuBuilder.FromPin->Direction == EGPD_Input || SoundNode->GetMaxChildNodes() > 0) { const FText Name = FText::FromString(SoundNodeClass->GetDescription()); { FFormatNamedArguments Arguments; Arguments.Add(TEXT("Name"), Name); const FText AddToolTip = FText::Format(LOCTEXT("NewSoundCueNodeTooltip", "Adds {Name} node here"), Arguments); TSharedPtr NewNodeAction(new FSoundCueGraphSchemaAction_NewNode(LOCTEXT("SoundNodeAction", "Sound Node"), Name, AddToolTip, 0)); ActionMenuBuilder.AddAction(NewNodeAction); NewNodeAction->SoundNodeClass = SoundNodeClass; } if (bShowSelectedActions && (SoundNode->GetMaxChildNodes() == USoundNode::MAX_ALLOWED_CHILD_NODES || ((SoundNodeClass == USoundNodeWavePlayer::StaticClass() && IsSoundWaveSelected) || (SoundNodeClass == USoundNodeDialoguePlayer::StaticClass() && IsDialogueWaveSelected)))) { FFormatNamedArguments Arguments; Arguments.Add(TEXT("Name"), Name); Arguments.Add(TEXT("SelectedItems"), SelectedItemText); const FText MenuDesc = FText::Format(LOCTEXT("NewSoundNodeRandom", "{Name}: {SelectedItems}"), Arguments); const FText ToolTip = FText::Format(LOCTEXT("NewSoundNodeRandomTooltip", "Adds a {Name} node for {SelectedItems} here"), Arguments); TSharedPtr NewNodeAction(new FSoundCueGraphSchemaAction_NewFromSelected(LOCTEXT("FromSelected", "From Selected"), MenuDesc, ToolTip, 0)); ActionMenuBuilder.AddAction(NewNodeAction); NewNodeAction->SoundNodeClass = (SoundNodeClass == USoundNodeWavePlayer::StaticClass() || SoundNodeClass == USoundNodeDialoguePlayer::StaticClass() ? NULL : SoundNodeClass); } } } } void USoundCueGraphSchema::GetCommentAction(FGraphActionMenuBuilder& ActionMenuBuilder, const UEdGraph* CurrentGraph) const { if (!ActionMenuBuilder.FromPin) { const bool bIsManyNodesSelected = CurrentGraph ? (FSoundCueEditorUtilities::GetNumberOfSelectedNodes(CurrentGraph) > 0) : false; const FText MenuDescription = bIsManyNodesSelected ? LOCTEXT("CreateCommentAction", "Create Comment from Selection") : LOCTEXT("AddCommentAction", "Add Comment..."); const FText ToolTip = LOCTEXT("CreateCommentToolTip", "Creates a comment."); TSharedPtr NewAction(new FSoundCueGraphSchemaAction_NewComment(FText::GetEmpty(), MenuDescription, ToolTip, 0)); ActionMenuBuilder.AddAction( NewAction ); } } int32 USoundCueGraphSchema::GetNodeSelectionCount(const UEdGraph* Graph) const { return FSoundCueEditorUtilities::GetNumberOfSelectedNodes(Graph); } TSharedPtr USoundCueGraphSchema::GetCreateCommentAction() const { return TSharedPtr(static_cast(new FSoundCueGraphSchemaAction_NewComment)); } #undef LOCTEXT_NAMESPACE