// Copyright Epic Games, Inc. All Rights Reserved. #include "SequencerSelection.h" #include "MovieSceneSection.h" #include "SequencerCommonHelpers.h" #include "MVVM/SharedViewModelData.h" #include "MVVM/ViewModels/SectionModel.h" #include "MVVM/ViewModels/TrackModel.h" #include "MVVM/ViewModels/ChannelModel.h" #include "MVVM/ViewModels/ViewModelIterators.h" #include "MVVM/Extensions/IObjectBindingExtension.h" #include "MVVM/Extensions/ISelectableExtension.h" #include "MVVM/SectionModelStorageExtension.h" FSequencerSelection::FSequencerSelection() : SerialNumber(0) , SuspendBroadcastCount(0) , bOutlinerNodeSelectionChangedBroadcastPending(false) , bEmptySelectedOutlinerNodesWithSectionsPending(false) , bNodesWithSelectedKeysOrSectionsDirty(false) { } TSharedPtr FSequencerSelection::GetRootModel() const { return RootModel; } void FSequencerSelection::SetRootModel(TSharedPtr InRootModel) { if (RootModel && HierarchyChangedHandle.IsValid()) { RootModel->GetSharedData()->UnsubscribeFromHierarchyChanged(RootModel, HierarchyChangedHandle); HierarchyChangedHandle = FDelegateHandle(); } RootModel = InRootModel; if (RootModel) { FSimpleMulticastDelegate& HierarchyChanged = RootModel->GetSharedData()->SubscribeToHierarchyChanged(RootModel); HierarchyChangedHandle = HierarchyChanged.AddRaw(this, &FSequencerSelection::OnHierarchyChanged); } } const TSet& FSequencerSelection::GetSelectedKeys() const { return SelectedKeys; } TSet> FSequencerSelection::GetSelectedSections() const { using namespace UE::Sequencer; TSet> Sections; for (const TWeakPtr& TrackAreaModel : SelectedTrackAreaItems) { if (TSharedPtr Pinned = TrackAreaModel.Pin()) { FSectionModel* Section = Pinned->CastThis(); if (Section && Section->GetSection()) { Sections.Add(Section->GetSection()); } } } return Sections; } const TSet>& FSequencerSelection::GetSelectedOutlinerItems() const { return SelectedOutlinerItems; } const TSet>& FSequencerSelection::GetSelectedTrackAreaItems() const { return SelectedTrackAreaItems; } const TSet>& FSequencerSelection::GetNodesWithSelectedKeysOrSections() const { using namespace UE::Sequencer; if (!bNodesWithSelectedKeysOrSectionsDirty) { return NodesWithSelectedKeysOrSections; } bNodesWithSelectedKeysOrSectionsDirty = false; NodesWithSelectedKeysOrSections.Empty(); // Handle selected keys for (const FSequencerSelectedKey& Key : SelectedKeys) { if (TSharedPtr Channel = Key.WeakChannel.Pin()) { TViewModelPtr Outliner = Channel->GetLinkedOutlinerItem(); if (Outliner) { NodesWithSelectedKeysOrSections.Add(Outliner.AsModel()); } } } // Handle selected track area items for (TWeakPtr WeakTrackAreaModel : SelectedTrackAreaItems) { TSharedPtr TrackAreaModel = WeakTrackAreaModel.Pin(); if (TrackAreaModel) { TViewModelPtr OutlinerItem = TrackAreaModel->FindAncestorOfType(); if (OutlinerItem) { NodesWithSelectedKeysOrSections.Add(OutlinerItem.AsModel()); } } } return NodesWithSelectedKeysOrSections; } FSequencerSelection::FOnSelectionChanged& FSequencerSelection::GetOnKeySelectionChanged() { return OnKeySelectionChanged; } FSequencerSelection::FOnSelectionChanged& FSequencerSelection::GetOnOutlinerNodeSelectionChanged() { return OnOutlinerNodeSelectionChanged; } FSequencerSelection::FOnSelectionChangedObjectGuids& FSequencerSelection::GetOnOutlinerNodeSelectionChangedObjectGuids() { return OnOutlinerNodeSelectionChangedObjectGuids; } TArray FSequencerSelection::GetBoundObjectsGuids() { using namespace UE::Sequencer; TArray OutGuids; TSet> SelectedItems = GetNodesWithSelectedKeysOrSections(); if (SelectedItems.Num() == 0) { SelectedItems = GetSelectedOutlinerItems(); } for (TWeakPtr WeakModel : SelectedItems) { TSharedPtr Model = WeakModel.Pin(); if (Model) { TSharedPtr ObjectBinding = Model->FindAncestorOfType(true); if (ObjectBinding) { OutGuids.Add(ObjectBinding->GetObjectGuid()); } } } return OutGuids; } TArray FSequencerSelection::GetSelectedTracks() const { using namespace UE::Sequencer; TArray OutTracks; for (TWeakPtr WeakModel : SelectedOutlinerItems) { TSharedPtr Model = WeakModel.Pin(); if (Model) { TSharedPtr Track = Model->FindAncestorOfType(true); if (Track) { OutTracks.Add(Track->GetTrack()); } } } return OutTracks; } void FSequencerSelection::AddToSelection(TSharedPtr InModel) { if (InModel->IsA()) { AddToOutlinerSelection(InModel); } else { AddToTrackAreaSelection(InModel); } } void FSequencerSelection::AddToTrackAreaSelection(TSharedPtr InModel) { using namespace UE::Sequencer; ISelectableExtension* Selectable = InModel->CastThis(); if (Selectable && Selectable->IsSelectable() == ESelectionIntent::Never) { return; } bNodesWithSelectedKeysOrSectionsDirty = true; ++SerialNumber; SelectedTrackAreaItems.Add(InModel); if (TSharedPtr ParentItem = InModel->FindAncestorOfType()) { ParentItem->ToggleSelectionState(EOutlinerSelectionState::HasSelectedTrackAreaItems, true); } if (FSectionModel* SectionModel = InModel->CastThis()) { if (UMovieSceneSection* Section = SectionModel->GetSection()) { if ( IsBroadcasting() ) { OnSectionSelectionChanged.Broadcast(); OnOutlinerNodeSelectionChangedObjectGuids.Broadcast(); } // Deselect any outliner nodes that aren't within the trunk of this section EmptySelectedOutlinerNodesWithoutSections(TArray{Section}); } } } void FSequencerSelection::AddToSelection(const FSequencerSelectedKey& Key) { using namespace UE::Sequencer; bNodesWithSelectedKeysOrSectionsDirty = true; ++SerialNumber; SelectedKeys.Add(Key); if (TViewModelPtr OutlinerItem = Key.WeakChannel.Pin()->GetLinkedOutlinerItem()) { OutlinerItem->ToggleSelectionState(EOutlinerSelectionState::HasSelectedKeys, true); } if ( IsBroadcasting() ) { OnKeySelectionChanged.Broadcast(); OnOutlinerNodeSelectionChangedObjectGuids.Broadcast(); // Deselect any outliner nodes that aren't within the trunk of this key TArray Sections; Sections.Add(Key.Section); EmptySelectedOutlinerNodesWithoutSections(Sections); } else { bEmptySelectedOutlinerNodesWithSectionsPending = true; } } void FSequencerSelection::AddToOutlinerSelection(TSharedPtr InModel) { using namespace UE::Sequencer; ISelectableExtension* Selectable = InModel->CastThis(); if (Selectable && Selectable->IsSelectable() == ESelectionIntent::Never) { return; } ++SerialNumber; SelectedOutlinerItems.Add(InModel); if (IOutlinerExtension* OutlinerItem = InModel->CastThis()) { OutlinerItem->SetSelectionState(EOutlinerSelectionState::SelectedDirectly); } if ( IsBroadcasting() ) { OnOutlinerNodeSelectionChanged.Broadcast(); OnOutlinerNodeSelectionChangedObjectGuids.Broadcast(); } EmptySelectedKeys(); EmptySelectedTrackAreaItems(); } void FSequencerSelection::AddToSelection(const TArrayView>& InModels) { using namespace UE::Sequencer; ++SerialNumber; for (TSharedPtr InModel : InModels) { SelectedOutlinerItems.Add(InModel); if (IOutlinerExtension* OutlinerItem = InModel->CastThis()) { OutlinerItem->SetSelectionState(EOutlinerSelectionState::SelectedDirectly); } } if (IsBroadcasting()) { OnOutlinerNodeSelectionChanged.Broadcast(); OnOutlinerNodeSelectionChangedObjectGuids.Broadcast(); } EmptySelectedKeys(); EmptySelectedTrackAreaItems(); } void FSequencerSelection::AddToSelection(const TArrayView>& InModels) { using namespace UE::Sequencer; ++SerialNumber; for (TSharedRef InModel : InModels) { SelectedOutlinerItems.Add(InModel); if (IOutlinerExtension* OutlinerItem = InModel->CastThis()) { OutlinerItem->SetSelectionState(EOutlinerSelectionState::SelectedDirectly); } } if (IsBroadcasting()) { OnOutlinerNodeSelectionChanged.Broadcast(); OnOutlinerNodeSelectionChangedObjectGuids.Broadcast(); } EmptySelectedKeys(); EmptySelectedTrackAreaItems(); } void FSequencerSelection::RemoveFromSelection(const FSequencerSelectedKey& Key) { using namespace UE::Sequencer; bNodesWithSelectedKeysOrSectionsDirty = true; ++SerialNumber; SelectedKeys.Remove(Key); if (TViewModelPtr OutlinerItem = Key.WeakChannel.Pin()->GetLinkedOutlinerItem()) { OutlinerItem->ToggleSelectionState(EOutlinerSelectionState::HasSelectedKeys, false); } if ( IsBroadcasting() ) { OnKeySelectionChanged.Broadcast(); } } void FSequencerSelection::RemoveFromSelection(TWeakPtr InModel) { if (TSharedPtr InModelPtr = InModel.Pin()) { RemoveFromSelection(InModelPtr); } } void FSequencerSelection::RemoveFromSelection(TSharedPtr OutlinerNode) { if (OutlinerNode) { RemoveFromSelection(OutlinerNode.ToSharedRef()); } } void FSequencerSelection::RemoveFromSelection(TSharedRef OutlinerNode) { using namespace UE::Sequencer; bNodesWithSelectedKeysOrSectionsDirty = true; ++SerialNumber; SelectedOutlinerItems.Remove(OutlinerNode); SelectedTrackAreaItems.Remove(OutlinerNode); if (IOutlinerExtension* OutlinerItem = OutlinerNode->CastThis()) { OutlinerItem->SetSelectionState(EOutlinerSelectionState::None); } if ( IsBroadcasting() ) { OnOutlinerNodeSelectionChanged.Broadcast(); } } bool FSequencerSelection::IsSelected(const FSequencerSelectedKey& Key) const { return SelectedKeys.Contains(Key); } bool FSequencerSelection::IsSelected(TWeakPtr InModel) const { return SelectedOutlinerItems.Contains(InModel) || SelectedTrackAreaItems.Contains(InModel); } bool FSequencerSelection::NodeHasSelectedKeysOrSections(TWeakPtr InModel) const { return GetNodesWithSelectedKeysOrSections().Contains(InModel); } void FSequencerSelection::Empty() { using namespace UE::Sequencer; bNodesWithSelectedKeysOrSectionsDirty = true; ++SerialNumber; FSectionModelStorageExtension* SectionModelStorage = RootModel->CastDynamicChecked(); for (const FSequencerSelectedKey& Key : SelectedKeys) { if (TSharedPtr SectionModel = SectionModelStorage->FindModelForSection(Key.Section)) { for (TSharedPtr ParentItem : SectionModel->GetAncestorsOfType(true)) { ParentItem->SetSelectionState(EOutlinerSelectionState::None); } } } for (TWeakPtr WeakModel : SelectedTrackAreaItems) { if (TSharedPtr Model = WeakModel.Pin()) { for (TSharedPtr ParentOutlinerItem : Model->GetAncestorsOfType()) { ParentOutlinerItem->SetSelectionState(EOutlinerSelectionState::None); } } } for (TWeakPtr WeakModel : SelectedOutlinerItems) { if (TSharedPtr Model = WeakModel.Pin()) { if (IOutlinerExtension* OutlinerItem = Model->CastThis()) { OutlinerItem->SetSelectionState(EOutlinerSelectionState::None); } } } EmptySelectedKeys(); EmptySelectedOutlinerNodes(); EmptySelectedTrackAreaItems(); } void FSequencerSelection::EmptySelectedKeys() { using namespace UE::Sequencer; if (!SelectedKeys.Num()) { return; } bNodesWithSelectedKeysOrSectionsDirty = true; ++SerialNumber; FSectionModelStorageExtension* SectionModelStorage = RootModel->CastDynamicChecked(); for (const FSequencerSelectedKey& Key : SelectedKeys) { if (TViewModelPtr OutlinerItem = Key.WeakChannel.Pin()->GetLinkedOutlinerItem()) { OutlinerItem->ToggleSelectionState(EOutlinerSelectionState::HasSelectedKeys, false); } } SelectedKeys.Empty(); if ( IsBroadcasting() ) { OnKeySelectionChanged.Broadcast(); } } void FSequencerSelection::EmptySelectedTrackAreaItems() { using namespace UE::Sequencer; if (SelectedTrackAreaItems.Num() > 0) { bNodesWithSelectedKeysOrSectionsDirty = true; ++SerialNumber; for (TWeakPtr WeakModel : SelectedTrackAreaItems) { if (TSharedPtr Model = WeakModel.Pin()) { for (TSharedPtr ParentOutlinerItem : Model->GetAncestorsOfType()) { ParentOutlinerItem->SetSelectionState(EOutlinerSelectionState::None); } } } SelectedTrackAreaItems.Empty(); if ( IsBroadcasting() ) { OnSectionSelectionChanged.Broadcast(); } } } void FSequencerSelection::EmptySelectedOutlinerNodes() { using namespace UE::Sequencer; if (!SelectedOutlinerItems.Num()) { return; } ++SerialNumber; for (TWeakPtr WeakModel : SelectedOutlinerItems) { if (TSharedPtr Model = WeakModel.Pin()) { if (IOutlinerExtension* OutlinerItem = Model->CastThis()) { OutlinerItem->SetSelectionState(EOutlinerSelectionState::None); } } } SelectedOutlinerItems.Empty(); if ( IsBroadcasting() ) { OnOutlinerNodeSelectionChanged.Broadcast(); } } /** Suspend or resume broadcast of selection changing */ void FSequencerSelection::SuspendBroadcast() { SuspendBroadcastCount++; } void FSequencerSelection::ResumeBroadcast() { SuspendBroadcastCount--; checkf(SuspendBroadcastCount >= 0, TEXT("Suspend/Resume broadcast mismatch!")); } bool FSequencerSelection::IsBroadcasting() { return SuspendBroadcastCount == 0; } void FSequencerSelection::EmptySelectedOutlinerNodesWithoutSections(const TArray& Sections) { using namespace UE::Sequencer; TSet> LocalSelectedOutlinerItems = SelectedOutlinerItems; SuspendBroadcast(); bool bRemoved = false; for (TWeakPtr WeakSelectedModel : LocalSelectedOutlinerItems) { TSharedPtr SelectedModel = WeakSelectedModel.Pin(); if (!SelectedModel) { continue; } bool bFoundMatch = false; for (TSharedPtr SectionModel : SelectedModel->GetDescendantsOfType()) { if (Sections.Contains(SectionModel->GetSection())) { bFoundMatch = true; break; } } if (!bFoundMatch) { RemoveFromSelection(WeakSelectedModel); bRemoved = true; } } ResumeBroadcast(); if (bRemoved) { ++SerialNumber; OnOutlinerNodeSelectionChanged.Broadcast(); } } void FSequencerSelection::RequestOutlinerNodeSelectionChangedBroadcast() { bOutlinerNodeSelectionChangedBroadcastPending = true; } void FSequencerSelection::Tick() { if ( bOutlinerNodeSelectionChangedBroadcastPending && IsBroadcasting() ) { bOutlinerNodeSelectionChangedBroadcastPending = false; OnOutlinerNodeSelectionChanged.Broadcast(); } if ( bEmptySelectedOutlinerNodesWithSectionsPending && IsBroadcasting() ) { bEmptySelectedOutlinerNodesWithSectionsPending = false; TArray Sections; for (TWeakObjectPtr SelectedSection : GetSelectedSections()) { if (SelectedSection.IsValid()) { Sections.Add(SelectedSection.Get()); } } for (FSequencerSelectedKey SelectedKey : SelectedKeys) { if (SelectedKey.IsValid() && !Sections.Contains(SelectedKey.Section)) { Sections.Add(SelectedKey.Section); } } EmptySelectedOutlinerNodesWithoutSections(Sections); } } void FSequencerSelection::OnHierarchyChanged() { // This is an esoteric hack that ensures we re-synchronize external (ie Actor) // selection when models are removed from the tree. Doing so ensures that // FSequencer::SynchronizeExternalSelectionWithSequencerSelection is called within // the scope of GIsTransacting being true, which prevents that function from creating new // transactions for the selection synchronization. This is important because otherwise // the undo/redo stack gets wiped by actor selections when undoing if the selection is // not identical RevalidateSelection(); } void FSequencerSelection::RevalidateSelection() { using namespace UE::Sequencer; bNodesWithSelectedKeysOrSectionsDirty = true; const int32 NumSelectedKeys = SelectedKeys.Num(); const int32 NumSelectedTrackAreaItems = SelectedTrackAreaItems.Num(); const int32 NumSelectedOutlinerItems = SelectedOutlinerItems.Num(); for (auto It = SelectedKeys.CreateIterator(); It; ++It) { if (!It->Section || !It->WeakChannel.Pin().Get()) { It.RemoveCurrent(); } } for (auto It = SelectedTrackAreaItems.CreateIterator(); It; ++It) { if (It->Pin().Get() == nullptr) { It.RemoveCurrent(); } } for (auto It = SelectedOutlinerItems.CreateIterator(); It; ++It) { if (It->Pin().Get() == nullptr) { It.RemoveCurrent(); } } if (NumSelectedKeys != SelectedKeys.Num()) { ++SerialNumber; OnKeySelectionChanged.Broadcast(); } if (NumSelectedTrackAreaItems != SelectedTrackAreaItems.Num()) { ++SerialNumber; OnSectionSelectionChanged.Broadcast(); OnOutlinerNodeSelectionChangedObjectGuids.Broadcast(); } if (NumSelectedOutlinerItems != SelectedOutlinerItems.Num()) { ++SerialNumber; OnOutlinerNodeSelectionChanged.Broadcast(); } }