diff --git a/src/Menus.cpp b/src/Menus.cpp index 841b322c4..bfb851ba8 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -3358,6 +3358,62 @@ void AudacityProject::OnSelContractRight(const wxEvent * evt) OnCursorLeft( true, true, bKeyUp ); } +#include "tracks/ui/TimeShiftHandle.h" + +// This function returns the amount moved. Possibly 0.0. +double AudacityProject::OnClipMove + ( ViewInfo &viewInfo, Track *track, + TrackList &trackList, bool syncLocked, bool right ) +{ + // just dealing with clips in wave tracks for the moment. Note tracks?? + if (track && track->GetKind() == Track::Wave) { + ClipMoveState state; + + auto wt = static_cast(track); + auto t0 = viewInfo.selectedRegion.t0(); + + state.capturedClip = wt->GetClipAtTime( t0 ); + if (state.capturedClip == nullptr) + return 0.0; + + state.capturedClipIsSelection = + track->GetSelected() && !viewInfo.selectedRegion.isPoint(); + state.trackExclusions.clear(); + + TimeShiftHandle::CreateListOfCapturedClips + ( state, viewInfo, *track, trackList, syncLocked, t0 ); + + auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) ); + auto desiredSlideAmount = desiredT0 - t0; + + // set it to a sample point, and minimum of 1 sample point + if (!right) + desiredSlideAmount *= -1; + double nSamples = rint(wt->GetRate() * desiredSlideAmount); + nSamples = std::max(nSamples, 1.0); + desiredSlideAmount = nSamples / wt->GetRate(); + if (!right) + desiredSlideAmount *= -1; + + state.hSlideAmount = desiredSlideAmount; + TimeShiftHandle::DoSlideHorizontal( state, trackList, *track ); + + // update t0 and t1. There is the possibility that the updated + // t0 may no longer be within the clip due to rounding errors, + // so t0 is adjusted so that it is. + double newT0 = t0 + state.hSlideAmount; + if (newT0 < state.capturedClip->GetStartTime()) + newT0 = state.capturedClip->GetStartTime(); + if (newT0 > state.capturedClip->GetEndTime()) + newT0 = state.capturedClip->GetEndTime(); + double diff = viewInfo.selectedRegion.duration(); + viewInfo.selectedRegion.setTimes(newT0, newT0 + diff); + + return state.hSlideAmount; + } + return 0.0; +} + void AudacityProject::DoClipLeftOrRight(bool right, bool keyUp ) { if (keyUp) { @@ -3367,7 +3423,7 @@ void AudacityProject::DoClipLeftOrRight(bool right, bool keyUp ) auto &panel = *GetTrackPanel(); - auto amount = TrackPanel::OnClipMove + auto amount = OnClipMove ( mViewInfo, panel.GetFocusedTrack(), *GetTracks(), IsSyncLocked(), right ); diff --git a/src/Menus.h b/src/Menus.h index 033f97be7..fe7039e64 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -164,6 +164,10 @@ void OnSelContractLeft(const wxEvent * evt); void OnSelContractRight(const wxEvent * evt); public: +static double OnClipMove + (ViewInfo &viewInfo, Track *track, + TrackList &trackList, bool syncLocked, bool right); + void DoClipLeftOrRight(bool right, bool keyUp ); void OnClipLeft(const wxEvent* evt); void OnClipRight(const wxEvent* evt); diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 5b250164b..0006ffd32 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -3303,364 +3303,6 @@ void TrackPanel::ForwardEventToEnvelope(wxMouseEvent & event) } } -// Helper for the above, adds a track's clips to mCapturedClipArray (eliminates -// duplication of this logic) -void TrackPanel::AddClipsToCaptured - ( ClipMoveState &state, const ViewInfo &viewInfo, - Track *t, bool withinSelection ) -{ - if (withinSelection) - AddClipsToCaptured( state, t, viewInfo.selectedRegion.t0(), - viewInfo.selectedRegion.t1() ); - else - AddClipsToCaptured( state, t, t->GetStartTime(), t->GetEndTime() ); -} - -// Adds a track's clips to state.capturedClipArray within a specified time -void TrackPanel::AddClipsToCaptured - ( ClipMoveState &state, Track *t, double t0, double t1 ) -{ - if (t->GetKind() == Track::Wave) - { - for(const auto &clip: static_cast(t)->GetClips()) - { - 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()) ); - } - } - } - 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 i = 0; i < state.capturedClipArray.size(); ++i ) { - if ( state.capturedClipArray[i].track == t ) { - newClip = false; - break; - } - } - - if (newClip) { -#ifdef USE_MIDI - // do not add NoteTrack if the data is outside of time bounds - if (t->GetKind() == Track::Note) { - if (t->GetEndTime() < t0 || t->GetStartTime() > t1) - return; - } -#endif - state.capturedClipArray.push_back(TrackClip(t, NULL)); - } - } -} - -namespace { - 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 TrackPanel::CreateListOfCapturedClips - ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, - TrackList &trackList, bool syncLocked, double clickTime ) -{ -// The captured clip is the focus, but we need to create a list - // of all clips that have to move, also... - - state.capturedClipArray.clear(); - - // First, if click was in selection, capture selected clips; otherwise - // just the clicked-on clip - if ( state.capturedClipIsSelection ) { - TrackListIterator iter( &trackList ); - for (Track *t = iter.First(); t; t = iter.Next()) { - if (t->GetSelected()) { - AddClipsToCaptured( state, viewInfo, t, true ); - if (t->GetKind() != Track::Wave) - state.trackExclusions.push_back(t); - } - } - } - else { - state.capturedClipArray.push_back - (TrackClip( &capturedTrack, state.capturedClip )); - - // Check for stereo partner - Track *partner = capturedTrack.GetLink(); - WaveTrack *wt; - if (state.capturedClip && - // Assume linked track is wave or null - nullptr != (wt = static_cast(partner))) { - WaveClip *const clip = FindClipAtTime(wt, clickTime); - - if (clip) - state.capturedClipArray.push_back(TrackClip(partner, clip)); - } - } - - // 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 - // 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) { - // 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 ) { - // Iterate over sync-lock group tracks. - SyncLockedTracksIterator git( &trackList ); - for (Track *t = git.StartWith( state.capturedClipArray[i].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); - } - } -#ifdef USE_MIDI - // Capture additional clips from NoteTracks - Track *nt = state.capturedClipArray[i].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 - } - } -} - -#if 0 -// Helper for the above, adds a track's clips to mCapturedClipArray (eliminates -// duplication of this logic) -void TrackPanel::AddClipsToCaptured - ( ClipMoveState &state, const ViewInfo &viewInfo, - Track *t, bool withinSelection ) -{ - if (withinSelection) - AddClipsToCaptured( state, t, viewInfo.selectedRegion.t0(), - viewInfo.selectedRegion.t1() ); - else - AddClipsToCaptured( state, t, t->GetStartTime(), t->GetEndTime() ); -} - -// Adds a track's clips to mCapturedClipArray within a specified time -void TrackPanel::AddClipsToCaptured - ( ClipMoveState &state, Track *t, double t0, double t1 ) -{ - if (t->GetKind() == Track::Wave) - { - for(const auto &clip: static_cast(t)->GetClips()) - { - 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()) ); - } - } - } - 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 i = 0; i < state.capturedClipArray.size(); ++i ) { - if ( state.capturedClipArray[i].track == t ) { - newClip = false; - break; - } - } - - if (newClip) { -#ifdef USE_MIDI - // do not add NoteTrack if the data is outside of time bounds - if (t->GetKind() == Track::Note) { - if (t->GetEndTime() < t0 || t->GetStartTime() > t1) - return; - } -#endif - state.capturedClipArray.push_back(TrackClip(t, NULL)); - } - } -} -#endif - -void TrackPanel::DoSlideHorizontal - ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack ) -{ -#ifdef USE_MIDI - if ( state.capturedClipArray.size() ) -#else - if ( state.capturedClip ) -#endif - { - double allowed; - double initialAllowed; - double safeBigDistance = 1000 + 2.0 * ( trackList.GetEndTime() - - trackList.GetStartTime() ); - - 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; - - 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); - } - - 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 ( 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 ); - } - } - } - 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 ); - } -} - -// This function returns the amount moved. Possibly 0.0. -double TrackPanel::OnClipMove - ( ViewInfo &viewInfo, Track *track, - TrackList &trackList, bool syncLocked, bool right ) -{ - // just dealing with clips in wave tracks for the moment. Note tracks?? - if (track && track->GetKind() == Track::Wave) { - ClipMoveState state; - - auto wt = static_cast(track); - auto t0 = viewInfo.selectedRegion.t0(); - - state.capturedClip = wt->GetClipAtTime( t0 ); - if (state.capturedClip == nullptr) - return 0.0; - - state.capturedClipIsSelection = - track->GetSelected() && !viewInfo.selectedRegion.isPoint(); - state.trackExclusions.clear(); - - CreateListOfCapturedClips - ( state, viewInfo, *track, trackList, syncLocked, t0 ); - - auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) ); - auto desiredSlideAmount = desiredT0 - t0; - - // set it to a sample point, and minimum of 1 sample point - if (!right) - desiredSlideAmount *= -1; - double nSamples = rint(wt->GetRate() * desiredSlideAmount); - nSamples = std::max(nSamples, 1.0); - desiredSlideAmount = nSamples / wt->GetRate(); - if (!right) - desiredSlideAmount *= -1; - - state.hSlideAmount = desiredSlideAmount; - DoSlideHorizontal( state, trackList, *track ); - - // update t0 and t1. There is the possibility that the updated - // t0 may no longer be within the clip due to rounding errors, - // so t0 is adjusted so that it is. - double newT0 = t0 + state.hSlideAmount; - if (newT0 < state.capturedClip->GetStartTime()) - newT0 = state.capturedClip->GetStartTime(); - if (newT0 > state.capturedClip->GetEndTime()) - newT0 = state.capturedClip->GetEndTime(); - double diff = viewInfo.selectedRegion.duration(); - viewInfo.selectedRegion.setTimes(newT0, newT0 + diff); - - return state.hSlideAmount; - } - return 0.0; -} - - /// Determines if drag zooming is active bool TrackPanel::IsDragZooming(int zoomStart, int zoomEnd) { diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 2460fef47..5021ac8c6 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -21,8 +21,6 @@ #include "SelectedRegion.h" #include "WaveTrackLocation.h" -#include "Track.h" -#include "Snap.h" #include "widgets/OverlayPanel.h" #include "SelectionState.h" @@ -256,27 +254,6 @@ private: const int DragThreshold = 3;// Anything over 3 pixels is a drag, else a click. -struct ClipMoveState { - // non-NULL only if click was in a WaveTrack and without Shift key: - WaveClip *capturedClip {}; - - bool capturedClipIsSelection {}; - TrackArray trackExclusions {}; - double hSlideAmount {}; - TrackClipArray capturedClipArray {}; - wxInt64 snapLeft { -1 }, snapRight { -1 }; - - void clear() - { - capturedClip = nullptr; - capturedClipIsSelection = false; - trackExclusions.clear(); - hSlideAmount = 0; - capturedClipArray.clear(); - snapLeft = snapRight = -1; - } -}; - class AUDACITY_DLL_API TrackPanel final : public OverlayPanel { public: @@ -371,10 +348,6 @@ class AUDACITY_DLL_API TrackPanel final : public OverlayPanel { // (ignoring any fisheye) virtual double GetScreenEndTime() const; - static double OnClipMove - (ViewInfo &viewInfo, Track *track, - TrackList &trackList, bool syncLocked, bool right); - protected: virtual MixerBoard* GetMixerBoard(); /** @brief Populates the track pop-down menu with the common set of @@ -503,19 +476,6 @@ protected: virtual void ForwardEventToWaveTrackEnvelope(wxMouseEvent & event); virtual void ForwardEventToEnvelope(wxMouseEvent &event); -public: - static void DoSlideHorizontal - ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack ); - static void CreateListOfCapturedClips - ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, - TrackList &trackList, bool syncLocked, double clickTime ); - static void AddClipsToCaptured - ( ClipMoveState &state, const ViewInfo &viewInfo, - Track *t, bool withinSelection ); - static void AddClipsToCaptured - ( ClipMoveState &state, Track *t, double t0, double t1 ); - -protected: static bool IsDragZooming(int zoomStart, int zoomEnd); virtual bool IsDragZooming() { return IsDragZooming(mZoomStart, mZoomEnd); } diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 3158e7fac..26d7a1bb5 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -89,7 +89,127 @@ TimeShiftHandle::~TimeShiftHandle() { } -namespace { +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 ) + { + if (t->GetKind() == Track::Wave) + { + for(const auto &clip: static_cast(t)->GetClips()) + { + 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()) ); + } + } + } + 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 i = 0; i < state.capturedClipArray.size(); ++i ) { + if ( state.capturedClipArray[i].track == t ) { + newClip = false; + break; + } + } + + if (newClip) { + #ifdef USE_MIDI + // do not add NoteTrack if the data is outside of time bounds + if (t->GetKind() == Track::Note) { + if (t->GetEndTime() < t0 || t->GetStartTime() > t1) + return; + } + #endif + state.capturedClipArray.push_back(TrackClip(t, NULL)); + } + } + } + + // Helper for the above, adds a track's clips to mCapturedClipArray (eliminates + // duplication of this logic) + void AddClipsToCaptured + ( ClipMoveState &state, const ViewInfo &viewInfo, + Track *t, bool withinSelection ) + { + if (withinSelection) + AddClipsToCaptured( state, t, viewInfo.selectedRegion.t0(), + viewInfo.selectedRegion.t1() ); + else + AddClipsToCaptured( state, t, t->GetStartTime(), t->GetEndTime() ); + } + // Don't count right channels. WaveTrack *NthAudioTrack(TrackList &list, int nn) { @@ -117,6 +237,177 @@ namespace { } return -1; } + + 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 TimeShiftHandle::CreateListOfCapturedClips + ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, + TrackList &trackList, bool syncLocked, double clickTime ) +{ +// The captured clip is the focus, but we need to create a list + // of all clips that have to move, also... + + state.capturedClipArray.clear(); + + // First, if click was in selection, capture selected clips; otherwise + // just the clicked-on clip + if ( state.capturedClipIsSelection ) { + TrackListIterator iter( &trackList ); + for (Track *t = iter.First(); t; t = iter.Next()) { + if (t->GetSelected()) { + AddClipsToCaptured( state, viewInfo, t, true ); + if (t->GetKind() != Track::Wave) + state.trackExclusions.push_back(t); + } + } + } + else { + state.capturedClipArray.push_back + (TrackClip( &capturedTrack, state.capturedClip )); + + // Check for stereo partner + Track *partner = capturedTrack.GetLink(); + WaveTrack *wt; + if (state.capturedClip && + // Assume linked track is wave or null + nullptr != (wt = static_cast(partner))) { + WaveClip *const clip = FindClipAtTime(wt, clickTime); + + if (clip) + state.capturedClipArray.push_back(TrackClip(partner, clip)); + } + } + + // 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 + // 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) { + // 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 ) { + // Iterate over sync-lock group tracks. + SyncLockedTracksIterator git( &trackList ); + for (Track *t = git.StartWith( state.capturedClipArray[i].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); + } + } +#ifdef USE_MIDI + // Capture additional clips from NoteTracks + Track *nt = state.capturedClipArray[i].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 + } + } +} + +void TimeShiftHandle::DoSlideHorizontal + ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack ) +{ +#ifdef USE_MIDI + if ( state.capturedClipArray.size() ) +#else + if ( state.capturedClip ) +#endif + { + double allowed; + double initialAllowed; + double safeBigDistance = 1000 + 2.0 * ( trackList.GetEndTime() - + trackList.GetStartTime() ); + + 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; + + 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); + } + + 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 ( 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 ); + } + } + } + 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 ); + } } UIHandle::Result TimeShiftHandle::Click @@ -169,7 +460,7 @@ UIHandle::Result TimeShiftHandle::Click return Cancelled; } - TrackPanel::CreateListOfCapturedClips + CreateListOfCapturedClips ( mClipMoveState, viewInfo, *pTrack, *trackList, pProject->IsSyncLocked(), clickTime ); } @@ -459,7 +750,7 @@ UIHandle::Result TimeShiftHandle::Drag mClipMoveState.hSlideAmount = desiredSlideAmount; - TrackPanel::DoSlideHorizontal( mClipMoveState, *trackList, *mCapturedTrack ); + DoSlideHorizontal( mClipMoveState, *trackList, *mCapturedTrack ); if (mClipMoveState.capturedClipIsSelection) { // Slide the selection, too diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 71f606ff8..224c2b8d2 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -18,11 +18,30 @@ Paul Licameli #include "../../Snap.h" #include "../../Track.h" -#include "../../TrackPanel.h" // for ClipMoveState - struct HitTestResult; class WaveClip; +struct ClipMoveState { + // non-NULL only if click was in a WaveTrack and without Shift key: + WaveClip *capturedClip {}; + + bool capturedClipIsSelection {}; + TrackArray trackExclusions {}; + double hSlideAmount {}; + TrackClipArray capturedClipArray {}; + wxInt64 snapLeft { -1 }, snapRight { -1 }; + + void clear() + { + capturedClip = nullptr; + capturedClipIsSelection = false; + trackExclusions.clear(); + hSlideAmount = 0; + capturedClipArray.clear(); + snapLeft = snapRight = -1; + } +}; + class TimeShiftHandle final : public UIHandle { TimeShiftHandle(); @@ -33,6 +52,15 @@ class TimeShiftHandle final : public UIHandle (const AudacityProject *pProject, bool unsafe); public: + // A utility function also used by menu commands + static void CreateListOfCapturedClips + ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, + TrackList &trackList, bool syncLocked, double clickTime ); + + // A utility function also used by menu commands + static void DoSlideHorizontal + ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack ); + static HitTestResult HitAnywhere(const AudacityProject *pProject); static HitTestResult HitTest (const wxMouseEvent &event, const wxRect &rect, const AudacityProject *pProject);