1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-15 15:49:36 +02:00

Move TrackShifter subclasses each to its own new source file...

... These do not require header files; they are tiny proto-plugins that work
by registration of AttachedVirtualFunction overrides at startup.

Dependency may go from them to TrackView subclasses but not back; so in
principle a reduced Audacity without time-shift could be linked that still uses
the TrackView subclasses.

But other work to make the hit test for the Time Shift tool registrable would
have to precede that.
This commit is contained in:
Paul Licameli 2020-09-20 14:17:27 -04:00 committed by James Crook
parent 4abd38b9a0
commit 2b542bf734
7 changed files with 486 additions and 462 deletions

View File

@ -774,6 +774,7 @@ list( APPEND SOURCES
tracks/labeltrack/ui/LabelTextHandle.h
tracks/labeltrack/ui/LabelTrackControls.cpp
tracks/labeltrack/ui/LabelTrackControls.h
tracks/labeltrack/ui/LabelTrackShifter.cpp
tracks/labeltrack/ui/LabelTrackVRulerControls.cpp
tracks/labeltrack/ui/LabelTrackVRulerControls.h
tracks/labeltrack/ui/LabelTrackView.cpp
@ -782,6 +783,7 @@ list( APPEND SOURCES
tracks/playabletrack/notetrack/ui/NoteTrackButtonHandle.h
tracks/playabletrack/notetrack/ui/NoteTrackControls.cpp
tracks/playabletrack/notetrack/ui/NoteTrackControls.h
tracks/playabletrack/notetrack/ui/NoteTrackShifter.cpp
tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.cpp
tracks/playabletrack/notetrack/ui/NoteTrackSliderHandles.h
tracks/playabletrack/notetrack/ui/NoteTrackVRulerControls.cpp
@ -808,6 +810,7 @@ list( APPEND SOURCES
tracks/playabletrack/wavetrack/ui/SpectrumView.h
tracks/playabletrack/wavetrack/ui/WaveTrackControls.cpp
tracks/playabletrack/wavetrack/ui/WaveTrackControls.h
tracks/playabletrack/wavetrack/ui/WaveTrackShifter.cpp
tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.cpp
tracks/playabletrack/wavetrack/ui/WaveTrackSliderHandles.h
tracks/playabletrack/wavetrack/ui/WaveTrackVRulerControls.cpp

View File

@ -0,0 +1,256 @@
/*!
@file LabelTrackShifter.cpp
@brief headerless file injects method definitions for time shifting of LabelTrack
*/
#include "LabelTrackView.h"
#include "../../ui/TimeShiftHandle.h"
#include "../../../LabelTrack.h"
#include "../../../ViewInfo.h"
class LabelTrackShifter final : public TrackShifter {
public:
LabelTrackShifter( LabelTrack &track, AudacityProject &project )
: mpTrack{ track.SharedPointer<LabelTrack>() }
, mProject{ project }
{
InitIntervals();
mpTrack->Bind(
EVT_LABELTRACK_PERMUTED, &LabelTrackShifter::OnLabelPermuted, this );
mpTrack->Bind(
EVT_LABELTRACK_ADDITION, &LabelTrackShifter::OnLabelAdded, this );
mpTrack->Bind(
EVT_LABELTRACK_DELETION, &LabelTrackShifter::OnLabelDeleted, this );
}
~LabelTrackShifter() override
{
mpTrack->Unbind(
EVT_LABELTRACK_PERMUTED, &LabelTrackShifter::OnLabelPermuted, this );
mpTrack->Unbind(
EVT_LABELTRACK_ADDITION, &LabelTrackShifter::OnLabelAdded, this );
mpTrack->Unbind(
EVT_LABELTRACK_DELETION, &LabelTrackShifter::OnLabelDeleted, this );
}
Track &GetTrack() const override { return *mpTrack; }
static inline size_t& GetIndex(TrackInterval &interval)
{
auto pExtra =
static_cast<LabelTrack::IntervalData*>( interval.Extra() );
return pExtra->index;
}
static inline size_t GetIndex(const TrackInterval &interval)
{
return GetIndex( const_cast<TrackInterval&>(interval) );
}
HitTestResult HitTest(
double time, const ViewInfo &viewInfo, HitTestParams *pParams ) override
{
HitTestResult result = HitTestResult::Intervals;
auto t0 = viewInfo.selectedRegion.t0();
auto t1 = viewInfo.selectedRegion.t1();
if ( mpTrack->IsSelected() && time >= t0 && time < t1 )
result = HitTestResult::Selection;
// Prefer the box that the mouse hovers over, else the selected one
int iLabel = -1;
if ( pParams )
iLabel =
LabelTrackView::OverATextBox(*mpTrack, pParams->xx, pParams->yy);
if (iLabel == -1)
iLabel = LabelTrackView::Get(*mpTrack).GetSelectedIndex(mProject);
if (iLabel != -1) {
UnfixIntervals([&](const auto &myInterval){
return GetIndex( myInterval ) == iLabel;
});
return result;
}
else {
// If the pick is within the selection, which overlaps some intervals,
// then move those intervals only
// Else move all labels (preserving the older beahvior of time shift)
if ( result == HitTestResult::Selection )
SelectInterval({ t0, t1 });
if (mMoving.empty())
return HitTestResult::Track;
else
return result;
}
}
void SelectInterval( const TrackInterval &interval ) override
{
CommonSelectInterval(interval);
}
bool SyncLocks() override { return false; }
bool MayMigrateTo( Track &otherTrack ) override
{
return CommonMayMigrateTo(otherTrack);
}
/* We need to copy a complete label when detaching it because LabelStruct
is stored in a vector in LabelTrack without an extra indirection.
So the detached intervals handed back to the caller are unlike those
reported by LabelTrack, but carry the extra information. */
struct IntervalData final: Track::IntervalData {
SelectedRegion region;
wxString title;
IntervalData(const LabelStruct &label)
: region{label.selectedRegion}
, title{label.title}
{}
};
Intervals Detach() override
{
auto pTrack = mpTrack.get();
auto moveLabel = [pTrack](TrackInterval &interval) -> TrackInterval {
auto &rindex = GetIndex(interval);
auto index = rindex;
rindex = -1;
auto result = TrackInterval{
interval.Start(), interval.End(),
std::make_unique<IntervalData>( *pTrack->GetLabel(index) ) };
pTrack->DeleteLabel(index);
return result;
};
Intervals result;
std::transform(
// Reverse traversal may lessen the shifting-left in the label array
mMoving.rbegin(), mMoving.rend(), std::back_inserter(result),
moveLabel );
mMoving = Intervals{};
return result;
}
bool AdjustFit(
const Track &, const Intervals &, double &, double ) override
{
// Labels have no overlapping constraints, so just...
return true;
}
bool Attach( Intervals intervals ) override
{
auto pTrack = mpTrack.get();
std::for_each( intervals.rbegin(), intervals.rend(),
[this, pTrack](auto &interval){
auto pData = static_cast<IntervalData*>( interval.Extra() );
auto index = pTrack->AddLabel(pData->region, pData->title);
// Recreate the simpler TrackInterval as would be reported by LabelTrack
mMoving.emplace_back( pTrack->MakeInterval(index) );
} );
return true;
}
void DoHorizontalOffset( double offset ) override
{
auto &labels = mpTrack->GetLabels();
for ( auto &interval : MovingIntervals() ) {
auto index = GetIndex( interval );
auto labelStruct = labels[index];
labelStruct.selectedRegion.move(offset);
mpTrack->SetLabel( index, labelStruct );
}
mpTrack->SortLabels(); // causes callback to OnLabelPermuted
}
private:
void OnLabelPermuted( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock() != mpTrack )
return;
auto former = e.mFormerPosition;
auto present = e.mPresentPosition;
// Avoid signed-unsigned comparison below!
if (former < 0 || present < 0) {
wxASSERT(false);
return;
}
auto update = [=]( TrackInterval &interval ){
auto &index = GetIndex( interval );
if ( index == former )
index = present;
else if ( former < index && index <= present )
-- index;
else if ( former > index && index >= present )
++ index;
};
std::for_each(mFixed.begin(), mFixed.end(), update);
std::for_each(mMoving.begin(), mMoving.end(), update);
}
void OnLabelAdded( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock() != mpTrack )
return;
auto present = e.mPresentPosition;
// Avoid signed-unsigned comparison below!
if (present < 0) {
wxASSERT(false);
return;
}
auto update = [=]( TrackInterval &interval ){
auto pExtra = static_cast<LabelTrack::IntervalData*>(interval.Extra());
auto &index = pExtra->index;
if ( index >= present )
++ index;
};
std::for_each(mFixed.begin(), mFixed.end(), update);
std::for_each(mMoving.begin(), mMoving.end(), update);
}
void OnLabelDeleted( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock() != mpTrack )
return;
auto former = e.mFormerPosition;
// Avoid signed-unsigned comparison below!
if (former < 0) {
wxASSERT(false);
return;
}
auto update = [=]( TrackInterval &interval ){
auto pExtra = static_cast<LabelTrack::IntervalData*>(interval.Extra());
auto &index = pExtra->index;
if ( index > former )
-- index;
else if ( index == former )
// It should have been deleted first!
wxASSERT( false );
};
std::for_each(mFixed.begin(), mFixed.end(), update);
std::for_each(mMoving.begin(), mMoving.end(), update);
}
std::shared_ptr<LabelTrack> mpTrack;
AudacityProject &mProject;
};
using MakeLabelTrackShifter = MakeTrackShifter::Override<LabelTrack>;
template<> template<> auto MakeLabelTrackShifter::Implementation() -> Function {
return [](LabelTrack &track, AudacityProject &project) {
return std::make_unique<LabelTrackShifter>(track, project);
};
}
static MakeLabelTrackShifter registerMakeLabelTrackShifter;

View File

@ -2133,252 +2133,3 @@ std::shared_ptr<TrackVRulerControls> LabelTrackView::DoGetVRulerControls()
return
std::make_shared<LabelTrackVRulerControls>( shared_from_this() );
}
#include "../../ui/TimeShiftHandle.h"
class LabelTrackShifter final : public TrackShifter {
public:
LabelTrackShifter( LabelTrack &track, AudacityProject &project )
: mpTrack{ track.SharedPointer<LabelTrack>() }
, mProject{ project }
{
InitIntervals();
mpTrack->Bind(
EVT_LABELTRACK_PERMUTED, &LabelTrackShifter::OnLabelPermuted, this );
mpTrack->Bind(
EVT_LABELTRACK_ADDITION, &LabelTrackShifter::OnLabelAdded, this );
mpTrack->Bind(
EVT_LABELTRACK_DELETION, &LabelTrackShifter::OnLabelDeleted, this );
}
~LabelTrackShifter() override
{
mpTrack->Unbind(
EVT_LABELTRACK_PERMUTED, &LabelTrackShifter::OnLabelPermuted, this );
mpTrack->Unbind(
EVT_LABELTRACK_ADDITION, &LabelTrackShifter::OnLabelAdded, this );
mpTrack->Unbind(
EVT_LABELTRACK_DELETION, &LabelTrackShifter::OnLabelDeleted, this );
}
Track &GetTrack() const override { return *mpTrack; }
static inline size_t& GetIndex(TrackInterval &interval)
{
auto pExtra =
static_cast<LabelTrack::IntervalData*>( interval.Extra() );
return pExtra->index;
}
static inline size_t GetIndex(const TrackInterval &interval)
{
return GetIndex( const_cast<TrackInterval&>(interval) );
}
HitTestResult HitTest(
double time, const ViewInfo &viewInfo, HitTestParams *pParams ) override
{
HitTestResult result = HitTestResult::Intervals;
auto t0 = viewInfo.selectedRegion.t0();
auto t1 = viewInfo.selectedRegion.t1();
if ( mpTrack->IsSelected() && time >= t0 && time < t1 )
result = HitTestResult::Selection;
// Prefer the box that the mouse hovers over, else the selected one
int iLabel = -1;
if ( pParams )
iLabel =
LabelTrackView::OverATextBox(*mpTrack, pParams->xx, pParams->yy);
if (iLabel == -1)
iLabel = LabelTrackView::Get(*mpTrack).GetSelectedIndex(mProject);
if (iLabel != -1) {
UnfixIntervals([&](const auto &myInterval){
return GetIndex( myInterval ) == iLabel;
});
return result;
}
else {
// If the pick is within the selection, which overlaps some intervals,
// then move those intervals only
// Else move all labels (preserving the older beahvior of time shift)
if ( result == HitTestResult::Selection )
SelectInterval({ t0, t1 });
if (mMoving.empty())
return HitTestResult::Track;
else
return result;
}
}
void SelectInterval( const TrackInterval &interval ) override
{
CommonSelectInterval(interval);
}
bool SyncLocks() override { return false; }
bool MayMigrateTo( Track &otherTrack ) override
{
return CommonMayMigrateTo(otherTrack);
}
/* We need to copy a complete label when detaching it because LabelStruct
is stored in a vector in LabelTrack without an extra indirection.
So the detached intervals handed back to the caller are unlike those
reported by LabelTrack, but carry the extra information. */
struct IntervalData final: Track::IntervalData {
SelectedRegion region;
wxString title;
IntervalData(const LabelStruct &label)
: region{label.selectedRegion}
, title{label.title}
{}
};
Intervals Detach() override
{
auto pTrack = mpTrack.get();
auto moveLabel = [pTrack](TrackInterval &interval) -> TrackInterval {
auto &rindex = GetIndex(interval);
auto index = rindex;
rindex = -1;
auto result = TrackInterval{
interval.Start(), interval.End(),
std::make_unique<IntervalData>( *pTrack->GetLabel(index) ) };
pTrack->DeleteLabel(index);
return result;
};
Intervals result;
std::transform(
// Reverse traversal may lessen the shifting-left in the label array
mMoving.rbegin(), mMoving.rend(), std::back_inserter(result),
moveLabel );
mMoving = Intervals{};
return result;
}
bool AdjustFit(
const Track &, const Intervals &, double &, double ) override
{
// Labels have no overlapping constraints, so just...
return true;
}
bool Attach( Intervals intervals ) override
{
auto pTrack = mpTrack.get();
std::for_each( intervals.rbegin(), intervals.rend(),
[this, pTrack](auto &interval){
auto pData = static_cast<IntervalData*>( interval.Extra() );
auto index = pTrack->AddLabel(pData->region, pData->title);
// Recreate the simpler TrackInterval as would be reported by LabelTrack
mMoving.emplace_back( pTrack->MakeInterval(index) );
} );
return true;
}
void DoHorizontalOffset( double offset ) override
{
auto &labels = mpTrack->GetLabels();
for ( auto &interval : MovingIntervals() ) {
auto index = GetIndex( interval );
auto labelStruct = labels[index];
labelStruct.selectedRegion.move(offset);
mpTrack->SetLabel( index, labelStruct );
}
mpTrack->SortLabels(); // causes callback to OnLabelPermuted
}
private:
void OnLabelPermuted( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock() != mpTrack )
return;
auto former = e.mFormerPosition;
auto present = e.mPresentPosition;
// Avoid signed-unsigned comparison below!
if (former < 0 || present < 0) {
wxASSERT(false);
return;
}
auto update = [=]( TrackInterval &interval ){
auto &index = GetIndex( interval );
if ( index == former )
index = present;
else if ( former < index && index <= present )
-- index;
else if ( former > index && index >= present )
++ index;
};
std::for_each(mFixed.begin(), mFixed.end(), update);
std::for_each(mMoving.begin(), mMoving.end(), update);
}
void OnLabelAdded( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock() != mpTrack )
return;
auto present = e.mPresentPosition;
// Avoid signed-unsigned comparison below!
if (present < 0) {
wxASSERT(false);
return;
}
auto update = [=]( TrackInterval &interval ){
auto pExtra = static_cast<LabelTrack::IntervalData*>(interval.Extra());
auto &index = pExtra->index;
if ( index >= present )
++ index;
};
std::for_each(mFixed.begin(), mFixed.end(), update);
std::for_each(mMoving.begin(), mMoving.end(), update);
}
void OnLabelDeleted( LabelTrackEvent &e )
{
e.Skip();
if ( e.mpTrack.lock() != mpTrack )
return;
auto former = e.mFormerPosition;
// Avoid signed-unsigned comparison below!
if (former < 0) {
wxASSERT(false);
return;
}
auto update = [=]( TrackInterval &interval ){
auto pExtra = static_cast<LabelTrack::IntervalData*>(interval.Extra());
auto &index = pExtra->index;
if ( index > former )
-- index;
else if ( index == former )
// It should have been deleted first!
wxASSERT( false );
};
std::for_each(mFixed.begin(), mFixed.end(), update);
std::for_each(mMoving.begin(), mMoving.end(), update);
}
std::shared_ptr<LabelTrack> mpTrack;
AudacityProject &mProject;
};
using MakeLabelTrackShifter = MakeTrackShifter::Override<LabelTrack>;
template<> template<> auto MakeLabelTrackShifter::Implementation() -> Function {
return [](LabelTrack &track, AudacityProject &project) {
return std::make_unique<LabelTrackShifter>(track, project);
};
}
static MakeLabelTrackShifter registerMakeLabelTrackShifter;

View File

@ -0,0 +1,49 @@
/*!
@file NoteTrackShifter.cpp
@brief headerless file injects method definitions for time shifting of NoteTrack
*/
#include "../../../ui/TimeShiftHandle.h"
#include "../../../../NoteTrack.h"
#include "../../../../ViewInfo.h"
class NoteTrackShifter final : public TrackShifter {
public:
NoteTrackShifter( NoteTrack &track )
: mpTrack{ track.SharedPointer<NoteTrack>() }
{
InitIntervals();
}
~NoteTrackShifter() override {}
Track &GetTrack() const override { return *mpTrack; }
HitTestResult HitTest(
double time, const ViewInfo &viewInfo, HitTestParams* ) override
{
UnfixAll();
auto t0 = viewInfo.selectedRegion.t0();
auto t1 = viewInfo.selectedRegion.t1();
if ( mpTrack->IsSelected() && time >= t0 && time < t1 )
return HitTestResult::Selection;
else
return HitTestResult::Intervals;
}
void SelectInterval( const TrackInterval &interval ) override
{
CommonSelectInterval( interval );
}
bool SyncLocks() override { return true; }
private:
std::shared_ptr<NoteTrack> mpTrack;
};
using MakeNoteTrackShifter = MakeTrackShifter::Override<NoteTrack>;
template<> template<> auto MakeNoteTrackShifter::Implementation() -> Function {
return [](NoteTrack &track, AudacityProject&) {
return std::make_unique<NoteTrackShifter>(track);
};
}
static MakeNoteTrackShifter registerMakeNoteTrackShifter;

View File

@ -729,48 +729,4 @@ void NoteTrackView::Draw(
}
CommonTrackView::Draw( context, rect, iPass );
}
#include "../../../ui/TimeShiftHandle.h"
class NoteTrackShifter final : public TrackShifter {
public:
NoteTrackShifter( NoteTrack &track )
: mpTrack{ track.SharedPointer<NoteTrack>() }
{
InitIntervals();
}
~NoteTrackShifter() override {}
Track &GetTrack() const override { return *mpTrack; }
HitTestResult HitTest(
double time, const ViewInfo &viewInfo, HitTestParams* ) override
{
UnfixAll();
auto t0 = viewInfo.selectedRegion.t0();
auto t1 = viewInfo.selectedRegion.t1();
if ( mpTrack->IsSelected() && time >= t0 && time < t1 )
return HitTestResult::Selection;
else
return HitTestResult::Intervals;
}
void SelectInterval( const TrackInterval &interval ) override
{
CommonSelectInterval( interval );
}
bool SyncLocks() override { return true; }
private:
std::shared_ptr<NoteTrack> mpTrack;
};
using MakeNoteTrackShifter = MakeTrackShifter::Override<NoteTrack>;
template<> template<> auto MakeNoteTrackShifter::Implementation() -> Function {
return [](NoteTrack &track, AudacityProject&) {
return std::make_unique<NoteTrackShifter>(track);
};
}
static MakeNoteTrackShifter registerMakeNoteTrackShifter;
#endif

View File

@ -0,0 +1,178 @@
/*!
@file WaveTrackShifter.cpp
@brief headerless file injects method definitions for time shifting of WaveTrack
*/
#include "../../../ui/TimeShiftHandle.h"
#include "../../../../ViewInfo.h"
#include "../../../../WaveClip.h"
#include "../../../../WaveTrack.h"
class WaveTrackShifter final : public TrackShifter {
public:
WaveTrackShifter( WaveTrack &track )
: mpTrack{ track.SharedPointer<WaveTrack>() }
{
InitIntervals();
}
~WaveTrackShifter() override {}
Track &GetTrack() const override { return *mpTrack; }
HitTestResult HitTest(
double time, const ViewInfo &viewInfo, HitTestParams* ) override
{
auto pClip = mpTrack->GetClipAtTime( time );
if (!pClip)
return HitTestResult::Miss;
auto t0 = viewInfo.selectedRegion.t0();
auto t1 = viewInfo.selectedRegion.t1();
if ( mpTrack->IsSelected() && time >= t0 && time < t1 ) {
// Unfix maybe many intervals (at least one because of test above)
SelectInterval({t0, t1});
return HitTestResult::Selection;
}
// Select just one interval
UnfixIntervals( [&](const auto &interval){
return
static_cast<WaveTrack::IntervalData*>(interval.Extra())
->GetClip().get() == pClip;
} );
return HitTestResult::Intervals;
}
void SelectInterval( const TrackInterval &interval ) override
{
UnfixIntervals( [&](auto &myInterval){
// Use a slightly different test from CommonSelectInterval, rounding times
// to exact samples according to the clip's rate
auto data =
static_cast<WaveTrack::IntervalData*>( myInterval.Extra() );
auto clip = data->GetClip().get();
return !(clip->IsClipStartAfterClip(interval.Start()) ||
clip->BeforeClip(interval.End()));
});
}
bool SyncLocks() override { return true; }
bool MayMigrateTo(Track &other) override
{
return TrackShifter::CommonMayMigrateTo(other);
}
double HintOffsetLarger(double desiredOffset) override
{
// set it to a sample point, and minimum of 1 sample point
bool positive = (desiredOffset > 0);
if (!positive)
desiredOffset *= -1;
double nSamples = rint(mpTrack->GetRate() * desiredOffset);
nSamples = std::max(nSamples, 1.0);
desiredOffset = nSamples / mpTrack->GetRate();
if (!positive)
desiredOffset *= -1;
return desiredOffset;
}
double QuantizeOffset( double desiredOffset ) override
{
const auto rate = mpTrack->GetRate();
// set it to a sample point
return rint(desiredOffset * rate) / rate;
}
double AdjustOffsetSmaller(double desiredOffset) override
{
std::vector< WaveClip * > movingClips;
for ( auto &interval : MovingIntervals() ) {
auto data =
static_cast<WaveTrack::IntervalData*>( interval.Extra() );
movingClips.push_back(data->GetClip().get());
}
double newAmount = 0;
(void) mpTrack->CanOffsetClips(movingClips, desiredOffset, &newAmount);
return newAmount;
}
Intervals Detach() override
{
for ( auto &interval: mMoving ) {
auto pData = static_cast<WaveTrack::IntervalData*>( interval.Extra() );
auto pClip = pData->GetClip().get();
// interval will still hold the clip, so ignore the return:
(void) mpTrack->RemoveAndReturnClip(pClip);
mMigrated.erase(pClip);
}
return std::move( mMoving );
}
bool AdjustFit(
const Track &otherTrack, const Intervals &intervals,
double &desiredOffset, double tolerance) override
{
bool ok = true;
auto pOtherWaveTrack = static_cast<const WaveTrack*>(&otherTrack);
for ( auto &interval: intervals ) {
auto pData =
static_cast<WaveTrack::IntervalData*>( interval.Extra() );
auto pClip = pData->GetClip().get();
ok = pOtherWaveTrack->CanInsertClip(
pClip, desiredOffset, tolerance );
if( !ok )
break;
}
return ok;
}
bool Attach( Intervals intervals ) override
{
for (auto &interval : intervals) {
auto pData = static_cast<WaveTrack::IntervalData*>( interval.Extra() );
auto pClip = pData->GetClip();
if ( !mpTrack->AddClip( pClip ) )
return false;
mMigrated.insert( pClip.get() );
mMoving.emplace_back( std::move( interval ) );
}
return true;
}
bool FinishMigration() override
{
auto rate = mpTrack->GetRate();
for (auto pClip : mMigrated) {
// Now that user has dropped the clip into a different track,
// make sure the sample rate matches the destination track.
pClip->Resample(rate);
pClip->MarkChanged();
}
return true;
}
void DoHorizontalOffset( double offset ) override
{
for ( auto &interval : MovingIntervals() ) {
auto data =
static_cast<WaveTrack::IntervalData*>( interval.Extra() );
data->GetClip()->Offset( offset );
}
}
private:
std::shared_ptr<WaveTrack> mpTrack;
// Clips that may require resampling
std::unordered_set<WaveClip *> mMigrated;
};
using MakeWaveTrackShifter = MakeTrackShifter::Override<WaveTrack>;
template<> template<> auto MakeWaveTrackShifter::Implementation() -> Function {
return [](WaveTrack &track, AudacityProject&) {
return std::make_unique<WaveTrackShifter>(track);
};
}
static MakeWaveTrackShifter registerMakeWaveTrackShifter;

View File

@ -1303,172 +1303,3 @@ void WaveTrackView::Draw(
CommonTrackView::Draw( context, rect, iPass );
}
class WaveTrackShifter final : public TrackShifter {
public:
WaveTrackShifter( WaveTrack &track )
: mpTrack{ track.SharedPointer<WaveTrack>() }
{
InitIntervals();
}
~WaveTrackShifter() override {}
Track &GetTrack() const override { return *mpTrack; }
HitTestResult HitTest(
double time, const ViewInfo &viewInfo, HitTestParams* ) override
{
auto pClip = mpTrack->GetClipAtTime( time );
if (!pClip)
return HitTestResult::Miss;
auto t0 = viewInfo.selectedRegion.t0();
auto t1 = viewInfo.selectedRegion.t1();
if ( mpTrack->IsSelected() && time >= t0 && time < t1 ) {
// Unfix maybe many intervals (at least one because of test above)
SelectInterval({t0, t1});
return HitTestResult::Selection;
}
// Select just one interval
UnfixIntervals( [&](const auto &interval){
return
static_cast<WaveTrack::IntervalData*>(interval.Extra())
->GetClip().get() == pClip;
} );
return HitTestResult::Intervals;
}
void SelectInterval( const TrackInterval &interval ) override
{
UnfixIntervals( [&](auto &myInterval){
// Use a slightly different test from CommonSelectInterval, rounding times
// to exact samples according to the clip's rate
auto data =
static_cast<WaveTrack::IntervalData*>( myInterval.Extra() );
auto clip = data->GetClip().get();
return !(clip->IsClipStartAfterClip(interval.Start()) ||
clip->BeforeClip(interval.End()));
});
}
bool SyncLocks() override { return true; }
bool MayMigrateTo(Track &other) override
{
return TrackShifter::CommonMayMigrateTo(other);
}
double HintOffsetLarger(double desiredOffset) override
{
// set it to a sample point, and minimum of 1 sample point
bool positive = (desiredOffset > 0);
if (!positive)
desiredOffset *= -1;
double nSamples = rint(mpTrack->GetRate() * desiredOffset);
nSamples = std::max(nSamples, 1.0);
desiredOffset = nSamples / mpTrack->GetRate();
if (!positive)
desiredOffset *= -1;
return desiredOffset;
}
double QuantizeOffset( double desiredOffset ) override
{
const auto rate = mpTrack->GetRate();
// set it to a sample point
return rint(desiredOffset * rate) / rate;
}
double AdjustOffsetSmaller(double desiredOffset) override
{
std::vector< WaveClip * > movingClips;
for ( auto &interval : MovingIntervals() ) {
auto data =
static_cast<WaveTrack::IntervalData*>( interval.Extra() );
movingClips.push_back(data->GetClip().get());
}
double newAmount = 0;
(void) mpTrack->CanOffsetClips(movingClips, desiredOffset, &newAmount);
return newAmount;
}
Intervals Detach() override
{
for ( auto &interval: mMoving ) {
auto pData = static_cast<WaveTrack::IntervalData*>( interval.Extra() );
auto pClip = pData->GetClip().get();
// interval will still hold the clip, so ignore the return:
(void) mpTrack->RemoveAndReturnClip(pClip);
mMigrated.erase(pClip);
}
return std::move( mMoving );
}
bool AdjustFit(
const Track &otherTrack, const Intervals &intervals,
double &desiredOffset, double tolerance) override
{
bool ok = true;
auto pOtherWaveTrack = static_cast<const WaveTrack*>(&otherTrack);
for ( auto &interval: intervals ) {
auto pData =
static_cast<WaveTrack::IntervalData*>( interval.Extra() );
auto pClip = pData->GetClip().get();
ok = pOtherWaveTrack->CanInsertClip(
pClip, desiredOffset, tolerance );
if( !ok )
break;
}
return ok;
}
bool Attach( Intervals intervals ) override
{
for (auto &interval : intervals) {
auto pData = static_cast<WaveTrack::IntervalData*>( interval.Extra() );
auto pClip = pData->GetClip();
if ( !mpTrack->AddClip( pClip ) )
return false;
mMigrated.insert( pClip.get() );
mMoving.emplace_back( std::move( interval ) );
}
return true;
}
bool FinishMigration() override
{
auto rate = mpTrack->GetRate();
for (auto pClip : mMigrated) {
// Now that user has dropped the clip into a different track,
// make sure the sample rate matches the destination track.
pClip->Resample(rate);
pClip->MarkChanged();
}
return true;
}
void DoHorizontalOffset( double offset ) override
{
for ( auto &interval : MovingIntervals() ) {
auto data =
static_cast<WaveTrack::IntervalData*>( interval.Extra() );
data->GetClip()->Offset( offset );
}
}
private:
std::shared_ptr<WaveTrack> mpTrack;
// Clips that may require resampling
std::unordered_set<WaveClip *> mMigrated;
};
using MakeWaveTrackShifter = MakeTrackShifter::Override<WaveTrack>;
template<> template<> auto MakeWaveTrackShifter::Implementation() -> Function {
return [](WaveTrack &track, AudacityProject&) {
return std::make_unique<WaveTrackShifter>(track);
};
}
static MakeWaveTrackShifter registerMakeWaveTrackShifter;