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

Merge branch 'master' into scrubbing

This commit is contained in:
Paul Licameli 2016-05-05 22:28:56 -04:00
commit f7765a5068
26 changed files with 5036 additions and 4452 deletions

View File

@ -17,16 +17,32 @@ It is also a place to document colour usage policy in Audacity
*//********************************************************************/ *//********************************************************************/
#include "Audacity.h"
#include "AColor.h"
#include <wx/colour.h> #include <wx/colour.h>
#include <wx/dc.h> #include <wx/dc.h>
#include <wx/settings.h> #include <wx/settings.h>
#include <wx/utils.h> #include <wx/utils.h>
#include "AColor.h"
#include "Theme.h" #include "Theme.h"
#include "Experimental.h" #include "Experimental.h"
#include "AllThemeResources.h" #include "AllThemeResources.h"
void DCUnchanger::operator () (wxDC *pDC) const
{
if (pDC) {
pDC->SetPen(pen);
pDC->SetBrush(brush);
pDC->SetLogicalFunction(wxRasterOperationMode(logicalOperation));
}
}
ADCChanger::ADCChanger(wxDC *pDC)
: Base{ pDC, ::DCUnchanger{ pDC->GetBrush(), pDC->GetPen(),
long(pDC->GetLogicalFunction()) } }
{}
bool AColor::inited = false; bool AColor::inited = false;
wxBrush AColor::lightBrush[2]; wxBrush AColor::lightBrush[2];
wxBrush AColor::mediumBrush[2]; wxBrush AColor::mediumBrush[2];

View File

@ -14,12 +14,39 @@
#ifndef __AUDACITY_COLOR__ #ifndef __AUDACITY_COLOR__
#define __AUDACITY_COLOR__ #define __AUDACITY_COLOR__
#include "MemoryX.h"
#include <wx/brush.h> #include <wx/brush.h>
#include <wx/pen.h> #include <wx/pen.h>
class wxDC; class wxDC;
class wxRect; class wxRect;
struct DCUnchanger {
public:
DCUnchanger() {}
DCUnchanger(const wxBrush &brush_, const wxPen &pen_, long logicalOperation_)
: brush(brush_), pen(pen_), logicalOperation(logicalOperation_)
{}
void operator () (wxDC *pDC) const;
wxBrush brush {};
wxPen pen {};
long logicalOperation {};
};
// Like wxDCPenChanger, etc., but simple and general
// Make temporary drawing context changes that you back out of, RAII style
class ADCChanger : public std::unique_ptr<wxDC, ::DCUnchanger>
{
using Base = std::unique_ptr<wxDC, ::DCUnchanger>;
public:
ADCChanger() : Base{} {}
ADCChanger(wxDC *pDC);
};
class AColor { class AColor {
public: public:

View File

@ -61,7 +61,7 @@ void AboutDialog::CreateCreditsList()
AddCredit(wxString(wxT("Greg Kozikowski, ")) + _("documentation and support"), roleTeamMember); AddCredit(wxString(wxT("Greg Kozikowski, ")) + _("documentation and support"), roleTeamMember);
AddCredit(wxString(wxT("Paul Licameli, ")) + _("developer"), roleTeamMember); AddCredit(wxString(wxT("Paul Licameli, ")) + _("developer"), roleTeamMember);
AddCredit(wxString(wxT("Leland Lucius, ")) + _("developer"), roleTeamMember); AddCredit(wxString(wxT("Leland Lucius, ")) + _("developer"), roleTeamMember);
AddCredit(wxString(wxT("Peter Sampson, ")) + _("documentation and support"), roleTeamMember); AddCredit(wxString(wxT("Peter Sampson")), roleTeamMember);
AddCredit(wxString(wxT("Martyn Shaw, ")) + _("developer"), roleTeamMember); AddCredit(wxString(wxT("Martyn Shaw, ")) + _("developer"), roleTeamMember);
AddCredit(wxString(wxT("Bill Wharrie, ")) + _("documentation and support"), roleTeamMember); AddCredit(wxString(wxT("Bill Wharrie, ")) + _("documentation and support"), roleTeamMember);

View File

@ -56,7 +56,7 @@ DECLARE_EXPORTED_EVENT_TYPE(AUDACITY_DLL_API, EVT_OPEN_AUDIO_FILE, -1);
// These flags represent the majority of the states that affect // These flags represent the majority of the states that affect
// whether or not items in menus are enabled or disabled. // whether or not items in menus are enabled or disabled.
enum enum CommandFlag : unsigned long long
{ {
AlwaysEnabledFlag = 0x00000000, AlwaysEnabledFlag = 0x00000000,
@ -93,11 +93,89 @@ enum
CaptureNotBusyFlag = 0x20000000, CaptureNotBusyFlag = 0x20000000,
CanStopAudioStreamFlag = 0x40000000, CanStopAudioStreamFlag = 0x40000000,
AudioStreamNotScrubbingFlag AudioStreamNotScrubbingFlag
= 0x80000000, = 0x80000000ULL, // prl
NoFlagsSpecifed = 0xffffffff NoFlagsSpecifed = ~0ULL
}; };
// Prevent accidental misuse with narrower types
bool operator == (CommandFlag, unsigned long) PROHIBITED;
bool operator == (CommandFlag, long) PROHIBITED;
bool operator == (unsigned long, CommandFlag) PROHIBITED;
bool operator == (long, CommandFlag) PROHIBITED;
bool operator != (CommandFlag, unsigned long) PROHIBITED;
bool operator != (CommandFlag, long) PROHIBITED;
bool operator != (unsigned long, CommandFlag) PROHIBITED;
bool operator != (long, CommandFlag) PROHIBITED;
CommandFlag operator & (CommandFlag, unsigned long) PROHIBITED;
CommandFlag operator & (CommandFlag, long) PROHIBITED;
CommandFlag operator & (unsigned long, CommandFlag) PROHIBITED;
CommandFlag operator & (long, CommandFlag) PROHIBITED;
CommandFlag operator | (CommandFlag, unsigned long) PROHIBITED;
CommandFlag operator | (CommandFlag, long) PROHIBITED;
CommandFlag operator | (unsigned long, CommandFlag) PROHIBITED;
CommandFlag operator | (long, CommandFlag) PROHIBITED;
CommandFlag operator ^ (CommandFlag, unsigned long) PROHIBITED;
CommandFlag operator ^ (CommandFlag, long) PROHIBITED;
CommandFlag operator ^ (unsigned long, CommandFlag) PROHIBITED;
CommandFlag operator ^ (long, CommandFlag) PROHIBITED;
bool operator == (CommandFlag, unsigned int) PROHIBITED;
bool operator == (CommandFlag, int) PROHIBITED;
bool operator == (unsigned int, CommandFlag) PROHIBITED;
bool operator == (int, CommandFlag) PROHIBITED;
bool operator != (CommandFlag, unsigned int) PROHIBITED;
bool operator != (CommandFlag, int) PROHIBITED;
bool operator != (unsigned int, CommandFlag) PROHIBITED;
bool operator != (int, CommandFlag) PROHIBITED;
CommandFlag operator & (CommandFlag, unsigned int) PROHIBITED;
CommandFlag operator & (CommandFlag, int) PROHIBITED;
CommandFlag operator & (unsigned int, CommandFlag) PROHIBITED;
CommandFlag operator & (int, CommandFlag) PROHIBITED;
CommandFlag operator | (CommandFlag, unsigned int) PROHIBITED;
CommandFlag operator | (CommandFlag, int) PROHIBITED;
CommandFlag operator | (unsigned int, CommandFlag) PROHIBITED;
CommandFlag operator | (int, CommandFlag) PROHIBITED;
CommandFlag operator ^ (CommandFlag, unsigned int) PROHIBITED;
CommandFlag operator ^ (CommandFlag, int) PROHIBITED;
CommandFlag operator ^ (unsigned int, CommandFlag) PROHIBITED;
CommandFlag operator ^ (int, CommandFlag) PROHIBITED;
// Supply the bitwise operations
inline CommandFlag operator ~ (CommandFlag flag)
{
return static_cast<CommandFlag>( ~ static_cast<unsigned long long> (flag) );
}
inline CommandFlag operator & (CommandFlag lhs, CommandFlag rhs)
{
return static_cast<CommandFlag> (
static_cast<unsigned long long>(lhs) & static_cast<unsigned long long>(rhs)
);
}
inline CommandFlag operator | (CommandFlag lhs, CommandFlag rhs)
{
return static_cast<CommandFlag> (
static_cast<unsigned long long>(lhs) | static_cast<unsigned long long>(rhs)
);
}
inline CommandFlag & operator |= (CommandFlag &lhs, CommandFlag rhs)
{
lhs = lhs | rhs;
return lhs;
}
using CommandMask = CommandFlag;
class BlockFile; class BlockFile;
class AudacityApp final : public wxApp { class AudacityApp final : public wxApp {

View File

@ -2258,7 +2258,9 @@ void AudioIO::StopStream()
while( mAudioThreadShouldCallFillBuffersOnce == true ) while( mAudioThreadShouldCallFillBuffersOnce == true )
{ {
// LLL: Experienced recursive yield here...once. // LLL: Experienced recursive yield here...once.
wxGetApp().Yield(true); // Pass true for onlyIfNeeded to avoid recursive call error. // PRL: Made it safe yield to avoid a certain recursive event processing in the
// time ruler when switching from scrub to quick play.
wxGetApp().SafeYield(nullptr, true); // Pass true for onlyIfNeeded to avoid recursive call error.
wxMilliSleep( 50 ); wxMilliSleep( 50 );
} }

View File

@ -907,7 +907,8 @@ void AudacityProject::CreateMenusAndCommands()
#else #else
wxT("Ctrl+M"), wxT("Ctrl+M"),
#endif #endif
0, AudioIONotBusyFlag); AlwaysEnabledFlag, // is this correct??
AudioIONotBusyFlag);
c->AddItem(wxT("EditLabels"), _("&Edit Labels..."), FN(OnEditLabels)); c->AddItem(wxT("EditLabels"), _("&Edit Labels..."), FN(OnEditLabels));
c->AddSeparator(); c->AddSeparator();
@ -1214,7 +1215,7 @@ void AudacityProject::CreateMenusAndCommands()
c->AddCommand(wxT("PlaySpeedInc"), _("Increase playback speed"), FN(OnPlaySpeedInc)); c->AddCommand(wxT("PlaySpeedInc"), _("Increase playback speed"), FN(OnPlaySpeedInc));
c->AddCommand(wxT("PlaySpeedDec"), _("Decrease playback speed"), FN(OnPlaySpeedDec)); c->AddCommand(wxT("PlaySpeedDec"), _("Decrease playback speed"), FN(OnPlaySpeedDec));
mLastFlags = 0; mLastFlags = AlwaysEnabledFlag;
#if defined(__WXDEBUG__) #if defined(__WXDEBUG__)
// c->CheckDups(); // c->CheckDups();
@ -1223,8 +1224,8 @@ void AudacityProject::CreateMenusAndCommands()
void AudacityProject::PopulateEffectsMenu(CommandManager* c, void AudacityProject::PopulateEffectsMenu(CommandManager* c,
EffectType type, EffectType type,
int batchflags, CommandFlag batchflags,
int realflags) CommandFlag realflags)
{ {
PluginManager & pm = PluginManager::Get(); PluginManager & pm = PluginManager::Get();
@ -1295,8 +1296,8 @@ void AudacityProject::PopulateEffectsMenu(CommandManager* c,
void AudacityProject::AddEffectMenuItems(CommandManager *c, void AudacityProject::AddEffectMenuItems(CommandManager *c,
EffectPlugs & plugs, EffectPlugs & plugs,
int batchflags, CommandFlag batchflags,
int realflags, CommandFlag realflags,
bool isDefault) bool isDefault)
{ {
size_t pluginCnt = plugs.GetCount(); size_t pluginCnt = plugs.GetCount();
@ -1311,7 +1312,7 @@ void AudacityProject::AddEffectMenuItems(CommandManager *c,
wxArrayString groupNames; wxArrayString groupNames;
PluginIDList groupPlugs; PluginIDList groupPlugs;
wxArrayInt groupFlags; std::vector<CommandFlag> groupFlags;
if (grouped) if (grouped)
{ {
wxString last; wxString last;
@ -1361,13 +1362,13 @@ void AudacityProject::AddEffectMenuItems(CommandManager *c,
groupNames.Clear(); groupNames.Clear();
groupPlugs.Clear(); groupPlugs.Clear();
groupFlags.Clear(); groupFlags.clear();
last = current; last = current;
} }
groupNames.Add(name); groupNames.Add(name);
groupPlugs.Add(plug->GetID()); groupPlugs.Add(plug->GetID());
groupFlags.Add(plug->IsEffectRealtime() ? realflags : batchflags); groupFlags.push_back(plug->IsEffectRealtime() ? realflags : batchflags);
} }
if (groupNames.GetCount() > 0) if (groupNames.GetCount() > 0)
@ -1414,7 +1415,7 @@ void AudacityProject::AddEffectMenuItems(CommandManager *c,
groupNames.Add(group + name); groupNames.Add(group + name);
groupPlugs.Add(plug->GetID()); groupPlugs.Add(plug->GetID());
groupFlags.Add(plug->IsEffectRealtime() ? realflags : batchflags); groupFlags.push_back(plug->IsEffectRealtime() ? realflags : batchflags);
} }
if (groupNames.GetCount() > 0) if (groupNames.GetCount() > 0)
@ -1430,7 +1431,7 @@ void AudacityProject::AddEffectMenuItems(CommandManager *c,
void AudacityProject::AddEffectMenuItemGroup(CommandManager *c, void AudacityProject::AddEffectMenuItemGroup(CommandManager *c,
const wxArrayString & names, const wxArrayString & names,
const PluginIDList & plugs, const PluginIDList & plugs,
const wxArrayInt & flags, const std::vector<CommandFlag> & flags,
bool isDefault) bool isDefault)
{ {
int namesCnt = (int) names.GetCount(); int namesCnt = (int) names.GetCount();
@ -1609,7 +1610,7 @@ void AudacityProject::RebuildOtherMenus()
} }
} }
int AudacityProject::GetFocusedFrame() CommandFlag AudacityProject::GetFocusedFrame()
{ {
wxWindow *w = FindFocus(); wxWindow *w = FindFocus();
@ -1629,16 +1630,16 @@ int AudacityProject::GetFocusedFrame()
w = w->GetParent(); w = w->GetParent();
} }
return 0; return AlwaysEnabledFlag;
} }
wxUint32 AudacityProject::GetUpdateFlags() CommandFlag AudacityProject::GetUpdateFlags()
{ {
// This method determines all of the flags that determine whether // This method determines all of the flags that determine whether
// certain menu items and commands should be enabled or disabled, // certain menu items and commands should be enabled or disabled,
// and returns them in a bitfield. Note that if none of the flags // and returns them in a bitfield. Note that if none of the flags
// have changed, it's not necessary to even check for updates. // have changed, it's not necessary to even check for updates.
wxUint32 flags = 0; auto flags = AlwaysEnabledFlag;
if (!gAudioIO->IsAudioTokenActive(GetAudioIOToken())) if (!gAudioIO->IsAudioTokenActive(GetAudioIOToken()))
flags |= AudioIONotBusyFlag; flags |= AudioIONotBusyFlag;
@ -1767,8 +1768,8 @@ wxUint32 AudacityProject::GetUpdateFlags()
void AudacityProject::SelectAllIfNone() void AudacityProject::SelectAllIfNone()
{ {
wxUint32 flags = GetUpdateFlags(); auto flags = GetUpdateFlags();
if(((flags & TracksSelectedFlag) ==0) || if(!(flags & TracksSelectedFlag) ||
(mViewInfo.selectedRegion.isPoint())) (mViewInfo.selectedRegion.isPoint()))
OnSelectAll(); OnSelectAll();
} }
@ -1850,17 +1851,17 @@ void AudacityProject::UpdateMenus(bool checkActive)
if (checkActive && !IsActive()) if (checkActive && !IsActive())
return; return;
wxUint32 flags = GetUpdateFlags(); auto flags = GetUpdateFlags();
wxUint32 flags2 = flags; auto flags2 = flags;
// We can enable some extra items if we have select-all-on-none. // We can enable some extra items if we have select-all-on-none.
//EXPLAIN-ME: Why is this here rather than in GetUpdateFlags()? //EXPLAIN-ME: Why is this here rather than in GetUpdateFlags()?
if (mSelectAllOnNone) if (mSelectAllOnNone)
{ {
if ((flags & TracksExistFlag) != 0) if ((flags & TracksExistFlag))
{ {
flags2 |= TracksSelectedFlag; flags2 |= TracksSelectedFlag;
if ((flags & WaveTracksExistFlag) != 0 ) if ((flags & WaveTracksExistFlag))
{ {
flags2 |= TimeSelectedFlag flags2 |= TimeSelectedFlag
| WaveTracksSelectedFlag | WaveTracksSelectedFlag
@ -1875,21 +1876,21 @@ void AudacityProject::UpdateMenus(bool checkActive)
return; return;
mLastFlags = flags; mLastFlags = flags;
mCommandManager.EnableUsingFlags(flags2 , 0xFFFFFFFF); mCommandManager.EnableUsingFlags(flags2 , NoFlagsSpecifed);
// With select-all-on-none, some items that we don't want enabled may have // With select-all-on-none, some items that we don't want enabled may have
// been enabled, since we changed the flags. Here we manually disable them. // been enabled, since we changed the flags. Here we manually disable them.
if (mSelectAllOnNone) if (mSelectAllOnNone)
{ {
if ((flags & TracksSelectedFlag) == 0) if (!(flags & TracksSelectedFlag))
{ {
mCommandManager.Enable(wxT("SplitCut"), false); mCommandManager.Enable(wxT("SplitCut"), false);
if ((flags & WaveTracksSelectedFlag) == 0) if (!(flags & WaveTracksSelectedFlag))
{ {
mCommandManager.Enable(wxT("Split"), false); mCommandManager.Enable(wxT("Split"), false);
} }
if ((flags & TimeSelectedFlag) == 0) if (!(flags & TimeSelectedFlag))
{ {
mCommandManager.Enable(wxT("ExportSel"), false); mCommandManager.Enable(wxT("ExportSel"), false);
mCommandManager.Enable(wxT("SplitNew"), false); mCommandManager.Enable(wxT("SplitNew"), false);
@ -2690,6 +2691,9 @@ void AudacityProject::NextFrame()
case BotDockHasFocus: case BotDockHasFocus:
mToolManager->GetTopDock()->SetFocus(); mToolManager->GetTopDock()->SetFocus();
break; break;
default:
break;
} }
} }
@ -2708,6 +2712,9 @@ void AudacityProject::PrevFrame()
case BotDockHasFocus: case BotDockHasFocus:
mTrackPanel->SetFocus(); mTrackPanel->SetFocus();
break; break;
default:
break;
} }
} }

View File

@ -24,17 +24,21 @@
private: private:
void CreateMenusAndCommands(); void CreateMenusAndCommands();
void PopulateEffectsMenu(CommandManager *c, EffectType type, int batchflags, int realflags); void PopulateEffectsMenu(CommandManager *c, EffectType type,
void AddEffectMenuItems(CommandManager *c, EffectPlugs & plugs, int batchflags, int realflags, bool isDefault); CommandFlag batchflags, CommandFlag realflags);
void AddEffectMenuItemGroup(CommandManager *c, const wxArrayString & names, const PluginIDList & plugs, const wxArrayInt & flags, bool isDefault); void AddEffectMenuItems(CommandManager *c, EffectPlugs & plugs,
CommandFlag batchflags, CommandFlag realflags, bool isDefault);
void AddEffectMenuItemGroup(CommandManager *c, const wxArrayString & names,
const PluginIDList & plugs,
const std::vector<CommandFlag> & flags, bool isDefault);
void CreateRecentFilesMenu(CommandManager *c); void CreateRecentFilesMenu(CommandManager *c);
void ModifyUndoMenuItems(); void ModifyUndoMenuItems();
void ModifyToolbarMenus(); void ModifyToolbarMenus();
// Calls ModifyToolbarMenus() on all projects // Calls ModifyToolbarMenus() on all projects
void ModifyAllProjectToolbarMenus(); void ModifyAllProjectToolbarMenus();
int GetFocusedFrame(); CommandFlag GetFocusedFrame();
wxUint32 GetUpdateFlags(); CommandFlag GetUpdateFlags();
double NearestZeroCrossing(double t0); double NearestZeroCrossing(double t0);

View File

@ -243,10 +243,31 @@ public:
} }
} }
void SetScrollbar(int position, int thumbSize,
int range, int pageSize,
bool refresh = true) override;
private: private:
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };
void ScrollBar::SetScrollbar(int position, int thumbSize,
int range, int pageSize,
bool refresh)
{
// Mitigate flashing of scrollbars by refreshing only when something really changes.
auto changed =
position != GetThumbPosition() ||
thumbSize != GetThumbSize() ||
range != GetRange() ||
pageSize != GetPageSize();
if (!changed)
return;
wxScrollBar::SetScrollbar(position, thumbSize, range, pageSize, refresh);
}
BEGIN_EVENT_TABLE(ScrollBar, wxScrollBar) BEGIN_EVENT_TABLE(ScrollBar, wxScrollBar)
EVT_SET_FOCUS(ScrollBar::OnSetFocus) EVT_SET_FOCUS(ScrollBar::OnSetFocus)
END_EVENT_TABLE() END_EVENT_TABLE()
@ -933,14 +954,18 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id,
mCursorOverlay = std::make_unique<EditCursorOverlay>(this); mCursorOverlay = std::make_unique<EditCursorOverlay>(this);
#ifdef EXPERIMENTAL_SCRUBBING_BASIC #ifdef EXPERIMENTAL_SCRUBBING_BASIC
// This must follow construction of *mIndicatorOverlay, because it must
// attach its timer event handler later (so that its handler is invoked
// earlier)
mScrubOverlay = std::make_unique<ScrubbingOverlay>(this); mScrubOverlay = std::make_unique<ScrubbingOverlay>(this);
mScrubber = std::make_unique<Scrubber>(this); mScrubber = std::make_unique<Scrubber>(this);
#endif #endif
// This must follow construction of *mScrubOverlay, because it must // More order dependencies here...
// This must follow construction of *mIndicatorOverlay, because it must
// attach its timer event handler later (so that its handler is invoked
// earlier)
mPlaybackScroller = std::make_unique<PlaybackScroller>(this);
// This must follow construction of *mPlaybackScroller,
// because it must
// 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)
this->Connect(EVT_TRACK_PANEL_TIMER, this->Connect(EVT_TRACK_PANEL_TIMER,
@ -1771,7 +1796,6 @@ void AudacityProject::FixScrollbars()
mHsbar->SetScrollbar(scaledSbarH + offset, scaledSbarScreen, scaledSbarTotal, mHsbar->SetScrollbar(scaledSbarH + offset, scaledSbarScreen, scaledSbarTotal,
scaledSbarScreen, TRUE); scaledSbarScreen, TRUE);
mHsbar->Refresh();
} }
// Vertical scrollbar // Vertical scrollbar
@ -1779,7 +1803,6 @@ void AudacityProject::FixScrollbars()
panelHeight / mViewInfo.scrollStep, panelHeight / mViewInfo.scrollStep,
totalHeight / mViewInfo.scrollStep, totalHeight / mViewInfo.scrollStep,
panelHeight / mViewInfo.scrollStep, TRUE); panelHeight / mViewInfo.scrollStep, TRUE);
mVsbar->Refresh();
if (refresh || (rescroll && if (refresh || (rescroll &&
(GetScreenEndTime() - mViewInfo.h) < mViewInfo.total)) { (GetScreenEndTime() - mViewInfo.h) < mViewInfo.total)) {
@ -2049,11 +2072,12 @@ void AudacityProject::OnScroll(wxScrollEvent & WXUNUSED(event))
/// Determines if flags for command are compatible with current state. /// Determines if flags for command are compatible with current state.
/// If not, then try some recovery action to make it so. /// If not, then try some recovery action to make it so.
/// @return whether compatible or not after any actions taken. /// @return whether compatible or not after any actions taken.
bool AudacityProject::TryToMakeActionAllowed( wxUint32 & flags, wxUint32 flagsRqd, wxUint32 mask ) bool AudacityProject::TryToMakeActionAllowed
( CommandFlag & flags, CommandFlag flagsRqd, CommandFlag mask )
{ {
bool bAllowed; bool bAllowed;
if( flags == 0 ) if( !flags )
flags = GetUpdateFlags(); flags = GetUpdateFlags();
bAllowed = ((flags & mask) == (flagsRqd & mask)); bAllowed = ((flags & mask) == (flagsRqd & mask));
@ -2065,12 +2089,12 @@ bool AudacityProject::TryToMakeActionAllowed( wxUint32 & flags, wxUint32 flagsRq
if( !mSelectAllOnNone ) if( !mSelectAllOnNone )
return false; return false;
wxUint32 MissingFlags = (flags & ~flagsRqd) & mask; auto MissingFlags = (flags & ~flagsRqd) & mask;
// IF selecting all audio won't do any good, THEN return with failure. // IF selecting all audio won't do any good, THEN return with failure.
if( (flags & WaveTracksExistFlag) == 0 ) if( !(flags & WaveTracksExistFlag) )
return false; return false;
if( (MissingFlags & ~( TimeSelectedFlag | WaveTracksSelectedFlag))!=0) if( (MissingFlags & ~( TimeSelectedFlag | WaveTracksSelectedFlag)) )
return false; return false;
OnSelectAll(); OnSelectAll();
@ -2084,7 +2108,7 @@ void AudacityProject::OnMenu(wxCommandEvent & event)
bool handled = mCommandManager.HandleMenuID(event.GetId(), bool handled = mCommandManager.HandleMenuID(event.GetId(),
GetUpdateFlags(), GetUpdateFlags(),
0xFFFFFFFF); NoFlagsSpecifed);
if (handled) if (handled)
event.Skip(false); event.Skip(false);
@ -5315,3 +5339,46 @@ int AudacityProject::GetEstimatedRecordingMinsLeftOnDisk() {
int iRecMins = (int)(dRecTime / 60.0); int iRecMins = (int)(dRecTime / 60.0);
return iRecMins; return iRecMins;
} }
AudacityProject::PlaybackScroller::PlaybackScroller(AudacityProject *project)
: mProject(project)
{
mProject->Connect(EVT_TRACK_PANEL_TIMER,
wxCommandEventHandler(PlaybackScroller::OnTimer),
NULL,
this);
}
AudacityProject::PlaybackScroller::~PlaybackScroller()
{
mProject->Disconnect(EVT_TRACK_PANEL_TIMER,
wxCommandEventHandler(PlaybackScroller::OnTimer),
NULL,
this);
}
void AudacityProject::PlaybackScroller::OnTimer(wxCommandEvent &event)
{
// Let other listeners get the notification
event.Skip();
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
if (mActive && mProject->IsAudioActive())
{
// Pan the view, so that we center the play indicator.
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
}

View File

@ -504,7 +504,8 @@ public:
void OnAudioIONewBlockFiles(const AutoSaveFile & blockFileLog) override; void OnAudioIONewBlockFiles(const AutoSaveFile & blockFileLog) override;
// Command Handling // Command Handling
bool TryToMakeActionAllowed( wxUint32 & flags, wxUint32 flagsRqd, wxUint32 mask ); bool TryToMakeActionAllowed
( CommandFlag & flags, CommandFlag flagsRqd, CommandFlag mask );
///Prevents DELETE from external thread - for e.g. use of GetActiveProject ///Prevents DELETE from external thread - for e.g. use of GetActiveProject
static void AllProjectsDeleteLock(); static void AllProjectsDeleteLock();
@ -580,7 +581,7 @@ public:
CommandManager mCommandManager; CommandManager mCommandManager;
wxUint32 mLastFlags; CommandFlag mLastFlags;
// Window elements // Window elements
@ -720,6 +721,28 @@ public:
const Scrubber &GetScrubber() const { return *mScrubber; } const Scrubber &GetScrubber() const { return *mScrubber; }
#endif #endif
class PlaybackScroller final : public wxEvtHandler
{
public:
explicit PlaybackScroller(AudacityProject *project);
~PlaybackScroller();
void Activate(bool active)
{
mActive = active;
}
private:
void OnTimer(wxCommandEvent &event);
AudacityProject *mProject;
bool mActive { false };
};
std::unique_ptr<PlaybackScroller> mPlaybackScroller;
public:
PlaybackScroller &GetPlaybackScroller() { return *mPlaybackScroller; }
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };

File diff suppressed because it is too large Load Diff

View File

@ -1015,6 +1015,25 @@ void TrackPanel::ScrollDuringDrag()
mAutoScrolling = true; mAutoScrolling = true;
mListener->TP_ScrollLeft(); mListener->TP_ScrollLeft();
} }
else {
// Bug1387: enable autoscroll during drag, if the pointer is at either extreme x
// coordinate of the screen, even if that is still within the track area.
int xx = mMouseMostRecentX, yy = 0;
this->ClientToScreen(&xx, &yy);
if (xx == 0) {
mAutoScrolling = true;
mListener->TP_ScrollLeft();
}
else {
int width, height;
::wxDisplaySize(&width, &height);
if (xx == width - 1) {
mAutoScrolling = true;
mListener->TP_ScrollRight();
}
}
}
if (mAutoScrolling) { if (mAutoScrolling) {
// AS: To keep the selection working properly as we scroll, // AS: To keep the selection working properly as we scroll,
@ -5901,16 +5920,26 @@ void TrackPanel::OnMouseEvent(wxMouseEvent & event)
ReleaseMouse(); ReleaseMouse();
} }
if (event.Leaving() && !event.ButtonIsDown(wxMOUSE_BTN_ANY)) if (event.Leaving())
{ {
// PRL: was this test really needed? It interfered with my refactoring // PRL: was this test really needed? It interfered with my refactoring
// that tried to eliminate those enum values. // that tried to eliminate those enum values.
// I think it was never true, that mouse capture was pan or gain sliding, // I think it was never true, that mouse capture was pan or gain sliding,
// but no mouse button was down. // but no mouse button was down.
// if (mMouseCapture != IsPanSliding && mMouseCapture != IsGainSliding) // if (mMouseCapture != IsPanSliding && mMouseCapture != IsGainSliding)
{
auto buttons =
// Bug 1325: button state in Leaving events is unreliable on Mac.
// Poll the global state instead.
// event.ButtonIsDown(wxMOUSE_BTN_ANY);
::wxGetMouseState().ButtonIsDown(wxMOUSE_BTN_ANY);
if(!buttons) {
SetCapturedTrack(NULL); SetCapturedTrack(NULL);
#if defined(__WXMAC__) #if defined(__WXMAC__)
// We must install the cursor ourselves since the window under // We must install the cursor ourselves since the window under
// the mouse is no longer this one and wx2.8.12 makes that check. // the mouse is no longer this one and wx2.8.12 makes that check.
// Should re-evaluate with wx3. // Should re-evaluate with wx3.

View File

@ -395,8 +395,8 @@ CommandManager::CommandManager():
mCurrentID(17000), mCurrentID(17000),
mCurrentMenuName(COMMAND), mCurrentMenuName(COMMAND),
mCurrentMenu(NULL), mCurrentMenu(NULL),
mDefaultFlags(0), mDefaultFlags(AlwaysEnabledFlag),
mDefaultMask(0) mDefaultMask(AlwaysEnabledFlag)
{ {
mbSeparatorAllowed = false; mbSeparatorAllowed = false;
} }
@ -647,15 +647,15 @@ void CommandManager::AddCheck(const wxChar *name,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
int checkmark) int checkmark)
{ {
AddItem(name, label, callback, wxT(""), (unsigned int)NoFlagsSpecifed, (unsigned int)NoFlagsSpecifed, checkmark); AddItem(name, label, callback, wxT(""), NoFlagsSpecifed, NoFlagsSpecifed, checkmark);
} }
void CommandManager::AddCheck(const wxChar *name, void CommandManager::AddCheck(const wxChar *name,
const wxChar *label, const wxChar *label,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
int checkmark, int checkmark,
unsigned int flags, CommandFlag flags,
unsigned int mask) CommandMask mask)
{ {
AddItem(name, label, callback, wxT(""), flags, mask, checkmark); AddItem(name, label, callback, wxT(""), flags, mask, checkmark);
} }
@ -663,8 +663,8 @@ void CommandManager::AddCheck(const wxChar *name,
void CommandManager::AddItem(const wxChar *name, void CommandManager::AddItem(const wxChar *name,
const wxChar *label, const wxChar *label,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
unsigned int flags, CommandFlag flags,
unsigned int mask) CommandMask mask)
{ {
AddItem(name, label, callback, wxT(""), flags, mask); AddItem(name, label, callback, wxT(""), flags, mask);
} }
@ -673,8 +673,8 @@ void CommandManager::AddItem(const wxChar *name,
const wxChar *label_in, const wxChar *label_in,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
const wxChar *accel, const wxChar *accel,
unsigned int flags, CommandFlag flags,
unsigned int mask, CommandMask mask,
int checkmark) int checkmark)
{ {
CommandListEntry *entry = NewIdentifier(name, label_in, accel, CurrentMenu(), callback, false, 0, 0); CommandListEntry *entry = NewIdentifier(name, label_in, accel, CurrentMenu(), callback, false, 0, 0);
@ -726,8 +726,8 @@ void CommandManager::AddItemList(const wxString & name,
void CommandManager::AddCommand(const wxChar *name, void CommandManager::AddCommand(const wxChar *name,
const wxChar *label, const wxChar *label,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
unsigned int flags, CommandFlag flags,
unsigned int mask) CommandMask mask)
{ {
AddCommand(name, label, callback, wxT(""), flags, mask); AddCommand(name, label, callback, wxT(""), flags, mask);
} }
@ -736,8 +736,8 @@ void CommandManager::AddCommand(const wxChar *name,
const wxChar *label_in, const wxChar *label_in,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
const wxChar *accel, const wxChar *accel,
unsigned int flags, CommandFlag flags,
unsigned int mask) CommandMask mask)
{ {
NewIdentifier(name, label_in, accel, NULL, callback, false, 0, 0); NewIdentifier(name, label_in, accel, NULL, callback, false, 0, 0);
@ -755,8 +755,8 @@ void CommandManager::AddGlobalCommand(const wxChar *name,
entry->enabled = false; entry->enabled = false;
entry->isGlobal = true; entry->isGlobal = true;
entry->flags = 0; entry->flags = AlwaysEnabledFlag;
entry->mask = 0; entry->mask = AlwaysEnabledFlag;
} }
void CommandManager::AddSeparator() void CommandManager::AddSeparator()
@ -978,13 +978,13 @@ void CommandManager::Enable(const wxString &name, bool enabled)
Enable(entry, enabled); Enable(entry, enabled);
} }
void CommandManager::EnableUsingFlags(wxUint32 flags, wxUint32 mask) void CommandManager::EnableUsingFlags(CommandFlag flags, CommandMask mask)
{ {
for(const auto &entry : mCommandList) { for(const auto &entry : mCommandList) {
if (entry->multi && entry->index != 0) if (entry->multi && entry->index != 0)
continue; continue;
wxUint32 combinedMask = (mask & entry->mask); auto combinedMask = (mask & entry->mask);
if (combinedMask) { if (combinedMask) {
bool enable = ((flags & combinedMask) == bool enable = ((flags & combinedMask) ==
(entry->flags & combinedMask)); (entry->flags & combinedMask));
@ -1038,15 +1038,15 @@ void CommandManager::SetKeyFromIndex(int i, const wxString &key)
entry->key = KeyStringNormalize(key); entry->key = KeyStringNormalize(key);
} }
void CommandManager::TellUserWhyDisallowed( wxUint32 flagsGot, wxUint32 flagsRequired ) void CommandManager::TellUserWhyDisallowed( CommandFlag flagsGot, CommandMask flagsRequired )
{ {
// The default string for 'reason' is a catch all. I hope it won't ever be seen // The default string for 'reason' is a catch all. I hope it won't ever be seen
// and that we will get something more specific. // and that we will get something more specific.
wxString reason = _("There was a problem with your last action. If you think\nthis is a bug, please tell us exactly where it occurred."); wxString reason = _("There was a problem with your last action. If you think\nthis is a bug, please tell us exactly where it occurred.");
wxUint32 missingFlags = flagsRequired & (~flagsGot ); auto missingFlags = flagsRequired & (~flagsGot );
if( missingFlags & AudioIONotBusyFlag ) if( missingFlags & AudioIONotBusyFlag )
reason= _("You can only do this when playing and recording are\n stopped. (Pausing is not sufficient.)"); reason = _("You can only do this when playing and recording are\n stopped. (Pausing is not sufficient.)");
else if( missingFlags & StereoRequiredFlag ) else if( missingFlags & StereoRequiredFlag )
reason = _("You must first select some stereo audio for this\n to use. (You cannot use this with mono.)"); reason = _("You must first select some stereo audio for this\n to use. (You cannot use this with mono.)");
else if( missingFlags & TimeSelectedFlag ) else if( missingFlags & TimeSelectedFlag )
@ -1081,7 +1081,7 @@ bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent &
// enable them temporarily and then disable them again after handling. // enable them temporarily and then disable them again after handling.
// LL: Why do they need to be disabled??? // LL: Why do they need to be disabled???
entry->enabled = true; entry->enabled = true;
bool ret = HandleCommandEntry(entry, 0xffffffff, 0xffffffff, &evt); bool ret = HandleCommandEntry(entry, NoFlagsSpecifed, NoFlagsSpecifed, &evt);
entry->enabled = false; entry->enabled = false;
return ret; return ret;
} }
@ -1094,7 +1094,7 @@ bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent &
return false; return false;
} }
wxUint32 flags = project->GetUpdateFlags(); auto flags = project->GetUpdateFlags();
wxKeyEvent temp = evt; wxKeyEvent temp = evt;
@ -1105,12 +1105,12 @@ bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent &
return true; return true;
} }
return HandleCommandEntry(entry, flags, 0xffffffff, &temp); return HandleCommandEntry(entry, flags, NoFlagsSpecifed, &temp);
} }
if (type == wxEVT_KEY_UP && entry->wantKeyup) if (type == wxEVT_KEY_UP && entry->wantKeyup)
{ {
return HandleCommandEntry(entry, flags, 0xffffffff, &temp); return HandleCommandEntry(entry, flags, NoFlagsSpecifed, &temp);
} }
return false; return false;
@ -1120,12 +1120,13 @@ bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent &
/// returning true iff successful. If you pass any flags, /// returning true iff successful. If you pass any flags,
///the command won't be executed unless the flags are compatible ///the command won't be executed unless the flags are compatible
///with the command's flags. ///with the command's flags.
bool CommandManager::HandleCommandEntry(const CommandListEntry * entry, wxUint32 flags, wxUint32 mask, const wxEvent * evt) bool CommandManager::HandleCommandEntry(const CommandListEntry * entry,
CommandFlag flags, CommandMask mask, const wxEvent * evt)
{ {
if (!entry || !entry->enabled) if (!entry || !entry->enabled)
return false; return false;
wxUint32 combinedMask = (mask & entry->mask); auto combinedMask = (mask & entry->mask);
if (combinedMask) { if (combinedMask) {
AudacityProject * proj; AudacityProject * proj;
@ -1154,7 +1155,7 @@ bool CommandManager::HandleCommandEntry(const CommandListEntry * entry, wxUint32
///CommandManagerListener function. If you pass any flags, ///CommandManagerListener function. If you pass any flags,
///the command won't be executed unless the flags are compatible ///the command won't be executed unless the flags are compatible
///with the command's flags. ///with the command's flags.
bool CommandManager::HandleMenuID(int id, wxUint32 flags, wxUint32 mask) bool CommandManager::HandleMenuID(int id, CommandFlag flags, CommandMask mask)
{ {
CommandListEntry *entry = mCommandIDHash[id]; CommandListEntry *entry = mCommandIDHash[id];
return HandleCommandEntry( entry, flags, mask ); return HandleCommandEntry( entry, flags, mask );
@ -1163,7 +1164,7 @@ bool CommandManager::HandleMenuID(int id, wxUint32 flags, wxUint32 mask)
/// HandleTextualCommand() allows us a limitted version of script/batch /// HandleTextualCommand() allows us a limitted version of script/batch
/// behavior, since we can get from a string command name to the actual /// behavior, since we can get from a string command name to the actual
/// code to run. /// code to run.
bool CommandManager::HandleTextualCommand(wxString & Str, wxUint32 flags, wxUint32 mask) bool CommandManager::HandleTextualCommand(wxString & Str, CommandFlag flags, CommandMask mask)
{ {
// Linear search for now... // Linear search for now...
for (const auto &entry : mCommandList) for (const auto &entry : mCommandList)
@ -1410,14 +1411,14 @@ void CommandManager::WriteXML(XMLWriter &xmlFile)
xmlFile.EndTag(wxT("audacitykeyboard")); xmlFile.EndTag(wxT("audacitykeyboard"));
} }
void CommandManager::SetDefaultFlags(wxUint32 flags, wxUint32 mask) void CommandManager::SetDefaultFlags(CommandFlag flags, CommandMask mask)
{ {
mDefaultFlags = flags; mDefaultFlags = flags;
mDefaultMask = mask; mDefaultMask = mask;
} }
void CommandManager::SetCommandFlags(const wxString &name, void CommandManager::SetCommandFlags(const wxString &name,
wxUint32 flags, wxUint32 mask) CommandFlag flags, CommandMask mask)
{ {
CommandListEntry *entry = mCommandNameHash[name]; CommandListEntry *entry = mCommandNameHash[name];
if (entry) { if (entry) {
@ -1427,7 +1428,7 @@ void CommandManager::SetCommandFlags(const wxString &name,
} }
void CommandManager::SetCommandFlags(const wxChar **names, void CommandManager::SetCommandFlags(const wxChar **names,
wxUint32 flags, wxUint32 mask) CommandFlag flags, CommandMask mask)
{ {
const wxChar **nptr = names; const wxChar **nptr = names;
while(*nptr) { while(*nptr) {
@ -1436,7 +1437,7 @@ void CommandManager::SetCommandFlags(const wxChar **names,
} }
} }
void CommandManager::SetCommandFlags(wxUint32 flags, wxUint32 mask, ...) void CommandManager::SetCommandFlags(CommandFlag flags, CommandMask mask, ...)
{ {
va_list list; va_list list;
va_start(list, mask); va_start(list, mask);

View File

@ -66,8 +66,8 @@ struct CommandListEntry
bool skipKeydown; bool skipKeydown;
bool wantKeyup; bool wantKeyup;
bool isGlobal; bool isGlobal;
wxUint32 flags; CommandFlag flags;
wxUint32 mask; CommandMask mask;
}; };
using MenuBarList = std::vector < MenuBarListEntry >; using MenuBarList = std::vector < MenuBarListEntry >;
@ -132,21 +132,21 @@ class AUDACITY_DLL_API CommandManager final : public XMLTagHandler
const wxChar *label, const wxChar *label,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
int checkmark, int checkmark,
unsigned int flags, CommandFlag flags,
unsigned int mask); CommandMask mask);
void AddItem(const wxChar *name, void AddItem(const wxChar *name,
const wxChar *label, const wxChar *label,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
unsigned int flags = NoFlagsSpecifed, CommandFlag flags = NoFlagsSpecifed,
unsigned int mask = NoFlagsSpecifed); CommandMask mask = NoFlagsSpecifed);
void AddItem(const wxChar *name, void AddItem(const wxChar *name,
const wxChar *label_in, const wxChar *label_in,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
const wxChar *accel, const wxChar *accel,
unsigned int flags = NoFlagsSpecifed, CommandFlag flags = NoFlagsSpecifed,
unsigned int mask = NoFlagsSpecifed, CommandMask mask = NoFlagsSpecifed,
int checkmark = -1); int checkmark = -1);
void AddSeparator(); void AddSeparator();
@ -156,15 +156,15 @@ class AUDACITY_DLL_API CommandManager final : public XMLTagHandler
void AddCommand(const wxChar *name, void AddCommand(const wxChar *name,
const wxChar *label, const wxChar *label,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
unsigned int flags = NoFlagsSpecifed, CommandFlag flags = NoFlagsSpecifed,
unsigned int mask = NoFlagsSpecifed); CommandMask mask = NoFlagsSpecifed);
void AddCommand(const wxChar *name, void AddCommand(const wxChar *name,
const wxChar *label, const wxChar *label,
const CommandFunctorPointer &callback, const CommandFunctorPointer &callback,
const wxChar *accel, const wxChar *accel,
unsigned int flags = NoFlagsSpecifed, CommandFlag flags = NoFlagsSpecifed,
unsigned int mask = NoFlagsSpecifed); CommandMask mask = NoFlagsSpecifed);
void AddGlobalCommand(const wxChar *name, void AddGlobalCommand(const wxChar *name,
const wxChar *label, const wxChar *label,
@ -175,21 +175,21 @@ class AUDACITY_DLL_API CommandManager final : public XMLTagHandler
// //
// For NEW items/commands // For NEW items/commands
void SetDefaultFlags(wxUint32 flags, wxUint32 mask); void SetDefaultFlags(CommandFlag flags, CommandMask mask);
wxUint32 GetDefaultFlags() const { return mDefaultFlags; } CommandFlag GetDefaultFlags() const { return mDefaultFlags; }
wxUint32 GetDefaultMask() const { return mDefaultMask; } CommandMask GetDefaultMask() const { return mDefaultMask; }
void SetCommandFlags(const wxString &name, wxUint32 flags, wxUint32 mask); void SetCommandFlags(const wxString &name, CommandFlag flags, CommandMask mask);
void SetCommandFlags(const wxChar **names, void SetCommandFlags(const wxChar **names,
wxUint32 flags, wxUint32 mask); CommandFlag flags, CommandMask mask);
// Pass multiple command names as const wxChar *, terminated by NULL // Pass multiple command names as const wxChar *, terminated by NULL
void SetCommandFlags(wxUint32 flags, wxUint32 mask, ...); void SetCommandFlags(CommandFlag flags, CommandMask mask, ...);
// //
// Modifying menus // Modifying menus
// //
void EnableUsingFlags(wxUint32 flags, wxUint32 mask); void EnableUsingFlags(CommandFlag flags, CommandMask mask);
void Enable(const wxString &name, bool enabled); void Enable(const wxString &name, bool enabled);
void Check(const wxString &name, bool checked); void Check(const wxString &name, bool checked);
void Modify(const wxString &name, const wxString &newLabel); void Modify(const wxString &name, const wxString &newLabel);
@ -208,8 +208,8 @@ class AUDACITY_DLL_API CommandManager final : public XMLTagHandler
// "permit" allows filtering even if the active window isn't a child of the project. // "permit" allows filtering even if the active window isn't a child of the project.
// Lyrics and MixerTrackCluster classes use it. // Lyrics and MixerTrackCluster classes use it.
bool FilterKeyEvent(AudacityProject *project, const wxKeyEvent & evt, bool permit = false); bool FilterKeyEvent(AudacityProject *project, const wxKeyEvent & evt, bool permit = false);
bool HandleMenuID(int id, wxUint32 flags, wxUint32 mask); bool HandleMenuID(int id, CommandFlag flags, CommandMask mask);
bool HandleTextualCommand(wxString & Str, wxUint32 flags, wxUint32 mask); bool HandleTextualCommand(wxString & Str, CommandFlag flags, CommandMask mask);
// //
// Accessing // Accessing
@ -271,8 +271,8 @@ protected:
// Executing commands // Executing commands
// //
bool HandleCommandEntry(const CommandListEntry * entry, wxUint32 flags, wxUint32 mask, const wxEvent * evt = NULL); bool HandleCommandEntry(const CommandListEntry * entry, CommandFlag flags, CommandMask mask, const wxEvent * evt = NULL);
void TellUserWhyDisallowed(wxUint32 flagsGot, wxUint32 flagsRequired); void TellUserWhyDisallowed(CommandFlag flagsGot, CommandFlag flagsRequired);
// //
// Modifying // Modifying
@ -313,8 +313,8 @@ private:
wxString mCurrentMenuName; wxString mCurrentMenuName;
wxMenu * mCurrentMenu; wxMenu * mCurrentMenu;
wxUint32 mDefaultFlags; CommandFlag mDefaultFlags;
wxUint32 mDefaultMask; CommandMask mDefaultMask;
}; };
#endif #endif

View File

@ -38,7 +38,7 @@ bool ExecMenuCommand::Apply(CommandExecutionContext context)
CommandManager *cmdManager = context.GetProject()->GetCommandManager(); CommandManager *cmdManager = context.GetProject()->GetCommandManager();
wxString cmdName = GetString(wxT("CommandName")); wxString cmdName = GetString(wxT("CommandName"));
wxUint32 cmdFlags = 0; // TODO ? auto cmdFlags = AlwaysEnabledFlag; // TODO ?
wxUint32 cmdMask = 0; auto cmdMask = AlwaysEnabledFlag;
return cmdManager->HandleTextualCommand(cmdName, cmdFlags, cmdMask); return cmdManager->HandleTextualCommand(cmdName, cmdFlags, cmdMask);
} }

View File

@ -3219,7 +3219,7 @@ void EffectUIHost::OnApply(wxCommandEvent & evt)
// Honor the "select all if none" preference...a little hackish, but whatcha gonna do... // Honor the "select all if none" preference...a little hackish, but whatcha gonna do...
if (!mIsBatch && mEffect->GetType() != EffectTypeGenerate && mProject->mViewInfo.selectedRegion.isPoint()) if (!mIsBatch && mEffect->GetType() != EffectTypeGenerate && mProject->mViewInfo.selectedRegion.isPoint())
{ {
wxUint32 flags = 0; auto flags = AlwaysEnabledFlag;
bool allowed = mProject->TryToMakeActionAllowed(flags, bool allowed = mProject->TryToMakeActionAllowed(flags,
WaveTracksSelectedFlag | TimeSelectedFlag, WaveTracksSelectedFlag | TimeSelectedFlag,
WaveTracksSelectedFlag | TimeSelectedFlag); WaveTracksSelectedFlag | TimeSelectedFlag);

View File

@ -738,16 +738,24 @@ void ControlToolBar::OnKeyEvent(wxKeyEvent & event)
void ControlToolBar::OnPlay(wxCommandEvent & WXUNUSED(evt)) void ControlToolBar::OnPlay(wxCommandEvent & WXUNUSED(evt))
{ {
if (!CanStopAudioStream()) auto doubleClicked = mPlay->IsDoubleClicked();
return; mPlay->ClearDoubleClicked();
StopPlaying(); auto p = GetActiveProject();
AudacityProject *p = GetActiveProject(); if (doubleClicked)
if (p) p->TP_DisplaySelection(); p->GetPlaybackScroller().Activate(true);
else {
if (!CanStopAudioStream())
return;
PlayDefault(); StopPlaying();
UpdateStatusBar(GetActiveProject());
if (p) p->TP_DisplaySelection();
PlayDefault();
UpdateStatusBar(p);
}
} }
void ControlToolBar::OnStop(wxCommandEvent & WXUNUSED(evt)) void ControlToolBar::OnStop(wxCommandEvent & WXUNUSED(evt))
@ -778,9 +786,11 @@ void ControlToolBar::StopPlaying(bool stopStream /* = true*/)
{ {
AudacityProject *project = GetActiveProject(); AudacityProject *project = GetActiveProject();
if(project) if(project) {
project->GetPlaybackScroller().Activate(false);
// Let scrubbing code do some appearance change // Let scrubbing code do some appearance change
project->GetScrubber().StopScrubbing(); project->GetScrubber().StopScrubbing();
}
if (!CanStopAudioStream()) if (!CanStopAudioStream())
return; return;

View File

@ -474,11 +474,24 @@ void TranscriptionToolBar::PlayAtSpeed(bool looped, bool cutPreview)
// Come here from button clicks only // Come here from button clicks only
void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & WXUNUSED(event)) void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & WXUNUSED(event))
{ {
// Let control have precedence over shift auto button = mButtons[TTB_PlaySpeed];
const bool cutPreview = mButtons[TTB_PlaySpeed]->WasControlDown();
const bool looped = !cutPreview && auto doubleClicked = button->IsDoubleClicked();
mButtons[TTB_PlaySpeed]->WasShiftDown(); button->ClearDoubleClicked();
PlayAtSpeed(looped, cutPreview);
if (doubleClicked) {
GetActiveProject()->GetPlaybackScroller().Activate(true);
// Pop up the button
SetButton(false, button);
}
else {
// Let control have precedence over shift
const bool cutPreview = mButtons[TTB_PlaySpeed]->WasControlDown();
const bool looped = !cutPreview &&
button->WasShiftDown();
PlayAtSpeed(looped, cutPreview);
}
} }
void TranscriptionToolBar::OnSpeedSlider(wxCommandEvent& WXUNUSED(event)) void TranscriptionToolBar::OnSpeedSlider(wxCommandEvent& WXUNUSED(event))

View File

@ -8,6 +8,7 @@ Paul Licameli split from TrackPanel.cpp
**********************************************************************/ **********************************************************************/
#include "../../Audacity.h"
#include "PlayIndicatorOverlay.h" #include "PlayIndicatorOverlay.h"
#include "../../AColor.h" #include "../../AColor.h"

View File

@ -200,7 +200,7 @@ void Scrubber::MarkScrubStart(
// needed for the decision to start scrubbing later when handling // needed for the decision to start scrubbing later when handling
// drag events. // drag events.
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL #ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
mSmoothScrollingScrub = smoothScrolling; SetScrollScrubbing (smoothScrolling);
#endif #endif
mAlwaysSeeking = alwaysSeeking; mAlwaysSeeking = alwaysSeeking;
mScrubStartPosition = xx; mScrubStartPosition = xx;
@ -356,25 +356,6 @@ void Scrubber::ContinueScrubbing()
if (mScrubSpeedDisplayCountdown > 0) if (mScrubSpeedDisplayCountdown > 0)
--mScrubSpeedDisplayCountdown; --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
} }
void Scrubber::StopScrubbing() void Scrubber::StopScrubbing()
@ -382,7 +363,7 @@ void Scrubber::StopScrubbing()
UncheckAllMenuItems(); UncheckAllMenuItems();
mScrubStartPosition = -1; mScrubStartPosition = -1;
mSmoothScrollingScrub = false; SetScrollScrubbing (false);
if (!IsScrubbing()) if (!IsScrubbing())
{ {
@ -393,6 +374,12 @@ void Scrubber::StopScrubbing()
} }
} }
void Scrubber::SetScrollScrubbing(bool scrollScrubbing)
{
mSmoothScrollingScrub = scrollScrubbing;
mProject->GetPlaybackScroller().Activate(scrollScrubbing);
}
bool Scrubber::IsScrubbing() const bool Scrubber::IsScrubbing() const
{ {
if (mScrubToken <= 0) if (mScrubToken <= 0)
@ -403,6 +390,7 @@ bool Scrubber::IsScrubbing() const
const_cast<Scrubber&>(*this).mScrubToken = -1; const_cast<Scrubber&>(*this).mScrubToken = -1;
const_cast<Scrubber&>(*this).mScrubStartPosition = -1; const_cast<Scrubber&>(*this).mScrubStartPosition = -1;
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL #ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
// Don't call SetScrollScrubbing
const_cast<Scrubber&>(*this).mSmoothScrollingScrub = false; const_cast<Scrubber&>(*this).mSmoothScrollingScrub = false;
#endif #endif
return false; return false;
@ -557,7 +545,6 @@ void ScrubbingOverlay::Draw
dc.DrawText(mLastScrubSpeedText, mLastScrubRect.GetX(), mLastScrubRect.GetY()); 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
@ -666,7 +653,7 @@ void Scrubber::DoScrub(bool scroll, bool seek)
MarkScrubStart(xx, scroll, seek); MarkScrubStart(xx, scroll, seek);
} }
else if(!match) { else if(!match) {
mSmoothScrollingScrub = scroll; SetScrollScrubbing(scroll);
mAlwaysSeeking = seek; mAlwaysSeeking = seek;
UncheckAllMenuItems(); UncheckAllMenuItems();
CheckMenuItem(); CheckMenuItem();

View File

@ -54,8 +54,11 @@ public:
bool HasStartedScrubbing() const bool HasStartedScrubbing() const
{ return GetScrubStartPosition() >= 0; } { return GetScrubStartPosition() >= 0; }
bool IsScrubbing() const; bool IsScrubbing() const;
bool IsScrollScrubbing() const // If true, implies HasStartedScrubbing() bool IsScrollScrubbing() const // If true, implies HasStartedScrubbing()
{ return mSmoothScrollingScrub; } { return mSmoothScrollingScrub; }
void SetScrollScrubbing(bool scrollScrubbing);
bool IsAlwaysSeeking() const bool IsAlwaysSeeking() const
{ return mAlwaysSeeking; } { return mAlwaysSeeking; }

View File

@ -407,6 +407,8 @@ void AButton::OnMouseEvent(wxMouseEvent & event)
if (mEnabled && event.IsButton()) { if (mEnabled && event.IsButton()) {
if (event.ButtonIsDown(wxMOUSE_BTN_ANY)) { if (event.ButtonIsDown(wxMOUSE_BTN_ANY)) {
mIsClicking = true; mIsClicking = true;
if (event.ButtonDClick())
mIsDoubleClicked = true;
if( !HasCapture() ) if( !HasCapture() )
CaptureMouse(); CaptureMouse();
} }

View File

@ -109,6 +109,11 @@ class AButton final : public wxWindow {
bool WasControlDown(); // returns true if control was held down bool WasControlDown(); // returns true if control was held down
// the last time the button was clicked // the last time the button was clicked
bool IsDown(){ return mButtonIsDown;} bool IsDown(){ return mButtonIsDown;}
// Double click is detected, but not automatically cleared.
bool IsDoubleClicked() const { return mIsDoubleClicked; }
void ClearDoubleClicked() { mIsDoubleClicked = false; }
void SetButtonToggles( bool toggler ){ mToggle = toggler;} void SetButtonToggles( bool toggler ){ mToggle = toggler;}
void Toggle(){ mButtonIsDown ? PopUp() : PushDown();} void Toggle(){ mButtonIsDown ? PopUp() : PushDown();}
void Click(); void Click();
@ -157,6 +162,7 @@ class AButton final : public wxWindow {
bool mIsClicking; bool mIsClicking;
bool mEnabled; bool mEnabled;
bool mUseDisabledAsDownHiliteImage; bool mUseDisabledAsDownHiliteImage;
bool mIsDoubleClicked {};
struct ImageArr { ImageRoll mArr[4]; }; struct ImageArr { ImageRoll mArr[4]; };
std::vector<ImageArr> mImages; std::vector<ImageArr> mImages;

View File

@ -467,6 +467,15 @@ void Meter::OnPaint(wxPaintEvent & WXUNUSED(event))
// MixerTrackCluster style has no icon or L/R labels // MixerTrackCluster style has no icon or L/R labels
if (mStyle != MixerTrackCluster) if (mStyle != MixerTrackCluster)
{ {
bool highlight = InIcon();
if (highlight) {
auto rect = mIconRect;
rect.Inflate(gap, gap);
wxColour colour(247, 247, 247);
dc.SetBrush(colour);
dc.SetPen(colour );
dc.DrawRectangle(rect);
}
dc.DrawBitmap(*mIcon, mIconRect.GetPosition(), true); dc.DrawBitmap(*mIcon, mIconRect.GetPosition(), true);
dc.SetFont(GetFont()); dc.SetFont(GetFont());
dc.DrawText(mLeftText, mLeftTextPos.x, mLeftTextPos.y); dc.DrawText(mLeftText, mLeftTextPos.x, mLeftTextPos.y);
@ -668,8 +677,22 @@ void Meter::OnSize(wxSizeEvent & WXUNUSED(event))
mLayoutValid = false; mLayoutValid = false;
} }
bool Meter::InIcon(wxMouseEvent *pEvent) const
{
auto point = pEvent ? pEvent->GetPosition() : ScreenToClient(::wxGetMousePosition());
return mIconRect.Contains(point);
}
void Meter::OnMouse(wxMouseEvent &evt) void Meter::OnMouse(wxMouseEvent &evt)
{ {
bool shouldHighlight = InIcon(&evt);
if ((evt.GetEventType() == wxEVT_MOTION || evt.Entering() || evt.Leaving()) &&
(mHighlighted != shouldHighlight)) {
mHighlighted = shouldHighlight;
mLayoutValid = false;
Refresh();
}
if (mStyle == MixerTrackCluster) // MixerTrackCluster style has no menu. if (mStyle == MixerTrackCluster) // MixerTrackCluster style has no menu.
return; return;
@ -688,7 +711,7 @@ void Meter::OnMouse(wxMouseEvent &evt)
#endif #endif
if (evt.RightDown() || if (evt.RightDown() ||
(evt.ButtonDown() && mIconRect.Contains(evt.m_x, evt.m_y))) (evt.ButtonDown() && InIcon(&evt)))
{ {
wxMenu menu; wxMenu menu;
// Note: these should be kept in the same order as the enum // Note: these should be kept in the same order as the enum

View File

@ -186,6 +186,7 @@ class Meter final : public wxPanel
void OnErase(wxEraseEvent &evt); void OnErase(wxEraseEvent &evt);
void OnPaint(wxPaintEvent &evt); void OnPaint(wxPaintEvent &evt);
void OnSize(wxSizeEvent &evt); void OnSize(wxSizeEvent &evt);
bool InIcon(wxMouseEvent *pEvent = nullptr) const;
void OnMouse(wxMouseEvent &evt); void OnMouse(wxMouseEvent &evt);
void OnKeyDown(wxKeyEvent &evt); void OnKeyDown(wxKeyEvent &evt);
void OnKeyUp(wxKeyEvent &evt); void OnKeyUp(wxKeyEvent &evt);
@ -280,6 +281,8 @@ class Meter final : public wxPanel
friend class MeterAx; friend class MeterAx;
bool mHighlighted {};
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };

View File

@ -84,6 +84,9 @@ array of Ruler::Label.
#include "../Snap.h" #include "../Snap.h"
#include "../tracks/ui/Scrubbing.h" #include "../tracks/ui/Scrubbing.h"
//#define SCRUB_ABOVE
#define RULER_DOUBLE_CLICK
using std::min; using std::min;
using std::max; using std::max;
@ -1774,6 +1777,9 @@ BEGIN_EVENT_TABLE(AdornedRulerPanel, wxPanel)
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)
END_EVENT_TABLE() END_EVENT_TABLE()
AdornedRulerPanel::AdornedRulerPanel(AudacityProject* parent, AdornedRulerPanel::AdornedRulerPanel(AudacityProject* parent,
@ -1869,7 +1875,7 @@ namespace {
bool ReadScrubEnabledPref() bool ReadScrubEnabledPref()
{ {
bool result {}; bool result {};
gPrefs->Read(scrubEnabledPrefName, &result, true); gPrefs->Read(scrubEnabledPrefName, &result, false);
return result; return result;
} }
@ -1895,11 +1901,56 @@ void AdornedRulerPanel::UpdatePrefs()
// Affected by the last // Affected by the last
UpdateRects(); UpdateRects();
RegenerateTooltips(); RegenerateTooltips(mPrevZone);
mButtonFontSize = -1; mButtonFontSize = -1;
} }
namespace {
enum { ArrowWidth = 8, ArrowSpacing = 1, ArrowHeight = ArrowWidth / 2 };
// Find the part of the button rectangle in which you can click the arrow.
// It includes the lower right corner.
wxRect GetArrowRect(const wxRect &buttonRect)
{
// Change the following lines to change the size of the hot zone.
// Make the hot zone as tall as the button
auto width = std::min(
std::max(1, buttonRect.GetWidth()) - 1,
ArrowWidth + 2 * ArrowSpacing
+ 2 // bevel around arrow
+ 2 // outline around the bevel
);
auto height = buttonRect.GetHeight();
return wxRect {
buttonRect.GetRight() + 1 - width,
buttonRect.GetBottom() + 1 - height,
width, height
};
}
wxRect GetTextRect(const wxRect &buttonRect)
{
auto result = buttonRect;
result.width -= GetArrowRect(buttonRect).width;
return result;
}
// Compensate for off-by-one problem in the bevel-drawing functions
struct Deflator {
Deflator(wxRect &rect) : mRect(rect) {
--mRect.width;
--mRect.height;
}
~Deflator() {
++mRect.width;
++mRect.height;
}
wxRect mRect;
};
}
wxFont &AdornedRulerPanel::GetButtonFont() const wxFont &AdornedRulerPanel::GetButtonFont() const
{ {
if (mButtonFontSize < 0) { if (mButtonFontSize < 0) {
@ -1911,12 +1962,21 @@ wxFont &AdornedRulerPanel::GetButtonFont() const
mButtonFont.SetPointSize(mButtonFontSize); mButtonFont.SetPointSize(mButtonFontSize);
wxCoord width, height; wxCoord width, height;
for (auto button = StatusChoice::FirstButton; done && IsButton(button); ++button) { for (auto button = StatusChoice::FirstButton; done && IsButton(button); ++button) {
auto allowableWidth = GetButtonRect(button).GetWidth() - 2; auto rect = GetTextRect(GetButtonRect(button));
// 2 corresponds with the Inflate(-1, -1) auto availableWidth = rect.GetWidth();
auto availableHeight = rect.GetHeight();
// Deduct for outlines, and room to move text
// I might deduct 2 more for bevel, but that made the text too small.
availableWidth -= 2 + 1;
availableHeight -= 2 + 1;
GetParent()->GetTextExtent( GetParent()->GetTextExtent(
wxGetTranslation(GetPushButtonStrings(button)->label), wxGetTranslation(GetPushButtonStrings(button)->label),
&width, &height, NULL, NULL, &mButtonFont); &width, &height, NULL, NULL, &mButtonFont);
done = width < allowableWidth;
// Yes, < not <= ! Leave at least some room.
done = width < availableWidth && height < availableHeight;
} }
mButtonFontSize--; mButtonFontSize--;
} while (mButtonFontSize > 0 && !done); } while (mButtonFontSize > 0 && !done);
@ -1930,7 +1990,7 @@ void AdornedRulerPanel::InvalidateRuler()
mRuler.Invalidate(); mRuler.Invalidate();
} }
void AdornedRulerPanel::RegenerateTooltips() void AdornedRulerPanel::RegenerateTooltips(StatusChoice choice)
{ {
#if wxUSE_TOOLTIPS #if wxUSE_TOOLTIPS
if (mTimelineToolTip) { if (mTimelineToolTip) {
@ -1938,7 +1998,7 @@ void AdornedRulerPanel::RegenerateTooltips()
this->SetToolTip(_("Timeline actions disabled during recording")); this->SetToolTip(_("Timeline actions disabled during recording"));
} }
else { else {
switch(mPrevZone) { switch(choice) {
case StatusChoice::QuickPlayButton : case StatusChoice::QuickPlayButton :
case StatusChoice::EnteringQP : case StatusChoice::EnteringQP :
if (!mQuickPlayEnabled) { if (!mQuickPlayEnabled) {
@ -1988,12 +2048,15 @@ void AdornedRulerPanel::OnCapture(wxCommandEvent & evt)
// if recording is initiated by a modal window (Timer Record). // if recording is initiated by a modal window (Timer Record).
SetCursor(mCursorDefault); SetCursor(mCursorDefault);
mIsRecording = true; mIsRecording = true;
// The quick play indicator is useless during recording
HideQuickPlayIndicator();
} }
else { else {
SetCursor(mCursorHand); SetCursor(mCursorHand);
mIsRecording = false; mIsRecording = false;
} }
RegenerateTooltips(); RegenerateTooltips(mPrevZone);
} }
enum : int { enum : int {
@ -2051,7 +2114,7 @@ void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
mBack->Create(sz.x, sz.y, dc); mBack->Create(sz.x, sz.y, dc);
mBackDC.SelectObject(*mBack); mBackDC.SelectObject(*mBack);
DoDrawBorder(&mBackDC); DoDrawBackground(&mBackDC);
if (!mViewInfo->selectedRegion.isPoint()) if (!mViewInfo->selectedRegion.isPoint())
{ {
@ -2063,8 +2126,7 @@ void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
if (mIndType >= 0) if (mIndType >= 0)
{ {
const bool scrub = mProject->GetScrubber().HasStartedScrubbing(); const bool scrub = mProject->GetScrubber().HasStartedScrubbing();
auto width = scrub ? IndicatorBigWidth() : IndicatorMediumWidth; DoDrawIndicator(&mBackDC, mIndTime, mIndType != 0, IndicatorMediumWidth, false);
DoDrawIndicator(&mBackDC, mIndTime, mIndType != 0, width, scrub);
} }
if (mViewInfo->selectedRegion.isPoint()) if (mViewInfo->selectedRegion.isPoint())
@ -2076,11 +2138,13 @@ void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
DoDrawPushbuttons(&mBackDC); DoDrawPushbuttons(&mBackDC);
DoDrawEdge(&mBackDC);
dc.Blit(0, 0, mBack->GetWidth(), mBack->GetHeight(), &mBackDC, 0, 0); dc.Blit(0, 0, mBack->GetWidth(), mBack->GetHeight(), &mBackDC, 0, 0);
if (mQuickPlayInd) if (mQuickPlayInd)
{ {
DrawQuickPlayIndicator(&dc); DrawQuickPlayIndicator(&dc, true);
} }
} }
@ -2103,21 +2167,30 @@ void AdornedRulerPanel::UpdateRects()
mInner.x += LeftMargin; mInner.x += LeftMargin;
mInner.width -= (LeftMargin + RightMargin); mInner.width -= (LeftMargin + RightMargin);
wxRect *top = &mInner; auto top = &mInner;
auto bottom = &mInner;
if (mShowScrubbing) { if (mShowScrubbing) {
mScrubZone = mInner; mScrubZone = mInner;
auto scrubHeight = std::min(mScrubZone.height, int(ScrubHeight)); auto scrubHeight = std::min(mScrubZone.height, int(ScrubHeight));
mScrubZone.height = scrubHeight;
mInner.height -= scrubHeight; int topHeight;
mInner.y += scrubHeight; #ifdef SCRUB_ABOVE
top = &mScrubZone; top = &mScrubZone, topHeight = scrubHeight;
#else
auto qpHeight = mScrubZone.height - scrubHeight;
bottom = &mScrubZone, topHeight = qpHeight;
#endif
top->height = topHeight;
bottom->height -= topHeight;
bottom->y += topHeight;
} }
top->y += TopMargin; top->y += TopMargin;
top->height -= TopMargin; top->height -= TopMargin;
mInner.height -= BottomMargin; bottom->height -= BottomMargin;
if (!mShowScrubbing) if (!mShowScrubbing)
mScrubZone = mInner; mScrubZone = mInner;
@ -2166,7 +2239,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
} }
const bool overButtons = GetButtonAreaRect(true).Contains(evt.GetPosition()); const bool overButtons = GetButtonAreaRect(true).Contains(evt.GetPosition());
const StatusChoice button = FindButton(evt.GetPosition()); auto button = FindButton(evt).button;
const bool inScrubZone = !overButtons && const bool inScrubZone = !overButtons &&
// only if scrubbing is allowed now // only if scrubbing is allowed now
mProject->GetScrubber().CanScrub() && mProject->GetScrubber().CanScrub() &&
@ -2179,9 +2252,11 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
? button ? button
: inScrubZone : inScrubZone
? StatusChoice::EnteringScrubZone ? StatusChoice::EnteringScrubZone
: StatusChoice::EnteringQP; : mInner.Contains(evt.GetPosition())
? StatusChoice::EnteringQP
: StatusChoice::NoChange;
const bool changeInZone = (zone != mPrevZone); const bool changeInZone = (zone != mPrevZone);
mPrevZone = zone; const bool changing = evt.Leaving() || evt.Entering() || changeInZone;
wxCoord xx = evt.GetX(); wxCoord xx = evt.GetX();
wxCoord mousePosX = xx; wxCoord mousePosX = xx;
@ -2195,36 +2270,49 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
} }
// Handle status bar messages // Handle status bar messages
UpdateStatusBarAndTooltips ( UpdateStatusBarAndTooltips (changing ? zone : StatusChoice::NoChange);
evt.Leaving() || evt.Entering() || changeInZone
? zone if ((IsButton(zone) || IsButton(mPrevZone)) &&
: StatusChoice::NoChange (changing || evt.Moving() || evt.Dragging()))
); // So that the highlights in pushbuttons can update
Refresh(false);
mPrevZone = zone;
auto &scrubber = mProject->GetScrubber(); auto &scrubber = mProject->GetScrubber();
if (scrubber.HasStartedScrubbing()) { if (scrubber.HasStartedScrubbing()) {
// If already clicked for scrub, preempt the usual event handling, if (zone == StatusChoice::EnteringQP &&
// no matter what the y coordinate. evt.LeftDown()) {
// Stop scrubbing
if (HasCapture())
ReleaseMouse();
mProject->OnStop();
// Continue to quick play event handling
}
else {
// If already clicked for scrub, preempt the usual event handling,
// no matter what the y coordinate.
// Do this hack so scrubber can detect mouse drags anywhere // Do this hack so scrubber can detect mouse drags anywhere
evt.ResumePropagation(wxEVENT_PROPAGATE_MAX); evt.ResumePropagation(wxEVENT_PROPAGATE_MAX);
if (scrubber.IsScrubbing()) if (scrubber.IsScrubbing())
evt.Skip(); evt.Skip();
else if (evt.LeftDClick()) else if (evt.LeftDClick())
// On the second button down, switch the pending scrub to scrolling // On the second button down, switch the pending scrub to scrolling
scrubber.MarkScrubStart(evt.m_x, true, false); scrubber.MarkScrubStart(evt.m_x, true, false);
else else
evt.Skip(); evt.Skip();
mQuickPlayInd = true; mQuickPlayInd = true;
wxClientDC dc(this); wxClientDC dc(this);
DrawQuickPlayIndicator(&dc); DrawQuickPlayIndicator(&dc);
if (HasCapture()) if (HasCapture())
ReleaseMouse(); ReleaseMouse();
return; return;
}
} }
// Store the initial play region state // Store the initial play region state
@ -2260,7 +2348,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
return; return;
} }
if (HasCapture() && mCaptureState != StatusChoice::NoButton) if (HasCapture() && mCaptureState.button != StatusChoice::NoButton)
HandlePushbuttonEvent(evt); HandlePushbuttonEvent(evt);
else if (!HasCapture() && overButtons) else if (!HasCapture() && overButtons)
HandlePushbuttonClick(evt); HandlePushbuttonClick(evt);
@ -2304,7 +2392,15 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
} }
} }
#ifdef RULER_DOUBLE_CLICK
if (evt.LeftDClick()) {
mDoubleClick = true;
HandleQPDoubleClick(evt, mousePosX);
}
else
#endif
if (evt.LeftDown()) { if (evt.LeftDown()) {
mDoubleClick = false;
HandleQPClick(evt, mousePosX); HandleQPClick(evt, mousePosX);
HandleQPDrag(evt, mousePosX); HandleQPDrag(evt, mousePosX);
} }
@ -2319,6 +2415,11 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
} }
} }
void AdornedRulerPanel::HandleQPDoubleClick(wxMouseEvent &evt, wxCoord mousePosX)
{
mProject->GetPlaybackScroller().Activate(true);
}
void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX) void AdornedRulerPanel::HandleQPClick(wxMouseEvent &evt, wxCoord mousePosX)
{ {
// Temporarily unlock locked play region // Temporarily unlock locked play region
@ -2452,12 +2553,15 @@ void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &event, wxCoord mousePosX)
void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt) void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
{ {
if (mDoubleClick)
return;
HideQuickPlayIndicator(); HideQuickPlayIndicator();
if (HasCapture()) if (HasCapture())
ReleaseMouse(); ReleaseMouse();
mCaptureState = StatusChoice::NoButton; mCaptureState = CaptureState{};
if (mPlayRegionEnd < mPlayRegionStart) { if (mPlayRegionEnd < mPlayRegionStart) {
// Swap values to ensure mPlayRegionStart < mPlayRegionEnd // Swap values to ensure mPlayRegionStart < mPlayRegionEnd
@ -2492,6 +2596,28 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
ClearPlayRegion(); ClearPlayRegion();
} }
StartQPPlay(evt.ShiftDown(), evt.ControlDown());
mMouseEventState = mesNone;
mIsDragging = false;
mLeftDownClick = -1;
if (mPlayRegionLock) {
// Restore Locked Play region
SetPlayRegion(mOldPlayRegionStart, mOldPlayRegionEnd);
mProject->OnLockPlayRegion();
// and release local lock
mPlayRegionLock = false;
}
}
void AdornedRulerPanel::StartQPPlay(bool looped, bool cutPreview)
{
const double t0 = mTracks->GetStartTime();
const double t1 = mTracks->GetEndTime();
const double sel0 = mProject->GetSel0();
const double sel1 = mProject->GetSel1();
// Start / Restart playback on left click. // Start / Restart playback on left click.
bool startPlaying = (mPlayRegionStart >= 0); bool startPlaying = (mPlayRegionStart >= 0);
@ -2502,7 +2628,7 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
bool loopEnabled = true; bool loopEnabled = true;
double start, end; double start, end;
if ((mPlayRegionEnd - mPlayRegionStart == 0.0) && evt.ShiftDown()) { if ((mPlayRegionEnd - mPlayRegionStart == 0.0) && looped) {
// Loop play a point will loop either a selection or the project. // Loop play a point will loop either a selection or the project.
if ((mPlayRegionStart > sel0) && (mPlayRegionStart < sel1)) { if ((mPlayRegionStart > sel0) && (mPlayRegionStart < sel1)) {
// we are in a selection, so use the selection // we are in a selection, so use the selection
@ -2522,15 +2648,15 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
loopEnabled = ((end - start) > 0.001)? true : false; loopEnabled = ((end - start) > 0.001)? true : false;
AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions()); AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions());
options.playLooped = (loopEnabled && evt.ShiftDown()); options.playLooped = (loopEnabled && looped);
if (!evt.ControlDown()) if (!cutPreview)
options.pStartTime = &mPlayRegionStart; options.pStartTime = &mPlayRegionStart;
else else
options.timeTrack = NULL; options.timeTrack = NULL;
ControlToolBar::PlayAppearance appearance = ControlToolBar::PlayAppearance appearance =
evt.ControlDown() ? ControlToolBar::PlayAppearance::CutPreview cutPreview ? ControlToolBar::PlayAppearance::CutPreview
: options.playLooped ? ControlToolBar::PlayAppearance::Looped : options.playLooped ? ControlToolBar::PlayAppearance::Looped
: ControlToolBar::PlayAppearance::Straight; : ControlToolBar::PlayAppearance::Straight;
ctb->PlayPlayRegion((SelectedRegion(start, end)), ctb->PlayPlayRegion((SelectedRegion(start, end)),
@ -2543,18 +2669,6 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
mPlayRegionEnd = end; mPlayRegionEnd = end;
Refresh(); Refresh();
} }
mMouseEventState = mesNone;
mIsDragging = false;
mLeftDownClick = -1;
if (mPlayRegionLock) {
// Restore Locked Play region
SetPlayRegion(mOldPlayRegionStart, mOldPlayRegionEnd);
mProject->OnLockPlayRegion();
// and release local lock
mPlayRegionLock = false;
}
} }
void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice) void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice)
@ -2567,8 +2681,7 @@ void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice)
if (IsButton(choice)) { if (IsButton(choice)) {
bool state = GetButtonState(choice); bool state = GetButtonState(choice);
const auto &strings = *GetPushButtonStrings(choice); const auto &strings = *GetPushButtonStrings(choice);
message += wxGetTranslation(state ? strings.disable : strings.enable); message = wxGetTranslation(state ? strings.disable : strings.enable);
message += wxT(" ") + _("(Right-Click for options)");
} }
else { else {
const auto &scrubber = mProject->GetScrubber(); const auto &scrubber = mProject->GetScrubber();
@ -2603,10 +2716,10 @@ void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice)
// Display a message, or empty message // Display a message, or empty message
mProject->TP_DisplayStatusMessage(message); mProject->TP_DisplayStatusMessage(message);
RegenerateTooltips(); RegenerateTooltips(choice);
} }
void AdornedRulerPanel::OnToggleScrubbing() void AdornedRulerPanel::OnToggleScrubbing(wxCommandEvent&)
{ {
mShowScrubbing = !mShowScrubbing; mShowScrubbing = !mShowScrubbing;
WriteScrubEnabledPref(mShowScrubbing); WriteScrubEnabledPref(mShowScrubbing);
@ -2686,6 +2799,15 @@ void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
auto cleanup = finally([this]{ PopEventHandler(); }); auto cleanup = finally([this]{ PopEventHandler(); });
wxMenu rulerMenu; wxMenu rulerMenu;
auto label = wxGetTranslation(
AdornedRulerPanel::PushbuttonLabels
[static_cast<int>(StatusChoice::ScrubBarButton)].label);
rulerMenu.AppendCheckItem(OnShowHideScrubbingID, _("Scrub Bar"));
if(GetButtonState(StatusChoice::ScrubBarButton))
rulerMenu.FindItem(OnShowHideScrubbingID)->Check();
rulerMenu.AppendSeparator();
mProject->GetScrubber().PopulateMenu(rulerMenu); mProject->GetScrubber().PopulateMenu(rulerMenu);
PopupMenu(&rulerMenu, pos); PopupMenu(&rulerMenu, pos);
} }
@ -2695,7 +2817,7 @@ void AdornedRulerPanel::OnToggleQuickPlay(wxCommandEvent&)
mQuickPlayEnabled = (mQuickPlayEnabled)? false : true; mQuickPlayEnabled = (mQuickPlayEnabled)? false : true;
gPrefs->Write(wxT("/QuickPlay/QuickPlayEnabled"), mQuickPlayEnabled); gPrefs->Write(wxT("/QuickPlay/QuickPlayEnabled"), mQuickPlayEnabled);
gPrefs->Flush(); gPrefs->Flush();
RegenerateTooltips(); RegenerateTooltips(mPrevZone);
} }
void AdornedRulerPanel::OnSyncSelToQuickPlay(wxCommandEvent&) void AdornedRulerPanel::OnSyncSelToQuickPlay(wxCommandEvent&)
@ -2736,7 +2858,7 @@ void AdornedRulerPanel::OnTimelineToolTips(wxCommandEvent&)
gPrefs->Write(wxT("/QuickPlay/ToolTips"), mTimelineToolTip); gPrefs->Write(wxT("/QuickPlay/ToolTips"), mTimelineToolTip);
gPrefs->Flush(); gPrefs->Flush();
#if wxUSE_TOOLTIPS #if wxUSE_TOOLTIPS
RegenerateTooltips(); RegenerateTooltips(mPrevZone);
#endif #endif
} }
@ -2860,19 +2982,40 @@ wxRect AdornedRulerPanel::GetButtonRect( StatusChoice button ) const
return rect; return rect;
} }
bool AdornedRulerPanel::InButtonRect( StatusChoice button ) const auto AdornedRulerPanel::InButtonRect( StatusChoice button, wxMouseEvent *pEvent ) const
-> PointerState
{ {
return GetButtonRect(button).Contains(ScreenToClient(::wxGetMousePosition())); auto rect = GetButtonRect(button);
auto state = pEvent ? *pEvent : ::wxGetMouseState();
auto point = pEvent ? pEvent->GetPosition() : ScreenToClient(state.GetPosition());
if(!rect.Contains(point))
return PointerState::Out;
else {
auto rightDown = state.RightIsDown()
#ifdef __WXMAC__
// make drag with Mac Control down act like right drag
|| (state.RawControlDown() && state.ButtonIsDown(wxMOUSE_BTN_ANY))
#endif
;
if(rightDown ||
(pEvent && pEvent->RightUp()) ||
GetArrowRect(rect).Contains(point))
return PointerState::InArrow;
else
return PointerState::In;
}
} }
auto AdornedRulerPanel::FindButton( wxPoint position ) const -> StatusChoice auto AdornedRulerPanel::FindButton( wxMouseEvent &mouseEvent ) const
-> CaptureState
{ {
for (auto button = StatusChoice::FirstButton; IsButton(button); ++button) { for (auto button = StatusChoice::FirstButton; IsButton(button); ++button) {
if(GetButtonRect(button).Contains(position)) auto state = InButtonRect( button, &mouseEvent );
return button; if (state != PointerState::Out)
return CaptureState{ button, state };
} }
return StatusChoice::NoButton; return { StatusChoice::NoButton, PointerState::Out };
} }
bool AdornedRulerPanel::GetButtonState( StatusChoice button ) const bool AdornedRulerPanel::GetButtonState( StatusChoice button ) const
@ -2890,30 +3033,33 @@ bool AdornedRulerPanel::GetButtonState( StatusChoice button ) const
void AdornedRulerPanel::ToggleButtonState( StatusChoice button ) void AdornedRulerPanel::ToggleButtonState( StatusChoice button )
{ {
wxCommandEvent dummy;
switch(button) { switch(button) {
case StatusChoice::QuickPlayButton: { case StatusChoice::QuickPlayButton:
wxCommandEvent dummy;
OnToggleQuickPlay(dummy); OnToggleQuickPlay(dummy);
}
break; break;
case StatusChoice::ScrubBarButton: case StatusChoice::ScrubBarButton:
OnToggleScrubbing(); OnToggleScrubbing(dummy);
break; break;
default: default:
wxASSERT(false); wxASSERT(false);
} }
UpdateStatusBarAndTooltips(mCaptureState.button);
} }
void AdornedRulerPanel::ShowButtonMenu( StatusChoice button, wxPoint position) void AdornedRulerPanel::ShowButtonMenu( StatusChoice button, wxPoint position)
{ {
switch (button) { switch (button) {
case StatusChoice::QuickPlayButton: case StatusChoice::QuickPlayButton:
return ShowMenu(position); ShowMenu(position); break;
case StatusChoice::ScrubBarButton: case StatusChoice::ScrubBarButton:
return ShowScrubMenu(position); ShowScrubMenu(position); break;
default: default:
return; return;
} }
// dismiss and clear Quick-Play indicator
HideQuickPlayIndicator();
} }
const AdornedRulerPanel::ButtonStrings AdornedRulerPanel::PushbuttonLabels const AdornedRulerPanel::ButtonStrings AdornedRulerPanel::PushbuttonLabels
@ -2924,48 +3070,105 @@ const AdornedRulerPanel::ButtonStrings AdornedRulerPanel::PushbuttonLabels
{ XO("Scrub Bar"), XO("Show Scrub Bar"), XO("Hide Scrub Bar") }, { XO("Scrub Bar"), XO("Show Scrub Bar"), XO("Hide Scrub Bar") },
}; };
void AdornedRulerPanel::DoDrawPushbutton(wxDC *dc, StatusChoice button, bool down) const namespace {
void DrawButtonBackground(wxDC *dc, const wxRect &rect, bool down, bool highlight) {
// Choose the pen
if (highlight)
AColor::Light(dc, false);
else
// This color choice corresponds to part of TrackInfo::DrawBordersWithin() :
AColor::Dark(dc, false);
auto pen = dc->GetPen();
// pen.SetWidth(2);
// Choose the brush
if (down)
AColor::Solo(dc, true, false);
else
AColor::MediumTrackInfo(dc, false);
dc->SetPen(pen);
dc->DrawRectangle(rect);
// Draw the bevel
auto rect2 = rect.Deflate(1, 1);
Deflator def(rect2);
AColor::BevelTrackInfo(*dc, !down, rect2);
}
}
void AdornedRulerPanel::DoDrawPushbutton
(wxDC *dc, StatusChoice button, PointerState down, PointerState pointerState) const
{ {
// Adapted from TrackInfo::DrawMuteSolo() // Adapted from TrackInfo::DrawMuteSolo()
ADCChanger changer(dc);
auto bev = GetButtonRect( button ); const auto rect = GetButtonRect( button );
const auto arrowRect = GetArrowRect(rect);
auto arrowBev = arrowRect.Deflate(1, 1);
const auto textRect = GetTextRect(rect);
auto textBev = textRect.Deflate(1, 1);
// This part corresponds to part of TrackInfo::DrawBordersWithin() : // Draw borders, bevels, and backgrounds of the split sections
AColor::Dark(dc, false);
dc->DrawRectangle(bev);
bev.Inflate(-1, -1); if (pointerState == PointerState::InArrow) {
if (down) // Draw highlighted arrow after
AColor::Solo(dc, true, false); DrawButtonBackground(dc, textRect, (down == PointerState::In), false);
else DrawButtonBackground(dc, arrowRect, (down == PointerState::InArrow), true);
AColor::MediumTrackInfo(dc, false); }
dc->SetPen( *wxTRANSPARENT_PEN );//No border! else {
dc->DrawRectangle(bev); // Draw maybe highlighted text after
DrawButtonBackground(dc, arrowRect, (down == PointerState::InArrow), false);
DrawButtonBackground(
dc, textRect, (down == PointerState::In), (pointerState == PointerState::In));
}
dc->SetTextForeground(theTheme.Colour(clrTrackPanelText)); // Draw the menu triangle
{
auto x = arrowBev.GetX() + ArrowSpacing;
auto y = arrowBev.GetY() + (arrowBev.GetHeight() - ArrowHeight) / 2;
wxCoord textWidth, textHeight; // Color it as in TrackInfo::DrawTitleBar
wxString str = wxGetTranslation(GetPushButtonStrings(button)->label); #ifdef EXPERIMENTAL_THEMING
dc->SetFont(GetButtonFont()); wxColour c = theTheme.Colour( clrTrackPanelText );
dc->GetTextExtent(str, &textWidth, &textHeight); #else
dc->DrawText(str, bev.x + (bev.width - textWidth) / 2, wxColour c = *wxBLACK;
bev.y + (bev.height - textHeight) / 2); #endif
AColor::BevelTrackInfo(*dc, !down, bev); if (pointerState == PointerState::InArrow)
dc->SetBrush( wxBrush{ c } );
else
dc->SetBrush( wxBrush{ *wxTRANSPARENT_BRUSH } ); // Make outlined arrow only
dc->SetPen( wxPen{ c } );
// This function draws an arrow half as tall as wide:
AColor::Arrow(*dc, x, y, ArrowWidth);
}
// Draw the text
{
dc->SetTextForeground(theTheme.Colour(clrTrackPanelText));
wxCoord textWidth, textHeight;
wxString str = wxGetTranslation(GetPushButtonStrings(button)->label);
dc->SetFont(GetButtonFont());
dc->GetTextExtent(str, &textWidth, &textHeight);
auto xx = textBev.x + (textBev.width - textWidth) / 2;
auto yy = textBev.y + (textBev.height - textHeight) / 2;
if (down == PointerState::In)
// Shift the text a bit for "down" appearance
++xx, ++yy;
dc->DrawText(str, xx, yy);
}
} }
void AdornedRulerPanel::HandlePushbuttonClick(wxMouseEvent &evt) void AdornedRulerPanel::HandlePushbuttonClick(wxMouseEvent &evt)
{ {
auto button = FindButton(evt.GetPosition()); auto pair = FindButton(evt);
if (IsButton(button)) { auto button = pair.button;
if (evt.LeftDown()) { if (IsButton(button) && evt.ButtonDown()) {
CaptureMouse(); CaptureMouse();
mCaptureState = button; mCaptureState = pair;
Refresh(); Refresh();
}
else if (evt.RightDown()) {
auto rect = GetButtonRect(button);
ShowButtonMenu( button, wxPoint{ rect.GetX() + 1, rect.GetBottom() + 1 } );
}
} }
} }
@ -2974,11 +3177,21 @@ void AdornedRulerPanel::HandlePushbuttonEvent(wxMouseEvent &evt)
if(evt.ButtonUp()) { if(evt.ButtonUp()) {
if(HasCapture()) if(HasCapture())
ReleaseMouse(); ReleaseMouse();
if(InButtonRect(mCaptureState)) {
ToggleButtonState(mCaptureState); auto button = mCaptureState.button;
UpdateStatusBarAndTooltips(mCaptureState); auto capturedIn = mCaptureState.state;
auto in = InButtonRect(button, &evt);
if (in != capturedIn)
;
else if (in == PointerState::In)
ToggleButtonState(button);
else {
auto rect = GetArrowRect(GetButtonRect(button));
wxPoint point { rect.GetLeft() + 1, rect.GetBottom() + 1 };
ShowButtonMenu(button, point);
} }
mCaptureState = StatusChoice::NoButton;
mCaptureState = CaptureState{};
} }
Refresh(); Refresh();
@ -2988,18 +3201,37 @@ void AdornedRulerPanel::DoDrawPushbuttons(wxDC *dc) const
{ {
// Paint the area behind the buttons // Paint the area behind the buttons
wxRect background = GetButtonAreaRect(); wxRect background = GetButtonAreaRect();
#ifndef SCRUB_ABOVE
// Reduce the height
background.y = mInner.y;
background.height = mInner.height;
#endif
AColor::MediumTrackInfo(dc, false); AColor::MediumTrackInfo(dc, false);
dc->DrawRectangle(background); dc->DrawRectangle(background);
for (auto button = StatusChoice::FirstButton; IsButton(button); ++button) { for (auto button = StatusChoice::FirstButton; IsButton(button); ++button) {
auto state = GetButtonState(button); bool state = GetButtonState(button);
auto toggle = (button == mCaptureState && InButtonRect(button)); auto in = InButtonRect(button, nullptr);
auto down = (state != toggle); auto down = PointerState::Out;
DoDrawPushbutton(dc, button, down); if (button == mCaptureState.button && in == mCaptureState.state) {
if (in == PointerState::In) {
// Toggle button's apparent state for mouseover
down = state ? PointerState::Out : in;
}
else if (in == PointerState::InArrow) {
// Menu arrow is not sticky
down = in;
}
}
else if (state)
down = PointerState::In;
DoDrawPushbutton(dc, button, down, in);
} }
} }
void AdornedRulerPanel::DoDrawBorder(wxDC * dc) void AdornedRulerPanel::DoDrawBackground(wxDC * dc)
{ {
// Draw AdornedRulerPanel border // Draw AdornedRulerPanel border
AColor::MediumTrackInfo( dc, false ); AColor::MediumTrackInfo( dc, false );
@ -3012,6 +3244,10 @@ void AdornedRulerPanel::DoDrawBorder(wxDC * dc)
dc->DrawRectangle(mScrubZone); dc->DrawRectangle(mScrubZone);
} }
}
void AdornedRulerPanel::DoDrawEdge(wxDC *dc)
{
wxRect r = mOuter; wxRect r = mOuter;
r.width -= RightMargin; r.width -= RightMargin;
r.height -= BottomMargin; r.height -= BottomMargin;
@ -3112,6 +3348,7 @@ void AdornedRulerPanel::DrawIndicator( double time, bool rec )
void AdornedRulerPanel::DoDrawIndicator void AdornedRulerPanel::DoDrawIndicator
(wxDC * dc, double time, bool playing, int width, bool scrub) (wxDC * dc, double time, bool playing, int width, bool scrub)
{ {
ADCChanger changer(dc); // Undo pen and brush changes at function exit
const int x = Time2Pos(time); const int x = Time2Pos(time);
AColor::IndicatorColor( dc, playing ); AColor::IndicatorColor( dc, playing );
@ -3122,12 +3359,15 @@ void AdornedRulerPanel::DoDrawIndicator
const int IndicatorHalfWidth = width / 2; const int IndicatorHalfWidth = width / 2;
// Double headed, left-right // Double headed, left-right
auto yy = mShowScrubbing
? mScrubZone.y
: (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
tri[ 0 ].x = x - IndicatorOffset; tri[ 0 ].x = x - IndicatorOffset;
tri[ 0 ].y = mScrubZone.y; tri[ 0 ].y = yy;
tri[ 1 ].x = x - IndicatorOffset; tri[ 1 ].x = x - IndicatorOffset;
tri[ 1 ].y = mScrubZone.y + height; tri[ 1 ].y = yy + height;
tri[ 2 ].x = x - IndicatorHalfWidth; tri[ 2 ].x = x - IndicatorHalfWidth;
tri[ 2 ].y = mScrubZone.y + height / 2; tri[ 2 ].y = yy + height / 2;
dc->DrawPolygon( 3, tri ); dc->DrawPolygon( 3, tri );
tri[ 0 ].x = tri[ 1 ].x = x + IndicatorOffset; tri[ 0 ].x = tri[ 1 ].x = x + IndicatorOffset;
tri[ 2 ].x = x + IndicatorHalfWidth; tri[ 2 ].x = x + IndicatorHalfWidth;
@ -3138,11 +3378,11 @@ void AdornedRulerPanel::DoDrawIndicator
auto height = IndicatorHeightForWidth(width); auto height = IndicatorHeightForWidth(width);
const int IndicatorHalfWidth = width / 2; const int IndicatorHalfWidth = width / 2;
tri[ 0 ].x = x - IndicatorHalfWidth; tri[ 0 ].x = x - IndicatorHalfWidth;
tri[ 0 ].y = mScrubZone.y; tri[ 0 ].y = mInner.y;
tri[ 1 ].x = x + IndicatorHalfWidth; tri[ 1 ].x = x + IndicatorHalfWidth;
tri[ 1 ].y = mScrubZone.y; tri[ 1 ].y = mInner.y;
tri[ 2 ].x = x; tri[ 2 ].x = x;
tri[ 2 ].y = mScrubZone.y + height; tri[ 2 ].y = mInner.y + height;
dc->DrawPolygon( 3, tri ); dc->DrawPolygon( 3, tri );
} }
} }
@ -3159,13 +3399,13 @@ void AdornedRulerPanel::DoEraseIndicator(wxDC *dc, int x)
// Restore the background, but make it a little oversized to make // Restore the background, but make it a little oversized to make
// it happy OSX. // it happy OSX.
dc->Blit(x - indsize - 1, auto xx = x - indsize - 1;
mScrubZone.y - 1, auto yy = 0;
dc->Blit(xx, yy,
indsize * 2 + 1 + 2, indsize * 2 + 1 + 2,
mScrubZone.y + height + 2, GetSize().GetHeight(),
&mBackDC, &mBackDC,
x - indsize - 1, xx, yy);
0);
} }
} }
@ -3180,7 +3420,7 @@ QuickPlayIndicatorOverlay *AdornedRulerPanel::GetOverlay()
} }
// Draws the vertical line and green triangle indicating the Quick Play cursor position. // Draws the vertical line and green triangle indicating the Quick Play cursor position.
void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc) void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc, bool repainting)
{ {
double latestEnd = std::max(mTracks->GetEndTime(), mProject->GetSel1()); double latestEnd = std::max(mTracks->GetEndTime(), mProject->GetSel1());
if (dc == NULL || (mQuickPlayPos >= latestEnd)) { if (dc == NULL || (mQuickPlayPos >= latestEnd)) {
@ -3195,7 +3435,9 @@ void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc)
!mProject->GetScrubber().IsScrubbing(); !mProject->GetScrubber().IsScrubbing();
GetOverlay()->Update(x, mIsSnapped, previewScrub); GetOverlay()->Update(x, mIsSnapped, previewScrub);
DoEraseIndicator(dc, mLastQuickPlayX); if (!repainting)
DoEraseIndicator(dc, mLastQuickPlayX);
mLastQuickPlayX = x; mLastQuickPlayX = x;
auto scrub = mPrevZone == StatusChoice::EnteringScrubZone || auto scrub = mPrevZone == StatusChoice::EnteringScrubZone ||

View File

@ -312,20 +312,6 @@ public:
void InvalidateRuler(); void InvalidateRuler();
void UpdatePrefs(); void UpdatePrefs();
void RegenerateTooltips();
void HideQuickPlayIndicator();
void UpdateQuickPlayPos(wxCoord &mousPosX);
private:
void OnCapture(wxCommandEvent & evt);
void OnPaint(wxPaintEvent &evt);
void OnSize(wxSizeEvent &evt);
void UpdateRects();
void OnMouseEvents(wxMouseEvent &evt);
void HandleQPClick(wxMouseEvent &event, wxCoord mousePosX);
void HandleQPDrag(wxMouseEvent &event, wxCoord mousePosX);
void HandleQPRelease(wxMouseEvent &event);
enum class StatusChoice { enum class StatusChoice {
FirstButton = 0, FirstButton = 0,
@ -340,10 +326,38 @@ private:
Leaving, Leaving,
NoChange NoChange
}; };
enum class PointerState {
Out = 0, In, InArrow
};
struct CaptureState {
CaptureState() {}
CaptureState(StatusChoice s, PointerState p) : button(s), state(p) {}
StatusChoice button { StatusChoice::NoButton };
PointerState state { PointerState::Out };
};
friend inline StatusChoice &operator++ (StatusChoice &choice) { friend inline StatusChoice &operator++ (StatusChoice &choice) {
choice = static_cast<StatusChoice>(1 + static_cast<int>(choice)); choice = static_cast<StatusChoice>(1 + static_cast<int>(choice));
return choice; return choice;
} }
void RegenerateTooltips(StatusChoice choice);
void HideQuickPlayIndicator();
void UpdateQuickPlayPos(wxCoord &mousPosX);
private:
void OnCapture(wxCommandEvent & evt);
void OnPaint(wxPaintEvent &evt);
void OnSize(wxSizeEvent &evt);
void UpdateRects();
void OnMouseEvents(wxMouseEvent &evt);
void HandleQPDoubleClick(wxMouseEvent &event, wxCoord mousePosX);
void HandleQPClick(wxMouseEvent &event, wxCoord mousePosX);
void HandleQPDrag(wxMouseEvent &event, wxCoord mousePosX);
void HandleQPRelease(wxMouseEvent &event);
void StartQPPlay(bool looped, bool cutPreview);
static inline bool IsButton(StatusChoice choice) static inline bool IsButton(StatusChoice choice)
{ {
auto integer = static_cast<int>(choice); auto integer = static_cast<int>(choice);
@ -357,14 +371,15 @@ private:
void OnCaptureLost(wxMouseCaptureLostEvent &evt); void OnCaptureLost(wxMouseCaptureLostEvent &evt);
void DoDrawBorder(wxDC * dc); void DoDrawBackground(wxDC * dc);
void DoDrawEdge(wxDC *dc);
void DoDrawMarks(wxDC * dc, bool /*text */ ); void DoDrawMarks(wxDC * dc, bool /*text */ );
void DoDrawCursor(wxDC * dc); void DoDrawCursor(wxDC * dc);
void DoDrawSelection(wxDC * dc); void DoDrawSelection(wxDC * dc);
void DoDrawIndicator(wxDC * dc, double time, bool playing, int width, bool scrub); void DoDrawIndicator(wxDC * dc, double time, bool playing, int width, bool scrub);
void DoEraseIndicator(wxDC *dc, int x); void DoEraseIndicator(wxDC *dc, int x);
QuickPlayIndicatorOverlay *GetOverlay(); QuickPlayIndicatorOverlay *GetOverlay();
void DrawQuickPlayIndicator(wxDC * dc /*NULL to DELETE old only*/); void DrawQuickPlayIndicator(wxDC * dc /*NULL to DELETE old only*/, bool repainting = false);
void DoDrawPlayRegion(wxDC * dc); void DoDrawPlayRegion(wxDC * dc);
wxRect GetButtonAreaRect(bool includeBorder = false) const; wxRect GetButtonAreaRect(bool includeBorder = false) const;
@ -381,12 +396,13 @@ private:
} }
wxRect GetButtonRect( StatusChoice button ) const; wxRect GetButtonRect( StatusChoice button ) const;
bool InButtonRect( StatusChoice button ) const; PointerState InButtonRect( StatusChoice button, wxMouseEvent *pEvent ) const;
StatusChoice FindButton( wxPoint position ) const; CaptureState FindButton( wxMouseEvent &mouseEvent ) const;
bool GetButtonState( StatusChoice button ) const; bool GetButtonState( StatusChoice button ) const;
void ToggleButtonState( StatusChoice button ); void ToggleButtonState( StatusChoice button );
void ShowButtonMenu( StatusChoice button, wxPoint position); void ShowButtonMenu( StatusChoice button, wxPoint position);
void DoDrawPushbutton(wxDC *dc, StatusChoice button, bool down) const; void DoDrawPushbutton(wxDC *dc, StatusChoice button, PointerState down,
PointerState pointerState) const;
void DoDrawPushbuttons(wxDC *dc) const; void DoDrawPushbuttons(wxDC *dc) const;
void HandlePushbuttonClick(wxMouseEvent &evt); void HandlePushbuttonClick(wxMouseEvent &evt);
void HandlePushbuttonEvent(wxMouseEvent &evt); void HandlePushbuttonEvent(wxMouseEvent &evt);
@ -453,14 +469,13 @@ private:
void OnAutoScroll(wxCommandEvent &evt); void OnAutoScroll(wxCommandEvent &evt);
void OnLockPlayRegion(wxCommandEvent &evt); void OnLockPlayRegion(wxCommandEvent &evt);
void OnToggleScrubbing(); void OnToggleScrubbing(wxCommandEvent&);
bool mPlayRegionDragsSelection; bool mPlayRegionDragsSelection;
bool mTimelineToolTip; bool mTimelineToolTip;
bool mQuickPlayEnabled; bool mQuickPlayEnabled;
CaptureState mCaptureState {};
StatusChoice mCaptureState { StatusChoice::NoButton };
enum MouseEventState { enum MouseEventState {
mesNone, mesNone,
@ -483,6 +498,8 @@ private:
mutable int mButtonFontSize { -1 }; mutable int mButtonFontSize { -1 };
mutable wxFont mButtonFont; mutable wxFont mButtonFont;
bool mDoubleClick {};
DECLARE_EVENT_TABLE() DECLARE_EVENT_TABLE()
}; };