1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-07-04 22:49:07 +02:00

Toggle buttons on ruler for scrub and seek; redo scrub menu items...

Two toggle buttons for three states (the state with both down is disallowed).
Scrub, seek, or hidden scrub bar (but that still allows scrub via menu item).
Also yet another button image for transport play, while seeking.
Also change the appearance of the scrub handle in the ruler, for seeking.
This commit is contained in:
Paul Licameli 2016-06-02 16:07:20 -04:00
commit 6388ae3aa0
11 changed files with 4526 additions and 4384 deletions

View File

@ -69,6 +69,8 @@ from there. Audacity will look for a file called "Pause.png".
DEFINE_IMAGE( bmpAppendRecordDisabled, wxImage( 16, 16 ), wxT("AppendRecordDisabled")); DEFINE_IMAGE( bmpAppendRecordDisabled, wxImage( 16, 16 ), wxT("AppendRecordDisabled"));
DEFINE_IMAGE( bmpScrubDisabled, wxImage( 16, 16 ), wxT("ScrubDisabled")); DEFINE_IMAGE( bmpScrubDisabled, wxImage( 16, 16 ), wxT("ScrubDisabled"));
DEFINE_IMAGE( bmpScrub, wxImage( 16, 16 ), wxT("Scrub")); DEFINE_IMAGE( bmpScrub, wxImage( 16, 16 ), wxT("Scrub"));
DEFINE_IMAGE( bmpSeekDisabled, wxImage( 24, 16 ), wxT("SeekDisabled"));
DEFINE_IMAGE( bmpSeek, wxImage( 24, 16 ), wxT("Seek"));
SET_THEME_FLAGS( resFlagNewLine ); SET_THEME_FLAGS( resFlagNewLine );
DEFINE_IMAGE( bmpUpButtonLarge, wxImage( 48, 48 ), wxT("UpButtonLarge")); DEFINE_IMAGE( bmpUpButtonLarge, wxImage( 48, 48 ), wxT("UpButtonLarge"));

View File

@ -2384,7 +2384,6 @@ void AudacityProject::OnTogglePinnedHead()
auto ruler = GetRulerPanel(); auto ruler = GetRulerPanel();
if (ruler) if (ruler)
// Update button image // Update button image
ruler->UpdateButtonStates(); ruler->UpdateButtonStates();
auto &scrubber = GetScrubber(); auto &scrubber = GetScrubber();

View File

@ -890,7 +890,7 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id,
mRuler = safenew AdornedRulerPanel( this, mRuler = safenew AdornedRulerPanel( this,
wxID_ANY, wxID_ANY,
wxDefaultPosition, wxDefaultPosition,
wxSize( -1, AdornedRulerPanel::GetRulerHeight() ), wxSize( -1, AdornedRulerPanel::GetRulerHeight(false) ),
&mViewInfo ); &mViewInfo );
// //

File diff suppressed because it is too large Load Diff

View File

@ -168,6 +168,8 @@ void ControlToolBar::Populate()
bmpCutPreview, bmpCutPreview, bmpCutPreviewDisabled); bmpCutPreview, bmpCutPreview, bmpCutPreviewDisabled);
MakeAlternateImages(*mPlay, 3, MakeAlternateImages(*mPlay, 3,
bmpScrub, bmpScrub, bmpScrubDisabled); bmpScrub, bmpScrub, bmpScrubDisabled);
MakeAlternateImages(*mPlay, 4,
bmpSeek, bmpSeek, bmpSeekDisabled);
mPlay->FollowModifierKeys(); mPlay->FollowModifierKeys();
mStop = MakeButton( bmpStop, bmpStop, bmpStopDisabled , mStop = MakeButton( bmpStop, bmpStop, bmpStopDisabled ,

View File

@ -62,7 +62,7 @@ class ControlToolBar final : public ToolBar {
// Choice among the appearances of the play button: // Choice among the appearances of the play button:
enum class PlayAppearance { enum class PlayAppearance {
Straight, Looped, CutPreview, Scrub Straight, Looped, CutPreview, Scrub, Seek
}; };
//These allow buttons to be controlled externally: //These allow buttons to be controlled externally:

View File

@ -99,7 +99,7 @@ void PlayIndicatorOverlayBase::Draw(OverlayPanel &panel, wxDC &dc)
else if(auto ruler = dynamic_cast<AdornedRulerPanel*>(&panel)) { else if(auto ruler = dynamic_cast<AdornedRulerPanel*>(&panel)) {
wxASSERT(!mIsMaster); wxASSERT(!mIsMaster);
ruler->DoDrawIndicator(&dc, mLastIndicatorX, !rec, IndicatorMediumWidth, false); ruler->DoDrawIndicator(&dc, mLastIndicatorX, !rec, IndicatorMediumWidth, false, false);
} }
else else
wxASSERT(false); wxASSERT(false);

View File

@ -33,9 +33,6 @@ Paul Licameli split from TrackPanel.cpp
#include <wx/dc.h> #include <wx/dc.h>
// Conditional compilation switch for making scrub menu items checkable
#define CHECKABLE_SCRUB_MENU_ITEMS
enum { enum {
// PRL: // PRL:
// Mouse must move at least this far to distinguish ctrl-drag to scrub // Mouse must move at least this far to distinguish ctrl-drag to scrub
@ -221,24 +218,27 @@ namespace {
wxString status; wxString status;
void (Scrubber::*memFn)(wxCommandEvent&); void (Scrubber::*memFn)(wxCommandEvent&);
bool seek; bool seek;
bool (Scrubber::*StatusTest)() const;
const wxString &GetStatus() const { return status; } const wxString &GetStatus() const { return status; }
} menuItems[] = { } menuItems[] = {
/* i18n-hint: These commands assist the user in finding a sound by ear. ... /* i18n-hint: These commands assist the user in finding a sound by ear. ...
"Scrubbing" is variable-speed playback, ... "Scrubbing" is variable-speed playback, ...
"Seeking" is normal speed playback but with skips, ... "Seeking" is normal speed playback but with skips, ...
"Scrolling" keeps the playback position at a fixed place on screen while the waveform moves
*/ */
{ wxT("Scrub"), XO("&Scrub"), XO("Scrubbing"), { wxT("Scrub"), XO("&Scrub"), XO("Scrubbing"),
&Scrubber::OnScrub, false }, &Scrubber::OnScrub, false, &Scrubber::Scrubs },
{ wxT("Seek"), XO("See&k"), XO("Seeking"), { wxT("Seek"), XO("See&k"), XO("Seeking"),
&Scrubber::OnSeek, true }, &Scrubber::OnSeek, true, &Scrubber::Seeks },
{ wxT("StartScrubSeek"), XO("Star&t"), XO(""),
&Scrubber::OnStart, true, nullptr },
}; };
enum { nMenuItems = sizeof(menuItems) / sizeof(*menuItems) }; enum { nMenuItems = sizeof(menuItems) / sizeof(*menuItems), StartMenuItem = 2 };
// This never finds the last item:
inline const MenuItem &FindMenuItem(bool seek) inline const MenuItem &FindMenuItem(bool seek)
{ {
return *std::find_if(menuItems, menuItems + nMenuItems, return *std::find_if(menuItems, menuItems + nMenuItems,
@ -252,16 +252,13 @@ namespace {
void Scrubber::MarkScrubStart( void Scrubber::MarkScrubStart(
// Assume xx is relative to the left edge of TrackPanel! // Assume xx is relative to the left edge of TrackPanel!
wxCoord xx, bool smoothScrolling, bool alwaysSeeking wxCoord xx, bool smoothScrolling
) )
{ {
UncheckAllMenuItems();
// 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.
mSmoothScrollingScrub = smoothScrolling; mSmoothScrollingScrub = smoothScrolling;
mAlwaysSeeking = alwaysSeeking;
ControlToolBar * const ctb = mProject->GetControlToolBar(); ControlToolBar * const ctb = mProject->GetControlToolBar();
@ -272,14 +269,14 @@ void Scrubber::MarkScrubStart(
// scrubber state // scrubber state
mProject->SetAudioIOToken(0); mProject->SetAudioIOToken(0);
ctb->SetPlay(true, ControlToolBar::PlayAppearance::Scrub); ctb->SetPlay(true, mSeeking
? ControlToolBar::PlayAppearance::Seek
: ControlToolBar::PlayAppearance::Scrub);
ctb->UpdateStatusBar(mProject); ctb->UpdateStatusBar(mProject);
mScrubStartPosition = xx; mScrubStartPosition = xx;
mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis(); mOptions.startClockTimeMillis = ::wxGetLocalTimeMillis();
CheckMenuItem();
} }
#ifdef EXPERIMENTAL_SCRUBBING_SUPPORT #ifdef EXPERIMENTAL_SCRUBBING_SUPPORT
@ -354,8 +351,9 @@ bool Scrubber::MaybeStartScrubbing(wxCoord xx)
mOptions.minStutter = mOptions.minStutter =
mDragging ? 0.0 : lrint(std::max(0.0, MinStutter) * options.rate); mDragging ? 0.0 : lrint(std::max(0.0, MinStutter) * options.rate);
ControlToolBar::PlayAppearance appearance = ControlToolBar::PlayAppearance appearance = mSeeking
ControlToolBar::PlayAppearance::Scrub; ? ControlToolBar::PlayAppearance::Seek
: ControlToolBar::PlayAppearance::Scrub;
const bool cutPreview = false; const bool cutPreview = false;
const bool backwards = time1 < time0; const bool backwards = time1 < time0;
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL #ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL
@ -468,12 +466,12 @@ void Scrubber::ContinueScrubbingUI()
{ {
// Show the correct status for seeking. // Show the correct status for seeking.
bool backup = mAlwaysSeeking; bool backup = mSeeking;
mAlwaysSeeking = seek; mSeeking = seek;
const auto ctb = mProject->GetControlToolBar(); const auto ctb = mProject->GetControlToolBar();
if (ctb) if (ctb)
ctb->UpdateStatusBar(mProject); ctb->UpdateStatusBar(mProject);
mAlwaysSeeking = backup; mSeeking = backup;
} }
if (seek) if (seek)
@ -498,8 +496,6 @@ void Scrubber::StopScrubbing()
mPoller->Stop(); mPoller->Stop();
UncheckAllMenuItems();
mScrubStartPosition = -1; mScrubStartPosition = -1;
mDragging = false; mDragging = false;
@ -770,13 +766,12 @@ Scrubber &ScrubbingOverlay::GetScrubber()
bool Scrubber::PollIsSeeking() bool Scrubber::PollIsSeeking()
{ {
return mDragging || (mAlwaysSeeking || ::wxGetMouseState().LeftIsDown()); return mDragging || (mSeeking || ::wxGetMouseState().LeftIsDown());
} }
void Scrubber::DoScrub(bool seek) void Scrubber::DoScrub()
{ {
const bool wasScrubbing = IsScrubbing(); const bool wasScrubbing = IsScrubbing();
const bool match = (seek == mAlwaysSeeking);
const bool scroll = PlaybackPrefs::GetPinnedHeadPreference(); const bool scroll = PlaybackPrefs::GetPinnedHeadPreference();
if (!wasScrubbing) { if (!wasScrubbing) {
auto tp = mProject->GetTrackPanel(); auto tp = mProject->GetTrackPanel();
@ -788,33 +783,43 @@ void Scrubber::DoScrub(bool seek)
const auto offset = tp->GetLeftOffset(); const auto offset = tp->GetLeftOffset();
xx = (std::max(offset, std::min(offset + width - 1, xx))); xx = (std::max(offset, std::min(offset + width - 1, xx)));
MarkScrubStart(xx, scroll, seek); MarkScrubStart(xx, scroll);
} }
else if(!match) { }
mSmoothScrollingScrub = scroll;
mAlwaysSeeking = seek;
UncheckAllMenuItems();
CheckMenuItem();
void Scrubber::OnScrubOrSeek(bool &toToggle, bool &other)
{
toToggle = !toToggle;
if (toToggle)
other = false;
if (HasStartedScrubbing()) {
// Show the correct status. // Show the correct status.
const auto ctb = mProject->GetControlToolBar(); const auto ctb = mProject->GetControlToolBar();
ctb->UpdateStatusBar(mProject); ctb->UpdateStatusBar(mProject);
} }
else {
// This will call back to Scrubber::StopScrubbing auto ruler = mProject->GetRulerPanel();
const auto ctb = mProject->GetControlToolBar(); if (ruler)
ctb->StopPlaying(); // Update button images
} ruler->UpdateButtonStates();
CheckMenuItem();
} }
void Scrubber::OnScrub(wxCommandEvent&) void Scrubber::OnScrub(wxCommandEvent&)
{ {
DoScrub(false); OnScrubOrSeek(mScrubbing, mSeeking);
} }
void Scrubber::OnSeek(wxCommandEvent&) void Scrubber::OnSeek(wxCommandEvent&)
{ {
DoScrub(true); OnScrubOrSeek(mSeeking, mScrubbing);
}
void Scrubber::OnStart(wxCommandEvent&)
{
DoScrub();
} }
enum { CMD_ID = 8000 }; enum { CMD_ID = 8000 };
@ -828,14 +833,14 @@ BEGIN_EVENT_TABLE(Scrubber::Forwarder, wxEvtHandler)
EVT_MOUSE_EVENTS(Scrubber::Forwarder::OnMouse) EVT_MOUSE_EVENTS(Scrubber::Forwarder::OnMouse)
END_EVENT_TABLE() END_EVENT_TABLE()
static_assert(nMenuItems == 2, "wrong number of items"); static_assert(nMenuItems == 3, "wrong number of items");
const wxString &Scrubber::GetUntranslatedStateString() const const wxString &Scrubber::GetUntranslatedStateString() const
{ {
static wxString empty; static wxString empty;
if (HasStartedScrubbing()) { if (HasStartedScrubbing()) {
auto &item = FindMenuItem(mAlwaysSeeking); auto &item = FindMenuItem(mSeeking);
return item.status; return item.status;
} }
else else
@ -846,15 +851,19 @@ std::vector<wxString> Scrubber::GetAllUntranslatedStatusStrings()
{ {
using namespace std; using namespace std;
vector<wxString> results; vector<wxString> results;
transform(menuItems, menuItems + nMenuItems, back_inserter(results), for (const auto &item : menuItems) {
mem_fun_ref(&MenuItem::GetStatus)); const auto &status = item.GetStatus();
if (!status.empty())
results.push_back(status);
}
return move(results); return move(results);
} }
bool Scrubber::CanScrub() const bool Scrubber::CanScrub() const
{ {
// Return the enabled state for the menu item that really launches the scrub or seek.
auto cm = mProject->GetCommandManager(); auto cm = mProject->GetCommandManager();
return cm->GetEnabled(menuItems[0].name); return cm->GetEnabled(menuItems[StartMenuItem].name);
} }
void Scrubber::AddMenuItems() void Scrubber::AddMenuItems()
@ -865,15 +874,14 @@ void Scrubber::AddMenuItems()
cm->BeginSubMenu(_("Scru&bbing")); cm->BeginSubMenu(_("Scru&bbing"));
for (const auto &item : menuItems) { for (const auto &item : menuItems) {
#ifdef CHECKABLE_SCRUB_MENU_ITEMS if (!item.GetStatus().empty())
cm->AddCheck(item.name, wxGetTranslation(item.label), cm->AddCheck(item.name, wxGetTranslation(item.label),
FNT(Scrubber, this, item.memFn), FNT(Scrubber, this, item.memFn),
false, flags, mask); false, flags, mask);
#else else
cm->AddItem(item.name, wxGetTranslation(item.label), cm->AddItem(item.name, wxGetTranslation(item.label),
FNT(Scrubber, this, item.memFn), FNT(Scrubber, this, item.memFn),
flags, mask); flags, mask);
#endif
} }
cm->EndSubMenu(); cm->EndSubMenu();
CheckMenuItem(); CheckMenuItem();
@ -885,40 +893,23 @@ void Scrubber::PopulateMenu(wxMenu &menu)
auto cm = mProject->GetCommandManager(); auto cm = mProject->GetCommandManager();
const MenuItem *checkedItem = const MenuItem *checkedItem =
HasStartedScrubbing() HasStartedScrubbing()
? &FindMenuItem(mAlwaysSeeking) ? &FindMenuItem(mSeeking)
: nullptr; : nullptr;
for (const auto &item : menuItems) { for (const auto &item : menuItems) {
if (cm->GetEnabled(item.name)) { if (cm->GetEnabled(item.name)) {
#ifdef CHECKABLE_SCRUB_MENU_ITEMS
menu.AppendCheckItem(id, item.label); menu.AppendCheckItem(id, item.label);
if(&item == checkedItem) if(&item == checkedItem)
menu.FindItem(id)->Check(); menu.FindItem(id)->Check();
#else
menu.Append(id, item.label);
#endif
} }
++id; ++id;
} }
} }
void Scrubber::UncheckAllMenuItems()
{
#ifdef CHECKABLE_SCRUB_MENU_ITEMS
auto cm = mProject->GetCommandManager();
for (const auto &item : menuItems)
cm->Check(item.name, false);
#endif
}
void Scrubber::CheckMenuItem() void Scrubber::CheckMenuItem()
{ {
#ifdef CHECKABLE_SCRUB_MENU_ITEMS
if(HasStartedScrubbing()) {
auto cm = mProject->GetCommandManager(); auto cm = mProject->GetCommandManager();
auto item = FindMenuItem(mAlwaysSeeking); cm->Check(menuItems[0].name, mScrubbing);
cm->Check(item.name, true); cm->Check(menuItems[1].name, mSeeking);
}
#endif
} }
#endif #endif

View File

@ -71,11 +71,7 @@ public:
~Scrubber(); ~Scrubber();
// Assume xx is relative to the left edge of TrackPanel! // Assume xx is relative to the left edge of TrackPanel!
void MarkScrubStart( void MarkScrubStart(wxCoord xx, bool smoothScrolling);
wxCoord xx, bool smoothScrolling,
bool alwaysSeeking // if false, can switch seeking or scrubbing
// 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:
// Assume xx is relative to the left edge of TrackPanel! // Assume xx is relative to the left edge of TrackPanel!
@ -101,8 +97,11 @@ public:
void SetScrollScrubbing(bool value) void SetScrollScrubbing(bool value)
{ mSmoothScrollingScrub = value; } { mSmoothScrollingScrub = value; }
bool IsAlwaysSeeking() const bool Seeks() const
{ return mAlwaysSeeking; } { return mSeeking; }
bool Scrubs() const
{ return mScrubbing; }
bool ShouldDrawScrubSpeed(); bool ShouldDrawScrubSpeed();
double FindScrubSpeed(bool seeking, double time) const; double FindScrubSpeed(bool seeking, double time) const;
@ -120,10 +119,13 @@ public:
// For popup // For popup
void PopulateMenu(wxMenu &menu); void PopulateMenu(wxMenu &menu);
void OnScrubOrSeek(bool &toToggle, bool &other);
void OnScrub(wxCommandEvent&); void OnScrub(wxCommandEvent&);
void OnSeek(wxCommandEvent&); void OnSeek(wxCommandEvent&);
void OnStart(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
// when scrub or seek is in progress, or else empty.
const wxString &GetUntranslatedStateString() const; const wxString &GetUntranslatedStateString() const;
// All possible status strings. // All possible status strings.
@ -133,9 +135,8 @@ public:
bool IsPaused() const; bool IsPaused() const;
private: private:
void DoScrub(bool seek); void DoScrub();
void OnActivateOrDeactivateApp(wxActivateEvent & event); void OnActivateOrDeactivateApp(wxActivateEvent & event);
void UncheckAllMenuItems();
void CheckMenuItem(); void CheckMenuItem();
// I need this because I can't push the scrubber as an event handler // I need this because I can't push the scrubber as an event handler
@ -158,7 +159,12 @@ private:
wxCoord mLastScrubPosition {}; wxCoord mLastScrubPosition {};
bool mScrubSeekPress; bool mScrubSeekPress;
bool mSmoothScrollingScrub; bool mSmoothScrollingScrub;
bool mAlwaysSeeking {};
// These hold the three-way choice among click-to-scrub, click-to-seek, or disabled.
// Not both true.
bool mScrubbing {};
bool mSeeking {};
bool mDragging {}; bool mDragging {};
#ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL #ifdef EXPERIMENTAL_SCRUBBING_SCROLL_WHEEL

View File

@ -1791,7 +1791,7 @@ std::pair<wxRect, bool> QuickPlayRulerOverlay::DoGetRectangle(wxSize size)
if (x >= 0) { if (x >= 0) {
// These dimensions are always sufficient, even if a little // These dimensions are always sufficient, even if a little
// excessive for the small triangle: // excessive for the small triangle:
const int width = IndicatorBigWidth(); const int width = IndicatorBigWidth() * 3 / 2;
const auto height = IndicatorHeightForWidth(width); const auto height = IndicatorHeightForWidth(width);
const int indsize = width / 2; const int indsize = width / 2;
@ -1814,12 +1814,14 @@ void QuickPlayRulerOverlay::Draw(OverlayPanel &panel, wxDC &dc)
mOldQPIndicatorPos = mNewQPIndicatorPos; mOldQPIndicatorPos = mNewQPIndicatorPos;
if (mOldQPIndicatorPos >= 0) { if (mOldQPIndicatorPos >= 0) {
auto ruler = GetRuler(); auto ruler = GetRuler();
const auto &scrubber = mPartner.mProject->GetScrubber();
auto scrub = auto scrub =
ruler->mMouseEventState == AdornedRulerPanel::mesNone && ruler->mMouseEventState == AdornedRulerPanel::mesNone &&
(ruler->mPrevZone == AdornedRulerPanel::StatusChoice::EnteringScrubZone || (ruler->mPrevZone == AdornedRulerPanel::StatusChoice::EnteringScrubZone ||
(mPartner.mProject->GetScrubber().HasStartedScrubbing())); (scrubber.HasStartedScrubbing()));
auto seek = scrub && scrubber.Seeks();
auto width = scrub ? IndicatorBigWidth() : IndicatorSmallWidth; auto width = scrub ? IndicatorBigWidth() : IndicatorSmallWidth;
ruler->DoDrawIndicator(&dc, mOldQPIndicatorPos, true, width, scrub); ruler->DoDrawIndicator(&dc, mOldQPIndicatorPos, true, width, scrub, seek);
} }
} }
@ -1918,8 +1920,8 @@ enum {
OnLockPlayRegionID, OnLockPlayRegionID,
OnTogglePinnedStateID, OnTogglePinnedStateID,
OnScrubID,
OnShowHideScrubbingID, OnSeekID,
}; };
BEGIN_EVENT_TABLE(AdornedRulerPanel, OverlayPanel) BEGIN_EVENT_TABLE(AdornedRulerPanel, OverlayPanel)
@ -1935,15 +1937,18 @@ BEGIN_EVENT_TABLE(AdornedRulerPanel, OverlayPanel)
EVT_MENU(OnAutoScrollID, AdornedRulerPanel::OnAutoScroll) EVT_MENU(OnAutoScrollID, AdornedRulerPanel::OnAutoScroll)
EVT_MENU(OnLockPlayRegionID, AdornedRulerPanel::OnLockPlayRegion) EVT_MENU(OnLockPlayRegionID, AdornedRulerPanel::OnLockPlayRegion)
// Scrub bar menu commands
EVT_MENU(OnShowHideScrubbingID, AdornedRulerPanel::OnToggleScrubbing)
// Pop up menus on Windows // Pop up menus on Windows
EVT_CONTEXT_MENU(AdornedRulerPanel::OnContextMenu) EVT_CONTEXT_MENU(AdornedRulerPanel::OnContextMenu)
EVT_COMMAND( OnTogglePinnedStateID, EVT_COMMAND( OnTogglePinnedStateID,
wxEVT_COMMAND_BUTTON_CLICKED, wxEVT_COMMAND_BUTTON_CLICKED,
AdornedRulerPanel::OnTogglePinnedState ) AdornedRulerPanel::OnTogglePinnedState )
EVT_COMMAND( OnScrubID,
wxEVT_COMMAND_BUTTON_CLICKED,
AdornedRulerPanel::OnScrub )
EVT_COMMAND( OnSeekID,
wxEVT_COMMAND_BUTTON_CLICKED,
AdornedRulerPanel::OnSeek )
END_EVENT_TABLE() END_EVENT_TABLE()
@ -2026,6 +2031,7 @@ AdornedRulerPanel::~AdornedRulerPanel()
this); this);
} }
#if 0
namespace { namespace {
static const wxChar *scrubEnabledPrefName = wxT("/QuickPlay/ScrubbingEnabled"); static const wxChar *scrubEnabledPrefName = wxT("/QuickPlay/ScrubbingEnabled");
@ -2041,6 +2047,7 @@ namespace {
gPrefs->Write(scrubEnabledPrefName, value); gPrefs->Write(scrubEnabledPrefName, value);
} }
} }
#endif
void AdornedRulerPanel::UpdatePrefs() void AdornedRulerPanel::UpdatePrefs()
{ {
@ -2058,7 +2065,7 @@ void AdornedRulerPanel::UpdatePrefs()
#endif #endif
#endif #endif
mShowScrubbing = ReadScrubEnabledPref(); // mShowScrubbing = ReadScrubEnabledPref();
// Affected by the last // Affected by the last
UpdateRects(); UpdateRects();
@ -2088,9 +2095,10 @@ void AdornedRulerPanel::ReCreateButtons()
// Make the short row of time ruler pushbottons. // Make the short row of time ruler pushbottons.
// Don't bother with sizers. Their sizes and positions are fixed. // Don't bother with sizers. Their sizes and positions are fixed.
wxPoint position{ FocusBorderLeft, FocusBorderTop }; wxPoint position{ FocusBorderLeft, 0 };
size_t iButton = 0; size_t iButton = 0;
const auto size = theTheme.ImageSize( bmpRecoloredUpSmall ); auto size = theTheme.ImageSize( bmpRecoloredUpSmall );
size.y = std::min(size.y, GetRulerHeight(false));
auto buttonMaker = [&] auto buttonMaker = [&]
(wxWindowID id, teBmps bitmap, bool toggle) (wxWindowID id, teBmps bitmap, bool toggle)
@ -2113,6 +2121,8 @@ void AdornedRulerPanel::ReCreateButtons()
bmpRecoloredUpSmall, bmpRecoloredDownSmall, bmpRecoloredHiliteSmall, bmpRecoloredUpSmall, bmpRecoloredDownSmall, bmpRecoloredHiliteSmall,
bmpUnpinnedPlayRecordHead, bmpUnpinnedPlayRecordHead, bmpUnpinnedPlayRecordHead, bmpUnpinnedPlayRecordHead, bmpUnpinnedPlayRecordHead, bmpUnpinnedPlayRecordHead,
size); size);
buttonMaker(OnScrubID, bmpScrub, true);
buttonMaker(OnSeekID, bmpSeek, true);
UpdateButtonStates(); UpdateButtonStates();
} }
@ -2421,7 +2431,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
} }
else if (!HasCapture() && inScrubZone) { else if (!HasCapture() && inScrubZone) {
if (evt.LeftDown()) { if (evt.LeftDown()) {
scrubber.MarkScrubStart(evt.m_x, PlaybackPrefs::GetPinnedHeadPreference(), false); scrubber.MarkScrubStart(evt.m_x, PlaybackPrefs::GetPinnedHeadPreference());
UpdateStatusBarAndTooltips(StatusChoice::EnteringScrubZone); UpdateStatusBarAndTooltips(StatusChoice::EnteringScrubZone);
} }
ShowQuickPlayIndicator(); ShowQuickPlayIndicator();
@ -2730,12 +2740,10 @@ void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice)
case StatusChoice::EnteringScrubZone: case StatusChoice::EnteringScrubZone:
{ {
if (scrubbing) { if(scrubber.Seeks())
if(!scrubber.IsAlwaysSeeking())
message = _("Click or drag to seek"); message = _("Click or drag to seek");
} else if(scrubber.Scrubs())
else message = _("Click or drag to scrub");
message = _("Click to scrub, Double-Click to scroll, Drag to seek");
} }
break; break;
@ -2749,10 +2757,10 @@ void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice)
RegenerateTooltips(choice); RegenerateTooltips(choice);
} }
void AdornedRulerPanel::OnToggleScrubbing(wxCommandEvent&) void AdornedRulerPanel::OnToggleScrubbing(/*wxCommandEvent&*/)
{ {
mShowScrubbing = !mShowScrubbing; mShowScrubbing = !mShowScrubbing;
WriteScrubEnabledPref(mShowScrubbing); //WriteScrubEnabledPref(mShowScrubbing);
gPrefs->Flush(); gPrefs->Flush();
wxSize size { GetSize().GetWidth(), GetRulerHeight(mShowScrubbing) }; wxSize size { GetSize().GetWidth(), GetRulerHeight(mShowScrubbing) };
SetSize(size); SetSize(size);
@ -2767,6 +2775,13 @@ void AdornedRulerPanel::OnContextMenu(wxContextMenuEvent & WXUNUSED(event))
void AdornedRulerPanel::UpdateButtonStates() void AdornedRulerPanel::UpdateButtonStates()
{ {
auto common = [this]
(wxWindow *button, const wxString &commandName, const wxString &label){
const auto &fullLabel = ComposeButtonLabel(*mProject, commandName, label);
button->SetLabel(fullLabel);
button->SetToolTip(fullLabel);
};
{ {
bool state = PlaybackPrefs::GetPinnedHeadPreference(); bool state = PlaybackPrefs::GetPinnedHeadPreference();
auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID)); auto pinButton = static_cast<AButton*>(FindWindow(OnTogglePinnedStateID));
@ -2777,10 +2792,38 @@ void AdornedRulerPanel::UpdateButtonStates()
// (which is, to toggle the state) // (which is, to toggle the state)
? _("Pinned play/record Head") ? _("Pinned play/record Head")
: _("Unpinned play/record Head"); : _("Unpinned play/record Head");
const auto &fullLabel = ComposeButtonLabel(*mProject, wxT("PinnedHead"), label); common(pinButton, wxT("PinnedHead"), label);
pinButton->SetLabel(fullLabel);
pinButton->SetToolTip(fullLabel);
} }
const auto scrubber = &mProject->GetScrubber();
{
const auto scrubButton = static_cast<AButton*>(FindWindow(OnScrubID));
/* i18n-hint: These commands assist the user in finding a sound by ear. ...
"Scrubbing" is variable-speed playback
*/
common(scrubButton, wxT("Scrub"), _("Scrub"));
if (scrubber && scrubber->Scrubs())
scrubButton->PushDown();
else
scrubButton->PopUp();
}
{
const auto seekButton = static_cast<AButton*>(FindWindow(OnSeekID));
/* i18n-hint: These commands assist the user in finding a sound by ear. ...
"Seeking" is normal speed playback but with skips
*/
common(seekButton, wxT("Seek"), _("Seek"));
if (scrubber && scrubber->Seeks())
seekButton->PushDown();
else
seekButton->PopUp();
}
if(scrubber &&
mShowScrubbing != (scrubber->Scrubs() || scrubber->Seeks()))
OnToggleScrubbing();
} }
void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & event) void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & event)
@ -2789,6 +2832,16 @@ void AdornedRulerPanel::OnTogglePinnedState(wxCommandEvent & event)
UpdateButtonStates(); UpdateButtonStates();
} }
void AdornedRulerPanel::OnSeek(wxCommandEvent & event)
{
mProject->GetScrubber().OnSeek(event);
}
void AdornedRulerPanel::OnScrub(wxCommandEvent & event)
{
mProject->GetScrubber().OnScrub(event);
}
void AdornedRulerPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(evt)) void AdornedRulerPanel::OnCaptureLost(wxMouseCaptureLostEvent & WXUNUSED(evt))
{ {
HideQuickPlayIndicator(); HideQuickPlayIndicator();
@ -2858,12 +2911,6 @@ void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
auto cleanup = finally([this]{ PopEventHandler(); }); auto cleanup = finally([this]{ PopEventHandler(); });
wxMenu rulerMenu; wxMenu rulerMenu;
rulerMenu.AppendCheckItem(OnShowHideScrubbingID, _("Scrub Bar"));
if(mShowScrubbing)
rulerMenu.FindItem(OnShowHideScrubbingID)->Check();
rulerMenu.AppendSeparator();
mProject->GetScrubber().PopulateMenu(rulerMenu); mProject->GetScrubber().PopulateMenu(rulerMenu);
PopupMenu(&rulerMenu, pos); PopupMenu(&rulerMenu, pos);
} }
@ -3104,11 +3151,6 @@ void AdornedRulerPanel::DoDrawSelection(wxDC * dc)
dc->DrawRectangle( r ); dc->DrawRectangle( r );
} }
int AdornedRulerPanel::GetRulerHeight()
{
return GetRulerHeight(ReadScrubEnabledPref());
}
int AdornedRulerPanel::GetRulerHeight(bool showScrubBar) int AdornedRulerPanel::GetRulerHeight(bool showScrubBar)
{ {
return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0); return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
@ -3122,14 +3164,46 @@ void AdornedRulerPanel::SetLeftOffset(int offset)
// Draws the play/recording position indicator. // Draws the play/recording position indicator.
void AdornedRulerPanel::DoDrawIndicator void AdornedRulerPanel::DoDrawIndicator
(wxDC * dc, wxCoord xx, bool playing, int width, bool scrub) (wxDC * dc, wxCoord xx, bool playing, int width, bool scrub, bool seek)
{ {
ADCChanger changer(dc); // Undo pen and brush changes at function exit ADCChanger changer(dc); // Undo pen and brush changes at function exit
AColor::IndicatorColor( dc, playing ); AColor::IndicatorColor( dc, playing );
wxPoint tri[ 3 ]; wxPoint tri[ 3 ];
if (scrub) { if (seek) {
auto height = IndicatorHeightForWidth(width);
// Make four triangles
const int TriangleWidth = width * 3 / 8;
// Double-double headed, left-right
auto yy = mShowScrubbing
? mScrubZone.y
: (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
tri[ 0 ].x = xx - IndicatorOffset;
tri[ 0 ].y = yy;
tri[ 1 ].x = xx - IndicatorOffset;
tri[ 1 ].y = yy + height;
tri[ 2 ].x = xx - TriangleWidth;
tri[ 2 ].y = yy + height / 2;
dc->DrawPolygon( 3, tri );
tri[ 0 ].x -= TriangleWidth;
tri[ 1 ].x -= TriangleWidth;
tri[ 2 ].x -= TriangleWidth;
dc->DrawPolygon( 3, tri );
tri[ 0 ].x = tri[ 1 ].x = xx + IndicatorOffset;
tri[ 2 ].x = xx + TriangleWidth;
dc->DrawPolygon( 3, tri );
tri[ 0 ].x += TriangleWidth;
tri[ 1 ].x += TriangleWidth;
tri[ 2 ].x += TriangleWidth;
dc->DrawPolygon( 3, tri );
}
else if (scrub) {
auto height = IndicatorHeightForWidth(width); auto height = IndicatorHeightForWidth(width);
const int IndicatorHalfWidth = width / 2; const int IndicatorHalfWidth = width / 2;

View File

@ -298,7 +298,7 @@ public:
#endif #endif
public: public:
static int GetRulerHeight(); int GetRulerHeight() { return GetRulerHeight(mShowScrubbing); }
static int GetRulerHeight(bool showScrubBar); static int GetRulerHeight(bool showScrubBar);
wxRect GetInnerRect() const { return mInner; } wxRect GetInnerRect() const { return mInner; }
@ -351,7 +351,7 @@ private:
void DoDrawSelection(wxDC * dc); void DoDrawSelection(wxDC * dc);
public: public:
void DoDrawIndicator(wxDC * dc, wxCoord xx, bool playing, int width, bool scrub); void DoDrawIndicator(wxDC * dc, wxCoord xx, bool playing, int width, bool scrub, bool seek);
void UpdateButtonStates(); void UpdateButtonStates();
private: private:
@ -413,7 +413,9 @@ private:
void OnAutoScroll(wxCommandEvent &evt); void OnAutoScroll(wxCommandEvent &evt);
void OnLockPlayRegion(wxCommandEvent &evt); void OnLockPlayRegion(wxCommandEvent &evt);
void OnToggleScrubbing(wxCommandEvent&); void OnToggleScrubbing(/*wxCommandEvent&*/);
void OnScrub(wxCommandEvent&);
void OnSeek(wxCommandEvent&);
void OnContextMenu(wxContextMenuEvent & WXUNUSED(event)); void OnContextMenu(wxContextMenuEvent & WXUNUSED(event));
@ -446,7 +448,7 @@ private:
friend QuickPlayRulerOverlay; friend QuickPlayRulerOverlay;
wxWindow *mButtons[1]; wxWindow *mButtons[3];
bool mNeedButtonUpdate { true }; bool mNeedButtonUpdate { true };
}; };