1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-12-20 07:31:19 +01:00
Files
audacity/src/Track.cpp
Paul Licameli e653b4aaf8 Eliminate Experimental.h, configure compile options instead...
... This makes it impossible to forget to include the EXPERIMENTAL definitions
(such as when cutting and pasting code) and so get unintended quiet changes of
behavior.

The EXPERIMENTAL flags are now specified instead in new file Experimental.cmake
2021-04-27 12:40:07 -04:00

1299 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 <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();
}
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;
}