1
0
mirror of https://github.com/cookiengineer/audacity synced 2025-08-08 16:11:14 +02:00

Define classes and interpreter to be used in menu definition tables

This commit is contained in:
Paul Licameli 2018-10-17 10:37:00 -04:00
parent 4cc92a26fa
commit e756f2e71c
2 changed files with 383 additions and 0 deletions

View File

@ -363,6 +363,155 @@ void MenuManager::UpdatePrefs()
mStopIfWasPaused = true; // not configurable for now, but could be later.
}
namespace MenuTable {
BaseItem::~BaseItem() {}
ComputedItem::~ComputedItem() {}
GroupItem::GroupItem( BaseItemPtrs &&items_ )
: items{ std::move( items_ ) }
{
}
void GroupItem::AppendOne( BaseItemPtr&& ptr )
{
items.push_back( std::move( ptr ) );
}
GroupItem::~GroupItem() {}
MenuItem::MenuItem( const wxString &title_, BaseItemPtrs &&items_ )
: GroupItem{ std::move( items_ ) }, title{ title_ }
{
wxASSERT( !title.empty() );
}
MenuItem::~MenuItem() {}
ConditionalGroupItem::ConditionalGroupItem(
Condition condition_, BaseItemPtrs &&items_ )
: GroupItem{ std::move( items_ ) }, condition{ condition_ }
{
}
ConditionalGroupItem::~ConditionalGroupItem() {}
SeparatorItem::~SeparatorItem() {}
CommandItem::CommandItem(const wxString &name_,
const wxString &label_in_,
bool hasDialog_,
CommandHandlerFinder finder_,
CommandFunctorPointer callback_,
CommandFlag flags_,
const CommandManager::Options &options_)
: name{ name_ }, label_in{ label_in_ }, hasDialog{ hasDialog_ }
, finder{ finder_ }, callback{ callback_ }
, flags{ flags_ }, options{ options_ }
{}
CommandItem::~CommandItem() {}
CommandGroupItem::CommandGroupItem(const wxString &name_,
const IdentInterfaceSymbol items_[],
size_t nItems_,
CommandHandlerFinder finder_,
CommandFunctorPointer callback_,
CommandFlag flags_,
bool isEffect_)
: name{ name_ }, items{ items_, items_ + nItems_ }
, finder{ finder_ }, callback{ callback_ }
, flags{ flags_ }, isEffect{ isEffect_ }
{}
CommandGroupItem::~CommandGroupItem() {}
SpecialItem::~SpecialItem() {}
}
namespace {
void VisitItem( AudacityProject &project, MenuTable::BaseItem *pItem );
void VisitItems(
AudacityProject &project, const MenuTable::BaseItemPtrs &items )
{
for ( auto &pSubItem : items )
VisitItem( project, pSubItem.get() );
}
void VisitItem( AudacityProject &project, MenuTable::BaseItem *pItem )
{
if (!pItem)
return;
auto &manager = *project.GetCommandManager();
using namespace MenuTable;
if (const auto pComputed =
dynamic_cast<ComputedItem*>( pItem )) {
// TODO maybe? memo-ize the results of the function, but that requires
// invalidating the memo at the right times
auto result = pComputed->factory( project );
if (result)
// recursion
VisitItem( project, result.get() );
}
else
if (const auto pCommand =
dynamic_cast<CommandItem*>( pItem )) {
manager.AddItem(
pCommand->name, pCommand->label_in, pCommand->hasDialog,
pCommand->finder, pCommand->callback,
pCommand->flags, pCommand->options
);
}
else
if (const auto pCommandList =
dynamic_cast<CommandGroupItem*>( pItem ) ) {
manager.AddItemList(pCommandList->name,
pCommandList->items.data(), pCommandList->items.size(),
pCommandList->finder, pCommandList->callback,
pCommandList->flags, pCommandList->isEffect);
}
else
if (const auto pMenu =
dynamic_cast<MenuItem*>( pItem )) {
manager.BeginMenu( pMenu->title );
// recursion
VisitItems( project, pMenu->items );
manager.EndMenu();
}
else
if (const auto pConditionalGroup =
dynamic_cast<ConditionalGroupItem*>( pItem )) {
const auto flag = pConditionalGroup->condition();
if (!flag)
manager.BeginOccultCommands();
// recursion
VisitItems( project, pConditionalGroup->items );
if (!flag)
manager.EndOccultCommands();
}
else
if (const auto pGroup =
dynamic_cast<GroupItem*>( pItem )) {
// recursion
VisitItems( project, pGroup->items );
}
else
if (dynamic_cast<SeparatorItem*>( pItem )) {
manager.AddSeparator();
}
else
if (const auto pSpecial =
dynamic_cast<SpecialItem*>( pItem )) {
const auto pMenu = manager.CurrentMenu();
wxASSERT( pMenu );
pSpecial->fn( project, *pMenu );
}
else
wxASSERT( false );
}
}
/// CreateMenusAndCommands builds the menus, and also rebuilds them after
/// changes in configured preferences - for example changes in key-bindings
/// affect the short-cut key legend that appears beside each command,

View File

@ -362,7 +362,9 @@ private:
wxMenuBar * CurrentMenuBar() const;
wxMenuBar * GetMenuBar(const wxString & sMenu) const;
wxMenu * CurrentSubMenu() const;
public:
wxMenu * CurrentMenu() const;
private:
wxString GetLabel(const CommandListEntry *entry) const;
wxString GetLabelWithDisabledAccel(const CommandListEntry *entry) const;
@ -398,4 +400,236 @@ private:
std::unique_ptr< wxMenuBar > mTempMenuBar;
};
// Define items that populate tables that describe menu trees
namespace MenuTable {
// TODO C++17: maybe use std::variant (discriminated unions) to achieve
// polymorphism by other means, not needing unique_ptr and dynamic_cast
// and using less heap.
// Most items in the table will be the large ones describing commands, so the
// waste of space in unions for separators and sub-menus should not be
// large.
struct BaseItem {
// declare at least one virtual function so dynamic_cast will work
virtual ~BaseItem();
};
using BaseItemPtr = std::unique_ptr<BaseItem>;
using BaseItemPtrs = std::vector<BaseItemPtr>;
// The type of functions that generate menu table descriptions.
// Return type is a shared_ptr to let the function decide whether to recycle
// the object or rebuild it on demand each time.
// Return value from the factory may be null.
using Factory = std::function<
std::shared_ptr< MenuTable::BaseItem >( AudacityProject & )
>;
struct ComputedItem : BaseItem {
explicit ComputedItem( const Factory &factory_ )
: factory{ factory_ }
{}
~ComputedItem() override;
Factory factory;
};
struct GroupItem : BaseItem {
// Construction from a previously built-up vector of pointers
GroupItem( BaseItemPtrs &&items_ );
// In-line, variadic constructor that doesn't require building a vector
template< typename... Args >
GroupItem( Args&&... args )
{ Append( std::forward< Args >( args )... ); }
~GroupItem() override;
BaseItemPtrs items;
private:
// nullary overload grounds the recursion
void Append() {}
// recursive overload
template< typename Arg, typename... Args >
void Append( Arg &&arg, Args&&... moreArgs )
{
// Dispatch one argument to the proper overload of AppendOne.
// std::forward preserves rvalue/lvalue distinction of the actual
// argument of the constructor call; that is, it inserts a
// std::move() if and only if the original argument is rvalue
AppendOne( std::forward<Arg>( arg ) );
// recur with the rest of the arguments
Append( std::forward<Args>(moreArgs)... );
};
void AppendOne( BaseItemPtr&& ptr );
// This override allows a lambda or function pointer in the variadic
// argument lists without any other syntactic wrapping:
void AppendOne( const Factory &factory )
{ AppendOne( std::make_unique<ComputedItem>( factory ) ); }
};
struct MenuItem final : GroupItem {
// Construction from a previously built-up vector of pointers
MenuItem( const wxString &title_, BaseItemPtrs &&items_ );
// In-line, variadic constructor that doesn't require building a vector
template< typename... Args >
MenuItem( const wxString &title_, Args&&... args )
: GroupItem{ std::forward<Args>(args)... }
, title{ title_ }
{}
~MenuItem() override;
wxString title; // translated
};
struct ConditionalGroupItem final : GroupItem {
using Condition = std::function< bool() >;
// Construction from a previously built-up vector of pointers
ConditionalGroupItem( Condition condition_, BaseItemPtrs &&items_ );
// In-line, variadic constructor that doesn't require building a vector
template< typename... Args >
ConditionalGroupItem( Condition condition_, Args&&... args )
: GroupItem{ std::forward<Args>(args)... }
, condition{ condition_ }
{}
~ConditionalGroupItem() override;
Condition condition;
};
struct SeparatorItem final : BaseItem
{
~SeparatorItem() override;
};
struct CommandItem final : BaseItem {
CommandItem(const wxString &name_,
const wxString &label_in_,
bool hasDialog_,
CommandHandlerFinder finder_,
CommandFunctorPointer callback_,
CommandFlag flags_,
const CommandManager::Options &options_);
~CommandItem() override;
const wxString name;
const wxString label_in;
bool hasDialog;
CommandHandlerFinder finder;
CommandFunctorPointer callback;
CommandFlag flags;
CommandManager::Options options;
};
struct CommandGroupItem final : BaseItem {
CommandGroupItem(const wxString &name_,
const IdentInterfaceSymbol items_[],
size_t nItems_,
CommandHandlerFinder finder_,
CommandFunctorPointer callback_,
CommandFlag flags_,
bool isEffect_);
~CommandGroupItem() override;
const wxString name;
const std::vector<IdentInterfaceSymbol> items;
CommandHandlerFinder finder;
CommandFunctorPointer callback;
CommandFlag flags;
bool isEffect;
};
// For manipulating the enclosing menu or sub-menu directly,
// adding any number of items, not using the CommandManager
struct SpecialItem final : BaseItem
{
using Appender = std::function< void( AudacityProject&, wxMenu& ) >;
explicit SpecialItem( const Appender &fn_ )
: fn{ fn_ }
{}
~SpecialItem() override;
Appender fn;
};
// Following are the functions to use directly in writing table definitions.
// Group items can be constructed two ways.
// Pointers to subordinate items are moved into the result.
// Null pointers are permitted, and ignored when building the menu.
// Items are spliced into the enclosing menu
template< typename... Args >
inline BaseItemPtr Items( Args&&... args )
{ return std::make_unique<GroupItem>(
std::forward<Args>(args)... ); }
inline BaseItemPtr Items(
const wxString &title, BaseItemPtrs &&items )
{ return std::make_unique<GroupItem>( std::move( items ) ); }
// Menu items can be constructed two ways, as for group items
// Items will appear in a main toolbar menu or in a sub-menu
template< typename... Args >
inline BaseItemPtr Menu(
const wxString &title, Args&&... args )
{ return std::make_unique<MenuItem>(
title, std::forward<Args>(args)... ); }
inline BaseItemPtr Menu(
const wxString &title, BaseItemPtrs &&items )
{ return std::make_unique<MenuItem>( title, std::move( items ) ); }
// Conditional group items can be constructed two ways, as for group items
// These items register in the CommandManager but are not shown in menus
template< typename... Args >
inline BaseItemPtr ConditionalItems(
ConditionalGroupItem::Condition condition, Args&&... args )
{ return std::make_unique<ConditionalGroupItem>(
condition, std::forward<Args>(args)... ); }
inline BaseItemPtr ConditionalItems(
ConditionalGroupItem::Condition condition, BaseItemPtrs &&items )
{ return std::make_unique<ConditionalGroupItem>(
condition, std::move( items ) ); }
// Make either a menu item or just a group, depending on the nonemptiness
// of the title
template< typename... Args >
inline BaseItemPtr MenuOrItems(
const wxString &title, Args&&... args )
{ if ( title.empty() ) return Items( std::forward<Args>(args)... );
else return std::make_unique<MenuItem>(
title, std::forward<Args>(args)... ); }
inline BaseItemPtr MenuOrItems(
const wxString &title, BaseItemPtrs &&items )
{ if ( title.empty() ) return Items( std::move( items ) );
else return std::make_unique<MenuItem>( title, std::move( items ) ); }
inline std::unique_ptr<SeparatorItem> Separator()
{ return std::make_unique<SeparatorItem>(); }
inline std::unique_ptr<CommandItem> Command(
const wxString &name, const wxString &label_in, bool hasDialog,
CommandHandlerFinder finder, CommandFunctorPointer callback,
CommandFlag flags, const CommandManager::Options &options = {})
{
return std::make_unique<CommandItem>(
name, label_in, hasDialog, finder, callback, flags, options
);
}
inline std::unique_ptr<CommandGroupItem> CommandGroup(
const wxString &name,
const IdentInterfaceSymbol items[], size_t nItems,
CommandHandlerFinder finder, CommandFunctorPointer callback,
CommandFlag flags, bool isEffect = false)
{
return std::make_unique<CommandGroupItem>(
name, items, nItems, finder, callback, flags, isEffect
);
}
inline std::unique_ptr<SpecialItem> Special(
const SpecialItem::Appender &fn )
{ return std::make_unique<SpecialItem>( fn ); }
}
#endif