1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-07 07:39:29 +02:00

Implement pushbuttons for quick play and scrub bar, and fix a few bugs

This commit is contained in:
Paul Licameli 2016-04-30 22:55:19 -04:00
commit 911685fd83
6 changed files with 266 additions and 116 deletions

View File

@ -183,7 +183,7 @@ void AColor::DrawFocus(wxDC & dc, wxRect & rect)
dc.SetLogicalFunction(wxCOPY);
}
void AColor::Bevel(wxDC & dc, bool up, wxRect & r)
void AColor::Bevel(wxDC & dc, bool up, const wxRect & r)
{
if (up)
AColor::Light(&dc, false);
@ -211,7 +211,7 @@ wxColour AColor::Blend( const wxColour & c1, const wxColour & c2 )
return c3;
}
void AColor::BevelTrackInfo(wxDC & dc, bool up, wxRect & r)
void AColor::BevelTrackInfo(wxDC & dc, bool up, const wxRect & r)
{
#ifndef EXPERIMENTAL_THEMING
Bevel( dc, up, r );

View File

@ -38,8 +38,8 @@ class AColor {
static void Arrow(wxDC & dc, wxCoord x, wxCoord y, int width, bool down = true);
static void Line(wxDC & dc, wxCoord x1, wxCoord y1, wxCoord x2, wxCoord y2);
static void DrawFocus(wxDC & dc, wxRect & r);
static void Bevel(wxDC & dc, bool up, wxRect & r);
static void BevelTrackInfo(wxDC & dc, bool up, wxRect & r);
static void Bevel(wxDC & dc, bool up, const wxRect & r);
static void BevelTrackInfo(wxDC & dc, bool up, const wxRect & r);
static wxColour Blend(const wxColour & c1, const wxColour & c2);
static void UseThemeColour( wxDC * dc, int iIndex );

View File

@ -926,6 +926,7 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id,
&mViewInfo,
this,
mRuler);
mTrackPanel->UpdatePrefs();
mIndicatorOverlay = std::make_unique<PlayIndicatorOverlay>(this);
@ -1024,7 +1025,6 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id,
InitialState();
FixScrollbars();
mRuler->SetLeftOffset(mTrackPanel->GetLeftOffset()); // bevel on AdornedRuler
mRuler->SetProject(this);
//
// Set the Icon

View File

@ -460,7 +460,6 @@ TrackPanel::TrackPanel(wxWindow * parent, wxWindowID id,
mLabelTrackStartXPos=-1;
mCircularTrackNavigation = false;
UpdatePrefs();
mRedrawAfterStop = false;

View File

@ -1774,9 +1774,6 @@ BEGIN_EVENT_TABLE(AdornedRulerPanel, wxPanel)
EVT_MENU(OnAutoScrollID, AdornedRulerPanel::OnAutoScroll)
EVT_MENU(OnLockPlayRegionID, AdornedRulerPanel::OnLockPlayRegion)
// Main menu commands
EVT_MENU(OnShowHideScrubbingID, AdornedRulerPanel::OnShowHideScrubbing)
END_EVENT_TABLE()
AdornedRulerPanel::AdornedRulerPanel(AudacityProject* parent,
@ -1785,6 +1782,7 @@ AdornedRulerPanel::AdornedRulerPanel(AudacityProject* parent,
const wxSize& size,
ViewInfo *viewinfo)
: wxPanel(parent, id, pos, size)
, mProject(parent)
, mViewInfo(viewinfo)
{
SetLabel( _("Timeline") );
@ -1829,6 +1827,9 @@ AdornedRulerPanel::AdornedRulerPanel(AudacityProject* parent,
mPlayRegionDragsSelection = (gPrefs->Read(wxT("/QuickPlay/DragSelection"), 0L) == 1)? true : false;
mQuickPlayEnabled = !!gPrefs->Read(wxT("/QuickPlay/QuickPlayEnabled"), 1L);
int fontSize = 10;
mButtonFont.Create(fontSize, wxFONTFAMILY_SWISS, wxFONTSTYLE_NORMAL, wxFONTWEIGHT_NORMAL);
UpdatePrefs();
#if wxUSE_TOOLTIPS
@ -1979,7 +1980,7 @@ int AdornedRulerPanel::IndicatorBigWidth()
enum {
ScrubHeight = 14,
RulerHeight = 28
ProperRulerHeight = 28
};
int AdornedRulerPanel::IndicatorBigHeight()
@ -2026,6 +2027,8 @@ void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
DoDrawPlayRegion(&mBackDC);
DoDrawPushbuttons(&mBackDC);
dc.Blit(0, 0, mBack->GetWidth(), mBack->GetHeight(), &mBackDC, 0, 0);
if (mQuickPlayInd)
@ -2115,14 +2118,20 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
return;
}
const bool inScrubZone =
const bool overButtons = GetButtonAreaRect().Contains(evt.GetPosition());
const bool inScrubZone = !overButtons &&
// only if scrubbing is allowed now
mProject->GetScrubber().CanScrub() &&
mShowScrubbing &&
mScrubZone.Contains(evt.GetPosition());
const bool changeInScrubZone = (inScrubZone != mPrevInScrubZone);
mPrevInScrubZone = inScrubZone;
const StatusChoice zone =
overButtons
? StatusChoice::EnteringPushbuttons
: inScrubZone
? StatusChoice::EnteringScrubZone
: StatusChoice::EnteringQP;
const bool changeInZone = (zone != mPrevZone);
mPrevZone = zone;
wxCoord xx = evt.GetX();
wxCoord mousePosX = xx;
@ -2130,33 +2139,20 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
HandleSnapping();
// If not looping, restrict selection to end of project
if (!inScrubZone && !evt.ShiftDown()) {
if (zone == StatusChoice::EnteringQP && !evt.ShiftDown()) {
const double t1 = mTracks->GetEndTime();
mQuickPlayPos = std::min(t1, mQuickPlayPos);
}
// If position was adjusted right, we are over menu
const bool overMenu = (xx < mousePosX);
auto &scrubber = mProject->GetScrubber();
// Handle status bar messages
UpdateStatusBar (
overMenu || evt.Leaving()
evt.Leaving()
? StatusChoice::Leaving
: evt.Entering() || changeInScrubZone
? inScrubZone
? StatusChoice::EnteringScrubZone
: StatusChoice::EnteringQP
: evt.Entering() || changeInZone
? zone
: StatusChoice::NoChange
);
if (overMenu && evt.Button(wxMOUSE_BTN_ANY)) {
if(evt.ButtonDown())
DoMainMenu();
return;
}
// Handle popup menus
if (evt.RightDown() && !(evt.LeftIsDown())) {
if(inScrubZone)
@ -2172,6 +2168,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
return;
}
auto &scrubber = mProject->GetScrubber();
if (scrubber.HasStartedScrubbing()) {
// If already clicked for scrub, preempt the usual event handling,
// no matter what the y coordinate.
@ -2191,6 +2188,9 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
wxClientDC dc(this);
DrawQuickPlayIndicator(&dc);
if (HasCapture())
ReleaseMouse();
return;
}
@ -2203,7 +2203,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
// Handle entering and leaving of the bar, or movement from
// one portion (quick play or scrub) to the other
if (evt.Leaving() || (changeInScrubZone && inScrubZone)) {
if (evt.Leaving() || (changeInZone && zone != StatusChoice::EnteringQP)) {
if (evt.Leaving()) {
// Erase the line
HideQuickPlayIndicator();
@ -2221,52 +2221,67 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
return;
// else, may detect a scrub click below
}
else if (evt.Entering() || (changeInScrubZone && !inScrubZone)) {
else if (evt.Entering() || (changeInZone && zone == StatusChoice::EnteringQP)) {
SetCursor(mCursorHand);
HideQuickPlayIndicator();
return;
}
if (inScrubZone) {
if (evt.LeftDown())
if (HasCapture() && mCaptureState != Button::NoButton)
HandlePushbuttonEvent(evt);
else if (!HasCapture() && overButtons) {
if (evt.LeftDown()) {
auto position = evt.GetPosition();
for (unsigned ii = 0; ii < static_cast<unsigned>(Button::NumButtons); ++ii) {
auto button = static_cast<Button>(ii);
if(GetButtonRect(button).Contains(position)) {
CaptureMouse();
mCaptureState = button;
Refresh();
break;
}
}
}
}
else if (!HasCapture() && inScrubZone) {
if (evt.LeftDown()) {
scrubber.MarkScrubStart(evt.m_x, false, false);
UpdateStatusBar(StatusChoice::EnteringScrubZone);
UpdateStatusBar(StatusChoice::EnteringScrubZone);
}
wxClientDC dc(this);
DrawQuickPlayIndicator(&dc);
return;
}
else if ( mQuickPlayEnabled) {
bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
if (!mQuickPlayEnabled)
return;
bool isWithinStart = IsWithinMarker(mousePosX, mOldPlayRegionStart);
bool isWithinEnd = IsWithinMarker(mousePosX, mOldPlayRegionEnd);
if (isWithinStart || isWithinEnd) {
if (!mIsWE) {
SetCursor(mCursorSizeWE);
mIsWE = true;
if (isWithinStart || isWithinEnd) {
if (!mIsWE) {
SetCursor(mCursorSizeWE);
mIsWE = true;
}
}
}
else {
if (mIsWE) {
SetCursor(mCursorHand);
mIsWE = false;
else {
if (mIsWE) {
SetCursor(mCursorHand);
mIsWE = false;
}
}
}
if (evt.LeftDown()) {
HandleQPClick(evt, mousePosX);
HandleQPDrag(evt, mousePosX);
}
else if (evt.LeftIsDown())
HandleQPDrag(evt, mousePosX);
else if (evt.LeftUp())
HandleQPRelease(evt);
if (evt.LeftDown()) {
HandleQPClick(evt, mousePosX);
HandleQPDrag(evt, mousePosX);
}
else if (evt.LeftIsDown())
HandleQPDrag(evt, mousePosX);
else if (evt.LeftUp())
HandleQPRelease(evt);
mQuickPlayInd = true;
wxClientDC dc(this);
DrawQuickPlayIndicator(&dc);
mQuickPlayInd = true;
wxClientDC dc(this);
DrawQuickPlayIndicator(&dc);
}
}
void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
@ -2407,6 +2422,8 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
if (HasCapture())
ReleaseMouse();
mCaptureState = Button::NoButton;
if (mPlayRegionEnd < mPlayRegionStart) {
// Swap values to ensure mPlayRegionStart < mPlayRegionEnd
double tmp = mPlayRegionStart;
@ -2510,56 +2527,45 @@ void AdornedRulerPanel::UpdateStatusBar(StatusChoice choice)
if (choice == StatusChoice::NoChange)
return;
const auto &scrubber = mProject->GetScrubber();
const bool scrubbing = scrubber.HasStartedScrubbing();
if (scrubbing && choice != StatusChoice::Leaving)
// Don't distinguish zones
choice = StatusChoice::EnteringScrubZone;
wxString message{};
wxString message {};
switch (choice) {
case StatusChoice::EnteringQP:
{
// message = Insert timeline status bar message here
}
break;
if (choice == StatusChoice::EnteringPushbuttons)
;
else {
const auto &scrubber = mProject->GetScrubber();
const bool scrubbing = scrubber.HasStartedScrubbing();
if (scrubbing && choice != StatusChoice::Leaving)
// Don't distinguish zones
choice = StatusChoice::EnteringScrubZone;
case StatusChoice::EnteringScrubZone:
{
if (scrubbing) {
if(!scrubber.IsAlwaysSeeking())
message = _("Click or drag to seek");
switch (choice) {
case StatusChoice::EnteringQP:
{
// message = Insert timeline status bar message here
}
else
message = _("Click to scrub, Double-Click to scroll, Drag to seek");
}
break;
break;
case StatusChoice::Leaving:
default:
break;
case StatusChoice::EnteringScrubZone:
{
if (scrubbing) {
if(!scrubber.IsAlwaysSeeking())
message = _("Click or drag to seek");
}
else
message = _("Click to scrub, Double-Click to scroll, Drag to seek");
}
break;
default:
break;
}
}
// Display a message, or empty message
mProject->TP_DisplayStatusMessage(message);
}
void AdornedRulerPanel::DoMainMenu()
{
wxMenu menu;
menu.AppendCheckItem(OnShowHideScrubbingID, _("Scrub Bar"));
menu.Check(OnShowHideScrubbingID, mShowScrubbing);
// Position the popup similarly to the track control panel menus
wxPoint pos {
kLeftInset + kTrackInfoBtnSize + 1,
GetSize().GetHeight() + 1
};
PopupMenu(&menu, pos);
}
void AdornedRulerPanel::OnShowHideScrubbing(wxCommandEvent&)
void AdornedRulerPanel::OnToggleScrubbing()
{
mShowScrubbing = !mShowScrubbing;
WriteScrubEnabledPref(mShowScrubbing);
@ -2770,6 +2776,132 @@ void AdornedRulerPanel::DoDrawPlayRegion(wxDC * dc)
}
}
wxRect AdornedRulerPanel::GetButtonAreaRect() const
{
auto x = LeftMargin, y = TopMargin;
wxRect rect {
x, y,
mProject->GetTrackPanel()->GetLeftOffset() - x,
GetRulerHeight() - y - BottomMargin
};
// Leave room for one digit on the ruler, so "0.0" is not obscured if you go to start.
// But the digit string at the left end may be longer if you are not at the start.
// Perhaps there should be room for more than one digit.
wxScreenDC dc;
dc.SetFont(*mRuler.GetFonts().major);
rect.width -= dc.GetTextExtent(wxT("0")).GetWidth();
return rect;
}
wxRect AdornedRulerPanel::GetButtonRect( Button button ) const
{
wxRect rect { GetButtonAreaRect() };
// Reduce the height
rect.height -= (GetRulerHeight() - ProperRulerHeight);
auto num = static_cast<unsigned>(button);
auto denom = static_cast<unsigned>(Button::NumButtons);
rect.x += (num * rect.width) / denom;
rect.width = (((1 + num) * rect.width) / denom) - rect.x;
return rect;
}
bool AdornedRulerPanel::InButtonRect( Button button ) const
{
return GetButtonRect(button).Contains(ScreenToClient(::wxGetMousePosition()));
}
bool AdornedRulerPanel::GetButtonState( Button button ) const
{
switch(button) {
case Button::QuickPlay:
return mQuickPlayEnabled;
case Button::ScrubBar:
return mShowScrubbing;
default:
wxASSERT(false);
return false;
}
}
void AdornedRulerPanel::ToggleButtonState( Button button )
{
switch(button) {
case Button::QuickPlay: {
wxCommandEvent dummy;
OnToggleQuickPlay(dummy);
}
break;
case Button::ScrubBar:
OnToggleScrubbing();
break;
default:
wxASSERT(false);
}
}
void AdornedRulerPanel::DoDrawPushbutton(wxDC *dc, Button button, bool down) const
{
// Adapted from TrackInfo::DrawMuteSolo()
static const wxString labels[static_cast<size_t>(Button::NumButtons)] {
XO("Quick Play"),
/* i18n-hint: A long screen area (bar) controlling variable speed play (scrubbing) */
XO("Scrub Bar"),
};
const auto bev = GetButtonRect( button ).Inflate(-1, -1);
if (down)
AColor::Solo(dc, true, false);
else
AColor::MediumTrackInfo(dc, false);
dc->SetPen( *wxTRANSPARENT_PEN );//No border!
dc->DrawRectangle(bev);
dc->SetTextForeground(theTheme.Colour(clrTrackPanelText));
wxCoord textWidth, textHeight;
wxString str = wxGetTranslation(labels[static_cast<unsigned>(button)]);
dc->SetFont(mButtonFont);
dc->GetTextExtent(str, &textWidth, &textHeight);
dc->DrawText(str, bev.x + (bev.width - textWidth) / 2,
bev.y + (bev.height - textHeight) / 2);
AColor::BevelTrackInfo(*dc, !down, bev);
}
void AdornedRulerPanel::HandlePushbuttonEvent(wxMouseEvent &evt)
{
if(evt.LeftUp()) {
if(HasCapture())
ReleaseMouse();
if(InButtonRect(mCaptureState))
ToggleButtonState(mCaptureState);
mCaptureState = Button::NoButton;
}
Refresh();
}
void AdornedRulerPanel::DoDrawPushbuttons(wxDC *dc) const
{
// Paint the area behind the buttons
wxRect background = GetButtonAreaRect();
AColor::MediumTrackInfo(dc, false);
dc->DrawRectangle(background);
for (unsigned ii = 0; ii < static_cast<unsigned>(Button::NumButtons); ++ii) {
auto button = static_cast<Button>(ii);
auto state = GetButtonState(button);
auto toggle = (button == mCaptureState && InButtonRect(button));
auto down = (state != toggle);
DoDrawPushbutton(dc, button, down);
}
}
void AdornedRulerPanel::DoDrawBorder(wxDC * dc)
{
// Draw AdornedRulerPanel border
@ -2779,7 +2911,7 @@ void AdornedRulerPanel::DoDrawBorder(wxDC * dc)
if (mShowScrubbing) {
// Let's distinguish the scrubbing area by using the same gray as for
// selected track control panel.
AColor::Medium(dc, true);
AColor::MediumTrackInfo(dc, true);
dc->DrawRectangle(mScrubZone);
}
@ -2836,7 +2968,7 @@ int AdornedRulerPanel::GetRulerHeight()
int AdornedRulerPanel::GetRulerHeight(bool showScrubBar)
{
return RulerHeight + (showScrubBar ? ScrubHeight : 0);
return ProperRulerHeight + (showScrubBar ? ScrubHeight : 0);
}
void AdornedRulerPanel::SetLeftOffset(int offset)
@ -2962,14 +3094,15 @@ void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc)
const int x = Time2Pos(mQuickPlayPos);
bool previewScrub =
mPrevInScrubZone &&
mPrevZone == StatusChoice::EnteringScrubZone &&
!mProject->GetScrubber().IsScrubbing();
GetOverlay()->Update(x, mIsSnapped, previewScrub);
DoEraseIndicator(dc, mLastQuickPlayX);
mLastQuickPlayX = x;
auto scrub = mPrevInScrubZone || mProject->GetScrubber().HasStartedScrubbing();
auto scrub = mPrevZone == StatusChoice::EnteringScrubZone ||
mProject->GetScrubber().HasStartedScrubbing();
auto width = scrub ? IndicatorBigWidth() : IndicatorSmallWidth;
DoDrawIndicator(dc, mQuickPlayPos, true, width, scrub);
}

View File

@ -102,6 +102,9 @@ class AUDACITY_DLL_API Ruler {
// Good defaults are provided, but you can override here
void SetFonts(const wxFont &minorFont, const wxFont &majorFont, const wxFont &minorMinorFont);
struct Fonts { wxFont *major, *minor, *minorMinor; };
Fonts GetFonts() const
{ return { mMajorFont, mMinorFont, mMinorMinorFont }; }
// Copies *pScale if it is not NULL
void SetNumberScale(const NumberScale *pScale);
@ -304,7 +307,6 @@ public:
void ClearPlayRegion();
void GetPlayRegion(double* playRegionStart, double* playRegionEnd);
void SetProject(AudacityProject* project) {mProject = project;}
void GetMaxSize(wxCoord *width, wxCoord *height);
void InvalidateRuler();
@ -326,6 +328,7 @@ private:
void HandleQPRelease(wxMouseEvent &event);
enum class StatusChoice {
EnteringPushbuttons,
EnteringQP,
EnteringScrubZone,
Leaving,
@ -333,8 +336,6 @@ private:
};
void UpdateStatusBar(StatusChoice choice);
void DoMainMenu();
void OnCaptureLost(wxMouseCaptureLostEvent &evt);
void DoDrawBorder(wxDC * dc);
@ -347,6 +348,22 @@ private:
void DrawQuickPlayIndicator(wxDC * dc /*NULL to DELETE old only*/);
void DoDrawPlayRegion(wxDC * dc);
wxRect GetButtonAreaRect() const;
enum class Button {
QuickPlay = 0,
ScrubBar,
NumButtons,
NoButton = -1
};
wxRect GetButtonRect( Button button ) const;
bool InButtonRect( Button button ) const;
bool GetButtonState( Button button ) const;
void ToggleButtonState( Button button );
void DoDrawPushbutton(wxDC *dc, Button button, bool down) const;
void DoDrawPushbuttons(wxDC *dc) const;
void HandlePushbuttonEvent(wxMouseEvent &evt);
double Pos2Time(int p, bool ignoreFisheye = false);
int Time2Pos(double t, bool ignoreFisheye = false);
@ -364,7 +381,7 @@ private:
Ruler mRuler;
ViewInfo *const mViewInfo;
AudacityProject *mProject;
AudacityProject *const mProject;
TrackList *mTracks;
wxBitmap *mBack;
@ -407,16 +424,15 @@ private:
void OnAutoScroll(wxCommandEvent &evt);
void OnLockPlayRegion(wxCommandEvent &evt);
//
// Main menu
//
void OnShowHideScrubbing(wxCommandEvent &evt);
void OnToggleScrubbing();
bool mPlayRegionDragsSelection;
bool mTimelineToolTip;
bool mQuickPlayEnabled;
Button mCaptureState { Button::NoButton };
enum MouseEventState {
mesNone,
mesDraggingPlayRegionStart,
@ -432,9 +448,11 @@ private:
std::unique_ptr<QuickPlayIndicatorOverlay> mOverlay;
bool mPrevInScrubZone{};
StatusChoice mPrevZone { StatusChoice::NoChange };
bool mShowScrubbing { true };
wxFont mButtonFont;
DECLARE_EVENT_TABLE()
};