1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-16 16:10:06 +02:00

Implement drag-scrub, compatibly with the existing move-scrub...

Also fix scroll-scrub and remove obsolete mouse preferences messages
This commit is contained in:
Paul Licameli 2016-05-10 09:40:06 -04:00
commit 9f8e34ad0f
12 changed files with 150 additions and 83 deletions

View File

@ -402,6 +402,14 @@ struct AudioIO::ScrubQueue
}
~ScrubQueue() {}
double LastTimeInQueue() const
{
// Needed by the main thread sometimes
wxCriticalSectionLocker locker(mUpdating);
const Entry &previous = mEntries[(mLeadingIdx + Size - 1) % Size];
return previous.mS1 / mRate;
}
bool Producer(double end, double maxSpeed, bool bySpeed, bool maySkip)
{
// Main thread indicates a scrubbing interval
@ -670,7 +678,7 @@ private:
const double mRate;
const long mMinStutter;
wxLongLong mLastScrubTimeMillis;
wxCriticalSection mUpdating;
mutable wxCriticalSection mUpdating;
};
#endif
@ -2426,6 +2434,15 @@ bool AudioIO::EnqueueScrubBySignedSpeed(double speed, double maxSpeed, bool mayS
else
return false;
}
double AudioIO::GetLastTimeInScrubQueue() const
{
if (mScrubQueue)
return mScrubQueue->LastTimeInQueue();
else
return -1.0;
}
#endif
bool AudioIO::IsBusy()

View File

@ -208,6 +208,10 @@ class AUDACITY_DLL_API AudioIO final {
* Return true if some work was really enqueued.
*/
bool EnqueueScrubBySignedSpeed(double speed, double maxSpeed, bool maySkip);
/** \brief return the ending time of the last enqueued scrub interval.
*/
double GetLastTimeInScrubQueue() const;
#endif
/** \brief Returns true if audio i/o is busy starting, stopping, playing,

View File

@ -173,7 +173,6 @@
// The following enable parts of the scrubbing user interface.
#define EXPERIMENTAL_SCRUBBING_BASIC
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
#define EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
#define EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
#endif
#endif

View File

@ -2281,8 +2281,13 @@ void AudacityProject::OnRecordAppend()
GetControlToolBar()->OnRecord(evt);
}
// The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and "OnStopSelect" merged.
void AudacityProject::OnPlayStopSelect()
{
DoPlayStopSelect(false, false);
}
// The code for "OnPlayStopSelect" is simply the code of "OnPlayStop" and "OnStopSelect" merged.
void AudacityProject::DoPlayStopSelect(bool click, bool shift)
{
wxCommandEvent evt;
ControlToolBar *toolbar = GetControlToolBar();
@ -2291,7 +2296,36 @@ void AudacityProject::OnPlayStopSelect()
if (gAudioIO->IsStreamActive(GetAudioIOToken())) {
toolbar->SetPlay(false); //Pops
toolbar->SetStop(true); //Pushes stop down
mViewInfo.selectedRegion.setT0(gAudioIO->GetStreamTime(), false);
// change the selection
auto time = gAudioIO->GetStreamTime();
auto &selection = mViewInfo.selectedRegion;
if (shift && click) {
// Change the region selection, as if by shift-click at the play head
auto t0 = selection.t0(), t1 = selection.t1();
if (time < t0)
// Grow selection
t0 = time;
else if (time > t1)
// Grow selection
t1 = time;
else {
// Shrink selection, changing the nearer boundary
if (fabs(t0 - time) < fabs(t1 - time))
t0 = time;
else
t1 = time;
}
selection.setTimes(t0, t1);
}
else if (click)
// Set a point selection, as if by a click at the play head
selection.setTimes(time, time);
else
// How stop and set cursor always worked
// -- change t0, collapsing to point only if t1 was greater
selection.setT0(time, false);
ModifyState(false); // without bWantsAutoSave
toolbar->OnStop(evt);
}

View File

@ -80,6 +80,7 @@ void OnSeekRightLong();
bool MakeReadyToPlay(bool loop = false, bool cutpreview = false); // Helper function that sets button states etc.
void OnPlayStop();
void DoPlayStopSelect(bool click, bool shift);
void OnPlayStopSelect();
void OnPlayOneSecond();
void OnPlayToSelection();

View File

@ -5364,7 +5364,6 @@ void AudacityProject::PlaybackScroller::OnTimer(wxCommandEvent &event)
// Let other listeners get the notification
event.Skip();
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
if (mActive && mProject->IsAudioActive())
{
// Pan the view, so that we center the play indicator.
@ -5382,5 +5381,4 @@ void AudacityProject::PlaybackScroller::OnTimer(wxCommandEvent &event)
viewInfo.h = std::max(0.0, viewInfo.h);
trackPanel->Refresh(false);
}
#endif
}

View File

@ -5472,12 +5472,9 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
(event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0);
if (event.ShiftDown()
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
// Don't pan during smooth scrolling. That would conflict with keeping
// the play indicator centered.
&& !GetProject()->GetScrubber().IsScrollScrubbing()
#endif
)
&& !GetProject()->GetScrubber().IsScrollScrubbing())
{
// MM: Scroll left/right when used with Shift key down
mListener->TP_ScrollWindow(
@ -5506,15 +5503,12 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
// Time corresponding to mouse position
wxCoord xx;
double center_h;
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
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);
}
else
#endif
{
else {
xx = event.m_x;
center_h = mViewInfo->PositionToTime(xx, trackLeftEdge);
}

View File

@ -110,13 +110,6 @@ void MousePrefs::CreateList()
AddItem(_("Left-Drag"), _("Select"), _("Set Selection Range"));
AddItem(_("Shift-Left-Click"), _("Select"), _("Extend Selection Range"));
AddItem(_("Left-Double-Click"), _("Select"), _("Select Clip or Entire Track"));
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
AddItem(CTRL + _("-Left-Click"), _("Select"), _("Scrub"));
AddItem(CTRL + _("-Left-Drag"), _("Select"), _("Seek"));
#endif
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
AddItem(CTRL + _("-Left-Double-Click"), _("Select"), _("Scroll-scrub"));
#endif
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
AddItem(_("Wheel-Rotate"), _("Select"), _("Change scrub speed"));
#endif

View File

@ -188,11 +188,7 @@ namespace {
void Scrubber::MarkScrubStart(
// Assume xx is relative to the left edge of TrackPanel!
wxCoord xx
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
, bool smoothScrolling
#endif
, bool alwaysSeeking
wxCoord xx, bool smoothScrolling, bool alwaysSeeking
)
{
UncheckAllMenuItems();
@ -200,9 +196,7 @@ void Scrubber::MarkScrubStart(
// 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
SetScrollScrubbing (smoothScrolling);
#endif
mSmoothScrollingScrub = smoothScrolling;
mAlwaysSeeking = alwaysSeeking;
ControlToolBar * const ctb = mProject->GetControlToolBar();
@ -236,6 +230,9 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
if (IsScrubbing())
return false;
else {
const auto state = ::wxGetMouseState();
mDragging = state.LeftIsDown();
const bool busy = gAudioIO->IsBusy();
if (busy && gAudioIO->GetNumCaptureChannels() > 0) {
// Do not stop recording, and don't try to start scrubbing after
@ -265,6 +262,14 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
mScrubStartPosition = position;
}
if (mDragging && mSmoothScrollingScrub) {
auto delta = time0 - time1;
time0 = std::max(0.0, std::min(maxTime,
(viewInfo.h + mProject->GetScreenEndTime()) / 2
));
time1 = time0 + delta;
}
AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions());
options.timeTrack = NULL;
options.scrubDelay = (kTimerInterval / 1000.0);
@ -278,8 +283,10 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
p->GetTranscriptionToolBar()->GetPlaySpeed();
}
#else
// That idea seems unpopular... just make it one
mMaxScrubSpeed = options.maxScrubSpeed = 1.0;
// That idea seems unpopular... just make it one for move-scrub,
// but big for drag-scrub
mMaxScrubSpeed = options.maxScrubSpeed =
mDragging ? AudioIO::GetMaxScrubSpeed() : 1.0;
#endif
options.maxScrubTime = mProject->GetTracks()->GetEndTime();
ControlToolBar::PlayAppearance appearance =
@ -303,8 +310,11 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
// Wait to test again
mScrubStartClockTimeMillis = ::wxGetLocalTimeMillis();
if (IsScrubbing())
if (IsScrubbing()) {
mProject->GetPlaybackScroller().Activate(mSmoothScrollingScrub);
mScrubHasFocus = true;
mLastScrubPosition = xx;
}
// Return true whether we started scrub, or are still waiting to decide.
return true;
@ -313,13 +323,19 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
void Scrubber::ContinueScrubbing()
{
const wxMouseState state(::wxGetMouseState());
if (mDragging && !state.LeftIsDown()) {
// Stop and set cursor
mProject->DoPlayStopSelect(true, state.ShiftDown());
return;
}
// 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();
// Decide whether to skip play, because either mouse is down now,
@ -337,26 +353,32 @@ void Scrubber::ContinueScrubbing()
}
const wxPoint position = trackPanel->ScreenToClient(state.GetPosition());
// When we don't have focus, enqueue silent scrubs until we regain focus.
const auto &viewInfo = mProject->GetViewInfo();
bool result = false;
if (!mScrubHasFocus)
// When we don't have focus, enqueue silent scrubs until we regain focus.
result = gAudioIO->EnqueueScrubBySignedSpeed(0, mMaxScrubSpeed, false);
else if (mDragging && mSmoothScrollingScrub) {
const auto lastTime = gAudioIO->GetLastTimeInScrubQueue();
const auto delta = mLastScrubPosition - position.x;
const double time = viewInfo.OffsetTimeByPixels(lastTime, delta);
result = gAudioIO->EnqueueScrubByPosition(time, mMaxScrubSpeed, false);
mLastScrubPosition = position.x;
}
else {
const double time = mProject->GetViewInfo().PositionToTime(position.x, trackPanel->GetLeftOffset());
const double time = viewInfo.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);
(time, seek ? 1.0 : mMaxScrubSpeed, seek);
}
if (result)
@ -364,12 +386,9 @@ void Scrubber::ContinueScrubbing()
// 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
{
else {
if (mScrubSpeedDisplayCountdown > 0)
--mScrubSpeedDisplayCountdown;
}
@ -380,7 +399,8 @@ void Scrubber::StopScrubbing()
UncheckAllMenuItems();
mScrubStartPosition = -1;
SetScrollScrubbing (false);
mProject->GetPlaybackScroller().Activate(false);
mDragging = false;
if (!IsScrubbing())
{
@ -391,12 +411,11 @@ void Scrubber::StopScrubbing()
}
mProject->GetRulerPanel()->HideQuickPlayIndicator();
}
void Scrubber::SetScrollScrubbing(bool scrollScrubbing)
{
mSmoothScrollingScrub = scrollScrubbing;
mProject->GetPlaybackScroller().Activate(scrollScrubbing);
// Need this in case ruler gets the mouse-up event after escaping scrubbing:
// prevent reappearance of the
// quick play guideline
mProject->GetRulerPanel()->IgnoreMouseUp();
}
bool Scrubber::IsScrubbing() const
@ -408,24 +427,22 @@ bool Scrubber::IsScrubbing() const
else {
const_cast<Scrubber&>(*this).mScrubToken = -1;
const_cast<Scrubber&>(*this).mScrubStartPosition = -1;
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
// Don't call SetScrollScrubbing
const_cast<Scrubber&>(*this).mSmoothScrollingScrub = false;
#endif
return false;
}
}
bool Scrubber::ShouldDrawScrubSpeed()
{
if (mDragging)
return false;
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
);
}
@ -439,6 +456,10 @@ double Scrubber::FindScrubSpeed(bool seeking, double time) const
void Scrubber::HandleScrollWheel(int steps)
{
if (mDragging)
// Not likely you would spin it with the left button down, but...
return;
const int newLogMaxScrubSpeed = mLogMaxScrubSpeed + steps;
static const double maxScrubSpeedBase =
pow(2.0, 1.0 / ScrubSpeedStepsPerOctave);
@ -447,9 +468,7 @@ void Scrubber::HandleScrollWheel(int steps)
newSpeed <= AudioIO::GetMaxScrubSpeed()) {
mLogMaxScrubSpeed = newLogMaxScrubSpeed;
mMaxScrubSpeed = newSpeed;
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
if (!mSmoothScrollingScrub)
#endif
// Show the speed for one second
mScrubSpeedDisplayCountdown = kOneSecondCountdown + 1;
}
@ -472,7 +491,8 @@ void Scrubber::Forwarder::OnMouse(wxMouseEvent &event)
if (isScrubbing && !event.HasAnyModifiers()) {
if(event.LeftDown() ||
(event.LeftIsDown() && event.Dragging())) {
scrubber.mScrubSeekPress = true;
if (!scrubber.mDragging)
scrubber.mScrubSeekPress = true;
auto xx = ruler->ScreenToClient(::wxGetMousePosition()).x;
ruler->UpdateQuickPlayPos(xx);
ruler->ShowQuickPlayIndicator();
@ -544,11 +564,9 @@ void ScrubbingOverlay::Draw(OverlayPanel &, wxDC &dc)
// (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());
@ -565,12 +583,13 @@ void ScrubbingOverlay::OnTimer(wxCommandEvent &event)
auto position = ::wxGetMousePosition();
{
auto xx = ruler->ScreenToClient(position).x;
ruler->UpdateQuickPlayPos(xx);
if(scrubber.HasStartedScrubbing()) {
auto xx = ruler->ScreenToClient(position).x;
ruler->UpdateQuickPlayPos(xx);
if(!isScrubbing && scrubber.HasStartedScrubbing()) {
// Really start scrub if motion is far enough
scrubber.MaybeStartScrubbing(xx);
if (!isScrubbing)
// Really start scrub if motion is far enough
scrubber.MaybeStartScrubbing(xx);
}
if (!isScrubbing) {
@ -603,23 +622,17 @@ void ScrubbingOverlay::OnTimer(wxCommandEvent &event)
// Find the text
const double maxScrubSpeed = GetScrubber().GetMaxScrubSpeed();
const double speed =
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
scrubber.IsScrollScrubbing()
? scrubber.FindScrubSpeed
(seeking, mProject->GetViewInfo().PositionToTime(position.x, trackPanel->GetLeftOffset()))
:
#endif
maxScrubSpeed;
: maxScrubSpeed;
const wxChar *format =
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
scrubber.IsScrollScrubbing()
? seeking
? wxT("%+.2fX")
: wxT("%+.2f")
:
#endif
wxT("%.2f");
: wxT("%.2f");
mNextScrubSpeedText = wxString::Format(format, speed);
@ -657,7 +670,7 @@ Scrubber &ScrubbingOverlay::GetScrubber()
bool Scrubber::PollIsSeeking()
{
return mAlwaysSeeking || ::wxGetMouseState().LeftIsDown();
return !mDragging && (mAlwaysSeeking || ::wxGetMouseState().LeftIsDown());
}
void Scrubber::DoScrub(bool scroll, bool seek)
@ -677,7 +690,8 @@ void Scrubber::DoScrub(bool scroll, bool seek)
MarkScrubStart(xx, scroll, seek);
}
else if(!match) {
SetScrollScrubbing(scroll);
mSmoothScrollingScrub = scroll;
mProject->GetPlaybackScroller().Activate(scroll);
mAlwaysSeeking = seek;
UncheckAllMenuItems();
CheckMenuItem();

View File

@ -29,11 +29,8 @@ public:
// Assume xx is relative to the left edge of TrackPanel!
void MarkScrubStart(
wxCoord xx
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
, bool smoothScrolling
#endif
, bool alwaysSeeking // if false, can switch seeking or scrubbing
wxCoord xx, bool smoothScrolling,
bool alwaysSeeking // if false, can switch seeking or scrubbing
// by mouse button state
);
@ -57,7 +54,6 @@ public:
bool IsScrollScrubbing() const // If true, implies HasStartedScrubbing()
{ return mSmoothScrollingScrub; }
void SetScrollScrubbing(bool scrollScrubbing);
bool IsAlwaysSeeking() const
{ return mAlwaysSeeking; }
@ -113,10 +109,12 @@ private:
bool mScrubHasFocus;
int mScrubSpeedDisplayCountdown;
wxCoord mScrubStartPosition;
wxCoord mLastScrubPosition {};
double mMaxScrubSpeed;
bool mScrubSeekPress;
bool mSmoothScrollingScrub;
bool mAlwaysSeeking{};
bool mAlwaysSeeking {};
bool mDragging {};
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
int mLogMaxScrubSpeed;

View File

@ -2326,6 +2326,17 @@ bool AdornedRulerPanel::IsWithinMarker(int mousePosX, double markerTime)
void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
{
if (mIgnoreMouseUp) {
if (evt.Dragging())
return;
else if (evt.ButtonUp()) {
mIgnoreMouseUp = false;
return;
}
else
mIgnoreMouseUp = false;
}
// PRL: why do I need these two lines on Windows but not on Mac?
if (evt.ButtonDown(wxMOUSE_BTN_ANY))
SetFocus();
@ -2654,10 +2665,12 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
if (mDoubleClick)
return;
HideQuickPlayIndicator();
if (HasCapture())
ReleaseMouse();
else
return;
HideQuickPlayIndicator();
mCaptureState = CaptureState{};

View File

@ -350,6 +350,7 @@ public:
void ShowQuickPlayIndicator();
void HideQuickPlayIndicator();
void UpdateQuickPlayPos(wxCoord &mousPosX);
void IgnoreMouseUp() { mIgnoreMouseUp = true; }
private:
void OnCapture(wxCommandEvent & evt);
@ -531,6 +532,7 @@ private:
mutable wxFont mButtonFont;
bool mDoubleClick {};
bool mIgnoreMouseUp {};
DECLARE_EVENT_TABLE()