diff --git a/src/WaveTrack.cpp b/src/WaveTrack.cpp index c8755cd6e..28bce0cc2 100644 --- a/src/WaveTrack.cpp +++ b/src/WaveTrack.cpp @@ -2212,32 +2212,43 @@ int WaveTrack::GetNumClips() const return mClips.size(); } -bool WaveTrack::CanOffsetClip(WaveClip* clip, double amount, - double *allowedAmount /* = NULL */) +bool WaveTrack::CanOffsetClips( + const std::vector &clips, + double amount, + double *allowedAmount /* = NULL */) { if (allowedAmount) *allowedAmount = amount; - for (const auto &c: mClips) - { - if (c.get() != clip && c->GetStartTime() < clip->GetEndTime()+amount && - c->GetEndTime() > clip->GetStartTime()+amount) - { - if (!allowedAmount) - return false; // clips overlap + const auto &moving = [&](WaveClip *clip){ + // linear search might be improved, but expecting few moving clips + // compared with the fixed clips + return clips.end() != std::find( clips.begin(), clips.end(), clip ); + }; - 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) - *allowedAmount = c->GetStartTime()-clip->GetEndTime(); - if (*allowedAmount < 0) - *allowedAmount = 0; - } else - { - if (c->GetEndTime()-clip->GetStartTime() > *allowedAmount) - *allowedAmount = c->GetEndTime()-clip->GetStartTime(); - if (*allowedAmount > 0) - *allowedAmount = 0; + if (!allowedAmount) + return false; // clips overlap + + if (amount > 0) + { + if (c->GetStartTime()-clip->GetEndTime() < *allowedAmount) + *allowedAmount = c->GetStartTime()-clip->GetEndTime(); + if (*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 // any other constraint - if (!CanOffsetClip(clip, *allowedAmount, NULL)) { + if (!CanOffsetClips(clips, *allowedAmount, nullptr)) { *allowedAmount = 0; // play safe and don't allow anything return false; } diff --git a/src/WaveTrack.h b/src/WaveTrack.h index 627fcaa69..1caaaccc1 100644 --- a/src/WaveTrack.h +++ b/src/WaveTrack.h @@ -465,11 +465,16 @@ private: WaveClipPointers SortedClipArray(); WaveClipConstPointers SortedClipArray() const; - // Before calling 'Offset' on a clip, use this function to see if the - // offsetting is allowed with respect to the other clips in this track. - // This function can optionally return the amount that is allowed for offsetting - // in this direction maximally. - bool CanOffsetClip(WaveClip* clip, double amount, double *allowedAmount=NULL); + //! Decide whether the clips could be offset (and inserted) together without overlapping other clips + /*! + @return true if possible to offset by `(allowedAmount ? *allowedAmount : amount)` + */ + bool CanOffsetClips( + const std::vector &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 // function to see if the times are valid (i.e. don't overlap with diff --git a/src/menus/ClipMenus.cpp b/src/menus/ClipMenus.cpp index 2e97981af..8bf81d2cf 100644 --- a/src/menus/ClipMenus.cpp +++ b/src/menus/ClipMenus.cpp @@ -659,8 +659,7 @@ double DoClipMove auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) ); auto desiredSlideAmount = pShifter->HintOffsetLarger( desiredT0 - t0 ); - auto hSlideAmount = - state.DoSlideHorizontal( desiredSlideAmount, trackList ); + auto hSlideAmount = state.DoSlideHorizontal( desiredSlideAmount ); // update t0 and t1. There is the possibility that the updated // t0 may no longer be within the clip due to rounding errors, diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp index 33a6a4a33..cab71a70f 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp @@ -1372,6 +1372,19 @@ public: return rint(desiredOffset * rate) / rate; } + double AdjustOffsetSmaller(double desiredOffset) override + { + std::vector< WaveClip * > movingClips; + for ( auto &interval : MovingIntervals() ) { + auto data = + static_cast( 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 ) { diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index f8df91b26..cad59810f 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -179,16 +179,13 @@ namespace return 0; } - void DoOffset( ClipMoveState &state, Track *pTrack, double offset, - WaveClip *pExcludedClip = nullptr ) + void DoOffset( ClipMoveState &state, Track *pTrack, double offset ) { auto &clips = state.capturedClipArray; if ( !clips.empty() ) { for (auto &clip : clips) { - if (clip.clip) { - if (clip.clip != pExcludedClip) - clip.clip->Offset( offset ); - } + if (clip.clip) + clip.clip->Offset( offset ); else clip.track->Offset( offset ); } @@ -244,6 +241,11 @@ double TrackShifter::QuantizeOffset(double desiredOffset) return desiredOffset; } +double TrackShifter::AdjustOffsetSmaller(double desiredOffset) +{ + return desiredOffset; +} + bool TrackShifter::MayMigrateTo(Track &) { return false; @@ -518,63 +520,44 @@ const TrackInterval *ClipMoveState::CapturedInterval() const return nullptr; } -double ClipMoveState::DoSlideHorizontal( - double desiredSlideAmount, TrackList &trackList ) +double ClipMoveState::DoSlideHorizontal( double desiredSlideAmount ) { auto &state = *this; auto &capturedTrack = *state.mCapturedTrack; - state.hSlideAmount = desiredSlideAmount; // 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() ) - { - double allowed; - double initialAllowed; - double safeBigDistance = 1000 + 2.0 * ( trackList.GetEndTime() - - trackList.GetStartTime() ); - + if ( !state.shifters.empty() ) { + double initialAllowed = 0; do { // loop to compute allowed, does not actually move anything yet - initialAllowed = state.hSlideAmount; + initialAllowed = desiredSlideAmount; - 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 ); - - // 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 - 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 + for (auto &pair : shifters) { + auto newAmount = pair.second->AdjustOffsetSmaller( desiredSlideAmount ); + if ( desiredSlideAmount != newAmount ) { + if ( newAmount * desiredSlideAmount < 0 || + fabs(newAmount) > fabs(desiredSlideAmount) ) { + wxASSERT( false ); // AdjustOffsetSmaller didn't honor postcondition! + newAmount = 0; // Be sure the loop progresses to termination! } + 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 ); + if ( desiredSlideAmount != 0.0 ) + DoOffset( state, nullptr, desiredSlideAmount ); } else - // For Shift key down, or - // For non wavetracks, specifically label tracks ... - DoOffset( state, &capturedTrack, state.hSlideAmount ); + // Moving whole track + DoOffset( state, &capturedTrack, desiredSlideAmount ); - return state.hSlideAmount; + return (state.hSlideAmount = desiredSlideAmount); } namespace { @@ -1013,7 +996,7 @@ UIHandle::Result TimeShiftHandle::Drag // Note that mouse dragging doesn't use TrackShifter::HintOffsetLarger() - mClipMoveState.DoSlideHorizontal( desiredSlideAmount, trackList ); + mClipMoveState.DoSlideHorizontal( desiredSlideAmount ); if (mClipMoveState.movingSelection) { // Slide the selection, too diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index ed3a850d7..18d61dbca 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -79,6 +79,15 @@ public: /*! 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 */ /*! Default implementation returns false */ virtual bool MayMigrateTo( Track &otherTrack ); @@ -188,7 +197,7 @@ struct ClipMoveState { const TrackInterval *CapturedInterval() const; /*! @return actual slide amount, maybe adjusted toward zero from desired */ - double DoSlideHorizontal( double desiredSlideAmount, TrackList &trackList ); + double DoSlideHorizontal( double desiredSlideAmount ); std::shared_ptr mCapturedTrack;