// Copyright Epic Games, Inc. All Rights Reserved. #include "MVVM/Selection/Selection.h" #include "MVVM/SharedViewModelData.h" #include "MVVM/ViewModels/EditorViewModel.h" #include "MVVM/ViewModels/SectionModel.h" #include "MVVM/ViewModels/ChannelModel.h" #include "MVVM/Extensions/IObjectBindingExtension.h" #include "MVVM/Extensions/ITrackExtension.h" #include "MVVM/Extensions/ISelectableExtension.h" #include "MVVM/ViewModels/TrackRowModel.h" #include "Channels/MovieSceneChannel.h" #include "MovieSceneSection.h" #include "MovieSceneTrack.h" #include "SequencerCommonHelpers.h" namespace UE::Sequencer { void FKeySelection::Deselect(FKeyHandle InKey) { SequencerHelpers::RemoveDuplicateKeys(*this, MakeArrayView(&InKey, 1)); TUniqueFragmentSelectionSet::Deselect(InKey); } void FKeySelection::Empty() { SequencerHelpers::RemoveDuplicateKeys(*this, MakeArrayView(GetSelected().Array())); TUniqueFragmentSelectionSet::Empty(); } bool FTrackAreaSelection::OnSelectItem(const FWeakViewModelPtr& WeakViewModel) { if (TSharedPtr ViewModel = WeakViewModel.Pin()) { ISelectableExtension* Selectable = ViewModel->CastThis(); if (Selectable && Selectable->IsSelectable() == ESelectionIntent::Never) { return false; } return true; } return false; } FSequencerSelection::FSequencerSelection() { AddSelectionSet(&Outliner); AddSelectionSet(&TrackArea); AddSelectionSet(&KeySelection); AddSelectionSet(&MarkedFrames); } void FSequencerSelection::Initialize(TViewModelPtr InViewModel) { FViewModelPtr RootModel = InViewModel->GetRootModel(); if (RootModel) { FSimpleMulticastDelegate& HierarchyChanged = RootModel->GetSharedData()->SubscribeToHierarchyChanged(RootModel); HierarchyChanged.AddSP(this, &FSequencerSelection::OnHierarchyChanged); } } void FSequencerSelection::Empty() { FSelectionEventSuppressor EventSuppressor = SuppressEvents(); Outliner.Empty(); TrackArea.Empty(); KeySelection.Empty(); MarkedFrames.Empty(); } void FSequencerSelection::PreSelectionSetChangeEvent(FSelectionBase* InSelectionSet) { if (InSelectionSet == &Outliner) { // Empty the track area selection when selecting anything on the outliner if (!TrackArea.HasPendingChanges() && !KeySelection.HasPendingChanges()) { TrackArea.Empty(); KeySelection.Empty(); } } } void FSequencerSelection::PreBroadcastChangeEvent() { // Repopulate the nodes with keys or sections set // First off reset the selection states from the previous set for (TWeakViewModelPtr WeakOldNode : NodesWithKeysOrSections) { TViewModelPtr OldNode = WeakOldNode.Pin(); if (OldNode) { OldNode->ToggleSelectionState(EOutlinerSelectionState::HasSelectedKeys | EOutlinerSelectionState::HasSelectedTrackAreaItems, false); OldNode = OldNode.AsModel()->FindAncestorOfType(); while (OldNode) { OldNode->ToggleSelectionState(EOutlinerSelectionState::DescendentHasSelectedTrackAreaItems | EOutlinerSelectionState::DescendentHasSelectedKeys, false); OldNode = OldNode.AsModel()->FindAncestorOfType(); } } } // Reset the selection set NodesWithKeysOrSections.Reset(); // Gather selection states from selected track area items for (FViewModelPtr TrackAreaModel : TrackArea) { TViewModelPtr ParentOutlinerNode = TrackAreaModel->FindAncestorOfType(); if (ParentOutlinerNode) { ParentOutlinerNode->ToggleSelectionState(EOutlinerSelectionState::HasSelectedTrackAreaItems, true); NodesWithKeysOrSections.Add(ParentOutlinerNode); ParentOutlinerNode = ParentOutlinerNode.AsModel()->FindAncestorOfType(); while (ParentOutlinerNode) { ParentOutlinerNode->ToggleSelectionState(EOutlinerSelectionState::DescendentHasSelectedTrackAreaItems, true); ParentOutlinerNode = ParentOutlinerNode.AsModel()->FindAncestorOfType(); } } } // Gather selection states from selected keys { TSet> Channels; for (const FKeyHandle& Key : KeySelection) { if (TViewModelPtr Channel = KeySelection.GetModelForKey(Key)) { Channels.Add(Channel); } } TSet> ParentOutlinerNodes; ParentOutlinerNodes.Reserve(Channels.Num()); for (const TViewModelPtr& Channel : Channels) { if (TViewModelPtr ParentOutlinerNode = Channel ? Channel->GetLinkedOutlinerItem() : nullptr) { ParentOutlinerNodes.Add(ParentOutlinerNode); } } NodesWithKeysOrSections.Reserve(NodesWithKeysOrSections.Num() + ParentOutlinerNodes.Num()); for (TViewModelPtr ParentOutlinerNode: ParentOutlinerNodes) { ParentOutlinerNode->ToggleSelectionState(EOutlinerSelectionState::HasSelectedKeys, true); NodesWithKeysOrSections.Add(ParentOutlinerNode); ParentOutlinerNode = ParentOutlinerNode.AsModel()->FindAncestorOfType(); while (ParentOutlinerNode) { ParentOutlinerNode->ToggleSelectionState(EOutlinerSelectionState::DescendentHasSelectedKeys, true); ParentOutlinerNode = ParentOutlinerNode.AsModel()->FindAncestorOfType(); } } } FSelectionEventSuppressor EventSuppressor = SuppressEvents(); FOutlinerSelection OutlinerCopy = Outliner; // Select any outliner nodes that don't have keys or sections selected for (TViewModelPtr OutlinerItem : Outliner) { bool bFound = false; bool bAnyIndirectSelection = false; const TSet> ObjectBindingAncestors = OutlinerItem.AsModel()->GetAncestorsOfType(true).Populate(); for (TViewModelPtr IndirectItem : IterateIndirectOutlinerSelection()) { bAnyIndirectSelection = true; if (IndirectItem == OutlinerItem) { bFound = true; break; } for (TViewModelPtr IndirectObjectBindingItem : IndirectItem.AsModel()->GetAncestorsOfType(true)) { if (ObjectBindingAncestors.Contains(IndirectObjectBindingItem)) { bFound = true; break; } } if (bFound) { break; } } if (bAnyIndirectSelection && !bFound) { OutlinerCopy.Deselect(OutlinerItem); } } Outliner = OutlinerCopy; } FIndirectOutlinerSelectionIterator FSequencerSelection::IterateIndirectOutlinerSelection() const { return FIndirectOutlinerSelectionIterator{ &NodesWithKeysOrSections }; } TSet FSequencerSelection::GetBoundObjectsGuids() { TSet OutGuids; for (const TWeakViewModelPtr& WeakModel : NodesWithKeysOrSections) { FViewModelPtr Model = WeakModel.Pin(); if (Model) { TSharedPtr ObjectBinding = Model->FindAncestorOfType(true); if (ObjectBinding) { OutGuids.Add(ObjectBinding->GetObjectGuid()); } } } if (OutGuids.IsEmpty()) { for (FViewModelPtr Model : Outliner) { TSharedPtr ObjectBinding = Model->FindAncestorOfType(true); if (ObjectBinding) { OutGuids.Add(ObjectBinding->GetObjectGuid()); } } } return OutGuids; } TSet FSequencerSelection::GetSelectedSections() const { TSet SelectedSections; SelectedSections.Reserve(TrackArea.Num()); for (TViewModelPtr Model : TrackArea.Filter()) { if (UMovieSceneSection* Section = Model->GetSection()) { SelectedSections.Add(Section); } } return SelectedSections; } TSet FSequencerSelection::GetSelectedTracks() const { TSet SelectedTracks; SelectedTracks.Reserve(TrackArea.Num()); for (TViewModelPtr TrackExtension : Outliner.Filter()) { if (UMovieSceneTrack* Track = TrackExtension->GetTrack()) { SelectedTracks.Add(Track); } } return SelectedTracks; } TSet> FSequencerSelection::GetSelectedTrackRows() const { TSet> SelectedTrackRows; SelectedTrackRows.Reserve(TrackArea.Num()); for (TViewModelPtr TrackExtension : Outliner.Filter()) { // Only add a 'track row' as selected if either we have an actual 'track row' selected, or else we have a track selected and there's only a single // track row, and the track allows multiple rows. if (UMovieSceneTrack* Track = TrackExtension->GetTrack()) { if (TViewModelPtr TrackRowModel = TrackExtension.ImplicitCast()) { SelectedTrackRows.Add(TPair(Track, TrackExtension->GetRowIndex())); } else if (Track->SupportsMultipleRows() && Track->GetMaxRowIndex() == 0) { SelectedTrackRows.Add(TPair(Track, TrackExtension->GetRowIndex())); } } } TSet SelectedSections = GetSelectedSections(); for (UMovieSceneSection* Section : SelectedSections) { if (UMovieSceneTrack* Track = Section->GetTypedOuter()) { if (Track->SupportsMultipleRows()) { SelectedTrackRows.Add(TPair(Track, Section->GetRowIndex())); } } } return SelectedTrackRows; } 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() { FSelectionEventSuppressor EventSuppressor = SuppressEvents(); KeySelection.RemoveByPredicate( [this](FKeyHandle Key) { TViewModelPtr Channel = this->KeySelection.GetModelForKey(Key); return !Channel || Channel->GetSection() == nullptr; } ); TrackArea.RemoveByPredicate( [this](const FWeakViewModelPtr& Key) { return Key.Pin() == nullptr; } ); Outliner.RemoveByPredicate( [this](const TWeakViewModelPtr& Key) { return Key.Pin() == nullptr; } ); } } // namespace UE::Sequencer