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

1116 lines
30 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "SoundCueEditor.h"
#include "Framework/Application/SlateApplication.h"
#include "Framework/MultiBox/MultiBoxBuilder.h"
#include "EdGraph/EdGraphNode.h"
#include "EngineAnalytics.h"
#include "Modules/ModuleManager.h"
#include "Styling/AppStyle.h"
#include "SoundCueGraph/SoundCueGraph.h"
#include "SoundCueGraph/SoundCueGraphNode.h"
#include "SoundCueGraph/SoundCueGraphNode_Root.h"
#include "SoundCueGraph/SoundCueGraphSchema.h"
#include "Sound/SoundWave.h"
#include "Sound/DialogueWave.h"
#include "Sound/SoundCue.h"
#include "Components/AudioComponent.h"
#include "AudioEditorModule.h"
#include "Sound/SoundNodeWavePlayer.h"
#include "ScopedTransaction.h"
#include "GraphEditor.h"
#include "GraphEditorActions.h"
#include "Kismet2/BlueprintEditorUtils.h"
#include "EdGraphUtilities.h"
#include "SNodePanel.h"
#include "Editor.h"
#include "SoundCueGraphEditorCommands.h"
#include "PropertyEditorModule.h"
#include "IDetailsView.h"
#include "Widgets/Docking/SDockTab.h"
#include "Framework/Commands/GenericCommands.h"
#include "Sound/SoundNodeDialoguePlayer.h"
#include "SSoundCuePalette.h"
#include "HAL/PlatformApplicationMisc.h"
#include "AudioDeviceManager.h"
#include "Audio/AudioDebug.h"
#define LOCTEXT_NAMESPACE "SoundCueEditor"
const FName FSoundCueEditor::GraphCanvasTabId( TEXT( "SoundCueEditor_GraphCanvas" ) );
const FName FSoundCueEditor::PropertiesTabId( TEXT( "SoundCueEditor_Properties" ) );
const FName FSoundCueEditor::PaletteTabId( TEXT( "SoundCueEditor_Palette" ) );
FSoundCueEditor::FSoundCueEditor()
: SoundCue(nullptr)
#if ENABLE_AUDIO_DEBUG
, Debugger(nullptr)
#endif
{
}
void FSoundCueEditor::RegisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
WorkspaceMenuCategory = InTabManager->AddLocalWorkspaceMenuCategory(LOCTEXT("WorkspaceMenu_SoundCueEditor", "Sound Cue Editor"));
auto WorkspaceMenuCategoryRef = WorkspaceMenuCategory.ToSharedRef();
FAssetEditorToolkit::RegisterTabSpawners(InTabManager);
InTabManager->RegisterTabSpawner( GraphCanvasTabId, FOnSpawnTab::CreateSP(this, &FSoundCueEditor::SpawnTab_GraphCanvas) )
.SetDisplayName( LOCTEXT("GraphCanvasTab", "Viewport") )
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "GraphEditor.EventGraph_16x"));
InTabManager->RegisterTabSpawner( PropertiesTabId, FOnSpawnTab::CreateSP(this, &FSoundCueEditor::SpawnTab_Properties) )
.SetDisplayName( LOCTEXT("DetailsTab", "Details") )
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "LevelEditor.Tabs.Details"));
InTabManager->RegisterTabSpawner( PaletteTabId, FOnSpawnTab::CreateSP(this, &FSoundCueEditor::SpawnTab_Palette) )
.SetDisplayName( LOCTEXT("PaletteTab", "Palette") )
.SetGroup(WorkspaceMenuCategoryRef)
.SetIcon(FSlateIcon(FAppStyle::GetAppStyleSetName(), "Kismet.Tabs.Palette"));
}
void FSoundCueEditor::UnregisterTabSpawners(const TSharedRef<class FTabManager>& InTabManager)
{
FAssetEditorToolkit::UnregisterTabSpawners(InTabManager);
InTabManager->UnregisterTabSpawner( GraphCanvasTabId );
InTabManager->UnregisterTabSpawner( PropertiesTabId );
InTabManager->UnregisterTabSpawner( PaletteTabId );
}
FSoundCueEditor::~FSoundCueEditor()
{
// Stop any playing sound cues when the cue editor closes
UAudioComponent* PreviewComp = GEditor->GetPreviewAudioComponent();
if (PreviewComp && PreviewComp->IsPlaying())
{
Stop();
}
GEditor->UnregisterForUndo( this );
}
void FSoundCueEditor::InitSoundCueEditor(const EToolkitMode::Type Mode, const TSharedPtr< class IToolkitHost >& InitToolkitHost, UObject* ObjectToEdit)
{
SoundCue = CastChecked<USoundCue>(ObjectToEdit);
// Support undo/redo
SoundCue->SetFlags(RF_Transactional);
GEditor->RegisterForUndo(this);
FGraphEditorCommands::Register();
FSoundCueGraphEditorCommands::Register();
BindGraphCommands();
CreateInternalWidgets();
const TSharedRef<FTabManager::FLayout> StandaloneDefaultLayout = FTabManager::NewLayout("Standalone_SoundCueEditor_Layout_v5")
->AddArea
(
FTabManager::NewPrimaryArea()
->SetOrientation(Orient_Vertical)
->Split(FTabManager::NewSplitter()
->SetOrientation(Orient_Horizontal)
->SetSizeCoefficient(0.9f)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.225f)
->SetHideTabWell(true)
->AddTab(PropertiesTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.65f)
->SetHideTabWell(true)
->AddTab(GraphCanvasTabId, ETabState::OpenedTab)
)
->Split
(
FTabManager::NewStack()
->SetSizeCoefficient(0.125f)
->SetHideTabWell(true)
->AddTab(PaletteTabId, ETabState::OpenedTab)
)
)
);
const bool bCreateDefaultStandaloneMenu = true;
const bool bCreateDefaultToolbar = true;
FAssetEditorToolkit::InitAssetEditor(Mode, InitToolkitHost, TEXT("SoundCueEditorApp"), StandaloneDefaultLayout, bCreateDefaultStandaloneMenu, bCreateDefaultToolbar, ObjectToEdit, false);
IAudioEditorModule* AudioEditorModule = &FModuleManager::LoadModuleChecked<IAudioEditorModule>( "AudioEditor" );
AddMenuExtender(AudioEditorModule->GetSoundCueMenuExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
ExtendToolbar();
RegenerateMenusAndToolbars();
#if ENABLE_AUDIO_DEBUG
if (GEditor->GetAudioDeviceManager())
{
Debugger = &GEditor->GetAudioDeviceManager()->GetDebugger();
}
#endif
// @todo toolkit world centric editing
/*if(IsWorldCentricAssetEditor())
{
SpawnToolkitTab(GetToolbarTabId(), FString(), EToolkitTabSpot::ToolBar);
SpawnToolkitTab(GraphCanvasTabId, FString(), EToolkitTabSpot::Viewport);
SpawnToolkitTab(PropertiesTabId, FString(), EToolkitTabSpot::Details);
}*/
}
USoundCue* FSoundCueEditor::GetSoundCue() const
{
return SoundCue;
}
void FSoundCueEditor::SetSelection(TArray<UObject*> SelectedObjects)
{
if (SoundCueProperties.IsValid())
{
SoundCueProperties->SetObjects(SelectedObjects);
}
}
bool FSoundCueEditor::GetBoundsForSelectedNodes(class FSlateRect& Rect, float Padding )
{
return SoundCueGraphEditor->GetBoundsForSelectedNodes(Rect, Padding);
}
int32 FSoundCueEditor::GetNumberOfSelectedNodes() const
{
return SoundCueGraphEditor->GetSelectedNodes().Num();
}
FName FSoundCueEditor::GetToolkitFName() const
{
return FName("SoundCueEditor");
}
FText FSoundCueEditor::GetBaseToolkitName() const
{
return LOCTEXT("AppLabel", "SoundCue Editor");
}
FString FSoundCueEditor::GetWorldCentricTabPrefix() const
{
return LOCTEXT("WorldCentricTabPrefix", "SoundCue ").ToString();
}
FLinearColor FSoundCueEditor::GetWorldCentricTabColorScale() const
{
return FLinearColor(0.3f, 0.2f, 0.5f, 0.5f);
}
TSharedRef<SDockTab> FSoundCueEditor::SpawnTab_GraphCanvas(const FSpawnTabArgs& Args)
{
check( Args.GetTabId() == GraphCanvasTabId );
TSharedRef<SDockTab> SpawnedTab = SNew(SDockTab)
.Label(LOCTEXT("SoundCueGraphCanvasTitle", "Viewport"));
if (SoundCueGraphEditor.IsValid())
{
SpawnedTab->SetContent(SoundCueGraphEditor.ToSharedRef());
}
return SpawnedTab;
}
TSharedRef<SDockTab> FSoundCueEditor::SpawnTab_Properties(const FSpawnTabArgs& Args)
{
check( Args.GetTabId() == PropertiesTabId );
return SNew(SDockTab)
.Label(LOCTEXT("SoundCueDetailsTitle", "Details"))
[
SoundCueProperties.ToSharedRef()
];
}
TSharedRef<SDockTab> FSoundCueEditor::SpawnTab_Palette(const FSpawnTabArgs& Args)
{
check( Args.GetTabId() == PaletteTabId );
return SNew(SDockTab)
.Label(LOCTEXT("SoundCuePaletteTitle", "Palette"))
[
Palette.ToSharedRef()
];
}
void FSoundCueEditor::AddReferencedObjects(FReferenceCollector& Collector)
{
Collector.AddReferencedObject(SoundCue);
}
void FSoundCueEditor::PostUndo(bool bSuccess)
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->ClearSelectionSet();
SoundCueGraphEditor->NotifyGraphChanged();
FSlateApplication::Get().DismissAllMenus();
}
}
void FSoundCueEditor::NotifyPostChange( const FPropertyChangedEvent& PropertyChangedEvent, class FProperty* PropertyThatChanged)
{
if (SoundCueGraphEditor.IsValid() && PropertyChangedEvent.ChangeType != EPropertyChangeType::Interactive)
{
SoundCueGraphEditor->NotifyGraphChanged();
}
}
void FSoundCueEditor::CreateInternalWidgets()
{
SoundCueGraphEditor = CreateGraphEditorWidget();
FDetailsViewArgs Args;
Args.bHideSelectionTip = true;
Args.NotifyHook = this;
FPropertyEditorModule& PropertyModule = FModuleManager::LoadModuleChecked<FPropertyEditorModule>("PropertyEditor");
SoundCueProperties = PropertyModule.CreateDetailView(Args);
SoundCueProperties->SetObject( SoundCue );
Palette = SNew(SSoundCuePalette);
}
void FSoundCueEditor::ExtendToolbar()
{
struct Local
{
static void FillToolbar(FToolBarBuilder& ToolbarBuilder)
{
ToolbarBuilder.BeginSection("Debug");
{
ToolbarBuilder.AddToolBarButton(FSoundCueGraphEditorCommands::Get().ToggleSolo);
ToolbarBuilder.AddToolBarButton(FSoundCueGraphEditorCommands::Get().ToggleMute);
}
ToolbarBuilder.EndSection();
ToolbarBuilder.BeginSection("Toolbar");
{
ToolbarBuilder.AddToolBarButton(FSoundCueGraphEditorCommands::Get().PlayCue);
ToolbarBuilder.AddToolBarButton(FSoundCueGraphEditorCommands::Get().PlayNode);
ToolbarBuilder.AddToolBarButton(FSoundCueGraphEditorCommands::Get().StopCueNode);
}
ToolbarBuilder.EndSection();
}
};
TSharedPtr<FExtender> ToolbarExtender = MakeShareable(new FExtender);
ToolbarExtender->AddToolBarExtension(
"Asset",
EExtensionHook::After,
GetToolkitCommands(),
FToolBarExtensionDelegate::CreateStatic( &Local::FillToolbar )
);
AddToolbarExtender(ToolbarExtender);
IAudioEditorModule* AudioEditorModule = &FModuleManager::LoadModuleChecked<IAudioEditorModule>( "AudioEditor" );
AddToolbarExtender(AudioEditorModule->GetSoundCueToolBarExtensibilityManager()->GetAllExtenders(GetToolkitCommands(), GetEditingObjects()));
}
void FSoundCueEditor::BindGraphCommands()
{
const FSoundCueGraphEditorCommands& Commands = FSoundCueGraphEditorCommands::Get();
ToolkitCommands->MapAction(
Commands.PlayCue,
FExecuteAction::CreateSP(this, &FSoundCueEditor::PlayCue));
ToolkitCommands->MapAction(
Commands.PlayNode,
FExecuteAction::CreateSP(this, &FSoundCueEditor::PlayNode),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanPlayNode ));
ToolkitCommands->MapAction(
Commands.StopCueNode,
FExecuteAction::CreateSP(this, &FSoundCueEditor::Stop));
ToolkitCommands->MapAction(
Commands.TogglePlayback,
FExecuteAction::CreateSP(this, &FSoundCueEditor::TogglePlayback));
ToolkitCommands->MapAction(
FGenericCommands::Get().Undo,
FExecuteAction::CreateSP( this, &FSoundCueEditor::UndoGraphAction ));
ToolkitCommands->MapAction(
FGenericCommands::Get().Redo,
FExecuteAction::CreateSP( this, &FSoundCueEditor::RedoGraphAction ));
ToolkitCommands->MapAction(
Commands.ToggleSolo,
FExecuteAction::CreateSP(this, &FSoundCueEditor::ToggleSolo),
FCanExecuteAction::CreateSP(this, &FSoundCueEditor::CanExcuteToggleSolo),
FIsActionChecked::CreateSP(this, &FSoundCueEditor::IsSoloToggled));
ToolkitCommands->MapAction(
Commands.ToggleMute,
FExecuteAction::CreateSP(this, &FSoundCueEditor::ToggleMute),
FCanExecuteAction::CreateSP(this, &FSoundCueEditor::CanExcuteToggleMute),
FIsActionChecked::CreateSP(this, &FSoundCueEditor::IsMuteToggled));
}
void FSoundCueEditor::PlayCue()
{
GEditor->PlayPreviewSound(SoundCue);
SoundCueGraphEditor->RegisterActiveTimer(0.0f,
FWidgetActiveTimerDelegate::CreateLambda(
[](double InCurrentTime, float InDeltaTime)
{
UAudioComponent* PreviewComp = GEditor->GetPreviewAudioComponent();
if (PreviewComp && PreviewComp->IsPlaying())
{
return EActiveTimerReturnType::Continue;
}
else
{
return EActiveTimerReturnType::Stop;
}
}));
}
void FSoundCueEditor::PlayNode()
{
// already checked that only one node is selected
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
PlaySingleNode(CastChecked<UEdGraphNode>(*NodeIt));
}
}
bool FSoundCueEditor::CanPlayNode() const
{
return GetSelectedNodes().Num() == 1;
}
void FSoundCueEditor::Stop()
{
GEditor->ResetPreviewAudioComponent();
}
void FSoundCueEditor::TogglePlayback()
{
UAudioComponent* PreviewComp = GEditor->GetPreviewAudioComponent();
if ( PreviewComp && PreviewComp->IsPlaying() )
{
Stop();
}
else
{
PlayCue();
}
}
void FSoundCueEditor::ToggleSolo()
{
#if ENABLE_AUDIO_DEBUG
if (Debugger)
{
Debugger->ToggleSoloSoundCue(SoundCue->GetFName());
}
#endif
}
bool FSoundCueEditor::CanExcuteToggleSolo() const
{
#if ENABLE_AUDIO_DEBUG
// Allow Solo if Mute is not Toggle on
if (Debugger)
{
return !Debugger->IsMuteSoundCue(SoundCue->GetFName());
}
#endif
return false;
}
bool FSoundCueEditor::IsSoloToggled() const
{
#if ENABLE_AUDIO_DEBUG
if (Debugger)
{
return Debugger->IsSoloSoundCue(SoundCue->GetFName());
}
#endif
return false;
}
void FSoundCueEditor::ToggleMute()
{
#if ENABLE_AUDIO_DEBUG
if (Debugger)
{
Debugger->ToggleMuteSoundCue(SoundCue->GetFName());
}
#endif
}
bool FSoundCueEditor::CanExcuteToggleMute() const
{
#if ENABLE_AUDIO_DEBUG
// Allow Mute if Solo is not Toggle on
if (Debugger)
{
return !Debugger->IsSoloSoundCue(SoundCue->GetFName());
}
#endif
return false;
}
bool FSoundCueEditor::IsMuteToggled() const
{
#if ENABLE_AUDIO_DEBUG
if (Debugger)
{
return Debugger->IsMuteSoundCue(SoundCue->GetFName());
}
#endif
return false;
}
void FSoundCueEditor::PlaySingleNode(UEdGraphNode* Node)
{
USoundCueGraphNode* SoundGraphNode = Cast<USoundCueGraphNode>(Node);
if (SoundGraphNode)
{
GEditor->PlayPreviewSound(NULL, SoundGraphNode->SoundNode);
}
else
{
// must be root node, play the whole cue
PlayCue();
}
}
void FSoundCueEditor::SyncInBrowser()
{
TArray<UObject*> ObjectsToSync;
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
USoundCueGraphNode* SelectedNode = Cast<USoundCueGraphNode>(*NodeIt);
if (SelectedNode)
{
USoundNodeWavePlayer* SelectedWave = Cast<USoundNodeWavePlayer>(SelectedNode->SoundNode);
if (SelectedWave && SelectedWave->GetSoundWave())
{
ObjectsToSync.AddUnique(SelectedWave->GetSoundWave());
}
USoundNodeDialoguePlayer* SelectedDialogue = Cast<USoundNodeDialoguePlayer>(SelectedNode->SoundNode);
if (SelectedDialogue && SelectedDialogue->GetDialogueWave())
{
ObjectsToSync.AddUnique(SelectedDialogue->GetDialogueWave());
}
}
}
if (ObjectsToSync.Num() > 0)
{
GEditor->SyncBrowserToObjects(ObjectsToSync);
}
}
bool FSoundCueEditor::CanSyncInBrowser() const
{
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
USoundCueGraphNode* SelectedNode = Cast<USoundCueGraphNode>(*NodeIt);
if (SelectedNode)
{
USoundNodeWavePlayer* WavePlayer = Cast<USoundNodeWavePlayer>(SelectedNode->SoundNode);
if (WavePlayer && WavePlayer->GetSoundWave())
{
return true;
}
USoundNodeDialoguePlayer* SelectedDialogue = Cast<USoundNodeDialoguePlayer>(SelectedNode->SoundNode);
if (SelectedDialogue && SelectedDialogue->GetDialogueWave())
{
return true;
}
}
}
return false;
}
void FSoundCueEditor::AddInput()
{
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
// Iterator used but should only contain one node
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
USoundCueGraphNode* SelectedNode = Cast<USoundCueGraphNode>(*NodeIt);
if (SelectedNode)
{
SelectedNode->AddInputPin();
break;
}
}
}
bool FSoundCueEditor::CanAddInput() const
{
return GetSelectedNodes().Num() == 1;
}
void FSoundCueEditor::DeleteInput()
{
if (SoundCueGraphEditor.IsValid())
{
UEdGraphPin* SelectedPin = SoundCueGraphEditor->GetGraphPinForMenu();
if (ensure(SelectedPin))
{
USoundCueGraphNode* SelectedNode = Cast<USoundCueGraphNode>(SelectedPin->GetOwningNode());
if (SelectedNode && SelectedNode == SelectedPin->GetOwningNode())
{
SelectedNode->RemoveInputPin(SelectedPin);
}
}
}
}
bool FSoundCueEditor::CanDeleteInput() const
{
return true;
}
void FSoundCueEditor::OnCreateComment()
{
FSoundCueGraphSchemaAction_NewComment CommentAction;
CommentAction.PerformAction(SoundCue->SoundCueGraph, NULL, SoundCueGraphEditor->GetPasteLocation2f());
}
TSharedRef<SGraphEditor> FSoundCueEditor::CreateGraphEditorWidget()
{
if ( !GraphEditorCommands.IsValid() )
{
GraphEditorCommands = MakeShareable( new FUICommandList );
GraphEditorCommands->MapAction( FSoundCueGraphEditorCommands::Get().PlayNode,
FExecuteAction::CreateSP(this, &FSoundCueEditor::PlayNode),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanPlayNode ));
GraphEditorCommands->MapAction( FSoundCueGraphEditorCommands::Get().BrowserSync,
FExecuteAction::CreateSP(this, &FSoundCueEditor::SyncInBrowser),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanSyncInBrowser ));
GraphEditorCommands->MapAction( FSoundCueGraphEditorCommands::Get().AddInput,
FExecuteAction::CreateSP(this, &FSoundCueEditor::AddInput),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanAddInput ));
GraphEditorCommands->MapAction( FSoundCueGraphEditorCommands::Get().DeleteInput,
FExecuteAction::CreateSP(this, &FSoundCueEditor::DeleteInput),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanDeleteInput ));
// Graph Editor Commands
GraphEditorCommands->MapAction( FGraphEditorCommands::Get().CreateComment,
FExecuteAction::CreateSP( this, &FSoundCueEditor::OnCreateComment )
);
// Editing commands
GraphEditorCommands->MapAction( FGenericCommands::Get().SelectAll,
FExecuteAction::CreateSP( this, &FSoundCueEditor::SelectAllNodes ),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanSelectAllNodes )
);
GraphEditorCommands->MapAction( FGenericCommands::Get().Delete,
FExecuteAction::CreateSP( this, &FSoundCueEditor::DeleteSelectedNodes ),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanDeleteNodes )
);
GraphEditorCommands->MapAction( FGenericCommands::Get().Copy,
FExecuteAction::CreateSP( this, &FSoundCueEditor::CopySelectedNodes ),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanCopyNodes )
);
GraphEditorCommands->MapAction( FGenericCommands::Get().Cut,
FExecuteAction::CreateSP( this, &FSoundCueEditor::CutSelectedNodes ),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanCutNodes )
);
GraphEditorCommands->MapAction( FGenericCommands::Get().Paste,
FExecuteAction::CreateSP( this, &FSoundCueEditor::PasteNodes ),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanPasteNodes )
);
GraphEditorCommands->MapAction( FGenericCommands::Get().Duplicate,
FExecuteAction::CreateSP( this, &FSoundCueEditor::DuplicateNodes ),
FCanExecuteAction::CreateSP( this, &FSoundCueEditor::CanDuplicateNodes )
);
// Alignment Commands
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesTop,
FExecuteAction::CreateSP(this, &FSoundCueEditor::OnAlignTop)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesMiddle,
FExecuteAction::CreateSP(this, &FSoundCueEditor::OnAlignMiddle)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesBottom,
FExecuteAction::CreateSP(this, &FSoundCueEditor::OnAlignBottom)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesLeft,
FExecuteAction::CreateSP(this, &FSoundCueEditor::OnAlignLeft)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesCenter,
FExecuteAction::CreateSP(this, &FSoundCueEditor::OnAlignCenter)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().AlignNodesRight,
FExecuteAction::CreateSP(this, &FSoundCueEditor::OnAlignRight)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().StraightenConnections,
FExecuteAction::CreateSP(this, &FSoundCueEditor::OnStraightenConnections)
);
// Distribution Commands
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DistributeNodesHorizontally,
FExecuteAction::CreateSP(this, &FSoundCueEditor::OnDistributeNodesH)
);
GraphEditorCommands->MapAction(FGraphEditorCommands::Get().DistributeNodesVertically,
FExecuteAction::CreateSP(this, &FSoundCueEditor::OnDistributeNodesV)
);
}
FGraphAppearanceInfo AppearanceInfo;
AppearanceInfo.CornerText = LOCTEXT("AppearanceCornerText_SoundCue", "SOUND CUE");
SGraphEditor::FGraphEditorEvents InEvents;
InEvents.OnSelectionChanged = SGraphEditor::FOnSelectionChanged::CreateSP(this, &FSoundCueEditor::OnSelectedNodesChanged);
InEvents.OnTextCommitted = FOnNodeTextCommitted::CreateSP(this, &FSoundCueEditor::OnNodeTitleCommitted);
InEvents.OnNodeDoubleClicked = FSingleNodeEvent::CreateSP(this, &FSoundCueEditor::PlaySingleNode);
return SNew(SGraphEditor)
.AdditionalCommands(GraphEditorCommands)
.IsEditable(true)
.Appearance(AppearanceInfo)
.GraphToEdit(SoundCue->GetGraph())
.GraphEvents(InEvents)
.AutoExpandActionMenu(true)
.ShowGraphStateOverlay(false);
}
FGraphPanelSelectionSet FSoundCueEditor::GetSelectedNodes() const
{
FGraphPanelSelectionSet CurrentSelection;
if (SoundCueGraphEditor.IsValid())
{
CurrentSelection = SoundCueGraphEditor->GetSelectedNodes();
}
return CurrentSelection;
}
void FSoundCueEditor::OnSelectedNodesChanged(const TSet<class UObject*>& NewSelection)
{
TArray<UObject*> Selection;
if(NewSelection.Num())
{
for(TSet<class UObject*>::TConstIterator SetIt(NewSelection);SetIt;++SetIt)
{
if (Cast<USoundCueGraphNode_Root>(*SetIt))
{
Selection.Add(GetSoundCue());
}
else if (USoundCueGraphNode* GraphNode = Cast<USoundCueGraphNode>(*SetIt))
{
Selection.Add(GraphNode->SoundNode);
}
else
{
Selection.Add(*SetIt);
}
}
//Selection = NewSelection.Array();
}
else
{
Selection.Add(GetSoundCue());
}
SetSelection(Selection);
}
void FSoundCueEditor::OnNodeTitleCommitted(const FText& NewText, ETextCommit::Type CommitInfo, UEdGraphNode* NodeBeingChanged)
{
if (NodeBeingChanged)
{
const FScopedTransaction Transaction( LOCTEXT( "RenameNode", "Rename Node" ) );
NodeBeingChanged->Modify();
NodeBeingChanged->OnRenameNode(NewText.ToString());
}
}
void FSoundCueEditor::SelectAllNodes()
{
SoundCueGraphEditor->SelectAllNodes();
}
bool FSoundCueEditor::CanSelectAllNodes() const
{
return true;
}
void FSoundCueEditor::DeleteSelectedNodes()
{
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "SoundCueEditorDeleteSelectedNode", "Delete Selected Sound Cue Node") );
SoundCueGraphEditor->GetCurrentGraph()->Modify();
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
SoundCueGraphEditor->ClearSelectionSet();
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
UEdGraphNode* Node = CastChecked<UEdGraphNode>(*NodeIt);
if (Node->CanUserDeleteNode())
{
if (USoundCueGraphNode* SoundGraphNode = Cast<USoundCueGraphNode>(Node))
{
USoundNode* DelNode = SoundGraphNode->SoundNode;
FBlueprintEditorUtils::RemoveNode(NULL, SoundGraphNode, true);
// Make sure SoundCue is updated to match graph
SoundCue->CompileSoundNodesFromGraphNodes();
// Remove this node from the SoundCue's list of all SoundNodes
SoundCue->AllNodes.Remove(DelNode);
SoundCue->MarkPackageDirty();
}
else
{
FBlueprintEditorUtils::RemoveNode(NULL, Node, true);
}
}
}
}
bool FSoundCueEditor::CanDeleteNodes() const
{
const FGraphPanelSelectionSet SelectedNodes = GetSelectedNodes();
if (SelectedNodes.Num() == 1)
{
for (FGraphPanelSelectionSet::TConstIterator NodeIt(SelectedNodes); NodeIt; ++NodeIt)
{
if (Cast<USoundCueGraphNode_Root>(*NodeIt))
{
// Return false if only root node is selected, as it can't be deleted
return false;
}
}
}
return SelectedNodes.Num() > 0;
}
void FSoundCueEditor::DeleteSelectedDuplicatableNodes()
{
// Cache off the old selection
const FGraphPanelSelectionSet OldSelectedNodes = GetSelectedNodes();
// Clear the selection and only select the nodes that can be duplicated
FGraphPanelSelectionSet RemainingNodes;
SoundCueGraphEditor->ClearSelectionSet();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(OldSelectedNodes); SelectedIter; ++SelectedIter)
{
UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter);
if ((Node != NULL) && Node->CanDuplicateNode())
{
SoundCueGraphEditor->SetNodeSelection(Node, true);
}
else
{
RemainingNodes.Add(Node);
}
}
// Delete the duplicatable nodes
DeleteSelectedNodes();
// Reselect whatever's left from the original selection after the deletion
SoundCueGraphEditor->ClearSelectionSet();
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(RemainingNodes); SelectedIter; ++SelectedIter)
{
if (UEdGraphNode* Node = Cast<UEdGraphNode>(*SelectedIter))
{
SoundCueGraphEditor->SetNodeSelection(Node, true);
}
}
}
void FSoundCueEditor::CutSelectedNodes()
{
CopySelectedNodes();
// Cut should only delete nodes that can be duplicated
DeleteSelectedDuplicatableNodes();
}
bool FSoundCueEditor::CanCutNodes() const
{
return CanCopyNodes() && CanDeleteNodes();
}
void FSoundCueEditor::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(USoundCueGraphNode* Node = Cast<USoundCueGraphNode>(*SelectedIter))
{
Node->PrepareForCopying();
}
}
FEdGraphUtilities::ExportNodesToText(SelectedNodes, /*out*/ ExportedText);
FPlatformApplicationMisc::ClipboardCopy(*ExportedText);
// Make sure SoundCue remains the owner of the copied nodes
for (FGraphPanelSelectionSet::TConstIterator SelectedIter(SelectedNodes); SelectedIter; ++SelectedIter)
{
if (USoundCueGraphNode* Node = Cast<USoundCueGraphNode>(*SelectedIter))
{
Node->PostCopyNode();
}
}
}
bool FSoundCueEditor::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 != NULL) && Node->CanDuplicateNode())
{
return true;
}
}
return false;
}
void FSoundCueEditor::PasteNodes()
{
PasteNodesHere(SoundCueGraphEditor->GetPasteLocation2f());
}
void FSoundCueEditor::PasteNodesHere(const FVector2D& Location)
{
// Undo/Redo support
const FScopedTransaction Transaction( NSLOCTEXT("UnrealEd", "SoundCueEditorPaste", "Paste Sound Cue Node") );
SoundCue->GetGraph()->Modify();
SoundCue->Modify();
// Clear the selection set (newly pasted stuff will be selected)
SoundCueGraphEditor->ClearSelectionSet();
// Grab the text to paste from the clipboard.
FString TextToImport;
FPlatformApplicationMisc::ClipboardPaste(TextToImport);
// Import the nodes
TSet<UEdGraphNode*> PastedNodes;
FEdGraphUtilities::ImportNodesFromText(SoundCue->GetGraph(), 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<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
{
UEdGraphNode* Node = *It;
AvgNodePosition.X += Node->NodePosX;
AvgNodePosition.Y += Node->NodePosY;
}
if ( PastedNodes.Num() > 0 )
{
float InvNumNodes = 1.0f/float(PastedNodes.Num());
AvgNodePosition.X *= InvNumNodes;
AvgNodePosition.Y *= InvNumNodes;
}
for (TSet<UEdGraphNode*>::TIterator It(PastedNodes); It; ++It)
{
UEdGraphNode* Node = *It;
if (USoundCueGraphNode* SoundGraphNode = Cast<USoundCueGraphNode>(Node))
{
SoundCue->AllNodes.Add(SoundGraphNode->SoundNode);
}
// Select the newly pasted stuff
SoundCueGraphEditor->SetNodeSelection(Node, true);
Node->NodePosX = (Node->NodePosX - AvgNodePosition.X) + Location.X ;
Node->NodePosY = (Node->NodePosY - AvgNodePosition.Y) + Location.Y ;
Node->SnapToGrid(SNodePanel::GetSnapGridSize());
// Give new node a different Guid from the old one
Node->CreateNewGuid();
}
// Force new pasted SoundNodes to have same connections as graph nodes
SoundCue->CompileSoundNodesFromGraphNodes();
// Update UI
SoundCueGraphEditor->NotifyGraphChanged();
SoundCue->PostEditChange();
SoundCue->MarkPackageDirty();
}
bool FSoundCueEditor::CanPasteNodes() const
{
FString ClipboardContent;
FPlatformApplicationMisc::ClipboardPaste(ClipboardContent);
return FEdGraphUtilities::CanImportNodesFromText(SoundCue->SoundCueGraph, ClipboardContent);
}
void FSoundCueEditor::DuplicateNodes()
{
// Copy and paste current selection
CopySelectedNodes();
PasteNodes();
}
bool FSoundCueEditor::CanDuplicateNodes() const
{
return CanCopyNodes();
}
void FSoundCueEditor::UndoGraphAction()
{
GEditor->UndoTransaction();
}
void FSoundCueEditor::RedoGraphAction()
{
// Clear selection, to avoid holding refs to nodes that go away
SoundCueGraphEditor->ClearSelectionSet();
GEditor->RedoTransaction();
}
void FSoundCueEditor::OnAlignTop()
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->OnAlignTop();
}
}
void FSoundCueEditor::OnAlignMiddle()
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->OnAlignMiddle();
}
}
void FSoundCueEditor::OnAlignBottom()
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->OnAlignBottom();
}
}
void FSoundCueEditor::OnAlignLeft()
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->OnAlignLeft();
}
}
void FSoundCueEditor::OnAlignCenter()
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->OnAlignCenter();
}
}
void FSoundCueEditor::OnAlignRight()
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->OnAlignRight();
}
}
void FSoundCueEditor::OnStraightenConnections()
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->OnStraightenConnections();
}
}
void FSoundCueEditor::OnDistributeNodesH()
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->OnDistributeNodesH();
}
}
void FSoundCueEditor::OnDistributeNodesV()
{
if (SoundCueGraphEditor.IsValid())
{
SoundCueGraphEditor->OnDistributeNodesV();
}
}
#undef LOCTEXT_NAMESPACE