diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index 62d132ff2..6556c544c 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -1205,6 +1205,7 @@ 28FE4A080ABF4E960056F5C4 /* mmx_optimized.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28FE4A060ABF4E960056F5C4 /* mmx_optimized.cpp */; }; 28FE4A090ABF4E960056F5C4 /* sse_optimized.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28FE4A070ABF4E960056F5C4 /* sse_optimized.cpp */; }; 5E000A211EC7B5D500E8FD93 /* SampleHandle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E000A1F1EC7B5D500E8FD93 /* SampleHandle.cpp */; }; + 5E7396441DAFD8C600BA0A4D /* TimeShiftHandle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E7396421DAFD8C600BA0A4D /* TimeShiftHandle.cpp */; }; 5E02BFF21D1164DF00EB7578 /* Distortion.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E02BFF01D1164DF00EB7578 /* Distortion.cpp */; }; 5E07842E1DEE6B8600CA76EA /* FileException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E07842C1DEE6B8600CA76EA /* FileException.cpp */; }; 5E0784311DF1E4F400CA76EA /* UserException.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E07842F1DF1E4F400CA76EA /* UserException.cpp */; }; @@ -3004,6 +3005,8 @@ 28FEC1B21A12B6FB00FACE48 /* EffectAutomationParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EffectAutomationParameters.h; path = ../include/audacity/EffectAutomationParameters.h; sourceTree = SOURCE_ROOT; }; 5E000A1F1EC7B5D500E8FD93 /* SampleHandle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = SampleHandle.cpp; sourceTree = ""; }; 5E000A201EC7B5D500E8FD93 /* SampleHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SampleHandle.h; sourceTree = ""; }; + 5E7396421DAFD8C600BA0A4D /* TimeShiftHandle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TimeShiftHandle.cpp; sourceTree = ""; }; + 5E7396431DAFD8C600BA0A4D /* TimeShiftHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TimeShiftHandle.h; sourceTree = ""; }; 5E02BFF01D1164DF00EB7578 /* Distortion.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Distortion.cpp; sourceTree = ""; }; 5E02BFF11D1164DF00EB7578 /* Distortion.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Distortion.h; sourceTree = ""; }; 5E07842C1DEE6B8600CA76EA /* FileException.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = FileException.cpp; sourceTree = ""; }; @@ -5732,6 +5735,7 @@ 5E74D2DD1CC4429700D88B0B /* EditCursorOverlay.cpp */, 5E74D2DF1CC4429700D88B0B /* PlayIndicatorOverlay.cpp */, 5E74D2E11CC4429700D88B0B /* Scrubbing.cpp */, + 5E7396421DAFD8C600BA0A4D /* TimeShiftHandle.cpp */, 5E1512681DB0010C00702E29 /* TrackControls.cpp */, 5E15126A1DB0010C00702E29 /* TrackUI.cpp */, 5E15126B1DB0010C00702E29 /* TrackVRulerControls.cpp */, @@ -5740,6 +5744,7 @@ 5E74D2DE1CC4429700D88B0B /* EditCursorOverlay.h */, 5E74D2E01CC4429700D88B0B /* PlayIndicatorOverlay.h */, 5E74D2E21CC4429700D88B0B /* Scrubbing.h */, + 5E7396431DAFD8C600BA0A4D /* TimeShiftHandle.h */, 5E1512691DB0010C00702E29 /* TrackControls.h */, 5E15126C1DB0010C00702E29 /* TrackVRulerControls.h */, 5E73963D1DAFD86000BA0A4D /* ZoomHandle.h */, @@ -7844,6 +7849,7 @@ EDAD326E1544452E009C6220 /* sv.po in Sources */, ED87F50A1986424100AC520B /* ta.po in Sources */, 2888496E131B6CF600B59735 /* tg.po in Sources */, + 5E7396441DAFD8C600BA0A4D /* TimeShiftHandle.cpp in Sources */, 2888496F131B6CF600B59735 /* tr.po in Sources */, 28884970131B6CF600B59735 /* uk.po in Sources */, 28884971131B6CF600B59735 /* vi.po in Sources */, diff --git a/src/LabelTrack.cpp b/src/LabelTrack.cpp index bf63eba8c..163f516e2 100644 --- a/src/LabelTrack.cpp +++ b/src/LabelTrack.cpp @@ -60,6 +60,7 @@ for drawing different aspects of the label and its text box. #include "AColor.h" #include "Project.h" #include "TrackArtist.h" +#include "Snap.h" #include "TrackPanel.h" #include "UndoManager.h" #include "commands/CommandManager.h" diff --git a/src/Makefile.am b/src/Makefile.am index 65a86d4f2..83f179db1 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -579,6 +579,8 @@ audacity_SOURCES = \ tracks/ui/PlayIndicatorOverlay.h \ tracks/ui/Scrubbing.cpp \ tracks/ui/Scrubbing.h \ + tracks/ui/TimeShiftHandle.cpp \ + tracks/ui/TimeShiftHandle.h \ tracks/ui/TrackControls.cpp \ tracks/ui/TrackControls.h \ tracks/ui/TrackUI.cpp \ diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 67d9e330e..5b250164b 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -461,7 +461,6 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id, } mMouseCapture = IsUncaptured; - mSlideUpDownOnly = false; mLabelTrackStartXPos=-1; @@ -470,7 +469,6 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id, mSelectCursor = MakeCursor( wxCURSOR_IBEAM, IBeamCursorXpm, 17, 16); mEnvelopeCursor= MakeCursor( wxCURSOR_ARROW, EnvCursorXpm, 16, 16); mDisabledCursor= MakeCursor( wxCURSOR_NO_ENTRY, DisabledCursorXpm,16, 16); - mSlideCursor = MakeCursor( wxCURSOR_SIZEWE, TimeCursorXpm, 16, 16); mZoomInCursor = MakeCursor( wxCURSOR_MAGNIFIER, ZoomInCursorXpm, 19, 15); mZoomOutCursor = MakeCursor( wxCURSOR_MAGNIFIER, ZoomOutCursorXpm, 19, 15); @@ -1152,7 +1150,6 @@ void TrackPanel::HandleInterruptedDrag() IsClosing, IsAdjustingLabel, IsRearranging, - IsSliding, IsEnveloping, IsGainSliding, IsPanSliding, @@ -1409,9 +1406,6 @@ bool TrackPanel::SetCursorByActivity( ) case IsSelecting: SetCursor(*mSelectCursor); return true; - case IsSliding: - SetCursor( unsafe ? *mDisabledCursor : *mSlideCursor); - return true; case IsEnveloping: SetCursor( unsafe ? *mDisabledCursor : *mEnvelopeCursor); return true; @@ -1766,9 +1760,6 @@ void TrackPanel::SetCursorAndTipByTool( int tool, case envelopeTool: SetCursor(unsafe ? *mDisabledCursor : *mEnvelopeCursor); break; - case slideTool: - SetCursor(unsafe ? *mDisabledCursor : *mSlideCursor); - break; } // doesn't actually change the tip itself, but it could (should?) do at some // future date. @@ -3312,105 +3303,71 @@ void TrackPanel::ForwardEventToEnvelope(wxMouseEvent & event) } } -void TrackPanel::HandleSlide( 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 (event.LeftDown()) - StartSlide(event); + if (withinSelection) + AddClipsToCaptured( state, t, viewInfo.selectedRegion.t0(), + viewInfo.selectedRegion.t1() ); + else + AddClipsToCaptured( state, t, t->GetStartTime(), t->GetEndTime() ); +} - if (mMouseCapture != IsSliding) - return; - - if (event.Dragging() && mCapturedTrack) - DoSlide(event); - - if (event.LeftUp()) { - - SetCapturedTrack( NULL ); - - mSnapManager.reset(); - - // Do not draw yellow lines - if ( mClipMoveState.snapLeft != -1 || mClipMoveState.snapRight != -1) { - mClipMoveState.snapLeft = mClipMoveState.snapRight = -1; - Refresh(false); - } - - if (!mDidSlideVertically && mClipMoveState.hSlideAmount == 0) - return; - - for (size_t i = 0; i < mClipMoveState.capturedClipArray.size(); i++) +// 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()) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[i]; - WaveClip* pWaveClip = trackClip.clip; - // Note that per TrackPanel::AddClipsToCaptured(Track *t, double t0, double t1), - // in the non-WaveTrack case, the code adds a NULL clip to mCapturedClipArray, - // so we have to check for that any time we're going to deref it. - // Previous code did not check it here, and that caused bug 367 crash. - if (pWaveClip && - trackClip.track != trackClip.origTrack) + if ( ! clip->AfterClip(t0) && ! clip->BeforeClip(t1) ) { - // Now that user has dropped the clip into a different track, - // make sure the sample rate matches the destination track (mCapturedTrack). - // Assume the clip was dropped in a wave track - pWaveClip->Resample - (static_cast(trackClip.track)->GetRate()); - pWaveClip->MarkChanged(); + // 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; } } - MakeParentRedrawScrollbars(); - - wxString msg; - bool consolidate; - if (mDidSlideVertically) { - msg.Printf(_("Moved clips to another track")); - consolidate = false; + 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)); } - else { - wxString direction = mClipMoveState.hSlideAmount > 0 ? - /* i18n-hint: a direction as in left or right.*/ - _("right") : - /* i18n-hint: a direction as in left or right.*/ - _("left"); - /* i18n-hint: %s is a direction like left or right */ - msg.Printf(_("Time shifted tracks/clips %s %.02f seconds"), - direction.c_str(), fabs( mClipMoveState.hSlideAmount )); - consolidate = true; - } - MakeParentPushState(msg, _("Time-Shift"), - consolidate ? (UndoPush::CONSOLIDATE) : (UndoPush::AUTOSAVE)); } } namespace { - // Don't count right channels. - WaveTrack *NthAudioTrack(TrackList &list, int nn) - { - if (nn >= 0) { - TrackListOfKindIterator iter(Track::Wave, &list); - Track *pTrack = iter.First(); - while (pTrack && nn--) - pTrack = iter.Next(true); - return static_cast(pTrack); - } - - return NULL; - } - - // Don't count right channels. - int TrackPosition(TrackList &list, Track *pFindTrack) - { - Track *const partner = pFindTrack->GetLink(); - TrackListOfKindIterator iter(Track::Wave, &list); - int nn = 0; - for (Track *pTrack = iter.First(); pTrack; pTrack = iter.Next(true), ++nn) { - if (pTrack == pFindTrack || - pTrack == partner) - return nn; - } - return -1; - } - WaveClip *FindClipAtTime(WaveTrack *pTrack, double time) { if (pTrack) { @@ -3427,87 +3384,6 @@ namespace { } } -/// Prepare for sliding. -void TrackPanel::StartSlide(wxMouseEvent & event) -{ - mClipMoveState.clear(); - - mDidSlideVertically = false; - - const auto foundCell = FindCell(event.m_x, event.m_y); - auto &vt = foundCell.pTrack; - if (!vt || foundCell.type != CellType::Track) - return; - auto &rect = foundCell.rect; - - ToolsToolBar * ttb = mListener->TP_GetToolsToolBar(); - bool multiToolModeActive = (ttb && ttb->IsDown(multiTool)); - - double clickTime = - mViewInfo->PositionToTime(event.m_x, GetLeftOffset()); - mClipMoveState.capturedClipIsSelection = - (vt->GetSelected() && - clickTime > mViewInfo->selectedRegion.t0() && - clickTime < mViewInfo->selectedRegion.t1()); - - WaveTrack *wt = vt->GetKind() == Track::Wave - ? static_cast(vt) : nullptr; - - if ((wt -#ifdef USE_MIDI - || vt->GetKind() == Track::Note -#endif - ) && !event.ShiftDown()) - { -#ifdef USE_MIDI - if (!wt) - mClipMoveState.capturedClip = NULL; - else -#endif - { - mClipMoveState.capturedClip = wt->GetClipAtX(event.m_x); - if (mClipMoveState.capturedClip == NULL) - return; - } - - mCapturedTrack = vt; - CreateListOfCapturedClips - ( mClipMoveState, *mViewInfo, *mCapturedTrack, *GetTracks(), - GetProject()->IsSyncLocked(), clickTime ); - - } else { - mClipMoveState.capturedClip = NULL; - mClipMoveState.capturedClipArray.clear(); - mCapturedTrack = vt; - } - - mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive; - - mCapturedRect = rect; - - mMouseClickX = event.m_x; - mMouseClickY = event.m_y; - - mSelStartValid = true; - mSelStart = mViewInfo->PositionToTime(event.m_x, rect.x); - - mSnapManager = std::make_unique(GetTracks(), - mViewInfo, - &mClipMoveState.capturedClipArray, - &mClipMoveState.trackExclusions, - true); // don't snap to time - mClipMoveState.snapLeft = -1; - mClipMoveState.snapRight = -1; - mSnapPreferRightEdge = false; - if (mClipMoveState.capturedClip) { - if (fabs(mSelStart - mClipMoveState.capturedClip->GetEndTime()) < - fabs(mSelStart - mClipMoveState.capturedClip->GetStartTime())) - mSnapPreferRightEdge = true; - } - - mMouseCapture = IsSliding; -} - void TrackPanel::CreateListOfCapturedClips ( ClipMoveState &state, const ViewInfo &viewInfo, Track &capturedTrack, TrackList &trackList, bool syncLocked, double clickTime ) @@ -3589,6 +3465,7 @@ void TrackPanel::CreateListOfCapturedClips } } +#if 0 // Helper for the above, adds a track's clips to mCapturedClipArray (eliminates // duplication of this logic) void TrackPanel::AddClipsToCaptured @@ -3652,261 +3529,7 @@ void TrackPanel::AddClipsToCaptured } } } - -/// Slide tracks horizontally, or slide clips horizontally or vertically -/// (e.g. moving clips between tracks). - -// GM: DoSlide now implementing snap-to -// samples functionality based on sample rate. -void TrackPanel::DoSlide(wxMouseEvent & event) -{ - unsigned int i; - - // find which track the mouse is currently in (mouseTrack) - - // this may not be the same as the one we started in... - - const auto foundCell = FindCell(event.m_x, event.m_y); - if (foundCell.type != CellType::Track) - // Allow sliding only if x is - // within the bounds of the tracks area. - return; - - auto mouseTrack = foundCell.pTrack; - if (mouseTrack == nullptr) { - // Allow sliding if the pointer is not over any track. - mouseTrack = mCapturedTrack; - } - - // Start by undoing the current slide amount; everything - // happens relative to the original horizontal position of - // each clip... -#ifdef USE_MIDI - if ( mClipMoveState.capturedClipArray.size() ) -#else - if ( mClipMoveState.capturedClip ) #endif - { - for ( i = 0; i < mClipMoveState.capturedClipArray.size(); ++i ) { - if ( mClipMoveState.capturedClipArray[i].clip ) - mClipMoveState.capturedClipArray[i].clip->Offset - ( -mClipMoveState.hSlideAmount ); - else - mClipMoveState.capturedClipArray[i].track->Offset - ( -mClipMoveState.hSlideAmount ); - } - } - else { - mCapturedTrack->Offset( -mClipMoveState.hSlideAmount ); - Track* link = mCapturedTrack->GetLink(); - if (link) - link->Offset( -mClipMoveState.hSlideAmount ); - } - - if ( mClipMoveState.capturedClipIsSelection ) { - // Slide the selection, too - mViewInfo->selectedRegion.move( -mClipMoveState.hSlideAmount ); - } - mClipMoveState.hSlideAmount = 0.0; - - // Implement sliding within the track(s) - double desiredSlideAmount; - if (mSlideUpDownOnly) { - desiredSlideAmount = 0.0; - } - else { - desiredSlideAmount = - mViewInfo->PositionToTime(event.m_x) - - mViewInfo->PositionToTime(mMouseClickX); - bool trySnap = false; - double clipLeft = 0, clipRight = 0; -#ifdef USE_MIDI - if (mouseTrack->GetKind() == Track::Wave) { - WaveTrack *mtw = (WaveTrack *)mouseTrack; - desiredSlideAmount = rint(mtw->GetRate() * desiredSlideAmount) / - mtw->GetRate(); // set it to a sample point - } - // Adjust desiredSlideAmount using SnapManager - if (mSnapManager && mClipMoveState.capturedClipArray.size()) { - trySnap = true; - if ( mClipMoveState.capturedClip ) { - clipLeft = mClipMoveState.capturedClip->GetStartTime() - + desiredSlideAmount; - clipRight = mClipMoveState.capturedClip->GetEndTime() - + desiredSlideAmount; - } - else { - clipLeft = mCapturedTrack->GetStartTime() + desiredSlideAmount; - clipRight = mCapturedTrack->GetEndTime() + desiredSlideAmount; - } - } -#else - { - trySnap = true; - if (mouseTrack->GetKind() == Track::Wave) { - WaveTrack *mtw = (WaveTrack *)mouseTrack; - desiredSlideAmount = rint(mtw->GetRate() * desiredSlideAmount) / - mtw->GetRate(); // set it to a sample point - } - if (mSnapManager && mClipMoveState.capturedClip) { - clipLeft = mClipMoveState.capturedClip->GetStartTime() + desiredSlideAmount; - clipRight = mClipMoveState.capturedClip->GetEndTime() + desiredSlideAmount; - } - } -#endif - if (trySnap) { - double newClipLeft = clipLeft; - double newClipRight = clipRight; - - bool dummy1, dummy2; - mSnapManager->Snap(mCapturedTrack, clipLeft, false, &newClipLeft, - &dummy1, &dummy2); - mSnapManager->Snap(mCapturedTrack, clipRight, false, &newClipRight, - &dummy1, &dummy2); - - // Only one of them is allowed to snap - if (newClipLeft != clipLeft && newClipRight != clipRight) { - if (mSnapPreferRightEdge) - newClipLeft = clipLeft; - else - newClipRight = clipRight; - } - - // Take whichever one snapped (if any) and compute the NEW desiredSlideAmount - mClipMoveState.snapLeft = -1; - mClipMoveState.snapRight = -1; - if (newClipLeft != clipLeft) { - double difference = (newClipLeft - clipLeft); - desiredSlideAmount += difference; - mClipMoveState.snapLeft = - mViewInfo->TimeToPosition(newClipLeft, GetLeftOffset()); - } - else if (newClipRight != clipRight) { - double difference = (newClipRight - clipRight); - desiredSlideAmount += difference; - mClipMoveState.snapRight = - mViewInfo->TimeToPosition(newClipRight, GetLeftOffset()); - } - } - } - - // Scroll during vertical drag. - // EnsureVisible(mouseTrack); //vvv Gale says this has problems on Linux, per bug 393 thread. Revert for 2.0.2. - bool slidVertically = false; - - // If the mouse is over a track that isn't the captured track, - // decide which tracks the captured clips should go to. - if ( mClipMoveState.capturedClip && mouseTrack != mCapturedTrack /*&& - !mCapturedClipIsSelection*/ ) - { - const int diff = - TrackPosition(*mTracks, mouseTrack) - - TrackPosition(*mTracks, mCapturedTrack); - for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); ii < nn; ++ii ) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - if (trackClip.clip) { - // Move all clips up or down by an equal count of audio tracks. - Track *const pSrcTrack = trackClip.track; - auto pDstTrack = NthAudioTrack(*mTracks, - diff + TrackPosition(*mTracks, pSrcTrack)); - // Can only move mono to mono, or left to left, or right to right - // And that must be so for each captured clip - bool stereo = (pSrcTrack->GetLink() != 0); - if (pDstTrack && stereo && !pSrcTrack->GetLinked()) - // Assume linked track is wave or null - pDstTrack = static_cast(pDstTrack->GetLink()); - bool ok = pDstTrack && - (stereo == (pDstTrack->GetLink() != 0)) && - (!stereo || (pSrcTrack->GetLinked() == pDstTrack->GetLinked())); - if (ok) - trackClip.dstTrack = pDstTrack; - else - return; - } - } - - // Having passed that test, remove clips temporarily from their - // tracks, so moving clips don't interfere with each other - // when we call CanInsertClip() - for ( unsigned ii = 0, - nn = mClipMoveState.capturedClipArray.size(); ii < nn; ++ii ) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip) - trackClip.holder = - // Assume track is wave because it has a clip - static_cast(trackClip.track)-> - RemoveAndReturnClip(pSrcClip); - } - - // Now check that the move is possible - bool ok = true; - for ( unsigned ii = 0, - nn = mClipMoveState.capturedClipArray.size(); ok && ii < nn; ++ii) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip) - ok = trackClip.dstTrack->CanInsertClip(pSrcClip); - } - - if (!ok) { - // Failure -- put clips back where they were - for ( unsigned ii = 0, - nn = mClipMoveState.capturedClipArray.size(); ii < nn; ++ii) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip) - // Assume track is wave because it has a clip - static_cast(trackClip.track)-> - AddClip(std::move(trackClip.holder)); - } - return; - } - else { - // Do the vertical moves of clips - for ( unsigned ii = 0, - nn = mClipMoveState.capturedClipArray.size(); ii < nn; ++ii) { - TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; - WaveClip *const pSrcClip = trackClip.clip; - if (pSrcClip) { - const auto dstTrack = trackClip.dstTrack; - dstTrack->AddClip(std::move(trackClip.holder)); - trackClip.track = dstTrack; - } - } - - mCapturedTrack = mouseTrack; - mDidSlideVertically = true; - - // Make the offset permanent; start from a "clean slate" - mMouseClickX = event.m_x; - } - - // Not done yet, check for horizontal movement. - slidVertically = true; - } - - if (desiredSlideAmount == 0.0) { - Refresh(false); - return; - } - - mClipMoveState.hSlideAmount = desiredSlideAmount; - - DoSlideHorizontal( mClipMoveState, *GetTracks(), *mCapturedTrack ); - - - if ( mClipMoveState.capturedClipIsSelection ) { - // Slide the selection, too - mViewInfo->selectedRegion.move( mClipMoveState.hSlideAmount ); - } - - if (slidVertically) { - // NEW origin - mClipMoveState.hSlideAmount = 0; - } - - Refresh(false); -} void TrackPanel::DoSlideHorizontal ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack ) @@ -6543,10 +6166,6 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event) if (!unsafe) HandleEnvelope(event); break; - case slideTool: - if (!unsafe) - HandleSlide(event); - break; } } } @@ -6554,8 +6173,7 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event) if ((event.Moving() || event.LeftUp()) && (mMouseCapture == IsUncaptured )) // (mMouseCapture != IsSelecting ) && -// (mMouseCapture != IsEnveloping) && -// (mMouseCapture != IsSliding) ) +// (mMouseCapture != IsEnveloping) { HandleCursor(event); } @@ -6608,14 +6226,8 @@ int TrackPanel::DetermineToolToUse( ToolsToolBar * pTtb, const wxMouseEvent & ev // From here on the order in which we hit test determines // which tool takes priority in the rare cases where it // could be more than one. - } else if (event.CmdDown()){ - // msmeyer: If control is down, slide single clip - // msmeyer: If control and shift are down, slide all clips - currentTool = slideTool; - } else if( HitTestEnvelope( pTrack, rect, event ) ){ + } else if( HitTestEnvelope( pTrack, rect, event ) ) { currentTool = envelopeTool; - } else if( HitTestSlide( pTrack, rect, event )){ - currentTool = slideTool; } //Use the false argument since in multimode we don't @@ -6725,28 +6337,6 @@ bool TrackPanel::HitTestEnvelope(Track *track, const wxRect &rect, const wxMouse return( distance < yTolerance ); } -/// method that tells us if the mouse event landed on a -/// time-slider that allows us to time shift the sequence. -bool TrackPanel::HitTestSlide(Track * WXUNUSED(track), const wxRect &rect, const wxMouseEvent & event) -{ - // Perhaps we should delegate this to TrackArtist as only TrackArtist - // knows what the real sizes are?? - - // The drag Handle width includes border, width and a little extra margin. - const int adjustedDragHandleWidth = 14; - // The hotspot for the cursor isn't at its centre. Adjust for this. - const int hotspotOffset = 5; - - // We are doing an approximate test here - is the mouse in the right or left border? - if( event.m_x + hotspotOffset < rect.x + adjustedDragHandleWidth) - return true; - - if( event.m_x + hotspotOffset > rect.x + rect.width - adjustedDragHandleWidth) - return true; - - return false; -} - double TrackPanel::GetMostRecentXPos() { return mViewInfo->PositionToTime(mMouseMostRecentX, GetLabelWidth()); diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 89747e0f8..2460fef47 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -21,8 +21,8 @@ #include "SelectedRegion.h" #include "WaveTrackLocation.h" -#include "Snap.h" #include "Track.h" +#include "Snap.h" #include "widgets/OverlayPanel.h" #include "SelectionState.h" @@ -33,6 +33,8 @@ class wxRect; class EnvelopeEditor; class LabelTrack; class SpectrumAnalyst; +class Track; +class TrackList; class TrackPanel; class TrackPanelCell; class TrackArtist; @@ -49,6 +51,7 @@ class TrackPanelAx; class ViewInfo; +class NoteTrack; class WaveTrack; class WaveClip; class Envelope; @@ -254,7 +257,9 @@ 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 {}; @@ -398,7 +403,6 @@ class AUDACITY_DLL_API TrackPanel final : public OverlayPanel { // Working out where to dispatch the event to. virtual int DetermineToolToUse( ToolsToolBar * pTtb, const wxMouseEvent & event); virtual bool HitTestEnvelope(Track *track, const wxRect &rect, const wxMouseEvent & event); - virtual bool HitTestSlide(Track *track, const wxRect &rect, const wxMouseEvent & event); #ifdef USE_MIDI // data for NoteTrack interactive stretch operations: // Stretching applies to a selected region after quantizing the @@ -499,10 +503,7 @@ protected: virtual void ForwardEventToWaveTrackEnvelope(wxMouseEvent & event); virtual void ForwardEventToEnvelope(wxMouseEvent &event); - // AS: Track sliding handlers - virtual void HandleSlide(wxMouseEvent & event); - virtual void StartSlide(wxMouseEvent &event); - virtual void DoSlide(wxMouseEvent &event); +public: static void DoSlideHorizontal ( ClipMoveState &state, TrackList &trackList, Track &capturedTrack ); static void CreateListOfCapturedClips @@ -514,6 +515,7 @@ protected: 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); } @@ -767,18 +769,14 @@ protected: Track *mCapturedTrack; Envelope *mCapturedEnvelope; - ClipMoveState mClipMoveState; WaveTrackLocation mCapturedTrackLocation; wxRect mCapturedTrackLocationRect; wxRect mCapturedRect; - bool mDidSlideVertically; - bool mRedrawAfterStop; wxMouseEvent mLastMouseEvent; - int mMouseClickX; int mMouseClickY; int mMouseMostRecentX; @@ -792,23 +790,17 @@ protected: // are the horizontal index of pixels to display user feedback // guidelines so the user knows when such snapping is taking place. std::unique_ptr mSnapManager; + wxInt64 mSnapLeft { -1 }; wxInt64 mSnapRight { -1 }; - bool mSnapPreferRightEdge; public: wxInt64 GetSnapLeft () const { - if ( mMouseCapture == IsSliding ) - return mClipMoveState.snapLeft ; - else return mSnapLeft ; } wxInt64 GetSnapRight() const { - if ( mMouseCapture == IsSliding ) - return mClipMoveState.snapRight; - else return mSnapRight; } @@ -869,7 +861,6 @@ public: IsResizingBetweenLinkedTracks, IsResizingBelowLinkedTracks, IsRearranging, - IsSliding, IsEnveloping, IsMuting, IsSoloing, @@ -888,8 +879,6 @@ protected: enum MouseCaptureEnum mMouseCapture; virtual void SetCapturedTrack( Track * t, enum MouseCaptureEnum MouseCapture=IsUncaptured ); - bool mSlideUpDownOnly; - // JH: if the user is dragging a track, at what y // coordinate should the dragging track move up or down? int mMoveUpThreshold; @@ -898,7 +887,7 @@ protected: std::unique_ptr mArrowCursor, mSelectCursor, - mResizeCursor, mSlideCursor, mEnvelopeCursor, // doubles as the center frequency cursor + mResizeCursor, mEnvelopeCursor, // doubles as the center frequency cursor // for spectral selection mZoomInCursor, mZoomOutCursor, mRearrangeCursor, diff --git a/src/TrackPanelAx.h b/src/TrackPanelAx.h index 691a20e24..41e546f6c 100644 --- a/src/TrackPanelAx.h +++ b/src/TrackPanelAx.h @@ -18,6 +18,8 @@ #include #endif +#include "Snap.h" +#include "Track.h" #include "TrackPanel.h" class TrackPanelAx final diff --git a/src/prefs/SpectrumPrefs.cpp b/src/prefs/SpectrumPrefs.cpp index ee98e1425..38306b484 100644 --- a/src/prefs/SpectrumPrefs.cpp +++ b/src/prefs/SpectrumPrefs.cpp @@ -26,6 +26,8 @@ #include "../Project.h" #include "../ShuttleGui.h" #include "../WaveTrack.h" +#include "../Snap.h" + #include "../TrackPanel.h" #include diff --git a/src/prefs/WaveformPrefs.cpp b/src/prefs/WaveformPrefs.cpp index 925b3cdd7..6e52b8baf 100644 --- a/src/prefs/WaveformPrefs.cpp +++ b/src/prefs/WaveformPrefs.cpp @@ -21,6 +21,8 @@ Paul Licameli #include #include "../Project.h" +#include "../Snap.h" + #include "../TrackPanel.h" #include "../ShuttleGui.h" #include "../WaveTrack.h" diff --git a/src/toolbars/ToolsToolBar.cpp b/src/toolbars/ToolsToolBar.cpp index fc91cee8e..72eb3ebd2 100644 --- a/src/toolbars/ToolsToolBar.cpp +++ b/src/toolbars/ToolsToolBar.cpp @@ -102,7 +102,11 @@ ToolsToolBar::ToolsToolBar() mMessageOfTool[zoomTool] = _("Left=Zoom In, Right=Zoom Out, Middle=Normal"); #endif + // TODO: Should it say "track or clip" ? Non-wave tracks can move, or clips in a wave track. + // TODO: mention effects of shift (move all clips of selected wave track) and ctrl (move vertically only) ? + // -- but not all of that is available in multi tool. mMessageOfTool[slideTool] = _("Click and drag to move a track in time"); + mMessageOfTool[multiTool] = wxT(""); // multi-mode tool bool multiToolActive = false; diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp index be86b4de9..c31dcccf5 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp @@ -18,6 +18,7 @@ Paul Licameli split from TrackPanel.cpp #include "../../../../toolbars/ToolsToolBar.h" #include "SampleHandle.h" +#include "../../../ui/TimeShiftHandle.h" HitTestResult WaveTrack::HitTest (const TrackPanelMouseEvent &event, @@ -29,7 +30,14 @@ HitTestResult WaveTrack::HitTest const ToolsToolBar *const pTtb = pProject->GetToolsToolBar(); if (pTtb->IsDown(multiTool)) { - if (NULL != (result = + // Replicate some of the logic of TrackPanel::DetermineToolToUse + int currentTool = -1; + if (event.event.CmdDown()) + result = TimeShiftHandle::HitAnywhere(pProject); + else if (NULL != (result = + TimeShiftHandle::HitTest(event.event, event.rect, pProject)).preview.cursor) + ; + else if (NULL != (result = SampleHandle::HitTest(event.event, event.rect, pProject, this)).preview.cursor) ; } diff --git a/src/tracks/ui/TimeShiftHandle.cpp b/src/tracks/ui/TimeShiftHandle.cpp new file mode 100644 index 000000000..3158e7fac --- /dev/null +++ b/src/tracks/ui/TimeShiftHandle.cpp @@ -0,0 +1,570 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +TimeShiftHandle.cpp + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#include "../../Audacity.h" +#include "TimeShiftHandle.h" + +#include "TrackControls.h" +#include "../../AColor.h" +#include "../../HitTestResult.h" +#include "../../Project.h" +#include "../../RefreshCode.h" +#include "../../Snap.h" +#include "../../TrackPanelMouseEvent.h" +#include "../../toolbars/ToolsToolBar.h" +#include "../../UndoManager.h" +#include "../../WaveTrack.h" +#include "../../../images/Cursors.h" + +TimeShiftHandle::TimeShiftHandle() +{ +} + +TimeShiftHandle &TimeShiftHandle::Instance() +{ + static TimeShiftHandle instance; + return instance; +} + +HitTestPreview TimeShiftHandle::HitPreview +(const AudacityProject *pProject, bool unsafe) +{ + static auto disabledCursor = + ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16); + static auto slideCursor = + MakeCursor(wxCURSOR_SIZEWE, TimeCursorXpm, 16, 16); + const ToolsToolBar *const ttb = pProject->GetToolsToolBar(); + return { + ttb->GetMessageForTool(slideTool), + (unsafe + ? &*disabledCursor + : &*slideCursor) + }; +} + +HitTestResult TimeShiftHandle::HitAnywhere(const AudacityProject *pProject) +{ + // After all that, it still may be unsafe to drag. + // Even if so, make an informative cursor change from default to "banned." + const bool unsafe = pProject->IsAudioActive(); + return { + HitPreview(pProject, unsafe), + (unsafe + ? NULL + : &Instance()) + }; +} + +HitTestResult TimeShiftHandle::HitTest + (const wxMouseEvent & event, const wxRect &rect, const AudacityProject *pProject) +{ + /// method that tells us if the mouse event landed on a + /// time-slider that allows us to time shift the sequence. + /// (Those are the two "grips" drawn at left and right edges for multi tool mode.) + + // Perhaps we should delegate this to TrackArtist as only TrackArtist + // knows what the real sizes are?? + + // The drag Handle width includes border, width and a little extra margin. + const int adjustedDragHandleWidth = 14; + // The hotspot for the cursor isn't at its centre. Adjust for this. + const int hotspotOffset = 5; + + // We are doing an approximate test here - is the mouse in the right or left border? + if (!(event.m_x + hotspotOffset < rect.x + adjustedDragHandleWidth || + event.m_x + hotspotOffset >= rect.x + rect.width - adjustedDragHandleWidth)) + return {}; + + return HitAnywhere(pProject); +} + +TimeShiftHandle::~TimeShiftHandle() +{ +} + +namespace { + // Don't count right channels. + WaveTrack *NthAudioTrack(TrackList &list, int nn) + { + if (nn >= 0) { + TrackListOfKindIterator iter(Track::Wave, &list); + Track *pTrack = iter.First(); + while (pTrack && nn--) + pTrack = iter.Next(true); + return static_cast(pTrack); + } + + return NULL; + } + + // Don't count right channels. + int TrackPosition(TrackList &list, Track *pFindTrack) + { + Track *const partner = pFindTrack->GetLink(); + TrackListOfKindIterator iter(Track::Wave, &list); + int nn = 0; + for (Track *pTrack = iter.First(); pTrack; pTrack = iter.Next(true), ++nn) { + if (pTrack == pFindTrack || + pTrack == partner) + return nn; + } + return -1; + } +} + +UIHandle::Result TimeShiftHandle::Click +(const TrackPanelMouseEvent &evt, AudacityProject *pProject) +{ + const wxMouseEvent &event = evt.event; + const wxRect &rect = evt.rect; + const ViewInfo &viewInfo = pProject->GetViewInfo(); + + Track *const pTrack = static_cast(evt.pCell); + + using namespace RefreshCode; + + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) + return Cancelled; + + TrackList *const trackList = pProject->GetTracks(); + + mClipMoveState.clear(); + mDidSlideVertically = false; + + ToolsToolBar *const ttb = pProject->GetToolsToolBar(); + const bool multiToolModeActive = (ttb && ttb->IsDown(multiTool)); + + const double clickTime = + viewInfo.PositionToTime(event.m_x, rect.x); + mClipMoveState.capturedClipIsSelection = + (pTrack->GetSelected() && + clickTime >= viewInfo.selectedRegion.t0() && + clickTime < viewInfo.selectedRegion.t1()); + + WaveTrack *wt = pTrack->GetKind() == Track::Wave + ? static_cast(pTrack) : nullptr; + + if ((wt +#ifdef USE_MIDI + || pTrack->GetKind() == Track::Note +#endif + ) && !event.ShiftDown()) + { +#ifdef USE_MIDI + if (!wt) + mClipMoveState.capturedClip = nullptr; + else +#endif + { + mClipMoveState.capturedClip = wt->GetClipAtX(event.m_x); + if (mClipMoveState.capturedClip == NULL) + return Cancelled; + } + + TrackPanel::CreateListOfCapturedClips + ( mClipMoveState, viewInfo, *pTrack, *trackList, + pProject->IsSyncLocked(), clickTime ); + } + else { + // Shift was down, or track was not Wave or Note + mClipMoveState.capturedClip = NULL; + mClipMoveState.capturedClipArray.clear(); + } + + mSlideUpDownOnly = event.CmdDown() && !multiToolModeActive; + mCapturedTrack = pTrack; + mRect = rect; + mMouseClickX = event.m_x; + const double selStart = viewInfo.PositionToTime(event.m_x, mRect.x); + mSnapManager = std::make_unique(trackList, + &viewInfo, + &mClipMoveState.capturedClipArray, + &mClipMoveState.trackExclusions, + true); // don't snap to time + mClipMoveState.snapLeft = -1; + mClipMoveState.snapRight = -1; + mSnapPreferRightEdge = + mClipMoveState.capturedClip && + (fabs(selStart - mClipMoveState.capturedClip->GetEndTime()) < + fabs(selStart - mClipMoveState.capturedClip->GetStartTime())); + + return RefreshNone; +} + +UIHandle::Result TimeShiftHandle::Drag +(const TrackPanelMouseEvent &evt, AudacityProject *pProject) +{ + const wxMouseEvent &event = evt.event; + ViewInfo &viewInfo = pProject->GetViewInfo(); + + // We may switch pTrack to its stereo partner below + Track *pTrack = dynamic_cast(evt.pCell); + + // Uncommenting this permits drag to continue to work even over the controls area + /* + pTrack = static_cast(evt.pCell)->FindTrack(); + */ + + if (!pTrack) { + // Allow sliding if the pointer is not over any track, but only if x is + // within the bounds of the tracks area. + if (event.m_x >= mRect.GetX() && + event.m_x < mRect.GetX() + mRect.GetWidth()) + pTrack = mCapturedTrack; + } + + if (!pTrack) + return RefreshCode::RefreshNone; + + using namespace RefreshCode; + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) { + this->Cancel(pProject); + return RefreshAll | Cancelled; + } + + TrackList *const trackList = pProject->GetTracks(); + + // GM: DoSlide now implementing snap-to + // samples functionality based on sample rate. + + // Start by undoing the current slide amount; everything + // happens relative to the original horizontal position of + // each clip... +#ifdef USE_MIDI + if (mClipMoveState.capturedClipArray.size()) +#else + if (mClipMoveState.capturedClip) +#endif + { + for (unsigned ii = 0; ii < mClipMoveState.capturedClipArray.size(); ++ii) { + if (mClipMoveState.capturedClipArray[ii].clip) + mClipMoveState.capturedClipArray[ii].clip->Offset + ( -mClipMoveState.hSlideAmount ); + else + mClipMoveState.capturedClipArray[ii].track->Offset + ( -mClipMoveState.hSlideAmount ); + } + } + else { + // Was a shift-click + mCapturedTrack->Offset( -mClipMoveState.hSlideAmount ); + Track *const link = mCapturedTrack->GetLink(); + if (link) + link->Offset( -mClipMoveState.hSlideAmount ); + } + + if ( mClipMoveState.capturedClipIsSelection ) { + // Slide the selection, too + viewInfo.selectedRegion.move( -mClipMoveState.hSlideAmount ); + } + mClipMoveState.hSlideAmount = 0.0; + + double desiredSlideAmount; + if (mSlideUpDownOnly) { + desiredSlideAmount = 0.0; + } + else { + desiredSlideAmount = + viewInfo.PositionToTime(event.m_x) - + viewInfo.PositionToTime(mMouseClickX); + bool trySnap = false; + double clipLeft = 0, clipRight = 0; +#ifdef USE_MIDI + if (pTrack->GetKind() == Track::Wave) { + WaveTrack *const mtw = static_cast(pTrack); + const double rate = mtw->GetRate(); + // set it to a sample point + desiredSlideAmount = rint(desiredSlideAmount * rate) / rate; + } + + // Adjust desiredSlideAmount using SnapManager + if (mSnapManager.get() && mClipMoveState.capturedClipArray.size()) { + trySnap = true; + if (mClipMoveState.capturedClip) { + clipLeft = mClipMoveState.capturedClip->GetStartTime() + + desiredSlideAmount; + clipRight = mClipMoveState.capturedClip->GetEndTime() + + desiredSlideAmount; + } + else { + clipLeft = mCapturedTrack->GetStartTime() + desiredSlideAmount; + clipRight = mCapturedTrack->GetEndTime() + desiredSlideAmount; + } + } +#else + { + trySnap = true; + if (pTrack->GetKind() == Track::Wave) { + auto wt = static_cast(pTrack); + const double rate = wt->GetRate(); + // set it to a sample point + desiredSlideAmount = rint(desiredSlideAmount * rate) / rate; + if (mSnapManager && mClipMoveState.capturedClip) { + clipLeft = mClipMoveState.capturedClip->GetStartTime() + + desiredSlideAmount; + clipRight = mClipMoveState.capturedClip->GetEndTime() + + desiredSlideAmount; + } + } + } +#endif + if (trySnap) + { + double newClipLeft = clipLeft; + double newClipRight = clipRight; + + bool dummy1, dummy2; + mSnapManager->Snap(mCapturedTrack, clipLeft, false, &newClipLeft, + &dummy1, &dummy2); + mSnapManager->Snap(mCapturedTrack, clipRight, false, &newClipRight, + &dummy1, &dummy2); + + // Only one of them is allowed to snap + if (newClipLeft != clipLeft && newClipRight != clipRight) { + // Un-snap the un-preferred edge + if (mSnapPreferRightEdge) + newClipLeft = clipLeft; + else + newClipRight = clipRight; + } + + // Take whichever one snapped (if any) and compute the NEW desiredSlideAmount + mClipMoveState.snapLeft = -1; + mClipMoveState.snapRight = -1; + if (newClipLeft != clipLeft) { + const double difference = (newClipLeft - clipLeft); + desiredSlideAmount += difference; + mClipMoveState.snapLeft = + viewInfo.TimeToPosition(newClipLeft, mRect.x); + } + else if (newClipRight != clipRight) { + const double difference = (newClipRight - clipRight); + desiredSlideAmount += difference; + mClipMoveState.snapRight = + viewInfo.TimeToPosition(newClipRight, mRect.x); + } + } + } + + // Scroll during vertical drag. + // EnsureVisible(pTrack); //vvv Gale says this has problems on Linux, per bug 393 thread. Revert for 2.0.2. + bool slidVertically = false; + + // If the mouse is over a track that isn't the captured track, + // decide which tracks the captured clips should go to. + if (mClipMoveState.capturedClip && + pTrack != mCapturedTrack && + pTrack->GetKind() == Track::Wave + /* && !mCapturedClipIsSelection*/) + { + const int diff = + TrackPosition(*trackList, pTrack) - + TrackPosition(*trackList, mCapturedTrack); + for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); + ii < nn; ++ii ) { + TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; + if (trackClip.clip) { + // Move all clips up or down by an equal count of audio tracks. + Track *const pSrcTrack = trackClip.track; + auto pDstTrack = NthAudioTrack(*trackList, + diff + TrackPosition(*trackList, pSrcTrack)); + // Can only move mono to mono, or left to left, or right to right + // And that must be so for each captured clip + bool stereo = (pSrcTrack->GetLink() != 0); + if (pDstTrack && stereo && !pSrcTrack->GetLinked()) + // Assume linked track is wave or null + pDstTrack = static_cast(pDstTrack->GetLink()); + bool ok = pDstTrack && + (stereo == (pDstTrack->GetLink() != 0)) && + (!stereo || (pSrcTrack->GetLinked() == pDstTrack->GetLinked())); + if (ok) + trackClip.dstTrack = pDstTrack; + else + return RefreshAll; + } + } + + // Having passed that test, remove clips temporarily from their + // tracks, so moving clips don't interfere with each other + // when we call CanInsertClip() + for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); + ii < nn; ++ii ) { + TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; + WaveClip *const pSrcClip = trackClip.clip; + if (pSrcClip) + trackClip.holder = + // Assume track is wave because it has a clip + static_cast(trackClip.track)-> + RemoveAndReturnClip(pSrcClip); + } + + // Now check that the move is possible + bool ok = true; + for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); + ok && ii < nn; ++ii) { + TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; + WaveClip *const pSrcClip = trackClip.clip; + if (pSrcClip) + ok = trackClip.dstTrack->CanInsertClip(pSrcClip); + } + + if (!ok) { + // Failure -- put clips back where they were + for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); + ii < nn; ++ii) { + TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; + WaveClip *const pSrcClip = trackClip.clip; + if (pSrcClip) + // Assume track is wave because it has a clip + static_cast(trackClip.track)-> + AddClip(std::move(trackClip.holder)); + } + return RefreshAll; + } + else { + // Do the vertical moves of clips + for ( unsigned ii = 0, nn = mClipMoveState.capturedClipArray.size(); + ii < nn; ++ii ) { + TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; + WaveClip *const pSrcClip = trackClip.clip; + if (pSrcClip) { + const auto dstTrack = trackClip.dstTrack; + dstTrack->AddClip(std::move(trackClip.holder)); + trackClip.track = dstTrack; + } + } + + mCapturedTrack = pTrack; + mDidSlideVertically = true; + + // Make the offset permanent; start from a "clean slate" + mMouseClickX = event.m_x; + } + + // Not done yet, check for horizontal movement. + slidVertically = true; + } + + if (desiredSlideAmount == 0.0) + return RefreshAll; + + mClipMoveState.hSlideAmount = desiredSlideAmount; + + TrackPanel::DoSlideHorizontal( mClipMoveState, *trackList, *mCapturedTrack ); + + if (mClipMoveState.capturedClipIsSelection) { + // Slide the selection, too + viewInfo.selectedRegion.move( mClipMoveState.hSlideAmount ); + } + + + if (slidVertically) { + // NEW origin + mClipMoveState.hSlideAmount = 0; + } + + return RefreshAll; +} + +HitTestPreview TimeShiftHandle::Preview +(const TrackPanelMouseEvent &, const AudacityProject *pProject) +{ + return HitPreview(pProject, false); +} + +UIHandle::Result TimeShiftHandle::Release +(const TrackPanelMouseEvent &, AudacityProject *pProject, + wxWindow *) +{ + using namespace RefreshCode; + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) + return this->Cancel(pProject); + + Result result = RefreshNone; + + mCapturedTrack = NULL; + mSnapManager.reset(NULL); + mClipMoveState.capturedClipArray.clear(); + + // Do not draw yellow lines + if ( mClipMoveState.snapLeft != -1 || mClipMoveState.snapRight != -1) { + mClipMoveState.snapLeft = mClipMoveState.snapRight = -1; + result |= RefreshAll; + } + + if ( !mDidSlideVertically && mClipMoveState.hSlideAmount == 0 ) + return result; + + for ( size_t ii = 0; ii < mClipMoveState.capturedClipArray.size(); ++ii ) + { + TrackClip &trackClip = mClipMoveState.capturedClipArray[ii]; + WaveClip* pWaveClip = trackClip.clip; + // Note that per AddClipsToCaptured(Track *t, double t0, double t1), + // in the non-WaveTrack case, the code adds a NULL clip to mCapturedClipArray, + // so we have to check for that any time we're going to deref it. + // Previous code did not check it here, and that caused bug 367 crash. + if (pWaveClip && + trackClip.track != trackClip.origTrack) + { + // Now that user has dropped the clip into a different track, + // make sure the sample rate matches the destination track (mCapturedTrack). + // Assume the clip was dropped in a wave track + pWaveClip->Resample + (static_cast(trackClip.track)->GetRate()); + pWaveClip->MarkChanged(); + } + } + + wxString msg; + bool consolidate; + if (mDidSlideVertically) { + msg.Printf(_("Moved clips to another track")); + consolidate = false; + } + else { + wxString direction = mClipMoveState.hSlideAmount > 0 ? + /* i18n-hint: a direction as in left or right.*/ + _("right") : + /* i18n-hint: a direction as in left or right.*/ + _("left"); + /* i18n-hint: %s is a direction like left or right */ + msg.Printf(_("Time shifted tracks/clips %s %.02f seconds"), + direction.c_str(), fabs( mClipMoveState.hSlideAmount )); + consolidate = true; + } + pProject->PushState(msg, _("Time-Shift"), + consolidate ? (UndoPush::CONSOLIDATE) : (UndoPush::AUTOSAVE)); + + return result | FixScrollbars; +} + +UIHandle::Result TimeShiftHandle::Cancel(AudacityProject *pProject) +{ + pProject->RollbackState(); + mCapturedTrack = nullptr; + mSnapManager.reset(); + mClipMoveState.clear(); + return RefreshCode::RefreshAll; +} + +void TimeShiftHandle::DrawExtras +(DrawingPass pass, + wxDC * dc, const wxRegion &, const wxRect &) +{ + if (pass == Panel) { + // Draw snap guidelines if we have any + if ( mSnapManager ) + mSnapManager->Draw + ( dc, mClipMoveState.snapLeft, mClipMoveState.snapRight ); + } +} diff --git a/src/tracks/ui/TimeShiftHandle.h b/src/tracks/ui/TimeShiftHandle.h new file mode 100644 index 000000000..71f606ff8 --- /dev/null +++ b/src/tracks/ui/TimeShiftHandle.h @@ -0,0 +1,84 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +TimeShiftHandle.h + +Paul Licameli + +**********************************************************************/ + +#ifndef __AUDACITY_TIMESHIFT_HANDLE__ +#define __AUDACITY_TIMESHIFT_HANDLE__ + +#include "../../UIHandle.h" + +#include "../../MemoryX.h" + +#include "../../Snap.h" +#include "../../Track.h" + +#include "../../TrackPanel.h" // for ClipMoveState + +struct HitTestResult; +class WaveClip; + +class TimeShiftHandle final : public UIHandle +{ + TimeShiftHandle(); + TimeShiftHandle(const TimeShiftHandle&) = delete; + TimeShiftHandle &operator=(const TimeShiftHandle&) = delete; + static TimeShiftHandle& Instance(); + static HitTestPreview HitPreview + (const AudacityProject *pProject, bool unsafe); + +public: + static HitTestResult HitAnywhere(const AudacityProject *pProject); + static HitTestResult HitTest + (const wxMouseEvent &event, const wxRect &rect, const AudacityProject *pProject); + + virtual ~TimeShiftHandle(); + + Result Click + (const TrackPanelMouseEvent &event, AudacityProject *pProject) override; + + Result Drag + (const TrackPanelMouseEvent &event, AudacityProject *pProject) override; + + HitTestPreview Preview + (const TrackPanelMouseEvent &event, const AudacityProject *pProject) + override; + + Result Release + (const TrackPanelMouseEvent &event, AudacityProject *pProject, + wxWindow *pParent) override; + + Result Cancel(AudacityProject *pProject) override; + + void DrawExtras + (DrawingPass pass, + wxDC * dc, const wxRegion &, const wxRect &panelRect) override; + + bool StopsOnKeystroke() override { return true; } + +private: + Track *mCapturedTrack{}; + wxRect mRect{}; + + bool mDidSlideVertically{}; + bool mSlideUpDownOnly{}; + + bool mSnapPreferRightEdge{}; + + int mMouseClickX{}; + + // Handles snapping the selection boundaries or track boundaries to + // line up with existing tracks or labels. mSnapLeft and mSnapRight + // are the horizontal index of pixels to display user feedback + // guidelines so the user knows when such snapping is taking place. + std::unique_ptr mSnapManager{}; + + ClipMoveState mClipMoveState{}; +}; + +#endif diff --git a/src/tracks/ui/TrackUI.cpp b/src/tracks/ui/TrackUI.cpp index 2b2d03f15..60f804f89 100644 --- a/src/tracks/ui/TrackUI.cpp +++ b/src/tracks/ui/TrackUI.cpp @@ -19,6 +19,7 @@ Paul Licameli split from TrackPanel.cpp #include "../playabletrack/wavetrack/ui/SampleHandle.h" #include "ZoomHandle.h" +#include "TimeShiftHandle.h" HitTestResult Track::HitTest (const TrackPanelMouseEvent &event, @@ -33,10 +34,11 @@ HitTestResult Track::HitTest return SampleHandle::HitAnywhere(event.event, pProject); case zoomTool: return ZoomHandle::HitAnywhere(event.event, pProject); + case slideTool: + return TimeShiftHandle::HitAnywhere(pProject); case selectTool: case envelopeTool: - case slideTool: default: // cases not yet implemented // fallthru diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index f2c1a3a3a..949d32ac8 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -241,6 +241,7 @@ + @@ -503,6 +504,7 @@ + @@ -1175,4 +1177,4 @@ - \ No newline at end of file + diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index b75bb4906..7abbbc487 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -953,6 +953,9 @@ src\tracks\ui + + src\tracks\ui + src\tracks\timetrack\ui @@ -1984,6 +1987,9 @@ src\tracks\playabletrack\wavetrack\ui + + src\tracks\ui + @@ -2207,4 +2213,4 @@ plug-ins - \ No newline at end of file +