1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-06-17 00:20:06 +02:00
audacity/src/widgets/PopupMenuTable.h
Paul Licameli b81c03c194 Change the menu labels in vertical rulers...
... and recompute popup menu tables that depend on preferences.

The code for labels in the rulers was unsatisfactory because

(1) rescanning and editing the post-translation strings is a hack to avoid, and
(2) separating the hints like "Shift-Left-Click" with a tab character made them
invisible on Mac.  So instead, append strings like " (Shift-Left-Click)"
2020-05-23 13:58:22 -04:00

323 lines
9.3 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 "../MemoryX.h"
#include "../Internat.h"
#include "../commands/CommandManager.h"
class PopupMenuHandler;
class PopupMenuTable;
struct 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 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 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.
Exmple 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