Populate popup menus with a registry visitor

This commit is contained in:
Paul Licameli 2020-02-05 18:01:28 -05:00
parent 99e3dd5d3f
commit 23c7122985
2 changed files with 191 additions and 99 deletions

View File

@ -11,6 +11,19 @@ Paul Licameli split from TrackPanel.cpp
#include "../Audacity.h"
#include "PopupMenuTable.h"
PopupMenuTableEntry::~PopupMenuTableEntry()
{}
PopupSubMenu::~PopupSubMenu()
{}
PopupSubMenu::PopupSubMenu( const Identifier &stringId,
const TranslatableString &caption_, PopupMenuTable &table )
: ConcreteGroupItem< false >{ stringId }
, caption{ caption_ }
{
}
namespace {
struct PopupMenu : public wxMenu
{
@ -28,75 +41,134 @@ struct PopupMenu : public wxMenu
void *pUserData;
};
class PopupMenuBuilder : public MenuVisitor {
public:
explicit
PopupMenuBuilder( PopupMenuTable &table, PopupMenu &menu, void *pUserData )
: mTable{ table }
, mMenu{ &menu }
, mRoot{ mMenu }
, mpUserData{ pUserData }
{}
void DoBeginGroup( Registry::GroupItem &item, const Path &path ) override;
void DoEndGroup( Registry::GroupItem &item, const Path &path ) override;
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;
};
void PopupMenuBuilder::DoBeginGroup( Registry::GroupItem &item, const Path &path )
{
if ( auto pItem = dynamic_cast<PopupSubMenu*>(&item) ) {
if ( !pItem->caption.empty() ) {
auto newMenu =
std::make_unique<PopupMenu>( mMenu->pParent, mMenu->pUserData );
mMenu = newMenu.get();
mMenus.push_back( std::move( newMenu ) );
}
}
}
void PopupMenuBuilder::DoEndGroup( Registry::GroupItem &item, const Path &path )
{
if ( auto pItem = dynamic_cast<PopupSubMenu*>(&item) ) {
if ( !pItem->caption.empty() ) {
auto subMenu = std::move( mMenus.back() );
mMenus.pop_back();
mMenu = mMenus.empty() ? mRoot : mMenus.back().get();
mMenu->AppendSubMenu( subMenu.release(), pItem->caption.Translation());
}
}
}
void PopupMenuBuilder::DoVisit( Registry::SingleItem &item, const Path &path )
{
auto connect = [this]( const PopupMenuTable::Entry *pEntry ) {
mMenu->pParent->Bind
(wxEVT_COMMAND_MENU_SELECTED,
pEntry->func, &mTable, pEntry->id);
};
auto pEntry = static_cast<PopupMenuTableEntry*>( &item );
switch (pEntry->type) {
case PopupMenuTable::Entry::Item:
{
mMenu->Append(pEntry->id, pEntry->caption.Translation());
connect( pEntry );
break;
}
case PopupMenuTable::Entry::RadioItem:
{
mMenu->AppendRadioItem(pEntry->id, pEntry->caption.Translation());
connect( pEntry );
break;
}
case PopupMenuTable::Entry::CheckItem:
{
mMenu->AppendCheckItem(pEntry->id, pEntry->caption.Translation());
connect( pEntry );
break;
}
default:
break;
}
}
void PopupMenuBuilder::DoSeparator()
{
mMenu->AppendSeparator();
}
PopupMenu::~PopupMenu()
{
// Event connections between the parent window and the singleton table
// object must be broken when this menu is destroyed.
Disconnect();
}
void PopupMenu::Extend(PopupMenuTable *pTable)
{
pTable->InitUserData(pUserData);
auto connect = [&]( const PopupMenuTable::Entry *pEntry ) {
this->pParent->Bind
(wxEVT_COMMAND_MENU_SELECTED,
pEntry->func, pTable, pEntry->id);
};
for (const PopupMenuTable::Entry *pEntry = &*pTable->Get().begin();
pEntry->IsValid(); ++pEntry) {
switch (pEntry->type) {
case PopupMenuTable::Entry::Item:
{
this->Append(pEntry->id, pEntry->caption.Translation());
connect( pEntry );
break;
}
case PopupMenuTable::Entry::RadioItem:
{
this->AppendRadioItem(pEntry->id, pEntry->caption.Translation());
connect( pEntry );
break;
}
case PopupMenuTable::Entry::CheckItem:
{
this->AppendCheckItem(pEntry->id, pEntry->caption.Translation());
connect( pEntry );
break;
}
case PopupMenuTable::Entry::Separator:
this->AppendSeparator();
break;
case PopupMenuTable::Entry::SubMenu:
{
const auto subTable = pEntry->subTable;
auto subMenu =
PopupMenuTable::BuildMenu( this->pParent, subTable, pUserData );
this->AppendSubMenu(
subMenu.release(), subTable->Caption().Translation());
}
default:
break;
}
}
pTable->InitMenu(this);
tables.push_back( pTable );
}
void PopupMenuTable::ExtendMenu( wxMenu &menu, PopupMenuTable &table )
{
auto &theMenu = dynamic_cast<PopupMenu&>(menu);
table.InitUserData(theMenu.pUserData);
PopupMenuBuilder visitor{ table, theMenu, theMenu.pUserData };
Registry::Visit( visitor, table.Get().get() );
table.InitMenu( &menu );
theMenu.tables.push_back( &table );
}
namespace{
void PopupMenu::DisconnectTable(PopupMenuTable *pTable)
{
for (const PopupMenuTable::Entry *pEntry = &*pTable->Get().begin();
pEntry->IsValid(); ++pEntry) {
if ( pEntry->IsItem() )
pParent->Unbind( wxEVT_COMMAND_MENU_SELECTED,
pEntry->func, pTable, pEntry->id );
else if ( pEntry->IsSubMenu() )
// recur
DisconnectTable(pEntry->subTable);
}
class PopupMenuDestroyer : public MenuVisitor {
public:
explicit
PopupMenuDestroyer( PopupMenuTable &table, PopupMenu &menu )
: mTable{ table }
, mMenu{ menu }
{}
void DoVisit( Registry::SingleItem &item, const Path &path ) override
{
auto pEntry = static_cast<PopupMenuTableEntry*>( &item );
mMenu.pParent->Unbind( wxEVT_COMMAND_MENU_SELECTED,
pEntry->func, &mTable, pEntry->id );
}
PopupMenuTable &mTable;
PopupMenu &mMenu;
};
PopupMenuDestroyer visitor{ *pTable, *this };
Registry::Visit( visitor, pTable->Get().get() );
pTable->DestroyMenu();
}
@ -118,11 +190,7 @@ std::unique_ptr<wxMenu> PopupMenuTable::BuildMenu
{
// Rebuild as needed each time. That makes it safe in case of language change.
auto theMenu = std::make_unique<PopupMenu>( pParent, pUserData );
theMenu->Extend(pTable);
ExtendMenu( *theMenu, *pTable );
return theMenu;
}
void PopupMenuTable::ExtendMenu( wxMenu &menu, PopupMenuTable &otherTable )
{
dynamic_cast<PopupMenu&>(menu).Extend(&otherTable);
}

View File

@ -25,12 +25,13 @@ class wxString;
#include "../MemoryX.h"
#include "../Internat.h"
#include "../commands/CommandManager.h"
class PopupMenuTable;
struct PopupMenuTableEntry
struct PopupMenuTableEntry : Registry::SingleItem
{
enum Type { Item, RadioItem, CheckItem, Separator, SubMenu, Invalid };
enum Type { Item, RadioItem, CheckItem };
Type type;
int id;
@ -38,18 +39,33 @@ struct PopupMenuTableEntry
wxCommandEventFunction func;
PopupMenuTable *subTable;
PopupMenuTableEntry(Type type_, int id_, TranslatableString caption_,
wxCommandEventFunction func_, PopupMenuTable *subTable_)
: type(type_)
PopupMenuTableEntry( const Identifier &stringId,
Type type_, int id_, const TranslatableString &caption_,
wxCommandEventFunction func_)
: SingleItem{ stringId }
, type(type_)
, id(id_)
, caption(caption_)
, func(func_)
, subTable(subTable_)
{}
bool IsItem() const { return type == Item || type == RadioItem || type == CheckItem; }
bool IsSubMenu() const { return type == SubMenu; }
bool IsValid() const { return type != Invalid; }
~PopupMenuTableEntry() override;
};
struct PopupSubMenu : Registry::ConcreteGroupItem< false >
{
TranslatableString caption;
PopupSubMenu( const Identifier &stringId,
const TranslatableString &caption_, PopupMenuTable &table );
~PopupSubMenu() override;
};
struct PopupMenuSection
: Registry::ConcreteGroupItem< false >
, MenuTable::MenuSection {
using ConcreteGroupItem< false >::ConcreteGroupItem;
};
class PopupMenuTable : public wxEvtHandler
@ -59,7 +75,8 @@ public:
// Supply a nonempty caption for sub-menu tables
PopupMenuTable( const Identifier &id, const TranslatableString &caption = {} )
: mCaption{ caption }
: mId{ id }
, mCaption{ caption }
{}
// Called before the menu items are appended.
@ -79,25 +96,28 @@ public:
static std::unique_ptr<wxMenu> BuildMenu
(wxEvtHandler *pParent, PopupMenuTable *pTable, void *pUserData = NULL);
const Identifier &Id() const { return mId; }
const TranslatableString &Caption() const { return mCaption; }
// menu must have been built by BuildMenu
// More items get added to the end of it
static void ExtendMenu( wxMenu &menu, PopupMenuTable &otherTable );
using Entries = std::vector<PopupMenuTableEntry>;
const Entries& Get()
const std::shared_ptr< PopupSubMenu > &Get()
{
if (mContents.empty())
if (!mTop)
Populate();
return mContents;
return mTop;
}
protected:
virtual void Populate() = 0;
void Clear() { mContents.clear(); }
void Clear() { mTop.reset(); }
Entries mContents;
std::shared_ptr< PopupSubMenu > mTop;
std::vector< Registry::GroupItem* > mStack;
Identifier mId;
TranslatableString mCaption;
};
@ -164,24 +184,27 @@ That's all!
// begins function
#define BEGIN_POPUP_MENU(HandlerClass) \
void HandlerClass::Populate() { \
using My = HandlerClass;
using My = HandlerClass; \
mTop = std::make_shared< PopupSubMenu >( \
Id(), Caption(), *this ); \
mStack.clear(); \
mStack.push_back( mTop.get() );
#define POPUP_MENU_APPEND(stringId, type, id, string, memFn, subTable) \
mContents.push_back( Entry { \
#define POPUP_MENU_APPEND(stringId, type, id, string, memFn) \
mStack.back()->items.push_back( std::make_unique<Entry>( \
Identifier{ stringId }, \
type, \
id, \
string, \
memFn, \
subTable \
} );
memFn \
) );
#define POPUP_MENU_APPEND_ITEM(stringId, type, id, string, memFn) \
POPUP_MENU_APPEND( stringId, \
type, \
id, \
string, \
(wxCommandEventFunction) (&My::memFn), \
nullptr )
(wxCommandEventFunction) (&My::memFn) )
#define POPUP_MENU_ITEM(stringId, id, string, memFn) \
POPUP_MENU_APPEND_ITEM(stringId, Entry::Item, id, string, memFn);
@ -194,19 +217,20 @@ void HandlerClass::Populate() { \
// classname names a class that derives from MenuTable and defines Instance()
#define POPUP_MENU_SUB_MENU(stringId, classname) \
POPUP_MENU_APPEND( stringId, \
Entry::SubMenu, -1, classname::Instance().Caption(), nullptr, &classname::Instance() );
mStack.back()->items.push_back( \
Registry::Shared( classname::Instance().Get() ) );
#define BEGIN_POPUP_MENU_SECTION( name ) \
POPUP_MENU_APPEND( "", \
Entry::Separator, -1, {}, nullptr, nullptr );
#define END_POPUP_MENU_SECTION()
// ends function
#define END_POPUP_MENU() \
POPUP_MENU_APPEND( "", \
Entry::Invalid, -1, {}, nullptr, nullptr ) \
{ auto uSection = std::make_unique< PopupMenuSection >( \
Identifier{ name } ); \
auto section = uSection.get(); \
mStack.back()->items.push_back( std::move( uSection ) ); \
mStack.push_back( section ); \
}
#define END_POPUP_MENU_SECTION() mStack.pop_back();
// ends function
#define END_POPUP_MENU() }
#endif