1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-22 15:20:15 +02:00

Scrubbing Phase 2 -- (left or right or double) click the play head in the ruler

This commit is contained in:
Paul Licameli 2016-04-26 12:17:50 -04:00
commit d0a8e33e1c
16 changed files with 368 additions and 199 deletions

View File

@ -689,8 +689,6 @@ struct Final_action {
Final_action(F f) : clean{ f } {} Final_action(F f) : clean{ f } {}
~Final_action() { clean(); } ~Final_action() { clean(); }
F clean; F clean;
Final_action(const Final_action&) PROHIBITED;
Final_action& operator= (const Final_action&) PROHIBITED;
}; };
// Function template with type deduction lets you construct Final_action // Function template with type deduction lets you construct Final_action

View File

@ -1613,7 +1613,7 @@ void AudacityProject::TP_ScrollWindow(double scrollto)
// handler in Track Panel. A positive argument makes the window // handler in Track Panel. A positive argument makes the window
// scroll down, while a negative argument scrolls up. // scroll down, while a negative argument scrolls up.
// //
void AudacityProject::TP_ScrollUpDown(int delta) bool AudacityProject::TP_ScrollUpDown(int delta)
{ {
int oldPos = mVsbar->GetThumbPosition(); int oldPos = mVsbar->GetThumbPosition();
int pos = oldPos + delta; int pos = oldPos + delta;
@ -1634,7 +1634,10 @@ void AudacityProject::TP_ScrollUpDown(int delta)
wxScrollEvent dummy; wxScrollEvent dummy;
OnScroll(dummy); OnScroll(dummy);
return true;
} }
else
return false;
} }
void AudacityProject::FixScrollbars() void AudacityProject::FixScrollbars()

View File

@ -430,7 +430,7 @@ class AUDACITY_DLL_API AudacityProject final : public wxFrame,
void TP_ScrollLeft() override; void TP_ScrollLeft() override;
void TP_ScrollRight() override; void TP_ScrollRight() override;
void TP_ScrollWindow(double scrollto) override; void TP_ScrollWindow(double scrollto) override;
void TP_ScrollUpDown(int delta) override; bool TP_ScrollUpDown(int delta) override;
void TP_HandleResize() override; void TP_HandleResize() override;
// ToolBar // ToolBar

View File

@ -1525,13 +1525,7 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t,
// If not shift-down and not snapping center, then // If not shift-down and not snapping center, then
// choose boundaries only in snapping tolerance, // choose boundaries only in snapping tolerance,
// and may choose center. // and may choose center.
// But don't change the cursor when scrubbing.
SelectionBoundary boundary = SelectionBoundary boundary =
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
GetProject()->GetScrubber().IsScrubbing()
? SBNone
:
#endif
ChooseBoundary(event, t, rect, !bShiftDown, !bShiftDown); ChooseBoundary(event, t, rect, !bShiftDown, !bShiftDown);
#ifdef USE_MIDI #ifdef USE_MIDI
@ -1705,14 +1699,7 @@ void TrackPanel::HandleCursor(const wxMouseEvent & event)
tip = ttb->GetMessageForTool(tool); tip = ttb->GetMessageForTool(tool);
const auto &scrubber = GetProject()->GetScrubber(); if( tool != selectTool )
if (scrubber.HasStartedScrubbing()) {
if (scrubber.IsScrollScrubbing())
tip = _("Move to adjust speed, click to skip, ESC to stop.");
else
tip = _("Move to scrub, click to seek, ESC to stop.");
}
else if( tool != selectTool )
{ {
// We don't include the select tool in // We don't include the select tool in
// SetCursorAndTipByTool() because it's more complex than // SetCursorAndTipByTool() because it's more complex than
@ -1773,11 +1760,7 @@ void TrackPanel::HandleSelect(wxMouseEvent & event)
mFreqSelMode = FREQ_SEL_INVALID; mFreqSelMode = FREQ_SEL_INVALID;
#endif #endif
} else if (event.LeftDClick() && !event.ShiftDown() } else if (event.LeftDClick() && !event.ShiftDown()) {
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
&& !event.CmdDown()
#endif
) {
if (!mCapturedTrack) { if (!mCapturedTrack) {
wxRect rect; wxRect rect;
mCapturedTrack = mCapturedTrack =
@ -1950,29 +1933,9 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
#endif #endif
) { ) {
#ifdef EXPERIMENTAL_SCRUBBING_BASIC // Used to jump the play head, but it is redundant with timeline quick play
if (
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
event.LeftDClick() ||
#endif
event.LeftDown()) {
SetCapturedTrack(nullptr, IsUncaptured);
GetProject()->GetScrubber().MarkScrubStart(
event
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
, event.LeftDClick()
#endif
, false
);
return;
}
#else
// StartOrJumpPlayback(event); // StartOrJumpPlayback(event);
#endif
// Not starting a drag // Not starting a drag
SetCapturedTrack(NULL, IsUncaptured); SetCapturedTrack(NULL, IsUncaptured);
return; return;
@ -2693,14 +2656,6 @@ void TrackPanel::Stretch(int mouseXCoordinate, int trackLeftEdge,
/// handle it here. /// handle it here.
void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack) void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
{ {
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
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 // AS: If we're not in the process of selecting (set in
// the SelectionHandleClick above), fuhggeddaboudit. // the SelectionHandleClick above), fuhggeddaboudit.
if (mMouseCapture!=IsSelecting) if (mMouseCapture!=IsSelecting)
@ -5518,6 +5473,13 @@ void TrackPanel::HandleResize(wxMouseEvent & event)
/// Handle mouse wheel rotation (for zoom in/out, vertical and horizontal scrolling) /// Handle mouse wheel rotation (for zoom in/out, vertical and horizontal scrolling)
void TrackPanel::HandleWheelRotation(wxMouseEvent & event) void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
{ {
if(!event.HasAnyModifiers()) {
// We will later un-skip if we do anything, but if we don't,
// propagate the event up for the sake of the scrubber
event.Skip();
event.ResumePropagation(wxEVENT_PROPAGATE_MAX);
}
if (GetTracks()->IsEmpty()) if (GetTracks()->IsEmpty())
// Scrolling and Zoom in and out commands are disabled when there are no tracks. // Scrolling and Zoom in and out commands are disabled when there are no tracks.
// This should be disabled too for consistency. Otherwise // This should be disabled too for consistency. Otherwise
@ -5530,6 +5492,9 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
Track *const pTrack = FindTrack(event.m_x, event.m_y, true, false, &rect); Track *const pTrack = FindTrack(event.m_x, event.m_y, true, false, &rect);
if (pTrack && event.m_x >= GetVRulerOffset()) { if (pTrack && event.m_x >= GetVRulerOffset()) {
HandleWheelRotationInVRuler(event, pTrack, rect); HandleWheelRotationInVRuler(event, pTrack, rect);
// Always stop propagation even if the ruler didn't change. The ruler
// is a narrow enough target.
event.Skip(false);
return; return;
} }
} }
@ -5619,7 +5584,8 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
double lines = steps * 4 + mVertScrollRemainder; double lines = steps * 4 + mVertScrollRemainder;
mVertScrollRemainder = lines - floor(lines); mVertScrollRemainder = lines - floor(lines);
lines = floor(lines); lines = floor(lines);
mListener->TP_ScrollUpDown((int)-lines); const bool didSomething = mListener->TP_ScrollUpDown((int)-lines);
event.Skip(!didSomething);
} }
} }
} }
@ -5907,6 +5873,13 @@ void TrackPanel::OnMouseEvent(wxMouseEvent & event)
if (event.m_wheelRotation != 0) if (event.m_wheelRotation != 0)
HandleWheelRotation(event); HandleWheelRotation(event);
if (event.LeftDown() || event.LeftIsDown() || event.Moving()) {
// Skip, even if we do something, so that the left click or drag
// may have an additional effect in the scrubber.
event.Skip();
event.ResumePropagation(wxEVENT_PROPAGATE_MAX);
}
if (!mAutoScrolling) { if (!mAutoScrolling) {
mMouseMostRecentX = event.m_x; mMouseMostRecentX = event.m_x;
mMouseMostRecentY = event.m_y; mMouseMostRecentY = event.m_y;
@ -6366,20 +6339,6 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event)
return; return;
} }
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
if (GetProject()->GetScrubber().IsScrubbing() &&
GetRect().Contains(event.GetPosition()) &&
(!pTrack ||
pTrack->GetKind() == Track::Wave)) {
if (event.LeftDown()) {
GetProject()->GetScrubber().SetSeeking();
return;
}
else if (event.LeftIsDown())
return;
}
#endif
bool handled = false; bool handled = false;
if (pTrack && (pTrack->GetKind() == Track::Wave) && if (pTrack && (pTrack->GetKind() == Track::Wave) &&

View File

@ -34,7 +34,7 @@ class AUDACITY_DLL_API TrackPanelListener /* not final */ {
virtual void TP_ScrollLeft() = 0; virtual void TP_ScrollLeft() = 0;
virtual void TP_ScrollRight() = 0; virtual void TP_ScrollRight() = 0;
virtual void TP_ScrollWindow(double scrollto) = 0; virtual void TP_ScrollWindow(double scrollto) = 0;
virtual void TP_ScrollUpDown(int delta) = 0; virtual bool TP_ScrollUpDown(int delta) = 0;
virtual void TP_HandleResize() = 0; virtual void TP_HandleResize() = 0;
}; };

View File

@ -10,6 +10,7 @@
#define __AUDACITY_COMMAND_FUNCTORS__ #define __AUDACITY_COMMAND_FUNCTORS__
#include <wx/string.h> #include <wx/string.h>
#include <wx/event.h>
#include "../MemoryX.h" #include "../MemoryX.h"
class wxEvent; class wxEvent;
@ -61,6 +62,24 @@ private:
const audCommandKeyFunction<OBJ> mCommandKeyFunction; const audCommandKeyFunction<OBJ> mCommandKeyFunction;
}; };
// This allows functions to be used either by command manager or by a wxMenu popup,
// but the functions MUST ignore the argument!
template<typename OBJ>
using audCommandPopupFunction = void (OBJ::*)(wxCommandEvent&);
template<typename OBJ>
class PopupFunctor final : public CommandFunctor
{
public:
explicit PopupFunctor(OBJ *This, audCommandPopupFunction<OBJ> pfn)
: mThis{ This }, mCommandPopupFunction{ pfn } {}
void operator () (int, const wxEvent *) override
{ wxCommandEvent dummy; (mThis->*mCommandPopupFunction) (dummy); }
private:
OBJ *const mThis;
const audCommandPopupFunction<OBJ> mCommandPopupFunction;
};
template<typename OBJ> template<typename OBJ>
using audCommandListFunction = void (OBJ::*)(int); using audCommandListFunction = void (OBJ::*)(int);
@ -108,6 +127,11 @@ inline CommandFunctorPointer MakeFunctor(OBJ *This,
audCommandKeyFunction<OBJ> pfn) audCommandKeyFunction<OBJ> pfn)
{ return CommandFunctorPointer{ safenew KeyFunctor<OBJ>{ This, pfn } }; } { return CommandFunctorPointer{ safenew KeyFunctor<OBJ>{ This, pfn } }; }
template<typename OBJ>
inline CommandFunctorPointer MakeFunctor(OBJ *This,
audCommandPopupFunction<OBJ> pfn)
{ return CommandFunctorPointer{ safenew PopupFunctor<OBJ>{ This, pfn } }; }
template<typename OBJ> template<typename OBJ>
inline CommandFunctorPointer MakeFunctor(OBJ *This, inline CommandFunctorPointer MakeFunctor(OBJ *This,
audCommandListFunction<OBJ> pfn) audCommandListFunction<OBJ> pfn)

View File

@ -276,6 +276,7 @@ DEFINE_EVENT_TYPE(EVT_TOOLBAR_UPDATED)
BEGIN_EVENT_TABLE( ToolBar, wxPanel ) BEGIN_EVENT_TABLE( ToolBar, wxPanel )
EVT_PAINT( ToolBar::OnPaint ) EVT_PAINT( ToolBar::OnPaint )
EVT_ERASE_BACKGROUND( ToolBar::OnErase ) EVT_ERASE_BACKGROUND( ToolBar::OnErase )
EVT_MOUSE_EVENTS( ToolBar::OnMouseEvents )
END_EVENT_TABLE() END_EVENT_TABLE()
// //
@ -841,6 +842,13 @@ void ToolBar::OnPaint( wxPaintEvent & event )
#endif #endif
} }
void ToolBar::OnMouseEvents(wxMouseEvent &event)
{
// Do this hack so scrubber can detect mouse drags anywhere
event.ResumePropagation(wxEVENT_PROPAGATE_MAX);
event.Skip();
}
int ToolBar::GetResizeGrabberWidth() int ToolBar::GetResizeGrabberWidth()
{ {
return RWIDTH; return RWIDTH;

View File

@ -188,6 +188,7 @@ class ToolBar /* not final */ : public wxPanel
void OnErase(wxEraseEvent & event); void OnErase(wxEraseEvent & event);
void OnPaint(wxPaintEvent & event); void OnPaint(wxPaintEvent & event);
void OnMouseEvents(wxMouseEvent &event);
protected: protected:
wxString mLabel; wxString mLabel;

View File

@ -64,6 +64,7 @@ BEGIN_EVENT_TABLE( ToolDock, wxPanel )
EVT_ERASE_BACKGROUND( ToolDock::OnErase ) EVT_ERASE_BACKGROUND( ToolDock::OnErase )
EVT_PAINT( ToolDock::OnPaint ) EVT_PAINT( ToolDock::OnPaint )
EVT_SIZE( ToolDock::OnSize ) EVT_SIZE( ToolDock::OnSize )
EVT_MOUSE_EVENTS( ToolDock::OnMouseEvents )
END_EVENT_TABLE() END_EVENT_TABLE()
// //
@ -556,3 +557,10 @@ void ToolDock::OnPaint( wxPaintEvent & WXUNUSED(event) )
} }
} }
} }
void ToolDock::OnMouseEvents(wxMouseEvent &event)
{
// Do this hack so scrubber can detect mouse drags anywhere
event.ResumePropagation(wxEVENT_PROPAGATE_MAX);
event.Skip();
}

View File

@ -70,6 +70,7 @@ class ToolDock final : public wxPanel
void OnSize( wxSizeEvent & event ); void OnSize( wxSizeEvent & event );
void OnPaint( wxPaintEvent & event ); void OnPaint( wxPaintEvent & event );
void OnGrabber( GrabberEvent & event ); void OnGrabber( GrabberEvent & event );
void OnMouseEvents(wxMouseEvent &event);
private: private:

View File

@ -55,9 +55,6 @@
#include "../Theme.h" #include "../Theme.h"
#include "../Experimental.h" #include "../Experimental.h"
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
#include "../tracks/ui/Scrubbing.h"
#endif
#include "../widgets/AButton.h" #include "../widgets/AButton.h"
@ -87,21 +84,7 @@ ToolsToolBar::ToolsToolBar()
wxASSERT( drawTool == drawTool - firstTool ); wxASSERT( drawTool == drawTool - firstTool );
wxASSERT( multiTool == multiTool - firstTool ); wxASSERT( multiTool == multiTool - firstTool );
{ mMessageOfTool[selectTool] = _("Click and drag to select audio");
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
mMessageOfTool[selectTool] =
#if defined(__WXMAC__)
_("Click and drag to select audio, Command-Click to scrub, Command-Double-Click to scroll-scrub, Command-drag to seek")
#else
_("Click and drag to select audio, Ctrl-Click to scrub, Ctrl-Double-Click to scroll-scrub, Ctrl-drag to seek")
#endif
;
#else
mMessageOfTool[selectTool] = _("Click and drag to select audio");
#endif
}
mMessageOfTool[envelopeTool] = _("Click and drag to edit the amplitude envelope"); mMessageOfTool[envelopeTool] = _("Click and drag to edit the amplitude envelope");
mMessageOfTool[drawTool] = _("Click and drag to edit the samples"); mMessageOfTool[drawTool] = _("Click and drag to edit the samples");
@ -219,14 +202,6 @@ void ToolsToolBar::SetCurrentTool(int tool, bool show)
//In multi-mode the current tool is shown by the //In multi-mode the current tool is shown by the
//cursor icon. The buttons are not updated. //cursor icon. The buttons are not updated.
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
if (tool != selectTool) {
AudacityProject *const p = GetActiveProject();
if (p)
p->GetScrubber().StopScrubbing();
}
#endif
bool leavingMulticlipMode = bool leavingMulticlipMode =
IsDown(multiTool) && show && tool != multiTool; IsDown(multiTool) && show && tool != multiTool;
@ -290,14 +265,6 @@ void ToolsToolBar::OnTool(wxCommandEvent & evt)
else else
mTool[i]->PopUp(); mTool[i]->PopUp();
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
if (0 != mCurrentTool) {
AudacityProject *const p = GetActiveProject();
if (p)
p->GetScrubber().StopScrubbing();
}
#endif
RedrawAllProjects(); RedrawAllProjects();
gPrefs->Write(wxT("/GUI/ToolBars/Tools/MultiToolActive"), gPrefs->Write(wxT("/GUI/ToolBars/Tools/MultiToolActive"),

View File

@ -19,6 +19,7 @@ Paul Licameli split from TrackPanel.cpp
#include "../../TrackPanelCellIterator.h" #include "../../TrackPanelCellIterator.h"
#include "../../commands/CommandFunctors.h" #include "../../commands/CommandFunctors.h"
#include "../../toolbars/ControlToolBar.h" #include "../../toolbars/ControlToolBar.h"
#include "../../widgets/Ruler.h"
#include <algorithm> #include <algorithm>
@ -126,10 +127,12 @@ Scrubber::Scrubber(AudacityProject *project)
wxTheApp->Connect wxTheApp->Connect
(wxEVT_ACTIVATE_APP, (wxEVT_ACTIVATE_APP,
wxActivateEventHandler(Scrubber::OnActivateOrDeactivateApp), NULL, this); wxActivateEventHandler(Scrubber::OnActivateOrDeactivateApp), NULL, this);
mProject->PushEventHandler(this);
} }
Scrubber::~Scrubber() Scrubber::~Scrubber()
{ {
mProject->PopEventHandler();
if (wxTheApp) if (wxTheApp)
wxTheApp->Disconnect wxTheApp->Disconnect
(wxEVT_ACTIVATE_APP, (wxEVT_ACTIVATE_APP,
@ -141,7 +144,7 @@ namespace {
wxString name; wxString name;
wxString label; wxString label;
wxString status; wxString status;
void (Scrubber::*memFn)(); void (Scrubber::*memFn)(wxCommandEvent&);
bool scroll; bool scroll;
bool seek; bool seek;
@ -180,7 +183,8 @@ namespace {
} }
void Scrubber::MarkScrubStart( void Scrubber::MarkScrubStart(
const wxMouseEvent &event // Assume xx is relative to the left edge of TrackPanel!
wxCoord xx
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL #ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
, bool smoothScrolling , bool smoothScrolling
#endif #endif
@ -189,8 +193,6 @@ void Scrubber::MarkScrubStart(
{ {
UncheckAllMenuItems(); UncheckAllMenuItems();
const wxCoord xx = event.m_x;
// Don't actually start scrubbing, but collect some information // Don't actually start scrubbing, but collect some information
// needed for the decision to start scrubbing later when handling // needed for the decision to start scrubbing later when handling
// drag events. // drag events.
@ -204,13 +206,13 @@ void Scrubber::MarkScrubStart(
ControlToolBar * const ctb = mProject->GetControlToolBar(); ControlToolBar * const ctb = mProject->GetControlToolBar();
ctb->SetPlay(true, ControlToolBar::PlayAppearance::Scrub); ctb->SetPlay(true, ControlToolBar::PlayAppearance::Scrub);
ctb->UpdateStatusBar(mProject); ctb->UpdateStatusBar(mProject);
mProject->GetTrackPanel()->HandleCursor(event);
CheckMenuItem(); CheckMenuItem();
} }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
bool Scrubber::MaybeStartScrubbing(const wxMouseEvent &event) // Assume xx is relative to the left edge of TrackPanel!
bool Scrubber::MaybeStartScrubbing(wxCoord xx)
{ {
if (mScrubStartPosition < 0) if (mScrubStartPosition < 0)
return false; return false;
@ -225,7 +227,7 @@ bool Scrubber::MaybeStartScrubbing(const wxMouseEvent &event)
return false; return false;
} }
wxCoord position = event.m_x; wxCoord position = xx;
if (abs(mScrubStartPosition - position) >= SCRUBBING_PIXEL_TOLERANCE) { if (abs(mScrubStartPosition - position) >= SCRUBBING_PIXEL_TOLERANCE) {
const ViewInfo &viewInfo = mProject->GetViewInfo(); const ViewInfo &viewInfo = mProject->GetViewInfo();
TrackPanel *const trackPanel = mProject->GetTrackPanel(); TrackPanel *const trackPanel = mProject->GetTrackPanel();
@ -299,9 +301,11 @@ void Scrubber::ContinueScrubbing()
// Seek only when the pointer is in the panel. Else, scrub. // Seek only when the pointer is in the panel. Else, scrub.
const wxMouseState state(::wxGetMouseState()); const wxMouseState state(::wxGetMouseState());
TrackPanel *const trackPanel = mProject->GetTrackPanel(); TrackPanel *const trackPanel = mProject->GetTrackPanel();
const wxPoint position = trackPanel->ScreenToClient(state.GetPosition());
const bool inPanel = trackPanel->GetRect().Contains(position); // Decide whether to skip play, because either mouse is down now,
const bool seek = inPanel && (mScrubSeekPress || PollIsSeeking()); // or there was a left click event. (This is then a delayed reaction, in a
// timer callback, to a left click event detected elsewhere.)
const bool seek = PollIsSeeking() || mScrubSeekPress;
{ {
// Show the correct status for seeking. // Show the correct status for seeking.
@ -312,6 +316,7 @@ void Scrubber::ContinueScrubbing()
mAlwaysSeeking = backup; mAlwaysSeeking = backup;
} }
const wxPoint position = trackPanel->ScreenToClient(state.GetPosition());
// When we don't have focus, enqueue silent scrubs until we regain focus. // When we don't have focus, enqueue silent scrubs until we regain focus.
bool result = false; bool result = false;
if (!mScrubHasFocus) if (!mScrubHasFocus)
@ -450,6 +455,40 @@ void Scrubber::OnActivateOrDeactivateApp(wxActivateEvent &event)
event.Skip(); event.Skip();
} }
void Scrubber::OnMouse(wxMouseEvent &event)
{
auto isScrubbing = IsScrubbing();
if (!isScrubbing && HasStartedScrubbing()) {
if (!event.HasAnyModifiers() &&
event.GetEventType() == wxEVT_MOTION) {
// Really start scrub if motion is far enough
auto ruler = mProject->GetRulerPanel();
auto xx = ruler->ScreenToClient(::wxGetMousePosition()).x;
MaybeStartScrubbing(xx
);
}
}
else if (isScrubbing && !event.HasAnyModifiers()) {
if(event.LeftDown() ||
(event.LeftIsDown() && event.Dragging())) {
mScrubSeekPress = true;
auto ruler = mProject->GetRulerPanel();
auto xx = ruler->ScreenToClient(::wxGetMousePosition()).x;
ruler->UpdateQuickPlayPos(xx);
}
else if (event.m_wheelRotation) {
double steps = event.m_wheelRotation /
(event.m_wheelDelta > 0 ? (double)event.m_wheelDelta : 120.0);
HandleScrollWheel(steps);
}
else
event.Skip();
}
else
event.Skip();
}
/////////////////////////////////////////////////////////////////////////////// ///////////////////////////////////////////////////////////////////////////////
// class ScrubbingOverlay is responsible for drawing the speed numbers // class ScrubbingOverlay is responsible for drawing the speed numbers
@ -615,9 +654,7 @@ void Scrubber::DoScrub(bool scroll, bool seek)
if (!wasScrubbing) { if (!wasScrubbing) {
auto tp = mProject->GetTrackPanel(); auto tp = mProject->GetTrackPanel();
wxCoord xx = tp->ScreenToClient(::wxGetMouseState().GetPosition()).x; wxCoord xx = tp->ScreenToClient(::wxGetMouseState().GetPosition()).x;
wxMouseEvent evt; MarkScrubStart(xx, scroll, seek);
evt.SetX(xx);
MarkScrubStart(evt, scroll, seek);
} }
else if(!match) { else if(!match) {
mSmoothScrollingScrub = scroll; mSmoothScrollingScrub = scroll;
@ -636,26 +673,40 @@ void Scrubber::DoScrub(bool scroll, bool seek)
} }
} }
void Scrubber::OnScrub() void Scrubber::OnScrub(wxCommandEvent&)
{ {
DoScrub(false, false); DoScrub(false, false);
} }
void Scrubber::OnScrollScrub() void Scrubber::OnScrollScrub(wxCommandEvent&)
{ {
DoScrub(true, false); DoScrub(true, false);
} }
void Scrubber::OnSeek() void Scrubber::OnSeek(wxCommandEvent&)
{ {
DoScrub(false, true); DoScrub(false, true);
} }
void Scrubber::OnScrollSeek() void Scrubber::OnScrollSeek(wxCommandEvent&)
{ {
DoScrub(true, true); DoScrub(true, true);
} }
enum { CMD_ID = 8000 };
BEGIN_EVENT_TABLE(Scrubber, wxEvtHandler)
EVT_MENU(CMD_ID, Scrubber::OnScrub)
EVT_MENU(CMD_ID + 1, Scrubber::OnScrollScrub)
EVT_MENU(CMD_ID + 2, Scrubber::OnSeek)
EVT_MENU(CMD_ID + 3, Scrubber::OnScrollSeek)
EVT_MOUSE_EVENTS(Scrubber::OnMouse)
END_EVENT_TABLE()
static_assert(nMenuItems == 4, "wrong number of items");
const wxString &Scrubber::GetUntranslatedStateString() const const wxString &Scrubber::GetUntranslatedStateString() const
{ {
static wxString empty; static wxString empty;
@ -677,6 +728,12 @@ std::vector<wxString> Scrubber::GetAllUntranslatedStatusStrings()
return move(results); return move(results);
} }
bool Scrubber::CanScrub() const
{
auto cm = mProject->GetCommandManager();
return cm->GetEnabled(menuItems[0].name);
}
void Scrubber::AddMenuItems() void Scrubber::AddMenuItems()
{ {
auto cm = mProject->GetCommandManager(); auto cm = mProject->GetCommandManager();
@ -693,6 +750,17 @@ void Scrubber::AddMenuItems()
CheckMenuItem(); CheckMenuItem();
} }
void Scrubber::PopulateMenu(wxMenu &menu)
{
int id = CMD_ID;
auto cm = mProject->GetCommandManager();
for (const auto &item : menuItems) {
if (cm->GetEnabled(item.name))
menu.Append(id, item.label);
++id;
}
}
void Scrubber::UncheckAllMenuItems() void Scrubber::UncheckAllMenuItems()
{ {
auto cm = mProject->GetCommandManager(); auto cm = mProject->GetCommandManager();

View File

@ -27,16 +27,20 @@ public:
Scrubber(AudacityProject *project); Scrubber(AudacityProject *project);
~Scrubber(); ~Scrubber();
// Assume xx is relative to the left edge of TrackPanel!
void MarkScrubStart( void MarkScrubStart(
const wxMouseEvent &event wxCoord xx
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL #ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
, bool smoothScrolling , bool smoothScrolling
#endif #endif
, bool alwaysSeeking // if false, can switch seeking or scrubbing , bool alwaysSeeking // if false, can switch seeking or scrubbing
// by mouse button state // by mouse button state
); );
// Returns true iff the event should be considered consumed by this: // Returns true iff the event should be considered consumed by this:
bool MaybeStartScrubbing(const wxMouseEvent &event); // Assume xx is relative to the left edge of TrackPanel!
bool MaybeStartScrubbing(wxCoord xx);
void ContinueScrubbing(); void ContinueScrubbing();
// This is meant to be called only from ControlToolBar // This is meant to be called only from ControlToolBar
@ -52,6 +56,8 @@ public:
bool IsScrubbing() const; bool IsScrubbing() const;
bool IsScrollScrubbing() const // If true, implies HasStartedScrubbing() bool IsScrollScrubbing() const // If true, implies HasStartedScrubbing()
{ return mSmoothScrollingScrub; } { return mSmoothScrollingScrub; }
bool IsAlwaysSeeking() const
{ return mAlwaysSeeking; }
bool ShouldDrawScrubSpeed(); bool ShouldDrawScrubSpeed();
double FindScrubSpeed(bool seeking, double time) const; double FindScrubSpeed(bool seeking, double time) const;
@ -59,15 +65,20 @@ public:
void HandleScrollWheel(int steps); void HandleScrollWheel(int steps);
void SetSeeking() { mScrubSeekPress = true; }
bool PollIsSeeking(); bool PollIsSeeking();
void AddMenuItems(); // This returns the same as the enabled state of the menu items:
bool CanScrub() const;
void OnScrub(); // For the toolbar
void OnScrollScrub(); void AddMenuItems();
void OnSeek(); // For popup
void OnScrollSeek(); void PopulateMenu(wxMenu &menu);
void OnScrub(wxCommandEvent&);
void OnScrollScrub(wxCommandEvent&);
void OnSeek(wxCommandEvent&);
void OnScrollSeek(wxCommandEvent&);
// A string to put in the leftmost part of the status bar. // A string to put in the leftmost part of the status bar.
const wxString &GetUntranslatedStateString() const; const wxString &GetUntranslatedStateString() const;
@ -80,6 +91,7 @@ private:
void OnActivateOrDeactivateApp(wxActivateEvent & event); void OnActivateOrDeactivateApp(wxActivateEvent & event);
void UncheckAllMenuItems(); void UncheckAllMenuItems();
void CheckMenuItem(); void CheckMenuItem();
void OnMouse(wxMouseEvent &event);
private: private:
int mScrubToken; int mScrubToken;
@ -97,6 +109,8 @@ private:
#endif #endif
AudacityProject *mProject; AudacityProject *mProject;
DECLARE_EVENT_TABLE()
}; };
// Specialist in drawing the scrub speed, and listening for certain events // Specialist in drawing the scrub speed, and listening for certain events

View File

@ -452,6 +452,8 @@ void AButton::OnMouseEvent(wxMouseEvent & event)
GetActiveProject()->TP_DisplayStatusMessage(wxT("")); GetActiveProject()->TP_DisplayStatusMessage(wxT(""));
} }
} }
else
event.Skip();
} }
void AButton::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event)) void AButton::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(event))

View File

@ -1717,8 +1717,6 @@ void QuickPlayIndicatorOverlay::Draw
if (mOldQPIndicatorPos >= 0) { if (mOldQPIndicatorPos >= 0) {
mOldPreviewingScrub mOldPreviewingScrub
? AColor::IndicatorColor(&dc, true) // Draw green line for preview. ? AColor::IndicatorColor(&dc, true) // Draw green line for preview.
// Drawing during actual scrub not by this class,
// but by PlayIndicatorOverlay
: mOldQPIndicatorSnapped : mOldQPIndicatorSnapped
? AColor::SnapGuidePen(&dc) ? AColor::SnapGuidePen(&dc)
: AColor::Light(&dc, false) : AColor::Light(&dc, false)
@ -2061,14 +2059,61 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
if (mIsRecording) if (mIsRecording)
return; return;
const bool inScrubZone =
// only if scrubbing is allowed now
mProject->GetScrubber().CanScrub() &&
evt.m_y < IndicatorBigHeight();
const bool changeInScrubZone = (inScrubZone != mPrevInScrubZone);
mPrevInScrubZone = inScrubZone;
auto &scrubber = mProject->GetScrubber();
// Handle status bar messages // Handle status bar messages
if(evt.Leaving()) { UpdateStatusBar (
mProject->TP_DisplayStatusMessage(wxT("")); evt.Leaving()
? StatusChoice::Leaving
: evt.Entering() || changeInScrubZone
? inScrubZone
? StatusChoice::EnteringScrubZone
: StatusChoice::EnteringQP
: StatusChoice::NoChange
);
double t0 = mTracks->GetStartTime();
double t1 = mTracks->GetEndTime();
double sel0 = mProject->GetSel0();
double sel1 = mProject->GetSel1();
wxCoord mousePosX = evt.GetX();
UpdateQuickPlayPos(mousePosX);
// If not looping, restrict selection to end of project
if (!inScrubZone && !evt.ShiftDown()) {
mQuickPlayPos = std::min(t1, mQuickPlayPos);
} }
else if(evt.Entering()) {
// Insert timeline status bar messages here if (scrubber.HasStartedScrubbing()) {
mProject->TP_DisplayStatusMessage // If already clicked for scrub, preempt the usual event handling,
(wxT("")); // no matter what the y coordinate.
// Do this hack so scrubber can detect mouse drags anywhere
evt.ResumePropagation(wxEVENT_PROPAGATE_MAX);
if (scrubber.IsScrubbing())
evt.Skip();
else if (evt.LeftDClick())
// On the second button down, switch the pending scrub to scrolling
scrubber.MarkScrubStart(evt.m_x, true, false);
else
evt.Skip();
mQuickPlayInd = true;
wxClientDC dc(this);
DrawQuickPlayIndicator(&dc);
return;
} }
// Store the initial play region state // Store the initial play region state
@ -2078,34 +2123,18 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
mPlayRegionLock = mProject->IsPlayRegionLocked(); mPlayRegionLock = mProject->IsPlayRegionLocked();
} }
// Keep Quick-Play within usable track area.
TrackPanel *tp = mProject->GetTrackPanel();
int mousePosX, width;
tp->GetTracksUsableArea(&width, NULL);
mousePosX = std::max(evt.GetX(), tp->GetLeftOffset());
mousePosX = std::min(mousePosX, tp->GetLeftOffset() + width - 1);
bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart); bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd); bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
bool isWithinClick = (mLeftDownClick >= 0) && IsWithinMarker(mousePosX, mLeftDownClick); bool isWithinClick = (mLeftDownClick >= 0) && IsWithinMarker(mousePosX, mLeftDownClick);
bool canDragSel = !mPlayRegionLock && mPlayRegionDragsSelection; bool canDragSel = !mPlayRegionLock && mPlayRegionDragsSelection;
double t0 = mTracks->GetStartTime(); // Handle entering and leaving of the bar, or movement from
double t1 = mTracks->GetEndTime(); // one portion (quick play or scrub) to the other
double sel0 = mProject->GetSel0(); if (evt.Leaving() || (changeInScrubZone && inScrubZone)) {
double sel1 = mProject->GetSel1(); if (evt.Leaving()) {
// Erase the line
mLastMouseX = mousePosX; HideQuickPlayIndicator();
mQuickPlayPos = Pos2Time(mousePosX); }
// If not looping, restrict selection to end of project
if (!evt.ShiftDown()) {
mQuickPlayPos = std::min(t1, mQuickPlayPos);
}
if (evt.Leaving()) {
mQuickPlayInd = false;
DrawQuickPlayIndicator(NULL);
Refresh();
SetCursor(mCursorDefault); SetCursor(mCursorDefault);
mIsWE = false; mIsWE = false;
@ -2114,19 +2143,37 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
delete mSnapManager; delete mSnapManager;
mSnapManager = NULL; mSnapManager = NULL;
} }
return;
if(evt.Leaving())
return;
// else, may detect a scrub click below
} }
else if (evt.Entering()) { else if (evt.Entering() || (changeInScrubZone && !inScrubZone)) {
SetCursor(mCursorHand); SetCursor(mCursorHand);
mQuickPlayInd = false; HideQuickPlayIndicator();
DrawQuickPlayIndicator(NULL);
return; return;
} }
if (evt.RightDown() && !(evt.LeftIsDown())) { if (evt.RightDown() && !(evt.LeftIsDown())) {
ShowMenu(evt.GetPosition()); if(inScrubZone)
ShowScrubMenu(evt.GetPosition());
else
ShowMenu(evt.GetPosition());
// dismiss and clear Quick-Play indicator
HideQuickPlayIndicator();
if (HasCapture()) if (HasCapture())
ReleaseMouse(); ReleaseMouse();
return;
}
else if (inScrubZone) {
if (evt.LeftDown())
scrubber.MarkScrubStart(evt.m_x, false, false);
UpdateStatusBar(StatusChoice::EnteringScrubZone);
wxClientDC dc(this);
DrawQuickPlayIndicator(&dc);
return;
} }
if (!mQuickPlayEnabled) if (!mQuickPlayEnabled)
@ -2277,8 +2324,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
if (evt.LeftUp()) if (evt.LeftUp())
{ {
mQuickPlayInd = false; HideQuickPlayIndicator();
DrawQuickPlayIndicator(NULL);
if (HasCapture()) if (HasCapture())
ReleaseMouse(); ReleaseMouse();
@ -2377,6 +2423,45 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
} }
} }
void AdornedRulerPanel::UpdateStatusBar(StatusChoice choice)
{
if (choice == StatusChoice::NoChange)
return;
const auto &scrubber = mProject->GetScrubber();
const bool scrubbing = scrubber.HasStartedScrubbing();
if (scrubbing && choice != StatusChoice::Leaving)
// Don't distinguish zones
choice = StatusChoice::EnteringScrubZone;
wxString message{};
switch (choice) {
case StatusChoice::EnteringQP:
{
// message = Insert timeline status bar message here
}
break;
case StatusChoice::EnteringScrubZone:
{
if (scrubbing) {
if(!scrubber.IsAlwaysSeeking())
message = _("Click or drag to seek");
}
else
message = _("Click to scrub, Double-Click to scroll, Drag to seek");
}
break;
case StatusChoice::Leaving:
default:
break;
}
// Display a message, or empty message
mProject->TP_DisplayStatusMessage(message);
}
void AdornedRulerPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(evt)) void AdornedRulerPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(evt))
{ {
DrawQuickPlayIndicator(NULL); DrawQuickPlayIndicator(NULL);
@ -2386,52 +2471,68 @@ void AdornedRulerPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(evt))
OnMouseEvents(e); OnMouseEvents(e);
} }
// Pop-up menu void AdornedRulerPanel::UpdateQuickPlayPos(wxCoord &mousePosX)
{
// Keep Quick-Play within usable track area.
TrackPanel *tp = mProject->GetTrackPanel();
int width;
tp->GetTracksUsableArea(&width, NULL);
mousePosX = std::max(mousePosX, tp->GetLeftOffset());
mousePosX = std::min(mousePosX, tp->GetLeftOffset() + width - 1);
mLastMouseX = mousePosX;
mQuickPlayPos = Pos2Time(mousePosX);
}
// Pop-up menus
void AdornedRulerPanel::ShowMenu(const wxPoint & pos) void AdornedRulerPanel::ShowMenu(const wxPoint & pos)
{ {
{ wxMenu rulerMenu;
wxMenu rulerMenu;
if (mQuickPlayEnabled) if (mQuickPlayEnabled)
rulerMenu.Append(OnToggleQuickPlayID, _("Disable Quick-Play")); rulerMenu.Append(OnToggleQuickPlayID, _("Disable Quick-Play"));
else else
rulerMenu.Append(OnToggleQuickPlayID, _("Enable Quick-Play")); rulerMenu.Append(OnToggleQuickPlayID, _("Enable Quick-Play"));
wxMenuItem *dragitem; wxMenuItem *dragitem;
if (mPlayRegionDragsSelection && !mProject->IsPlayRegionLocked()) if (mPlayRegionDragsSelection && !mProject->IsPlayRegionLocked())
dragitem = rulerMenu.Append(OnSyncQuickPlaySelID, _("Disable dragging selection")); dragitem = rulerMenu.Append(OnSyncQuickPlaySelID, _("Disable dragging selection"));
else else
dragitem = rulerMenu.Append(OnSyncQuickPlaySelID, _("Enable dragging selection")); dragitem = rulerMenu.Append(OnSyncQuickPlaySelID, _("Enable dragging selection"));
dragitem->Enable(mQuickPlayEnabled && !mProject->IsPlayRegionLocked()); dragitem->Enable(mQuickPlayEnabled && !mProject->IsPlayRegionLocked());
#if wxUSE_TOOLTIPS #if wxUSE_TOOLTIPS
if (mTimelineToolTip) if (mTimelineToolTip)
rulerMenu.Append(OnTimelineToolTipID, _("Disable Timeline Tooltips")); rulerMenu.Append(OnTimelineToolTipID, _("Disable Timeline Tooltips"));
else else
rulerMenu.Append(OnTimelineToolTipID, _("Enable Timeline Tooltips")); rulerMenu.Append(OnTimelineToolTipID, _("Enable Timeline Tooltips"));
#endif #endif
if (mViewInfo->bUpdateTrackIndicator) if (mViewInfo->bUpdateTrackIndicator)
rulerMenu.Append(OnAutoScrollID, _("Do not scroll while playing")); rulerMenu.Append(OnAutoScrollID, _("Do not scroll while playing"));
else else
rulerMenu.Append(OnAutoScrollID, _("Update display while playing")); rulerMenu.Append(OnAutoScrollID, _("Update display while playing"));
wxMenuItem *prlitem; wxMenuItem *prlitem;
if (!mProject->IsPlayRegionLocked()) if (!mProject->IsPlayRegionLocked())
prlitem = rulerMenu.Append(OnLockPlayRegionID, _("Lock Play Region")); prlitem = rulerMenu.Append(OnLockPlayRegionID, _("Lock Play Region"));
else else
prlitem = rulerMenu.Append(OnLockPlayRegionID, _("Unlock Play Region")); prlitem = rulerMenu.Append(OnLockPlayRegionID, _("Unlock Play Region"));
prlitem->Enable(mProject->IsPlayRegionLocked() || (mPlayRegionStart != mPlayRegionEnd)); prlitem->Enable(mProject->IsPlayRegionLocked() || (mPlayRegionStart != mPlayRegionEnd));
PopupMenu(&rulerMenu, pos); PopupMenu(&rulerMenu, pos);
} }
// dismiss and clear Quick-Play indicator void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
mQuickPlayInd = false; {
DrawQuickPlayIndicator(NULL); auto &scrubber = mProject->GetScrubber();
PushEventHandler(&scrubber);
auto cleanup = finally([this]{ PopEventHandler(); });
Refresh(); wxMenu rulerMenu;
mProject->GetScrubber().PopulateMenu(rulerMenu);
PopupMenu(&rulerMenu, pos);
} }
void AdornedRulerPanel::OnToggleQuickPlay(wxCommandEvent&) void AdornedRulerPanel::OnToggleQuickPlay(wxCommandEvent&)
@ -2745,7 +2846,10 @@ void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc)
} }
const int x = Time2Pos(mQuickPlayPos); const int x = Time2Pos(mQuickPlayPos);
GetOverlay()->Update(x, mIsSnapped, mPrevInScrubZone); bool previewScrub =
mPrevInScrubZone &&
!mProject->GetScrubber().IsScrubbing();
GetOverlay()->Update(x, mIsSnapped, previewScrub);
DoEraseIndicator(dc, mLastQuickPlayX); DoEraseIndicator(dc, mLastQuickPlayX);
mLastQuickPlayX = x; mLastQuickPlayX = x;

View File

@ -312,11 +312,22 @@ public:
void RegenerateTooltips(); void RegenerateTooltips();
void HideQuickPlayIndicator(); void HideQuickPlayIndicator();
void UpdateQuickPlayPos(wxCoord &mousPosX);
private: private:
void OnCapture(wxCommandEvent & evt); void OnCapture(wxCommandEvent & evt);
void OnPaint(wxPaintEvent &evt); void OnPaint(wxPaintEvent &evt);
void OnSize(wxSizeEvent &evt); void OnSize(wxSizeEvent &evt);
void OnMouseEvents(wxMouseEvent &evt); void OnMouseEvents(wxMouseEvent &evt);
enum class StatusChoice {
EnteringQP,
EnteringScrubZone,
Leaving,
NoChange
};
void UpdateStatusBar(StatusChoice choice);
void OnCaptureLost(wxMouseCaptureLostEvent &evt); void OnCaptureLost(wxMouseCaptureLostEvent &evt);
void DoDrawBorder(wxDC * dc); void DoDrawBorder(wxDC * dc);
@ -381,6 +392,7 @@ private:
// Pop-up menu // Pop-up menu
// //
void ShowMenu(const wxPoint & pos); void ShowMenu(const wxPoint & pos);
void ShowScrubMenu(const wxPoint & pos);
void DragSelection(); void DragSelection();
void HandleSnapping(); void HandleSnapping();
void OnToggleQuickPlay(wxCommandEvent &evt); void OnToggleQuickPlay(wxCommandEvent &evt);