1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-24 08:10: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/dc.h>
#include <wx/settings.h>
#include <wx/utils.h>
#include "AColor.h"
#include "Theme.h"
#include "Experimental.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;
wxBrush AColor::lightBrush[2];
wxBrush AColor::mediumBrush[2];

View File

@ -14,12 +14,39 @@
#ifndef __AUDACITY_COLOR__
#define __AUDACITY_COLOR__
#include "MemoryX.h"
#include <wx/brush.h>
#include <wx/pen.h>
class wxDC;
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 {
public:

View File

@ -61,7 +61,7 @@ void AboutDialog::CreateCreditsList()
AddCredit(wxString(wxT("Greg Kozikowski, ")) + _("documentation and support"), roleTeamMember);
AddCredit(wxString(wxT("Paul Licameli, ")) + _("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("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
// whether or not items in menus are enabled or disabled.
enum
enum CommandFlag : unsigned long long
{
AlwaysEnabledFlag = 0x00000000,
@ -93,11 +93,89 @@ enum
CaptureNotBusyFlag = 0x20000000,
CanStopAudioStreamFlag = 0x40000000,
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 AudacityApp final : public wxApp {

View File

@ -2258,7 +2258,9 @@ void AudioIO::StopStream()
while( mAudioThreadShouldCallFillBuffersOnce == true )
{
// 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 );
}

View File

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

View File

@ -24,17 +24,21 @@
private:
void CreateMenusAndCommands();
void PopulateEffectsMenu(CommandManager *c, EffectType type, int batchflags, int realflags);
void AddEffectMenuItems(CommandManager *c, EffectPlugs & plugs, int batchflags, int realflags, bool isDefault);
void AddEffectMenuItemGroup(CommandManager *c, const wxArrayString & names, const PluginIDList & plugs, const wxArrayInt & flags, bool isDefault);
void PopulateEffectsMenu(CommandManager *c, EffectType type,
CommandFlag batchflags, CommandFlag realflags);
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 ModifyUndoMenuItems();
void ModifyToolbarMenus();
// Calls ModifyToolbarMenus() on all projects
void ModifyAllProjectToolbarMenus();
int GetFocusedFrame();
wxUint32 GetUpdateFlags();
CommandFlag GetFocusedFrame();
CommandFlag GetUpdateFlags();
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:
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)
EVT_SET_FOCUS(ScrollBar::OnSetFocus)
END_EVENT_TABLE()
@ -933,14 +954,18 @@ AudacityProject::AudacityProject(wxWindow * parent, wxWindowID id,
mCursorOverlay = std::make_unique<EditCursorOverlay>(this);
#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);
mScrubber = std::make_unique<Scrubber>(this);
#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
// earlier)
this->Connect(EVT_TRACK_PANEL_TIMER,
@ -1771,7 +1796,6 @@ void AudacityProject::FixScrollbars()
mHsbar->SetScrollbar(scaledSbarH + offset, scaledSbarScreen, scaledSbarTotal,
scaledSbarScreen, TRUE);
mHsbar->Refresh();
}
// Vertical scrollbar
@ -1779,7 +1803,6 @@ void AudacityProject::FixScrollbars()
panelHeight / mViewInfo.scrollStep,
totalHeight / mViewInfo.scrollStep,
panelHeight / mViewInfo.scrollStep, TRUE);
mVsbar->Refresh();
if (refresh || (rescroll &&
(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.
/// If not, then try some recovery action to make it so.
/// @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;
if( flags == 0 )
if( !flags )
flags = GetUpdateFlags();
bAllowed = ((flags & mask) == (flagsRqd & mask));
@ -2065,12 +2089,12 @@ bool AudacityProject::TryToMakeActionAllowed( wxUint32 & flags, wxUint32 flagsRq
if( !mSelectAllOnNone )
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( (flags & WaveTracksExistFlag) == 0 )
if( !(flags & WaveTracksExistFlag) )
return false;
if( (MissingFlags & ~( TimeSelectedFlag | WaveTracksSelectedFlag))!=0)
if( (MissingFlags & ~( TimeSelectedFlag | WaveTracksSelectedFlag)) )
return false;
OnSelectAll();
@ -2084,7 +2108,7 @@ void AudacityProject::OnMenu(wxCommandEvent & event)
bool handled = mCommandManager.HandleMenuID(event.GetId(),
GetUpdateFlags(),
0xFFFFFFFF);
NoFlagsSpecifed);
if (handled)
event.Skip(false);
@ -5315,3 +5339,46 @@ int AudacityProject::GetEstimatedRecordingMinsLeftOnDisk() {
int iRecMins = (int)(dRecTime / 60.0);
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;
// 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
static void AllProjectsDeleteLock();
@ -580,7 +581,7 @@ public:
CommandManager mCommandManager;
wxUint32 mLastFlags;
CommandFlag mLastFlags;
// Window elements
@ -720,6 +721,28 @@ public:
const Scrubber &GetScrubber() const { return *mScrubber; }
#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()
};

File diff suppressed because it is too large Load Diff

View File

@ -1015,6 +1015,25 @@ void TrackPanel::ScrollDuringDrag()
mAutoScrolling = true;
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) {
// AS: To keep the selection working properly as we scroll,
@ -5901,16 +5920,26 @@ void TrackPanel::OnMouseEvent(wxMouseEvent & event)
ReleaseMouse();
}
if (event.Leaving() && !event.ButtonIsDown(wxMOUSE_BTN_ANY))
if (event.Leaving())
{
// PRL: was this test really needed? It interfered with my refactoring
// that tried to eliminate those enum values.
// I think it was never true, that mouse capture was pan or gain sliding,
// but no mouse button was down.
// 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);
#if defined(__WXMAC__)
// We must install the cursor ourselves since the window under
// the mouse is no longer this one and wx2.8.12 makes that check.
// Should re-evaluate with wx3.

View File

@ -395,8 +395,8 @@ CommandManager::CommandManager():
mCurrentID(17000),
mCurrentMenuName(COMMAND),
mCurrentMenu(NULL),
mDefaultFlags(0),
mDefaultMask(0)
mDefaultFlags(AlwaysEnabledFlag),
mDefaultMask(AlwaysEnabledFlag)
{
mbSeparatorAllowed = false;
}
@ -647,15 +647,15 @@ void CommandManager::AddCheck(const wxChar *name,
const CommandFunctorPointer &callback,
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,
const wxChar *label,
const CommandFunctorPointer &callback,
int checkmark,
unsigned int flags,
unsigned int mask)
CommandFlag flags,
CommandMask mask)
{
AddItem(name, label, callback, wxT(""), flags, mask, checkmark);
}
@ -663,8 +663,8 @@ void CommandManager::AddCheck(const wxChar *name,
void CommandManager::AddItem(const wxChar *name,
const wxChar *label,
const CommandFunctorPointer &callback,
unsigned int flags,
unsigned int mask)
CommandFlag flags,
CommandMask mask)
{
AddItem(name, label, callback, wxT(""), flags, mask);
}
@ -673,8 +673,8 @@ void CommandManager::AddItem(const wxChar *name,
const wxChar *label_in,
const CommandFunctorPointer &callback,
const wxChar *accel,
unsigned int flags,
unsigned int mask,
CommandFlag flags,
CommandMask mask,
int checkmark)
{
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,
const wxChar *label,
const CommandFunctorPointer &callback,
unsigned int flags,
unsigned int mask)
CommandFlag flags,
CommandMask mask)
{
AddCommand(name, label, callback, wxT(""), flags, mask);
}
@ -736,8 +736,8 @@ void CommandManager::AddCommand(const wxChar *name,
const wxChar *label_in,
const CommandFunctorPointer &callback,
const wxChar *accel,
unsigned int flags,
unsigned int mask)
CommandFlag flags,
CommandMask mask)
{
NewIdentifier(name, label_in, accel, NULL, callback, false, 0, 0);
@ -755,8 +755,8 @@ void CommandManager::AddGlobalCommand(const wxChar *name,
entry->enabled = false;
entry->isGlobal = true;
entry->flags = 0;
entry->mask = 0;
entry->flags = AlwaysEnabledFlag;
entry->mask = AlwaysEnabledFlag;
}
void CommandManager::AddSeparator()
@ -978,13 +978,13 @@ void CommandManager::Enable(const wxString &name, bool enabled)
Enable(entry, enabled);
}
void CommandManager::EnableUsingFlags(wxUint32 flags, wxUint32 mask)
void CommandManager::EnableUsingFlags(CommandFlag flags, CommandMask mask)
{
for(const auto &entry : mCommandList) {
if (entry->multi && entry->index != 0)
continue;
wxUint32 combinedMask = (mask & entry->mask);
auto combinedMask = (mask & entry->mask);
if (combinedMask) {
bool enable = ((flags & combinedMask) ==
(entry->flags & combinedMask));
@ -1038,15 +1038,15 @@ void CommandManager::SetKeyFromIndex(int i, const wxString &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
// 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.");
wxUint32 missingFlags = flagsRequired & (~flagsGot );
auto missingFlags = flagsRequired & (~flagsGot );
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 )
reason = _("You must first select some stereo audio for this\n to use. (You cannot use this with mono.)");
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.
// LL: Why do they need to be disabled???
entry->enabled = true;
bool ret = HandleCommandEntry(entry, 0xffffffff, 0xffffffff, &evt);
bool ret = HandleCommandEntry(entry, NoFlagsSpecifed, NoFlagsSpecifed, &evt);
entry->enabled = false;
return ret;
}
@ -1094,7 +1094,7 @@ bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent &
return false;
}
wxUint32 flags = project->GetUpdateFlags();
auto flags = project->GetUpdateFlags();
wxKeyEvent temp = evt;
@ -1105,12 +1105,12 @@ bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent &
return true;
}
return HandleCommandEntry(entry, flags, 0xffffffff, &temp);
return HandleCommandEntry(entry, flags, NoFlagsSpecifed, &temp);
}
if (type == wxEVT_KEY_UP && entry->wantKeyup)
{
return HandleCommandEntry(entry, flags, 0xffffffff, &temp);
return HandleCommandEntry(entry, flags, NoFlagsSpecifed, &temp);
}
return false;
@ -1120,12 +1120,13 @@ bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent &
/// returning true iff successful. If you pass any flags,
///the command won't be executed unless the flags are compatible
///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)
return false;
wxUint32 combinedMask = (mask & entry->mask);
auto combinedMask = (mask & entry->mask);
if (combinedMask) {
AudacityProject * proj;
@ -1154,7 +1155,7 @@ bool CommandManager::HandleCommandEntry(const CommandListEntry * entry, wxUint32
///CommandManagerListener function. If you pass any flags,
///the command won't be executed unless the flags are compatible
///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];
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
/// behavior, since we can get from a string command name to the actual
/// 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...
for (const auto &entry : mCommandList)
@ -1410,14 +1411,14 @@ void CommandManager::WriteXML(XMLWriter &xmlFile)
xmlFile.EndTag(wxT("audacitykeyboard"));
}
void CommandManager::SetDefaultFlags(wxUint32 flags, wxUint32 mask)
void CommandManager::SetDefaultFlags(CommandFlag flags, CommandMask mask)
{
mDefaultFlags = flags;
mDefaultMask = mask;
}
void CommandManager::SetCommandFlags(const wxString &name,
wxUint32 flags, wxUint32 mask)
CommandFlag flags, CommandMask mask)
{
CommandListEntry *entry = mCommandNameHash[name];
if (entry) {
@ -1427,7 +1428,7 @@ void CommandManager::SetCommandFlags(const wxString &name,
}
void CommandManager::SetCommandFlags(const wxChar **names,
wxUint32 flags, wxUint32 mask)
CommandFlag flags, CommandMask mask)
{
const wxChar **nptr = names;
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_start(list, mask);

View File

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

View File

@ -38,7 +38,7 @@ bool ExecMenuCommand::Apply(CommandExecutionContext context)
CommandManager *cmdManager = context.GetProject()->GetCommandManager();
wxString cmdName = GetString(wxT("CommandName"));
wxUint32 cmdFlags = 0; // TODO ?
wxUint32 cmdMask = 0;
auto cmdFlags = AlwaysEnabledFlag; // TODO ?
auto cmdMask = AlwaysEnabledFlag;
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...
if (!mIsBatch && mEffect->GetType() != EffectTypeGenerate && mProject->mViewInfo.selectedRegion.isPoint())
{
wxUint32 flags = 0;
auto flags = AlwaysEnabledFlag;
bool allowed = mProject->TryToMakeActionAllowed(flags,
WaveTracksSelectedFlag | TimeSelectedFlag,
WaveTracksSelectedFlag | TimeSelectedFlag);

View File

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

View File

@ -474,11 +474,24 @@ void TranscriptionToolBar::PlayAtSpeed(bool looped, bool cutPreview)
// Come here from button clicks only
void TranscriptionToolBar::OnPlaySpeed(wxCommandEvent & WXUNUSED(event))
{
// Let control have precedence over shift
const bool cutPreview = mButtons[TTB_PlaySpeed]->WasControlDown();
const bool looped = !cutPreview &&
mButtons[TTB_PlaySpeed]->WasShiftDown();
PlayAtSpeed(looped, cutPreview);
auto button = mButtons[TTB_PlaySpeed];
auto doubleClicked = button->IsDoubleClicked();
button->ClearDoubleClicked();
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))

View File

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

View File

@ -200,7 +200,7 @@ void Scrubber::MarkScrubStart(
// needed for the decision to start scrubbing later when handling
// drag events.
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
mSmoothScrollingScrub = smoothScrolling;
SetScrollScrubbing (smoothScrolling);
#endif
mAlwaysSeeking = alwaysSeeking;
mScrubStartPosition = xx;
@ -356,25 +356,6 @@ void Scrubber::ContinueScrubbing()
if (mScrubSpeedDisplayCountdown > 0)
--mScrubSpeedDisplayCountdown;
}
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
if (mSmoothScrollingScrub) {
// Pan the view, so that we center the play indicator.
ViewInfo &viewInfo = mProject->GetViewInfo();
TrackPanel *const trackPanel = mProject->GetTrackPanel();
const int posX = viewInfo.TimeToPosition(viewInfo.mRecentStreamTime);
int width;
trackPanel->GetTracksUsableArea(&width, NULL);
const int deltaX = posX - width / 2;
viewInfo.h =
viewInfo.OffsetTimeByPixels(viewInfo.h, deltaX, true);
if (!viewInfo.bScrollBeyondZero)
// Can't scroll too far left
viewInfo.h = std::max(0.0, viewInfo.h);
trackPanel->Refresh(false);
}
#endif
}
void Scrubber::StopScrubbing()
@ -382,7 +363,7 @@ void Scrubber::StopScrubbing()
UncheckAllMenuItems();
mScrubStartPosition = -1;
mSmoothScrollingScrub = false;
SetScrollScrubbing (false);
if (!IsScrubbing())
{
@ -393,6 +374,12 @@ void Scrubber::StopScrubbing()
}
}
void Scrubber::SetScrollScrubbing(bool scrollScrubbing)
{
mSmoothScrollingScrub = scrollScrubbing;
mProject->GetPlaybackScroller().Activate(scrollScrubbing);
}
bool Scrubber::IsScrubbing() const
{
if (mScrubToken <= 0)
@ -403,6 +390,7 @@ bool Scrubber::IsScrubbing() const
const_cast<Scrubber&>(*this).mScrubToken = -1;
const_cast<Scrubber&>(*this).mScrubStartPosition = -1;
#ifdef EXPERIMENTAL_SCRUBBING_SMOOTH_SCROLL
// Don't call SetScrollScrubbing
const_cast<Scrubber&>(*this).mSmoothScrollingScrub = false;
#endif
return false;
@ -557,7 +545,6 @@ void ScrubbingOverlay::Draw
dc.DrawText(mLastScrubSpeedText, mLastScrubRect.GetX(), mLastScrubRect.GetY());
}
void ScrubbingOverlay::OnTimer(wxCommandEvent &event)
{
// Let other listeners get the notification
@ -666,7 +653,7 @@ void Scrubber::DoScrub(bool scroll, bool seek)
MarkScrubStart(xx, scroll, seek);
}
else if(!match) {
mSmoothScrollingScrub = scroll;
SetScrollScrubbing(scroll);
mAlwaysSeeking = seek;
UncheckAllMenuItems();
CheckMenuItem();

View File

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

View File

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

View File

@ -109,6 +109,11 @@ class AButton final : public wxWindow {
bool WasControlDown(); // returns true if control was held down
// the last time the button was clicked
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 Toggle(){ mButtonIsDown ? PopUp() : PushDown();}
void Click();
@ -157,6 +162,7 @@ class AButton final : public wxWindow {
bool mIsClicking;
bool mEnabled;
bool mUseDisabledAsDownHiliteImage;
bool mIsDoubleClicked {};
struct ImageArr { ImageRoll mArr[4]; };
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
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.SetFont(GetFont());
dc.DrawText(mLeftText, mLeftTextPos.x, mLeftTextPos.y);
@ -668,8 +677,22 @@ void Meter::OnSize(wxSizeEvent & WXUNUSED(event))
mLayoutValid = false;
}
bool Meter::InIcon(wxMouseEvent *pEvent) const
{
auto point = pEvent ? pEvent->GetPosition() : ScreenToClient(::wxGetMousePosition());
return mIconRect.Contains(point);
}
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.
return;
@ -688,7 +711,7 @@ void Meter::OnMouse(wxMouseEvent &evt)
#endif
if (evt.RightDown() ||
(evt.ButtonDown() && mIconRect.Contains(evt.m_x, evt.m_y)))
(evt.ButtonDown() && InIcon(&evt)))
{
wxMenu menu;
// 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 OnPaint(wxPaintEvent &evt);
void OnSize(wxSizeEvent &evt);
bool InIcon(wxMouseEvent *pEvent = nullptr) const;
void OnMouse(wxMouseEvent &evt);
void OnKeyDown(wxKeyEvent &evt);
void OnKeyUp(wxKeyEvent &evt);
@ -280,6 +281,8 @@ class Meter final : public wxPanel
friend class MeterAx;
bool mHighlighted {};
DECLARE_EVENT_TABLE()
};

View File

@ -84,6 +84,9 @@ array of Ruler::Label.
#include "../Snap.h"
#include "../tracks/ui/Scrubbing.h"
//#define SCRUB_ABOVE
#define RULER_DOUBLE_CLICK
using std::min;
using std::max;
@ -1774,6 +1777,9 @@ BEGIN_EVENT_TABLE(AdornedRulerPanel, wxPanel)
EVT_MENU(OnAutoScrollID, AdornedRulerPanel::OnAutoScroll)
EVT_MENU(OnLockPlayRegionID, AdornedRulerPanel::OnLockPlayRegion)
// Scrub bar menu commands
EVT_MENU(OnShowHideScrubbingID, AdornedRulerPanel::OnToggleScrubbing)
END_EVENT_TABLE()
AdornedRulerPanel::AdornedRulerPanel(AudacityProject* parent,
@ -1869,7 +1875,7 @@ namespace {
bool ReadScrubEnabledPref()
{
bool result {};
gPrefs->Read(scrubEnabledPrefName, &result, true);
gPrefs->Read(scrubEnabledPrefName, &result, false);
return result;
}
@ -1895,11 +1901,56 @@ void AdornedRulerPanel::UpdatePrefs()
// Affected by the last
UpdateRects();
RegenerateTooltips();
RegenerateTooltips(mPrevZone);
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
{
if (mButtonFontSize < 0) {
@ -1911,12 +1962,21 @@ wxFont &AdornedRulerPanel::GetButtonFont() const
mButtonFont.SetPointSize(mButtonFontSize);
wxCoord width, height;
for (auto button = StatusChoice::FirstButton; done && IsButton(button); ++button) {
auto allowableWidth = GetButtonRect(button).GetWidth() - 2;
// 2 corresponds with the Inflate(-1, -1)
auto rect = GetTextRect(GetButtonRect(button));
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(
wxGetTranslation(GetPushButtonStrings(button)->label),
&width, &height, NULL, NULL, &mButtonFont);
done = width < allowableWidth;
// Yes, < not <= ! Leave at least some room.
done = width < availableWidth && height < availableHeight;
}
mButtonFontSize--;
} while (mButtonFontSize > 0 && !done);
@ -1930,7 +1990,7 @@ void AdornedRulerPanel::InvalidateRuler()
mRuler.Invalidate();
}
void AdornedRulerPanel::RegenerateTooltips()
void AdornedRulerPanel::RegenerateTooltips(StatusChoice choice)
{
#if wxUSE_TOOLTIPS
if (mTimelineToolTip) {
@ -1938,7 +1998,7 @@ void AdornedRulerPanel::RegenerateTooltips()
this->SetToolTip(_("Timeline actions disabled during recording"));
}
else {
switch(mPrevZone) {
switch(choice) {
case StatusChoice::QuickPlayButton :
case StatusChoice::EnteringQP :
if (!mQuickPlayEnabled) {
@ -1988,12 +2048,15 @@ void AdornedRulerPanel::OnCapture(wxCommandEvent & evt)
// if recording is initiated by a modal window (Timer Record).
SetCursor(mCursorDefault);
mIsRecording = true;
// The quick play indicator is useless during recording
HideQuickPlayIndicator();
}
else {
SetCursor(mCursorHand);
mIsRecording = false;
}
RegenerateTooltips();
RegenerateTooltips(mPrevZone);
}
enum : int {
@ -2051,7 +2114,7 @@ void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
mBack->Create(sz.x, sz.y, dc);
mBackDC.SelectObject(*mBack);
DoDrawBorder(&mBackDC);
DoDrawBackground(&mBackDC);
if (!mViewInfo->selectedRegion.isPoint())
{
@ -2063,8 +2126,7 @@ void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
if (mIndType >= 0)
{
const bool scrub = mProject->GetScrubber().HasStartedScrubbing();
auto width = scrub ? IndicatorBigWidth() : IndicatorMediumWidth;
DoDrawIndicator(&mBackDC, mIndTime, mIndType != 0, width, scrub);
DoDrawIndicator(&mBackDC, mIndTime, mIndType != 0, IndicatorMediumWidth, false);
}
if (mViewInfo->selectedRegion.isPoint())
@ -2076,11 +2138,13 @@ void AdornedRulerPanel::OnPaint(wxPaintEvent & WXUNUSED(evt))
DoDrawPushbuttons(&mBackDC);
DoDrawEdge(&mBackDC);
dc.Blit(0, 0, mBack->GetWidth(), mBack->GetHeight(), &mBackDC, 0, 0);
if (mQuickPlayInd)
{
DrawQuickPlayIndicator(&dc);
DrawQuickPlayIndicator(&dc, true);
}
}
@ -2103,21 +2167,30 @@ void AdornedRulerPanel::UpdateRects()
mInner.x += LeftMargin;
mInner.width -= (LeftMargin + RightMargin);
wxRect *top = &mInner;
auto top = &mInner;
auto bottom = &mInner;
if (mShowScrubbing) {
mScrubZone = mInner;
auto scrubHeight = std::min(mScrubZone.height, int(ScrubHeight));
mScrubZone.height = scrubHeight;
mInner.height -= scrubHeight;
mInner.y += scrubHeight;
top = &mScrubZone;
int topHeight;
#ifdef SCRUB_ABOVE
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->height -= TopMargin;
mInner.height -= BottomMargin;
bottom->height -= BottomMargin;
if (!mShowScrubbing)
mScrubZone = mInner;
@ -2166,7 +2239,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
}
const bool overButtons = GetButtonAreaRect(true).Contains(evt.GetPosition());
const StatusChoice button = FindButton(evt.GetPosition());
auto button = FindButton(evt).button;
const bool inScrubZone = !overButtons &&
// only if scrubbing is allowed now
mProject->GetScrubber().CanScrub() &&
@ -2179,9 +2252,11 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
? button
: inScrubZone
? StatusChoice::EnteringScrubZone
: StatusChoice::EnteringQP;
: mInner.Contains(evt.GetPosition())
? StatusChoice::EnteringQP
: StatusChoice::NoChange;
const bool changeInZone = (zone != mPrevZone);
mPrevZone = zone;
const bool changing = evt.Leaving() || evt.Entering() || changeInZone;
wxCoord xx = evt.GetX();
wxCoord mousePosX = xx;
@ -2195,36 +2270,49 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
}
// Handle status bar messages
UpdateStatusBarAndTooltips (
evt.Leaving() || evt.Entering() || changeInZone
? zone
: StatusChoice::NoChange
);
UpdateStatusBarAndTooltips (changing ? zone : StatusChoice::NoChange);
if ((IsButton(zone) || IsButton(mPrevZone)) &&
(changing || evt.Moving() || evt.Dragging()))
// So that the highlights in pushbuttons can update
Refresh(false);
mPrevZone = zone;
auto &scrubber = mProject->GetScrubber();
if (scrubber.HasStartedScrubbing()) {
// If already clicked for scrub, preempt the usual event handling,
// no matter what the y coordinate.
if (zone == StatusChoice::EnteringQP &&
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
evt.ResumePropagation(wxEVENT_PROPAGATE_MAX);
// Do this hack so scrubber can detect mouse drags anywhere
evt.ResumePropagation(wxEVENT_PROPAGATE_MAX);
if (scrubber.IsScrubbing())
evt.Skip();
else if (evt.LeftDClick())
// On the second button down, switch the pending scrub to scrolling
scrubber.MarkScrubStart(evt.m_x, true, false);
else
evt.Skip();
if (scrubber.IsScrubbing())
evt.Skip();
else if (evt.LeftDClick())
// On the second button down, switch the pending scrub to scrolling
scrubber.MarkScrubStart(evt.m_x, true, false);
else
evt.Skip();
mQuickPlayInd = true;
wxClientDC dc(this);
DrawQuickPlayIndicator(&dc);
mQuickPlayInd = true;
wxClientDC dc(this);
DrawQuickPlayIndicator(&dc);
if (HasCapture())
ReleaseMouse();
if (HasCapture())
ReleaseMouse();
return;
return;
}
}
// Store the initial play region state
@ -2260,7 +2348,7 @@ void AdornedRulerPanel::OnMouseEvents(wxMouseEvent &evt)
return;
}
if (HasCapture() && mCaptureState != StatusChoice::NoButton)
if (HasCapture() && mCaptureState.button != StatusChoice::NoButton)
HandlePushbuttonEvent(evt);
else if (!HasCapture() && overButtons)
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()) {
mDoubleClick = false;
HandleQPClick(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)
{
// Temporarily unlock locked play region
@ -2452,12 +2553,15 @@ void AdornedRulerPanel::HandleQPDrag(wxMouseEvent &event, wxCoord mousePosX)
void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
{
if (mDoubleClick)
return;
HideQuickPlayIndicator();
if (HasCapture())
ReleaseMouse();
mCaptureState = StatusChoice::NoButton;
mCaptureState = CaptureState{};
if (mPlayRegionEnd < mPlayRegionStart) {
// Swap values to ensure mPlayRegionStart < mPlayRegionEnd
@ -2492,6 +2596,28 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
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.
bool startPlaying = (mPlayRegionStart >= 0);
@ -2502,7 +2628,7 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
bool loopEnabled = true;
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.
if ((mPlayRegionStart > sel0) && (mPlayRegionStart < sel1)) {
// 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;
AudioIOStartStreamOptions options(mProject->GetDefaultPlayOptions());
options.playLooped = (loopEnabled && evt.ShiftDown());
options.playLooped = (loopEnabled && looped);
if (!evt.ControlDown())
if (!cutPreview)
options.pStartTime = &mPlayRegionStart;
else
options.timeTrack = NULL;
ControlToolBar::PlayAppearance appearance =
evt.ControlDown() ? ControlToolBar::PlayAppearance::CutPreview
cutPreview ? ControlToolBar::PlayAppearance::CutPreview
: options.playLooped ? ControlToolBar::PlayAppearance::Looped
: ControlToolBar::PlayAppearance::Straight;
ctb->PlayPlayRegion((SelectedRegion(start, end)),
@ -2543,18 +2669,6 @@ void AdornedRulerPanel::HandleQPRelease(wxMouseEvent &evt)
mPlayRegionEnd = end;
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)
@ -2567,8 +2681,7 @@ void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice)
if (IsButton(choice)) {
bool state = GetButtonState(choice);
const auto &strings = *GetPushButtonStrings(choice);
message += wxGetTranslation(state ? strings.disable : strings.enable);
message += wxT(" ") + _("(Right-Click for options)");
message = wxGetTranslation(state ? strings.disable : strings.enable);
}
else {
const auto &scrubber = mProject->GetScrubber();
@ -2603,10 +2716,10 @@ void AdornedRulerPanel::UpdateStatusBarAndTooltips(StatusChoice choice)
// Display a message, or empty message
mProject->TP_DisplayStatusMessage(message);
RegenerateTooltips();
RegenerateTooltips(choice);
}
void AdornedRulerPanel::OnToggleScrubbing()
void AdornedRulerPanel::OnToggleScrubbing(wxCommandEvent&)
{
mShowScrubbing = !mShowScrubbing;
WriteScrubEnabledPref(mShowScrubbing);
@ -2686,6 +2799,15 @@ void AdornedRulerPanel::ShowScrubMenu(const wxPoint & pos)
auto cleanup = finally([this]{ PopEventHandler(); });
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);
PopupMenu(&rulerMenu, pos);
}
@ -2695,7 +2817,7 @@ void AdornedRulerPanel::OnToggleQuickPlay(wxCommandEvent&)
mQuickPlayEnabled = (mQuickPlayEnabled)? false : true;
gPrefs->Write(wxT("/QuickPlay/QuickPlayEnabled"), mQuickPlayEnabled);
gPrefs->Flush();
RegenerateTooltips();
RegenerateTooltips(mPrevZone);
}
void AdornedRulerPanel::OnSyncSelToQuickPlay(wxCommandEvent&)
@ -2736,7 +2858,7 @@ void AdornedRulerPanel::OnTimelineToolTips(wxCommandEvent&)
gPrefs->Write(wxT("/QuickPlay/ToolTips"), mTimelineToolTip);
gPrefs->Flush();
#if wxUSE_TOOLTIPS
RegenerateTooltips();
RegenerateTooltips(mPrevZone);
#endif
}
@ -2860,19 +2982,40 @@ wxRect AdornedRulerPanel::GetButtonRect( StatusChoice button ) const
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) {
if(GetButtonRect(button).Contains(position))
return button;
auto state = InButtonRect( button, &mouseEvent );
if (state != PointerState::Out)
return CaptureState{ button, state };
}
return StatusChoice::NoButton;
return { StatusChoice::NoButton, PointerState::Out };
}
bool AdornedRulerPanel::GetButtonState( StatusChoice button ) const
@ -2890,30 +3033,33 @@ bool AdornedRulerPanel::GetButtonState( StatusChoice button ) const
void AdornedRulerPanel::ToggleButtonState( StatusChoice button )
{
wxCommandEvent dummy;
switch(button) {
case StatusChoice::QuickPlayButton: {
wxCommandEvent dummy;
case StatusChoice::QuickPlayButton:
OnToggleQuickPlay(dummy);
}
break;
case StatusChoice::ScrubBarButton:
OnToggleScrubbing();
OnToggleScrubbing(dummy);
break;
default:
wxASSERT(false);
}
UpdateStatusBarAndTooltips(mCaptureState.button);
}
void AdornedRulerPanel::ShowButtonMenu( StatusChoice button, wxPoint position)
{
switch (button) {
case StatusChoice::QuickPlayButton:
return ShowMenu(position);
ShowMenu(position); break;
case StatusChoice::ScrubBarButton:
return ShowScrubMenu(position);
ShowScrubMenu(position); break;
default:
return;
}
// dismiss and clear Quick-Play indicator
HideQuickPlayIndicator();
}
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") },
};
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()
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() :
AColor::Dark(dc, false);
dc->DrawRectangle(bev);
// Draw borders, bevels, and backgrounds of the split sections
bev.Inflate(-1, -1);
if (down)
AColor::Solo(dc, true, false);
else
AColor::MediumTrackInfo(dc, false);
dc->SetPen( *wxTRANSPARENT_PEN );//No border!
dc->DrawRectangle(bev);
if (pointerState == PointerState::InArrow) {
// Draw highlighted arrow after
DrawButtonBackground(dc, textRect, (down == PointerState::In), false);
DrawButtonBackground(dc, arrowRect, (down == PointerState::InArrow), true);
}
else {
// 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;
wxString str = wxGetTranslation(GetPushButtonStrings(button)->label);
dc->SetFont(GetButtonFont());
dc->GetTextExtent(str, &textWidth, &textHeight);
dc->DrawText(str, bev.x + (bev.width - textWidth) / 2,
bev.y + (bev.height - textHeight) / 2);
AColor::BevelTrackInfo(*dc, !down, bev);
// Color it as in TrackInfo::DrawTitleBar
#ifdef EXPERIMENTAL_THEMING
wxColour c = theTheme.Colour( clrTrackPanelText );
#else
wxColour c = *wxBLACK;
#endif
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)
{
auto button = FindButton(evt.GetPosition());
if (IsButton(button)) {
if (evt.LeftDown()) {
CaptureMouse();
mCaptureState = button;
Refresh();
}
else if (evt.RightDown()) {
auto rect = GetButtonRect(button);
ShowButtonMenu( button, wxPoint{ rect.GetX() + 1, rect.GetBottom() + 1 } );
}
auto pair = FindButton(evt);
auto button = pair.button;
if (IsButton(button) && evt.ButtonDown()) {
CaptureMouse();
mCaptureState = pair;
Refresh();
}
}
@ -2974,11 +3177,21 @@ void AdornedRulerPanel::HandlePushbuttonEvent(wxMouseEvent &evt)
if(evt.ButtonUp()) {
if(HasCapture())
ReleaseMouse();
if(InButtonRect(mCaptureState)) {
ToggleButtonState(mCaptureState);
UpdateStatusBarAndTooltips(mCaptureState);
auto button = mCaptureState.button;
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();
@ -2988,18 +3201,37 @@ void AdornedRulerPanel::DoDrawPushbuttons(wxDC *dc) const
{
// Paint the area behind the buttons
wxRect background = GetButtonAreaRect();
#ifndef SCRUB_ABOVE
// Reduce the height
background.y = mInner.y;
background.height = mInner.height;
#endif
AColor::MediumTrackInfo(dc, false);
dc->DrawRectangle(background);
for (auto button = StatusChoice::FirstButton; IsButton(button); ++button) {
auto state = GetButtonState(button);
auto toggle = (button == mCaptureState && InButtonRect(button));
auto down = (state != toggle);
DoDrawPushbutton(dc, button, down);
bool state = GetButtonState(button);
auto in = InButtonRect(button, nullptr);
auto down = PointerState::Out;
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
AColor::MediumTrackInfo( dc, false );
@ -3012,6 +3244,10 @@ void AdornedRulerPanel::DoDrawBorder(wxDC * dc)
dc->DrawRectangle(mScrubZone);
}
}
void AdornedRulerPanel::DoDrawEdge(wxDC *dc)
{
wxRect r = mOuter;
r.width -= RightMargin;
r.height -= BottomMargin;
@ -3112,6 +3348,7 @@ void AdornedRulerPanel::DrawIndicator( double time, bool rec )
void AdornedRulerPanel::DoDrawIndicator
(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);
AColor::IndicatorColor( dc, playing );
@ -3122,12 +3359,15 @@ void AdornedRulerPanel::DoDrawIndicator
const int IndicatorHalfWidth = width / 2;
// Double headed, left-right
auto yy = mShowScrubbing
? mScrubZone.y
: (mInner.GetBottom() + 1) - 1 /* bevel */ - height;
tri[ 0 ].x = x - IndicatorOffset;
tri[ 0 ].y = mScrubZone.y;
tri[ 0 ].y = yy;
tri[ 1 ].x = x - IndicatorOffset;
tri[ 1 ].y = mScrubZone.y + height;
tri[ 1 ].y = yy + height;
tri[ 2 ].x = x - IndicatorHalfWidth;
tri[ 2 ].y = mScrubZone.y + height / 2;
tri[ 2 ].y = yy + height / 2;
dc->DrawPolygon( 3, tri );
tri[ 0 ].x = tri[ 1 ].x = x + IndicatorOffset;
tri[ 2 ].x = x + IndicatorHalfWidth;
@ -3138,11 +3378,11 @@ void AdornedRulerPanel::DoDrawIndicator
auto height = IndicatorHeightForWidth(width);
const int IndicatorHalfWidth = width / 2;
tri[ 0 ].x = x - IndicatorHalfWidth;
tri[ 0 ].y = mScrubZone.y;
tri[ 0 ].y = mInner.y;
tri[ 1 ].x = x + IndicatorHalfWidth;
tri[ 1 ].y = mScrubZone.y;
tri[ 1 ].y = mInner.y;
tri[ 2 ].x = x;
tri[ 2 ].y = mScrubZone.y + height;
tri[ 2 ].y = mInner.y + height;
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
// it happy OSX.
dc->Blit(x - indsize - 1,
mScrubZone.y - 1,
auto xx = x - indsize - 1;
auto yy = 0;
dc->Blit(xx, yy,
indsize * 2 + 1 + 2,
mScrubZone.y + height + 2,
GetSize().GetHeight(),
&mBackDC,
x - indsize - 1,
0);
xx, yy);
}
}
@ -3180,7 +3420,7 @@ QuickPlayIndicatorOverlay *AdornedRulerPanel::GetOverlay()
}
// 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());
if (dc == NULL || (mQuickPlayPos >= latestEnd)) {
@ -3195,7 +3435,9 @@ void AdornedRulerPanel::DrawQuickPlayIndicator(wxDC * dc)
!mProject->GetScrubber().IsScrubbing();
GetOverlay()->Update(x, mIsSnapped, previewScrub);
DoEraseIndicator(dc, mLastQuickPlayX);
if (!repainting)
DoEraseIndicator(dc, mLastQuickPlayX);
mLastQuickPlayX = x;
auto scrub = mPrevZone == StatusChoice::EnteringScrubZone ||

View File

@ -312,20 +312,6 @@ public:
void InvalidateRuler();
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 {
FirstButton = 0,
@ -340,10 +326,38 @@ private:
Leaving,
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) {
choice = static_cast<StatusChoice>(1 + static_cast<int>(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)
{
auto integer = static_cast<int>(choice);
@ -357,14 +371,15 @@ private:
void OnCaptureLost(wxMouseCaptureLostEvent &evt);
void DoDrawBorder(wxDC * dc);
void DoDrawBackground(wxDC * dc);
void DoDrawEdge(wxDC *dc);
void DoDrawMarks(wxDC * dc, bool /*text */ );
void DoDrawCursor(wxDC * dc);
void DoDrawSelection(wxDC * dc);
void DoDrawIndicator(wxDC * dc, double time, bool playing, int width, bool scrub);
void DoEraseIndicator(wxDC *dc, int x);
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);
wxRect GetButtonAreaRect(bool includeBorder = false) const;
@ -381,12 +396,13 @@ private:
}
wxRect GetButtonRect( StatusChoice button ) const;
bool InButtonRect( StatusChoice button ) const;
StatusChoice FindButton( wxPoint position ) const;
PointerState InButtonRect( StatusChoice button, wxMouseEvent *pEvent ) const;
CaptureState FindButton( wxMouseEvent &mouseEvent ) const;
bool GetButtonState( StatusChoice button ) const;
void ToggleButtonState( StatusChoice button );
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 HandlePushbuttonClick(wxMouseEvent &evt);
void HandlePushbuttonEvent(wxMouseEvent &evt);
@ -453,14 +469,13 @@ private:
void OnAutoScroll(wxCommandEvent &evt);
void OnLockPlayRegion(wxCommandEvent &evt);
void OnToggleScrubbing();
void OnToggleScrubbing(wxCommandEvent&);
bool mPlayRegionDragsSelection;
bool mTimelineToolTip;
bool mQuickPlayEnabled;
StatusChoice mCaptureState { StatusChoice::NoButton };
CaptureState mCaptureState {};
enum MouseEventState {
mesNone,
@ -483,6 +498,8 @@ private:
mutable int mButtonFontSize { -1 };
mutable wxFont mButtonFont;
bool mDoubleClick {};
DECLARE_EVENT_TABLE()
};