diff --git a/src/Menus.cpp b/src/Menus.cpp index 4e4944d19..c516096fb 100644 --- a/src/Menus.cpp +++ b/src/Menus.cpp @@ -1276,6 +1276,9 @@ void AudacityProject::CreateMenusAndCommands() c->AddCommand(wxT("CursorLongJumpLeft"), _("Cursor Long Jump Left"), FN(OnCursorLongJumpLeft), wxT("Shift+,")); c->AddCommand(wxT("CursorLongJumpRight"), _("Cursor Long Jump Right"), FN(OnCursorLongJumpRight), wxT("Shift+.")); + c->AddCommand(wxT("ClipLeft"), _("Clip Left"), FN(OnClipLeft), wxT("")); + c->AddCommand(wxT("ClipRight"), _("Clip Right"), FN(OnClipRight), wxT("")); + c->AddCommand(wxT("SelExtLeft"), _("Selection Extend Left"), FN(OnSelExtendLeft), wxT("Shift+Left\twantKeyup\tallowDup")); c->AddCommand(wxT("SelExtRight"), _("Selection Extend Right"), FN(OnSelExtendRight), wxT("Shift+Right\twantKeyup\tallowDup")); @@ -2930,6 +2933,16 @@ void AudacityProject::OnSelContractRight(const wxEvent * evt) OnCursorLeft( true, true, evt->GetEventType() == wxEVT_KEY_UP ); } +void AudacityProject::OnClipLeft() +{ + mTrackPanel->OnClipMove(false); +} + +void AudacityProject::OnClipRight() +{ + mTrackPanel->OnClipMove(true); +} + //this pops up a dialog which allows the left selection to be set. //If playing/recording is happening, it sets the left selection at //the current play position. diff --git a/src/Menus.h b/src/Menus.h index 83a9ec719..4ce144273 100644 --- a/src/Menus.h +++ b/src/Menus.h @@ -160,6 +160,9 @@ void OnSelExtendRight(const wxEvent * evt); void OnSelContractLeft(const wxEvent * evt); void OnSelContractRight(const wxEvent * evt); +void OnClipLeft(); +void OnClipRight(); + void OnCursorShortJumpLeft(); void OnCursorShortJumpRight(); void OnCursorLongJumpLeft(); diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 9a6d61b31..f13f7be1e 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -3479,89 +3479,18 @@ void TrackPanel::StartSlide(wxMouseEvent & event) if (mCapturedClip == NULL) return; } - // The captured clip is the focus, but we need to create a list - // of all clips that have to move, also... - mCapturedClipArray.clear(); - - // First, if click was in selection, capture selected clips; otherwise - // just the clicked-on clip - if (mCapturedClipIsSelection) { - TrackListIterator iter(GetTracks()); - for (Track *t = iter.First(); t; t = iter.Next()) { - if (t->GetSelected()) { - AddClipsToCaptured(t, true); - if (t->GetKind() != Track::Wave) - mTrackExclusions.push_back(t); - } - } - } - else { - mCapturedClipArray.push_back(TrackClip(vt, mCapturedClip)); - - // Check for stereo partner - Track *partner = vt->GetLink(); - WaveTrack *wt; - if (mCapturedClip && - // Assume linked track is wave or null - nullptr != (wt = static_cast(partner))) { - WaveClip *const clip = - FindClipAtTime(wt, - mViewInfo->PositionToTime(event.m_x, GetLeftOffset())); - if (clip) - mCapturedClipArray.push_back(TrackClip(partner, clip)); - } - } - - // Now, if sync-lock is enabled, capture any clip that's linked to a - // captured clip. - if (GetProject()->IsSyncLocked()) { - // 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 < mCapturedClipArray.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 (mCapturedClipArray[i].clip) { - // Iterate over sync-lock group tracks. - SyncLockedTracksIterator git(GetTracks()); - for (Track *t = git.StartWith(mCapturedClipArray[i].track); - t; t = git.Next() ) - { - AddClipsToCaptured(t, - mCapturedClipArray[i].clip->GetStartTime(), - mCapturedClipArray[i].clip->GetEndTime() ); - if (t->GetKind() != Track::Wave) - mTrackExclusions.push_back(t); - } - } -#ifdef USE_MIDI - // Capture additional clips from NoteTracks - Track *nt = mCapturedClipArray[i].track; - if (nt->GetKind() == Track::Note) { - // Iterate over sync-lock group tracks. - SyncLockedTracksIterator git(GetTracks()); - for (Track *t = git.StartWith(nt); t; t = git.Next()) - { - AddClipsToCaptured(t, nt->GetStartTime(), nt->GetEndTime()); - if (t->GetKind() != Track::Wave) - mTrackExclusions.push_back(t); - } - } -#endif - } - } + mCapturedTrack = vt; + CreateListOfCapturedClips(clickTime); } else { mCapturedClip = NULL; mCapturedClipArray.clear(); + mCapturedTrack = vt; } mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive; - mCapturedTrack = vt; mCapturedRect = rect; mMouseClickX = event.m_x; @@ -3587,6 +3516,83 @@ void TrackPanel::StartSlide(wxMouseEvent & event) mMouseCapture = IsSliding; } +void TrackPanel::CreateListOfCapturedClips(double clickTime) +{ +// The captured clip is the focus, but we need to create a list + // of all clips that have to move, also... + + mCapturedClipArray.clear(); + + // First, if click was in selection, capture selected clips; otherwise + // just the clicked-on clip + if (mCapturedClipIsSelection) { + TrackListIterator iter(GetTracks()); + for (Track *t = iter.First(); t; t = iter.Next()) { + if (t->GetSelected()) { + AddClipsToCaptured(t, true); + if (t->GetKind() != Track::Wave) + mTrackExclusions.push_back(t); + } + } + } + else { + mCapturedClipArray.push_back(TrackClip(mCapturedTrack, mCapturedClip)); + + // Check for stereo partner + Track *partner = mCapturedTrack->GetLink(); + WaveTrack *wt; + if (mCapturedClip && + // Assume linked track is wave or null + nullptr != (wt = static_cast(partner))) { + WaveClip *const clip = FindClipAtTime(wt, clickTime); + + if (clip) + mCapturedClipArray.push_back(TrackClip(partner, clip)); + } + } + + // Now, if sync-lock is enabled, capture any clip that's linked to a + // captured clip. + if (GetProject()->IsSyncLocked()) { + // 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 < mCapturedClipArray.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 (mCapturedClipArray[i].clip) { + // Iterate over sync-lock group tracks. + SyncLockedTracksIterator git(GetTracks()); + for (Track *t = git.StartWith(mCapturedClipArray[i].track); + t; t = git.Next() ) + { + AddClipsToCaptured(t, + mCapturedClipArray[i].clip->GetStartTime(), + mCapturedClipArray[i].clip->GetEndTime() ); + if (t->GetKind() != Track::Wave) + mTrackExclusions.push_back(t); + } + } +#ifdef USE_MIDI + // Capture additional clips from NoteTracks + Track *nt = mCapturedClipArray[i].track; + if (nt->GetKind() == Track::Note) { + // Iterate over sync-lock group tracks. + SyncLockedTracksIterator git(GetTracks()); + for (Track *t = git.StartWith(nt); t; t = git.Next()) + { + AddClipsToCaptured(t, nt->GetStartTime(), nt->GetEndTime()); + if (t->GetKind() != Track::Wave) + mTrackExclusions.push_back(t); + } + } +#endif + } + } +} + // Helper for the above, adds a track's clips to mCapturedClipArray (eliminates // duplication of this logic) void TrackPanel::AddClipsToCaptured(Track *t, bool withinSelection) @@ -3877,6 +3883,24 @@ void TrackPanel::DoSlide(wxMouseEvent & event) mHSlideAmount = desiredSlideAmount; + DoSlideHorizontal(); + + + if (mCapturedClipIsSelection) { + // Slide the selection, too + mViewInfo->selectedRegion.move(mHSlideAmount); + } + + if (slidVertically) { + // NEW origin + mHSlideAmount = 0; + } + + Refresh(false); +} + +void TrackPanel::DoSlideHorizontal() +{ #ifdef USE_MIDI if (mCapturedClipArray.size()) #else @@ -3947,18 +3971,55 @@ void TrackPanel::DoSlide(wxMouseEvent & event) if (link) link->Offset(mHSlideAmount); } +} - if (mCapturedClipIsSelection) { - // Slide the selection, too - mViewInfo->selectedRegion.move(mHSlideAmount); +void TrackPanel::OnClipMove(bool right) +{ + auto track = GetFocusedTrack(); + + + // just dealing with clips in wave tracks for the moment. Note tracks?? + if (track && track->GetKind() == Track::Wave) { + auto wt = static_cast(track); + mCapturedClip = wt->GetClipAtTime(mViewInfo->selectedRegion.t0()); + if (mCapturedClip == nullptr) + return; + + mCapturedTrack = track; + mCapturedClipIsSelection = track->GetSelected() && !mViewInfo->selectedRegion.isPoint(); + mTrackExclusions.clear(); + + CreateListOfCapturedClips(mViewInfo->selectedRegion.t0()); + + double desiredSlideAmount = mViewInfo->OffsetTimeByPixels(0.0, 1); + + // set it to a sample point, and minimum of 1 sample point + double nSamples = rint(wt->GetRate() * desiredSlideAmount); + nSamples = std::max(nSamples, 1.0); + desiredSlideAmount = nSamples / wt->GetRate(); + + if (!right) + desiredSlideAmount *= -1; + mHSlideAmount = desiredSlideAmount; + DoSlideHorizontal(); + + // 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 = mViewInfo->selectedRegion.t0() + mHSlideAmount; + if (newT0 < mCapturedClip->GetStartTime()) + newT0 = mCapturedClip->GetStartTime(); + if (newT0 > mCapturedClip->GetEndTime()) + newT0 = mCapturedClip->GetEndTime(); + double diff = mViewInfo->selectedRegion.t1() - mViewInfo->selectedRegion.t0(); + mViewInfo->selectedRegion.setTimes(newT0, newT0 + diff); + + ScrollIntoView(mViewInfo->selectedRegion.t0()); + Refresh(false); + + if (mHSlideAmount == 0.0) + MessageForScreenReader( _("clip not moved")); } - - if (slidVertically) { - // NEW origin - mHSlideAmount = 0; - } - - Refresh(false); } diff --git a/src/TrackPanel.h b/src/TrackPanel.h index a0f444033..10d38c629 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -252,6 +252,8 @@ class AUDACITY_DLL_API TrackPanel final : public OverlayPanel { // (ignoring any fisheye) virtual double GetScreenEndTime() const; + virtual void OnClipMove(bool right); + protected: virtual MixerBoard* GetMixerBoard(); /** @brief Populates the track pop-down menu with the common set of @@ -377,6 +379,8 @@ protected: virtual void HandleSlide(wxMouseEvent & event); virtual void StartSlide(wxMouseEvent &event); virtual void DoSlide(wxMouseEvent &event); + virtual void DoSlideHorizontal(); + virtual void CreateListOfCapturedClips(double clickTime); virtual void AddClipsToCaptured(Track *t, bool withinSelection); virtual void AddClipsToCaptured(Track *t, double t0, double t1); diff --git a/src/WaveTrack.cpp b/src/WaveTrack.cpp index bc1a3bffd..ffc12a8c4 100644 --- a/src/WaveTrack.cpp +++ b/src/WaveTrack.cpp @@ -2228,6 +2228,17 @@ WaveClip* WaveTrack::GetClipAtSample(sampleCount sample) return NULL; } +WaveClip* WaveTrack::GetClipAtTime(double time) +{ + // When the time is both the end of a clip and the start of the next clip, the + // latter clip is returned. + const auto clips = SortedClipArray(); + auto result = find_if(clips.rbegin(), clips.rend(), [&] (WaveClip* const& clip) { + return time >= clip->GetStartTime() && time <= clip->GetEndTime(); }); + + return result != clips.rend() ? *result : nullptr; +} + Envelope* WaveTrack::GetEnvelopeAtX(int xcoord) { WaveClip* clip = GetClipAtX(xcoord); diff --git a/src/WaveTrack.h b/src/WaveTrack.h index a5e59873b..12e0006b1 100644 --- a/src/WaveTrack.h +++ b/src/WaveTrack.h @@ -276,6 +276,7 @@ class AUDACITY_DLL_API WaveTrack final : public PlayableTrack { Envelope* GetEnvelopeAtX(int xcoord); WaveClip* GetClipAtSample(sampleCount sample); + WaveClip* GetClipAtTime(double time); // // Getting information about the track's internal block sizes