mirror of
https://github.com/cookiengineer/audacity
synced 2025-06-23 15:50:05 +02:00
Scrub speed as overlay; scrub event handling details out of TrackPanel.cpp
This commit is contained in:
parent
1722ee9e32
commit
8b7ae748a3
@ -937,8 +937,7 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id,
|
|||||||
// attach its timer event handler later (so that its handler is invoked
|
// attach its timer event handler later (so that its handler is invoked
|
||||||
// earlier)
|
// earlier)
|
||||||
mScrubOverlay = std::make_unique<ScrubbingOverlay>(this);
|
mScrubOverlay = std::make_unique<ScrubbingOverlay>(this);
|
||||||
#else
|
mScrubber = std::make_unique<Scrubber>(this);
|
||||||
mScrubOverlay = NULL;
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// This must follow construction of *mScrubOverlay, because it must
|
// 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
|
// Add the overlays, in the sequence in which they will be painted
|
||||||
mTrackPanel->AddOverlay(mIndicatorOverlay.get());
|
mTrackPanel->AddOverlay(mIndicatorOverlay.get());
|
||||||
mTrackPanel->AddOverlay(mCursorOverlay.get());
|
mTrackPanel->AddOverlay(mCursorOverlay.get());
|
||||||
|
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
||||||
mTrackPanel->AddOverlay(mScrubOverlay.get());
|
mTrackPanel->AddOverlay(mScrubOverlay.get());
|
||||||
|
#endif
|
||||||
|
|
||||||
// LLL: When Audacity starts or becomes active after returning from
|
// LLL: When Audacity starts or becomes active after returning from
|
||||||
// another application, the first window that can accept focus
|
// another application, the first window that can accept focus
|
||||||
@ -1082,7 +1083,9 @@ AudacityProject::~AudacityProject()
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(mTrackPanel) {
|
if(mTrackPanel) {
|
||||||
|
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
||||||
mTrackPanel->RemoveOverlay(mScrubOverlay.get());
|
mTrackPanel->RemoveOverlay(mScrubOverlay.get());
|
||||||
|
#endif
|
||||||
mTrackPanel->RemoveOverlay(mCursorOverlay.get());
|
mTrackPanel->RemoveOverlay(mCursorOverlay.get());
|
||||||
mTrackPanel->RemoveOverlay(mIndicatorOverlay.get());
|
mTrackPanel->RemoveOverlay(mIndicatorOverlay.get());
|
||||||
}
|
}
|
||||||
|
@ -73,6 +73,7 @@ class DeviceToolBar;
|
|||||||
class EditToolBar;
|
class EditToolBar;
|
||||||
class MeterToolBar;
|
class MeterToolBar;
|
||||||
class MixerToolBar;
|
class MixerToolBar;
|
||||||
|
class Scrubber;
|
||||||
class SelectionBar;
|
class SelectionBar;
|
||||||
class SpectralSelectionBar;
|
class SpectralSelectionBar;
|
||||||
class Toolbar;
|
class Toolbar;
|
||||||
@ -709,7 +710,15 @@ public:
|
|||||||
|
|
||||||
// TrackPanelOverlay objects
|
// TrackPanelOverlay objects
|
||||||
std::unique_ptr<TrackPanelOverlay>
|
std::unique_ptr<TrackPanelOverlay>
|
||||||
mIndicatorOverlay, mCursorOverlay, mScrubOverlay;
|
mIndicatorOverlay, mCursorOverlay;
|
||||||
|
|
||||||
|
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
||||||
|
std::unique_ptr<TrackPanelOverlay> mScrubOverlay;
|
||||||
|
std::unique_ptr<Scrubber> mScrubber;
|
||||||
|
public:
|
||||||
|
Scrubber &GetScrubber() { return *mScrubber; }
|
||||||
|
const Scrubber &GetScrubber() const { return *mScrubber; }
|
||||||
|
#endif
|
||||||
|
|
||||||
DECLARE_EVENT_TABLE()
|
DECLARE_EVENT_TABLE()
|
||||||
};
|
};
|
||||||
|
@ -198,6 +198,9 @@ is time to refresh some aspect of the screen.
|
|||||||
#include "toolbars/ControlToolBar.h"
|
#include "toolbars/ControlToolBar.h"
|
||||||
#include "toolbars/ToolsToolBar.h"
|
#include "toolbars/ToolsToolBar.h"
|
||||||
|
|
||||||
|
// To do: eliminate this!
|
||||||
|
#include "tracks/ui/Scrubbing.h"
|
||||||
|
|
||||||
#define ZOOMLIMIT 0.001f
|
#define ZOOMLIMIT 0.001f
|
||||||
|
|
||||||
//This loads the appropriate set of cursors, depending on platform.
|
//This loads the appropriate set of cursors, depending on platform.
|
||||||
@ -268,20 +271,6 @@ enum {
|
|||||||
kBottomMargin = kShadowThickness + kBorderThickness,
|
kBottomMargin = kShadowThickness + kBorderThickness,
|
||||||
kLeftMargin = kLeftInset + kBorderThickness,
|
kLeftMargin = kLeftInset + kBorderThickness,
|
||||||
kRightMargin = kRightInset + kShadowThickness + 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?
|
// Is the distance between A and B less than D?
|
||||||
@ -583,30 +572,7 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
|
|||||||
mSelStartValid = false;
|
mSelStartValid = false;
|
||||||
mSelStart = 0;
|
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<bool>;
|
mInitialTrackSelection = new std::vector<bool>;
|
||||||
|
|
||||||
if (wxTheApp)
|
|
||||||
wxTheApp->Connect
|
|
||||||
(wxEVT_ACTIVATE_APP,
|
|
||||||
wxActivateEventHandler(TrackPanel::OnActivateOrDeactivateApp), NULL, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -614,11 +580,6 @@ TrackPanel::~TrackPanel()
|
|||||||
{
|
{
|
||||||
mTimer.Stop();
|
mTimer.Stop();
|
||||||
|
|
||||||
if (wxTheApp)
|
|
||||||
wxTheApp->Disconnect
|
|
||||||
(wxEVT_ACTIVATE_APP,
|
|
||||||
wxActivateEventHandler(TrackPanel::OnActivateOrDeactivateApp), NULL, this);
|
|
||||||
|
|
||||||
// Unregister for tracklist updates
|
// Unregister for tracklist updates
|
||||||
mTracks->Disconnect(EVT_TRACKLIST_UPDATED,
|
mTracks->Disconnect(EVT_TRACKLIST_UPDATED,
|
||||||
wxCommandEventHandler(TrackPanel::OnTrackListUpdated),
|
wxCommandEventHandler(TrackPanel::OnTrackListUpdated),
|
||||||
@ -1015,12 +976,6 @@ void TrackPanel::OnTimer(wxTimerEvent& )
|
|||||||
p->GetEventHandler()->ProcessEvent(e);
|
p->GetEventHandler()->ProcessEvent(e);
|
||||||
}
|
}
|
||||||
|
|
||||||
const double playPos = gAudioIO->GetStreamTime();
|
|
||||||
|
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
|
||||||
TimerUpdateScrubbing(playPos);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
DrawOverlays(false);
|
DrawOverlays(false);
|
||||||
|
|
||||||
if(IsAudioActive() && gAudioIO->GetNumCaptureChannels()) {
|
if(IsAudioActive() && gAudioIO->GetNumCaptureChannels()) {
|
||||||
@ -1611,7 +1566,7 @@ void TrackPanel::SetCursorAndTipWhenSelectTool( Track * t,
|
|||||||
// But don't change the cursor when scrubbing.
|
// But don't change the cursor when scrubbing.
|
||||||
SelectionBoundary boundary =
|
SelectionBoundary boundary =
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
||||||
IsScrubbing()
|
GetProject()->GetScrubber().IsScrubbing()
|
||||||
? SBNone
|
? SBNone
|
||||||
:
|
:
|
||||||
#endif
|
#endif
|
||||||
@ -1905,276 +1860,6 @@ void TrackPanel::HandleSelect(wxMouseEvent & event)
|
|||||||
SelectionHandleDrag(event, t);
|
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
|
/// This method gets called when we're handling selection
|
||||||
/// and the mouse was just clicked.
|
/// and the mouse was just clicked.
|
||||||
void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
|
void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
|
||||||
@ -2302,7 +1987,7 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
|
|||||||
event.LeftDClick() ||
|
event.LeftDClick() ||
|
||||||
#endif
|
#endif
|
||||||
event.LeftDown()) {
|
event.LeftDown()) {
|
||||||
MarkScrubStart(
|
GetProject()->GetScrubber().MarkScrubStart(
|
||||||
event.m_x
|
event.m_x
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
|
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
|
||||||
, event.LeftDClick()
|
, event.LeftDClick()
|
||||||
@ -2313,7 +1998,7 @@ void TrackPanel::SelectionHandleClick(wxMouseEvent & event,
|
|||||||
|
|
||||||
#else
|
#else
|
||||||
|
|
||||||
StartOrJumpPlayback(event);
|
// StartOrJumpPlayback(event);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -3038,11 +2723,11 @@ void TrackPanel::Stretch(int mouseXCoordinate, int trackLeftEdge,
|
|||||||
void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
|
void TrackPanel::SelectionHandleDrag(wxMouseEvent & event, Track *clickedTrack)
|
||||||
{
|
{
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
||||||
if (mScrubStartPosition >= 0) {
|
Scrubber &scrubber = GetProject()->GetScrubber();
|
||||||
MaybeStartScrubbing(event);
|
if (scrubber.IsScrubbing() ||
|
||||||
|
GetProject()->GetScrubber().MaybeStartScrubbing(event))
|
||||||
// Do nothing more, don't change selection
|
// Do nothing more, don't change selection
|
||||||
return;
|
return;
|
||||||
}
|
|
||||||
#endif
|
#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
|
||||||
@ -5885,7 +5570,7 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
|
|||||||
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
|
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
|
||||||
// Don't pan during smooth scrolling. That would conflict with keeping
|
// Don't pan during smooth scrolling. That would conflict with keeping
|
||||||
// the play indicator centered.
|
// the play indicator centered.
|
||||||
&& !mSmoothScrollingScrub
|
&& !GetProject()->GetScrubber().IsScrollScrubbing()
|
||||||
#endif
|
#endif
|
||||||
)
|
)
|
||||||
{
|
{
|
||||||
@ -5917,7 +5602,7 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
|
|||||||
wxCoord xx;
|
wxCoord xx;
|
||||||
double center_h;
|
double center_h;
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
|
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
|
||||||
if (mSmoothScrollingScrub) {
|
if (GetProject()->GetScrubber().IsScrollScrubbing()) {
|
||||||
// Expand or contract about the center, ignoring mouse position
|
// Expand or contract about the center, ignoring mouse position
|
||||||
center_h = mViewInfo->h + (GetScreenEndTime() - mViewInfo->h) / 2.0;
|
center_h = mViewInfo->h + (GetScreenEndTime() - mViewInfo->h) / 2.0;
|
||||||
xx = mViewInfo->TimeToPosition(center_h, trackLeftEdge);
|
xx = mViewInfo->TimeToPosition(center_h, trackLeftEdge);
|
||||||
@ -5953,21 +5638,8 @@ void TrackPanel::HandleWheelRotation(wxMouseEvent & event)
|
|||||||
else
|
else
|
||||||
{
|
{
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
|
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
|
||||||
if (IsScrubbing()) {
|
if (GetProject()->GetScrubber().IsScrubbing()) {
|
||||||
const int newLogMaxScrubSpeed = mLogMaxScrubSpeed + steps;
|
GetProject()->GetScrubber().HandleScrollWheel(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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
#endif
|
#endif
|
||||||
@ -6718,12 +6390,12 @@ void TrackPanel::HandleTrackSpecificMouseEvent(wxMouseEvent & event)
|
|||||||
}
|
}
|
||||||
|
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
||||||
if (IsScrubbing() &&
|
if (GetProject()->GetScrubber().IsScrubbing() &&
|
||||||
GetRect().Contains(event.GetPosition()) &&
|
GetRect().Contains(event.GetPosition()) &&
|
||||||
(!pTrack ||
|
(!pTrack ||
|
||||||
pTrack->GetKind() == Track::Wave)) {
|
pTrack->GetKind() == Track::Wave)) {
|
||||||
if (event.LeftDown()) {
|
if (event.LeftDown()) {
|
||||||
mScrubSeekPress = true;
|
GetProject()->GetScrubber().SetSeeking();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
else if (event.LeftIsDown())
|
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<wxRect, bool> 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
|
/// Draw zooming indicator that shows the region that will
|
||||||
/// be zoomed into when the user clicks and drags with a
|
/// be zoomed into when the user clicks and drags with a
|
||||||
/// zoom cursor. Handles both vertical and horizontal
|
/// zoom cursor. Handles both vertical and horizontal
|
||||||
@ -9334,18 +8808,6 @@ void TrackPanel::OnKillFocus(wxFocusEvent & WXUNUSED(event))
|
|||||||
Refresh( false);
|
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.
|
TrackInfo code is destined to move out of this file.
|
||||||
|
@ -74,6 +74,11 @@ enum class UndoPush : unsigned char;
|
|||||||
|
|
||||||
DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_TRACK_PANEL_TIMER, -1);
|
DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_TRACK_PANEL_TIMER, -1);
|
||||||
|
|
||||||
|
enum {
|
||||||
|
kTimerInterval = 50, // milliseconds
|
||||||
|
kOneSecondCountdown = 1000 / kTimerInterval,
|
||||||
|
};
|
||||||
|
|
||||||
class AUDACITY_DLL_API TrackInfo
|
class AUDACITY_DLL_API TrackInfo
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
@ -161,7 +166,6 @@ class AUDACITY_DLL_API TrackPanel final : public wxPanel {
|
|||||||
|
|
||||||
virtual void OnSetFocus(wxFocusEvent & event);
|
virtual void OnSetFocus(wxFocusEvent & event);
|
||||||
virtual void OnKillFocus(wxFocusEvent & event);
|
virtual void OnKillFocus(wxFocusEvent & event);
|
||||||
virtual void OnActivateOrDeactivateApp(wxActivateEvent & event);
|
|
||||||
|
|
||||||
virtual void OnContextMenu(wxContextMenuEvent & 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 bool IsOverCutline(WaveTrack * track, wxRect &rect, wxMouseEvent &event);
|
||||||
virtual void HandleTrackSpecificMouseEvent(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<wxRect, bool> GetScrubSpeedRectangle();
|
|
||||||
virtual void UndrawScrubSpeed(wxDC & dc);
|
|
||||||
virtual void DoDrawScrubSpeed(wxDC & dc);
|
|
||||||
#endif
|
|
||||||
|
|
||||||
virtual void ScrollDuringDrag();
|
virtual void ScrollDuringDrag();
|
||||||
|
|
||||||
// Working out where to dispatch the event to.
|
// 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 HandleSelect(wxMouseEvent & event);
|
||||||
virtual void SelectionHandleDrag(wxMouseEvent &event, Track *pTrack);
|
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:
|
protected:
|
||||||
#endif
|
|
||||||
|
|
||||||
virtual void SelectionHandleClick(wxMouseEvent &event,
|
virtual void SelectionHandleClick(wxMouseEvent &event,
|
||||||
Track* pTrack, wxRect rect);
|
Track* pTrack, wxRect rect);
|
||||||
@ -788,27 +759,6 @@ protected:
|
|||||||
int mMoveDownThreshold;
|
int mMoveDownThreshold;
|
||||||
int mRearrangeCount;
|
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<wxCursor>
|
std::unique_ptr<wxCursor>
|
||||||
mArrowCursor, mPencilCursor, mSelectCursor,
|
mArrowCursor, mPencilCursor, mSelectCursor,
|
||||||
mResizeCursor, mSlideCursor, mEnvelopeCursor, // doubles as the center frequency cursor
|
mResizeCursor, mSlideCursor, mEnvelopeCursor, // doubles as the center frequency cursor
|
||||||
|
@ -52,13 +52,15 @@
|
|||||||
#include "../AllThemeResources.h"
|
#include "../AllThemeResources.h"
|
||||||
#include "../ImageManipulation.h"
|
#include "../ImageManipulation.h"
|
||||||
#include "../Project.h"
|
#include "../Project.h"
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
|
||||||
#include "../TrackPanel.h"
|
|
||||||
#endif
|
|
||||||
#include "../Theme.h"
|
#include "../Theme.h"
|
||||||
#include "../widgets/AButton.h"
|
|
||||||
|
|
||||||
#include "../Experimental.h"
|
#include "../Experimental.h"
|
||||||
|
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
||||||
|
#include "../tracks/ui/Scrubbing.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#include "../widgets/AButton.h"
|
||||||
|
|
||||||
|
|
||||||
IMPLEMENT_CLASS(ToolsToolBar, ToolBar);
|
IMPLEMENT_CLASS(ToolsToolBar, ToolBar);
|
||||||
|
|
||||||
@ -219,13 +221,9 @@ void ToolsToolBar::SetCurrentTool(int tool, bool show)
|
|||||||
|
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
||||||
if (tool != selectTool) {
|
if (tool != selectTool) {
|
||||||
AudacityProject *p = GetActiveProject();
|
AudacityProject *const p = GetActiveProject();
|
||||||
if (p) {
|
if (p)
|
||||||
TrackPanel *tp = p->GetTrackPanel();
|
p->GetScrubber().StopScrubbing();
|
||||||
if (tp) {
|
|
||||||
tp->StopScrubbing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
@ -294,13 +292,9 @@ void ToolsToolBar::OnTool(wxCommandEvent & evt)
|
|||||||
|
|
||||||
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
#ifdef EXPERIMENTAL_SCRUBBING_BASIC
|
||||||
if (0 != mCurrentTool) {
|
if (0 != mCurrentTool) {
|
||||||
AudacityProject *p = GetActiveProject();
|
AudacityProject *const p = GetActiveProject();
|
||||||
if (p) {
|
if (p)
|
||||||
TrackPanel *tp = p->GetTrackPanel();
|
p->GetScrubber().StopScrubbing();
|
||||||
if (tp) {
|
|
||||||
tp->StopScrubbing();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -9,16 +9,385 @@ Paul Licameli split from TrackPanel.cpp
|
|||||||
**********************************************************************/
|
**********************************************************************/
|
||||||
|
|
||||||
#include "Scrubbing.h"
|
#include "Scrubbing.h"
|
||||||
|
#include "../../Experimental.h"
|
||||||
|
|
||||||
|
#include "../../AudioIO.h"
|
||||||
#include "../../Project.h"
|
#include "../../Project.h"
|
||||||
#include "../../TrackPanel.h"
|
#include "../../TrackPanel.h"
|
||||||
#include "../../TrackPanelCell.h"
|
#include "../../TrackPanelCell.h"
|
||||||
#include "../../TrackPanelCellIterator.h"
|
#include "../../TrackPanelCellIterator.h"
|
||||||
|
#include "../../toolbars/ControlToolBar.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
#include <wx/dc.h>
|
#include <wx/dc.h>
|
||||||
|
|
||||||
|
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<Scrubber&>(*this).mScrubToken = -1;
|
||||||
|
const_cast<Scrubber&>(*this).mScrubStartPosition = -1;
|
||||||
|
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
|
||||||
|
const_cast<Scrubber&>(*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)
|
ScrubbingOverlay::ScrubbingOverlay(AudacityProject *project)
|
||||||
: mProject(project)
|
: mProject(project)
|
||||||
|
, mLastScrubRect()
|
||||||
|
, mNextScrubRect()
|
||||||
|
, mLastScrubSpeedText()
|
||||||
|
, mNextScrubSpeedText()
|
||||||
{
|
{
|
||||||
mProject->Connect(EVT_TRACK_PANEL_TIMER,
|
mProject->Connect(EVT_TRACK_PANEL_TIMER,
|
||||||
wxCommandEventHandler(ScrubbingOverlay::OnTimer),
|
wxCommandEventHandler(ScrubbingOverlay::OnTimer),
|
||||||
@ -36,18 +405,130 @@ ScrubbingOverlay::~ScrubbingOverlay()
|
|||||||
|
|
||||||
std::pair<wxRect, bool> ScrubbingOverlay::DoGetRectangle(wxSize)
|
std::pair<wxRect, bool> 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
|
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)
|
void ScrubbingOverlay::OnTimer(wxCommandEvent &event)
|
||||||
{
|
{
|
||||||
// Let other listeners get the notification
|
// Let other listeners get the notification
|
||||||
event.Skip();
|
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
|
||||||
|
@ -12,12 +12,65 @@ Paul Licameli split from TrackPanel.cpp
|
|||||||
#define __AUDACITY_SCRUBBING__
|
#define __AUDACITY_SCRUBBING__
|
||||||
|
|
||||||
#include <wx/event.h>
|
#include <wx/event.h>
|
||||||
|
#include <wx/longlong.h>
|
||||||
|
|
||||||
|
#include "../../Experimental.h"
|
||||||
#include "../../TrackPanelOverlay.h"
|
#include "../../TrackPanelOverlay.h"
|
||||||
|
|
||||||
class AudacityProject;
|
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:
|
public:
|
||||||
ScrubbingOverlay(AudacityProject *project);
|
ScrubbingOverlay(AudacityProject *project);
|
||||||
@ -30,7 +83,13 @@ private:
|
|||||||
|
|
||||||
void OnTimer(wxCommandEvent &event);
|
void OnTimer(wxCommandEvent &event);
|
||||||
|
|
||||||
|
const Scrubber &GetScrubber() const;
|
||||||
|
Scrubber &GetScrubber();
|
||||||
|
|
||||||
AudacityProject *mProject;
|
AudacityProject *mProject;
|
||||||
|
|
||||||
|
wxRect mLastScrubRect, mNextScrubRect;
|
||||||
|
wxString mLastScrubSpeedText, mNextScrubSpeedText;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
Loading…
x
Reference in New Issue
Block a user