/********************************************************************** Audacity: A Digital Audio Editor PopupMenuTable.cpp Paul Licameli split from TrackPanel.cpp **********************************************************************/ #include "PopupMenuTable.h" PopupMenuTableEntry::~PopupMenuTableEntry() {} PopupSubMenu::~PopupSubMenu() {} PopupSubMenu::PopupSubMenu( const Identifier &stringId, const TranslatableString &caption_, PopupMenuTable &table_ ) : ConcreteGroupItem< false >{ stringId } , WholeMenu{ caption_.empty() } , caption{ caption_ } , table{ table_ } { } namespace { struct PopupMenu : public wxMenu { PopupMenu(wxEvtHandler *pParent_, void *pUserData_) : pParent{ pParent_ }, tables{}, pUserData{ pUserData_ } {} ~PopupMenu() override; void Extend(PopupMenuTable *pTable); void DisconnectTable(PopupMenuTable *pTable); void Disconnect(); wxEvtHandler *pParent; std::vector tables; void *pUserData; }; class PopupMenuBuilder : public PopupMenuVisitor { public: explicit PopupMenuBuilder( PopupMenuTable &table, PopupMenu &menu, void *pUserData ) : PopupMenuVisitor{ 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; std::vector< std::unique_ptr > mMenus; PopupMenu *mMenu, *mRoot; void *const mpUserData; }; void PopupMenuBuilder::DoBeginGroup( Registry::GroupItem &item, const Path &path ) { if ( auto pItem = dynamic_cast(&item) ) { if ( !pItem->caption.empty() ) { auto newMenu = std::make_unique( mMenu->pParent, mMenu->pUserData ); newMenu->tables.push_back( &pItem->table ); mMenu = newMenu.get(); mMenus.push_back( std::move( newMenu ) ); } } } void PopupMenuBuilder::DoEndGroup( Registry::GroupItem &item, const Path &path ) { if ( auto pItem = dynamic_cast(&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 pEntry = static_cast( &item ); switch (pEntry->type) { case PopupMenuTable::Entry::Item: { mMenu->Append(pEntry->id, pEntry->caption.Translation()); break; } case PopupMenuTable::Entry::RadioItem: { mMenu->AppendRadioItem(pEntry->id, pEntry->caption.Translation()); break; } case PopupMenuTable::Entry::CheckItem: { mMenu->AppendCheckItem(pEntry->id, pEntry->caption.Translation()); break; } default: wxASSERT( false ); break; } // This call necessary for externally registered items, else harmlessly // redundant pEntry->handler.InitUserData( mpUserData ); if ( pEntry->init ) pEntry->init( pEntry->handler, *mMenu, pEntry->id ); mMenu->pParent->Bind( wxEVT_COMMAND_MENU_SELECTED, pEntry->func, &pEntry->handler, pEntry->id); } 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 PopupMenuTable::ExtendMenu( wxMenu &menu, PopupMenuTable &table ) { auto &theMenu = dynamic_cast(menu); theMenu.tables.push_back( &table ); PopupMenuBuilder visitor{ table, theMenu, theMenu.pUserData }; Registry::Visit( visitor, table.Get( theMenu.pUserData ).get(), table.GetRegistry() ); } void PopupMenuTable::RegisterItem( const Registry::Placement &placement, Registry::BaseItemPtr pItem ) { Registry::RegisterItem( *mRegistry, placement, std::move( pItem ) ); } 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 ) { Append( std::make_unique( stringId, type, id, string, memFn, *this, init ) ); } void PopupMenuTable::BeginSection( const Identifier &name ) { auto uSection = std::make_unique< PopupMenuSection >( name ); auto section = uSection.get(); mStack.back()->items.push_back( std::move( uSection ) ); mStack.push_back( section ); } void PopupMenuTable::EndSection() { mStack.pop_back(); } namespace{ void PopupMenu::DisconnectTable(PopupMenuTable *pTable) { class PopupMenuDestroyer : public PopupMenuVisitor { public: explicit PopupMenuDestroyer( PopupMenuTable &table, PopupMenu &menu ) : PopupMenuVisitor{ table } , mMenu{ menu } {} void DoVisit( Registry::SingleItem &item, const Path &path ) override { auto pEntry = static_cast( &item ); mMenu.pParent->Unbind( wxEVT_COMMAND_MENU_SELECTED, pEntry->func, &pEntry->handler, pEntry->id ); pEntry->handler.DestroyMenu(); } PopupMenu &mMenu; }; PopupMenuDestroyer visitor{ *pTable, *this }; Registry::Visit( visitor, pTable->Get( nullptr ).get(), pTable->GetRegistry() ); } void PopupMenu::Disconnect() { for ( auto pTable : tables ) DisconnectTable(pTable); } } // static std::unique_ptr PopupMenuTable::BuildMenu ( wxEvtHandler *pParent, PopupMenuTable *pTable, void *pUserData ) { // Rebuild as needed each time. That makes it safe in case of language change. auto theMenu = std::make_unique( pParent, pUserData ); ExtendMenu( *theMenu, *pTable ); return theMenu; }