audacia/src/widgets/PopupMenuTable.h

323 lines
9.4 KiB
C++

/**********************************************************************
Audacity: A Digital Audio Editor
PopupMenuTable.h
Paul Licameli
This file defines PopupMenuTable, which inherits from wxEventHandler,
associated macros simplifying the population of tables,
and PopupMenuTable::Menu which is buildable from one or more such
tables, and automatically attaches and detaches the event handlers.
**********************************************************************/
#ifndef __AUDACITY_POPUP_MENU_TABLE__
#define __AUDACITY_POPUP_MENU_TABLE__
class wxCommandEvent;
#include <functional>
#include <vector>
#include <wx/menu.h> // to inherit wxMenu
#include <memory>
#include "Internat.h"
#include "../commands/CommandManager.h"
class PopupMenuHandler;
class PopupMenuTable;
struct AUDACITY_DLL_API PopupMenuTableEntry : Registry::SingleItem
{
enum Type { Item, RadioItem, CheckItem };
using InitFunction =
std::function< void( PopupMenuHandler &handler, wxMenu &menu, int id ) >;
Type type;
int id;
TranslatableString caption;
wxCommandEventFunction func;
PopupMenuHandler &handler;
InitFunction init;
PopupMenuTableEntry( const Identifier &stringId,
Type type_, int id_, const TranslatableString &caption_,
wxCommandEventFunction func_, PopupMenuHandler &handler_,
InitFunction init_ = {} )
: SingleItem{ stringId }
, type(type_)
, id(id_)
, caption(caption_)
, func(func_)
, handler( handler_ )
, init( init_ )
{}
~PopupMenuTableEntry() override;
};
struct AUDACITY_DLL_API PopupSubMenu : Registry::ConcreteGroupItem< false >
, MenuTable::WholeMenu
{
TranslatableString caption;
PopupMenuTable &table;
PopupSubMenu( const Identifier &stringId,
const TranslatableString &caption_, PopupMenuTable &table );
~PopupSubMenu() override;
};
struct PopupMenuSection
: Registry::ConcreteGroupItem< false >
, MenuTable::MenuSection {
using ConcreteGroupItem< false >::ConcreteGroupItem;
};
class PopupMenuHandler : public wxEvtHandler
{
public:
PopupMenuHandler() = default;
PopupMenuHandler( const PopupMenuHandler& ) = delete;
PopupMenuHandler& operator=( const PopupMenuHandler& ) = delete;
// Called before the menu items are appended.
// Store context data, if needed.
// May be called more than once before the menu opens.
virtual void InitUserData(void *pUserData) = 0;
// Called when menu is destroyed.
// May be called more than once.
virtual void DestroyMenu() = 0;
};
struct PopupMenuVisitor : public MenuVisitor {
explicit PopupMenuVisitor( PopupMenuTable &table ) : mTable{ table } {}
PopupMenuTable &mTable;
};
class AUDACITY_DLL_API PopupMenuTable : public PopupMenuHandler
{
public:
using Entry = PopupMenuTableEntry;
// Supply a nonempty caption for sub-menu tables
PopupMenuTable( const Identifier &id, const TranslatableString &caption = {} )
: mId{ id }
, mCaption{ caption }
, mRegistry{ std::make_unique<Registry::TransparentGroupItem<>>( mId ) }
{}
// Optional pUserData gets passed to the InitUserData routines of tables.
// No memory management responsibility is assumed by this function.
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; }
const Registry::GroupItem *GetRegistry() const { return mRegistry.get(); }
// Typically statically constructed:
struct AttachedItem {
AttachedItem( PopupMenuTable &table,
const Registry::Placement &placement, Registry::BaseItemPtr pItem )
{ table.RegisterItem( placement, std::move( pItem ) ); }
};
// menu must have been built by BuildMenu
// More items get added to the end of it
static void ExtendMenu( wxMenu &menu, PopupMenuTable &otherTable );
const std::shared_ptr< Registry::GroupItem > &Get( void *pUserData )
{
if ( pUserData )
this->InitUserData( pUserData );
if (!mTop)
Populate();
return mTop;
}
void Clear()
{
mTop.reset();
}
// Forms a computed item, which may be omitted when function returns null
// and thus can be a conditional item
template< typename Table >
static Registry::BaseItemPtr Computed(
const std::function< Registry::BaseItemPtr( Table& ) > &factory )
{
using namespace Registry;
return std::make_unique< ComputedItem >(
[factory]( Visitor &baseVisitor ){
auto &visitor = static_cast< PopupMenuVisitor& >( baseVisitor );
auto &table = static_cast< Table& >( visitor.mTable );
return factory( table );
}
);
}
private:
void RegisterItem(
const Registry::Placement &placement, Registry::BaseItemPtr pItem );
protected:
// This convenience function composes a label, with the following optional
// part put in parentheses if useExtra is true
static TranslatableString MakeLabel( const TranslatableString &label,
bool useExtra, const TranslatableString &extra )
{
return useExtra
? XXO("%s (%s)").Format( label, extra )
: label;
}
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,
// This callback might check or disable a menu item:
const PopupMenuTableEntry::InitFunction &init );
void AppendItem( const Identifier &stringId, int id,
const TranslatableString &string, wxCommandEventFunction memFn,
// This callback might check or disable a menu item:
const PopupMenuTableEntry::InitFunction &init = {} )
{ Append( stringId, PopupMenuTableEntry::Item, id, string, memFn, init ); }
void AppendRadioItem( const Identifier &stringId, int id,
const TranslatableString &string, wxCommandEventFunction memFn,
// This callback might check or disable a menu item:
const PopupMenuTableEntry::InitFunction &init = {} )
{ Append( stringId, PopupMenuTableEntry::RadioItem, id, string, memFn, init ); }
void AppendCheckItem( const Identifier &stringId, int id,
const TranslatableString &string, wxCommandEventFunction memFn,
const PopupMenuTableEntry::InitFunction &init = {} )
{ Append( stringId, PopupMenuTableEntry::CheckItem, id, string, memFn, init ); }
void BeginSection( const Identifier &name );
void EndSection();
std::shared_ptr< Registry::GroupItem > mTop;
std::vector< Registry::GroupItem* > mStack;
Identifier mId;
TranslatableString mCaption;
std::unique_ptr<Registry::GroupItem> mRegistry;
};
// 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, typename Base = PopupMenuTable >
class ComputedPopupMenuTable : public Base
{
public:
using Base::Base;
using Base::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& ) >;
static Registry::BaseItemPtr Computed( const Factory &factory )
{
return Base::Computed( factory );
}
void Append( const Factory &factory )
{
Append( Computed( factory ) );
}
};
/*
The following macros make it easy to attach a popup menu to a window.
Example of usage:
In class MyTable (maybe in the private section),
which inherits from PopupMenuTable,
DECLARE_POPUP_MENU(MyTable);
virtual void InitUserData(void *pUserData);
virtual void DestroyMenu();
Then in MyTable.cpp,
void MyTable::InitUserData(void *pUserData)
{
// Remember pData
auto pData = static_cast<MyData*>(pUserData);
}
void MyTable::DestroyMenu()
{
// Do cleanup
}
BEGIN_POPUP_MENU(MyTable)
// This is inside a function and can contain arbitrary code. But usually
// you only need a sequence of calls:
AppendItem("Cut",
OnCutSelectedTextID, XO("Cu&t"), POPUP_MENU_FN( OnCutSelectedText ),
// optional argument:
[](PopupMenuHandler &handler, wxMenu &menu, int id)
{
auto data = static_cast<MyTable&>( handler ).pData;
// maybe enable or disable the menu item
}
);
// etc.
END_POPUP_MENU()
where OnCutSelectedText is a (maybe private) member function of MyTable.
Elsewhere,
MyTable myTable;
MyData data;
auto pMenu = PopupMenuTable::BuildMenu(pParent, &myTable, &data);
// Optionally:
OtherTable otherTable;
PopupMenuTable::ExtendMenu( *pMenu, otherTable );
pParent->PopupMenu(pMenu.get(), event.m_x, event.m_y);
That's all!
*/
#define DECLARE_POPUP_MENU(HandlerClass) \
void Populate() override;
// begins function
#define BEGIN_POPUP_MENU(HandlerClass) \
void HandlerClass::Populate() { \
using My = HandlerClass; \
mTop = std::make_shared< PopupSubMenu >( \
Id(), Caption(), *this ); \
mStack.clear(); \
mStack.push_back( mTop.get() );
#define POPUP_MENU_FN( memFn ) ( (wxCommandEventFunction) (&My::memFn) )
#define POPUP_MENU_SUB_MENU(stringId, classname, pUserData ) \
mStack.back()->items.push_back( \
Registry::Shared( classname::Instance().Get( pUserData ) ) );
// ends function
#define END_POPUP_MENU() }
#endif