diff --git a/src/Snap.h b/src/Snap.h index 6aaf6f8bf..edddcccf4 100644 --- a/src/Snap.h +++ b/src/Snap.h @@ -40,8 +40,10 @@ public: Track *track; Track *origTrack; - WaveTrack *dstTrack; WaveClip *clip; + + // These fields are used only during time-shift dragging + WaveTrack *dstTrack; std::shared_ptr holder; }; diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 0b2129906..06988a0e9 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -61,8 +61,6 @@ UIHandlePtr TimeShiftHandle::HitAnywhere (std::weak_ptr &holder, const std::shared_ptr &pTrack, bool gripHit) { - // After all that, it still may be unsafe to drag. - // Even if so, make an informative cursor change from default to "banned." auto result = std::make_shared( pTrack, gripHit ); result = AssignUIHandlePtr(holder, result); return result; @@ -99,84 +97,22 @@ TimeShiftHandle::~TimeShiftHandle() namespace { - // Adds a track's clips to mCapturedClipArray within a specified time - void AddClipsToCaptured - (TrackClipArray &capturedClipArray, Track *pTrack, double t0, double t1) - { - if (pTrack->GetKind() == Track::Wave) - { - for(const auto &clip: static_cast(pTrack)->GetClips()) - { - if (!clip->AfterClip(t0) && !clip->BeforeClip(t1)) - { - // Avoid getting clips that were already captured - bool newClip = true; - for (unsigned int ii = 0; newClip && ii < capturedClipArray.size(); ++ii) - newClip = (capturedClipArray[ii].clip != clip.get()); - if (newClip) - capturedClipArray.push_back(TrackClip(pTrack, clip.get())); - } - } - } - else - { - // This handles label tracks rather heavy-handedly -- it would be nice to - // treat individual labels like clips - - // Avoid adding a track twice - bool newClip = true; - for (unsigned int ii = 0; newClip && ii < capturedClipArray.size(); ++ii) - newClip = (capturedClipArray[ii].track != pTrack); - if (newClip) { -#ifdef USE_MIDI - // do not add NoteTrack if the data is outside of time bounds - if (pTrack->GetKind() == Track::Note) { - if (pTrack->GetEndTime() < t0 || pTrack->GetStartTime() > t1) - return; - } -#endif - capturedClipArray.push_back(TrackClip(pTrack, NULL)); - } - } - } - - // Helper for the above, adds a track's clips to mCapturedClipArray (eliminates - // duplication of this logic) - void AddClipsToCaptured - (TrackClipArray &capturedClipArray, - const ViewInfo &viewInfo, Track *pTrack, bool withinSelection) - { - if (withinSelection) - AddClipsToCaptured(capturedClipArray, pTrack, - viewInfo.selectedRegion.t0(), viewInfo.selectedRegion.t1()); - else - AddClipsToCaptured(capturedClipArray, pTrack, - pTrack->GetStartTime(), pTrack->GetEndTime()); - } - // Adds a track's clips to state.capturedClipArray within a specified time void AddClipsToCaptured ( ClipMoveState &state, Track *t, double t0, double t1 ) { + auto &clips = state.capturedClipArray; + + bool exclude = true; if (t->GetKind() == Track::Wave) { + exclude = false; for(const auto &clip: static_cast(t)->GetClips()) - { - if ( ! clip->AfterClip(t0) && ! clip->BeforeClip(t1) ) - { + if ( ! clip->AfterClip(t0) && ! clip->BeforeClip(t1) && // Avoid getting clips that were already captured - bool newClip = true; - for (unsigned int i = 0; i < state.capturedClipArray.size(); ++i) { - if ( state.capturedClipArray[i].clip == clip.get() ) { - newClip = false; - break; - } - } - - if (newClip) - state.capturedClipArray.push_back( TrackClip(t, clip.get()) ); - } - } + ! std::any_of( clips.begin(), clips.end(), + [&](const TrackClip &c) { return c.clip == clip.get(); } ) ) + clips.emplace_back( t, clip.get() ); } else { @@ -184,15 +120,8 @@ namespace // treat individual labels like clips // Avoid adding a track twice - bool newClip = true; - for ( unsigned int i = 0; i < state.capturedClipArray.size(); ++i ) { - if ( state.capturedClipArray[i].track == t ) { - newClip = false; - break; - } - } - - if (newClip) { + if( ! std::any_of( clips.begin(), clips.end(), + [&](const TrackClip &c) { return c.track == t; } ) ) { #ifdef USE_MIDI // do not add NoteTrack if the data is outside of time bounds if (t->GetKind() == Track::Note) { @@ -200,12 +129,14 @@ namespace return; } #endif - state.capturedClipArray.push_back(TrackClip(t, NULL)); + clips.emplace_back( t, nullptr ); } } + if ( exclude ) + state.trackExclusions.push_back(t); } - // Helper for the above, adds a track's clips to mCapturedClipArray (eliminates + // Helper for the above, adds a track's clips to capturedClipArray (eliminates // duplication of this logic) void AddClipsToCaptured ( ClipMoveState &state, const ViewInfo &viewInfo, @@ -260,6 +191,31 @@ namespace 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 = + pTrack->GetLink() && !pTrack->GetLinked() + ? pTrack->GetLink() : pTrack; + channel; + channel = channel->GetLinked() ? channel->GetLink() : nullptr) + channel->Offset( offset ); + } + } } void TimeShiftHandle::CreateListOfCapturedClips @@ -276,11 +232,8 @@ void TimeShiftHandle::CreateListOfCapturedClips if ( state.capturedClipIsSelection ) { TrackListIterator iter( &trackList ); for (Track *t = iter.First(); t; t = iter.Next()) { - if (t->GetSelected()) { + if (t->GetSelected()) AddClipsToCaptured( state, viewInfo, t, true ); - if (t->GetKind() != Track::Wave) - state.trackExclusions.push_back(t); - } } } else { @@ -303,40 +256,33 @@ void TimeShiftHandle::CreateListOfCapturedClips // Now, if sync-lock is enabled, capture any clip that's linked to a // captured clip. if ( syncLocked ) { - // AWD: mCapturedClipArray expands as the loop runs, so newly-added + // 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 ( state.capturedClipArray[i].clip ) { + if ( trackClip.clip ) { // Iterate over sync-lock group tracks. SyncLockedTracksIterator git( &trackList ); - for (Track *t = git.StartWith( state.capturedClipArray[i].track ); + for (Track *t = git.StartWith( trackClip.track ); t; t = git.Next() ) - { AddClipsToCaptured(state, t, - state.capturedClipArray[i].clip->GetStartTime(), - state.capturedClipArray[i].clip->GetEndTime() ); - if (t->GetKind() != Track::Wave) - state.trackExclusions.push_back(t); - } + trackClip.clip->GetStartTime(), + trackClip.clip->GetEndTime() ); } #ifdef USE_MIDI // Capture additional clips from NoteTracks - Track *nt = state.capturedClipArray[i].track; + Track *nt = trackClip.track; if (nt->GetKind() == Track::Note) { // Iterate over sync-lock group tracks. SyncLockedTracksIterator git( &trackList ); for (Track *t = git.StartWith(nt); t; t = git.Next()) - { AddClipsToCaptured ( state, t, nt->GetStartTime(), nt->GetEndTime() ); - if (t->GetKind() != Track::Wave) - state.trackExclusions.push_back(t); - } } #endif } @@ -346,11 +292,10 @@ void TimeShiftHandle::CreateListOfCapturedClips void TimeShiftHandle::DoSlideHorizontal ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack ) { -#ifdef USE_MIDI + // Given a signed slide distance, move clips, but subject to constraint of + // non-overlapping with other clips, so the distance may be adjusted toward + // zero. if ( state.capturedClipArray.size() ) -#else - if ( state.capturedClip ) -#endif { double allowed; double initialAllowed; @@ -360,21 +305,18 @@ void TimeShiftHandle::DoSlideHorizontal do { // loop to compute allowed, does not actually move anything yet initialAllowed = state.hSlideAmount; - unsigned int i, j; - for ( i = 0; i < state.capturedClipArray.size(); ++i ) { - WaveTrack *track = (WaveTrack *)state.capturedClipArray[i].track; - WaveClip *clip = state. capturedClipArray[i].clip; + for ( auto &trackClip : state.capturedClipArray ) { + if (const auto clip = trackClip.clip) { + // only audio clips are used to compute allowed + const auto track = static_cast( trackClip.track ); - if (clip) { // only audio clips are used to compute allowed // Move all other selected clips totally out of the way // temporarily because they're all moving together and // we want to find out if OTHER clips are in the way, // not one of the moving ones - for ( j = 0; j < state.capturedClipArray.size(); j++ ) { - WaveClip *clip2 = state.capturedClipArray[j].clip; - if (clip2 && clip2 != clip) - clip2->Offset(-safeBigDistance); - } + DoOffset( state, nullptr, -safeBigDistance, clip ); + auto cleanup = finally( [&] + { DoOffset( state, nullptr, safeBigDistance, clip ); } ); if ( track->CanOffsetClip(clip, state.hSlideAmount, &allowed) ) { if ( state.hSlideAmount != allowed ) { @@ -386,36 +328,18 @@ void TimeShiftHandle::DoSlideHorizontal state.hSlideAmount = 0.0; state.snapLeft = state.snapRight = -1; // see bug 1067 } - - for ( j = 0; j < state.capturedClipArray.size(); ++j ) { - WaveClip *clip2 = state.capturedClipArray[j].clip; - if (clip2 && clip2 != clip) - clip2->Offset(safeBigDistance); - } } } } while ( state.hSlideAmount != initialAllowed ); - if ( state.hSlideAmount != 0.0 ) { // finally, here is where clips are moved - unsigned int i; - for ( i = 0; i < state.capturedClipArray.size(); ++i ) { - Track *track = state.capturedClipArray[i].track; - WaveClip *clip = state.capturedClipArray[i].clip; - if (clip) - clip->Offset( state.hSlideAmount ); - else - track->Offset( state.hSlideAmount ); - } - } + // finally, here is where clips are moved + if ( state.hSlideAmount != 0.0 ) + DoOffset( state, nullptr, state.hSlideAmount ); } - else { + else // For Shift key down, or // For non wavetracks, specifically label tracks ... - capturedTrack.Offset( state.hSlideAmount ); - Track* link = capturedTrack.GetLink(); - if (link) - link->Offset( state.hSlideAmount ); - } + DoOffset( state, &capturedTrack, state.hSlideAmount ); } UIHandle::Result TimeShiftHandle::Click @@ -449,40 +373,41 @@ UIHandle::Result TimeShiftHandle::Click clickTime >= viewInfo.selectedRegion.t0() && clickTime < viewInfo.selectedRegion.t1()); - WaveTrack *wt = pTrack->GetKind() == Track::Wave - ? static_cast(pTrack.get()) : nullptr; + mClipMoveState.capturedClip = NULL; + mClipMoveState.capturedClipArray.clear(); - if ((wt -#ifdef USE_MIDI - || pTrack->GetKind() == Track::Note -#endif - ) && !event.ShiftDown()) - { -#ifdef USE_MIDI - if (!wt) - mClipMoveState.capturedClip = nullptr; - else -#endif + bool ok = true; + bool captureClips = false; + + if (!event.ShiftDown()) { + WaveTrack *wt = pTrack->GetKind() == Track::Wave + ? static_cast(pTrack.get()) : nullptr; + if (wt) { - mClipMoveState.capturedClip = wt->GetClipAtX(event.m_x); - if (mClipMoveState.capturedClip == NULL) - return Cancelled; + if (nullptr == + (mClipMoveState.capturedClip = wt->GetClipAtX(event.m_x))) + ok = false; + else + captureClips = true; } +#ifdef USE_MIDI + else if (pTrack->GetKind() == Track::Note) + { + captureClips = true; + } +#endif + } + if ( ! ok ) + return Cancelled; + else if ( captureClips ) CreateListOfCapturedClips ( mClipMoveState, viewInfo, *pTrack, *trackList, pProject->IsSyncLocked(), clickTime ); - } - else { - // Shift was down, or track was not Wave or Note - mClipMoveState.capturedClip = NULL; - mClipMoveState.capturedClipArray.clear(); - } mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive; mRect = rect; - mMouseClickX = event.m_x; - const double selStart = viewInfo.PositionToTime(event.m_x, mRect.x); + mClipMoveState.mMouseClickX = event.m_x; mSnapManager = std::make_shared(trackList, &viewInfo, &mClipMoveState.capturedClipArray, @@ -492,24 +417,263 @@ UIHandle::Result TimeShiftHandle::Click mClipMoveState.snapRight = -1; mSnapPreferRightEdge = mClipMoveState.capturedClip && - (fabs(selStart - mClipMoveState.capturedClip->GetEndTime()) < - fabs(selStart - mClipMoveState.capturedClip->GetStartTime())); + (fabs(clickTime - mClipMoveState.capturedClip->GetEndTime()) < + fabs(clickTime - mClipMoveState.capturedClip->GetStartTime())); return RefreshNone; } +namespace { + double FindDesiredSlideAmount( + const ViewInfo &viewInfo, wxCoord xx, const wxMouseEvent &event, + SnapManager *pSnapManager, + bool slideUpDownOnly, bool snapPreferRightEdge, + ClipMoveState &state, + Track &capturedTrack, Track &track ) + { + if (slideUpDownOnly) + return 0.0; + else { + double desiredSlideAmount = + viewInfo.PositionToTime(event.m_x) - + viewInfo.PositionToTime(state.mMouseClickX); + double clipLeft = 0, clipRight = 0; + + if (track.GetKind() == Track::Wave) { + WaveTrack *const mtw = static_cast(&track); + const double rate = mtw->GetRate(); + // set it to a sample point + desiredSlideAmount = rint(desiredSlideAmount * rate) / rate; + } + + // Adjust desiredSlideAmount using SnapManager + if (pSnapManager) { + if (state.capturedClip) { + clipLeft = state.capturedClip->GetStartTime() + + desiredSlideAmount; + clipRight = state.capturedClip->GetEndTime() + + desiredSlideAmount; + } + else { + clipLeft = capturedTrack.GetStartTime() + desiredSlideAmount; + clipRight = capturedTrack.GetEndTime() + desiredSlideAmount; + } + + auto results = + pSnapManager->Snap(&capturedTrack, clipLeft, false); + auto newClipLeft = results.outTime; + results = + pSnapManager->Snap(&capturedTrack, clipRight, false); + auto newClipRight = results.outTime; + + // Only one of them is allowed to snap + if (newClipLeft != clipLeft && newClipRight != clipRight) { + // Un-snap the un-preferred edge + if (snapPreferRightEdge) + newClipLeft = clipLeft; + else + newClipRight = clipRight; + } + + // Take whichever one snapped (if any) and compute the NEW desiredSlideAmount + state.snapLeft = -1; + state.snapRight = -1; + if (newClipLeft != clipLeft) { + const double difference = (newClipLeft - clipLeft); + desiredSlideAmount += difference; + state.snapLeft = + viewInfo.TimeToPosition(newClipLeft, xx); + } + else if (newClipRight != clipRight) { + const double difference = (newClipRight - clipRight); + desiredSlideAmount += difference; + state.snapRight = + viewInfo.TimeToPosition(newClipRight, xx); + } + } + return desiredSlideAmount; + } + } + + bool FindCorrespondence( + TrackList &trackList, Track &track, Track &capturedTrack, + ClipMoveState &state) + { + const int diff = + TrackPosition(trackList, &track) - + TrackPosition(trackList, &capturedTrack); + for ( auto &trackClip : state.capturedClipArray ) { + if (trackClip.clip) { + // Move all clips up or down by an equal count of audio tracks. + Track *const pSrcTrack = trackClip.track; + auto pDstTrack = NthAudioTrack(trackList, + diff + TrackPosition(trackList, pSrcTrack)); + // Can only move mono to mono, or left to left, or right to right + // And that must be so for each captured clip + bool stereo = (pSrcTrack->GetLink() != 0); + if (pDstTrack && stereo && !pSrcTrack->GetLinked()) + // Assume linked track is wave or null + pDstTrack = static_cast(pDstTrack->GetLink()); + bool ok = pDstTrack && + (stereo == (pDstTrack->GetLink() != 0)) && + (!stereo || (pSrcTrack->GetLinked() == pDstTrack->GetLinked())); + if (ok) + trackClip.dstTrack = pDstTrack; + else + return false; + } + } + return true; + } + + bool CheckFit( + const ViewInfo &viewInfo, wxCoord xx, ClipMoveState &state, + double tolerance, double &desiredSlideAmount ) + { + bool ok = true; + double firstTolerance = tolerance; + + // The desiredSlideAmount may change and the tolerance may get used up. + for ( unsigned iPass = 0; iPass < 2 && ok; ++iPass ) { + for ( auto &trackClip : state.capturedClipArray ) { + WaveClip *const pSrcClip = trackClip.clip; + if (pSrcClip) { + ok = trackClip.dstTrack->CanInsertClip( + pSrcClip, desiredSlideAmount, tolerance ); + if( !ok ) + break; + } + } + // If it fits ok, desiredSlideAmount could have been updated to get + // the clip to fit. + // Check again, in the new position, this time with zero tolerance. + if (firstTolerance == 0) + break; + else + tolerance = 0.0; + } + + return ok; + } + + struct TemporaryClipRemover { + TemporaryClipRemover( ClipMoveState &clipMoveState ) + : state( clipMoveState ) + { + // Pluck the moving clips out of their tracks + for ( auto &trackClip : state.capturedClipArray ) { + WaveClip *const pSrcClip = trackClip.clip; + if (pSrcClip) + trackClip.holder = + // Assume track is wave because it has a clip + static_cast(trackClip.track)-> + RemoveAndReturnClip(pSrcClip); + } + } + + void Fail() + { + // Cause destructor to put all clips back where they came from + for ( auto &trackClip : state.capturedClipArray ) + trackClip.dstTrack = static_cast(trackClip.track); + } + + ~TemporaryClipRemover() + { + // 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; + dstTrack->AddClip(std::move(trackClip.holder)); + trackClip.track = dstTrack; + } + } + } + + ClipMoveState &state; + }; +} + +bool TimeShiftHandle::DoSlideVertical +( ViewInfo &viewInfo, wxCoord xx, + ClipMoveState &state, TrackList &trackList, Track &capturedTrack, + Track &dstTrack, double &desiredSlideAmount ) +{ + if (!FindCorrespondence( trackList, dstTrack, capturedTrack, state)) + return false; + + // Having passed that test, remove clips temporarily from their + // tracks, so moving clips don't interfere with each other + // when we call CanInsertClip() + TemporaryClipRemover remover{ state }; + + // Now check that the move is possible + double slide = desiredSlideAmount; // remember amount requested. + // The test for tolerance will need review with FishEye! + // The tolerance is supposed to be the time for one pixel, + // i.e. one pixel tolerance at current zoom. + double tolerance = + viewInfo.PositionToTime(xx + 1) - viewInfo.PositionToTime(xx); + bool ok = CheckFit( viewInfo, xx, state, tolerance, desiredSlideAmount ); + + if (!ok) { + // Failure, even with using tolerance. + remover.Fail(); + + // Failure -- we'll put clips back where they were + // ok will next indicate if a horizontal slide is OK. + tolerance = 0.0; + desiredSlideAmount = slide; + ok = CheckFit( viewInfo, xx, state, tolerance, desiredSlideAmount ); + for ( auto &trackClip : state.capturedClipArray) { + WaveClip *const pSrcClip = trackClip.clip; + if (pSrcClip){ + + // Attempt to move to a new track did not work. + // Put the clip back appropriately shifted! + if( ok) + trackClip.holder->Offset(slide); + } + } + // Make the offset permanent; start from a "clean slate" + if( ok ) { + state.mMouseClickX = xx; + if (state.capturedClipIsSelection) { + // Slide the selection, too + viewInfo.selectedRegion.move( slide ); + } + state.hSlideAmount = 0; + } + + return false; + } + + // Make the offset permanent; start from a "clean slate" + state.mMouseClickX = xx; + return true; +} + UIHandle::Result TimeShiftHandle::Drag (const TrackPanelMouseEvent &evt, AudacityProject *pProject) { + using namespace RefreshCode; + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) { + this->Cancel(pProject); + return RefreshAll | Cancelled; + } + const wxMouseEvent &event = evt.event; ViewInfo &viewInfo = pProject->GetViewInfo(); - // We may switch pTrack to its stereo partner below Track *track = dynamic_cast(evt.pCell.get()); // Uncommenting this permits drag to continue to work even over the controls area /* - pTrack = static_cast(evt.pCell)->FindTrack().get(); + track = static_cast(evt.pCell)->FindTrack().get(); */ if (!track) { @@ -526,13 +690,6 @@ UIHandle::Result TimeShiftHandle::Drag return RefreshCode::RefreshNone; - using namespace RefreshCode; - const bool unsafe = pProject->IsAudioActive(); - if (unsafe) { - this->Cancel(pProject); - return RefreshAll | Cancelled; - } - TrackList *const trackList = pProject->GetTracks(); // GM: DoSlide now implementing snap-to @@ -541,28 +698,8 @@ UIHandle::Result TimeShiftHandle::Drag // Start by undoing the current slide amount; everything // happens relative to the original horizontal position of // each clip... -#ifdef USE_MIDI - if (mClipMoveState.capturedClipArray.size()) -#else - if (mClipMoveState.capturedClip) -#endif - { - for (unsigned ii = 0; ii < mClipMoveState.capturedClipArray.size(); ++ii) { - if (mClipMoveState.capturedClipArray[ii].clip) - mClipMoveState.capturedClipArray[ii].clip->Offset - ( -mClipMoveState.hSlideAmount ); - else - mClipMoveState.capturedClipArray[ii].track->Offset - ( -mClipMoveState.hSlideAmount ); - } - } - else { - // Was a shift-click - mCapturedTrack->Offset( -mClipMoveState.hSlideAmount ); - Track *const link = mCapturedTrack->GetLink(); - if (link) - link->Offset( -mClipMoveState.hSlideAmount ); - } + DoOffset( + mClipMoveState, mCapturedTrack.get(), -mClipMoveState.hSlideAmount ); if ( mClipMoveState.capturedClipIsSelection ) { // Slide the selection, too @@ -570,69 +707,10 @@ UIHandle::Result TimeShiftHandle::Drag } mClipMoveState.hSlideAmount = 0.0; - double desiredSlideAmount; - if (mSlideUpDownOnly) { - desiredSlideAmount = 0.0; - } - else { - desiredSlideAmount = - viewInfo.PositionToTime(event.m_x) - - viewInfo.PositionToTime(mMouseClickX); - double clipLeft = 0, clipRight = 0; - - if (pTrack->GetKind() == Track::Wave) { - WaveTrack *const mtw = static_cast(pTrack.get()); - const double rate = mtw->GetRate(); - // set it to a sample point - desiredSlideAmount = rint(desiredSlideAmount * rate) / rate; - } - - // Adjust desiredSlideAmount using SnapManager - if (mSnapManager.get()) { - if (mClipMoveState.capturedClip) { - clipLeft = mClipMoveState.capturedClip->GetStartTime() - + desiredSlideAmount; - clipRight = mClipMoveState.capturedClip->GetEndTime() - + desiredSlideAmount; - } - else { - clipLeft = mCapturedTrack->GetStartTime() + desiredSlideAmount; - clipRight = mCapturedTrack->GetEndTime() + desiredSlideAmount; - } - - auto results = - mSnapManager->Snap(mCapturedTrack.get(), clipLeft, false); - auto newClipLeft = results.outTime; - results = - mSnapManager->Snap(mCapturedTrack.get(), clipRight, false); - auto newClipRight = results.outTime; - - // Only one of them is allowed to snap - if (newClipLeft != clipLeft && newClipRight != clipRight) { - // Un-snap the un-preferred edge - if (mSnapPreferRightEdge) - newClipLeft = clipLeft; - else - newClipRight = clipRight; - } - - // Take whichever one snapped (if any) and compute the NEW desiredSlideAmount - mClipMoveState.snapLeft = -1; - mClipMoveState.snapRight = -1; - if (newClipLeft != clipLeft) { - const double difference = (newClipLeft - clipLeft); - desiredSlideAmount += difference; - mClipMoveState.snapLeft = - viewInfo.TimeToPosition(newClipLeft, mRect.x); - } - else if (newClipRight != clipRight) { - const double difference = (newClipRight - clipRight); - desiredSlideAmount += difference; - mClipMoveState.snapRight = - viewInfo.TimeToPosition(newClipRight, mRect.x); - } - } - } + double desiredSlideAmount = + FindDesiredSlideAmount( viewInfo, mRect.x, event, mSnapManager.get(), + mSlideUpDownOnly, mSnapPreferRightEdge, mClipMoveState, + *mCapturedTrack, *pTrack ); // Scroll during vertical drag. // EnsureVisible(pTrack); //vvv Gale says this has problems on Linux, per bug 393 thread. Revert for 2.0.2. @@ -645,140 +723,13 @@ UIHandle::Result TimeShiftHandle::Drag pTrack->GetKind() == Track::Wave /* && !mCapturedClipIsSelection*/) { - const int diff = - TrackPosition(*trackList, pTrack.get()) - - TrackPosition(*trackList, mCapturedTrack.get()); - for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); - ii < nn; ++ii ) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - if (trackClip.clip) { - // Move all clips up or down by an equal count of audio tracks. - Track *const pSrcTrack = trackClip.track; - auto pDstTrack = NthAudioTrack(*trackList, - diff + TrackPosition(*trackList, pSrcTrack)); - // Can only move mono to mono, or left to left, or right to right - // And that must be so for each captured clip - bool stereo = (pSrcTrack->GetLink() != 0); - if (pDstTrack && stereo && !pSrcTrack->GetLinked()) - // Assume linked track is wave or null - pDstTrack = static_cast(pDstTrack->GetLink()); - bool ok = pDstTrack && - (stereo == (pDstTrack->GetLink() != 0)) && - (!stereo || (pSrcTrack->GetLinked() == pDstTrack->GetLinked())); - if (ok) - trackClip.dstTrack = pDstTrack; - else - return RefreshAll; - } - } - - // Having passed that test, remove clips temporarily from their - // tracks, so moving clips don't interfere with each other - // when we call CanInsertClip() - for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); - ii < nn; ++ii ) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip) - trackClip.holder = - // Assume track is wave because it has a clip - static_cast(trackClip.track)-> - RemoveAndReturnClip(pSrcClip); - } - - // Now check that the move is possible - bool ok = true; - // The test for tolerance will need review with FishEye! - // The tolerance is supposed to be the time for one pixel, i.e. one pixel tolerance - // at current zoom. - double slide = desiredSlideAmount; // remember amount requested. - double tolerance = viewInfo.PositionToTime(event.m_x+1) - viewInfo.PositionToTime(event.m_x); - - // The desiredSlideAmount may change and the tolerance may get used up. - for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); - ok && ii < nn; ++ii) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip) - ok = trackClip.dstTrack->CanInsertClip(pSrcClip, desiredSlideAmount, tolerance); - } - - if( ok ) { - // fits ok, but desiredSlideAmount could have been updated to get the clip to fit. - // Check again, in the new position, this time with zero tolerance. - tolerance = 0.0; - for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); - ok && ii < nn; ++ii) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip) - ok = trackClip.dstTrack->CanInsertClip(pSrcClip, desiredSlideAmount, tolerance); - } - } - - if (!ok) { - // Failure, even with using tolerance. - // Failure -- we'll put clips back where they were - // ok will next indicate if a horizontal slide is OK. - ok = true; // assume slide is OK. - tolerance = 0.0; - desiredSlideAmount = slide; - for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); - ii < nn; ++ii) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip){ - // back to the track it came from... - trackClip.dstTrack = static_cast(trackClip.track); - ok = ok && trackClip.dstTrack->CanInsertClip(pSrcClip, desiredSlideAmount, tolerance); - } - } - for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); - ii < nn; ++ii) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip){ - - // Attempt to move to a new track did not work. - // Put the clip back appropriately shifted! - if( ok) - trackClip.holder->Offset(slide); - // Assume track is wave because it has a clip - static_cast(trackClip.track)-> - AddClip(std::move(trackClip.holder)); - } - } - // Make the offset permanent; start from a "clean slate" - if( ok ) { - mMouseClickX = event.m_x; - if (mClipMoveState.capturedClipIsSelection) { - // Slide the selection, too - viewInfo.selectedRegion.move( slide ); - } - mClipMoveState.hSlideAmount = 0; - } - - return RefreshAll; - } - else { - // Do the vertical moves of clips - for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); - ii < nn; ++ii ) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip) { - const auto dstTrack = trackClip.dstTrack; - dstTrack->AddClip(std::move(trackClip.holder)); - trackClip.track = dstTrack; - } - } - + if ( DoSlideVertical( viewInfo, event.m_x, mClipMoveState, + *trackList, *mCapturedTrack, *pTrack, desiredSlideAmount ) ) { mCapturedTrack = pTrack; mDidSlideVertically = true; - - // Make the offset permanent; start from a "clean slate" - mMouseClickX = event.m_x; } + else + return RefreshAll; // Not done yet, check for horizontal movement. slidVertically = true; @@ -833,19 +784,18 @@ UIHandle::Result TimeShiftHandle::Release if ( !mDidSlideVertically && mClipMoveState.hSlideAmount == 0 ) return result; - for ( size_t ii = 0; ii < mClipMoveState.capturedClipArray.size(); ++ii ) + for ( auto &trackClip : mClipMoveState.capturedClipArray ) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; WaveClip* pWaveClip = trackClip.clip; // Note that per AddClipsToCaptured(Track *t, double t0, double t1), - // in the non-WaveTrack case, the code adds a NULL clip to mCapturedClipArray, + // in the non-WaveTrack case, the code adds a NULL clip to capturedClipArray, // so we have to check for that any time we're going to deref it. // Previous code did not check it here, and that caused bug 367 crash. if (pWaveClip && trackClip.track != trackClip.origTrack) { // Now that user has dropped the clip into a different track, - // make sure the sample rate matches the destination track (mCapturedTrack). + // make sure the sample rate matches the destination track. // Assume the clip was dropped in a wave track pWaveClip->Resample (static_cast(trackClip.track)->GetRate()); diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 0a81acfce..782ce7a83 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -30,6 +30,8 @@ struct ClipMoveState { TrackClipArray capturedClipArray {}; wxInt64 snapLeft { -1 }, snapRight { -1 }; + int mMouseClickX{}; + void clear() { capturedClip = nullptr; @@ -38,6 +40,7 @@ struct ClipMoveState { hSlideAmount = 0; capturedClipArray.clear(); snapLeft = snapRight = -1; + mMouseClickX = 0; } }; @@ -65,6 +68,14 @@ public: static void DoSlideHorizontal ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack ); + // Try to move clips from one WaveTrack to another, before also moving + // by some horizontal amount, which may be slightly adjusted to fit the + // destination tracks. + static bool DoSlideVertical + ( ViewInfo &viewInfo, wxCoord xx, + ClipMoveState &state, TrackList &trackList, Track &capturedTrack, + Track &dstTrack, double &desiredSlideAmount ); + static UIHandlePtr HitAnywhere (std::weak_ptr &holder, const std::shared_ptr &pTrack, bool gripHit); @@ -108,8 +119,6 @@ private: bool mSnapPreferRightEdge{}; - int mMouseClickX{}; - // Handles snapping the selection boundaries or track boundaries to // line up with existing tracks or labels. mSnapLeft and mSnapRight // are the horizontal index of pixels to display user feedback