diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index 6556c544c..2f540ad60 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -1206,6 +1206,7 @@ 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 */; }; + 5E7396471DAFD8F200BA0A4D /* EnvelopeHandle.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E7396451DAFD8F200BA0A4D /* EnvelopeHandle.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 */; }; @@ -3007,6 +3008,8 @@ 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 = ""; }; + 5E7396451DAFD8F200BA0A4D /* EnvelopeHandle.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EnvelopeHandle.cpp; sourceTree = ""; }; + 5E7396461DAFD8F200BA0A4D /* EnvelopeHandle.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EnvelopeHandle.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 = ""; }; @@ -5733,6 +5736,7 @@ children = ( 5E1512661DB0010C00702E29 /* CommonTrackPanelCell.cpp */, 5E74D2DD1CC4429700D88B0B /* EditCursorOverlay.cpp */, + 5E7396451DAFD8F200BA0A4D /* EnvelopeHandle.cpp */, 5E74D2DF1CC4429700D88B0B /* PlayIndicatorOverlay.cpp */, 5E74D2E11CC4429700D88B0B /* Scrubbing.cpp */, 5E7396421DAFD8C600BA0A4D /* TimeShiftHandle.cpp */, @@ -5742,6 +5746,7 @@ 5E73963C1DAFD86000BA0A4D /* ZoomHandle.cpp */, 5E1512671DB0010C00702E29 /* CommonTrackPanelCell.h */, 5E74D2DE1CC4429700D88B0B /* EditCursorOverlay.h */, + 5E7396461DAFD8F200BA0A4D /* EnvelopeHandle.h */, 5E74D2E01CC4429700D88B0B /* PlayIndicatorOverlay.h */, 5E74D2E21CC4429700D88B0B /* Scrubbing.h */, 5E7396431DAFD8C600BA0A4D /* TimeShiftHandle.h */, @@ -7877,6 +7882,7 @@ 280A8B4719F4403B0091DE70 /* ModuleManager.cpp in Sources */, 5EA018291EC7B226001F2996 /* NoteTrackUI.cpp in Sources */, 280A8B4A19F440880091DE70 /* EffectRack.cpp in Sources */, + 5E7396471DAFD8F200BA0A4D /* EnvelopeHandle.cpp in Sources */, 28001B3E1A0F0E5D007DD161 /* NumericTextCtrl.cpp in Sources */, 28001B4B1A0F0EB6007DD161 /* SpectralSelectionBar.cpp in Sources */, 28BB98051A15BE6800D1CC80 /* NoiseReduction.cpp in Sources */, diff --git a/src/Makefile.am b/src/Makefile.am index 83f179db1..95c9588ef 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -575,6 +575,8 @@ audacity_SOURCES = \ tracks/ui/CommonTrackPanelCell.h \ tracks/ui/EditCursorOverlay.cpp \ tracks/ui/EditCursorOverlay.h \ + tracks/ui/EnvelopeHandle.cpp \ + tracks/ui/EnvelopeHandle.h \ tracks/ui/PlayIndicatorOverlay.cpp \ tracks/ui/PlayIndicatorOverlay.h \ tracks/ui/Scrubbing.cpp \ diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index 0006ffd32..d5a0585af 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -170,7 +170,6 @@ is time to refresh some aspect of the screen. #include "AColor.h" #include "AllThemeResources.h" #include "AudioIO.h" -#include "Envelope.h" #include "float_cast.h" #include "LabelTrack.h" #include "MixerBoard.h" @@ -1150,7 +1149,6 @@ void TrackPanel::HandleInterruptedDrag() IsClosing, IsAdjustingLabel, IsRearranging, - IsEnveloping, IsGainSliding, IsPanSliding, WasOverCutLine, @@ -1406,9 +1404,6 @@ bool TrackPanel::SetCursorByActivity( ) case IsSelecting: SetCursor(*mSelectCursor); return true; - case IsEnveloping: - SetCursor( unsafe ? *mDisabledCursor : *mEnvelopeCursor); - return true; case IsRearranging: SetCursor( unsafe ? *mDisabledCursor : *mRearrangeCursor); return true; @@ -1750,16 +1745,11 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t, void TrackPanel::SetCursorAndTipByTool( int tool, const wxMouseEvent &, wxString& ) { - bool unsafe = IsUnsafe(); - // Change the cursor based on the active tool. switch (tool) { case selectTool: wxFAIL;// should have already been handled break; - case envelopeTool: - SetCursor(unsafe ? *mDisabledCursor : *mEnvelopeCursor); - break; } // doesn't actually change the tip itself, but it could (should?) do at some // future date. @@ -3138,171 +3128,6 @@ bool mayDragWidth, bool onlyWithinSnapDistance, } } -/// HandleEnvelope gets called when the user is changing the -/// amplitude envelope on a track. -void TrackPanel::HandleEnvelope(wxMouseEvent & event) -{ - if (event.LeftDown()) { - const auto foundCell = FindCell(event.m_x, event.m_y); - auto &pTrack = foundCell.pTrack; - auto &rect = foundCell.rect; - - if (!pTrack || foundCell.type != CellType::Track) - return; - - SetCapturedTrack(pTrack, IsEnveloping); - - if (mCapturedTrack->GetKind() == Track::Wave) - { - mCapturedEnvelope = - ((WaveTrack*)mCapturedTrack)->GetEnvelopeAtX(event.GetX()); - } else { - mCapturedEnvelope = NULL; - } - - mCapturedRect = rect; - mCapturedRect.y += kTopMargin; - mCapturedRect.height -= kTopMargin + kBottomMargin; - } - // AS: if there's actually a selected track, then forward all of the - // mouse events to its envelope. - if (mCapturedTrack) - ForwardEventToEnvelope(event); - - // We test for IsEnveloping, because we could have had our action stopped already, - // and already recorded and the second mouse up is bogus. - // e.g could be stopped by some key press. Bug 1496. - if ((mMouseCapture == IsEnveloping ) && event.LeftUp()) { - SetCapturedTrack( NULL ); - MakeParentPushState( - /* i18n-hint: (verb) Audacity has just adjusted the envelope .*/ - _("Adjusted envelope."), - /* i18n-hint: The envelope is a curve that controls the audio loudness.*/ - _("Envelope") - ); - } -} - -/// We've established we're a time track. -/// send events for its envelope. -void TrackPanel::ForwardEventToTimeTrackEnvelope(wxMouseEvent & event) -{ - // Assume captured track was time - const auto ptimetrack = static_cast(mCapturedTrack); - Envelope *pspeedenvelope = ptimetrack->GetEnvelope(); - - wxRect envRect = mCapturedRect; - double lower = ptimetrack->GetRangeLower(), upper = ptimetrack->GetRangeUpper(); - const double dBRange = mViewInfo->dBr; - if (ptimetrack->GetDisplayLog()) { - // MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale - lower = LINEAR_TO_DB(std::max(1.0e-7, lower)) / dBRange + 1.0; - upper = LINEAR_TO_DB(std::max(1.0e-7, upper)) / dBRange + 1.0; - } - if (event.ButtonDown()) { - mEnvelopeEditor = std::make_unique(*pspeedenvelope, false); - mEnvelopeEditorRight.reset(); - } - bool needUpdate = - mEnvelopeEditor && - mEnvelopeEditor->MouseEvent( - event, envRect, - *mViewInfo, - ptimetrack->GetDisplayLog(), dBRange, lower, upper); - if (needUpdate) { - RefreshTrack(mCapturedTrack); - } -} - -/// We've established we're a wave track. -/// send events for its envelope. -void TrackPanel::ForwardEventToWaveTrackEnvelope(wxMouseEvent & event) -{ - // Assume captured track was wave - const auto pwavetrack = static_cast(mCapturedTrack); - Envelope *penvelope = mCapturedEnvelope; - - // Possibly no-envelope, for example when in spectrum view mode. - // if so, then bail out. - if (!penvelope) - return; - - // AS: WaveTracks can be displayed in several different formats. - // This asks which one is in use. (ie, Wave, Spectrum, etc) - int display = pwavetrack->GetDisplay(); - - if (display == WaveTrack::Waveform) { - const bool dB = !pwavetrack->GetWaveformSettings().isLinear(); - const double dBRange = pwavetrack->GetWaveformSettings().dBRange; - bool needUpdate; - - // AS: Then forward our mouse event to the envelope. - // It'll recalculate and then tell us whether or not to redraw. - wxRect envRect = mCapturedRect; - float zoomMin, zoomMax; - pwavetrack->GetDisplayBounds(&zoomMin, &zoomMax); - if (event.ButtonDown()) { - mEnvelopeEditor = std::make_unique(*penvelope, true); - mEnvelopeEditorRight.reset(); - } - needUpdate = - mEnvelopeEditor && - mEnvelopeEditor->MouseEvent( - event, envRect, - *mViewInfo, - dB, dBRange, zoomMin, zoomMax); - - // If this track is linked to another track, make the identical - // change to the linked envelope: - // Assume linked track is wave or null - const auto link = static_cast(mCapturedTrack->GetLink()); - if (link) { - if (event.ButtonDown()) { - Envelope *e2 = link->GetEnvelopeAtX(event.GetX()); - if (e2) - mEnvelopeEditorRight = std::make_unique(*e2, true); - else { - // There isn't necessarily an envelope there; no guarantee a - // linked track has the same WaveClip structure... - } - } - if (mEnvelopeEditorRight) { - wxRect envRect = mCapturedRect; - float zoomMin, zoomMax; - pwavetrack->GetDisplayBounds(&zoomMin, &zoomMax); - needUpdate|= mEnvelopeEditorRight->MouseEvent(event, envRect, - *mViewInfo, - dB, dBRange, - zoomMin, zoomMax); - } - } - - if (needUpdate) { - RefreshTrack(mCapturedTrack); - } - } -} - - -/// The Envelope class actually handles things at the mouse -/// event level, so we have to forward the events over. Envelope -/// will then tell us whether or not we need to redraw. - -// AS: I'm not sure why we can't let the Envelope take care of -// redrawing itself. ? - -void TrackPanel::ForwardEventToEnvelope(wxMouseEvent & event) -{ - if (mCapturedTrack && mCapturedTrack->GetKind() == Track::Time) - { - ForwardEventToTimeTrackEnvelope( event ); - } - else if (mCapturedTrack && mCapturedTrack->GetKind() == Track::Wave) - { - ForwardEventToWaveTrackEnvelope( event ); - } -} - /// Determines if drag zooming is active bool TrackPanel::IsDragZooming(int zoomStart, int zoomEnd) { @@ -5700,8 +5525,6 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event) auto &pCell = foundCell.pCell; auto &rect = foundCell.rect; - bool unsafe = IsUnsafe(); - //call HandleResize if I'm over the border area // (Add margin back to bottom of the rectangle) if (event.LeftDown() && @@ -5804,18 +5627,13 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event) case selectTool: HandleSelect(event); break; - case envelopeTool: - if (!unsafe) - HandleEnvelope(event); - break; } } } if ((event.Moving() || event.LeftUp()) && (mMouseCapture == IsUncaptured )) -// (mMouseCapture != IsSelecting ) && -// (mMouseCapture != IsEnveloping) +// (mMouseCapture != IsSelecting ) { HandleCursor(event); } @@ -5857,9 +5675,7 @@ int TrackPanel::DetermineToolToUse( ToolsToolBar * pTtb, const wxMouseEvent & ev int trackKind = pTrack->GetKind(); currentTool = selectTool; // the default. - if (trackKind == Track::Time){ - currentTool = envelopeTool; - } else if( trackKind == Track::Label ){ + if( trackKind == Track::Label ){ currentTool = selectTool; } else if( trackKind != Track::Wave) { currentTool = selectTool; @@ -5868,8 +5684,6 @@ 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( HitTestEnvelope( pTrack, rect, event ) ) { - currentTool = envelopeTool; } //Use the false argument since in multimode we don't @@ -5910,75 +5724,6 @@ auto TrackPanel::HitTestStretch } #endif - -/// method that tells us if the mouse event landed on an -/// envelope boundary. -bool TrackPanel::HitTestEnvelope(Track *track, const wxRect &rect, const wxMouseEvent & event) -{ - wxASSERT(track); - if( track->GetKind() != Track::Wave ) - return false; - WaveTrack *wavetrack = (WaveTrack *)track; - Envelope *envelope = wavetrack->GetEnvelopeAtX(event.GetX()); - - if (!envelope) - return false; - - const int displayType = wavetrack->GetDisplay(); - // Not an envelope hit, unless we're using a type of wavetrack display - // suitable for envelopes operations, ie one of the Wave displays. - if ( displayType != WaveTrack::Waveform) - return false; // No envelope, not a hit, so return. - - // Get envelope point, range 0.0 to 1.0 - const bool dB = !wavetrack->GetWaveformSettings().isLinear(); - - const double envValue = envelope->GetValue(mViewInfo->PositionToTime(event.m_x, rect.x)); - - float zoomMin, zoomMax; - wavetrack->GetDisplayBounds(&zoomMin, &zoomMax); - - const double dBRange = wavetrack->GetWaveformSettings().dBRange; - - // Get y position of envelope point. - int yValue = GetWaveYPos( envValue, - zoomMin, zoomMax, - rect.height, dB, true, dBRange, false) + rect.y; - - // Get y position of center line - int ctr = GetWaveYPos( 0.0, - zoomMin, zoomMax, - rect.height, dB, true, dBRange, false) + rect.y; - - // Get y distance of mouse from center line (in pixels). - int yMouse = abs(ctr - event.m_y); - // Get y distance of envelope from center line (in pixels) - yValue = abs(ctr-yValue); - - // JKC: It happens that the envelope is actually drawn offset from its - // 'true' position (it is 3 pixels wide). yMisalign is really a fudge - // factor to allow us to hit it exactly, but I wouldn't dream of - // calling it yFudgeFactor :) - const int yMisalign = 2; - // Perhaps yTolerance should be put into preferences? - const int yTolerance = 5; // how far from envelope we may be and count as a hit. - int distance; - - // For amplification using the envelope we introduced the idea of contours. - // The contours have the same shape as the envelope, which may be partially off-screen. - // The contours are closer in to the center line. - int ContourSpacing = (int) (rect.height / (2* (zoomMax-zoomMin))); - const int MaxContours = 2; - - // Adding ContourSpacing/2 selects a region either side of the contour. - int yDisplace = yValue - yMisalign - yMouse + ContourSpacing/2; - if (yDisplace > (MaxContours * ContourSpacing)) - return false; - // Subtracting the ContourSpacing/2 we added earlier ensures distance is centred on the contour. - distance = abs( ( yDisplace % ContourSpacing ) - ContourSpacing/2); - return( distance < yTolerance ); -} - double TrackPanel::GetMostRecentXPos() { return mViewInfo->PositionToTime(mMouseMostRecentX, GetLabelWidth()); diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 5021ac8c6..459e06192 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -28,7 +28,6 @@ class wxMenu; class wxRect; -class EnvelopeEditor; class LabelTrack; class SpectrumAnalyst; class Track; @@ -52,7 +51,6 @@ class ViewInfo; class NoteTrack; class WaveTrack; class WaveClip; -class Envelope; class UIHandle; // Declared elsewhere, to reduce compilation dependencies @@ -375,7 +373,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); #ifdef USE_MIDI // data for NoteTrack interactive stretch operations: // Stretching applies to a selected region after quantizing the @@ -470,12 +467,6 @@ public: protected: virtual void MaySetOnDemandTip( Track * t, wxString &tip ); - // AS: Envelope editing handlers - virtual void HandleEnvelope(wxMouseEvent & event); - virtual void ForwardEventToTimeTrackEnvelope(wxMouseEvent & event); - virtual void ForwardEventToWaveTrackEnvelope(wxMouseEvent & event); - virtual void ForwardEventToEnvelope(wxMouseEvent &event); - static bool IsDragZooming(int zoomStart, int zoomEnd); virtual bool IsDragZooming() { return IsDragZooming(mZoomStart, mZoomEnd); } @@ -728,7 +719,6 @@ protected: #endif Track *mCapturedTrack; - Envelope *mCapturedEnvelope; WaveTrackLocation mCapturedTrackLocation; wxRect mCapturedTrackLocationRect; wxRect mCapturedRect; @@ -821,7 +811,6 @@ public: IsResizingBetweenLinkedTracks, IsResizingBelowLinkedTracks, IsRearranging, - IsEnveloping, IsMuting, IsSoloing, IsGainSliding, @@ -892,9 +881,6 @@ protected: // Keeps track of extra fractional vertical scroll steps double mVertScrollRemainder; - std::unique_ptr mEnvelopeEditor; - std::unique_ptr mEnvelopeEditorRight; - protected: // The screenshot class needs to access internals diff --git a/src/toolbars/ToolsToolBar.cpp b/src/toolbars/ToolsToolBar.cpp index 72eb3ebd2..db1ae6ca9 100644 --- a/src/toolbars/ToolsToolBar.cpp +++ b/src/toolbars/ToolsToolBar.cpp @@ -87,6 +87,7 @@ ToolsToolBar::ToolsToolBar() mMessageOfTool[selectTool] = _("Click and drag to select audio"); + // TODO: this message isn't appropriate for time track mMessageOfTool[envelopeTool] = _("Click and drag to edit the amplitude envelope"); // TODO: message should also mention the brush. Describing the modifier key diff --git a/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp b/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp index c31dcccf5..c7fbf0fa2 100644 --- a/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp +++ b/src/tracks/playabletrack/wavetrack/ui/WaveTrackUI.cpp @@ -17,6 +17,7 @@ Paul Licameli split from TrackPanel.cpp #include "../../../../TrackPanelMouseEvent.h" #include "../../../../toolbars/ToolsToolBar.h" +#include "../../../ui/EnvelopeHandle.h" #include "SampleHandle.h" #include "../../../ui/TimeShiftHandle.h" @@ -34,6 +35,10 @@ HitTestResult WaveTrack::HitTest int currentTool = -1; if (event.event.CmdDown()) result = TimeShiftHandle::HitAnywhere(pProject); + else if (NULL != + (result = EnvelopeHandle::WaveTrackHitTest(event.event, event.rect, pProject, this)) + .preview.cursor) + ; else if (NULL != (result = TimeShiftHandle::HitTest(event.event, event.rect, pProject)).preview.cursor) ; diff --git a/src/tracks/timetrack/ui/TimeTrackUI.cpp b/src/tracks/timetrack/ui/TimeTrackUI.cpp index e3f8bcc3e..9ae00f699 100644 --- a/src/tracks/timetrack/ui/TimeTrackUI.cpp +++ b/src/tracks/timetrack/ui/TimeTrackUI.cpp @@ -13,12 +13,25 @@ Paul Licameli split from TrackPanel.cpp #include "TimeTrackVRulerControls.h" #include "../../../HitTestResult.h" +#include "../../../Project.h" +#include "../../../toolbars/ToolsToolBar.h" + +#include "../../ui/EnvelopeHandle.h" HitTestResult TimeTrack::HitTest (const TrackPanelMouseEvent &event, const AudacityProject *pProject) { - return Track::HitTest(event, pProject); + HitTestResult result = Track::HitTest(event, pProject); + if (result.preview.cursor) + return result; + + const ToolsToolBar *const pTtb = pProject->GetToolsToolBar(); + if (pTtb->IsDown(multiTool)) + // No hit test --unconditional availability. + result = EnvelopeHandle::HitAnywhere(pProject); + + return result; } TrackControls *TimeTrack::GetControls() diff --git a/src/tracks/ui/EnvelopeHandle.cpp b/src/tracks/ui/EnvelopeHandle.cpp new file mode 100644 index 000000000..33939b4aa --- /dev/null +++ b/src/tracks/ui/EnvelopeHandle.cpp @@ -0,0 +1,286 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +EnvelopeHandle.cpp + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#include "../../Audacity.h" +#include "EnvelopeHandle.h" + +#include "../../MemoryX.h" + +#include "../../Envelope.h" +#include "../../HitTestResult.h" +#include "../../prefs/WaveformSettings.h" +#include "../../Project.h" +#include "../../RefreshCode.h" +#include "../../toolbars/ToolsToolBar.h" +#include "../../TimeTrack.h" +#include "../../TrackArtist.h" +#include "../../TrackPanelMouseEvent.h" +#include "../../ViewInfo.h" +#include "../../WaveTrack.h" +#include "../../../images/Cursors.h" + +EnvelopeHandle::EnvelopeHandle() +{ +} + +EnvelopeHandle &EnvelopeHandle::Instance() +{ + static EnvelopeHandle instance; + return instance; +} + +HitTestPreview EnvelopeHandle::HitPreview(const AudacityProject *pProject, bool unsafe) +{ + static auto disabledCursor = + ::MakeCursor(wxCURSOR_NO_ENTRY, DisabledCursorXpm, 16, 16); + static auto envelopeCursor = + ::MakeCursor(wxCURSOR_ARROW, EnvCursorXpm, 16, 16); + const ToolsToolBar *const ttb = pProject->GetToolsToolBar(); + return { + ttb->GetMessageForTool(envelopeTool), + (unsafe + ? &*disabledCursor + : &*envelopeCursor) + }; +} + +HitTestResult EnvelopeHandle::HitAnywhere(const AudacityProject *pProject) +{ + const bool unsafe = pProject->IsAudioActive(); + return { + HitPreview(pProject, unsafe), + (unsafe + ? NULL + : &Instance()) + }; +} + +HitTestResult EnvelopeHandle::WaveTrackHitTest +(const wxMouseEvent &event, const wxRect &rect, + const AudacityProject *pProject, Cell *pCell) +{ + const ViewInfo &viewInfo = pProject->GetViewInfo(); + Track *const pTrack = static_cast(pCell); + + /// method that tells us if the mouse event landed on an + /// envelope boundary. + if (pTrack->GetKind() != Track::Wave) + return {}; + + WaveTrack *const wavetrack = static_cast(pTrack); + Envelope *const envelope = wavetrack->GetEnvelopeAtX(event.GetX()); + + if (!envelope) + return {}; + + const int displayType = wavetrack->GetDisplay(); + // Not an envelope hit, unless we're using a type of wavetrack display + // suitable for envelopes operations, ie one of the Wave displays. + if (displayType != WaveTrack::Waveform) + return {}; // No envelope, not a hit, so return. + + // Get envelope point, range 0.0 to 1.0 + const bool dB = !wavetrack->GetWaveformSettings().isLinear(); + const double envValue = + envelope->GetValue(viewInfo.PositionToTime(event.m_x, rect.x)); + + float zoomMin, zoomMax; + wavetrack->GetDisplayBounds(&zoomMin, &zoomMax); + + const float dBRange = wavetrack->GetWaveformSettings().dBRange; + + // Get y position of envelope point. + int yValue = GetWaveYPos(envValue, + zoomMin, zoomMax, + rect.height, dB, true, dBRange, false) + rect.y; + + // Get y position of center line + int ctr = GetWaveYPos(0.0, + zoomMin, zoomMax, + rect.height, dB, true, dBRange, false) + rect.y; + + // Get y distance of mouse from center line (in pixels). + int yMouse = abs(ctr - event.m_y); + // Get y distance of envelope from center line (in pixels) + yValue = abs(ctr - yValue); + + // JKC: It happens that the envelope is actually drawn offset from its + // 'true' position (it is 3 pixels wide). yMisalign is really a fudge + // factor to allow us to hit it exactly, but I wouldn't dream of + // calling it yFudgeFactor :) + const int yMisalign = 2; + // Perhaps yTolerance should be put into preferences? + const int yTolerance = 5; // how far from envelope we may be and count as a hit. + int distance; + + // For amplification using the envelope we introduced the idea of contours. + // The contours have the same shape as the envelope, which may be partially off-screen. + // The contours are closer in to the center line. + int ContourSpacing = (int)(rect.height / (2 * (zoomMax - zoomMin))); + const int MaxContours = 2; + + // Adding ContourSpacing/2 selects a region either side of the contour. + int yDisplace = yValue - yMisalign - yMouse + ContourSpacing / 2; + if (yDisplace > (MaxContours * ContourSpacing)) + return {}; + // Subtracting the ContourSpacing/2 we added earlier ensures distance is centred on the contour. + distance = abs((yDisplace % ContourSpacing) - ContourSpacing / 2); + if (distance >= yTolerance) + return {}; + + return HitAnywhere(pProject); +} + +EnvelopeHandle::~EnvelopeHandle() +{ +} + +UIHandle::Result EnvelopeHandle::Click +(const TrackPanelMouseEvent &evt, AudacityProject *pProject) +{ + const wxMouseEvent &event = evt.event; + const ViewInfo &viewInfo = pProject->GetViewInfo(); + Track *const pTrack = static_cast(evt.pCell); + + using namespace RefreshCode; + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) + return Cancelled; + + if (pTrack->GetKind() == Track::Wave) { + WaveTrack *const wt = static_cast(pTrack); + if (wt->GetDisplay() != WaveTrack::Waveform) + return Cancelled; + + auto clickedEnvelope = + wt->GetEnvelopeAtX(event.GetX()); + if (!clickedEnvelope) + return Cancelled; + + mLog = !wt->GetWaveformSettings().isLinear(); + wt->GetDisplayBounds(&mLower, &mUpper); + mdBRange = wt->GetWaveformSettings().dBRange; + mEnvelopeEditor = + std::make_unique< EnvelopeEditor >( *clickedEnvelope, true ); + mEnvelopeEditorRight.reset(); + + // Assume linked track is wave or null + auto partner = static_cast(wt->GetLink()); + if (partner) + { + clickedEnvelope = partner->GetEnvelopeAtX(event.GetX()); + if (clickedEnvelope) + mEnvelopeEditorRight = + std::make_unique< EnvelopeEditor >( *clickedEnvelope, true ); + } + } + else if (pTrack->GetKind() == Track::Time) + { + TimeTrack *const tt = static_cast(pTrack); + auto clickedEnvelope = tt->GetEnvelope(); + if (!clickedEnvelope) + return Cancelled; + mLog = tt->GetDisplayLog(); + mLower = tt->GetRangeLower(), mUpper = tt->GetRangeUpper(); + if (mLog) { + // MB: silly way to undo the work of GetWaveYPos while still getting a logarithmic scale + mdBRange = viewInfo.dBr; + mLower = LINEAR_TO_DB(std::max(1.0e-7, double(mLower))) / mdBRange + 1.0; + mUpper = LINEAR_TO_DB(std::max(1.0e-7, double(mUpper))) / mdBRange + 1.0; + } + mEnvelopeEditor = + std::make_unique< EnvelopeEditor >( *clickedEnvelope, false ); + mEnvelopeEditorRight.reset(); + } + else + return Cancelled; + + mRect = evt.rect; + + const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo); + return needUpdate ? RefreshCell : RefreshNone; +} + +UIHandle::Result EnvelopeHandle::Drag +(const TrackPanelMouseEvent &evt, AudacityProject *pProject) +{ + using namespace RefreshCode; + const wxMouseEvent &event = evt.event; + const ViewInfo &viewInfo = pProject->GetViewInfo(); + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) { + this->Cancel(pProject); + return RefreshCell | Cancelled; + } + + const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo); + return needUpdate ? RefreshCell : RefreshNone; +} + +HitTestPreview EnvelopeHandle::Preview +(const TrackPanelMouseEvent &, const AudacityProject *pProject) +{ + return HitPreview(pProject, false); +} + +UIHandle::Result EnvelopeHandle::Release +(const TrackPanelMouseEvent &evt, AudacityProject *pProject, + wxWindow *) +{ + const wxMouseEvent &event = evt.event; + const ViewInfo &viewInfo = pProject->GetViewInfo(); + const bool unsafe = pProject->IsAudioActive(); + if (unsafe) + return this->Cancel(pProject); + + const bool needUpdate = ForwardEventToEnvelopes(event, viewInfo); + + pProject->PushState( + /* i18n-hint: (verb) Audacity has just adjusted the envelope .*/ + _("Adjusted envelope."), + /* i18n-hint: The envelope is a curve that controls the audio loudness.*/ + _("Envelope") + ); + + mEnvelopeEditor.reset(); + mEnvelopeEditorRight.reset(); + + using namespace RefreshCode; + return needUpdate ? RefreshCell : RefreshNone; +} + +UIHandle::Result EnvelopeHandle::Cancel(AudacityProject *pProject) +{ + pProject->RollbackState(); + mEnvelopeEditor.reset(); + mEnvelopeEditorRight.reset(); + return RefreshCode::RefreshCell; +} + +bool EnvelopeHandle::ForwardEventToEnvelopes + (const wxMouseEvent &event, const ViewInfo &viewInfo) +{ + /// The Envelope class actually handles things at the mouse + /// event level, so we have to forward the events over. Envelope + /// will then tell us whether or not we need to redraw. + + // AS: I'm not sure why we can't let the Envelope take care of + // redrawing itself. ? + bool needUpdate = + mEnvelopeEditor->MouseEvent( + event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper); + + if (mEnvelopeEditorRight) + needUpdate |= + mEnvelopeEditorRight->MouseEvent( + event, mRect, viewInfo, mLog, mdBRange, mLower, mUpper); + + return needUpdate; +} diff --git a/src/tracks/ui/EnvelopeHandle.h b/src/tracks/ui/EnvelopeHandle.h new file mode 100644 index 000000000..fe426888b --- /dev/null +++ b/src/tracks/ui/EnvelopeHandle.h @@ -0,0 +1,72 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +EnvelopeHandle.h + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#ifndef __AUDACITY_ENVELOPE_HANDLE__ +#define __AUDACITY_ENVELOPE_HANDLE__ + +#include "../../UIHandle.h" +#include "../../MemoryX.h" + +class wxMouseEvent; +#include + +class EnvelopeEditor; +struct HitTestResult; +class ViewInfo; +class WaveTrack; + +class EnvelopeHandle final : public UIHandle +{ + EnvelopeHandle(); + EnvelopeHandle(const EnvelopeHandle&) = delete; + EnvelopeHandle &operator=(const EnvelopeHandle&) = delete; + static EnvelopeHandle& Instance(); + static HitTestPreview HitPreview(const AudacityProject *pProject, bool unsafe); + +public: + static HitTestResult HitAnywhere(const AudacityProject *pProject); + static HitTestResult WaveTrackHitTest + (const wxMouseEvent &event, const wxRect &rect, + const AudacityProject *pProject, Cell *pCell); + + virtual ~EnvelopeHandle(); + + 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; + + bool StopsOnKeystroke() override { return true; } + +private: + bool ForwardEventToEnvelopes + (const wxMouseEvent &event, const ViewInfo &viewInfo); + + wxRect mRect{}; + bool mLog{}; + float mLower{}, mUpper{}; + double mdBRange{}; + + std::unique_ptr mEnvelopeEditor; + std::unique_ptr mEnvelopeEditorRight; +}; + +#endif diff --git a/src/tracks/ui/TrackUI.cpp b/src/tracks/ui/TrackUI.cpp index 60f804f89..91dc18d9e 100644 --- a/src/tracks/ui/TrackUI.cpp +++ b/src/tracks/ui/TrackUI.cpp @@ -17,6 +17,7 @@ Paul Licameli split from TrackPanel.cpp #include "../../Project.h" #include "../../toolbars/ToolsToolBar.h" +#include "EnvelopeHandle.h" #include "../playabletrack/wavetrack/ui/SampleHandle.h" #include "ZoomHandle.h" #include "TimeShiftHandle.h" @@ -30,6 +31,9 @@ HitTestResult Track::HitTest const bool isMultiTool = pTtb->IsDown(multiTool); if (!isMultiTool) { switch (pTtb->GetCurrentTool()) { + case envelopeTool: + // Pass "false" for unsafe -- let the tool decide to cancel itself + return EnvelopeHandle::HitAnywhere(pProject); case drawTool: return SampleHandle::HitAnywhere(event.event, pProject); case zoomTool: @@ -38,7 +42,6 @@ HitTestResult Track::HitTest return TimeShiftHandle::HitAnywhere(pProject); case selectTool: - case envelopeTool: default: // cases not yet implemented // fallthru diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index 949d32ac8..1034eff40 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -239,6 +239,7 @@ + @@ -500,6 +501,7 @@ + diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index 7abbbc487..f4899145f 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -956,6 +956,9 @@ src\tracks\ui + + src\tracks\ui + src\tracks\timetrack\ui @@ -1990,6 +1993,9 @@ src\tracks\ui + + src\tracks\ui +