From a3cdb08ee06f5d52d09b3d503f06d2698c373b24 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Wed, 2 Nov 2016 11:45:03 -0400 Subject: [PATCH] Classes and functions for type-safe iteration over tracks --- src/Track.cpp | 112 +++++++++++- src/Track.h | 462 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 565 insertions(+), 9 deletions(-) diff --git a/src/Track.cpp b/src/Track.cpp index 50b070dfb..7a2f30b8a 100644 --- a/src/Track.cpp +++ b/src/Track.cpp @@ -292,6 +292,26 @@ Track *Track::GetLink() const 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 @@ -383,6 +403,21 @@ bool PlayableTrack::HandleXMLAttribute(const wxChar *attr, const wxChar *value) 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(); } + // TrackListIterator TrackListIterator::TrackListIterator(TrackList * val, TrackNodePointer p) : l{ val } @@ -652,13 +687,6 @@ SyncLockedTracksIterator::SyncLockedTracksIterator(TrackList * val) { } -namespace { - inline bool IsSyncLockableNonLabelTrack( const Track *pTrack ) - { - return nullptr != dynamic_cast< const AudioTrack * >( pTrack ); - } -} - Track *SyncLockedTracksIterator::StartWith(Track * member) { Track *t = NULL; @@ -765,10 +793,50 @@ Track *SyncLockedTracksIterator::Last(bool skiplinked) break; t = Next(skiplinked); } - + return t; } +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 // @@ -877,6 +945,34 @@ void TrackList::ResizingEvent(TrackNodePointer node) QueueEvent(e.release()); } +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) { diff --git a/src/Track.h b/src/Track.h index 3dd0adf70..a70dd903a 100644 --- a/src/Track.h +++ b/src/Track.h @@ -321,7 +321,9 @@ public: Track *GetLink() const; - private: +private: + std::shared_ptr GetOwner() const { return mList.lock(); } + TrackNodePointer GetNode() const; void SetOwner (const std::weak_ptr &list, TrackNodePointer node); @@ -454,6 +456,15 @@ public: // Checks if sync-lock is on and any track in its sync-lock group is selected. bool IsSyncLockSelected() const; + // An always-true predicate useful for defining iterators + bool Any() const; + + // Frequently useful operands for + and - + bool IsSelected() const; + bool IsSelectedOrSyncLockSelected() const; + bool IsLeader() const; + bool IsSelectedLeader() const; + protected: std::shared_ptr FindTrack() override; @@ -544,6 +555,247 @@ template return nullptr; } +// new track iterators can eliminate the need to cast the result +template < + typename TrackType // Track or a subclass, maybe const-qualified +> class TrackIter + : public std::iterator< + std::bidirectional_iterator_tag, TrackType *const + > +{ +public: + // Type of predicate taking pointer to const TrackType + // TODO C++14: simplify away ::type + using FunctionType = std::function< bool( + typename std::add_pointer< + typename std::add_const< + typename std::remove_pointer< + TrackType + >::type + >::type + >::type + ) >; + + template + TrackIter( TrackNodePointer begin, TrackNodePointer iter, + TrackNodePointer end, const Predicate &pred = {} ) + : mBegin( begin ), mIter( iter ), mEnd( end ), mPred( pred ) + { + // Establish the class invariant + if (this->mIter != this->mEnd && !this->valid()) + this->operator ++ (); + } + + // Return an iterator that replaces the predicate, advancing to the first + // position at or after the old position that satisfies the new predicate, + // or to the end. + template < typename Predicate2 > + TrackIter Filter( const Predicate2 &pred2 ) const + { + return { this->mBegin, this->mIter, this->mEnd, pred2 }; + } + + // Return an iterator that refines the subclass (and not removing const), + // advancing to the first position at or after the old position that + // satisfies the type constraint, or to the end + template < typename TrackType2 > + auto Filter() const + -> typename std::enable_if< + std::is_base_of< TrackType, TrackType2 >::value && + (!std::is_const::value || + std::is_const::value), + TrackIter< TrackType2 > + >::type + { + return { this->mBegin, this->mIter, this->mEnd, this->mPred }; + } + + const FunctionType &GetPredicate() const + { return this->mPred; } + + // Unlike with STL iterators, this class gives well defined behavior when + // you increment an end iterator: you get the same. + TrackIter &operator ++ () + { + // Maintain the class invariant + if (this->mIter != this->mEnd) do + ++this->mIter.first; + while (this->mIter != this->mEnd && !this->valid() ); + return *this; + } + + TrackIter operator ++ (int) + { + TrackIter result { *this }; + this-> operator ++ (); + return result; + } + + // Unlike with STL iterators, this class gives well defined behavior when + // you decrement past the beginning of a range: you wrap and get an end + // iterator. + TrackIter &operator -- () + { + // Maintain the class invariant + do { + if (this->mIter == this->mBegin) + // Go circularly + this->mIter = this->mEnd; + else + --this->mIter.first; + } while (this->mIter != this->mEnd && !this->valid() ); + return *this; + } + + TrackIter operator -- (int) + { + TrackIter result { *this }; + this->operator -- (); + return result; + } + + // Unlike with STL iterators, this class gives well defined behavior when + // you dereference an end iterator: you get a null pointer. + TrackType *operator * () const + { + if (this->mIter == this->mEnd) + return nullptr; + else + // Other methods guarantee that the cast is correct + // (provided no operations on the TrackList invalidated + // underlying iterators or replaced the tracks there) + return static_cast< TrackType * >( &**this->mIter.first ); + } + + // This might be called operator + , + // but that might wrongly suggest constant time when the iterator is not + // random access. + TrackIter advance( long amount ) const + { + auto copy = *this; + std::advance( copy, amount ); + return copy; + } + + friend inline bool operator == (TrackIter a, TrackIter b) + { + // Assume the predicate is not stateful. Just compare the iterators. + return + a.mIter == b.mIter + // Assume this too: + // && a.mBegin == b.mBegin && a.mEnd == b.mEnd + ; + } + + friend inline bool operator != (TrackIter a, TrackIter b) + { + return !(a == b); + } + +private: + bool valid() const + { + // assume mIter != mEnd + const auto pTrack = track_cast< TrackType * >( &**this->mIter.first ); + if (!pTrack) + return false; + return !this->mPred || this->mPred( pTrack ); + } + + // The class invariant is that mIter == mEnd, or else, mIter != mEnd and + // **mIter is of the appropriate subclass and mPred(&**mIter) is true. + TrackNodePointer mBegin, mIter, mEnd; + FunctionType mPred; +}; + +template < + typename TrackType // Track or a subclass, maybe const-qualified +> struct TrackIterRange + : public IteratorRange< TrackIter< TrackType > > +{ + TrackIterRange + ( const TrackIter< TrackType > &begin, + const TrackIter< TrackType > &end ) + : IteratorRange< TrackIter< TrackType > > + ( begin, end ) + {} + + // Conjoin the filter predicate with another predicate + // Read + as "and" + template< typename Predicate2 > + TrackIterRange operator + ( const Predicate2 &pred2 ) const + { + const auto &pred1 = this->first.GetPredicate(); + using Function = typename TrackIter::FunctionType; + const auto &newPred = pred1 + ? Function{ [=] (typename Function::argument_type track) { + return pred1(track) && pred2(track); + } } + : Function{ pred2 }; + return { + this->first.Filter( newPred ), + this->second.Filter( newPred ) + }; + } + + // Specify the added conjunct as a pointer to member function + template< typename R, typename C > + TrackIterRange operator + ( R ( C ::* pmf ) () const ) const + { + return this->operator + ( std::mem_fn( pmf ) ); + } + + // Conjoin the filter predicate with the negation of another predicate + // Read - as "and not" + template< typename Predicate2 > + TrackIterRange operator - ( const Predicate2 &pred2 ) const + { + using ArgumentType = + typename TrackIterRange::iterator::FunctionType::argument_type; + auto neg = [=] (ArgumentType track) { return !pred2( track ); }; + return this->operator + ( neg ); + } + + // Specify the negated conjunct as a pointer to member function + template< typename R, typename C > + TrackIterRange operator - ( R ( C ::* pmf ) () const ) const + { + return this->operator + ( std::not1( std::mem_fn( pmf ) ) ); + } + + template< typename TrackType2 > + TrackIterRange< TrackType2 > Filter() const + { + return { + this-> first.template Filter< TrackType2 >(), + this->second.template Filter< TrackType2 >() + }; + } + + TrackIterRange StartingWith( const Track *pTrack ) const + { + return { + this->find( pTrack ), + this->second + }; + } + + TrackIterRange EndingAfter( const Track *pTrack ) const + { + return { + this->first, + this->reversal().find( pTrack ).base() + }; + } + + // Exclude one given track + TrackIterRange Excluding ( const TrackType *pExcluded ) const + { + return this->operator - ( + [=](const Track *pTrack){ return pExcluded == pTrack; } ); + } +}; + class AUDACITY_DLL_API TrackListIterator /* not final */ : public std::iterator< std::forward_iterator_tag, Track *const > { @@ -789,6 +1041,8 @@ class TrackList final : public wxEvtHandler, public ListOfTracks // Destructor virtual ~TrackList(); + // Iteration + // Hide the inherited begin() and end() using iterator = TrackListIterator; using const_iterator = TrackListConstIterator; @@ -801,6 +1055,161 @@ class TrackList final : public wxEvtHandler, public ListOfTracks const_iterator cbegin() const { return begin(); } const_iterator cend() const { return end(); } + // Turn a pointer into an iterator (constant time). + template < typename TrackType = Track > + auto Find(Track *pTrack) + -> TrackIter< TrackType > + { + if (!pTrack || pTrack->GetOwner().get() != this) + return EndIterator(); + else + return MakeTrackIterator( pTrack->GetNode() ); + } + + // Turn a pointer into an iterator (constant time). + template < typename TrackType = const Track > + auto Find(const Track *pTrack) const + -> typename std::enable_if< std::is_const::value, + TrackIter< TrackType > + >::type + { + if (!pTrack || pTrack->GetOwner().get() != this) + return EndIterator(); + else + return MakeTrackIterator( pTrack->GetNode() ); + } + + // If the track is not an audio track, or not one of a group of channels, + // return the track itself; else return the first channel of its group -- + // in either case as an iterator that will only visit other leader tracks. + // (Generalizing away from the assumption of at most stereo) + TrackIter< Track > FindLeader( Track *pTrack ); + + TrackIter< const Track > + FindLeader( const Track *pTrack ) const + { + return const_cast(this)-> + FindLeader( const_cast(pTrack) ).Filter< const Track >(); + } + + + template < typename TrackType = Track > + auto Any() + -> TrackIterRange< TrackType > + { + return Tracks< TrackType >(); + } + + template < typename TrackType = const Track > + auto Any() const + -> typename std::enable_if< std::is_const::value, + TrackIterRange< TrackType > + >::type + { + return Tracks< TrackType >(); + } + + // Abbreviating some frequently used cases + template < typename TrackType = Track > + auto Selected() + -> TrackIterRange< TrackType > + { + return Tracks< TrackType >( &Track::IsSelected ); + } + + template < typename TrackType = const Track > + auto Selected() const + -> typename std::enable_if< std::is_const::value, + TrackIterRange< TrackType > + >::type + { + return Tracks< TrackType >( &Track::IsSelected ); + } + + + template < typename TrackType = Track > + auto Leaders() + -> TrackIterRange< TrackType > + { + return Tracks< TrackType >( &Track::IsLeader ); + } + + template < typename TrackType = const Track > + auto Leaders() const + -> typename std::enable_if< std::is_const::value, + TrackIterRange< TrackType > + >::type + { + return Tracks< TrackType >( &Track::IsLeader ); + } + + + template < typename TrackType = Track > + auto SelectedLeaders() + -> TrackIterRange< TrackType > + { + return Tracks< TrackType >( &Track::IsSelectedLeader ); + } + + template < typename TrackType = const Track > + auto SelectedLeaders() const + -> typename std::enable_if< std::is_const::value, + TrackIterRange< TrackType > + >::type + { + return Tracks< TrackType >( &Track::IsSelectedLeader ); + } + + + template + static auto SingletonRange( TrackType *pTrack ) + -> TrackIterRange< TrackType > + { + return pTrack->GetOwner()->template Any() + .StartingWith( pTrack ).EndingAfter( pTrack ); + } + + + static TrackIterRange< Track > + SyncLockGroup( Track *pTrack ); + + static TrackIterRange< const Track > + SyncLockGroup( const Track *pTrack ) + { + return SyncLockGroup(const_cast(pTrack)).Filter(); + } + +private: + template< typename TrackType, typename InTrackType > + static TrackIterRange< TrackType > + Channels_( TrackIter< InTrackType > iter1 ) + { + // Assume iterator filters leader tracks + if (*iter1) { + return { + iter1.Filter( &Track::Any ) + .template Filter(), + (++iter1).Filter( &Track::Any ) + .template Filter() + }; + } + else + // empty range + return { + iter1.template Filter(), + iter1.template Filter() + }; + } + +public: + // Find an iterator range of channels including the given track. + template< typename TrackType > + static auto Channels( TrackType *pTrack ) + -> TrackIterRange< TrackType > + { + return Channels_( pTrack->GetOwner()->FindLeader(pTrack) ); + } + friend class Track; friend class TrackListIterator; friend class SyncLockedTracksIterator; @@ -903,6 +1312,57 @@ class TrackList final : public wxEvtHandler, public ListOfTracks #endif private: + + // Visit all tracks satisfying a predicate, mutative access + template < + typename TrackType = Track, + typename Pred = + typename TrackIterRange< TrackType >::iterator::FunctionType + > + auto Tracks( const Pred &pred = {} ) + -> TrackIterRange< TrackType > + { + auto b = getBegin(), e = getEnd(); + return { { b, b, e, pred }, { b, e, e, pred } }; + } + + // Visit all tracks satisfying a predicate, const access + template < + typename TrackType = const Track, + typename Pred = + typename TrackIterRange< TrackType >::iterator::FunctionType + > + auto Tracks( const Pred &pred = {} ) const + -> typename std::enable_if< std::is_const::value, + TrackIterRange< TrackType > + >::type + { + auto b = const_cast(this)->getBegin(); + auto e = const_cast(this)->getEnd(); + return { { b, b, e, pred }, { b, e, e, pred } }; + } + + std::pair FindSyncLockGroup(Track *pMember) const; + + template < typename TrackType > + TrackIter< TrackType > + MakeTrackIterator( TrackNodePointer iter ) const + { + auto b = const_cast(this)->getBegin(); + auto e = const_cast(this)->getEnd(); + return { b, iter, e }; + } + + template < typename TrackType > + TrackIter< TrackType > + EndIterator() const + { + auto e = const_cast(this)->getEnd(); + return { e, e, e }; + } + + TrackIterRange< Track > EmptyRange() const; + bool isNull(TrackNodePointer p) const { return (p.second == this && p.first == ListOfTracks::end()) || (p.second == &mPendingUpdates && p.first == mPendingUpdates.end()); }