Populate popup menus with a registry visitor
This commit is contained in:
parent
99e3dd5d3f
commit
23c7122985
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in New Issue
Block a user