Use computed registry items for conditionally shown popup menu items

This commit is contained in:
Paul Licameli 2020-02-10 15:22:40 -05:00
parent e07b5df6f3
commit 46f958f376
3 changed files with 111 additions and 65 deletions

View File

@ -550,13 +550,12 @@ void RateMenuTable::OnRateOther(wxCommandEvent &)
//=============================================================================
// Class defining common command handlers for mono and stereo tracks
struct WaveTrackMenuTable : PopupMenuTable
struct WaveTrackMenuTable : ComputedPopupMenuTable< WaveTrackMenuTable >
{
static WaveTrackMenuTable &Instance( Track * pTrack);
Track * mpTrack{};
static WaveTrackMenuTable &Instance();
WaveTrackMenuTable()
: PopupMenuTable{ "WaveTrack" }
: ComputedPopupMenuTable< WaveTrackMenuTable >{ "WaveTrack" }
{}
void InitUserData(void *pUserData) override;
@ -586,15 +585,9 @@ struct WaveTrackMenuTable : PopupMenuTable
void OnSplitStereoMono(wxCommandEvent & event);
};
WaveTrackMenuTable &WaveTrackMenuTable::Instance( Track * pTrack )
WaveTrackMenuTable &WaveTrackMenuTable::Instance()
{
static WaveTrackMenuTable instance;
// Clear it out so we force a repopulate
instance.Clear();
// Ensure we know how to populate.
// Messy, but the design does not seem to offer an alternative.
// We won't use pTrack after populate.
instance.mpTrack = pTrack;
return instance;
}
@ -614,8 +607,7 @@ static std::vector<WaveTrackSubViewType> AllTypes()
}
BEGIN_POPUP_MENU(WaveTrackMenuTable)
// Functions usable "now" (list population time) and also "later"
// (in callbacks to check and disable items)
// Functions usable in callbacks to check and disable items
static const auto findTrack =
[]( PopupMenuHandler &handler ) -> WaveTrack & {
return *static_cast< WaveTrack* >(
@ -635,20 +627,26 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
ProjectAudioIO::Get( project ).IsAudioActive();
};
const auto pTrack = &findTrack( *this );
const auto &view = WaveTrackView::Get( *pTrack );
BeginSection( "SubViews" );
if ( WaveTrackSubViews::slots() > 1 )
AppendCheckItem( "MultiView", OnMultiViewID, XO("&Multi-view"),
POPUP_MENU_FN( OnMultiView ),
[]( PopupMenuHandler &handler, wxMenu &menu, int id ){
auto &track = findTrack( handler );
const auto &view = WaveTrackView::Get( track );
menu.Check( id, view.GetMultiView() );
}
);
// Multi-view check mark item, if more than one track sub-view type is
// known
Append( []( My &table ) -> Registry::BaseItemPtr {
if ( WaveTrackSubViews::slots() > 1 )
return std::make_unique<Entry>(
"MultiView", Entry::CheckItem, OnMultiViewID, XO("&Multi-view"),
POPUP_MENU_FN( OnMultiView ),
table,
[]( PopupMenuHandler &handler, wxMenu &menu, int id ){
auto &track = findTrack( handler );
const auto &view = WaveTrackView::Get( track );
menu.Check( id, view.GetMultiView() );
} );
else
return nullptr;
} );
// Append either a checkbox or radio item for each sub-view.
// Radio buttons if in single-view mode, else checkboxes
int id = OnSetDisplayId;
for ( const auto &type : AllTypes() ) {
static const auto initFn = []( bool radio ){ return
@ -681,54 +679,66 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
menu.Enable( id, false );
};
};
if ( view.GetMultiView() ) {
AppendCheckItem( type.name.Internal(), id++, type.name.Msgid(),
POPUP_MENU_FN( OnSetDisplay ), initFn( false ) );
}
else {
AppendRadioItem( type.name.Internal(), id++, type.name.Msgid(),
POPUP_MENU_FN( OnSetDisplay ), initFn( true ) );
}
Append( [type, id]( My &table ) -> Registry::BaseItemPtr {
const auto pTrack = &findTrack( table );
const auto &view = WaveTrackView::Get( *pTrack );
const auto itemType =
view.GetMultiView() ? Entry::CheckItem : Entry::RadioItem;
return std::make_unique<Entry>( type.name.Internal(), itemType,
id, type.name.Msgid(),
POPUP_MENU_FN( OnSetDisplay ), table,
initFn( !view.GetMultiView() ) );
} );
++id;
}
EndSection();
if ( pTrack ) {
// Conditionally add sub-menu for wave color, if showing waveform
Append( []( My &table ) -> Registry::BaseItemPtr {
const auto pTrack = &findTrack( table );
const auto &view = WaveTrackView::Get( *pTrack );
const auto displays = view.GetDisplays();
bool hasWaveform = (displays.end() != std::find(
displays.begin(), displays.end(),
WaveTrackSubView::Type{ WaveTrackViewConstants::Waveform, {} }
) );
if( hasWaveform ){
BeginSection( "WaveColor" );
POPUP_MENU_SUB_MENU( "WaveColor", WaveColorMenuTable, mpData )
EndSection();
}
if( hasWaveform )
return std::make_unique<PopupMenuSection>( "WaveColor",
Registry::Shared( WaveColorMenuTable::Instance()
.Get( table.mpData ) ) );
else
return nullptr;
} );
// Conditionally add sub-menu for spectrogram settings, if showing spectrum
Append( []( My &table ) -> Registry::BaseItemPtr {
const auto pTrack = &findTrack( table );
const auto &view = WaveTrackView::Get( *pTrack );
const auto displays = view.GetDisplays();
bool hasSpectrum = (displays.end() != std::find(
displays.begin(), displays.end(),
WaveTrackSubView::Type{ WaveTrackViewConstants::Spectrum, {} }
) );
if( hasSpectrum ){
if( hasSpectrum )
// In future, we might move this to the context menu of the
// Spectrum vertical ruler.
// (But the latter won't be satisfactory without a means to
// open that other context menu with keystrokes only, and that
// would require some notion of a focused sub-view.)
BeginSection( "SpectrogramSettings" );
AppendItem( "SpectrogramSettings", OnSpectrogramSettingsID, XO("S&pectrogram Settings..."), POPUP_MENU_FN( OnSpectrogramSettings ),
return std::make_unique<PopupMenuSection>( "SpectrogramSettings",
std::make_unique<Entry>( "SpectrogramSettings", Entry::Item,
OnSpectrogramSettingsID,
XO("S&pectrogram Settings..."),
POPUP_MENU_FN( OnSpectrogramSettings ), table,
[]( PopupMenuHandler &handler, wxMenu &menu, int id ){
// Bug 1253. Shouldn't open preferences if audio is busy.
// We can't change them on the fly yet anyway.
auto gAudioIO = AudioIOBase::Get();
menu.Enable(id, !gAudioIO->IsBusy());
}
);
EndSection();
}
}
} ) );
else
return nullptr;
} );
BeginSection( "Channels" );
// If these are enabled again, choose a hot key for Mono that does not conflict
@ -791,7 +801,9 @@ BEGIN_POPUP_MENU(WaveTrackMenuTable)
POPUP_MENU_FN( OnSplitStereo ), enableSplitStereo );
// DA: Uses split stereo track and then drag pan sliders for split-stereo-to-mono
#ifndef EXPERIMENTAL_DA
AppendItem( "SplitToMono", OnSplitStereoMonoID, XO("Split Stereo to Mo&no"), POPUP_MENU_FN( OnSplitStereoMono ), enableSplitStereo );
AppendItem( "SplitToMono", OnSplitStereoMonoID,
XO("Split Stereo to Mo&no"), POPUP_MENU_FN( OnSplitStereoMono ),
enableSplitStereo );
#endif
EndSection();
@ -1126,7 +1138,7 @@ void WaveTrackMenuTable::OnSplitStereoMono(wxCommandEvent &)
PopupMenuTable *WaveTrackControls::GetMenuExtension(Track * pTrack)
{
WaveTrackMenuTable & result = WaveTrackMenuTable::Instance( pTrack );
WaveTrackMenuTable & result = WaveTrackMenuTable::Instance();
return &result;
}

View File

@ -43,11 +43,11 @@ struct PopupMenu : public wxMenu
void *pUserData;
};
class PopupMenuBuilder : public MenuVisitor {
class PopupMenuBuilder : public PopupMenuVisitor {
public:
explicit
PopupMenuBuilder( PopupMenuTable &table, PopupMenu &menu, void *pUserData )
: mTable{ table }
: PopupMenuVisitor{ table }
, mMenu{ &menu }
, mRoot{ mMenu }
, mpUserData{ pUserData }
@ -58,7 +58,6 @@ public:
void DoVisit( Registry::SingleItem &item, const Path &path ) override;
void DoSeparator() override;
PopupMenuTable &mTable;
std::vector< std::unique_ptr<PopupMenu> > mMenus;
PopupMenu *mMenu, *mRoot;
void *const mpUserData;
@ -146,14 +145,18 @@ void PopupMenuTable::ExtendMenu( wxMenu &menu, PopupMenuTable &table )
Registry::Visit( visitor, table.Get( theMenu.pUserData ).get() );
}
void PopupMenuTable::Append( Registry::BaseItemPtr pItem )
{
mStack.back()->items.push_back( std::move( pItem ) );
}
void PopupMenuTable::Append(
const Identifier &stringId, PopupMenuTableEntry::Type type, int id,
const TranslatableString &string, wxCommandEventFunction memFn,
const PopupMenuTableEntry::InitFunction &init )
{
mStack.back()->items.push_back( std::make_unique<Entry>(
stringId, type, id, string, memFn, *this, init
) );
Append( std::make_unique<Entry>(
stringId, type, id, string, memFn, *this, init ) );
}
void PopupMenuTable::BeginSection( const Identifier &name )
@ -172,11 +175,11 @@ void PopupMenuTable::EndSection()
namespace{
void PopupMenu::DisconnectTable(PopupMenuTable *pTable)
{
class PopupMenuDestroyer : public MenuVisitor {
class PopupMenuDestroyer : public PopupMenuVisitor {
public:
explicit
PopupMenuDestroyer( PopupMenuTable &table, PopupMenu &menu )
: mTable{ table }
: PopupMenuVisitor{ table }
, mMenu{ menu }
{}
@ -188,7 +191,6 @@ void PopupMenu::DisconnectTable(PopupMenuTable *pTable)
pEntry->handler.DestroyMenu();
}
PopupMenuTable &mTable;
PopupMenu &mMenu;
};

View File

@ -47,7 +47,7 @@ struct PopupMenuTableEntry : Registry::SingleItem
PopupMenuTableEntry( const Identifier &stringId,
Type type_, int id_, const TranslatableString &caption_,
wxCommandEventFunction func_, PopupMenuHandler &handler_,
InitFunction init_ )
InitFunction init_ = {} )
: SingleItem{ stringId }
, type(type_)
, id(id_)
@ -95,6 +95,11 @@ public:
virtual void DestroyMenu() = 0;
};
struct PopupMenuVisitor : public MenuVisitor {
explicit PopupMenuVisitor( PopupMenuTable &table ) : mTable{ table } {}
PopupMenuTable &mTable;
};
class PopupMenuTable : public PopupMenuHandler
{
public:
@ -120,7 +125,8 @@ public:
const std::shared_ptr< Registry::GroupItem > &Get( void *pUserData )
{
this->InitUserData( pUserData );
if ( pUserData )
this->InitUserData( pUserData );
if (!mTop)
Populate();
return mTop;
@ -130,6 +136,8 @@ protected:
virtual void Populate() = 0;
// To be used in implementations of Populate():
void Append( Registry::BaseItemPtr pItem );
void Append(
const Identifier &stringId, PopupMenuTableEntry::Type type, int id,
const TranslatableString &string, wxCommandEventFunction memFn,
@ -156,14 +164,38 @@ protected:
void BeginSection( const Identifier &name );
void EndSection();
void Clear() { mTop.reset(); }
std::shared_ptr< Registry::GroupItem > mTop;
std::vector< Registry::GroupItem* > mStack;
Identifier mId;
TranslatableString mCaption;
};
// A "CRTP" class that injects a convenience function, which appends a menu item
// computed lazily by a function that is passed the table (after it has stored
// its user data)
template< typename Derived >
class ComputedPopupMenuTable : public PopupMenuTable
{
public:
using PopupMenuTable::PopupMenuTable;
using PopupMenuTable::Append;
// Appends a computed item, which may be omitted when function returns null
// and thus can be a conditional item
using Factory = std::function< Registry::BaseItemPtr( Derived& ) >;
void Append( const Factory &factory )
{
using namespace Registry;
Append( std::make_unique< ComputedItem >(
[factory]( Visitor &baseVisitor ){
auto &visitor = static_cast< PopupMenuVisitor& >( baseVisitor );
auto &table = static_cast< Derived& >( visitor.mTable );
return factory( table );
}
) );
}
};
/*
The following macros make it easy to attach a popup menu to a window.