From 1722ee9e323ebb44f193bc1b2cd81599a0d50ba2 Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Tue, 25 Aug 2015 13:35:32 -0400 Subject: [PATCH 1/3] Define TrackPanelOverlay, use it to reimplement play indicator and cursor... ... but scrub speed display is disabled --- mac/Audacity.xcodeproj/project.pbxproj | 46 +- src/Makefile.am | 10 + src/Project.cpp | 41 ++ src/Project.h | 13 +- src/TrackPanel.cpp | 430 +++++------------- src/TrackPanel.h | 39 +- src/TrackPanelCell.h | 19 + src/TrackPanelCellIterator.h | 52 +++ src/TrackPanelOverlay.cpp | 40 ++ src/TrackPanelOverlay.h | 43 ++ src/ViewInfo.cpp | 8 + src/ViewInfo.h | 12 +- src/tracks/ui/EditCursorOverlay.cpp | 118 +++++ src/tracks/ui/EditCursorOverlay.h | 36 ++ src/tracks/ui/PlayIndicatorOverlay.cpp | 151 ++++++ src/tracks/ui/PlayIndicatorOverlay.h | 40 ++ src/tracks/ui/Scrubbing.cpp | 53 +++ src/tracks/ui/Scrubbing.h | 36 ++ win/Projects/Audacity/Audacity.vcxproj | 14 +- .../Audacity/Audacity.vcxproj.filters | 46 +- 20 files changed, 895 insertions(+), 352 deletions(-) create mode 100644 src/TrackPanelCell.h create mode 100644 src/TrackPanelCellIterator.h create mode 100644 src/TrackPanelOverlay.cpp create mode 100644 src/TrackPanelOverlay.h create mode 100644 src/tracks/ui/EditCursorOverlay.cpp create mode 100644 src/tracks/ui/EditCursorOverlay.h create mode 100644 src/tracks/ui/PlayIndicatorOverlay.cpp create mode 100644 src/tracks/ui/PlayIndicatorOverlay.h create mode 100644 src/tracks/ui/Scrubbing.cpp create mode 100644 src/tracks/ui/Scrubbing.h diff --git a/mac/Audacity.xcodeproj/project.pbxproj b/mac/Audacity.xcodeproj/project.pbxproj index 8587091da..d638d4621 100644 --- a/mac/Audacity.xcodeproj/project.pbxproj +++ b/mac/Audacity.xcodeproj/project.pbxproj @@ -1208,6 +1208,10 @@ 28FC1AFB0A47762C00A188AE /* WrappedType.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 28FC1AF90A47762C00A188AE /* WrappedType.cpp */; }; 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 */; }; + 5E74D2D81CC4425D00D88B0B /* TrackPanelOverlay.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E74D2D61CC4425D00D88B0B /* TrackPanelOverlay.cpp */; }; + 5E74D2E31CC4429700D88B0B /* EditCursorOverlay.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E74D2DD1CC4429700D88B0B /* EditCursorOverlay.cpp */; }; + 5E74D2E41CC4429700D88B0B /* PlayIndicatorOverlay.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E74D2DF1CC4429700D88B0B /* PlayIndicatorOverlay.cpp */; }; + 5E74D2E51CC4429700D88B0B /* Scrubbing.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 5E74D2E11CC4429700D88B0B /* Scrubbing.cpp */; }; 8406A93812D0F2510011EA01 /* EQDefaultCurves.xml in Resources */ = {isa = PBXBuildFile; fileRef = 8406A93712D0F2510011EA01 /* EQDefaultCurves.xml */; }; 8484F31413086237002DF7F0 /* DeviceManager.cpp in Sources */ = {isa = PBXBuildFile; fileRef = 8484F31213086237002DF7F0 /* DeviceManager.cpp */; }; ED15214D163C22F000451B5F /* lsr.c in Sources */ = {isa = PBXBuildFile; fileRef = ED152123163C220300451B5F /* lsr.c */; }; @@ -2968,6 +2972,16 @@ 28FE4A070ABF4E960056F5C4 /* sse_optimized.cpp */ = {isa = PBXFileReference; fileEncoding = 5; indentWidth = 3; lastKnownFileType = sourcecode.cpp.cpp; path = sse_optimized.cpp; sourceTree = ""; tabWidth = 3; }; 28FEC1B21A12B6FB00FACE48 /* EffectAutomationParameters.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = EffectAutomationParameters.h; path = ../include/audacity/EffectAutomationParameters.h; sourceTree = SOURCE_ROOT; }; 5E61EE0C1CBAA6BB0009FCF1 /* MemoryX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = MemoryX.h; sourceTree = ""; }; + 5E74D2D61CC4425D00D88B0B /* TrackPanelOverlay.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = TrackPanelOverlay.cpp; sourceTree = ""; }; + 5E74D2D71CC4425D00D88B0B /* TrackPanelOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackPanelOverlay.h; sourceTree = ""; }; + 5E74D2D91CC4427B00D88B0B /* TrackPanelCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackPanelCell.h; sourceTree = ""; }; + 5E74D2DA1CC4427B00D88B0B /* TrackPanelCellIterator.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = TrackPanelCellIterator.h; sourceTree = ""; }; + 5E74D2DD1CC4429700D88B0B /* EditCursorOverlay.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = EditCursorOverlay.cpp; sourceTree = ""; }; + 5E74D2DE1CC4429700D88B0B /* EditCursorOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = EditCursorOverlay.h; sourceTree = ""; }; + 5E74D2DF1CC4429700D88B0B /* PlayIndicatorOverlay.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = PlayIndicatorOverlay.cpp; sourceTree = ""; }; + 5E74D2E01CC4429700D88B0B /* PlayIndicatorOverlay.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = PlayIndicatorOverlay.h; sourceTree = ""; }; + 5E74D2E11CC4429700D88B0B /* Scrubbing.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = Scrubbing.cpp; sourceTree = ""; }; + 5E74D2E21CC4429700D88B0B /* Scrubbing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Scrubbing.h; sourceTree = ""; }; 5ED18DB61CC16B1E00FAFE95 /* Reverb_libSoX.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Reverb_libSoX.h; sourceTree = ""; }; 5ED18DB71CC290AB00FAFE95 /* wxFileNameWrapper.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = wxFileNameWrapper.h; sourceTree = ""; }; 82FF184D13CF01A600C1B664 /* dBTable.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = dBTable.cpp; path = sbsms/src/dBTable.cpp; sourceTree = ""; }; @@ -3793,7 +3807,6 @@ 1790AFC409883BFD008A330A /* src */ = { isa = PBXGroup; children = ( - 5ED18DB71CC290AB00FAFE95 /* wxFileNameWrapper.h */, 1790AFC709883BFD008A330A /* AboutDialog.cpp */, 1790AFC909883BFD008A330A /* AColor.cpp */, 1790AFCE09883BFD008A330A /* AudacityApp.cpp */, @@ -3870,6 +3883,7 @@ 1790B0EA09883BFD008A330A /* TrackArtist.cpp */, 1790B0EC09883BFD008A330A /* TrackPanel.cpp */, 1790B0EE09883BFD008A330A /* TrackPanelAx.cpp */, + 5E74D2D61CC4425D00D88B0B /* TrackPanelOverlay.cpp */, 1790B0F209883BFD008A330A /* UndoManager.cpp */, 28C8211C1B5C661E00B53328 /* ViewInfo.cpp */, 1790B0F709883BFD008A330A /* VoiceKey.cpp */, @@ -3964,7 +3978,10 @@ 1790B0EB09883BFD008A330A /* TrackArtist.h */, 1790B0ED09883BFD008A330A /* TrackPanel.h */, 1790B0EF09883BFD008A330A /* TrackPanelAx.h */, + 5E74D2D91CC4427B00D88B0B /* TrackPanelCell.h */, + 5E74D2DA1CC4427B00D88B0B /* TrackPanelCellIterator.h */, 2803C8B619F35AA000278526 /* TrackPanelListener.h */, + 5E74D2D71CC4425D00D88B0B /* TrackPanelOverlay.h */, 284416391B82D6BC0000574D /* TranslatableStringArray.h */, 1790B0F309883BFD008A330A /* UndoManager.h */, 1790B0F609883BFD008A330A /* ViewInfo.h */, @@ -3973,6 +3990,7 @@ 1790B0FC09883BFD008A330A /* WaveTrack.h */, 2844163A1B82D6BC0000574D /* WaveTrackLocation.h */, 28FC1AFA0A47762C00A188AE /* WrappedType.h */, + 5ED18DB71CC290AB00FAFE95 /* wxFileNameWrapper.h */, 1790AFDC09883BFD008A330A /* blockfile */, 174D9025098C78AF00D5909F /* commands */, 1790AFFD09883BFD008A330A /* effects */, @@ -3981,6 +3999,7 @@ 1841B4FD0E00AD3D00F386E9 /* ondemand */, 1790B0B509883BFD008A330A /* prefs */, 2897F6DB0AB3DB5A003C20C5 /* toolbars */, + 5E74D2DB1CC4429700D88B0B /* tracks */, 1790B0FD09883BFD008A330A /* widgets */, 1790B10D09883BFD008A330A /* xml */, ); @@ -5516,6 +5535,27 @@ path = expat/lib; sourceTree = ""; }; + 5E74D2DB1CC4429700D88B0B /* tracks */ = { + isa = PBXGroup; + children = ( + 5E74D2DC1CC4429700D88B0B /* ui */, + ); + path = tracks; + sourceTree = ""; + }; + 5E74D2DC1CC4429700D88B0B /* ui */ = { + isa = PBXGroup; + children = ( + 5E74D2DD1CC4429700D88B0B /* EditCursorOverlay.cpp */, + 5E74D2DE1CC4429700D88B0B /* EditCursorOverlay.h */, + 5E74D2DF1CC4429700D88B0B /* PlayIndicatorOverlay.cpp */, + 5E74D2E01CC4429700D88B0B /* PlayIndicatorOverlay.h */, + 5E74D2E11CC4429700D88B0B /* Scrubbing.cpp */, + 5E74D2E21CC4429700D88B0B /* Scrubbing.h */, + ); + path = ui; + sourceTree = ""; + }; ED05D0FF0E50AD5600CC4BD3 /* libscorealign */ = { isa = PBXGroup; children = ( @@ -7289,6 +7329,7 @@ 1790B14F09883BFD008A330A /* SoundTouchEffect.cpp in Sources */, 1790B15109883BFD008A330A /* StereoToMono.cpp in Sources */, 1790B15209883BFD008A330A /* ToneGen.cpp in Sources */, + 5E74D2E31CC4429700D88B0B /* EditCursorOverlay.cpp in Sources */, 1790B15309883BFD008A330A /* TruncSilence.cpp in Sources */, 1790B15409883BFD008A330A /* TwoPassSimpleMono.cpp in Sources */, 1790B15809883BFD008A330A /* Wahwah.cpp in Sources */, @@ -7421,6 +7462,7 @@ 1841B50B0E00AD6E00F386E9 /* ODManager.cpp in Sources */, 1841B50C0E00AD6E00F386E9 /* ODTask.cpp in Sources */, 1841B50D0E00AD6E00F386E9 /* ODTaskThread.cpp in Sources */, + 5E74D2E51CC4429700D88B0B /* Scrubbing.cpp in Sources */, 1841B50E0E00AD6E00F386E9 /* ODWaveTrackTaskQueue.cpp in Sources */, 1841B5110E00AD8D00F386E9 /* ODPCMAliasBlockFile.cpp in Sources */, 2860BA240E0F0D8600A13878 /* SoundActivatedRecord.cpp in Sources */, @@ -7540,6 +7582,7 @@ ED920CAF15B19F61008CA12C /* ModulePrefs.cpp in Sources */, EDD2431416934A6100D9DEC2 /* BassTreble.cpp in Sources */, ED19449A1733F92800F4F5CA /* Reverb.cpp in Sources */, + 5E74D2D81CC4425D00D88B0B /* TrackPanelOverlay.cpp in Sources */, 2849A42017F8BEC2005C653F /* KeyView.cpp in Sources */, 284FD04217FC72A50009A025 /* ScienFilter.cpp in Sources */, 284FD04517FC72EE0009A025 /* Biquad.cpp in Sources */, @@ -7559,6 +7602,7 @@ 28001B3E1A0F0E5D007DD161 /* NumericTextCtrl.cpp in Sources */, 28001B4B1A0F0EB6007DD161 /* SpectralSelectionBar.cpp in Sources */, 28BB98051A15BE6800D1CC80 /* NoiseReduction.cpp in Sources */, + 5E74D2E41CC4429700D88B0B /* PlayIndicatorOverlay.cpp in Sources */, 28D000A51A32920C00367B21 /* DeviceChange.cpp in Sources */, 28D8425C1AD8D69D00551353 /* SelectedRegion.cpp in Sources */, 2888A1631AE25F9A00E06FDC /* Diags.cpp in Sources */, diff --git a/src/Makefile.am b/src/Makefile.am index fc1869b2b..6fa576381 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -233,7 +233,11 @@ audacity_SOURCES = \ TrackPanel.h \ TrackPanelAx.cpp \ TrackPanelAx.h \ + TrackPanelCell.h \ + TrackPanelCellIterator.h \ TrackPanelListener.h \ + TrackPanelOverlay.cpp \ + TrackPanelOverlay.h \ TranslatableStringArray.h \ UndoManager.cpp \ UndoManager.h \ @@ -523,6 +527,12 @@ audacity_SOURCES = \ toolbars/ToolsToolBar.h \ toolbars/TranscriptionToolBar.cpp \ toolbars/TranscriptionToolBar.h \ + tracks/ui/EditCursorOverlay.cpp \ + tracks/ui/EditCursorOverlay.h \ + tracks/ui/PlayIndicatorOverlay.cpp \ + tracks/ui/PlayIndicatorOverlay.h \ + tracks/ui/Scrubbing.cpp \ + tracks/ui/Scrubbing.h \ widgets/AButton.cpp \ widgets/AButton.h \ widgets/ASlider.cpp \ diff --git a/src/Project.cpp b/src/Project.cpp index 4891e4ce4..fab8eec9e 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -156,6 +156,10 @@ scroll information. It also has some status flags. #include "toolbars/ToolsToolBar.h" #include "toolbars/TranscriptionToolBar.h" +#include "tracks/ui/EditCursorOverlay.h" +#include "tracks/ui/PlayIndicatorOverlay.h" +#include "tracks/ui/Scrubbing.h" + #include "commands/ScriptCommandRelay.h" #include "commands/CommandDirectory.h" #include "commands/CommandTargets.h" @@ -924,6 +928,32 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, this, mRuler); + mIndicatorOverlay = std::make_unique(this); + + mCursorOverlay = std::make_unique(this); + +#ifdef EXPERIMENTAL_SCRUBBING_BASIC + // This must follow construction of *mIndicatorOverlay, because it must + // attach its timer event handler later (so that its handler is invoked + // earlier) + mScrubOverlay = std::make_unique(this); +#else + mScrubOverlay = NULL; +#endif + + // This must follow construction of *mScrubOverlay, because it must + // attach its timer event handler later (so that its handler is invoked + // earlier) + this->Connect(EVT_TRACK_PANEL_TIMER, + wxCommandEventHandler(ViewInfo::OnTimer), + NULL, + &mViewInfo); + + // Add the overlays, in the sequence in which they will be painted + mTrackPanel->AddOverlay(mIndicatorOverlay.get()); + mTrackPanel->AddOverlay(mCursorOverlay.get()); + mTrackPanel->AddOverlay(mScrubOverlay.get()); + // LLL: When Audacity starts or becomes active after returning from // another application, the first window that can accept focus // will be given the focus even if we try to SetFocus(). By @@ -1051,6 +1081,12 @@ AudacityProject::~AudacityProject() wxGetApp().GetRecentFiles()->RemoveMenu(mRecentFilesMenu); } + if(mTrackPanel) { + mTrackPanel->RemoveOverlay(mScrubOverlay.get()); + mTrackPanel->RemoveOverlay(mCursorOverlay.get()); + mTrackPanel->RemoveOverlay(mIndicatorOverlay.get()); + } + wxTheApp->Disconnect(EVT_AUDIOIO_CAPTURE, wxCommandEventHandler(AudacityProject::OnCapture), NULL, @@ -2339,6 +2375,11 @@ void AudacityProject::OnCloseWindow(wxCloseEvent & event) #endif } + this->Disconnect(EVT_TRACK_PANEL_TIMER, + wxCommandEventHandler(ViewInfo::OnTimer), + NULL, + &mViewInfo); + Destroy(); mIsBeingDeleted = true; diff --git a/src/Project.h b/src/Project.h index b3b358863..d76ccb15d 100644 --- a/src/Project.h +++ b/src/Project.h @@ -62,6 +62,7 @@ class Tags; class EffectPlugs; class TrackPanel; +class TrackPanelOverlay; class FreqWindow; class ContrastDialog; class Meter; @@ -171,9 +172,13 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame, double GetRate() const { return mRate; } bool ZoomInAvailable() const { return mViewInfo.ZoomInAvailable(); } bool ZoomOutAvailable() const { return mViewInfo.ZoomOutAvailable(); } - double GetSel0() { return mViewInfo.selectedRegion.t0(); } - double GetSel1() { return mViewInfo.selectedRegion.t1(); } + const SelectedRegion &GetSelection() const { return mViewInfo.selectedRegion; } + SelectedRegion &GetSelection() { return mViewInfo.selectedRegion; } + double GetSel0() const { return mViewInfo.selectedRegion.t0(); } + double GetSel1() const { return mViewInfo.selectedRegion.t1(); } const ZoomInfo &GetZoomInfo() const { return mViewInfo; } + const ViewInfo &GetViewInfo() const { return mViewInfo; } + ViewInfo &GetViewInfo() { return mViewInfo; } Track *GetFirstVisible(); void UpdateFirstVisible(); @@ -702,6 +707,10 @@ public: // CommandManager needs to use private methods friend class CommandManager; + // TrackPanelOverlay objects + std::unique_ptr + mIndicatorOverlay, mCursorOverlay, mScrubOverlay; + DECLARE_EVENT_TABLE() }; diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index e917e81fb..d56a6f459 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -155,6 +155,9 @@ is time to refresh some aspect of the screen. #include "Audacity.h" #include "Experimental.h" #include "TrackPanel.h" +#include "TrackPanelCell.h" +#include "TrackPanelCellIterator.h" +#include "TrackPanelOverlay.h" //#define DEBUG_DRAW_TIMING 1 // #define SPECTRAL_EDITING_ESC_KEY @@ -287,12 +290,6 @@ template < class A, class B, class DIST > bool within(A a, B b, DIST d) return (a > b - d) && (a < b + d); } -template < class LOW, class MID, class HIGH > - bool between_incexc(LOW l, MID m, HIGH h) -{ - return (m >= l && m < h); -} - template < class CLIPPEE, class CLIPVAL > void clip_top(CLIPPEE & clippee, CLIPVAL val) { @@ -564,9 +561,6 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id, mSnapLeft = -1; mSnapRight = -1; - mLastCursorX = mNewCursorX = -1; - mLastIndicatorX = mNewIndicatorX = -1; - mCursorTime = -1.0; mOldQPIndicatorPos = -1; // Register for tracklist updates @@ -1023,11 +1017,9 @@ void TrackPanel::OnTimer(wxTimerEvent& ) const double playPos = gAudioIO->GetStreamTime(); - // The sequence of the next two is important. #ifdef EXPERIMENTAL_SCRUBBING_BASIC TimerUpdateScrubbing(playPos); #endif - TimerUpdateIndicator(playPos); DrawOverlays(false); @@ -1140,249 +1132,6 @@ double TrackPanel::GetScreenEndTime() const return mViewInfo->PositionToTime(width, true); } -void TrackPanel::TimerUpdateIndicator(double playPos) -{ - if (!IsAudioActive()) - mNewIndicatorX = -1; - else { - // Calculate the horizontal position of the indicator - - AudacityProject *p = GetProject(); - const bool - onScreen = playPos >= 0.0 && - between_incexc(mViewInfo->h, - playPos, - GetScreenEndTime()); - - // This displays the audio time, too... - DisplaySelection(); - - // BG: Scroll screen if option is set - // msmeyer: But only if not playing looped or in one-second mode - if( mViewInfo->bUpdateTrackIndicator && - p->mLastPlayMode != loopedPlay && - p->mLastPlayMode != oneSecondPlay && - playPos >= 0 && - !onScreen && - !gAudioIO->IsPaused() ) - { - mListener->TP_ScrollWindow( playPos ); - } - - // Always update scrollbars even if not scrolling the window. This is - // important when NEW audio is recorded, because this can change the - // length of the project and therefore the appearance of the scrollbar. - MakeParentRedrawScrollbars(); - - mNewIndicatorX = mViewInfo->TimeToPosition(playPos, GetLeftOffset()); - } -} - -std::pair TrackPanel::GetIndicatorRectangle() -{ - wxRect rect(mLastIndicatorX, 0, 1, mBacking->GetHeight()); -#if defined(__WXMAC__) - rect.Inflate(1, 0); -#endif - - return std::make_pair( - rect, - mLastIndicatorX != mNewIndicatorX - ); -} - -void TrackPanel::UndrawIndicator(wxDC & dc) -{ - // AS: The "indicator" is the little graphical mark shown in the ruler - // that indicates where the current play/record position is. (This also - // draws the moving vertical line.) - - // Erase the old indicator. - if (mLastIndicatorX != -1) - { - int width; - GetTracksUsableArea(&width, NULL); - const bool - onScreen = between_incexc(GetLeftOffset(), - mLastIndicatorX, - GetLeftOffset() + width); - if (onScreen) - { - // LL: Keep from trying to blit outsize of the source DC. This results in a crash on - // OSX due to allocating memory using negative sizes and can be caused by resizing - // the project window while recording or playing. - int w = dc.GetSize().GetWidth(); - if (mLastIndicatorX >= w) { - mLastIndicatorX = w - 1; - } - - // Restore the old position from the backing DC. -#if defined(__WXMAC__) - // On OSX, if a HiDPI resolution is being used, the line will actually take up - // more than 1 pixel (even though it is drawn as 1), so we restore the surrounding - // pixels as well. (This is because the wxClientDC doesn't know about the scaling.) - dc.Blit(mLastIndicatorX - 1, 0, 3, mBacking->GetHeight(), &mBackingDC, mLastIndicatorX - 1, 0); -#else - dc.Blit(mLastIndicatorX, 0, 1, mBacking->GetHeight(), &mBackingDC, mLastIndicatorX, 0); -#endif - } - - mRuler->ClearIndicator(); - } -} - -void TrackPanel::DoDrawIndicator(wxDC & dc) -{ - mLastIndicatorX = mNewIndicatorX; - if (mLastIndicatorX == -1) - return; - - double pos = mViewInfo->PositionToTime(mLastIndicatorX, GetLeftOffset()); - - // Set play/record color - bool rec = (gAudioIO->GetNumCaptureChannels() > 0); - AColor::IndicatorColor( &dc, !rec); - - mRuler->DrawIndicator( pos, rec ); - - // Ensure that we don't draw through the TrackInfo or vertical ruler. - wxRect clip = GetRect(); - int leftCutoff = clip.x + GetLeftOffset(); - int rightCutoff = clip.x + clip.width - kRightMargin; - if (!between_incexc(leftCutoff, mLastIndicatorX, rightCutoff)) - { - return; - } - - // Draw indicator in all visible tracks - VisibleTrackIterator iter( GetProject() ); - for( Track *t = iter.First(); t; t = iter.Next() ) - { - // Don't draw the indicator in label tracks - if( t->GetKind() == Track::Label ) - { - continue; - } - - // Convert virtual coordinate to physical - int y = t->GetY() - mViewInfo->vpos; - - // Draw the NEW indicator in its new location - AColor::Line(dc, - mLastIndicatorX, - y + kTopMargin, - mLastIndicatorX, - // Minus one more because AColor::Line includes both endpoints - y + t->GetHeight() - kBottomMargin - 1); - } -} - -#if 0 -// now unused -/// This method draws the cursor things, both in the -/// ruler as seen at the top of the screen, but also in each of the -/// selected tracks. -/// These are the 'vertical lines' through waves, notes, and ruler. -void TrackPanel::DrawCursor() -{ - wxClientDC dc( this ); - DoDrawCursor( dc ); -} -#endif - -std::pair TrackPanel::GetCursorRectangle() -{ - if (!mViewInfo->selectedRegion.isPoint()) { - mCursorTime = -1.0; - mNewCursorX = -1; - } - else { - mCursorTime = mViewInfo->selectedRegion.t0(); - mNewCursorX = mViewInfo->TimeToPosition(mCursorTime, GetLeftOffset()); - } - - wxRect rect(mLastCursorX, 0, 1, mBacking->GetHeight()); -#if defined(__WXMAC__) - rect.Inflate(1, 0); -#endif - - return std::make_pair( - rect, - mLastCursorX != mNewCursorX - ); -} - -void TrackPanel::UndrawCursor(wxDC & dc) -{ - bool onScreen; - - if( mLastCursorX != -1 ) - { - int width; - GetTracksUsableArea(&width, NULL); - onScreen = between_incexc(GetLeftOffset(), - mLastCursorX, - GetLeftOffset() + width); - if( onScreen ) -#if defined(__WXMAC__) - // On OSX, if a HiDPI resolution is being used, the line will actually take up - // more than 1 pixel (even though it is drawn as 1), so we restore the surrounding - // pixels as well. (This is because the wxClientDC doesn't know about the scaling.) - dc.Blit(mLastCursorX - 1, 0, 3, mBacking->GetHeight(), &mBackingDC, mLastCursorX - 1, 0); -#else - dc.Blit(mLastCursorX, 0, 1, mBacking->GetHeight(), &mBackingDC, mLastCursorX, 0); -#endif - } -} - -void TrackPanel::DoDrawCursor(wxDC & dc) -{ - mLastCursorX = mNewCursorX; - if (mLastCursorX == -1) - return; - - const bool - onScreen = between_incexc(mViewInfo->h, - mCursorTime, - GetScreenEndTime() ); - - if( !onScreen ) - return; - - AColor::CursorColor(&dc); - - // Draw cursor in all selected tracks - VisibleTrackIterator iter( GetProject() ); - for( Track *t = iter.First(); t; t = iter.Next() ) - { - if( t->GetSelected() || mAx->IsFocused( t ) ) - { - int y = t->GetY() - mViewInfo->vpos; - wxCoord top = y + kTopMargin; - // Minus one more because AColor::Line includes both endpoints - wxCoord bottom = y + t->GetHeight() - kBottomMargin - 1; - - // MB: warp() is not needed here as far as I know, in fact it creates a bug. Removing it fixes that. - AColor::Line(dc, mLastCursorX, top, mLastCursorX, bottom); // <-- The whole point of this routine. - -#ifdef EXPERIMENTAL_OUTPUT_DISPLAY - if(MONO_WAVE_PAN(t)){ - y = t->GetY(true) - mViewInfo->vpos + 1; - top = y + kTopInset; - bottom = y + t->GetHeight(true) - kTopInset; - AColor::Line( dc, mLastCursorX, top, mLastCursorX, bottom ); - } -#endif - - } - } - - // AS: Ah, no, this is where we draw the blinky thing in the ruler. - mRuler->DrawCursor(mCursorTime); - - DisplaySelection(); -} - /// OnSize() is called when the panel is resized void TrackPanel::OnSize(wxSizeEvent & /* event */) { @@ -7890,80 +7639,81 @@ void TrackPanel::DrawOutsideOfTrack(Track * t, wxDC * dc, const wxRect & rect) #endif } +void TrackPanel::AddOverlay(TrackPanelOverlay *pOverlay) +{ + mOverlays.push_back(pOverlay); +} + +bool TrackPanel::RemoveOverlay(TrackPanelOverlay *pOverlay) +{ + const size_t oldSize = mOverlays.size(); + std::remove(mOverlays.begin(), mOverlays.end(), pOverlay); + return oldSize != mOverlays.size(); +} + +void TrackPanel::ClearOverlays() +{ + mOverlays.clear(); +} + void TrackPanel::DrawOverlays(bool repaint) { - // Determine which overlays are outdated. - enum { - n_pairs = -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - 3 -#else - 2 -#endif - }; - std::pair pairs[n_pairs] = { - GetIndicatorRectangle(), - GetCursorRectangle(), -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - GetScrubSpeedRectangle(), -#endif - }; + size_t n_pairs = mOverlays.size(); - { - // Drawing now goes directly to the client area - wxClientDC dc(this); + std::vector< std::pair > pairs; + pairs.reserve(n_pairs); - // See what requires redrawing. If repainting, all. - // If not, then whatever is outdated, and whatever will be damaged by - // undrawing. - // By redrawing only what needs it, we avoid flashing things like - // the cursor that are drawn with xor. - if (!repaint) { - bool done; - do { - done = true; - for (int ii = 0; ii < n_pairs; ++ii) { - for (int jj = ii + 1; jj < n_pairs; ++jj) { - if (pairs[ii].second != pairs[jj].second && - pairs[ii].first.Intersects(pairs[jj].first)) { - done = false; - pairs[ii].second = pairs[jj].second = true; - } + // Find out the rectangles and outdatedness for each overlay + wxSize size(mBackingDC.GetSize()); + for (const auto pOverlay : mOverlays) + pairs.push_back(pOverlay->GetRectangle(size)); + + // See what requires redrawing. If repainting, all. + // If not, then whatever is outdated, and whatever will be damaged by + // undrawing. + // By redrawing only what needs it, we avoid flashing things like + // the cursor that are drawn with invert. + if (!repaint) { + bool done; + do { + done = true; + for (size_t ii = 0; ii < n_pairs; ++ii) { + for (size_t jj = ii + 1; jj < n_pairs; ++jj) { + if (pairs[ii].second != pairs[jj].second && + pairs[ii].first.Intersects(pairs[jj].first)) { + done = false; + pairs[ii].second = pairs[jj].second = true; } } - } while (!done); + } + } while (!done); + } + + // Erase + bool done = true; + auto it2 = pairs.begin(); + for (auto pOverlay : mOverlays) { + if (repaint || it2->second) { + done = false; + wxClientDC dc(this); + pOverlay->Erase(dc, mBackingDC); + } + ++it2; + } + + // Draw + if (!done) { + it2 = pairs.begin(); + for (auto pOverlay : mOverlays) { + if (repaint || it2->second) { + wxClientDC dc(this); + TrackPanelCellIterator begin(this, true); + TrackPanelCellIterator end(this, false); + pOverlay->Draw(dc, begin, end); + } + ++it2; } } - - if (repaint || pairs[0].second) { - wxClientDC dc(this); - UndrawIndicator(dc); - } - if (repaint || pairs[1].second) { - wxClientDC dc(this); - UndrawCursor(dc); - } -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - if (repaint || pairs[2].second) { - wxClientDC dc(this); - UndrawScrubSpeed(dc); - } -#endif - - if (repaint || pairs[0].second) { - wxClientDC dc(this); - DoDrawIndicator(dc); - } - if (repaint || pairs[1].second) { - wxClientDC dc(this); - DoDrawCursor(dc); - } -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - if (repaint || pairs[2].second) { - wxClientDC dc(this); - DoDrawScrubSpeed(dc); - } -#endif } /// Draw a three-level highlight gradient around the focused track. @@ -10036,6 +9786,46 @@ LWSlider * TrackInfo::PanSlider(WaveTrack *t, bool captured) const return captured ? mPanCaptured : mPan; } +TrackPanelCellIterator::TrackPanelCellIterator(TrackPanel *trackPanel, bool begin) + : mPanel(trackPanel) + , mIter(trackPanel->GetProject()) + , mpCell(begin ? mIter.First() : NULL) +{ +} + +TrackPanelCellIterator &TrackPanelCellIterator::operator++ () +{ + mpCell = mIter.Next(); + return *this; +} + +TrackPanelCellIterator TrackPanelCellIterator::operator++ (int) +{ + TrackPanelCellIterator copy(*this); + ++ *this; + return copy; +} + +auto TrackPanelCellIterator::operator* () const -> value_type +{ + if (!mpCell) + return std::make_pair((Track*)nullptr, wxRect()); + + // Convert virtual coordinate to physical + int width; + mPanel->GetTracksUsableArea(&width, NULL); + int y = mpCell->GetY() - mPanel->GetViewInfo()->vpos; + return std::make_pair( + mpCell, + wxRect( + mPanel->GetLeftOffset(), + y + kTopMargin, + width, + mpCell->GetHeight() - (kTopMargin + kBottomMargin) + ) + ); +} + static TrackPanel * TrackPanelFactory(wxWindow * parent, wxWindowID id, const wxPoint & pos, diff --git a/src/TrackPanel.h b/src/TrackPanel.h index e3b316418..e901f47c9 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -35,6 +35,7 @@ class wxRect; class LabelTrack; class SpectrumAnalyst; class TrackPanel; +class TrackPanelOverlay; class TrackArtist; class Ruler; class SnapManager; @@ -250,18 +251,6 @@ class AUDACITY_DLL_API TrackPanel final : public wxPanel { virtual bool IsOverCutline(WaveTrack * track, wxRect &rect, wxMouseEvent &event); virtual void HandleTrackSpecificMouseEvent(wxMouseEvent & event); - virtual void TimerUpdateIndicator(double playPos); - // Second member of pair indicates whether the indicator is out of date: - virtual std::pair GetIndicatorRectangle(); - virtual void UndrawIndicator(wxDC & dc); - /// draws the green line on the tracks to show playback position - virtual void DoDrawIndicator(wxDC & dc); - - // Second member of pair indicates whether the cursor is out of date: - virtual std::pair GetCursorRectangle(); - virtual void UndrawCursor(wxDC & dc); - virtual void DoDrawCursor(wxDC & dc); - #ifdef EXPERIMENTAL_SCRUBBING_BASIC bool ShouldDrawScrubSpeed(); virtual void TimerUpdateScrubbing(double playPos); @@ -547,6 +536,15 @@ protected: virtual void DrawOutsideOfTrack (Track *t, wxDC* dc, const wxRect & rect); public: + // Register and unregister overlay objects. + // The sequence in which they were registered is the sequence in + // which they are painted. + // TrackPanel is not responsible for their memory management. + virtual void AddOverlay(TrackPanelOverlay *pOverlay); + // Returns true if the overlay was found + virtual bool RemoveOverlay(TrackPanelOverlay *pOverlay); + virtual void ClearOverlays(); + // Erase and redraw things like the cursor, cheaply and directly to the // client area, without full refresh. virtual void DrawOverlays(bool repaint); @@ -597,14 +595,6 @@ protected: TrackPanel *parent; } mTimer; - // This stores the parts of the screen that get overwritten by the indicator - // and cursor - int mLastIndicatorX; - int mNewIndicatorX; - int mLastCursorX; - double mCursorTime; - int mNewCursorX; - // Quick-Play indicator postion int mOldQPIndicatorPos; @@ -854,6 +844,11 @@ protected: TrackPanelAx *mAx; +public: + TrackPanelAx &GetAx() { return *mAx; } + +protected: + wxString mSoloPref; // Keeps track of extra fractional vertical scroll steps @@ -867,6 +862,10 @@ protected: public: wxSize vrulerSize; + protected: + std::vector mOverlays; + + public: DECLARE_EVENT_TABLE() }; diff --git a/src/TrackPanelCell.h b/src/TrackPanelCell.h new file mode 100644 index 000000000..ca5adcde1 --- /dev/null +++ b/src/TrackPanelCell.h @@ -0,0 +1,19 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +TrackPanelCell.h + +Paul Licameli + +**********************************************************************/ + +#ifndef __AUDACITY_TRACK_PANEL_CELL__ +#define __AUDACITY_TRACK_PANEL_CELL__ + +// Future: TrackPanelCell will be generalized to a new abstract base class of Track +// and of other things. +class Track; +using TrackPanelCell = Track; + +#endif diff --git a/src/TrackPanelCellIterator.h b/src/TrackPanelCellIterator.h new file mode 100644 index 000000000..bba5fb3af --- /dev/null +++ b/src/TrackPanelCellIterator.h @@ -0,0 +1,52 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +TrackPanelCellIterator.h + +Paul Licameli + +**********************************************************************/ + +#ifndef __AUDACITY_TRACK_PANEL_CELL_ITERATOR__ +#define __AUDACITY_TRACK_PANEL_CELL_ITERATOR__ + +#include "Track.h" + +class Track; +typedef Track TrackPanelCell; + +class TrackPanel; + +// A class that allows iteration over the rectangles of visible cells. +class TrackPanelCellIterator +{ +public: + TrackPanelCellIterator(TrackPanel *trackPanel, bool begin); + + // implement the STL iterator idiom + + TrackPanelCellIterator &operator++ (); + TrackPanelCellIterator operator++ (int); + + friend inline bool operator== + (const TrackPanelCellIterator &lhs, const TrackPanelCellIterator &rhs) + { + return lhs.mpCell == rhs.mpCell; + } + + using value_type = std::pair; + value_type operator * () const; + +private: + TrackPanel *mPanel; + VisibleTrackIterator mIter; + TrackPanelCell *mpCell; +}; + +inline bool operator != +(const TrackPanelCellIterator &lhs, const TrackPanelCellIterator &rhs) +{ + return !(lhs == rhs); +} +#endif diff --git a/src/TrackPanelOverlay.cpp b/src/TrackPanelOverlay.cpp new file mode 100644 index 000000000..7179e97b4 --- /dev/null +++ b/src/TrackPanelOverlay.cpp @@ -0,0 +1,40 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +TrackPanelOverlay.cpp + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#include "TrackPanelOverlay.h" + +#include + +TrackPanelOverlay::~TrackPanelOverlay() +{ +} + +std::pair TrackPanelOverlay::GetRectangle(wxSize size) +{ + auto result = DoGetRectangle(size); +#ifdef __WXMAC__ + // On OSX, if a HiDPI resolution is being used, a vertical line will actually take up + // more than 1 pixel (even though it is drawn as 1), so we restore the surrounding + // pixels as well. (This is because the wxClientDC doesn't know about the scaling. + result.first.Inflate(1, 0); +#endif + return result; +} + +void TrackPanelOverlay::Erase(wxDC &dc, wxDC &src) +{ + wxRect rect(dc.GetSize()); + rect.Intersect(src.GetSize()); + auto smallRect(GetRectangle(src.GetSize()).first); + rect.Intersect(smallRect); + if (!rect.IsEmpty()) + dc.Blit(rect.x, rect.y, rect.width, rect.height, + &src, rect.x, rect.y); +} diff --git a/src/TrackPanelOverlay.h b/src/TrackPanelOverlay.h new file mode 100644 index 000000000..752c0db87 --- /dev/null +++ b/src/TrackPanelOverlay.h @@ -0,0 +1,43 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +TrackPanelOverlay.h + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#ifndef __AUDACITY_TRACK_PANEL_OVERLAY__ +#define __AUDACITY_TRACK_PANEL_OVERLAY__ + +class TrackPanelCellIterator; + +#include +class wxDC; +class wxRect; +class wxSize; + +class TrackPanelOverlay +{ +public: + virtual ~TrackPanelOverlay() = 0; + + // nonvirtual wrapper + std::pair GetRectangle(wxSize size); + + // size passes the dimensions of the backing dc + // First member of pair is the rectangle that would be erased + // Second member of pair indicates whether the overlay is out of date + virtual std::pair DoGetRectangle(wxSize size) = 0; + + // Default implementation blits from backing store over GetRectangle().first + virtual void Erase(wxDC &dc, wxDC &src); + + // Draw; dc.GetSize() tells you the total dimensions, and the iterators let you + // find the rectangles of tracks (or other sub-rectangles of the panel) + virtual void Draw + (wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) = 0; +}; + +#endif diff --git a/src/ViewInfo.cpp b/src/ViewInfo.cpp index 4d3f59fc2..b3ea4db9d 100644 --- a/src/ViewInfo.cpp +++ b/src/ViewInfo.cpp @@ -13,6 +13,7 @@ Paul Licameli #include +#include "AudioIO.h" #include "Internat.h" #include "prefs/GUISettings.h" #include "Prefs.h" @@ -123,6 +124,7 @@ ViewInfo::ViewInfo(double start, double screenDuration, double pixelsPerSecond) , scrollStep(16) , bUpdateTrackIndicator(true) , bScrollBeyondZero(false) + , mRecentStreamTime(-1.0) { UpdatePrefs(); } @@ -176,3 +178,9 @@ bool ViewInfo::ReadXMLAttribute(const wxChar *attr, const wxChar *value) return false; } + +void ViewInfo::OnTimer(wxCommandEvent &event) +{ + mRecentStreamTime = gAudioIO->GetStreamTime(); + event.Skip(); +} diff --git a/src/ViewInfo.h b/src/ViewInfo.h index 116960c55..af21a622d 100644 --- a/src/ViewInfo.h +++ b/src/ViewInfo.h @@ -12,6 +12,7 @@ #define __AUDACITY_VIEWINFO__ #include +#include #include "SelectedRegion.h" @@ -131,10 +132,10 @@ public: // Exclusive: wxInt64 GetFisheyeRightBoundary(wxInt64 WXUNUSED(origin = 0)) const {return 0;} // stub - }; -class AUDACITY_DLL_API ViewInfo final : public ZoomInfo +class AUDACITY_DLL_API ViewInfo final + : public wxEvtHandler, public ZoomInfo { public: ViewInfo(double start, double screenDuration, double pixelsPerSecond); @@ -180,8 +181,15 @@ public: bool bScrollBeyondZero; + // During timer update, grab the volatile stream time just once, so that + // various other drawing code can use the exact same value. + double mRecentStreamTime; + void WriteXMLAttributes(XMLWriter &xmlFile); bool ReadXMLAttribute(const wxChar *attr, const wxChar *value); + + // Receive track panel timer notifications + void OnTimer(wxCommandEvent &event); }; #endif diff --git a/src/tracks/ui/EditCursorOverlay.cpp b/src/tracks/ui/EditCursorOverlay.cpp new file mode 100644 index 000000000..15f147d3f --- /dev/null +++ b/src/tracks/ui/EditCursorOverlay.cpp @@ -0,0 +1,118 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +EditCursorOverlay.cpp + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#include "../../Audacity.h" +#include "EditCursorOverlay.h" +#include "../../Experimental.h" + +#include "../../AColor.h" +#include "../../widgets/Ruler.h" +#include "../../Project.h" +//#include "../../TrackPanel.h" +#include "../../TrackPanelCell.h" +#include "../../TrackPanelCellIterator.h" +#include "../../TrackPanelAx.h" +#include "../../ViewInfo.h" + +#include + +namespace { + template < class LOW, class MID, class HIGH > + bool between_incexc(LOW l, MID m, HIGH h) + { + return (m >= l && m < h); + } +} + +EditCursorOverlay::EditCursorOverlay(AudacityProject *project) + : mProject(project) + , mLastCursorX(-1) + , mCursorTime(-1) + , mNewCursorX(-1) +{ +} + +EditCursorOverlay::~EditCursorOverlay() +{ +} + +std::pair EditCursorOverlay::DoGetRectangle(wxSize size) +{ + const SelectedRegion &selection = mProject->GetSelection(); + if (!selection.isPoint()) { + mCursorTime = -1.0; + mNewCursorX = -1; + } + else { + mCursorTime = selection.t0(); + mNewCursorX = mProject->GetZoomInfo().TimeToPosition + (mCursorTime, mProject->GetTrackPanel()->GetLeftOffset()); + } + + return std::make_pair( + mLastCursorX == -1 + ? wxRect() + : wxRect(mLastCursorX, 0, 1, size.GetHeight()), + mLastCursorX != mNewCursorX + ); +} + + +void EditCursorOverlay::Draw + (wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) +{ + mLastCursorX = mNewCursorX; + if (mLastCursorX == -1) + return; + + const ZoomInfo &viewInfo = mProject->GetZoomInfo(); + + const bool + onScreen = between_incexc(viewInfo.h, + mCursorTime, + mProject->GetScreenEndTime()); + + if (!onScreen) + return; + + AColor::CursorColor(&dc); + + // Draw cursor in all selected tracks + for (; begin != end; ++begin) + { + TrackPanelCellIterator::value_type data(*begin); + Track *const pTrack = data.first; + if (!pTrack) + continue; + if (pTrack->GetSelected() || + mProject->GetTrackPanel()->GetAx().IsFocused(pTrack)) + { + const wxRect &rect = data.second; + // AColor::Line includes both endpoints so use GetBottom() + AColor::Line(dc, mLastCursorX, rect.GetTop(), mLastCursorX, rect.GetBottom()); // <-- The whole point of this routine. + +#ifdef EXPERIMENTAL_OUTPUT_DISPLAY + if (MONO_WAVE_PAN(t)){ + y = t->GetY(true) - mViewInfo->vpos + 1; + top = y + kTopInset; + bottom = y + t->GetHeight(true) - kTopInset; + AColor::Line(dc, mLastCursorX, top, mLastCursorX, bottom); + } +#endif + + } + } + + // AS: Ah, no, this is where we draw the blinky thing in the ruler. + mProject->GetRulerPanel()->DrawCursor(mCursorTime); + + // This updates related displays such as numbers on the status bar + mProject->TP_DisplaySelection(); +} diff --git a/src/tracks/ui/EditCursorOverlay.h b/src/tracks/ui/EditCursorOverlay.h new file mode 100644 index 000000000..2c13ec75b --- /dev/null +++ b/src/tracks/ui/EditCursorOverlay.h @@ -0,0 +1,36 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +EditCursorOverlay.h + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#ifndef __AUDACITY_EDIT_CURSOR_OVERLAY__ +#define __AUDACITY_EDIT_CURSOR_OVERLAY__ + +#include "../../TrackPanelOverlay.h" + +class AudacityProject; + +class EditCursorOverlay final : public TrackPanelOverlay +{ +public: + EditCursorOverlay(AudacityProject *project); + virtual ~EditCursorOverlay(); + +private: + std::pair DoGetRectangle(wxSize size) override; + void Draw + (wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) override; + + AudacityProject *mProject; + + int mLastCursorX; + double mCursorTime; + int mNewCursorX; +}; + +#endif diff --git a/src/tracks/ui/PlayIndicatorOverlay.cpp b/src/tracks/ui/PlayIndicatorOverlay.cpp new file mode 100644 index 000000000..1359b84c2 --- /dev/null +++ b/src/tracks/ui/PlayIndicatorOverlay.cpp @@ -0,0 +1,151 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +PlayIndicatorOverlay.cpp + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#include "PlayIndicatorOverlay.h" + +#include "../../AColor.h" +#include "../../AudioIO.h" +#include "../../Project.h" +#include "../../TrackPanel.h" +#include "../../TrackPanelCell.h" +#include "../../TrackPanelCellIterator.h" +#include "../../widgets/Ruler.h" + +#include + +#include + +namespace { + template < class LOW, class MID, class HIGH > + bool between_incexc(LOW l, MID m, HIGH h) + { + return (m >= l && m < h); + } +} + +PlayIndicatorOverlay::PlayIndicatorOverlay(AudacityProject *project) + : mProject(project) + , mLastIndicatorX(-1) + , mNewIndicatorX(-1) +{ + mProject->Connect(EVT_TRACK_PANEL_TIMER, + wxCommandEventHandler(PlayIndicatorOverlay::OnTimer), + NULL, + this); +} + +PlayIndicatorOverlay::~PlayIndicatorOverlay() +{ + mProject->Disconnect(EVT_TRACK_PANEL_TIMER, + wxCommandEventHandler(PlayIndicatorOverlay::OnTimer), + NULL, + this); +} + +std::pair PlayIndicatorOverlay::DoGetRectangle(wxSize size) +{ + wxRect rect(mLastIndicatorX, 0, 1, size.GetHeight()); + return std::make_pair( + rect, + mLastIndicatorX != mNewIndicatorX + ); +} + + +void PlayIndicatorOverlay::Draw + (wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) +{ + mLastIndicatorX = mNewIndicatorX; + if (!between_incexc(0, mLastIndicatorX, dc.GetSize().GetWidth())) + return; + + const ZoomInfo &viewInfo = mProject->GetZoomInfo(); + TrackPanel *const trackPanel = mProject->GetTrackPanel(); + + double pos = viewInfo.PositionToTime(mLastIndicatorX, trackPanel->GetLeftOffset()); + + // Set play/record color + bool rec = (gAudioIO->GetNumCaptureChannels() > 0); + AColor::IndicatorColor(&dc, !rec); + + mProject->GetRulerPanel()->DrawIndicator(pos, rec); + + // Draw indicator in all visible tracks + for (; begin != end; ++begin) + { + TrackPanelCellIterator::value_type data(*begin); + Track *const pTrack = data.first; + if (!pTrack) + continue; + + // Don't draw the indicator in label tracks + if (pTrack->GetKind() == Track::Label) + { + continue; + } + + // Draw the NEW indicator in its NEW location + // AColor::Line includes both endpoints so use GetBottom() + const wxRect &rect = data.second; + AColor::Line(dc, + mLastIndicatorX, + rect.GetTop(), + mLastIndicatorX, + rect.GetBottom()); + } +} + +void PlayIndicatorOverlay::Erase(wxDC &dc, wxDC &src) +{ + TrackPanelOverlay::Erase(dc, src); + mProject->GetRulerPanel()->ClearIndicator(); +} + +void PlayIndicatorOverlay::OnTimer(wxCommandEvent &event) +{ + // Let other listeners get the notification + event.Skip(); + + if (!mProject->IsAudioActive()) + mNewIndicatorX = -1; + else { + ViewInfo &viewInfo = mProject->GetViewInfo(); + + // Calculate the horizontal position of the indicator + const double playPos = viewInfo.mRecentStreamTime; + + const bool onScreen = playPos >= 0.0 && + between_incexc(viewInfo.h, + playPos, + mProject->GetScreenEndTime()); + + // This displays the audio time, too... + mProject->TP_DisplaySelection(); + + // BG: Scroll screen if option is set + // msmeyer: But only if not playing looped or in one-second mode + if (viewInfo.bUpdateTrackIndicator && + mProject->mLastPlayMode != loopedPlay && + mProject->mLastPlayMode != oneSecondPlay && + playPos >= 0 && + !onScreen && + !gAudioIO->IsPaused()) + { + mProject->TP_ScrollWindow(playPos); + } + + // Always update scrollbars even if not scrolling the window. This is + // important when NEW audio is recorded, because this can change the + // length of the project and therefore the appearance of the scrollbar. + mProject->TP_RedrawScrollbars(); + + mNewIndicatorX = viewInfo.TimeToPosition(playPos, mProject->GetTrackPanel()->GetLeftOffset()); + } +} diff --git a/src/tracks/ui/PlayIndicatorOverlay.h b/src/tracks/ui/PlayIndicatorOverlay.h new file mode 100644 index 000000000..643526946 --- /dev/null +++ b/src/tracks/ui/PlayIndicatorOverlay.h @@ -0,0 +1,40 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +PlayIndicatorOverlay.h + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#ifndef __AUDACITY_PLAY_INDICATOR_OVERLAY__ +#define __AUDACITY_PLAY_INDICATOR_OVERLAY__ + +#include "../../TrackPanelOverlay.h" +#include + +class AudacityProject; + + +class PlayIndicatorOverlay final : public wxEvtHandler, public TrackPanelOverlay +{ +public: + PlayIndicatorOverlay(AudacityProject *project); + virtual ~PlayIndicatorOverlay(); + +private: + std::pair DoGetRectangle(wxSize size) override; + void Draw + (wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) override; + void Erase(wxDC &dc, wxDC &src) override; + + void OnTimer(wxCommandEvent &event); + + + AudacityProject *mProject; + int mLastIndicatorX; + int mNewIndicatorX; +}; + +#endif diff --git a/src/tracks/ui/Scrubbing.cpp b/src/tracks/ui/Scrubbing.cpp new file mode 100644 index 000000000..4a262b545 --- /dev/null +++ b/src/tracks/ui/Scrubbing.cpp @@ -0,0 +1,53 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +Scrubbing.cpp + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#include "Scrubbing.h" + +#include "../../Project.h" +#include "../../TrackPanel.h" +#include "../../TrackPanelCell.h" +#include "../../TrackPanelCellIterator.h" + +#include + +ScrubbingOverlay::ScrubbingOverlay(AudacityProject *project) + : mProject(project) +{ + mProject->Connect(EVT_TRACK_PANEL_TIMER, + wxCommandEventHandler(ScrubbingOverlay::OnTimer), + NULL, + this); +} + +ScrubbingOverlay::~ScrubbingOverlay() +{ + mProject->Disconnect(EVT_TRACK_PANEL_TIMER, + wxCommandEventHandler(ScrubbingOverlay::OnTimer), + NULL, + this); +} + +std::pair ScrubbingOverlay::DoGetRectangle(wxSize) +{ + return std::make_pair(wxRect(), false); +} + +void ScrubbingOverlay::Draw + (wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) +{ +} + +void ScrubbingOverlay::OnTimer(wxCommandEvent &event) +{ + // Let other listeners get the notification + event.Skip(); + + // To do: move code here +} diff --git a/src/tracks/ui/Scrubbing.h b/src/tracks/ui/Scrubbing.h new file mode 100644 index 000000000..c6c60ac71 --- /dev/null +++ b/src/tracks/ui/Scrubbing.h @@ -0,0 +1,36 @@ +/********************************************************************** + +Audacity: A Digital Audio Editor + +Scrubbing.h + +Paul Licameli split from TrackPanel.cpp + +**********************************************************************/ + +#ifndef __AUDACITY_SCRUBBING__ +#define __AUDACITY_SCRUBBING__ + +#include + +#include "../../TrackPanelOverlay.h" + +class AudacityProject; + +class ScrubbingOverlay : public wxEvtHandler, public TrackPanelOverlay +{ +public: + ScrubbingOverlay(AudacityProject *project); + virtual ~ScrubbingOverlay(); + +private: + std::pair DoGetRectangle(wxSize size) override; + void Draw + (wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) override; + + void OnTimer(wxCommandEvent &event); + + AudacityProject *mProject; +}; + +#endif diff --git a/win/Projects/Audacity/Audacity.vcxproj b/win/Projects/Audacity/Audacity.vcxproj index a95af18d5..923d36a64 100755 --- a/win/Projects/Audacity/Audacity.vcxproj +++ b/win/Projects/Audacity/Audacity.vcxproj @@ -1,4 +1,4 @@ - + @@ -213,6 +213,10 @@ + + + + @@ -435,7 +439,13 @@ + + + + + + @@ -1097,4 +1107,4 @@ - \ No newline at end of file + diff --git a/win/Projects/Audacity/Audacity.vcxproj.filters b/win/Projects/Audacity/Audacity.vcxproj.filters index 6ecb3975e..aea512d60 100755 --- a/win/Projects/Audacity/Audacity.vcxproj.filters +++ b/win/Projects/Audacity/Audacity.vcxproj.filters @@ -1,4 +1,4 @@ - + @@ -69,6 +69,12 @@ {99325e00-3930-4e03-a599-999f275db78e} + + {7b403d1f-de11-43d5-becc-d9d118d06f18} + + + {aa9627ea-e614-4704-bf68-4a347023569f} + @@ -275,9 +281,6 @@ src - - src - src @@ -857,6 +860,21 @@ src + + src + + + src\tracks\ui + + + src\tracks\ui + + + src\tracks\ui + + + src + @@ -1714,6 +1732,9 @@ src\effects\VST + + src + src @@ -1732,6 +1753,21 @@ src + + src + + + src\tracks\ui + + + src\tracks\ui + + + src\tracks\ui + + + src + @@ -1952,4 +1988,4 @@ plug-ins - \ No newline at end of file + From 8b7ae748a3151a9b816f20e6dd1b06de4ea29e3b Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Thu, 27 Aug 2015 01:13:46 -0400 Subject: [PATCH 2/3] Scrub speed as overlay; scrub event handling details out of TrackPanel.cpp --- src/Project.cpp | 7 +- src/Project.h | 11 +- src/TrackPanel.cpp | 570 +--------------------------------- src/TrackPanel.h | 60 +--- src/toolbars/ToolsToolBar.cpp | 30 +- src/tracks/ui/Scrubbing.cpp | 487 ++++++++++++++++++++++++++++- src/tracks/ui/Scrubbing.h | 61 +++- 7 files changed, 592 insertions(+), 634 deletions(-) diff --git a/src/Project.cpp b/src/Project.cpp index fab8eec9e..b88ff9a5d 100644 --- a/src/Project.cpp +++ b/src/Project.cpp @@ -937,8 +937,7 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, // attach its timer event handler later (so that its handler is invoked // earlier) mScrubOverlay = std::make_unique(this); -#else - mScrubOverlay = NULL; + mScrubber = std::make_unique(this); #endif // This must follow construction of *mScrubOverlay, because it must @@ -952,7 +951,9 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id, // Add the overlays, in the sequence in which they will be painted mTrackPanel->AddOverlay(mIndicatorOverlay.get()); mTrackPanel->AddOverlay(mCursorOverlay.get()); +#ifdef EXPERIMENTAL_SCRUBBING_BASIC mTrackPanel->AddOverlay(mScrubOverlay.get()); +#endif // LLL: When Audacity starts or becomes active after returning from // another application, the first window that can accept focus @@ -1082,7 +1083,9 @@ AudacityProject::~AudacityProject() } if(mTrackPanel) { +#ifdef EXPERIMENTAL_SCRUBBING_BASIC mTrackPanel->RemoveOverlay(mScrubOverlay.get()); +#endif mTrackPanel->RemoveOverlay(mCursorOverlay.get()); mTrackPanel->RemoveOverlay(mIndicatorOverlay.get()); } diff --git a/src/Project.h b/src/Project.h index d76ccb15d..2e24b33d8 100644 --- a/src/Project.h +++ b/src/Project.h @@ -73,6 +73,7 @@ class DeviceToolBar; class EditToolBar; class MeterToolBar; class MixerToolBar; +class Scrubber; class SelectionBar; class SpectralSelectionBar; class Toolbar; @@ -709,7 +710,15 @@ public: // TrackPanelOverlay objects std::unique_ptr - mIndicatorOverlay, mCursorOverlay, mScrubOverlay; + mIndicatorOverlay, mCursorOverlay; + +#ifdef EXPERIMENTAL_SCRUBBING_BASIC + std::unique_ptr mScrubOverlay; + std::unique_ptr mScrubber; +public: + Scrubber &GetScrubber() { return *mScrubber; } + const Scrubber &GetScrubber() const { return *mScrubber; } +#endif DECLARE_EVENT_TABLE() }; diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index d56a6f459..c1c14f38b 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -198,6 +198,9 @@ is time to refresh some aspect of the screen. #include "toolbars/ControlToolBar.h" #include "toolbars/ToolsToolBar.h" +// To do: eliminate this! +#include "tracks/ui/Scrubbing.h" + #define ZOOMLIMIT 0.001f //This loads the appropriate set of cursors, depending on platform. @@ -268,20 +271,6 @@ enum { kBottomMargin = kShadowThickness + kBorderThickness, kLeftMargin = kLeftInset + kBorderThickness, kRightMargin = kRightInset + kShadowThickness + kBorderThickness, - - kTimerInterval = 50, // milliseconds - kOneSecondCountdown = 1000 / kTimerInterval, -}; - -enum { - // PRL: - // Mouse must move at least this far to distinguish ctrl-drag to scrub - // from ctrl-click for playback. - SCRUBBING_PIXEL_TOLERANCE = 10, - -#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL - ScrubSpeedStepsPerOctave = 4, -#endif }; // Is the distance between A and B less than D? @@ -583,30 +572,7 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id, mSelStartValid = false; mSelStart = 0; -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - mScrubToken = -1; - mScrubStartClockTimeMillis = -1; - mScrubStartPosition = -1; - mMaxScrubSpeed = 1.0; - mScrubSpeedDisplayCountdown = 0; - mScrubHasFocus = false; - mScrubSeekPress = false; -#endif - -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - mSmoothScrollingScrub = false; -#endif - -#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL - mLogMaxScrubSpeed = 0; -#endif - mInitialTrackSelection = new std::vector; - - if (wxTheApp) - wxTheApp->Connect - (wxEVT_ACTIVATE_APP, - wxActivateEventHandler(TrackPanel::OnActivateOrDeactivateApp), NULL, this); } @@ -614,11 +580,6 @@ TrackPanel::~TrackPanel() { mTimer.Stop(); - if (wxTheApp) - wxTheApp->Disconnect - (wxEVT_ACTIVATE_APP, - wxActivateEventHandler(TrackPanel::OnActivateOrDeactivateApp), NULL, this); - // Unregister for tracklist updates mTracks->Disconnect(EVT_TRACKLIST_UPDATED, wxCommandEventHandler(TrackPanel::OnTrackListUpdated), @@ -1015,12 +976,6 @@ void TrackPanel::OnTimer(wxTimerEvent& ) p->GetEventHandler()->ProcessEvent(e); } - const double playPos = gAudioIO->GetStreamTime(); - -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - TimerUpdateScrubbing(playPos); -#endif - DrawOverlays(false); if(IsAudioActive() && gAudioIO->GetNumCaptureChannels()) { @@ -1611,7 +1566,7 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t, // But don't change the cursor when scrubbing. SelectionBoundary boundary = #ifdef EXPERIMENTAL_SCRUBBING_BASIC - IsScrubbing() + GetProject()->GetScrubber().IsScrubbing() ? SBNone : #endif @@ -1905,276 +1860,6 @@ void TrackPanel::HandleSelect(wxMouseEvent & event) SelectionHandleDrag(event, t); } - -// Made obsolete by scrubbing: -#ifndef EXPERIMENTAL_SCRUBBING_BASIC -void TrackPanel::StartOrJumpPlayback(wxMouseEvent &event) -{ - AudacityProject *p = GetActiveProject(); - if (p) { - double clicktime = mViewInfo->PositionToTime(event.m_x, GetLeftOffset()); - const double t1 = mViewInfo->selectedRegion.t1(); - // Play to end of selection, or if that is not right of the pick, end of track - double endtime = clicktime < t1 ? t1 : mViewInfo->total; - - //Behavior should differ depending upon whether we are - //currently in playback mode or not. - - bool busy = gAudioIO->IsBusy(); - if (!busy) - { - //If we aren't currently playing back, start playing back at - //the clicked point - ControlToolBar * ctb = p->GetControlToolBar(); - //ctb->SetPlay(true);// Not needed as done in PlayPlayRegion - ctb->PlayPlayRegion - (SelectedRegion(clicktime, endtime), p->GetDefaultPlayOptions()); - } - else - { - //If we are playing back, stop and move playback - //to the clicked point. - //This unpauses paused audio as well. The right thing to do might be to - //leave it paused but move the point. This would probably - //require a NEW method in ControlToolBar: SetPause(); - ControlToolBar * ctb = p->GetControlToolBar(); - ctb->StopPlaying(); - ctb->PlayPlayRegion(SelectedRegion(clicktime, endtime), p->GetDefaultPlayOptions()); - } - } -} -#endif - - -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL -double TrackPanel::FindScrubSpeed(double timeAtMouse) const -{ - // Map a time (which was mapped from a mouse position) - // to a speed. - // Map times to positive and negative speeds, - // with the time at the midline of the screen mapping to 0, - // and the extremes to the maximum scrub speed. - - // Width of visible track area, in time terms: - const double screen = GetScreenEndTime() - mViewInfo->h; - const double origin = mViewInfo->h + screen / 2.0; - - // There are various snapping zones that are this fraction of screen: - const double snap = 0.05; - - // By shrinking denom a bit, we make margins left and right - // that snap to maximum and negative maximum speeds. - const double factor = 1.0 - (snap * 2); - const double denom = factor * screen / 2.0; - double fraction = std::min(1.0, fabs(timeAtMouse - origin) / denom); - - // Snap to 1.0 and -1.0 - const double unity = 1.0 / mMaxScrubSpeed; - const double tolerance = snap / factor; - // Make speeds near 1 available too by remapping fractions outside - // this snap zone - if (fraction <= unity - tolerance) - fraction *= unity / (unity - tolerance); - else if (fraction < unity + tolerance) - fraction = unity; - else - fraction = unity + (fraction - (unity + tolerance)) * - (1.0 - unity) / (1.0 - (unity + tolerance)); - - double result = fraction * mMaxScrubSpeed; - if (timeAtMouse < origin) - result *= -1.0; - return result; -} - -double TrackPanel::FindSeekSpeed(double timeAtMouse) const -{ - // Map a time (which was mapped from a mouse position) - // to a signed skip speed: a multiplier of the stutter duration, - // by which to advance the play position. - // (The stutter will play at unit speed.) - - // Times near the midline of the screen map to skip-less play, - // and the extremes to a value proportional to maximum scrub speed. - - // If the maximum scrubbing speed defaults to 1.0 when you begin to scroll-scrub, - // the extreme skipping for scroll-seek needs to be larger to be useful. - static const double ARBITRARY_MULTIPLIER = 10.0; - const double extreme = std::max(1.0, mMaxScrubSpeed * ARBITRARY_MULTIPLIER); - - // Width of visible track area, in time terms: - const double screen = GetScreenEndTime() - mViewInfo->h; - const double halfScreen = screen / 2.0; - const double origin = mViewInfo->h + halfScreen; - - // The snapping zone is this fraction of screen, on each side of the - // center line: - const double snap = 0.05; - const double fraction = - std::max(snap, std::min(1.0, fabs(timeAtMouse - origin) / halfScreen)); - - double result = 1.0 + ((fraction - snap) / (1.0 - snap)) * (extreme - 1.0); - if (timeAtMouse < origin) - result *= -1.0; - return result; -} -#endif - -#ifdef EXPERIMENTAL_SCRUBBING_BASIC -bool TrackPanel::PollIsSeeking() -{ - return ::wxGetMouseState().LeftIsDown(); -} - -bool TrackPanel::IsScrubbing() -{ - if (mScrubToken <= 0) - return false; - else if (mScrubToken == GetProject()->GetAudioIOToken()) - return true; - else { - mScrubToken = -1; - mScrubStartPosition = -1; -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - mSmoothScrollingScrub = false; -#endif - return false; - } -} - -void TrackPanel::MarkScrubStart( - wxCoord xx -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - , bool smoothScrolling -#endif -) -{ - // Don't actually start scrubbing, but collect some information - // needed for the decision to start scrubbing later when handling - // drag events. -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - mSmoothScrollingScrub = smoothScrolling; -#endif - mScrubStartPosition = xx; - mScrubStartClockTimeMillis = ::wxGetLocalTimeMillis(); -} - -bool TrackPanel::MaybeStartScrubbing(wxMouseEvent &event) -{ - if (IsScrubbing()) - return false; - else if (mScrubStartPosition >= 0) { - const bool busy = gAudioIO->IsBusy(); - if (busy && gAudioIO->GetNumCaptureChannels() > 0) { - // Do not stop recording, and don't try to start scrubbing after - // recording stops - mScrubStartPosition = -1; - return false; - } - - wxCoord position = event.m_x; - AudacityProject *p = GetActiveProject(); - if (p && - abs(mScrubStartPosition - position) >= SCRUBBING_PIXEL_TOLERANCE) { - ControlToolBar * ctb = p->GetControlToolBar(); - double maxTime = p->GetTracks()->GetEndTime(); - double time0 = std::min(maxTime, - mViewInfo->PositionToTime(mScrubStartPosition, GetLeftOffset()) - ); - double time1 = std::min(maxTime, - mViewInfo->PositionToTime(position, GetLeftOffset()) - ); - if (time1 != time0) - { - if (busy) - ctb->StopPlaying(); - - AudioIOStartStreamOptions options(p->GetDefaultPlayOptions()); - options.timeTrack = NULL; - options.scrubDelay = (kTimerInterval / 1000.0); - options.scrubStartClockTimeMillis = mScrubStartClockTimeMillis; - options.minScrubStutter = 0.2; -#if 0 - // Take the starting speed limit from the transcription toolbar, - // but it may be varied during the scrub. - mMaxScrubSpeed = options.maxScrubSpeed = - p->GetTranscriptionToolBar()->GetPlaySpeed(); -#else - // That idea seems unpopular... just make it one - mMaxScrubSpeed = options.maxScrubSpeed = 1.0; -#endif - options.maxScrubTime = mTracks->GetEndTime(); - const bool cutPreview = false; - const bool backwards = time1 < time0; -#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL - static const double maxScrubSpeedBase = - pow(2.0, 1.0 / ScrubSpeedStepsPerOctave); - mLogMaxScrubSpeed = floor(0.5 + - log(mMaxScrubSpeed) / log(maxScrubSpeedBase) - ); -#endif - mScrubSpeedDisplayCountdown = 0; - mScrubToken = - ctb->PlayPlayRegion(SelectedRegion(time0, time1), options, cutPreview, backwards); - } - } - else - // Wait to test again - mScrubStartClockTimeMillis = ::wxGetLocalTimeMillis(); - - if (IsScrubbing()) { - mScrubHasFocus = true; - //mMouseCapture = IsMiddleButtonScrubbing; - //CaptureMouse(); - } - return IsScrubbing(); - } - else - return false; -} - -bool TrackPanel::ContinueScrubbing(wxCoord position, bool hasFocus, bool seek) -{ - // When we don't have focus, enqueue silent scrubs until we regain focus. - if (!hasFocus) - return gAudioIO->EnqueueScrubBySignedSpeed(0, mMaxScrubSpeed, false); - - const double time = mViewInfo->PositionToTime(position, GetLeftOffset()); - - if (seek) - // Cause OnTimer() to suppress the speed display - mScrubSpeedDisplayCountdown = 1; - -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - if (mSmoothScrollingScrub) { - const double speed = seek ? FindSeekSpeed(time) : FindScrubSpeed(time); - return gAudioIO->EnqueueScrubBySignedSpeed(speed, mMaxScrubSpeed, seek); - } - else -#endif - return gAudioIO->EnqueueScrubByPosition - (time, seek ? 1.0 : mMaxScrubSpeed, seek); -} - -bool TrackPanel::StopScrubbing() -{ - if (IsScrubbing()) - { - if (gAudioIO->IsBusy()) { - AudacityProject *p = GetActiveProject(); - if (p) { - ControlToolBar * ctb = p->GetControlToolBar(); - ctb->StopPlaying(); - } - } - return true; - } - else - return false; -} -#endif - - /// This method gets called when we're handling selection /// and the mouse was just clicked. void TrackPanel::SelectionHandleClick(wxMouseEvent & event, @@ -2302,18 +1987,18 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event, event.LeftDClick() || #endif event.LeftDown()) { - MarkScrubStart( + GetProject()->GetScrubber().MarkScrubStart( event.m_x #ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL , event.LeftDClick() #endif - ); + ); return; } #else - StartOrJumpPlayback(event); + // StartOrJumpPlayback(event); #endif @@ -3038,11 +2723,11 @@ void TrackPanel::Stretch(int mouseXCoordinate, int trackLeftEdge, void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack) { #ifdef EXPERIMENTAL_SCRUBBING_BASIC - if (mScrubStartPosition >= 0) { - MaybeStartScrubbing(event); + Scrubber &scrubber = GetProject()->GetScrubber(); + if (scrubber.IsScrubbing() || + GetProject()->GetScrubber().MaybeStartScrubbing(event)) // Do nothing more, don't change selection return; - } #endif // AS: If we're not in the process of selecting (set in @@ -5885,7 +5570,7 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event) #ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL // Don't pan during smooth scrolling. That would conflict with keeping // the play indicator centered. - && !mSmoothScrollingScrub + && !GetProject()->GetScrubber().IsScrollScrubbing() #endif ) { @@ -5917,7 +5602,7 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event) wxCoord xx; double center_h; #ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - if (mSmoothScrollingScrub) { + if (GetProject()->GetScrubber().IsScrollScrubbing()) { // Expand or contract about the center, ignoring mouse position center_h = mViewInfo->h + (GetScreenEndTime() - mViewInfo->h) / 2.0; xx = mViewInfo->TimeToPosition(center_h, trackLeftEdge); @@ -5953,21 +5638,8 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event) else { #ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL - if (IsScrubbing()) { - const int newLogMaxScrubSpeed = mLogMaxScrubSpeed + steps; - static const double maxScrubSpeedBase = - pow(2.0, 1.0 / ScrubSpeedStepsPerOctave); - double newSpeed = pow(maxScrubSpeedBase, newLogMaxScrubSpeed); - if (newSpeed >= AudioIO::GetMinScrubSpeed() && - newSpeed <= AudioIO::GetMaxScrubSpeed()) { - mLogMaxScrubSpeed = newLogMaxScrubSpeed; - mMaxScrubSpeed = newSpeed; -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - if (!mSmoothScrollingScrub) -#endif - // Show the speed for one second - mScrubSpeedDisplayCountdown = kOneSecondCountdown + 1; - } + if (GetProject()->GetScrubber().IsScrubbing()) { + GetProject()->GetScrubber().HandleScrollWheel(steps); } else #endif @@ -6718,12 +6390,12 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event) } #ifdef EXPERIMENTAL_SCRUBBING_BASIC - if (IsScrubbing() && + if (GetProject()->GetScrubber().IsScrubbing() && GetRect().Contains(event.GetPosition()) && (!pTrack || pTrack->GetKind() == Track::Wave)) { if (event.LeftDown()) { - mScrubSeekPress = true; + GetProject()->GetScrubber().SetSeeking(); return; } else if (event.LeftIsDown()) @@ -7241,204 +6913,6 @@ void TrackPanel::DrawEverythingElse(wxDC * dc, } } -#ifdef EXPERIMENTAL_SCRUBBING_BASIC -bool TrackPanel::ShouldDrawScrubSpeed() -{ - return IsScrubbing() && - mScrubHasFocus && - ((!PollIsSeeking() && mScrubSpeedDisplayCountdown > 0) -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - // Draw always for scroll-scrub and for scroll-seek - || mSmoothScrollingScrub -#endif - ); -} - -void TrackPanel::TimerUpdateScrubbing(double playPos) -{ - if (!IsScrubbing()) { - mNextScrubRect = wxRect(); - return; - } - - // Call ContinueScrubbing() here in the timer handler - // rather than in SelectionHandleDrag() - // so that even without drag events, we can instruct the play head to - // keep approaching the mouse cursor, when its maximum speed is limited. - - // Thus scrubbing relies mostly on periodic polling of mouse and keys, - // not event notifications. But there are a few event handlers that - // leave messages for this routine, in mScrubSeekPress and in mScrubHasFocus. - - // Seek only when the pointer is in the panel. Else, scrub. - const wxMouseState state(::wxGetMouseState()); - const wxPoint position = ScreenToClient(state.GetPosition()); - const bool inPanel = GetRect().Contains(position); - const bool seek = inPanel && (mScrubSeekPress || PollIsSeeking()); - if (ContinueScrubbing(position.x, mScrubHasFocus, seek)) - mScrubSeekPress = false; - // else, if seek requested, try again at a later time when we might - // enqueue a long enough stutter - -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - if (mSmoothScrollingScrub) - ; - else -#endif - { - if (mScrubSpeedDisplayCountdown > 0) - --mScrubSpeedDisplayCountdown; - } - - if (!ShouldDrawScrubSpeed()) { - mNextScrubRect = wxRect(); - } - else { - int panelWidth, panelHeight; - GetSize(&panelWidth, &panelHeight); - - // Where's the mouse? - int xx, yy; - ::wxGetMousePosition(&xx, &yy); - ScreenToClient(&xx, &yy); - - const bool seeking = PollIsSeeking(); - - // Find the text - const double speed = -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - mSmoothScrollingScrub - ? seeking - ? FindSeekSpeed(mViewInfo->PositionToTime(xx, GetLeftOffset())) - : FindScrubSpeed(mViewInfo->PositionToTime(xx, GetLeftOffset())) - : -#endif - mMaxScrubSpeed; - - const wxChar *format = -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - mSmoothScrollingScrub - ? seeking - ? wxT("%+.2fX") - : wxT("%+.2f") - : -#endif - wxT("%.2f"); - - mNextScrubSpeedText = wxString::Format(format, speed); - - // Find the origin for drawing text - wxCoord width, height; - { - wxClientDC dc(this); - static const wxFont labelFont(24, wxSWISS, wxNORMAL, wxNORMAL); - dc.SetFont(labelFont); - dc.GetTextExtent(mNextScrubSpeedText, &width, &height); - } - xx = std::max(0, std::min(panelWidth - width, xx - width / 2)); - - // Put the text above the cursor, if it fits. - enum { offset = 20 }; - yy -= height + offset; - if (yy < 0) - yy += height + 2 * offset; - yy = std::max(0, std::min(panelHeight - height, yy)); - - mNextScrubRect = wxRect(xx, yy, width, height); - } - -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - if (mSmoothScrollingScrub) { - // Pan the view, so that we center the play indicator. - const int posX = mViewInfo->TimeToPosition(playPos); - int width; - GetTracksUsableArea(&width, NULL); - const int deltaX = posX - width / 2; - mViewInfo->h = - mViewInfo->OffsetTimeByPixels(mViewInfo->h, deltaX, true); - if (!mViewInfo->bScrollBeyondZero) - // Can't scroll too far left - mViewInfo->h = std::max(0.0, mViewInfo->h); - Refresh(false); - } -#endif -} - -std::pair TrackPanel::GetScrubSpeedRectangle() -{ - wxRect rect(mLastScrubRect); -#if defined(__WXMAC__) - rect.Inflate(1, 0); -#endif - - const bool outdated = - (mLastScrubRect != mNextScrubRect) || - (!mLastScrubRect.IsEmpty() && !ShouldDrawScrubSpeed()) || - (mLastScrubSpeedText != mNextScrubSpeedText); - return std::make_pair( - rect, - outdated - ); -} - -void TrackPanel::UndrawScrubSpeed(wxDC & dc) -{ - if (!mLastScrubRect.IsEmpty()) -#if defined(__WXMAC__) - // On OSX, if a HiDPI resolution is being used, the line will actually take up - // more than 1 pixel (even though it is drawn as 1), so we restore the surrounding - // pixels as well. (This is because the wxClientDC doesn't know about the scaling.) - dc.Blit( - mLastScrubRect.GetX() - 1, mLastScrubRect.GetY(), - mLastScrubRect.GetWidth() + 3, mLastScrubRect.GetHeight(), - &mBackingDC, - mLastScrubRect.GetX() - 1, mLastScrubRect.GetY()); -#else - dc.Blit( - mLastScrubRect.GetX(), mLastScrubRect.GetY(), - mLastScrubRect.GetWidth(), mLastScrubRect.GetHeight(), - &mBackingDC, - mLastScrubRect.GetX(), mLastScrubRect.GetY()); -#endif -} - -void TrackPanel::DoDrawScrubSpeed(wxDC &dc) -{ - if (!ShouldDrawScrubSpeed()) - return; - - mLastScrubRect = mNextScrubRect; - mLastScrubSpeedText = mNextScrubSpeedText; - const bool seeking = PollIsSeeking(); - if (// Draw for (non-scroll) scrub, sometimes, but never for seek - (!seeking && mScrubSpeedDisplayCountdown > 0) -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - // Draw always for scroll-scrub and for scroll-seek - || mSmoothScrollingScrub -#endif - - ) { - static const wxFont labelFont(24, wxSWISS, wxNORMAL, wxNORMAL); - dc.SetFont(labelFont); - - // These two colors were previously saturated red and green. However - // we have a rule to try to only use red for reserved purposes of - // (a) Recording - // (b) Error alerts - // So they were changed to 'orange' and 'lime'. - static const wxColour clrNoScroll(215, 162, 0), clrScroll(0, 204, 153); -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - if (mSmoothScrollingScrub) - dc.SetTextForeground(clrScroll); - else -#endif - dc.SetTextForeground(clrNoScroll); - - dc.DrawText(mLastScrubSpeedText, mLastScrubRect.GetX(), mLastScrubRect.GetY()); - } -} -#endif - /// Draw zooming indicator that shows the region that will /// be zoomed into when the user clicks and drags with a /// zoom cursor. Handles both vertical and horizontal @@ -9334,18 +8808,6 @@ void TrackPanel::OnKillFocus(wxFocusEvent & WXUNUSED(event)) Refresh( false); } -void TrackPanel::OnActivateOrDeactivateApp(wxActivateEvent &event) -{ -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - if (event.GetActive()) - mScrubHasFocus = IsScrubbing(); - else - mScrubHasFocus = false; -#endif - - event.Skip(); -} - /********************************************************************** TrackInfo code is destined to move out of this file. diff --git a/src/TrackPanel.h b/src/TrackPanel.h index e901f47c9..25285148a 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -74,6 +74,11 @@ enum class UndoPush : unsigned char; DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_TRACK_PANEL_TIMER, -1); +enum { + kTimerInterval = 50, // milliseconds + kOneSecondCountdown = 1000 / kTimerInterval, +}; + class AUDACITY_DLL_API TrackInfo { public: @@ -161,7 +166,6 @@ class AUDACITY_DLL_API TrackPanel final : public wxPanel { virtual void OnSetFocus(wxFocusEvent & event); virtual void OnKillFocus(wxFocusEvent & event); - virtual void OnActivateOrDeactivateApp(wxActivateEvent & event); virtual void OnContextMenu(wxContextMenuEvent & event); @@ -251,15 +255,6 @@ class AUDACITY_DLL_API TrackPanel final : public wxPanel { virtual bool IsOverCutline(WaveTrack * track, wxRect &rect, wxMouseEvent &event); virtual void HandleTrackSpecificMouseEvent(wxMouseEvent & event); -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - bool ShouldDrawScrubSpeed(); - virtual void TimerUpdateScrubbing(double playPos); - // Second member of pair indicates whether the cursor is out of date: - virtual std::pair GetScrubSpeedRectangle(); - virtual void UndrawScrubSpeed(wxDC & dc); - virtual void DoDrawScrubSpeed(wxDC & dc); -#endif - virtual void ScrollDuringDrag(); // Working out where to dispatch the event to. @@ -299,31 +294,7 @@ class AUDACITY_DLL_API TrackPanel final : public wxPanel { virtual void HandleSelect(wxMouseEvent & event); virtual void SelectionHandleDrag(wxMouseEvent &event, Track *pTrack); - // Made obsolete by scrubbing: -#ifndef EXPERIMENTAL_SCRUBBING_BASIC - void StartOrJumpPlayback(wxMouseEvent &event); -#endif - -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - double FindScrubSpeed(double timeAtMouse) const; - double FindSeekSpeed(double timeAtMouse) const; -#endif - -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - static bool PollIsSeeking(); - bool IsScrubbing(); - void MarkScrubStart( - wxCoord xx -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - , bool smoothScrolling -#endif - ); - bool MaybeStartScrubbing(wxMouseEvent &event); - bool ContinueScrubbing(wxCoord position, bool hasFocus, bool seek); -public: - bool StopScrubbing(); protected: -#endif virtual void SelectionHandleClick(wxMouseEvent &event, Track* pTrack, wxRect rect); @@ -788,27 +759,6 @@ protected: int mMoveDownThreshold; int mRearrangeCount; -#ifdef EXPERIMENTAL_SCRUBBING_BASIC - int mScrubToken; - wxLongLong mScrubStartClockTimeMillis; - wxCoord mScrubStartPosition; - double mMaxScrubSpeed; - int mScrubSpeedDisplayCountdown; - bool mScrubHasFocus; - bool mScrubSeekPress; - - wxRect mLastScrubRect, mNextScrubRect; - wxString mLastScrubSpeedText, mNextScrubSpeedText; -#endif - -#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL - bool mSmoothScrollingScrub; -#endif - -#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL - int mLogMaxScrubSpeed; -#endif - std::unique_ptr mArrowCursor, mPencilCursor, mSelectCursor, mResizeCursor, mSlideCursor, mEnvelopeCursor, // doubles as the center frequency cursor diff --git a/src/toolbars/ToolsToolBar.cpp b/src/toolbars/ToolsToolBar.cpp index fe44ab0e0..6ef363e49 100644 --- a/src/toolbars/ToolsToolBar.cpp +++ b/src/toolbars/ToolsToolBar.cpp @@ -52,13 +52,15 @@ #include "../AllThemeResources.h" #include "../ImageManipulation.h" #include "../Project.h" -#ifdef EXPERIMENTAL_SCRUBBING_BASIC -#include "../TrackPanel.h" -#endif #include "../Theme.h" -#include "../widgets/AButton.h" #include "../Experimental.h" +#ifdef EXPERIMENTAL_SCRUBBING_BASIC +#include "../tracks/ui/Scrubbing.h" +#endif + +#include "../widgets/AButton.h" + IMPLEMENT_CLASS(ToolsToolBar, ToolBar); @@ -219,13 +221,9 @@ void ToolsToolBar::SetCurrentTool(int tool, bool show) #ifdef EXPERIMENTAL_SCRUBBING_BASIC if (tool != selectTool) { - AudacityProject *p = GetActiveProject(); - if (p) { - TrackPanel *tp = p->GetTrackPanel(); - if (tp) { - tp->StopScrubbing(); - } - } + AudacityProject *const p = GetActiveProject(); + if (p) + p->GetScrubber().StopScrubbing(); } #endif @@ -294,13 +292,9 @@ void ToolsToolBar::OnTool(wxCommandEvent & evt) #ifdef EXPERIMENTAL_SCRUBBING_BASIC if (0 != mCurrentTool) { - AudacityProject *p = GetActiveProject(); - if (p) { - TrackPanel *tp = p->GetTrackPanel(); - if (tp) { - tp->StopScrubbing(); - } - } + AudacityProject *const p = GetActiveProject(); + if (p) + p->GetScrubber().StopScrubbing(); } #endif diff --git a/src/tracks/ui/Scrubbing.cpp b/src/tracks/ui/Scrubbing.cpp index 4a262b545..db63ab698 100644 --- a/src/tracks/ui/Scrubbing.cpp +++ b/src/tracks/ui/Scrubbing.cpp @@ -9,16 +9,385 @@ Paul Licameli split from TrackPanel.cpp **********************************************************************/ #include "Scrubbing.h" +#include "../../Experimental.h" +#include "../../AudioIO.h" #include "../../Project.h" #include "../../TrackPanel.h" #include "../../TrackPanelCell.h" #include "../../TrackPanelCellIterator.h" +#include "../../toolbars/ControlToolBar.h" + +#include #include +enum { + // PRL: + // Mouse must move at least this far to distinguish ctrl-drag to scrub + // from ctrl-click for playback. + SCRUBBING_PIXEL_TOLERANCE = 10, + +#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL + ScrubSpeedStepsPerOctave = 4, +#endif +}; + +namespace { + bool PollIsSeeking() + { + return ::wxGetMouseState().LeftIsDown(); + } + + double FindScrubbingSpeed(const ViewInfo &viewInfo, double maxScrubSpeed, double screen, double timeAtMouse) + { + // Map a time (which was mapped from a mouse position) + // to a speed. + // Map times to positive and negative speeds, + // with the time at the midline of the screen mapping to 0, + // and the extremes to the maximum scrub speed. + + // Width of visible track area, in time terms: + const double origin = viewInfo.h + screen / 2.0; + + // There are various snapping zones that are this fraction of screen: + const double snap = 0.05; + + // By shrinking denom a bit, we make margins left and right + // that snap to maximum and negative maximum speeds. + const double factor = 1.0 - (snap * 2); + const double denom = factor * screen / 2.0; + double fraction = std::min(1.0, fabs(timeAtMouse - origin) / denom); + + // Snap to 1.0 and -1.0 + const double unity = 1.0 / maxScrubSpeed; + const double tolerance = snap / factor; + // Make speeds near 1 available too by remapping fractions outside + // this snap zone + if (fraction <= unity - tolerance) + fraction *= unity / (unity - tolerance); + else if (fraction < unity + tolerance) + fraction = unity; + else + fraction = unity + (fraction - (unity + tolerance)) * + (1.0 - unity) / (1.0 - (unity + tolerance)); + + double result = fraction * maxScrubSpeed; + if (timeAtMouse < origin) + result *= -1.0; + return result; + } + + double FindSeekSpeed(const ViewInfo &viewInfo, double maxScrubSpeed, double screen, double timeAtMouse) + { + // Map a time (which was mapped from a mouse position) + // to a signed skip speed: a multiplier of the stutter duration, + // by which to advance the play position. + // (The stutter will play at unit speed.) + + // Times near the midline of the screen map to skip-less play, + // and the extremes to a value proportional to maximum scrub speed. + + // If the maximum scrubbing speed defaults to 1.0 when you begin to scroll-scrub, + // the extreme skipping for scroll-seek needs to be larger to be useful. + static const double ARBITRARY_MULTIPLIER = 10.0; + const double extreme = std::max(1.0, maxScrubSpeed * ARBITRARY_MULTIPLIER); + + // Width of visible track area, in time terms: + const double halfScreen = screen / 2.0; + const double origin = viewInfo.h + halfScreen; + + // The snapping zone is this fraction of screen, on each side of the + // center line: + const double snap = 0.05; + const double fraction = + std::max(snap, std::min(1.0, fabs(timeAtMouse - origin) / halfScreen)); + + double result = 1.0 + ((fraction - snap) / (1.0 - snap)) * (extreme - 1.0); + if (timeAtMouse < origin) + result *= -1.0; + return result; + } +} + +Scrubber::Scrubber(AudacityProject *project) + : mScrubToken(-1) + , mScrubStartClockTimeMillis(-1) + , mScrubHasFocus(false) + , mScrubSpeedDisplayCountdown(0) + , mScrubStartPosition(-1) + , mMaxScrubSpeed(-1.0) + , mScrubSeekPress(false) +#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL + , mSmoothScrollingScrub(false) + , mLogMaxScrubSpeed(0) +#endif + + , mProject(project) +{ + if (wxTheApp) + wxTheApp->Connect + (wxEVT_ACTIVATE_APP, + wxActivateEventHandler(Scrubber::OnActivateOrDeactivateApp), NULL, this); +} + +Scrubber::~Scrubber() +{ + if (wxTheApp) + wxTheApp->Disconnect + (wxEVT_ACTIVATE_APP, + wxActivateEventHandler(Scrubber::OnActivateOrDeactivateApp), NULL, this); +} + +void Scrubber::MarkScrubStart( + wxCoord xx +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + , bool smoothScrolling +#endif +) +{ + // Don't actually start scrubbing, but collect some information + // needed for the decision to start scrubbing later when handling + // drag events. +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + mSmoothScrollingScrub = smoothScrolling; +#endif + mScrubStartPosition = xx; + mScrubStartClockTimeMillis = ::wxGetLocalTimeMillis(); +} + +#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT +bool Scrubber::MaybeStartScrubbing(const wxMouseEvent &event) +{ + if (mScrubStartPosition < 0) + return false; + if (IsScrubbing()) + return false; + else { + const bool busy = gAudioIO->IsBusy(); + if (busy && gAudioIO->GetNumCaptureChannels() > 0) { + // Do not stop recording, and don't try to start scrubbing after + // recording stops + mScrubStartPosition = -1; + return false; + } + + wxCoord position = event.m_x; + if (abs(mScrubStartPosition - position) >= SCRUBBING_PIXEL_TOLERANCE) { + const ViewInfo &viewInfo = mProject->GetViewInfo(); + TrackPanel *const trackPanel = mProject->GetTrackPanel(); + ControlToolBar * const ctb = mProject->GetControlToolBar(); + double maxTime = mProject->GetTracks()->GetEndTime(); + const int leftOffset = trackPanel->GetLeftOffset(); + double time0 = std::min(maxTime, + viewInfo.PositionToTime(mScrubStartPosition, leftOffset) + ); + double time1 = std::min(maxTime, + viewInfo.PositionToTime(position, leftOffset) + ); + if (time1 != time0) + { + if (busy) + ctb->StopPlaying(); + + AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions()); + options.timeTrack = NULL; + options.scrubDelay = (kTimerInterval / 1000.0); + options.scrubStartClockTimeMillis = mScrubStartClockTimeMillis; + options.minScrubStutter = 0.2; +#if 0 + // Take the starting speed limit from the transcription toolbar, + // but it may be varied during the scrub. + mMaxScrubSpeed = options.maxScrubSpeed = + p->GetTranscriptionToolBar()->GetPlaySpeed(); +#else + // That idea seems unpopular... just make it one + mMaxScrubSpeed = options.maxScrubSpeed = 1.0; +#endif + options.maxScrubTime = mProject->GetTracks()->GetEndTime(); + const bool cutPreview = false; + const bool backwards = time1 < time0; +#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL + static const double maxScrubSpeedBase = + pow(2.0, 1.0 / ScrubSpeedStepsPerOctave); + mLogMaxScrubSpeed = floor(0.5 + + log(mMaxScrubSpeed) / log(maxScrubSpeedBase) + ); +#endif + mScrubSpeedDisplayCountdown = 0; + mScrubToken = + ctb->PlayPlayRegion(SelectedRegion(time0, time1), options, cutPreview, backwards); + } + } + else + // Wait to test again + mScrubStartClockTimeMillis = ::wxGetLocalTimeMillis(); + + if (IsScrubbing()) + mScrubHasFocus = true; + + // Return true whether we started scrub, or are still waiting to decide. + return true; + } +} + +void Scrubber::ContinueScrubbing() +{ + // Thus scrubbing relies mostly on periodic polling of mouse and keys, + // not event notifications. But there are a few event handlers that + // leave messages for this routine, in mScrubSeekPress and in mScrubHasFocus. + + // Seek only when the pointer is in the panel. Else, scrub. + const wxMouseState state(::wxGetMouseState()); + TrackPanel *const trackPanel = mProject->GetTrackPanel(); + const wxPoint position = trackPanel->ScreenToClient(state.GetPosition()); + const bool inPanel = trackPanel->GetRect().Contains(position); + const bool seek = inPanel && (mScrubSeekPress || PollIsSeeking()); + // When we don't have focus, enqueue silent scrubs until we regain focus. + bool result = false; + if (!mScrubHasFocus) + result = gAudioIO->EnqueueScrubBySignedSpeed(0, mMaxScrubSpeed, false); + else { + const double time = mProject->GetViewInfo().PositionToTime(position.x, trackPanel->GetLeftOffset()); + + if (seek) + // Cause OnTimer() to suppress the speed display + mScrubSpeedDisplayCountdown = 1; + +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + if (mSmoothScrollingScrub) { + const double speed = FindScrubSpeed(seek, time); + result = gAudioIO->EnqueueScrubBySignedSpeed(speed, mMaxScrubSpeed, seek); + } + else +#endif + result = gAudioIO->EnqueueScrubByPosition + (time, seek ? 1.0 : mMaxScrubSpeed, seek); + } + + if (result) + mScrubSeekPress = false; + // else, if seek requested, try again at a later time when we might + // enqueue a long enough stutter + +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + if (mSmoothScrollingScrub) + ; + else +#endif + { + if (mScrubSpeedDisplayCountdown > 0) + --mScrubSpeedDisplayCountdown; + } + +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + if (mSmoothScrollingScrub) { + // Pan the view, so that we center the play indicator. + + ViewInfo &viewInfo = mProject->GetViewInfo(); + TrackPanel *const trackPanel = mProject->GetTrackPanel(); + const int posX = viewInfo.TimeToPosition(viewInfo.mRecentStreamTime); + int width; + trackPanel->GetTracksUsableArea(&width, NULL); + const int deltaX = posX - width / 2; + viewInfo.h = + viewInfo.OffsetTimeByPixels(viewInfo.h, deltaX, true); + if (!viewInfo.bScrollBeyondZero) + // Can't scroll too far left + viewInfo.h = std::max(0.0, viewInfo.h); + trackPanel->Refresh(false); + } +#endif +} + +bool Scrubber::StopScrubbing() +{ + if (IsScrubbing()) + { + if (gAudioIO->IsBusy()) { + ControlToolBar *const ctb = mProject->GetControlToolBar(); + ctb->StopPlaying(); + } + return true; + } + else + return false; +} + +bool Scrubber::IsScrubbing() const +{ + if (mScrubToken <= 0) + return false; + else if (mScrubToken == mProject->GetAudioIOToken()) + return true; + else { + const_cast(*this).mScrubToken = -1; + const_cast(*this).mScrubStartPosition = -1; +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + const_cast(*this).mSmoothScrollingScrub = false; +#endif + return false; + } +} + +bool Scrubber::ShouldDrawScrubSpeed() +{ + return IsScrubbing() && + mScrubHasFocus && ( + // Draw for (non-scroll) scrub, sometimes, but never for seek + (!PollIsSeeking() && mScrubSpeedDisplayCountdown > 0) +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + // Draw always for scroll-scrub and for scroll-seek + || mSmoothScrollingScrub +#endif + ); +} + +double Scrubber::FindScrubSpeed(bool seeking, double time) const +{ + ViewInfo &viewInfo = mProject->GetViewInfo(); + const double screen = mProject->GetScreenEndTime() - viewInfo.h; + return (seeking ? FindSeekSpeed : FindScrubbingSpeed) + (viewInfo, mMaxScrubSpeed, screen, time); +} + +void Scrubber::HandleScrollWheel(int steps) +{ + const int newLogMaxScrubSpeed = mLogMaxScrubSpeed + steps; + static const double maxScrubSpeedBase = + pow(2.0, 1.0 / ScrubSpeedStepsPerOctave); + double newSpeed = pow(maxScrubSpeedBase, newLogMaxScrubSpeed); + if (newSpeed >= AudioIO::GetMinScrubSpeed() && + newSpeed <= AudioIO::GetMaxScrubSpeed()) { + mLogMaxScrubSpeed = newLogMaxScrubSpeed; + mMaxScrubSpeed = newSpeed; +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + if (!mSmoothScrollingScrub) +#endif + // Show the speed for one second + mScrubSpeedDisplayCountdown = kOneSecondCountdown + 1; + } +} + +void Scrubber::OnActivateOrDeactivateApp(wxActivateEvent &event) +{ + if (event.GetActive()) + mScrubHasFocus = IsScrubbing(); + else + mScrubHasFocus = false; + + event.Skip(); +} + +/////////////////////////////////////////////////////////////////////////////// +// class ScrubbingOverlay is responsible for drawing the speed numbers + ScrubbingOverlay::ScrubbingOverlay(AudacityProject *project) : mProject(project) + , mLastScrubRect() + , mNextScrubRect() + , mLastScrubSpeedText() + , mNextScrubSpeedText() { mProject->Connect(EVT_TRACK_PANEL_TIMER, wxCommandEventHandler(ScrubbingOverlay::OnTimer), @@ -36,18 +405,130 @@ ScrubbingOverlay::~ScrubbingOverlay() std::pair ScrubbingOverlay::DoGetRectangle(wxSize) { - return std::make_pair(wxRect(), false); + wxRect rect(mLastScrubRect); + const bool outdated = + (mLastScrubRect != mNextScrubRect) || + (!mLastScrubRect.IsEmpty() && !GetScrubber().ShouldDrawScrubSpeed()) || + (mLastScrubSpeedText != mNextScrubSpeedText); + return std::make_pair( + rect, + outdated + ); } void ScrubbingOverlay::Draw - (wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) + (wxDC &dc, TrackPanelCellIterator, TrackPanelCellIterator) { + mLastScrubRect = mNextScrubRect; + mLastScrubSpeedText = mNextScrubSpeedText; + + Scrubber &scrubber = GetScrubber(); + if (!scrubber.ShouldDrawScrubSpeed()) + return; + + static const wxFont labelFont(24, wxSWISS, wxNORMAL, wxNORMAL); + dc.SetFont(labelFont); + + // These two colors were previously saturated red and green. However + // we have a rule to try to only use red for reserved purposes of + // (a) Recording + // (b) Error alerts + // So they were changed to 'orange' and 'lime'. + static const wxColour clrNoScroll(215, 162, 0), clrScroll(0, 204, 153); +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + if (scrubber.IsScrollScrubbing()) + dc.SetTextForeground(clrScroll); + else +#endif + dc.SetTextForeground(clrNoScroll); + + dc.DrawText(mLastScrubSpeedText, mLastScrubRect.GetX(), mLastScrubRect.GetY()); } + void ScrubbingOverlay::OnTimer(wxCommandEvent &event) { // Let other listeners get the notification event.Skip(); - // To do: move code here + Scrubber &scrubber = GetScrubber(); + if (!GetScrubber().IsScrubbing()) { + mNextScrubRect = wxRect(); + return; + } + + // Call ContinueScrubbing() here in the timer handler + // rather than in SelectionHandleDrag() + // so that even without drag events, we can instruct the play head to + // keep approaching the mouse cursor, when its maximum speed is limited. + scrubber.ContinueScrubbing(); + + if (!scrubber.ShouldDrawScrubSpeed()) { + mNextScrubRect = wxRect(); + } + else { + TrackPanel *const trackPanel = mProject->GetTrackPanel(); + int panelWidth, panelHeight; + trackPanel->GetSize(&panelWidth, &panelHeight); + + // Where's the mouse? + int xx, yy; + ::wxGetMousePosition(&xx, &yy); + trackPanel->ScreenToClient(&xx, &yy); + + const bool seeking = PollIsSeeking(); + + // Find the text + const double maxScrubSpeed = GetScrubber().GetMaxScrubSpeed(); + const double speed = +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + scrubber.IsScrollScrubbing() + ? scrubber.FindScrubSpeed + (seeking, mProject->GetViewInfo().PositionToTime(xx, trackPanel->GetLeftOffset())) + : +#endif + maxScrubSpeed; + + const wxChar *format = +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + scrubber.IsScrollScrubbing() + ? seeking + ? wxT("%+.2fX") + : wxT("%+.2f") + : +#endif + wxT("%.2f"); + + mNextScrubSpeedText = wxString::Format(format, speed); + + // Find the origin for drawing text + wxCoord width, height; + { + wxClientDC dc(trackPanel); + static const wxFont labelFont(24, wxSWISS, wxNORMAL, wxNORMAL); + dc.SetFont(labelFont); + dc.GetTextExtent(mNextScrubSpeedText, &width, &height); + } + xx = std::max(0, std::min(panelWidth - width, xx - width / 2)); + + // Put the text above the cursor, if it fits. + enum { offset = 20 }; + yy -= height + offset; + if (yy < 0) + yy += height + 2 * offset; + yy = std::max(0, std::min(panelHeight - height, yy)); + + mNextScrubRect = wxRect(xx, yy, width, height); + } } + +const Scrubber &ScrubbingOverlay::GetScrubber() const +{ + return mProject->GetScrubber(); +} + +Scrubber &ScrubbingOverlay::GetScrubber() +{ + return mProject->GetScrubber(); +} +#endif diff --git a/src/tracks/ui/Scrubbing.h b/src/tracks/ui/Scrubbing.h index c6c60ac71..3d5d016dc 100644 --- a/src/tracks/ui/Scrubbing.h +++ b/src/tracks/ui/Scrubbing.h @@ -12,12 +12,65 @@ Paul Licameli split from TrackPanel.cpp #define __AUDACITY_SCRUBBING__ #include +#include +#include "../../Experimental.h" #include "../../TrackPanelOverlay.h" class AudacityProject; -class ScrubbingOverlay : public wxEvtHandler, public TrackPanelOverlay +// Scrub state object +class Scrubber : public wxEvtHandler +{ +public: + Scrubber(AudacityProject *project); + ~Scrubber(); + + void MarkScrubStart( + wxCoord xx +#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL + , bool smoothScrolling +#endif + ); + // Returns true iff the event should be considered consumed by this: + bool MaybeStartScrubbing(const wxMouseEvent &event); + void ContinueScrubbing(); + bool StopScrubbing(); + + bool IsScrubbing() const; + bool IsScrollScrubbing() const // If true, implies IsScrubbing() + { return mSmoothScrollingScrub; } + + bool ShouldDrawScrubSpeed(); + double FindScrubSpeed(bool seeking, double time) const; + double GetMaxScrubSpeed() const { return mMaxScrubSpeed; } + + void HandleScrollWheel(int steps); + + void SetSeeking() { mScrubSeekPress = true; } + +private: + int mScrubToken; + wxLongLong mScrubStartClockTimeMillis; + bool mScrubHasFocus; + int mScrubSpeedDisplayCountdown; + wxCoord mScrubStartPosition; + double mMaxScrubSpeed; + bool mScrubSeekPress; + bool mSmoothScrollingScrub; + +#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL + int mLogMaxScrubSpeed; +#endif + +private: + void OnActivateOrDeactivateApp(wxActivateEvent & event); + + AudacityProject *mProject; +}; + +// Specialist in drawing the scrub speed, and listening for certain events +class ScrubbingOverlay final : public wxEvtHandler, public TrackPanelOverlay { public: ScrubbingOverlay(AudacityProject *project); @@ -30,7 +83,13 @@ private: void OnTimer(wxCommandEvent &event); + const Scrubber &GetScrubber() const; + Scrubber &GetScrubber(); + AudacityProject *mProject; + + wxRect mLastScrubRect, mNextScrubRect; + wxString mLastScrubSpeedText, mNextScrubSpeedText; }; #endif From 9f65b80647084079ba3fb1ebe33f1ca5c7d1802b Mon Sep 17 00:00:00 2001 From: Paul Licameli Date: Mon, 24 Aug 2015 14:14:09 -0400 Subject: [PATCH 3/3] Reimplement quick play indicator as an overlay like the cursor --- src/TrackPanel.cpp | 43 ------------------ src/TrackPanel.h | 5 --- src/widgets/Ruler.cpp | 102 +++++++++++++++++++++++++++++++++++++++++- src/widgets/Ruler.h | 6 +++ 4 files changed, 106 insertions(+), 50 deletions(-) diff --git a/src/TrackPanel.cpp b/src/TrackPanel.cpp index c1c14f38b..ffbd66bf7 100644 --- a/src/TrackPanel.cpp +++ b/src/TrackPanel.cpp @@ -550,8 +550,6 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id, mSnapLeft = -1; mSnapRight = -1; - mOldQPIndicatorPos = -1; - // Register for tracklist updates mTracks->Connect(EVT_TRACKLIST_RESIZED, wxCommandEventHandler(TrackPanel::OnTrackListResized), @@ -1039,47 +1037,6 @@ void TrackPanel::ScrollDuringDrag() } } -void TrackPanel::DrawQuickPlayIndicator(int x, bool snapped) -{ - wxClientDC dc(this); - - // Erase the old indicator. - if (mOldQPIndicatorPos != x) { -#if defined(__WXMAC__) - // On OSX, if a HiDPI resolution is being used, the line will actually take up - // more than 1 pixel (even though it is drawn as 1), so we restore the surrounding - // pixels as well. (This is because the wxClientDC doesn't know about the scaling.) - dc.Blit(mOldQPIndicatorPos - 1, 0, 3, mBacking->GetHeight(), &mBackingDC, mOldQPIndicatorPos - 1, 0); -#else - dc.Blit(mOldQPIndicatorPos, 0, 1, mBacking->GetHeight(), &mBackingDC, mOldQPIndicatorPos, 0); -#endif - - mOldQPIndicatorPos = -1; - } - - if (x >= 0) { - snapped ? AColor::SnapGuidePen(&dc) : AColor::Light(&dc, false); - - // Draw indicator in all visible tracks - VisibleTrackIterator iter(GetProject()); - for (Track *t = iter.First(); t; t = iter.Next()) - { - // Convert virtual coordinate to physical - int y = t->GetY() - mViewInfo->vpos; - - // Draw the NEW indicator in its new location - AColor::Line(dc, - x, - y + kTopMargin, - x, - // Minus one more because AColor::Line includes both endpoints - y + t->GetHeight() - kBottomMargin - 1 ); - } - - mOldQPIndicatorPos = x; - } -} - double TrackPanel::GetScreenEndTime() const { int width; diff --git a/src/TrackPanel.h b/src/TrackPanel.h index 25285148a..5d27a5be6 100644 --- a/src/TrackPanel.h +++ b/src/TrackPanel.h @@ -229,8 +229,6 @@ class AUDACITY_DLL_API TrackPanel final : public wxPanel { virtual void UpdateTrackVRuler(Track *t); virtual void UpdateVRulerSize(); - virtual void DrawQuickPlayIndicator(int x, bool snapped = false); - // Returns the time corresponding to the pixel column one past the track area // (ignoring any fisheye) virtual double GetScreenEndTime() const; @@ -566,9 +564,6 @@ protected: TrackPanel *parent; } mTimer; - // Quick-Play indicator postion - int mOldQPIndicatorPos; - int mTimeCount; wxMemoryDC mBackingDC; diff --git a/src/widgets/Ruler.cpp b/src/widgets/Ruler.cpp index d2647e4ca..72562453d 100644 --- a/src/widgets/Ruler.cpp +++ b/src/widgets/Ruler.cpp @@ -66,6 +66,7 @@ array of Ruler::Label. #include #include +#include "../AColor.h" #include "../AudioIO.h" #include "../Internat.h" #include "../Project.h" @@ -75,6 +76,8 @@ array of Ruler::Label. #include "../Experimental.h" #include "../TimeTrack.h" #include "../TrackPanel.h" +#include "../TrackPanelCellIterator.h" +#include "../TrackPanelOverlay.h" #include "../Menus.h" #include "../NumberScale.h" #include "../Prefs.h" @@ -1643,6 +1646,91 @@ void RulerPanel::DoSetSize(int x, int y, ruler.SetBounds(0, 0, w-1, h-1); } +/********************************************************************** + +QuickPlayIndicatorOverlay. +Graphical helper for AdornedRulerPanel. + +**********************************************************************/ + +class QuickPlayIndicatorOverlay final : public TrackPanelOverlay +{ +public: + QuickPlayIndicatorOverlay(AudacityProject *project) + : mProject(project) + , mOldQPIndicatorPos(-1) + , mNewQPIndicatorPos(-1) + , mOldQPIndicatorSnapped(false) + , mNewQPIndicatorSnapped(false) + { + } + + virtual ~QuickPlayIndicatorOverlay() + { + } + + void Update(int x, bool snapped = false) + { + mNewQPIndicatorPos = x; + mNewQPIndicatorSnapped = snapped; + + // Not strictly needed, but this reduces the lag in updating + // track panel causing momentary mismatch between the triangle + // in the ruler and the white line. + + mProject->GetTrackPanel()->DrawOverlays(false); + } + +private: + std::pair DoGetRectangle(wxSize size) override; + void Draw + (wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) override; + + AudacityProject *mProject; + int mOldQPIndicatorPos; + int mNewQPIndicatorPos; + bool mOldQPIndicatorSnapped; + bool mNewQPIndicatorSnapped; +}; + +std::pair QuickPlayIndicatorOverlay::DoGetRectangle(wxSize size) +{ + wxRect rect(mOldQPIndicatorPos, 0, 1, size.GetHeight()); + return std::make_pair( + rect, + (mOldQPIndicatorPos != mNewQPIndicatorPos || + mOldQPIndicatorSnapped != mNewQPIndicatorSnapped) + ); +} + +void QuickPlayIndicatorOverlay::Draw +(wxDC &dc, TrackPanelCellIterator begin, TrackPanelCellIterator end) +{ + mOldQPIndicatorPos = mNewQPIndicatorPos; + mOldQPIndicatorSnapped = mNewQPIndicatorSnapped; + + if (mOldQPIndicatorPos >= 0) { + mOldQPIndicatorSnapped ? AColor::SnapGuidePen(&dc) : AColor::Light(&dc, false); + + // Draw indicator in all visible tracks + for (; begin != end; ++begin) + { + TrackPanelCellIterator::value_type data(*begin); + Track *const pTrack = dynamic_cast(data.first); + if (!pTrack) + continue; + const wxRect &rect = data.second; + + // Draw the NEW indicator in its NEW location + AColor::Line(dc, + mOldQPIndicatorPos, + rect.GetTop(), + mOldQPIndicatorPos, + rect.GetBottom()); + } + } +} + /********************************************************************** Implementation of AdornedRulerPanel. @@ -2525,6 +2613,16 @@ void AdornedRulerPanel::DoDrawIndicator(wxDC * dc) dc->DrawPolygon( 3, tri ); } +QuickPlayIndicatorOverlay *AdornedRulerPanel::GetOverlay() +{ + if (!mOverlay) { + TrackPanel *tp = mProject->GetTrackPanel(); + mOverlay = std::make_unique(mProject); + tp->AddOverlay(mOverlay.get()); + } + return mOverlay.get(); +} + // Draws the vertical line and green triangle indicating the Quick Play cursor position. void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc) { @@ -2532,7 +2630,7 @@ void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc) double latestEnd = std::max(mTracks->GetEndTime(), mProject->GetSel1()); if (dc == NULL || (mQuickPlayPos >= latestEnd)) { - tp->DrawQuickPlayIndicator(-1); + GetOverlay()->Update(-1); mLastQuickPlayX = -1; return; } @@ -2564,7 +2662,7 @@ void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc) AColor::IndicatorColor( dc, true); dc->DrawPolygon( 3, tri, x ); - tp->DrawQuickPlayIndicator(x, mIsSnapped); + GetOverlay()->Update(x, mIsSnapped); } void AdornedRulerPanel::SetPlayRegion(double playRegionStart, diff --git a/src/widgets/Ruler.h b/src/widgets/Ruler.h index d6846b01f..937fa9f5d 100644 --- a/src/widgets/Ruler.h +++ b/src/widgets/Ruler.h @@ -11,6 +11,7 @@ #ifndef __AUDACITY_RULER__ #define __AUDACITY_RULER__ +#include "../MemoryX.h" #include #include #include @@ -269,6 +270,8 @@ private: DECLARE_EVENT_TABLE() }; +class QuickPlayIndicatorOverlay; + // This is an Audacity Specific ruler panel which additionally // has border, selection markers, play marker. // Once TrackPanel uses wxSizers, we will derive it from some @@ -320,6 +323,7 @@ private: void DoDrawCursor(wxDC * dc); void DoDrawSelection(wxDC * dc); void DoDrawIndicator(wxDC * dc); + QuickPlayIndicatorOverlay *GetOverlay(); void DrawQuickPlayIndicator(wxDC * dc /*NULL to DELETE old only*/); void DoDrawPlayRegion(wxDC * dc); @@ -398,6 +402,8 @@ private: int mLastMouseX; // Pixel position bool mIsDragging; + std::unique_ptr mOverlay; + DECLARE_EVENT_TABLE() };