/********************************************************************** Audacity: A Digital Audio Editor Track.cpp Dominic Mazzoni *******************************************************************//** \class Track \brief Fundamental data object of Audacity, displayed in the TrackPanel. Classes derived form it include the WaveTrack, NoteTrack, LabelTrack and TimeTrack. \class AudioTrack \brief A Track that can load/save audio data to/from XML. \class PlayableTrack \brief An AudioTrack that can be played and stopped. *//*******************************************************************/ #include "Track.h" #include #include #include #include #include #include #include "tracks/ui/CommonTrackPanelCell.h" #include "Project.h" #include "ProjectSettings.h" #include "InconsistencyException.h" #ifdef _MSC_VER //Disable truncation warnings #pragma warning( disable : 4786 ) #endif Track::Track() : vrulerSize(36,0) { mSelected = false; mLinked = false; mIndex = 0; mOffset = 0.0; mChannel = MonoChannel; } Track::Track(const Track &orig) : vrulerSize( orig.vrulerSize ) { mIndex = 0; Init(orig); mOffset = orig.mOffset; } // Copy all the track properties except the actual contents void Track::Init(const Track &orig) { mId = orig.mId; mDefaultName = orig.mDefaultName; mName = orig.mName; mSelected = orig.mSelected; mLinked = orig.mLinked; mChannel = orig.mChannel; } void Track::SetName( const wxString &n ) { if ( mName != n ) { mName = n; Notify(); } } void Track::SetSelected(bool s) { if (mSelected != s) { mSelected = s; auto pList = mList.lock(); if (pList) pList->SelectionEvent( SharedPointer() ); } } void Track::EnsureVisible( bool modifyState ) { auto pList = mList.lock(); if (pList) pList->EnsureVisibleEvent( SharedPointer(), modifyState ); } void Track::Merge(const Track &orig) { mSelected = orig.mSelected; } Track::Holder Track::Duplicate() const { // invoke "virtual constructor" to copy track object proper: auto result = Clone(); if (mpView) // Copy view state that might be important to undo/redo mpView->CopyTo( *result ); return result; } Track::~Track() { } TrackNodePointer Track::GetNode() const { wxASSERT(mList.lock() == NULL || this == mNode.first->get()); return mNode; } void Track::SetOwner (const std::weak_ptr &list, TrackNodePointer node) { // BUG: When using this function to clear an owner, we may need to clear // focused track too. Otherwise focus could remain on an invisible (or deleted) track. mList = list; mNode = node; } const std::shared_ptr &Track::GetTrackView() { return mpView; } void Track::SetTrackView( const std::shared_ptr &pView ) { mpView = pView; } const std::shared_ptr &Track::GetTrackControls() { return mpControls; } void Track::SetTrackControls( const std::shared_ptr &pControls ) { mpControls = pControls; } int Track::GetIndex() const { return mIndex; } void Track::SetIndex(int index) { mIndex = index; } void Track::SetLinked(bool l) { auto pList = mList.lock(); if (pList && !pList->mPendingUpdates.empty()) { auto orig = pList->FindById( GetId() ); if (orig && orig != this) { orig->SetLinked(l); return; } } DoSetLinked(l); if (pList) { pList->RecalcPositions(mNode); pList->ResizingEvent(mNode); } } void Track::DoSetLinked(bool l) { mLinked = l; } Track *Track::GetLink() const { auto pList = mList.lock(); if (!pList) return nullptr; if (!pList->isNull(mNode)) { if (mLinked) { auto next = pList->getNext( mNode ); if ( !pList->isNull( next ) ) return next.first->get(); } if (mNode.first != mNode.second->begin()) { auto prev = pList->getPrev( mNode ); if ( !pList->isNull( prev ) ) { auto track = prev.first->get(); if (track && track->GetLinked()) return track; } } } return nullptr; } namespace { inline bool IsSyncLockableNonLabelTrack( const Track *pTrack ) { return nullptr != track_cast< const AudioTrack * >( pTrack ); } bool IsGoodNextSyncLockTrack(const Track *t, bool inLabelSection) { if (!t) return false; const bool isLabel = ( nullptr != track_cast(t) ); if (inLabelSection) return isLabel; else if (isLabel) return true; else return IsSyncLockableNonLabelTrack( t ); } } bool Track::IsSyncLockSelected() const { #ifdef EXPERIMENTAL_SYNC_LOCK auto pList = mList.lock(); if (!pList) return false; auto p = pList->GetOwner(); if (!p || !ProjectSettings::Get( *p ).IsSyncLocked()) return false; auto shTrack = this->SubstituteOriginalTrack(); if (!shTrack) return false; const auto pTrack = shTrack.get(); auto trackRange = TrackList::SyncLockGroup( pTrack ); if (trackRange.size() <= 1) { // Not in a sync-locked group. // Return true iff selected and of a sync-lockable type. return (IsSyncLockableNonLabelTrack( pTrack ) || track_cast( pTrack )) && GetSelected(); } // Return true iff any track in the group is selected. return *(trackRange + &Track::IsSelected).begin(); #endif return false; } void Track::Notify( int code ) { auto pList = mList.lock(); if (pList) pList->DataEvent( SharedPointer(), code ); } void Track::SyncLockAdjust(double oldT1, double newT1) { if (newT1 > oldT1) { // Insert space within the track if (oldT1 > GetEndTime()) return; auto tmp = Cut(oldT1, GetEndTime()); Paste(newT1, tmp.get()); } else if (newT1 < oldT1) { // Remove from the track Clear(newT1, oldT1); } } void PlayableTrack::Init( const PlayableTrack &orig ) { mMute = orig.mMute; mSolo = orig.mSolo; AudioTrack::Init( orig ); } void PlayableTrack::Merge( const Track &orig ) { auto pOrig = dynamic_cast(&orig); wxASSERT( pOrig ); mMute = pOrig->mMute; mSolo = pOrig->mSolo; AudioTrack::Merge( *pOrig ); } void PlayableTrack::SetMute( bool m ) { if ( mMute != m ) { mMute = m; Notify(); } } void PlayableTrack::SetSolo( bool s ) { if ( mSolo != s ) { mSolo = s; Notify(); } } // Serialize, not with tags of its own, but as attributes within a tag. void PlayableTrack::WriteXMLAttributes(XMLWriter &xmlFile) const { xmlFile.WriteAttr(wxT("mute"), mMute); xmlFile.WriteAttr(wxT("solo"), mSolo); AudioTrack::WriteXMLAttributes(xmlFile); } // Return true iff the attribute is recognized. bool PlayableTrack::HandleXMLAttribute(const wxChar *attr, const wxChar *value) { const wxString strValue{ value }; long nValue; if (!wxStrcmp(attr, wxT("mute")) && XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) { mMute = (nValue != 0); return true; } else if (!wxStrcmp(attr, wxT("solo")) && XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) { mSolo = (nValue != 0); return true; } return AudioTrack::HandleXMLAttribute(attr, value); } bool Track::Any() const { return true; } bool Track::IsSelected() const { return GetSelected(); } bool Track::IsSelectedOrSyncLockSelected() const { return GetSelected() || IsSyncLockSelected(); } bool Track::IsLeader() const { return !GetLink() || GetLinked(); } bool Track::IsSelectedLeader() const { return IsSelected() && IsLeader(); } void Track::FinishCopy (const Track *n, Track *dest) { if (dest) { dest->SetChannel(n->GetChannel()); dest->SetLinked(n->GetLinked()); dest->SetName(n->GetName()); } } bool Track::LinkConsistencyCheck() { // Sanity checks for linked tracks; unsetting the linked property // doesn't fix the problem, but it likely leaves us with orphaned // sample blocks instead of much worse problems. bool err = false; if (GetLinked()) { Track *l = GetLink(); if (l) { // A linked track's partner should never itself be linked if (l->GetLinked()) { wxLogWarning( wxT("Left track %s had linked right track %s with extra right track link.\n Removing extra link from right track."), GetName(), l->GetName()); err = true; l->SetLinked(false); } // Channels should be left and right if ( !( (GetChannel() == Track::LeftChannel && l->GetChannel() == Track::RightChannel) || (GetChannel() == Track::RightChannel && l->GetChannel() == Track::LeftChannel) ) ) { wxLogWarning( wxT("Track %s and %s had left/right track links out of order. Setting tracks to not be linked."), GetName(), l->GetName()); err = true; SetLinked(false); } } else { wxLogWarning( wxT("Track %s had link to NULL track. Setting it to not be linked."), GetName()); err = true; SetLinked(false); } } return ! err; } std::pair TrackList::FindSyncLockGroup(Track *pMember) const { if (!pMember) return { nullptr, nullptr }; // A non-trivial sync-locked group is a maximal sub-sequence of the tracks // consisting of any positive number of audio tracks followed by zero or // more label tracks. // Step back through any label tracks. auto member = pMember; while (member && ( nullptr != track_cast(member) )) { member = GetPrev(member); } // Step back through the wave and note tracks before the label tracks. Track *first = nullptr; while (member && IsSyncLockableNonLabelTrack(member)) { first = member; member = GetPrev(member); } if (!first) // Can't meet the criteria described above. In that case, // consider the track to be the sole member of a group. return { pMember, pMember }; Track *last = first; bool inLabels = false; while (const auto next = GetNext(last)) { if ( ! IsGoodNextSyncLockTrack(next, inLabels) ) break; last = next; inLabels = (nullptr != track_cast(last) ); } return { first, last }; } // TrackList // // The TrackList sends events whenever certain updates occur to the list it // is managing. Any other classes that may be interested in get these updates // should use TrackList::Connect() or TrackList::Bind(). // wxDEFINE_EVENT(EVT_TRACKLIST_TRACK_DATA_CHANGE, TrackListEvent); wxDEFINE_EVENT(EVT_TRACKLIST_SELECTION_CHANGE, TrackListEvent); wxDEFINE_EVENT(EVT_TRACKLIST_TRACK_REQUEST_VISIBLE, TrackListEvent); wxDEFINE_EVENT(EVT_TRACKLIST_PERMUTED, TrackListEvent); wxDEFINE_EVENT(EVT_TRACKLIST_RESIZING, TrackListEvent); wxDEFINE_EVENT(EVT_TRACKLIST_ADDITION, TrackListEvent); wxDEFINE_EVENT(EVT_TRACKLIST_DELETION, TrackListEvent); // same value as in the default constructed TrackId: long TrackList::sCounter = -1; static const AudacityProject::AttachedObjects::RegisteredFactory key{ [](AudacityProject &project) { return TrackList::Create( &project ); } }; TrackList &TrackList::Get( AudacityProject &project ) { return project.AttachedObjects::Get< TrackList >( key ); } const TrackList &TrackList::Get( const AudacityProject &project ) { return Get( const_cast< AudacityProject & >( project ) ); } TrackList::TrackList( AudacityProject *pOwner ) : wxEvtHandler() , mOwner{ pOwner } { } // Factory function std::shared_ptr TrackList::Create( AudacityProject *pOwner ) { return std::make_shared( pOwner ); } #if 0 TrackList &TrackList::operator= (TrackList &&that) { if (this != &that) { this->Clear(); Swap(that); } return *this; } #endif void TrackList::Swap(TrackList &that) { auto SwapLOTs = []( ListOfTracks &a, const std::weak_ptr< TrackList > &aSelf, ListOfTracks &b, const std::weak_ptr< TrackList > &bSelf ) { a.swap(b); for (auto it = a.begin(), last = a.end(); it != last; ++it) (*it)->SetOwner(aSelf, {it, &a}); for (auto it = b.begin(), last = b.end(); it != last; ++it) (*it)->SetOwner(bSelf, {it, &b}); }; const auto self = shared_from_this(); const auto otherSelf = that.shared_from_this(); SwapLOTs( *this, self, that, otherSelf ); SwapLOTs( this->mPendingUpdates, self, that.mPendingUpdates, otherSelf ); mUpdaters.swap(that.mUpdaters); } TrackList::~TrackList() { Clear(false); } void TrackList::RecalcPositions(TrackNodePointer node) { if ( isNull( node ) ) return; Track *t; int i = 0; auto prev = getPrev( node ); if ( !isNull( prev ) ) { t = prev.first->get(); i = t->GetIndex() + 1; } const auto theEnd = end(); for (auto n = Find( node.first->get() ); n != theEnd; ++n) { t = *n; t->SetIndex(i++); } UpdatePendingTracks(); } void TrackList::SelectionEvent( const std::shared_ptr &pTrack ) { // wxWidgets will own the event object QueueEvent( safenew TrackListEvent{ EVT_TRACKLIST_SELECTION_CHANGE, pTrack } ); } void TrackList::DataEvent( const std::shared_ptr &pTrack, int code ) { // wxWidgets will own the event object QueueEvent( safenew TrackListEvent{ EVT_TRACKLIST_TRACK_DATA_CHANGE, pTrack, code } ); } void TrackList::EnsureVisibleEvent( const std::shared_ptr &pTrack, bool modifyState ) { auto pEvent = std::make_unique( EVT_TRACKLIST_TRACK_REQUEST_VISIBLE, pTrack, 0 ); pEvent->SetInt( modifyState ? 1 : 0 ); // wxWidgets will own the event object QueueEvent( pEvent.release() ); } void TrackList::PermutationEvent(TrackNodePointer node) { // wxWidgets will own the event object QueueEvent( safenew TrackListEvent{ EVT_TRACKLIST_PERMUTED, *node.first } ); } void TrackList::DeletionEvent(TrackNodePointer node) { // wxWidgets will own the event object QueueEvent( safenew TrackListEvent{ EVT_TRACKLIST_DELETION, node.second && node.first != node.second->end() ? *node.first : nullptr } ); } void TrackList::AdditionEvent(TrackNodePointer node) { // wxWidgets will own the event object QueueEvent( safenew TrackListEvent{ EVT_TRACKLIST_ADDITION, *node.first } ); } void TrackList::ResizingEvent(TrackNodePointer node) { // wxWidgets will own the event object QueueEvent( safenew TrackListEvent{ EVT_TRACKLIST_RESIZING, *node.first } ); } auto TrackList::EmptyRange() const -> TrackIterRange< Track > { auto it = const_cast(this)->getEnd(); return { { it, it, it, &Track::Any }, { it, it, it, &Track::Any } }; } auto TrackList::SyncLockGroup( Track *pTrack ) -> TrackIterRange< Track > { auto pList = pTrack->GetOwner(); auto tracks = pList->FindSyncLockGroup( const_cast( pTrack ) ); return pList->Any().StartingWith(tracks.first).EndingAfter(tracks.second); } auto TrackList::FindLeader( Track *pTrack ) -> TrackIter< Track > { auto iter = Find(pTrack); while( *iter && ! ( *iter )->IsLeader() ) --iter; return iter.Filter( &Track::IsLeader ); } void TrackList::Permute(const std::vector &permutation) { for (const auto iter : permutation) { ListOfTracks::value_type track = *iter.first; erase(iter.first); Track *pTrack = track.get(); pTrack->SetOwner(shared_from_this(), { insert(ListOfTracks::end(), track), this }); } auto n = getBegin(); RecalcPositions(n); PermutationEvent(n); } Track *TrackList::FindById( TrackId id ) { // Linear search. Tracks in a project are usually very few. // Search only the non-pending tracks. auto it = std::find_if( ListOfTracks::begin(), ListOfTracks::end(), [=](const ListOfTracks::value_type &ptr){ return ptr->GetId() == id; } ); if (it == ListOfTracks::end()) return {}; return it->get(); } Track *TrackList::DoAddToHead(const std::shared_ptr &t) { Track *pTrack = t.get(); push_front(ListOfTracks::value_type(t)); auto n = getBegin(); pTrack->SetOwner(shared_from_this(), n); pTrack->SetId( TrackId{ ++sCounter } ); RecalcPositions(n); AdditionEvent(n); return front().get(); } Track *TrackList::DoAdd(const std::shared_ptr &t) { push_back(t); auto n = getPrev( getEnd() ); t->SetOwner(shared_from_this(), n); t->SetId( TrackId{ ++sCounter } ); RecalcPositions(n); AdditionEvent(n); return back().get(); } void TrackList::GroupChannels( Track &track, size_t groupSize, bool resetChannels ) { // If group size is exactly two, group as stereo, else mono (bug 2195). auto list = track.mList.lock(); if ( groupSize > 0 && list.get() == this ) { auto iter = track.mNode.first; auto after = iter; auto end = this->ListOfTracks::end(); auto count = groupSize; for ( ; after != end && count; ++after, --count ) ; if ( count == 0 ) { auto unlink = [&] ( Track &tr ) { if ( tr.GetLinked() ) { if ( resetChannels ) { auto link = tr.GetLink(); if ( link ) link->SetChannel( Track::MonoChannel ); } tr.SetLinked( false ); } if ( resetChannels ) tr.SetChannel( Track::MonoChannel ); }; // Disassociate previous tracks -- at most one auto pLeader = this->FindLeader( &track ); if ( *pLeader && *pLeader != &track ) unlink( **pLeader ); // First disassociate given and later tracks, then reassociate them for ( auto iter2 = iter; iter2 != after; ++iter2 ) unlink( **iter2 ); if ( groupSize > 1 ) { const auto channel = *iter++; channel->SetLinked( groupSize == 2 ); channel->SetChannel( groupSize == 2? Track::LeftChannel : Track::MonoChannel ); (*iter++)->SetChannel( groupSize == 2? Track::RightChannel : Track::MonoChannel ); while (iter != after) (*iter++)->SetChannel( Track::MonoChannel ); } return; } } // *this does not contain the track or sufficient following channels // or group size is zero THROW_INCONSISTENCY_EXCEPTION; } auto TrackList::Replace(Track * t, const ListOfTracks::value_type &with) -> ListOfTracks::value_type { ListOfTracks::value_type holder; if (t && with) { auto node = t->GetNode(); t->SetOwner({}, {}); holder = *node.first; Track *pTrack = with.get(); *node.first = with; pTrack->SetOwner(shared_from_this(), node); pTrack->SetId( t->GetId() ); RecalcPositions(node); DeletionEvent(node); AdditionEvent(node); } return holder; } TrackNodePointer TrackList::Remove(Track *t) { auto result = getEnd(); if (t) { auto node = t->GetNode(); t->SetOwner({}, {}); if ( !isNull( node ) ) { ListOfTracks::value_type holder = *node.first; result = getNext( node ); erase(node.first); if ( !isNull( result ) ) RecalcPositions(result); DeletionEvent(result); } } return result; } void TrackList::Clear(bool sendEvent) { // Null out the back-pointers to this in tracks, in case there // are outstanding shared_ptrs to those tracks, making them outlive // the temporary ListOfTracks below. for ( auto pTrack: *this ) pTrack->SetOwner( {}, {} ); for ( auto pTrack: mPendingUpdates ) pTrack->SetOwner( {}, {} ); ListOfTracks tempList; tempList.swap( *this ); ListOfTracks updating; updating.swap( mPendingUpdates ); mUpdaters.clear(); if (sendEvent) DeletionEvent(); } /// Return a track in the list that comes after Track t Track *TrackList::GetNext(Track * t, bool linked) const { if (t) { auto node = t->GetNode(); if ( !isNull( node ) ) { if ( linked && t->GetLinked() ) node = getNext( node ); if ( !isNull( node ) ) node = getNext( node ); if ( !isNull( node ) ) return node.first->get(); } } return nullptr; } Track *TrackList::GetPrev(Track * t, bool linked) const { if (t) { TrackNodePointer prev; auto node = t->GetNode(); if ( !isNull( node ) ) { // linked is true and input track second in team? if (linked) { prev = getPrev( node ); if( !isNull( prev ) && !t->GetLinked() && t->GetLink() ) // Make it the first node = prev; } prev = getPrev( node ); if ( !isNull( prev ) ) { // Back up once node = prev; // Back up twice sometimes when linked is true if (linked) { prev = getPrev( node ); if( !isNull( prev ) && !(*node.first)->GetLinked() && (*node.first)->GetLink() ) node = prev; } return node.first->get(); } } } return nullptr; } bool TrackList::CanMoveUp(Track * t) const { return GetPrev(t, true) != NULL; } bool TrackList::CanMoveDown(Track * t) const { return GetNext(t, true) != NULL; } // This is used when you want to swap the channel group starting // at s1 with that starting at s2. // The complication is that the tracks are stored in a single // linked list. void TrackList::SwapNodes(TrackNodePointer s1, TrackNodePointer s2) { // if a null pointer is passed in, we want to know about it wxASSERT(!isNull(s1)); wxASSERT(!isNull(s2)); // Deal with first track in each team s1 = ( * FindLeader( s1.first->get() ) )->GetNode(); s2 = ( * FindLeader( s2.first->get() ) )->GetNode(); // Safety check... if (s1 == s2) return; // Be sure s1 is the earlier iterator if ((*s1.first)->GetIndex() >= (*s2.first)->GetIndex()) std::swap(s1, s2); // For saving the removed tracks using Saved = std::vector< ListOfTracks::value_type >; Saved saved1, saved2; auto doSave = [&] ( Saved &saved, TrackNodePointer &s ) { size_t nn = Channels( s.first->get() ).size(); saved.resize( nn ); // Save them in backwards order while( nn-- ) saved[nn] = *s.first, s.first = erase(s.first); }; doSave( saved1, s1 ); // The two ranges are assumed to be disjoint but might abut const bool same = (s1 == s2); doSave( saved2, s2 ); if (same) // Careful, we invalidated s1 in the second doSave! s1 = s2; // Reinsert them auto doInsert = [&] ( Saved &saved, TrackNodePointer &s ) { Track *pTrack; for (auto & pointer : saved) pTrack = pointer.get(), // Insert before s, and reassign s to point at the new node before // old s; which is why we saved pointers in backwards order pTrack->SetOwner(shared_from_this(), s = { insert(s.first, pointer), this } ); }; // This does not invalidate s2 even when it equals s1: doInsert( saved2, s1 ); // Even if s2 was same as s1, this correctly inserts the saved1 range // after the saved2 range, when done after: doInsert( saved1, s2 ); // Now correct the Index in the tracks, and other things RecalcPositions(s1); PermutationEvent(s1); } bool TrackList::MoveUp(Track * t) { if (t) { Track *p = GetPrev(t, true); if (p) { SwapNodes(p->GetNode(), t->GetNode()); return true; } } return false; } bool TrackList::MoveDown(Track * t) { if (t) { Track *n = GetNext(t, true); if (n) { SwapNodes(t->GetNode(), n->GetNode()); return true; } } return false; } bool TrackList::Contains(const Track * t) const { return make_iterator_range( *this ).contains( t ); } bool TrackList::empty() const { return begin() == end(); } size_t TrackList::size() const { int cnt = 0; if (!empty()) cnt = getPrev( getEnd() ).first->get()->GetIndex() + 1; return cnt; } namespace { // Abstract the common pattern of the following three member functions inline double Accumulate (const TrackList &list, double (Track::*memfn)() const, double ident, const double &(*combine)(const double&, const double&)) { // Default the answer to zero for empty list if (list.empty()) { return 0.0; } // Otherwise accumulate minimum or maximum of track values return list.Any().accumulate(ident, combine, memfn); } } double TrackList::GetMinOffset() const { return Accumulate(*this, &Track::GetOffset, DBL_MAX, std::min); } double TrackList::GetStartTime() const { return Accumulate(*this, &Track::GetStartTime, DBL_MAX, std::min); } double TrackList::GetEndTime() const { return Accumulate(*this, &Track::GetEndTime, -DBL_MAX, std::max); } std::shared_ptr TrackList::RegisterPendingChangedTrack( Updater updater, Track *src ) { std::shared_ptr pTrack; if (src) { pTrack = src->Clone(); // not duplicate // Share the satellites with the original, though they do not point back // to the pending track pTrack->mpView = src->mpView; pTrack->mpControls = src->mpControls; } if (pTrack) { mUpdaters.push_back( updater ); mPendingUpdates.push_back( pTrack ); auto n = mPendingUpdates.end(); --n; pTrack->SetOwner(shared_from_this(), {n, &mPendingUpdates}); } return pTrack; } void TrackList::RegisterPendingNewTrack( const std::shared_ptr &pTrack ) { Add( pTrack ); pTrack->SetId( TrackId{} ); } void TrackList::UpdatePendingTracks() { auto pUpdater = mUpdaters.begin(); for (const auto &pendingTrack : mPendingUpdates) { // Copy just a part of the track state, according to the update // function const auto &updater = *pUpdater; auto src = FindById( pendingTrack->GetId() ); if (pendingTrack && src) { if (updater) updater( *pendingTrack, *src ); pendingTrack->DoSetLinked(src->GetLinked()); } ++pUpdater; } } /*! @excsafety{No-fail} */ void TrackList::ClearPendingTracks( ListOfTracks *pAdded ) { for (const auto &pTrack: mPendingUpdates) pTrack->SetOwner( {}, {} ); mPendingUpdates.clear(); mUpdaters.clear(); if (pAdded) pAdded->clear(); // To find the first node that remains after the first deleted one TrackNodePointer node; bool foundNode = false; for (auto it = ListOfTracks::begin(), stop = ListOfTracks::end(); it != stop;) { if (it->get()->GetId() == TrackId{}) { do { if (pAdded) pAdded->push_back( *it ); (*it)->SetOwner( {}, {} ); it = erase( it ); } while (it != stop && it->get()->GetId() == TrackId{}); if (!foundNode && it != stop) { node = (*it)->GetNode(); foundNode = true; } } else ++it; } if (!empty()) { RecalcPositions(getBegin()); DeletionEvent( node ); } } /*! @excsafety{Strong} */ bool TrackList::ApplyPendingTracks() { bool result = false; ListOfTracks additions; ListOfTracks updates; { // Always clear, even if one of the update functions throws auto cleanup = finally( [&] { ClearPendingTracks( &additions ); } ); UpdatePendingTracks(); updates.swap( mPendingUpdates ); } // Remaining steps must be No-fail-guarantee so that this function // gives Strong-guarantee std::vector< std::shared_ptr > reinstated; for (auto &pendingTrack : updates) { if (pendingTrack) { if (pendingTrack->mpView) pendingTrack->mpView->Reparent( pendingTrack ); if (pendingTrack->mpControls) pendingTrack->mpControls->Reparent( pendingTrack ); auto src = FindById( pendingTrack->GetId() ); if (src) this->Replace(src, pendingTrack), result = true; else // Perhaps a track marked for pending changes got deleted by // some other action. Recreate it so we don't lose the // accumulated changes. reinstated.push_back(pendingTrack); } } // If there are tracks to reinstate, append them to the list. for (auto &pendingTrack : reinstated) if (pendingTrack) this->Add( pendingTrack ), result = true; // Put the pending added tracks back into the list, preserving their // positions. bool inserted = false; ListOfTracks::iterator first; for (auto &pendingTrack : additions) { if (pendingTrack) { auto iter = ListOfTracks::begin(); std::advance( iter, pendingTrack->GetIndex() ); iter = ListOfTracks::insert( iter, pendingTrack ); pendingTrack->SetOwner( shared_from_this(), {iter, this} ); pendingTrack->SetId( TrackId{ ++sCounter } ); if (!inserted) { first = iter; inserted = true; } } } if (inserted) { TrackNodePointer node{first, this}; RecalcPositions(node); AdditionEvent(node); result = true; } return result; } std::shared_ptr Track::SubstitutePendingChangedTrack() { // Linear search. Tracks in a project are usually very few. auto pList = mList.lock(); if (pList) { const auto id = GetId(); const auto end = pList->mPendingUpdates.end(); auto it = std::find_if( pList->mPendingUpdates.begin(), end, [=](const ListOfTracks::value_type &ptr){ return ptr->GetId() == id; } ); if (it != end) return *it; } return SharedPointer(); } std::shared_ptr Track::SubstitutePendingChangedTrack() const { return const_cast(this)->SubstitutePendingChangedTrack(); } std::shared_ptr Track::SubstituteOriginalTrack() const { auto pList = mList.lock(); if (pList) { const auto id = GetId(); const auto pred = [=]( const ListOfTracks::value_type &ptr ) { return ptr->GetId() == id; }; const auto end = pList->mPendingUpdates.end(); const auto it = std::find_if( pList->mPendingUpdates.begin(), end, pred ); if (it != end) { const auto &list2 = (const ListOfTracks &) *pList; const auto end2 = list2.end(); const auto it2 = std::find_if( list2.begin(), end2, pred ); if ( it2 != end2 ) return *it2; } } return SharedPointer(); } bool Track::SupportsBasicEditing() const { return true; } auto Track::GetIntervals() const -> ConstIntervals { return {}; } auto Track::GetIntervals() -> Intervals { return {}; } // Serialize, not with tags of its own, but as attributes within a tag. void Track::WriteCommonXMLAttributes( XMLWriter &xmlFile, bool includeNameAndSelected) const { if (includeNameAndSelected) { xmlFile.WriteAttr(wxT("name"), GetName()); xmlFile.WriteAttr(wxT("isSelected"), this->GetSelected()); } if ( mpView ) mpView->WriteXMLAttributes( xmlFile ); if ( mpControls ) mpControls->WriteXMLAttributes( xmlFile ); } // Return true iff the attribute is recognized. bool Track::HandleCommonXMLAttribute(const wxChar *attr, const wxChar *value) { long nValue = -1; wxString strValue( value ); if ( mpView && mpView->HandleXMLAttribute( attr, value ) ) ; else if ( mpControls && mpControls->HandleXMLAttribute( attr, value ) ) ; else if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(strValue)) { SetName( strValue ); return true; } else if (!wxStrcmp(attr, wxT("isSelected")) && XMLValueChecker::IsGoodInt(strValue) && strValue.ToLong(&nValue)) { this->SetSelected(nValue != 0); return true; } return false; } void Track::AdjustPositions() { auto pList = mList.lock(); if (pList) { pList->RecalcPositions(mNode); pList->ResizingEvent(mNode); } } TrackIntervalData::~TrackIntervalData() = default; bool TrackList::HasPendingTracks() const { if ( !mPendingUpdates.empty() ) return true; if (end() != std::find_if(begin(), end(), [](const Track *t){ return t->GetId() == TrackId{}; })) return true; return false; }