From d8a894b95b60c3dfa578bf856e2c81030fa75de9 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Wed, 9 Sep 2020 05:10:23 -0400 Subject: [PATCH 1/9] Define TrackShifter, subclasses, factory method; collect them... ... in TimeShiftHandle. They do nothing useful yet, but responsibilities will shift into them. TypeSwitch is avoided and the subclasses are defined in appropriate places for dependency management, thanks to AttachedVirtualFunction. --- .../notetrack/ui/NoteTrackView.cpp | 23 ++++++++++++ .../wavetrack/ui/WaveTrackView.cpp | 21 +++++++++++ src/tracks/ui/TimeShiftHandle.cpp | 29 ++++++++++++++- src/tracks/ui/TimeShiftHandle.h | 35 ++++++++++++++++++- 4 files changed, 106 insertions(+), 2 deletions(-) diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp index 655d2631d..09f4dc42f 100644 --- a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp +++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp @@ -729,4 +729,27 @@ void NoteTrackView::Draw( } CommonTrackView::Draw( context, rect, iPass ); } + +#include "../../../ui/TimeShiftHandle.h" + +class NoteTrackShifter final : public TrackShifter { +public: + NoteTrackShifter( NoteTrack &track ) + : mpTrack{ track.SharedPointer() } + {} + ~NoteTrackShifter() override {} + Track &GetTrack() const override { return *mpTrack; } + +private: + std::shared_ptr mpTrack; +}; + +using MakeNoteTrackShifter = MakeTrackShifter::Override; +template<> template<> auto MakeNoteTrackShifter::Implementation() -> Function { + return [](NoteTrack &track) { + return std::make_unique(track); + }; +} +static MakeNoteTrackShifter registerMakeNoteTrackShifter; + #endif diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp index 892a1b1cf..8739a62dd 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp @@ -1301,3 +1301,24 @@ void WaveTrackView::Draw( CommonTrackView::Draw( context, rect, iPass ); } + +class WaveTrackShifter final : public TrackShifter { +public: + WaveTrackShifter( WaveTrack &track ) + : mpTrack{ track.SharedPointer() } + { + } + ~WaveTrackShifter() override {} + Track &GetTrack() const override { return *mpTrack; } + +private: + std::shared_ptr mpTrack; +}; + +using MakeWaveTrackShifter = MakeTrackShifter::Override; +template<> template<> auto MakeWaveTrackShifter::Implementation() -> Function { + return [](WaveTrack &track) { + return std::make_unique(track); + }; +} +static MakeWaveTrackShifter registerMakeWaveTrackShifter; diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 17ebcc2a7..acc58dbd9 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -244,6 +244,20 @@ namespace } } +TrackShifter::~TrackShifter() = default; + +CoarseTrackShifter::CoarseTrackShifter( Track &track ) + : mpTrack{ track.SharedPointer() } +{} + +CoarseTrackShifter::~CoarseTrackShifter() = default; + +template<> auto MakeTrackShifter::Implementation() -> Function { + return [](Track &track) { + return std::make_unique(track); + }; +} + void TimeShiftHandle::CreateListOfCapturedClips ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, TrackList &trackList, bool syncLocked, double clickTime ) @@ -456,6 +470,8 @@ UIHandle::Result TimeShiftHandle::Click bool ok = true; bool captureClips = false; + auto pShifter = MakeTrackShifter::Call( *pTrack ); + if (!event.ShiftDown()) pTrack->TypeSwitch( [&](WaveTrack *wt) { @@ -474,10 +490,21 @@ UIHandle::Result TimeShiftHandle::Click if ( ! ok ) return Cancelled; - else if ( captureClips ) + + if ( captureClips ) { + mClipMoveState.shifters[pTrack] = std::move( pShifter ); + + // Collect TrackShifters for the rest of the tracks + for ( auto track : trackList.Any() ) { + auto &pShifter = mClipMoveState.shifters[track]; + if (!pShifter) + pShifter = MakeTrackShifter::Call( *track ); + } + CreateListOfCapturedClips( mClipMoveState, viewInfo, *pTrack, trackList, ProjectSettings::Get( *pProject ).IsSyncLocked(), clickTime ); + } mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive; mRect = rect; diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 7c3ec0c0a..8e68354ba 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -11,12 +11,41 @@ Paul Licameli #ifndef __AUDACITY_TIMESHIFT_HANDLE__ #define __AUDACITY_TIMESHIFT_HANDLE__ +#include + +#include "../../AttachedVirtualFunction.h" #include "../../UIHandle.h" class SnapManager; class Track; using TrackArray = std::vector; class TrackList; + +class Track; + +//! Abstract base class for policies to manipulate a track type with the Time Shift tool +class TrackShifter { +public: + virtual ~TrackShifter() = 0; + //! There is always an associated track + virtual Track &GetTrack() const = 0; +}; + +//! Used in default of other reimplementations to shift any track as a whole, invoking Track::Offset() +class CoarseTrackShifter final : public TrackShifter { +public: + CoarseTrackShifter( Track &track ); + ~CoarseTrackShifter() override; + Track &GetTrack() const override { return *mpTrack; } + +private: + std::shared_ptr mpTrack; +}; + +struct MakeTrackShifterTag; +using MakeTrackShifter = AttachedVirtualFunction< + MakeTrackShifterTag, std::unique_ptr, Track>; + class ViewInfo; class WaveClip; class WaveTrack; @@ -40,12 +69,15 @@ public: using TrackClipArray = std::vector ; struct ClipMoveState { + using ShifterMap = std::unordered_map>; + // non-NULL only if click was in a WaveTrack and without Shift key: WaveClip *capturedClip {}; bool capturedClipIsSelection {}; TrackArray trackExclusions {}; double hSlideAmount {}; + ShifterMap shifters; TrackClipArray capturedClipArray {}; wxInt64 snapLeft { -1 }, snapRight { -1 }; @@ -57,6 +89,7 @@ struct ClipMoveState { capturedClipIsSelection = false; trackExclusions.clear(); hSlideAmount = 0; + shifters.clear(); capturedClipArray.clear(); snapLeft = snapRight = -1; mMouseClickX = 0; @@ -73,7 +106,7 @@ public: explicit TimeShiftHandle ( const std::shared_ptr &pTrack, bool gripHit ); - TimeShiftHandle &operator=(const TimeShiftHandle&) = default; + TimeShiftHandle &operator=(TimeShiftHandle&&) = default; bool IsGripHit() const { return mGripHit; } std::shared_ptr GetTrack() const { return mCapturedTrack; } From 8f8c20ac80452c727ab741475d135243245a4f7a Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Fri, 11 Sep 2020 11:05:07 -0400 Subject: [PATCH 2/9] TrackShifters can classify track intervals as fixed or not --- .../notetrack/ui/NoteTrackView.cpp | 4 ++- .../wavetrack/ui/WaveTrackView.cpp | 1 + src/tracks/ui/TimeShiftHandle.cpp | 29 ++++++++++++++++++- src/tracks/ui/TimeShiftHandle.h | 25 ++++++++++++++++ 4 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp index 09f4dc42f..b9be10278 100644 --- a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp +++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp @@ -736,7 +736,9 @@ class NoteTrackShifter final : public TrackShifter { public: NoteTrackShifter( NoteTrack &track ) : mpTrack{ track.SharedPointer() } - {} + { + InitIntervals(); + } ~NoteTrackShifter() override {} Track &GetTrack() const override { return *mpTrack; } diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp index 8739a62dd..ebf9c8899 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp @@ -1307,6 +1307,7 @@ public: WaveTrackShifter( WaveTrack &track ) : mpTrack{ track.SharedPointer() } { + InitIntervals(); } ~WaveTrackShifter() override {} Track &GetTrack() const override { return *mpTrack; } diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index acc58dbd9..d7d34ebc0 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -246,9 +246,36 @@ namespace TrackShifter::~TrackShifter() = default; +void TrackShifter::UnfixIntervals( + std::function< bool( const TrackInterval& ) > pred ) +{ + for ( auto iter = mFixed.begin(); iter != mFixed.end(); ) { + if ( pred( *iter) ) { + mMoving.push_back( std::move( *iter ) ); + iter = mFixed.erase( iter ); + } + else + ++iter; + } +} + +void TrackShifter::UnfixAll() +{ + std::move( mFixed.begin(), mFixed.end(), std::back_inserter(mMoving) ); + mFixed = Intervals{}; +} + +void TrackShifter::InitIntervals() +{ + mMoving.clear(); + mFixed = GetTrack().GetIntervals(); +} + CoarseTrackShifter::CoarseTrackShifter( Track &track ) : mpTrack{ track.SharedPointer() } -{} +{ + InitIntervals(); +} CoarseTrackShifter::~CoarseTrackShifter() = default; diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 8e68354ba..4e4ff0acf 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -11,6 +11,7 @@ Paul Licameli #ifndef __AUDACITY_TIMESHIFT_HANDLE__ #define __AUDACITY_TIMESHIFT_HANDLE__ +#include #include #include "../../AttachedVirtualFunction.h" @@ -22,6 +23,7 @@ using TrackArray = std::vector; class TrackList; class Track; +class TrackInterval; //! Abstract base class for policies to manipulate a track type with the Time Shift tool class TrackShifter { @@ -29,6 +31,29 @@ public: virtual ~TrackShifter() = 0; //! There is always an associated track virtual Track &GetTrack() const = 0; + + using Intervals = std::vector; + + //! Return special intervals of the track that will not move + const Intervals &FixedIntervals() const { return mFixed; } + + //! Return special intervals of the track that may move + const Intervals &MovingIntervals() const { return mMoving; } + + //! Change intervals satisfying a predicate from fixed to moving + void UnfixIntervals( + std::function< bool( const TrackInterval& ) > pred ); + + //! Change all intervals from fixed to moving + void UnfixAll(); + +protected: + //! Derived class constructor can initialize all intervals reported by the track as fixed, none moving + /*! This can't be called by the base class constructor, when GetTrack() isn't yet callable */ + void InitIntervals(); + + Intervals mFixed; + Intervals mMoving; }; //! Used in default of other reimplementations to shift any track as a whole, invoking Track::Offset() From c279fb6588c26e85754b5ded105316746172fd62 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Wed, 9 Sep 2020 23:50:17 -0400 Subject: [PATCH 3/9] Choose track or clip shifting behavior without a TypeSwitch... ... Preserving existing behavior, but maybe these cases should be reconsidered and made more uniform. (e.g. should hit test on NoteTrack sometimes miss, as with WaveTrack?) Also made details of WaveTrack hit testing consistent with what ClipMenus does --- .../notetrack/ui/NoteTrackView.cpp | 5 ++ .../wavetrack/ui/WaveTrackView.cpp | 17 +++++++ src/tracks/ui/TimeShiftHandle.cpp | 50 ++++++++++++------- src/tracks/ui/TimeShiftHandle.h | 13 +++++ 4 files changed, 67 insertions(+), 18 deletions(-) diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp index b9be10278..74ad9d3d3 100644 --- a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp +++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp @@ -742,6 +742,11 @@ public: ~NoteTrackShifter() override {} Track &GetTrack() const override { return *mpTrack; } + HitTestResult HitTest( double ) override + { + return HitTestResult::Intervals; + } + private: std::shared_ptr mpTrack; }; diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp index ebf9c8899..b00cb9c5b 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp @@ -1312,6 +1312,23 @@ public: ~WaveTrackShifter() override {} Track &GetTrack() const override { return *mpTrack; } + HitTestResult HitTest( double time ) override + { + auto pClip = mpTrack->GetClipAtTime( time ); + + if (!pClip) + return HitTestResult::Miss; + + // Make a side-effect on our intervals + UnfixIntervals( [&](const auto &interval){ + return + static_cast(interval.Extra()) + ->GetClip().get() == pClip; + } ); + + return HitTestResult::Intervals; + } + private: std::shared_ptr mpTrack; }; diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index d7d34ebc0..a53324a5c 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -279,6 +279,11 @@ CoarseTrackShifter::CoarseTrackShifter( Track &track ) CoarseTrackShifter::~CoarseTrackShifter() = default; +auto CoarseTrackShifter::HitTest( double ) -> HitTestResult +{ + return HitTestResult::Track; +} + template<> auto MakeTrackShifter::Implementation() -> Function { return [](Track &track) { return std::make_unique(track); @@ -494,29 +499,38 @@ UIHandle::Result TimeShiftHandle::Click mClipMoveState.capturedClip = NULL; mClipMoveState.capturedClipArray.clear(); - bool ok = true; bool captureClips = false; + bool capturedAClip = false; auto pShifter = MakeTrackShifter::Call( *pTrack ); - if (!event.ShiftDown()) - pTrack->TypeSwitch( - [&](WaveTrack *wt) { - if (nullptr == - (mClipMoveState.capturedClip = wt->GetClipAtX(event.m_x))) - ok = false; - else - captureClips = true; -#ifdef USE_MIDI - }, - [&](NoteTrack *) { - captureClips = true; -#endif - } - ); + if (!event.ShiftDown()) { + switch( pShifter->HitTest( clickTime ) ) { + case TrackShifter::HitTestResult::Miss: + return Cancelled; + case TrackShifter::HitTestResult::Intervals: { + captureClips = true; + if ( !pShifter->MovingIntervals().empty() ) { + capturedAClip = true; - if ( ! ok ) - return Cancelled; + // There is still some code special to WaveTracks here that + // needs to go elsewhere + auto &interval = pShifter->MovingIntervals()[0]; + auto pInfo = + dynamic_cast(interval.Extra()); + if ( pInfo ) + mClipMoveState.capturedClip = pInfo->GetClip().get(); + } + break; + } + case TrackShifter::HitTestResult::Track: + default: + break; + } + } + else { + // As in the default above: just do shifting of one whole track + } if ( captureClips ) { mClipMoveState.shifters[pTrack] = std::move( pShifter ); diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 4e4ff0acf..7aa942996 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -32,6 +32,17 @@ public: //! There is always an associated track virtual Track &GetTrack() const = 0; + //! Possibilities for HitTest on the clicked track + enum class HitTestResult { + Miss, //!< Don't shift anything + Intervals, //; //! Return special intervals of the track that will not move @@ -63,6 +74,8 @@ public: ~CoarseTrackShifter() override; Track &GetTrack() const override { return *mpTrack; } + HitTestResult HitTest( double ) override; + private: std::shared_ptr mpTrack; }; From eb22892064b5f5b5aa7c8d26842428240504d663 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Fri, 11 Sep 2020 11:44:55 -0400 Subject: [PATCH 4/9] TrackShifters decide what parts move or stay fixed... ... for now redundantly with the older logic. Also shorten a function name to Init --- src/menus/ClipMenus.cpp | 2 +- .../notetrack/ui/NoteTrackView.cpp | 9 +- .../wavetrack/ui/WaveTrackView.cpp | 15 +++ src/tracks/ui/TimeShiftHandle.cpp | 94 ++++++++++++++++++- src/tracks/ui/TimeShiftHandle.h | 18 +++- 5 files changed, 130 insertions(+), 8 deletions(-) diff --git a/src/menus/ClipMenus.cpp b/src/menus/ClipMenus.cpp index b38107e4e..b37d30cb5 100644 --- a/src/menus/ClipMenus.cpp +++ b/src/menus/ClipMenus.cpp @@ -654,7 +654,7 @@ double DoClipMove track->GetSelected() && !selectedRegion.isPoint(); state.trackExclusions.clear(); - TimeShiftHandle::CreateListOfCapturedClips( + TimeShiftHandle::Init( state, viewInfo, *track, trackList, syncLocked, t0 ); auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) ); diff --git a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp index 74ad9d3d3..3af61d5e5 100644 --- a/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp +++ b/src/tracks/playabletrack/notetrack/ui/NoteTrackView.cpp @@ -741,12 +741,19 @@ public: } ~NoteTrackShifter() override {} Track &GetTrack() const override { return *mpTrack; } - + HitTestResult HitTest( double ) override { return HitTestResult::Intervals; } + void SelectInterval( const TrackInterval &interval ) override + { + CommonSelectInterval( interval ); + } + + bool SyncLocks() override { return true; } + private: std::shared_ptr mpTrack; }; diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp index b00cb9c5b..059a3124b 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp @@ -1329,6 +1329,21 @@ public: return HitTestResult::Intervals; } + void SelectInterval( const TrackInterval &interval ) override + { + UnfixIntervals( [&](auto &myInterval){ + // Use a slightly different test from CommonSelectInterval, rounding times + // to exact samples according to the clip's rate + auto data = + static_cast( myInterval.Extra() ); + auto clip = data->GetClip().get(); + return !(clip->IsClipStartAfterClip(interval.Start()) || + clip->BeforeClip(interval.End())); + }); + } + + bool SyncLocks() override { return true; } + private: std::shared_ptr mpTrack; }; diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index a53324a5c..610d0f556 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -265,6 +265,19 @@ void TrackShifter::UnfixAll() mFixed = Intervals{}; } +void TrackShifter::SelectInterval( const TrackInterval & ) +{ + UnfixAll(); +} + +void TrackShifter::CommonSelectInterval(const TrackInterval &interval) +{ + UnfixIntervals( [&](auto &myInterval){ + return !(interval.End() < myInterval.Start() || + myInterval.End() < interval.Start()); + }); +} + void TrackShifter::InitIntervals() { mMoving.clear(); @@ -284,15 +297,21 @@ auto CoarseTrackShifter::HitTest( double ) -> HitTestResult return HitTestResult::Track; } +bool CoarseTrackShifter::SyncLocks() +{ + return false; +} + template<> auto MakeTrackShifter::Implementation() -> Function { return [](Track &track) { return std::make_unique(track); }; } -void TimeShiftHandle::CreateListOfCapturedClips +void TimeShiftHandle::Init ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, - TrackList &trackList, bool syncLocked, double clickTime ) + TrackList &trackList, bool syncLocked, double clickTime, + bool capturedAClip ) { // The captured clip is the focus, but we need to create a list // of all clips that have to move, also... @@ -302,9 +321,11 @@ void TimeShiftHandle::CreateListOfCapturedClips // First, if click was in selection, capture selected clips; otherwise // just the clicked-on clip if ( state.capturedClipIsSelection ) + // 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 )); @@ -320,6 +341,7 @@ void TimeShiftHandle::CreateListOfCapturedClips // 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 @@ -351,6 +373,70 @@ void TimeShiftHandle::CreateListOfCapturedClips #endif } } + + // Analogy of the steps above, but with TrackShifters, follows below + + if ( state.capturedClipIsSelection ) { + // All selected tracks may move some intervals + const TrackInterval interval{ + viewInfo.selectedRegion.t0(), + viewInfo.selectedRegion.t1() + }; + for ( const auto &pair : state.shifters ) { + auto &shifter = *pair.second; + auto &track = shifter.GetTrack(); + if ( track.IsSelected() ) + shifter.SelectInterval( interval ); + } + } + else { + // Move intervals only of the chosen channel group + for ( auto channel : TrackList::Channels( &capturedTrack ) ) { + auto &shifter = *state.shifters[channel]; + if ( capturedAClip ) { + if ( channel != &capturedTrack ) + shifter.SelectInterval(TrackInterval{clickTime, clickTime}); + } + else + shifter.UnfixAll(); + } + } + + // Sync lock propagation of unfixing of intervals + if ( syncLocked ) { + bool change = true; + while( change ) { + change = false; + + // Iterate over all unfixed intervals in all shifters + // that do propagation... + for ( auto &pair : state.shifters ) { + auto &shifter = *pair.second.get(); + if (!shifter.SyncLocks()) + continue; + auto &track = shifter.GetTrack(); + auto &intervals = shifter.MovingIntervals(); + for (auto &interval : intervals) { + + // ...and tell all other tracks to select that interval... + for ( auto &pair2 : state.shifters ) { + auto &shifter2 = *pair2.second.get(); + if (&shifter2.GetTrack() == &track) + continue; + auto size = shifter2.MovingIntervals().size(); + shifter2.SelectInterval( interval ); + change = change || + (shifter2.SyncLocks() && + size != shifter2.MovingIntervals().size()); + } + + } + } + + // ... and repeat if any other interval became unfixed in a + // shifter that propagates + } + } } void TimeShiftHandle::DoSlideHorizontal @@ -542,9 +628,9 @@ UIHandle::Result TimeShiftHandle::Click pShifter = MakeTrackShifter::Call( *track ); } - CreateListOfCapturedClips( + Init( mClipMoveState, viewInfo, *pTrack, trackList, - ProjectSettings::Get( *pProject ).IsSyncLocked(), clickTime ); + ProjectSettings::Get( *pProject ).IsSyncLocked(), clickTime, capturedAClip ); } mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive; diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 7aa942996..20bd90b38 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -58,7 +58,17 @@ public: //! Change all intervals from fixed to moving void UnfixAll(); + //! Notifies the shifter that a region is selected, so it may update its fixed and moving intervals + /*! Default behavior: if any part of the track is selected, unfix all parts of it. */ + virtual void SelectInterval( const TrackInterval &interval ); + + //! Whether unfixing of an interval should propagate to all overlapping intervals in the sync lock group + virtual bool SyncLocks() = 0; + protected: + /*! Unfix any of the intervals that intersect the given one; may be useful to override `SelectInterval()` */ + void CommonSelectInterval( const TrackInterval &interval ); + //! Derived class constructor can initialize all intervals reported by the track as fixed, none moving /*! This can't be called by the base class constructor, when GetTrack() isn't yet callable */ void InitIntervals(); @@ -76,6 +86,9 @@ public: HitTestResult HitTest( double ) override; + //! Returns false + bool SyncLocks() override; + private: std::shared_ptr mpTrack; }; @@ -150,9 +163,10 @@ public: std::shared_ptr GetTrack() const { return mCapturedTrack; } // A utility function also used by menu commands - static void CreateListOfCapturedClips + static void Init ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, - TrackList &trackList, bool syncLocked, double clickTime ); + TrackList &trackList, bool syncLocked, double clickTime, + bool capturedAClip = true ); // A utility function also used by menu commands static void DoSlideHorizontal From 00f761eb5e64274a54bfcadce7fc6bb65544d08b Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Wed, 9 Sep 2020 20:27:26 -0400 Subject: [PATCH 5/9] TrackShifters responsible for snapping; remove a TrackList::Visit... ... Also dependency of TimeShiftHandle on LabelTrack, lately added, is removed --- src/menus/ClipMenus.cpp | 1 - src/tracks/ui/TimeShiftHandle.cpp | 68 ++++++------------------------- src/tracks/ui/TimeShiftHandle.h | 2 - 3 files changed, 13 insertions(+), 58 deletions(-) diff --git a/src/menus/ClipMenus.cpp b/src/menus/ClipMenus.cpp index b37d30cb5..733d886b2 100644 --- a/src/menus/ClipMenus.cpp +++ b/src/menus/ClipMenus.cpp @@ -652,7 +652,6 @@ double DoClipMove state.capturedClipIsSelection = track->GetSelected() && !selectedRegion.isPoint(); - state.trackExclusions.clear(); TimeShiftHandle::Init( state, viewInfo, *track, trackList, syncLocked, t0 ); diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 610d0f556..4ab1e987a 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -121,11 +121,9 @@ namespace void AddClipsToCaptured ( ClipMoveState &state, Track *t, double t0, double t1 ) { - bool exclude = true; // to exclude a whole track. auto &clips = state.capturedClipArray; t->TypeSwitch( [&](WaveTrack *wt) { - exclude = false; for(const auto &clip: wt->GetClips()) if ( ! clip->IsClipStartAfterClip(t0) && ! clip->BeforeClip(t1) && // Avoid getting clips that were already captured @@ -154,8 +152,6 @@ namespace } } ); - if (exclude) - state.trackExclusions.push_back(t); } // Helper for the above, adds a track's clips to capturedClipArray (eliminates @@ -492,60 +488,23 @@ void TimeShiftHandle::DoSlideHorizontal DoOffset( state, &capturedTrack, state.hSlideAmount ); } -#include "LabelTrack.h" namespace { SnapPointArray FindCandidates( - const TrackList &tracks, - const TrackClipArray &clipExclusions, const TrackArray &trackExclusions ) + const TrackList &tracks, const ClipMoveState::ShifterMap &shifters ) { - // Special case restricted candidates for time shift + // Compare with the other function FindCandidates in Snap + // Make the snap manager more selective than it would be if just constructed + // from the track list SnapPointArray candidates; - auto trackRange = - tracks.Any() - - [&](const Track *pTrack){ - return - make_iterator_range( trackExclusions ).contains( pTrack ); - }; - trackRange.Visit( - [&](const LabelTrack *labelTrack) { - for (const auto &label : labelTrack->GetLabels()) - { - const double t0 = label.getT0(); - const double t1 = label.getT1(); - candidates.emplace_back(t0, labelTrack); - if (t1 != t0) - candidates.emplace_back(t1, labelTrack); - } - }, - [&](const WaveTrack *waveTrack) { - for (const auto &clip: waveTrack->GetClips()) - { - bool skip = false; - for (const auto &exclusion : clipExclusions) - { - if (exclusion.track == waveTrack && - exclusion.clip == clip.get()) - { - skip = true; - break; - } - } - - if (skip) - continue; - - candidates.emplace_back(clip->GetStartTime(), waveTrack); - candidates.emplace_back(clip->GetEndTime(), waveTrack); - } + for ( const auto &pair : shifters ) { + auto &shifter = pair.second; + auto &track = shifter->GetTrack(); + for (const auto &interval : shifter->FixedIntervals() ) { + candidates.emplace_back( interval.Start(), &track ); + if ( interval.Start() != interval.End() ) + candidates.emplace_back( interval.End(), &track ); } -#ifdef USE_MIDI - , - [&](const NoteTrack *track) { - candidates.emplace_back(track->GetStartTime(), track); - candidates.emplace_back(track->GetEndTime(), track); - } -#endif - ); + } return candidates; } } @@ -638,8 +597,7 @@ UIHandle::Result TimeShiftHandle::Click mClipMoveState.mMouseClickX = event.m_x; mSnapManager = std::make_shared(*trackList.GetOwner(), - FindCandidates( trackList, - mClipMoveState.capturedClipArray, mClipMoveState.trackExclusions), + FindCandidates( trackList, mClipMoveState.shifters ), viewInfo, true, // don't snap to time kPixelTolerance); diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 20bd90b38..5f0fdd220 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -126,7 +126,6 @@ struct ClipMoveState { WaveClip *capturedClip {}; bool capturedClipIsSelection {}; - TrackArray trackExclusions {}; double hSlideAmount {}; ShifterMap shifters; TrackClipArray capturedClipArray {}; @@ -138,7 +137,6 @@ struct ClipMoveState { { capturedClip = nullptr; capturedClipIsSelection = false; - trackExclusions.clear(); hSlideAmount = 0; shifters.clear(); capturedClipArray.clear(); From 329221b3925ae0b1d6ee77a9e2fe81711560cb38 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 12 Sep 2020 12:49:02 -0400 Subject: [PATCH 6/9] Lower more into Init(); ClipMenus uses ClipMoveState opaquely --- src/menus/ClipMenus.cpp | 42 +++++++----- src/tracks/ui/TimeShiftHandle.cpp | 108 +++++++++++++++++------------- src/tracks/ui/TimeShiftHandle.h | 27 ++++---- 3 files changed, 103 insertions(+), 74 deletions(-) diff --git a/src/menus/ClipMenus.cpp b/src/menus/ClipMenus.cpp index 733d886b2..63d9d5d09 100644 --- a/src/menus/ClipMenus.cpp +++ b/src/menus/ClipMenus.cpp @@ -639,22 +639,25 @@ double DoClipMove auto t0 = selectedRegion.t0(); + std::unique_ptr uShifter; + // Find the first channel that has a clip at time t0 for (auto channel : TrackList::Channels(wt) ) { - if( nullptr != (state.capturedClip = channel->GetClipAtTime( t0 )) ) { + uShifter = MakeTrackShifter::Call( *wt ); + if( uShifter->HitTest( t0 ) == TrackShifter::HitTestResult::Miss ) + uShifter.reset(); + else { wt = channel; break; } } - if (state.capturedClip == nullptr) + if (!uShifter) return 0.0; + auto pShifter = uShifter.get(); - state.capturedClipIsSelection = - track->GetSelected() && !selectedRegion.isPoint(); - - TimeShiftHandle::Init( - state, viewInfo, *track, trackList, syncLocked, t0 ); + state.Init( *track, std::move( uShifter ), + t0, viewInfo, trackList, syncLocked ); auto desiredT0 = viewInfo.OffsetTimeByPixels( t0, ( right ? 1 : -1 ) ); auto desiredSlideAmount = desiredT0 - t0; @@ -668,21 +671,26 @@ double DoClipMove if (!right) desiredSlideAmount *= -1; - state.hSlideAmount = desiredSlideAmount; - TimeShiftHandle::DoSlideHorizontal( state, trackList, *track ); + auto hSlideAmount = state.DoSlideHorizontal( + desiredSlideAmount, 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 = selectedRegion.duration(); - selectedRegion.setTimes(newT0, newT0 + diff); + double newT0 = t0 + hSlideAmount; + // pShifter is still undestroyed in the ClipMoveState + auto &intervals = pShifter->MovingIntervals(); + if ( !intervals.empty() ) { + auto &interval = intervals[0]; + if (newT0 < interval.Start()) + newT0 = interval.Start(); + if (newT0 > interval.End()) + newT0 = interval.End(); + double diff = selectedRegion.duration(); + selectedRegion.setTimes(newT0, newT0 + diff); + } - return state.hSlideAmount; + return hSlideAmount; } ); return 0.0; } diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 4ab1e987a..7ea7e3ee6 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -304,19 +304,57 @@ template<> auto MakeTrackShifter::Implementation() -> Function { }; } -void TimeShiftHandle::Init - ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, - TrackList &trackList, bool syncLocked, double clickTime, - bool capturedAClip ) +void ClipMoveState::Init( + Track &capturedTrack, + std::unique_ptr pHit, + double clickTime, + const ViewInfo &viewInfo, + TrackList &trackList, bool syncLocked ) { + capturedClipArray.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; + + state.movingSelection = capturedTrack.IsSelected() && + clickTime >= viewInfo.selectedRegion.t0() && + clickTime < viewInfo.selectedRegion.t1(); + + if (!pHit) + return; + + const bool capturedAClip = + 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(interval.Extra()); + if ( pInfo ) + state.capturedClip = pInfo->GetClip().get(); + } + + state.shifters[&capturedTrack] = std::move( pHit ); + + // Collect TrackShifters for the rest of the tracks + for ( auto track : trackList.Any() ) { + auto &pShifter = state.shifters[track]; + if (!pShifter) + 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... - state.capturedClipArray.clear(); - // First, if click was in selection, capture selected clips; otherwise // just the clicked-on clip - if ( state.capturedClipIsSelection ) + if ( state.movingSelection ) // All selected tracks may move some intervals for (auto t : trackList.Selected()) AddClipsToCaptured( state, viewInfo, t ); @@ -372,7 +410,7 @@ void TimeShiftHandle::Init // Analogy of the steps above, but with TrackShifters, follows below - if ( state.capturedClipIsSelection ) { + if ( state.movingSelection ) { // All selected tracks may move some intervals const TrackInterval interval{ viewInfo.selectedRegion.t0(), @@ -435,9 +473,12 @@ void TimeShiftHandle::Init } } -void TimeShiftHandle::DoSlideHorizontal - ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack ) +double ClipMoveState::DoSlideHorizontal( + double desiredSlideAmount, TrackList &trackList, Track &capturedTrack ) { + auto &state = *this; + 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. @@ -486,6 +527,8 @@ void TimeShiftHandle::DoSlideHorizontal // For Shift key down, or // For non wavetracks, specifically label tracks ... DoOffset( state, &capturedTrack, state.hSlideAmount ); + + return state.hSlideAmount; } namespace { @@ -536,16 +579,11 @@ UIHandle::Result TimeShiftHandle::Click const double clickTime = viewInfo.PositionToTime(event.m_x, rect.x); - mClipMoveState.capturedClipIsSelection = - (pTrack->GetSelected() && - clickTime >= viewInfo.selectedRegion.t0() && - clickTime < viewInfo.selectedRegion.t1()); mClipMoveState.capturedClip = NULL; mClipMoveState.capturedClipArray.clear(); bool captureClips = false; - bool capturedAClip = false; auto pShifter = MakeTrackShifter::Call( *pTrack ); @@ -555,17 +593,6 @@ UIHandle::Result TimeShiftHandle::Click return Cancelled; case TrackShifter::HitTestResult::Intervals: { captureClips = true; - if ( !pShifter->MovingIntervals().empty() ) { - capturedAClip = true; - - // There is still some code special to WaveTracks here that - // needs to go elsewhere - auto &interval = pShifter->MovingIntervals()[0]; - auto pInfo = - dynamic_cast(interval.Extra()); - if ( pInfo ) - mClipMoveState.capturedClip = pInfo->GetClip().get(); - } break; } case TrackShifter::HitTestResult::Track: @@ -577,20 +604,12 @@ UIHandle::Result TimeShiftHandle::Click // As in the default above: just do shifting of one whole track } - if ( captureClips ) { - mClipMoveState.shifters[pTrack] = std::move( pShifter ); + mClipMoveState.Init( *pTrack, + captureClips ? std::move( pShifter ) : nullptr, + clickTime, - // Collect TrackShifters for the rest of the tracks - for ( auto track : trackList.Any() ) { - auto &pShifter = mClipMoveState.shifters[track]; - if (!pShifter) - pShifter = MakeTrackShifter::Call( *track ); - } - - Init( - mClipMoveState, viewInfo, *pTrack, trackList, - ProjectSettings::Get( *pProject ).IsSyncLocked(), clickTime, capturedAClip ); - } + viewInfo, trackList, + ProjectSettings::Get( *pProject ).IsSyncLocked() ); mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive; mRect = rect; @@ -836,7 +855,7 @@ bool TimeShiftHandle::DoSlideVertical // Make the offset permanent; start from a "clean slate" if( ok ) { state.mMouseClickX = xx; - if (state.capturedClipIsSelection) { + if (state.movingSelection) { // Slide the selection, too viewInfo.selectedRegion.move( slide ); } @@ -897,7 +916,7 @@ UIHandle::Result TimeShiftHandle::Drag DoOffset( mClipMoveState, mCapturedTrack.get(), -mClipMoveState.hSlideAmount ); - if ( mClipMoveState.capturedClipIsSelection ) { + if ( mClipMoveState.movingSelection ) { // Slide the selection, too viewInfo.selectedRegion.move( -mClipMoveState.hSlideAmount ); } @@ -939,11 +958,10 @@ UIHandle::Result TimeShiftHandle::Drag if (desiredSlideAmount == 0.0) return RefreshAll; - mClipMoveState.hSlideAmount = desiredSlideAmount; + mClipMoveState.DoSlideHorizontal( + desiredSlideAmount, trackList, *mCapturedTrack ); - DoSlideHorizontal( mClipMoveState, trackList, *mCapturedTrack ); - - if (mClipMoveState.capturedClipIsSelection) { + if (mClipMoveState.movingSelection) { // Slide the selection, too viewInfo.selectedRegion.move( mClipMoveState.hSlideAmount ); } diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 5f0fdd220..f1106ab23 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -122,10 +122,23 @@ using TrackClipArray = std::vector ; struct ClipMoveState { using ShifterMap = std::unordered_map>; + //! Will associate a TrackShifter with each track in the list + void Init( + Track &capturedTrack, // pHit, /*!< + If null, only capturedTrack (with any sister channels) shifts, as a whole */ + double clickTime, + const ViewInfo &viewInfo, + TrackList &trackList, bool syncLocked ); + + /*! @return actual slide amount, maybe adjusted toward zero from desired */ + double DoSlideHorizontal( + double desiredSlideAmount, TrackList &trackList, Track &capturedTrack ); + // non-NULL only if click was in a WaveTrack and without Shift key: WaveClip *capturedClip {}; - bool capturedClipIsSelection {}; + bool movingSelection {}; double hSlideAmount {}; ShifterMap shifters; TrackClipArray capturedClipArray {}; @@ -136,7 +149,7 @@ struct ClipMoveState { void clear() { capturedClip = nullptr; - capturedClipIsSelection = false; + movingSelection = false; hSlideAmount = 0; shifters.clear(); capturedClipArray.clear(); @@ -160,16 +173,6 @@ public: bool IsGripHit() const { return mGripHit; } std::shared_ptr GetTrack() const { return mCapturedTrack; } - // A utility function also used by menu commands - static void Init - ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, - TrackList &trackList, bool syncLocked, double clickTime, - bool capturedAClip = true ); - - // A utility function also used by menu commands - 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. From bc7f527a3bf1a520e374dd7f065174e276f5232a Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sat, 12 Sep 2020 15:48:53 -0400 Subject: [PATCH 7/9] Lower mCapturedTrack into ClipMoveState, simplify one member function --- src/menus/ClipMenus.cpp | 4 ++-- src/tracks/ui/TimeShiftHandle.cpp | 25 +++++++++++++------------ src/tracks/ui/TimeShiftHandle.h | 8 ++++---- 3 files changed, 19 insertions(+), 18 deletions(-) diff --git a/src/menus/ClipMenus.cpp b/src/menus/ClipMenus.cpp index 63d9d5d09..15b11207e 100644 --- a/src/menus/ClipMenus.cpp +++ b/src/menus/ClipMenus.cpp @@ -671,8 +671,8 @@ double DoClipMove if (!right) desiredSlideAmount *= -1; - auto hSlideAmount = state.DoSlideHorizontal( - desiredSlideAmount, trackList, *track ); + auto hSlideAmount = + state.DoSlideHorizontal( desiredSlideAmount, trackList ); // 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/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 7ea7e3ee6..352456afe 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -45,9 +45,9 @@ TrackClip::~TrackClip() TimeShiftHandle::TimeShiftHandle ( const std::shared_ptr &pTrack, bool gripHit ) - : mCapturedTrack{ pTrack } - , mGripHit{ gripHit } + : mGripHit{ gripHit } { + mClipMoveState.mCapturedTrack = pTrack; } void TimeShiftHandle::Enter(bool, AudacityProject *) @@ -320,6 +320,7 @@ void ClipMoveState::Init( }); auto &state = *this; + state.mCapturedTrack = capturedTrack.SharedPointer(); state.movingSelection = capturedTrack.IsSelected() && clickTime >= viewInfo.selectedRegion.t0() && @@ -474,9 +475,10 @@ void ClipMoveState::Init( } double ClipMoveState::DoSlideHorizontal( - double desiredSlideAmount, TrackList &trackList, Track &capturedTrack ) + double desiredSlideAmount, TrackList &trackList ) { auto &state = *this; + auto &capturedTrack = *state.mCapturedTrack; state.hSlideAmount = desiredSlideAmount; // Given a signed slide distance, move clips, but subject to constraint of @@ -896,7 +898,7 @@ UIHandle::Result TimeShiftHandle::Drag // within the bounds of the tracks area. if (event.m_x >= mRect.GetX() && event.m_x < mRect.GetX() + mRect.GetWidth()) - track = mCapturedTrack.get(); + track = mClipMoveState.mCapturedTrack.get(); } // May need a shared_ptr to reassign mCapturedTrack below @@ -907,14 +909,14 @@ UIHandle::Result TimeShiftHandle::Drag auto &trackList = TrackList::Get( *pProject ); - // GM: DoSlide now implementing snap-to + // GM: slide now implementing snap-to // samples functionality based on sample rate. // Start by undoing the current slide amount; everything // happens relative to the original horizontal position of // each clip... DoOffset( - mClipMoveState, mCapturedTrack.get(), -mClipMoveState.hSlideAmount ); + mClipMoveState, mClipMoveState.mCapturedTrack.get(), -mClipMoveState.hSlideAmount ); if ( mClipMoveState.movingSelection ) { // Slide the selection, too @@ -925,7 +927,7 @@ UIHandle::Result TimeShiftHandle::Drag double desiredSlideAmount = FindDesiredSlideAmount( viewInfo, mRect.x, event, mSnapManager.get(), mSlideUpDownOnly, mSnapPreferRightEdge, mClipMoveState, - *mCapturedTrack, *pTrack ); + *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. @@ -935,12 +937,12 @@ UIHandle::Result TimeShiftHandle::Drag // decide which tracks the captured clips should go to. bool fail = ( mClipMoveState.capturedClip && - pTrack != mCapturedTrack + pTrack != mClipMoveState.mCapturedTrack /* && !mCapturedClipIsSelection*/ && pTrack->TypeSwitch( [&] (WaveTrack *) { if ( DoSlideVertical( viewInfo, event.m_x, mClipMoveState, - trackList, *mCapturedTrack, *pTrack, desiredSlideAmount ) ) { - mCapturedTrack = pTrack; + trackList, *mClipMoveState.mCapturedTrack, *pTrack, desiredSlideAmount ) ) { + mClipMoveState.mCapturedTrack = pTrack; mDidSlideVertically = true; } else @@ -958,8 +960,7 @@ UIHandle::Result TimeShiftHandle::Drag if (desiredSlideAmount == 0.0) return RefreshAll; - mClipMoveState.DoSlideHorizontal( - desiredSlideAmount, trackList, *mCapturedTrack ); + mClipMoveState.DoSlideHorizontal( desiredSlideAmount, trackList ); if (mClipMoveState.movingSelection) { // Slide the selection, too diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index f1106ab23..b68911ba4 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -132,8 +132,9 @@ struct ClipMoveState { TrackList &trackList, bool syncLocked ); /*! @return actual slide amount, maybe adjusted toward zero from desired */ - double DoSlideHorizontal( - double desiredSlideAmount, TrackList &trackList, Track &capturedTrack ); + double DoSlideHorizontal( double desiredSlideAmount, TrackList &trackList ); + + std::shared_ptr mCapturedTrack; // non-NULL only if click was in a WaveTrack and without Shift key: WaveClip *capturedClip {}; @@ -171,7 +172,7 @@ public: TimeShiftHandle &operator=(TimeShiftHandle&&) = default; bool IsGripHit() const { return mGripHit; } - std::shared_ptr GetTrack() const { return mCapturedTrack; } + std::shared_ptr GetTrack() const = delete; // Try to move clips from one WaveTrack to another, before also moving // by some horizontal amount, which may be slightly adjusted to fit the @@ -221,7 +222,6 @@ private: TrackPanelDrawingContext &, const wxRect &rect, const wxRect &panelRect, unsigned iPass ) override; - std::shared_ptr mCapturedTrack; wxRect mRect{}; bool mDidSlideVertically{}; From 4f159a7629c668307ecff2547401a910a4562163 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Sun, 13 Sep 2020 22:59:24 -0400 Subject: [PATCH 8/9] New method of TrackShifter moves a calculation out of ClipMenus --- src/menus/ClipMenus.cpp | 15 ++------------- .../playabletrack/wavetrack/ui/WaveTrackView.cpp | 14 ++++++++++++++ src/tracks/ui/TimeShiftHandle.cpp | 7 +++++++ src/tracks/ui/TimeShiftHandle.h | 10 ++++++++++ 4 files changed, 33 insertions(+), 13 deletions(-) diff --git a/src/menus/ClipMenus.cpp b/src/menus/ClipMenus.cpp index 15b11207e..0ca52119e 100644 --- a/src/menus/ClipMenus.cpp +++ b/src/menus/ClipMenus.cpp @@ -646,10 +646,8 @@ double DoClipMove uShifter = MakeTrackShifter::Call( *wt ); if( uShifter->HitTest( t0 ) == TrackShifter::HitTestResult::Miss ) uShifter.reset(); - else { - wt = channel; + else break; - } } if (!uShifter) @@ -660,16 +658,7 @@ double DoClipMove t0, viewInfo, trackList, syncLocked ); 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; + auto desiredSlideAmount = pShifter->HintOffsetLarger( desiredT0 - t0 ); auto hSlideAmount = state.DoSlideHorizontal( desiredSlideAmount, trackList ); diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp index 059a3124b..80de1288f 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackView.cpp @@ -1344,6 +1344,20 @@ public: bool SyncLocks() override { return true; } + double HintOffsetLarger(double desiredOffset) override + { + // set it to a sample point, and minimum of 1 sample point + bool positive = (desiredOffset > 0); + if (!positive) + desiredOffset *= -1; + double nSamples = rint(mpTrack->GetRate() * desiredOffset); + nSamples = std::max(nSamples, 1.0); + desiredOffset = nSamples / mpTrack->GetRate(); + if (!positive) + desiredOffset *= -1; + return desiredOffset; + } + private: std::shared_ptr mpTrack; }; diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 352456afe..974cf20b1 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -274,6 +274,11 @@ void TrackShifter::CommonSelectInterval(const TrackInterval &interval) }); } +double TrackShifter::HintOffsetLarger(double desiredOffset) +{ + return desiredOffset; +} + void TrackShifter::InitIntervals() { mMoving.clear(); @@ -960,6 +965,8 @@ UIHandle::Result TimeShiftHandle::Drag if (desiredSlideAmount == 0.0) return RefreshAll; + // Note that mouse dragging doesn't use TrackShifter::HintOffsetLarger() + mClipMoveState.DoSlideHorizontal( desiredSlideAmount, trackList ); if (mClipMoveState.movingSelection) { diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index b68911ba4..6e5e3309a 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -65,6 +65,16 @@ public: //! Whether unfixing of an interval should propagate to all overlapping intervals in the sync lock group virtual bool SyncLocks() = 0; + //! Given amount to shift by horizontally, maybe adjust it from zero to suggest minimum distance + /*! + Any interval placement constraints, not necessarily met at the suggested offset + 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 HintOffsetLarger( double desiredOffset ); + protected: /*! Unfix any of the intervals that intersect the given one; may be useful to override `SelectInterval()` */ void CommonSelectInterval( const TrackInterval &interval ); From 5591e1da0afc5df5bba582ecf955518304b3ede1 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Mon, 14 Sep 2020 05:33:40 -0400 Subject: [PATCH 9/9] Eliminate some uses of ClipMoveState::capturedClip --- src/tracks/ui/TimeShiftHandle.cpp | 34 +++++++++++++++++++++++-------- src/tracks/ui/TimeShiftHandle.h | 4 ++++ 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp index 974cf20b1..946860760 100644 --- a/src/tracks/ui/TimeShiftHandle.cpp +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -479,6 +479,23 @@ void ClipMoveState::Init( } } +const TrackInterval *ClipMoveState::CapturedInterval() const +{ + auto pTrack = mCapturedTrack.get(); + if ( pTrack ) { + auto iter = shifters.find( pTrack ); + if ( iter != shifters.end() ) { + auto &pShifter = iter->second; + if ( pShifter ) { + auto &intervals = pShifter->MovingIntervals(); + if ( !intervals.empty() ) + return &intervals[0]; + } + } + } + return nullptr; +} + double ClipMoveState::DoSlideHorizontal( double desiredSlideAmount, TrackList &trackList ) { @@ -629,10 +646,10 @@ UIHandle::Result TimeShiftHandle::Click kPixelTolerance); mClipMoveState.snapLeft = -1; mClipMoveState.snapRight = -1; - mSnapPreferRightEdge = - mClipMoveState.capturedClip && - (fabs(clickTime - mClipMoveState.capturedClip->GetEndTime()) < - fabs(clickTime - mClipMoveState.capturedClip->GetStartTime())); + auto pInterval = mClipMoveState.CapturedInterval(); + mSnapPreferRightEdge = pInterval && + (fabs(clickTime - pInterval->End()) < + fabs(clickTime - pInterval->Start())); return RefreshNone; } @@ -661,11 +678,10 @@ namespace { // Adjust desiredSlideAmount using SnapManager if (pSnapManager) { - if (state.capturedClip) { - clipLeft = state.capturedClip->GetStartTime() - + desiredSlideAmount; - clipRight = state.capturedClip->GetEndTime() - + desiredSlideAmount; + auto pInterval = state.CapturedInterval(); + if (pInterval) { + clipLeft = pInterval->Start() + desiredSlideAmount; + clipRight = pInterval->End() + desiredSlideAmount; } else { clipLeft = capturedTrack.GetStartTime() + desiredSlideAmount; diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h index 6e5e3309a..a353daa2f 100644 --- a/src/tracks/ui/TimeShiftHandle.h +++ b/src/tracks/ui/TimeShiftHandle.h @@ -141,6 +141,10 @@ struct ClipMoveState { const ViewInfo &viewInfo, TrackList &trackList, bool syncLocked ); + //! Return pointer to the first fixed interval of the captured track, if there is one + /*! Pointer may be invalidated by operations on the associated TrackShifter */ + const TrackInterval *CapturedInterval() const; + /*! @return actual slide amount, maybe adjusted toward zero from desired */ double DoSlideHorizontal( double desiredSlideAmount, TrackList &trackList );