audacia/src/Track.cpp

1294 lines
32 KiB
C++

/**********************************************************************
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 "Audacity.h" // for USE_* macros
#include "Track.h"
#include "Experimental.h"
#include <algorithm>
#include <numeric>
#include <float.h>
#include <wx/file.h>
#include <wx/textfile.h>
#include <wx/log.h>
#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<TrackList> &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<CommonTrackCell> &Track::GetTrackView()
{
return mpView;
}
void Track::SetTrackView( const std::shared_ptr<CommonTrackCell> &pView )
{
mpView = pView;
}
const std::shared_ptr<CommonTrackCell> &Track::GetTrackControls()
{
return mpControls;
}
void Track::SetTrackControls( const std::shared_ptr<CommonTrackCell> &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<const LabelTrack*>(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<const LabelTrack*>( 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<const PlayableTrack *>(&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<Track *, Track *> 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<const LabelTrack*>(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<const LabelTrack*>(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> TrackList::Create( AudacityProject *pOwner )
{
return std::make_shared<TrackList>( 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<Track> &pTrack )
{
// wxWidgets will own the event object
QueueEvent(
safenew TrackListEvent{ EVT_TRACKLIST_SELECTION_CHANGE, pTrack } );
}
void TrackList::DataEvent( const std::shared_ptr<Track> &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<Track> &pTrack, bool modifyState )
{
auto pEvent = std::make_unique<TrackListEvent>(
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<TrackList*>(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<Track*>( 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<TrackNodePointer> &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<Track> &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<Track> &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<Track>
TrackList::RegisterPendingChangedTrack( Updater updater, Track *src )
{
std::shared_ptr<Track> 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<Track> &pTrack )
{
Add<Track>( 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<Track> > 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> 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<const Track> Track::SubstitutePendingChangedTrack() const
{
return const_cast<Track*>(this)->SubstitutePendingChangedTrack();
}
std::shared_ptr<const Track> 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();
}
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;
}