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

Last virtuals, for horizontal shift; no more use of Track subclasses

This commit is contained in:
Paul Licameli 2020-09-18 23:50:37 -04:00
commit 69d5c1b12e
6 changed files with 166 additions and 312 deletions

View File

@ -2212,32 +2212,43 @@ int WaveTrack::GetNumClips() const
return mClips.size(); return mClips.size();
} }
bool WaveTrack::CanOffsetClip(WaveClip* clip, double amount, bool WaveTrack::CanOffsetClips(
double *allowedAmount /* = NULL */) const std::vector<WaveClip*> &clips,
double amount,
double *allowedAmount /* = NULL */)
{ {
if (allowedAmount) if (allowedAmount)
*allowedAmount = amount; *allowedAmount = amount;
for (const auto &c: mClips) const auto &moving = [&](WaveClip *clip){
{ // linear search might be improved, but expecting few moving clips
if (c.get() != clip && c->GetStartTime() < clip->GetEndTime()+amount && // compared with the fixed clips
c->GetEndTime() > clip->GetStartTime()+amount) return clips.end() != std::find( clips.begin(), clips.end(), clip );
{ };
if (!allowedAmount)
return false; // clips overlap
if (amount > 0) for (const auto &c: mClips) {
if ( moving( c.get() ) )
continue;
for (const auto clip : clips) {
if (c->GetStartTime() < clip->GetEndTime() + amount &&
c->GetEndTime() > clip->GetStartTime() + amount)
{ {
if (c->GetStartTime()-clip->GetEndTime() < *allowedAmount) if (!allowedAmount)
*allowedAmount = c->GetStartTime()-clip->GetEndTime(); return false; // clips overlap
if (*allowedAmount < 0)
*allowedAmount = 0; if (amount > 0)
} else {
{ if (c->GetStartTime()-clip->GetEndTime() < *allowedAmount)
if (c->GetEndTime()-clip->GetStartTime() > *allowedAmount) *allowedAmount = c->GetStartTime()-clip->GetEndTime();
*allowedAmount = c->GetEndTime()-clip->GetStartTime(); if (*allowedAmount < 0)
if (*allowedAmount > 0) *allowedAmount = 0;
*allowedAmount = 0; } else
{
if (c->GetEndTime()-clip->GetStartTime() > *allowedAmount)
*allowedAmount = c->GetEndTime()-clip->GetStartTime();
if (*allowedAmount > 0)
*allowedAmount = 0;
}
} }
} }
} }
@ -2249,7 +2260,7 @@ bool WaveTrack::CanOffsetClip(WaveClip* clip, double amount,
// Check if the NEW calculated amount would not violate // Check if the NEW calculated amount would not violate
// any other constraint // any other constraint
if (!CanOffsetClip(clip, *allowedAmount, NULL)) { if (!CanOffsetClips(clips, *allowedAmount, nullptr)) {
*allowedAmount = 0; // play safe and don't allow anything *allowedAmount = 0; // play safe and don't allow anything
return false; return false;
} }

View File

@ -465,11 +465,16 @@ private:
WaveClipPointers SortedClipArray(); WaveClipPointers SortedClipArray();
WaveClipConstPointers SortedClipArray() const; WaveClipConstPointers SortedClipArray() const;
// Before calling 'Offset' on a clip, use this function to see if the //! Decide whether the clips could be offset (and inserted) together without overlapping other clips
// offsetting is allowed with respect to the other clips in this track. /*!
// This function can optionally return the amount that is allowed for offsetting @return true if possible to offset by `(allowedAmount ? *allowedAmount : amount)`
// in this direction maximally. */
bool CanOffsetClip(WaveClip* clip, double amount, double *allowedAmount=NULL); bool CanOffsetClips(
const std::vector<WaveClip*> &clips, //!< not necessarily in this track
double amount, //!< signed
double *allowedAmount = nullptr /*!<
[out] if null, test exact amount only; else, largest (in magnitude) possible offset with same sign */
);
// Before moving a clip into a track (or inserting a clip), use this // Before moving a clip into a track (or inserting a clip), use this
// function to see if the times are valid (i.e. don't overlap with // function to see if the times are valid (i.e. don't overlap with

View File

@ -659,8 +659,7 @@ double DoClipMove
auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) ); auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) );
auto desiredSlideAmount = pShifter->HintOffsetLarger( desiredT0 - t0 ); auto desiredSlideAmount = pShifter->HintOffsetLarger( desiredT0 - t0 );
auto hSlideAmount = auto hSlideAmount = state.DoSlideHorizontal( desiredSlideAmount );
state.DoSlideHorizontal( desiredSlideAmount, trackList );
// update t0 and t1. There is the possibility that the updated // update t0 and t1. There is the possibility that the updated
// t0 may no longer be within the clip due to rounding errors, // t0 may no longer be within the clip due to rounding errors,

View File

@ -1364,6 +1364,26 @@ public:
desiredOffset *= -1; desiredOffset *= -1;
return desiredOffset; 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 Intervals Detach() override
{ {
@ -1420,6 +1440,15 @@ public:
return true; return true;
} }
void DoHorizontalOffset( double offset ) override
{
for ( auto &interval : MovingIntervals() ) {
auto data =
static_cast<WaveTrack::IntervalData*>( interval.Extra() );
data->GetClip()->Offset( offset );
}
}
private: private:
std::shared_ptr<WaveTrack> mpTrack; std::shared_ptr<WaveTrack> mpTrack;

View File

@ -16,33 +16,19 @@ Paul Licameli split from TrackPanel.cpp
#include "TrackView.h" #include "TrackView.h"
#include "../../AColor.h" #include "../../AColor.h"
#include "../../HitTestResult.h" #include "../../HitTestResult.h"
#include "../../NoteTrack.h"
#include "../../ProjectAudioIO.h" #include "../../ProjectAudioIO.h"
#include "../../ProjectHistory.h" #include "../../ProjectHistory.h"
#include "../../ProjectSettings.h" #include "../../ProjectSettings.h"
#include "../../RefreshCode.h" #include "../../RefreshCode.h"
#include "../../Snap.h" #include "../../Snap.h"
#include "../../Track.h"
#include "../../TrackArtist.h" #include "../../TrackArtist.h"
#include "../../TrackPanelDrawingContext.h" #include "../../TrackPanelDrawingContext.h"
#include "../../TrackPanelMouseEvent.h" #include "../../TrackPanelMouseEvent.h"
#include "../../UndoManager.h" #include "../../UndoManager.h"
#include "../../WaveClip.h"
#include "../../ViewInfo.h" #include "../../ViewInfo.h"
#include "../../WaveTrack.h"
#include "../../../images/Cursors.h" #include "../../../images/Cursors.h"
TrackClip::TrackClip(Track *t, WaveClip *c)
{
track = origTrack = t;
dstTrack = NULL;
clip = c;
}
TrackClip::~TrackClip()
{
}
TimeShiftHandle::TimeShiftHandle TimeShiftHandle::TimeShiftHandle
( const std::shared_ptr<Track> &pTrack, bool gripHit ) ( const std::shared_ptr<Track> &pTrack, bool gripHit )
: mGripHit{ gripHit } : mGripHit{ gripHit }
@ -115,88 +101,15 @@ TimeShiftHandle::~TimeShiftHandle()
{ {
} }
namespace void ClipMoveState::DoHorizontalOffset( double offset )
{ {
// Adds a track's clips to state.capturedClipArray within a specified time if ( !shifters.empty() ) {
void AddClipsToCaptured for ( auto &pair : shifters )
( ClipMoveState &state, Track *t, double t0, double t1 ) pair.second->DoHorizontalOffset( offset );
{
auto &clips = state.capturedClipArray;
t->TypeSwitch(
[&](WaveTrack *wt) {
for(const auto &clip: wt->GetClips())
if ( ! clip->IsClipStartAfterClip(t0) && ! clip->BeforeClip(t1) &&
// Avoid getting clips that were already captured
! std::any_of( clips.begin(), clips.end(),
[&](const TrackClip &c) { return c.clip == clip.get(); } ) )
clips.emplace_back( t, clip.get() );
},
#ifdef USE_MIDI
[&](NoteTrack *, const Track::Fallthrough &fallthrough){
// do not add NoteTrack if the data is outside of time bounds
if (t->GetEndTime() < t0 || t->GetStartTime() > t1)
return;
else
fallthrough();
},
#endif
[&](Track *t) {
// This handles label tracks rather heavy-handedly --
// it would be nice to
// treat individual labels like clips
// Avoid adding a track twice
if( !std::any_of( clips.begin(), clips.end(),
[&](const TrackClip &c) { return c.track == t; } ) ) {
clips.emplace_back( t, nullptr );
}
}
);
} }
else {
// Helper for the above, adds a track's clips to capturedClipArray (eliminates for (auto channel : TrackList::Channels( mCapturedTrack.get() ))
// duplication of this logic) channel->Offset( offset );
void AddClipsToCaptured
( ClipMoveState &state, const ViewInfo &viewInfo,
Track *t )
{
AddClipsToCaptured( state, t, viewInfo.selectedRegion.t0(),
viewInfo.selectedRegion.t1() );
}
WaveClip *FindClipAtTime(WaveTrack *pTrack, double time)
{
if (pTrack) {
// WaveClip::GetClipAtX doesn't work unless the clip is on the screen and can return bad info otherwise
// instead calculate the time manually
double rate = pTrack->GetRate();
auto s0 = (sampleCount)(time * rate + 0.5);
if (s0 >= 0)
return pTrack->GetClipAtSample(s0);
}
return 0;
}
void DoOffset( ClipMoveState &state, Track *pTrack, double offset,
WaveClip *pExcludedClip = nullptr )
{
auto &clips = state.capturedClipArray;
if ( !clips.empty() ) {
for (auto &clip : clips) {
if (clip.clip) {
if (clip.clip != pExcludedClip)
clip.clip->Offset( offset );
}
else
clip.track->Offset( offset );
}
}
else if ( pTrack )
// Was a shift-click
for (auto channel : TrackList::Channels( pTrack ))
channel->Offset( offset );
} }
} }
@ -209,6 +122,7 @@ void TrackShifter::UnfixIntervals(
if ( pred( *iter) ) { if ( pred( *iter) ) {
mMoving.push_back( std::move( *iter ) ); mMoving.push_back( std::move( *iter ) );
iter = mFixed.erase( iter ); iter = mFixed.erase( iter );
mAllFixed = false;
} }
else else
++iter; ++iter;
@ -219,6 +133,7 @@ void TrackShifter::UnfixAll()
{ {
std::move( mFixed.begin(), mFixed.end(), std::back_inserter(mMoving) ); std::move( mFixed.begin(), mFixed.end(), std::back_inserter(mMoving) );
mFixed = Intervals{}; mFixed = Intervals{};
mAllFixed = false;
} }
void TrackShifter::SelectInterval( const TrackInterval & ) void TrackShifter::SelectInterval( const TrackInterval & )
@ -239,6 +154,16 @@ double TrackShifter::HintOffsetLarger(double desiredOffset)
return desiredOffset; return desiredOffset;
} }
double TrackShifter::QuantizeOffset(double desiredOffset)
{
return desiredOffset;
}
double TrackShifter::AdjustOffsetSmaller(double desiredOffset)
{
return desiredOffset;
}
bool TrackShifter::MayMigrateTo(Track &) bool TrackShifter::MayMigrateTo(Track &)
{ {
return false; return false;
@ -296,6 +221,12 @@ bool TrackShifter::FinishMigration()
return true; return true;
} }
void TrackShifter::DoHorizontalOffset( double offset )
{
if (!AllFixed())
GetTrack().Offset( offset );
}
void TrackShifter::InitIntervals() void TrackShifter::InitIntervals()
{ {
mMoving.clear(); mMoving.clear();
@ -333,13 +264,7 @@ void ClipMoveState::Init(
const ViewInfo &viewInfo, const ViewInfo &viewInfo,
TrackList &trackList, bool syncLocked ) TrackList &trackList, bool syncLocked )
{ {
capturedClipArray.clear();
shifters.clear(); shifters.clear();
auto cleanup = finally([&]{
// In transition, this class holds two representations of what to shift.
// Be sure each is filled only if the other is.
wxASSERT( capturedClipArray.empty() == shifters.empty() );
});
auto &state = *this; auto &state = *this;
state.mCapturedTrack = capturedTrack.SharedPointer(); state.mCapturedTrack = capturedTrack.SharedPointer();
@ -353,15 +278,6 @@ void ClipMoveState::Init(
const bool capturedAClip = const bool capturedAClip =
pHit && !pHit->MovingIntervals().empty(); pHit && !pHit->MovingIntervals().empty();
if ( capturedAClip ) {
// There is still some code special to WaveTracks here that
// needs to go elsewhere
auto &interval = pHit->MovingIntervals()[0];
auto pInfo =
dynamic_cast<WaveTrack::IntervalData*>(interval.Extra());
if ( pInfo )
state.capturedClip = pInfo->GetClip().get();
}
state.shifters[&capturedTrack] = std::move( pHit ); state.shifters[&capturedTrack] = std::move( pHit );
@ -372,65 +288,6 @@ void ClipMoveState::Init(
pShifter = MakeTrackShifter::Call( *track ); pShifter = MakeTrackShifter::Call( *track );
} }
// The captured clip is the focus, but we need to create a list
// of all clips that have to move, also...
// First, if click was in selection, capture selected clips; otherwise
// just the clicked-on clip
if ( state.movingSelection )
// All selected tracks may move some intervals
for (auto t : trackList.Selected())
AddClipsToCaptured( state, viewInfo, t );
else {
// Move intervals only of the chosen channel group
state.capturedClipArray.push_back
(TrackClip( &capturedTrack, state.capturedClip ));
if (state.capturedClip) {
// Check for other channels
auto wt = static_cast<WaveTrack*>(&capturedTrack);
for ( auto channel : TrackList::Channels( wt ).Excluding( wt ) )
if (WaveClip *const clip = FindClipAtTime(channel, clickTime))
state.capturedClipArray.push_back(TrackClip(channel, clip));
}
}
// Now, if sync-lock is enabled, capture any clip that's linked to a
// captured clip.
if ( syncLocked ) {
// Sync lock propagation of unfixing of intervals
// AWD: capturedClipArray expands as the loop runs, so newly-added
// clips are considered (the effect is like recursion and terminates
// because AddClipsToCaptured doesn't add duplicate clips); to remove
// this behavior just store the array size beforehand.
for (unsigned int i = 0; i < state.capturedClipArray.size(); ++i) {
auto trackClip = state.capturedClipArray[i];
{
// Capture based on tracks that have clips -- that means we
// don't capture based on links to label tracks for now (until
// we can treat individual labels as clips)
if ( trackClip.clip ) {
// Iterate over sync-lock group tracks.
for (auto t : TrackList::SyncLockGroup( trackClip.track ))
AddClipsToCaptured(state, t,
trackClip.clip->GetStartTime(),
trackClip.clip->GetEndTime() );
}
}
#ifdef USE_MIDI
{
// Capture additional clips from NoteTracks
trackClip.track->TypeSwitch( [&](NoteTrack *nt) {
// Iterate over sync-lock group tracks.
for (auto t : TrackList::SyncLockGroup(nt))
AddClipsToCaptured
( state, t, nt->GetStartTime(), nt->GetEndTime() );
});
}
#endif
}
}
// Analogy of the steps above, but with TrackShifters, follows below // Analogy of the steps above, but with TrackShifters, follows below
if ( state.movingSelection ) { if ( state.movingSelection ) {
@ -513,63 +370,42 @@ const TrackInterval *ClipMoveState::CapturedInterval() const
return nullptr; return nullptr;
} }
double ClipMoveState::DoSlideHorizontal( double ClipMoveState::DoSlideHorizontal( double desiredSlideAmount )
double desiredSlideAmount, TrackList &trackList )
{ {
auto &state = *this; auto &state = *this;
auto &capturedTrack = *state.mCapturedTrack; auto &capturedTrack = *state.mCapturedTrack;
state.hSlideAmount = desiredSlideAmount;
// Given a signed slide distance, move clips, but subject to constraint of // Given a signed slide distance, move clips, but subject to constraint of
// non-overlapping with other clips, so the distance may be adjusted toward // non-overlapping with other clips, so the distance may be adjusted toward
// zero. // zero.
if ( state.capturedClipArray.size() ) if ( !state.shifters.empty() ) {
{ double initialAllowed = 0;
double allowed;
double initialAllowed;
double safeBigDistance = 1000 + 2.0 * ( trackList.GetEndTime() -
trackList.GetStartTime() );
do { // loop to compute allowed, does not actually move anything yet do { // loop to compute allowed, does not actually move anything yet
initialAllowed = state.hSlideAmount; initialAllowed = desiredSlideAmount;
for ( auto &trackClip : state.capturedClipArray ) { for (auto &pair : shifters) {
if (const auto clip = trackClip.clip) { auto newAmount = pair.second->AdjustOffsetSmaller( desiredSlideAmount );
// only audio clips are used to compute allowed if ( desiredSlideAmount != newAmount ) {
const auto track = static_cast<WaveTrack *>( trackClip.track ); if ( newAmount * desiredSlideAmount < 0 ||
fabs(newAmount) > fabs(desiredSlideAmount) ) {
// Move all other selected clips totally out of the way wxASSERT( false ); // AdjustOffsetSmaller didn't honor postcondition!
// temporarily because they're all moving together and newAmount = 0; // Be sure the loop progresses to termination!
// we want to find out if OTHER clips are in the way,
// not one of the moving ones
DoOffset( state, nullptr, -safeBigDistance, clip );
auto cleanup = finally( [&]
{ DoOffset( state, nullptr, safeBigDistance, clip ); } );
if ( track->CanOffsetClip(clip, state.hSlideAmount, &allowed) ) {
if ( state.hSlideAmount != allowed ) {
state.hSlideAmount = allowed;
state.snapLeft = state.snapRight = -1; // see bug 1067
}
}
else {
state.hSlideAmount = 0.0;
state.snapLeft = state.snapRight = -1; // see bug 1067
} }
desiredSlideAmount = newAmount;
state.snapLeft = state.snapRight = -1; // see bug 1067
} }
if (newAmount == 0)
break;
} }
} while ( state.hSlideAmount != initialAllowed ); } while ( desiredSlideAmount != initialAllowed );
// finally, here is where clips are moved
if ( state.hSlideAmount != 0.0 )
DoOffset( state, nullptr, state.hSlideAmount );
} }
else
// For Shift key down, or
// For non wavetracks, specifically label tracks ...
DoOffset( state, &capturedTrack, state.hSlideAmount );
return state.hSlideAmount; // Whether moving intervals or a whole track,
// finally, here is where clips are moved
if ( desiredSlideAmount != 0.0 )
state.DoHorizontalOffset( desiredSlideAmount );
return (state.hSlideAmount = desiredSlideAmount);
} }
namespace { namespace {
@ -621,9 +457,6 @@ UIHandle::Result TimeShiftHandle::Click
const double clickTime = const double clickTime =
viewInfo.PositionToTime(event.m_x, rect.x); viewInfo.PositionToTime(event.m_x, rect.x);
mClipMoveState.capturedClip = NULL;
mClipMoveState.capturedClipArray.clear();
bool captureClips = false; bool captureClips = false;
auto pShifter = MakeTrackShifter::Call( *pTrack ); auto pShifter = MakeTrackShifter::Call( *pTrack );
@ -677,8 +510,9 @@ namespace {
SnapManager *pSnapManager, SnapManager *pSnapManager,
bool slideUpDownOnly, bool snapPreferRightEdge, bool slideUpDownOnly, bool snapPreferRightEdge,
ClipMoveState &state, ClipMoveState &state,
Track &capturedTrack, Track &track ) Track &track )
{ {
auto &capturedTrack = *state.mCapturedTrack;
if (slideUpDownOnly) if (slideUpDownOnly)
return 0.0; return 0.0;
else { else {
@ -687,11 +521,9 @@ namespace {
viewInfo.PositionToTime(state.mMouseClickX); viewInfo.PositionToTime(state.mMouseClickX);
double clipLeft = 0, clipRight = 0; double clipLeft = 0, clipRight = 0;
track.TypeSwitch( [&](WaveTrack *mtw){ if (!state.shifters.empty())
const double rate = mtw->GetRate(); desiredSlideAmount =
// set it to a sample point state.shifters[ &track ]->QuantizeOffset( desiredSlideAmount );
desiredSlideAmount = rint(desiredSlideAmount * rate) / rate;
});
// Adjust desiredSlideAmount using SnapManager // Adjust desiredSlideAmount using SnapManager
if (pSnapManager) { if (pSnapManager) {
@ -791,12 +623,6 @@ namespace {
++iter; // Safe to increment TrackIter even at end of range ++iter; // Safe to increment TrackIter even at end of range
} }
// Record the correspondence in TrackClip
for ( auto &trackClip: state.capturedClipArray )
if ( trackClip.clip )
trackClip.dstTrack =
dynamic_cast<WaveTrack*>(correspondence[ trackClip.track ]);
return true; return true;
} }
@ -854,26 +680,12 @@ namespace {
void Fail() void Fail()
{ {
// Cause destructor to put all clips back where they came from
for ( auto &trackClip : state.capturedClipArray )
trackClip.dstTrack = static_cast<WaveTrack*>(trackClip.track);
failed = true; failed = true;
} }
void Reinsert( void Reinsert(
std::unordered_map< Track*, Track* > &correspondence ) std::unordered_map< Track*, Track* > &correspondence )
{ {
// Complete (or roll back) the vertical move.
// Put moving clips into their destination tracks
// which become the source tracks when we move again
for ( auto &trackClip : state.capturedClipArray ) {
WaveClip *const pSrcClip = trackClip.clip;
if (pSrcClip) {
const auto dstTrack = trackClip.dstTrack;
trackClip.track = dstTrack;
}
}
for (auto &pair : detached) { for (auto &pair : detached) {
auto pTrack = pair.first; auto pTrack = pair.first;
if (!failed && correspondence.count(pTrack)) if (!failed && correspondence.count(pTrack))
@ -970,8 +782,7 @@ UIHandle::Result TimeShiftHandle::Drag
// Start by undoing the current slide amount; everything // Start by undoing the current slide amount; everything
// happens relative to the original horizontal position of // happens relative to the original horizontal position of
// each clip... // each clip...
DoOffset( mClipMoveState.DoHorizontalOffset( -mClipMoveState.hSlideAmount );
mClipMoveState, mClipMoveState.mCapturedTrack.get(), -mClipMoveState.hSlideAmount );
if ( mClipMoveState.movingSelection ) { if ( mClipMoveState.movingSelection ) {
// Slide the selection, too // Slide the selection, too
@ -982,34 +793,29 @@ UIHandle::Result TimeShiftHandle::Drag
double desiredSlideAmount = double desiredSlideAmount =
FindDesiredSlideAmount( viewInfo, mRect.x, event, mSnapManager.get(), FindDesiredSlideAmount( viewInfo, mRect.x, event, mSnapManager.get(),
mSlideUpDownOnly, mSnapPreferRightEdge, mClipMoveState, mSlideUpDownOnly, mSnapPreferRightEdge, mClipMoveState,
*mClipMoveState.mCapturedTrack, *pTrack ); *pTrack );
// Scroll during vertical drag. // Scroll during vertical drag.
// If the mouse is over a track that isn't the captured track, // If the mouse is over a track that isn't the captured track,
// decide which tracks the captured clips should go to. // decide which tracks the captured clips should go to.
// EnsureVisible(pTrack); //vvv Gale says this has problems on Linux, per bug 393 thread. Revert for 2.0.2. // EnsureVisible(pTrack); //vvv Gale says this has problems on Linux, per bug 393 thread. Revert for 2.0.2.
bool slidVertically = ( bool slidVertically = (
mClipMoveState.capturedClip &&
pTrack != mClipMoveState.mCapturedTrack pTrack != mClipMoveState.mCapturedTrack
/* && !mCapturedClipIsSelection*/ /* && !mCapturedClipIsSelection*/
&& pTrack->TypeSwitch<bool>( [&] (WaveTrack *) { && DoSlideVertical( viewInfo, event.m_x, mClipMoveState,
if ( DoSlideVertical( viewInfo, event.m_x, mClipMoveState, trackList, *pTrack, desiredSlideAmount ) );
trackList, *pTrack, desiredSlideAmount ) ) { if (slidVertically)
mClipMoveState.mCapturedTrack = pTrack; {
mDidSlideVertically = true; mClipMoveState.mCapturedTrack = pTrack;
return true; mDidSlideVertically = true;
} }
else
return false;
})
);
if (desiredSlideAmount == 0.0) if (desiredSlideAmount == 0.0)
return RefreshAll; return RefreshAll;
// Note that mouse dragging doesn't use TrackShifter::HintOffsetLarger() // Note that mouse dragging doesn't use TrackShifter::HintOffsetLarger()
mClipMoveState.DoSlideHorizontal( desiredSlideAmount, trackList ); mClipMoveState.DoSlideHorizontal( desiredSlideAmount );
if (mClipMoveState.movingSelection) { if (mClipMoveState.movingSelection) {
// Slide the selection, too // Slide the selection, too

View File

@ -75,6 +75,19 @@ public:
*/ */
virtual double HintOffsetLarger( double desiredOffset ); virtual double HintOffsetLarger( double desiredOffset );
//! Given amount to shift by horizontally, do any preferred rounding, before placement constraint checks
/*! Default implementation returns argument */
virtual double QuantizeOffset( double desiredOffset );
//! Given amount to shift by horizontally, maybe adjust it toward zero to meet placement constraints
/*!
Default implementation returns the argument
@post `fabs(r) <= fabs(desiredOffset)`
@post `r * desiredOffset >= 0` (i.e. signs are not opposite)
@post (where `r` is return value)
*/
virtual double AdjustOffsetSmaller( double desiredOffset );
//! Whether intervals may migrate to the other track, not yet checking all placement constraints */ //! Whether intervals may migrate to the other track, not yet checking all placement constraints */
/*! Default implementation returns false */ /*! Default implementation returns false */
virtual bool MayMigrateTo( Track &otherTrack ); virtual bool MayMigrateTo( Track &otherTrack );
@ -109,6 +122,10 @@ public:
Default implementation does nothing and returns true */ Default implementation does nothing and returns true */
virtual bool FinishMigration(); virtual bool FinishMigration();
//! Shift all moving intervals horizontally
//! Default moves the whole track, provided `!AllFixed()`; else does nothing
virtual void DoHorizontalOffset( double offset );
protected: protected:
/*! Unfix any of the intervals that intersect the given one; may be useful to override `SelectInterval()` */ /*! Unfix any of the intervals that intersect the given one; may be useful to override `SelectInterval()` */
void CommonSelectInterval( const TrackInterval &interval ); void CommonSelectInterval( const TrackInterval &interval );
@ -122,8 +139,16 @@ protected:
/*! This can't be called by the base class constructor, when GetTrack() isn't yet callable */ /*! This can't be called by the base class constructor, when GetTrack() isn't yet callable */
void InitIntervals(); void InitIntervals();
bool AllFixed() const {
return mAllFixed && mMoving.empty();
}
Intervals mFixed; Intervals mFixed;
Intervals mMoving; Intervals mMoving;
private:
bool mAllFixed = true; /*!<
Becomes false after `UnfixAll()`, even if there are no intervals, or if any one interval was unfixed */
}; };
//! Used in default of other reimplementations to shift any track as a whole, invoking Track::Offset() //! Used in default of other reimplementations to shift any track as a whole, invoking Track::Offset()
@ -147,25 +172,6 @@ using MakeTrackShifter = AttachedVirtualFunction<
MakeTrackShifterTag, std::unique_ptr<TrackShifter>, Track>; MakeTrackShifterTag, std::unique_ptr<TrackShifter>, Track>;
class ViewInfo; class ViewInfo;
class WaveClip;
class WaveTrack;
class TrackClip
{
public:
TrackClip(Track *t, WaveClip *c);
~TrackClip();
Track *track;
Track *origTrack;
WaveClip *clip;
// These fields are used only during time-shift dragging
WaveTrack *dstTrack;
};
using TrackClipArray = std::vector <TrackClip>;
struct ClipMoveState { struct ClipMoveState {
using ShifterMap = std::unordered_map<Track*, std::unique_ptr<TrackShifter>>; using ShifterMap = std::unordered_map<Track*, std::unique_ptr<TrackShifter>>;
@ -183,29 +189,27 @@ struct ClipMoveState {
/*! Pointer may be invalidated by operations on the associated TrackShifter */ /*! Pointer may be invalidated by operations on the associated TrackShifter */
const TrackInterval *CapturedInterval() const; const TrackInterval *CapturedInterval() const;
//! Do sliding of tracks and intervals, maybe adjusting the offset
/*! @return actual slide amount, maybe adjusted toward zero from desired */ /*! @return actual slide amount, maybe adjusted toward zero from desired */
double DoSlideHorizontal( double desiredSlideAmount, TrackList &trackList ); double DoSlideHorizontal( double desiredSlideAmount );
//! Offset tracks or intervals horizontally, without adjusting the offset
void DoHorizontalOffset( double offset );
std::shared_ptr<Track> mCapturedTrack; std::shared_ptr<Track> mCapturedTrack;
// non-NULL only if click was in a WaveTrack and without Shift key:
WaveClip *capturedClip {};
bool movingSelection {}; bool movingSelection {};
double hSlideAmount {}; double hSlideAmount {};
ShifterMap shifters; ShifterMap shifters;
TrackClipArray capturedClipArray {};
wxInt64 snapLeft { -1 }, snapRight { -1 }; wxInt64 snapLeft { -1 }, snapRight { -1 };
int mMouseClickX{}; int mMouseClickX{};
void clear() void clear()
{ {
capturedClip = nullptr;
movingSelection = false; movingSelection = false;
hSlideAmount = 0; hSlideAmount = 0;
shifters.clear(); shifters.clear();
capturedClipArray.clear();
snapLeft = snapRight = -1; snapLeft = snapRight = -1;
mMouseClickX = 0; mMouseClickX = 0;
} }
@ -226,7 +230,7 @@ public:
bool IsGripHit() const { return mGripHit; } bool IsGripHit() const { return mGripHit; }
std::shared_ptr<Track> GetTrack() const = delete; std::shared_ptr<Track> GetTrack() const = delete;
// Try to move clips from one WaveTrack to another, before also moving // Try to move clips from one track to another, before also moving
// by some horizontal amount, which may be slightly adjusted to fit the // by some horizontal amount, which may be slightly adjusted to fit the
// destination tracks. // destination tracks.
static bool DoSlideVertical( static bool DoSlideVertical(