mirror of
https://github.com/cookiengineer/audacity
synced 2025-08-02 17:09:26 +02:00
The fix follows the agreed behavior (see emails from around October 25) . For the sake of convenience see the agreed behavior below: _"- first, check the xml-file. If it contains illegal shortcut duplicates, refuse importing. Shortcut duplicates are LEGAL if default settings also have those operations with the matching shortcuts. A refusal to import shortcuts must happen with the message that warns the user of a failure and explains the reason. if the xml-file looks ok, import the shortcuts. As discussed before, because different versions of Audacity might have different sets of operations with shortcuts, it is still possible to end up with illegal shortcut duplicates for a perfectly correct xml-file. This situation must be monitored. In case of any conflicts, the shortcut from the xml-file is used, and the pre-existing shortcut is wiped clean. When telling the user the commands which have had their shortcut removed, I think it would be useful to tell the user the name of the command, the shortcut, and and name of the command which still has that shortcut."_ I didn't find a clean way to intercept the imported content before it makes its way to the shortcut preferences, so I had to jump through some hoops right in KeyConfigPrefs::OnImport(). In general, I tried to keep changes minimal.
947 lines
33 KiB
C++
947 lines
33 KiB
C++
/**********************************************************************
|
||
|
||
Audacity: A Digital Audio Editor
|
||
|
||
CommandManager.h
|
||
|
||
Brian Gunlogson
|
||
Dominic Mazzoni
|
||
|
||
**********************************************************************/
|
||
|
||
#ifndef __AUDACITY_COMMAND_MANAGER__
|
||
#define __AUDACITY_COMMAND_MANAGER__
|
||
|
||
#include "../Experimental.h"
|
||
|
||
#include "audacity/Types.h"
|
||
|
||
#include "../ClientData.h"
|
||
#include "CommandFunctors.h"
|
||
#include "CommandFlag.h"
|
||
|
||
#include "Keyboard.h"
|
||
#include <vector>
|
||
|
||
#include "../xml/XMLTagHandler.h"
|
||
|
||
#include "audacity/Types.h"
|
||
|
||
#include <unordered_map>
|
||
|
||
class wxMenu;
|
||
class wxMenuBar;
|
||
class wxArrayString;
|
||
class wxMenu;
|
||
class wxMenuBar;
|
||
using CommandParameter = CommandID;
|
||
|
||
struct MenuBarListEntry
|
||
{
|
||
MenuBarListEntry(const wxString &name_, wxMenuBar *menubar_);
|
||
~MenuBarListEntry();
|
||
|
||
wxString name;
|
||
wxWeakRef<wxMenuBar> menubar; // This structure does not assume memory ownership!
|
||
};
|
||
|
||
struct SubMenuListEntry
|
||
{
|
||
SubMenuListEntry(const TranslatableString &name_, std::unique_ptr<wxMenu> menu_);
|
||
SubMenuListEntry( SubMenuListEntry&& ) = default;
|
||
~SubMenuListEntry();
|
||
|
||
TranslatableString name;
|
||
std::unique_ptr<wxMenu> menu;
|
||
};
|
||
|
||
struct CommandListEntry
|
||
{
|
||
int id;
|
||
CommandID name;
|
||
TranslatableString longLabel;
|
||
NormalizedKeyString key;
|
||
NormalizedKeyString defaultKey;
|
||
TranslatableString label;
|
||
TranslatableString labelPrefix;
|
||
wxString labelTop;
|
||
wxMenu *menu;
|
||
CommandHandlerFinder finder;
|
||
CommandFunctorPointer callback;
|
||
CommandParameter parameter;
|
||
bool multi;
|
||
int index;
|
||
int count;
|
||
bool enabled;
|
||
bool skipKeydown;
|
||
bool wantKeyup;
|
||
bool isGlobal;
|
||
bool isOccult;
|
||
bool isEffect;
|
||
bool excludeFromMacros;
|
||
CommandFlag flags;
|
||
bool useStrictFlags{ false };
|
||
};
|
||
|
||
using MenuBarList = std::vector < MenuBarListEntry >;
|
||
|
||
// to do: remove the extra indirection when Mac compiler moves to newer version
|
||
using SubMenuList = std::vector < std::unique_ptr<SubMenuListEntry> >;
|
||
|
||
// This is an array of pointers, not structures, because the hash maps also point to them,
|
||
// so we don't want the structures to relocate with vector operations.
|
||
using CommandList = std::vector<std::unique_ptr<CommandListEntry>>;
|
||
|
||
using CommandKeyHash = std::unordered_map<NormalizedKeyString, CommandListEntry*>;
|
||
using CommandNameHash = std::unordered_map<CommandID, CommandListEntry*>;
|
||
using CommandNumericIDHash = std::unordered_map<int, CommandListEntry*>;
|
||
|
||
class AudacityProject;
|
||
class CommandContext;
|
||
|
||
class AUDACITY_DLL_API CommandManager final
|
||
: public XMLTagHandler
|
||
, public ClientData::Base
|
||
{
|
||
public:
|
||
static CommandManager &Get( AudacityProject &project );
|
||
static const CommandManager &Get( const AudacityProject &project );
|
||
|
||
// Type of a function that can intercept menu item handling.
|
||
// If it returns true, bypass the usual dipatch of commands.
|
||
using MenuHook = std::function< bool(const CommandID&) >;
|
||
|
||
// install a menu hook, returning the previously installed one
|
||
static MenuHook SetMenuHook( const MenuHook &hook );
|
||
|
||
//
|
||
// Constructor / Destructor
|
||
//
|
||
|
||
CommandManager();
|
||
virtual ~CommandManager();
|
||
|
||
CommandManager(const CommandManager&) PROHIBITED;
|
||
CommandManager &operator= (const CommandManager&) PROHIBITED;
|
||
|
||
void SetMaxList();
|
||
void PurgeData();
|
||
|
||
//
|
||
// Creating menus and adding commands
|
||
//
|
||
|
||
std::unique_ptr<wxMenuBar> AddMenuBar(const wxString & sMenu);
|
||
|
||
wxMenu *BeginMenu(const TranslatableString & tName);
|
||
void EndMenu();
|
||
|
||
// For specifying unusual arguments in AddItem
|
||
struct Options
|
||
{
|
||
// type of a function that determines checkmark state
|
||
using CheckFn = std::function< bool(AudacityProject&) >;
|
||
|
||
Options() {}
|
||
// Allow implicit construction from an accelerator string, which is
|
||
// a very common case
|
||
Options( const wxChar *accel_ ) : accel{ accel_ } {}
|
||
// A two-argument constructor for another common case
|
||
Options(
|
||
const wxChar *accel_,
|
||
const TranslatableString &longName_ )
|
||
: accel{ accel_ }, longName{ longName_ } {}
|
||
|
||
Options &&Accel (const wxChar *value) &&
|
||
{ accel = value; return std::move(*this); }
|
||
Options &&IsEffect (bool value = true) &&
|
||
{ bIsEffect = value; return std::move(*this); }
|
||
Options &&Parameter (const CommandParameter &value) &&
|
||
{ parameter = value; return std::move(*this); }
|
||
Options &&LongName (const TranslatableString &value ) &&
|
||
{ longName = value; return std::move(*this); }
|
||
Options &&IsGlobal () &&
|
||
{ global = true; return std::move(*this); }
|
||
Options &&UseStrictFlags () &&
|
||
{ useStrictFlags = true; return std::move(*this); }
|
||
Options &&AllowInMacros ( int value = 1 ) &&
|
||
{ allowInMacros = value; return std::move(*this); }
|
||
|
||
// Specify a constant check state
|
||
Options &&CheckState (bool value) && {
|
||
checker = value
|
||
? [](AudacityProject&){ return true; }
|
||
: [](AudacityProject&){ return false; }
|
||
;
|
||
return std::move(*this);
|
||
}
|
||
// CheckTest is overloaded
|
||
// Take arbitrary predicate
|
||
Options &&CheckTest (const CheckFn &fn) &&
|
||
{ checker = fn; return std::move(*this); }
|
||
// Take a preference path
|
||
Options &&CheckTest (const wxChar *key, bool defaultValue) && {
|
||
checker = MakeCheckFn( key, defaultValue );
|
||
return std::move(*this);
|
||
}
|
||
|
||
const wxChar *accel{ wxT("") };
|
||
CheckFn checker; // default value means it's not a check item
|
||
bool bIsEffect{ false };
|
||
CommandParameter parameter{};
|
||
TranslatableString longName{};
|
||
bool global{ false };
|
||
bool useStrictFlags{ false };
|
||
int allowInMacros{ -1 }; // 0 = never, 1 = always, -1 = deduce from label
|
||
|
||
private:
|
||
static CheckFn MakeCheckFn( const wxString key, bool defaultValue );
|
||
};
|
||
|
||
void AddItemList(const CommandID & name,
|
||
const ComponentInterfaceSymbol items[],
|
||
size_t nItems,
|
||
CommandHandlerFinder finder,
|
||
CommandFunctorPointer callback,
|
||
CommandFlag flags,
|
||
bool bIsEffect = false);
|
||
|
||
void AddItem(AudacityProject &project,
|
||
const CommandID & name,
|
||
const TranslatableString &label_in,
|
||
CommandHandlerFinder finder,
|
||
CommandFunctorPointer callback,
|
||
CommandFlag flags,
|
||
const Options &options = {});
|
||
|
||
void AddSeparator();
|
||
|
||
void PopMenuBar();
|
||
void BeginOccultCommands();
|
||
void EndOccultCommands();
|
||
|
||
|
||
void SetCommandFlags(const CommandID &name, CommandFlag flags);
|
||
|
||
//
|
||
// Modifying menus
|
||
//
|
||
|
||
void EnableUsingFlags(
|
||
CommandFlag flags, CommandFlag strictFlags);
|
||
void Enable(const wxString &name, bool enabled);
|
||
void Check(const CommandID &name, bool checked);
|
||
void Modify(const wxString &name, const TranslatableString &newLabel);
|
||
|
||
//
|
||
// Modifying accelerators
|
||
//
|
||
|
||
void SetKeyFromName(const CommandID &name, const NormalizedKeyString &key);
|
||
void SetKeyFromIndex(int i, const NormalizedKeyString &key);
|
||
|
||
//
|
||
// Executing commands
|
||
//
|
||
|
||
// "permit" allows filtering even if the active window isn't a child of the project.
|
||
// Lyrics and MixerTrackCluster classes use it.
|
||
bool FilterKeyEvent(AudacityProject *project, const wxKeyEvent & evt, bool permit = false);
|
||
bool HandleMenuID(AudacityProject &project, int id, CommandFlag flags, bool alwaysEnabled);
|
||
|
||
enum TextualCommandResult {
|
||
CommandFailure,
|
||
CommandSuccess,
|
||
CommandNotFound
|
||
};
|
||
|
||
TextualCommandResult
|
||
HandleTextualCommand(const CommandID & Str,
|
||
const CommandContext & context, CommandFlag flags, bool alwaysEnabled);
|
||
|
||
//
|
||
// Accessing
|
||
//
|
||
|
||
void GetCategories(wxArrayString &cats, AudacityProject *);
|
||
void GetAllCommandNames(CommandIDs &names, bool includeMultis) const;
|
||
void GetAllCommandLabels(
|
||
TranslatableStrings &labels, std::vector<bool> &vExcludeFromMacros,
|
||
bool includeMultis) const;
|
||
void GetAllCommandData(
|
||
CommandIDs &names,
|
||
std::vector<NormalizedKeyString> &keys,
|
||
std::vector<NormalizedKeyString> &default_keys,
|
||
TranslatableStrings &labels, wxArrayString &categories,
|
||
#if defined(EXPERIMENTAL_KEY_VIEW)
|
||
TranslatableStrings &prefixes,
|
||
#endif
|
||
bool includeMultis);
|
||
|
||
// Each command is assigned a numerical ID for use in wxMenu and wxEvent,
|
||
// which need not be the same across platforms or sessions
|
||
CommandID GetNameFromNumericID( int id );
|
||
|
||
TranslatableString GetLabelFromName(const CommandID &name);
|
||
TranslatableString GetPrefixedLabelFromName(const CommandID &name);
|
||
wxString GetCategoryFromName(const CommandID &name);
|
||
NormalizedKeyString GetKeyFromName(const CommandID &name) const;
|
||
NormalizedKeyString GetDefaultKeyFromName(const CommandID &name);
|
||
|
||
bool GetEnabled(const CommandID &name);
|
||
int GetNumberOfKeysRead() const;
|
||
|
||
#if defined(__WXDEBUG__)
|
||
void CheckDups();
|
||
#endif
|
||
|
||
//
|
||
// Loading/Saving
|
||
//
|
||
|
||
void WriteXML(XMLWriter &xmlFile) const /* not override */;
|
||
|
||
///
|
||
/// Formatting summaries that include shortcut keys
|
||
///
|
||
TranslatableString DescribeCommandsAndShortcuts
|
||
(
|
||
// If a shortcut key is defined for the command, then it is appended,
|
||
// parenthesized, after the translated name.
|
||
const ComponentInterfaceSymbol commands[], size_t nCommands) const;
|
||
|
||
// Sorted list of the shortcut keys to be exluded from the standard defaults
|
||
static const std::vector<NormalizedKeyString> &ExcludedList();
|
||
|
||
private:
|
||
|
||
//
|
||
// Creating menus and adding commands
|
||
//
|
||
|
||
int NextIdentifier(int ID);
|
||
CommandListEntry *NewIdentifier(const CommandID & name,
|
||
const TranslatableString & label,
|
||
wxMenu *menu,
|
||
CommandHandlerFinder finder,
|
||
CommandFunctorPointer callback,
|
||
const CommandID &nameSuffix,
|
||
int index,
|
||
int count,
|
||
const Options &options);
|
||
|
||
void AddGlobalCommand(const CommandID &name,
|
||
const TranslatableString &label,
|
||
CommandHandlerFinder finder,
|
||
CommandFunctorPointer callback,
|
||
const Options &options = {});
|
||
|
||
//
|
||
// Executing commands
|
||
//
|
||
|
||
bool HandleCommandEntry(AudacityProject &project,
|
||
const CommandListEntry * entry, CommandFlag flags,
|
||
bool alwaysEnabled, const wxEvent * evt = NULL);
|
||
|
||
//
|
||
// Modifying
|
||
//
|
||
|
||
void Enable(CommandListEntry *entry, bool enabled);
|
||
wxMenu *BeginMainMenu(const TranslatableString & tName);
|
||
void EndMainMenu();
|
||
wxMenu* BeginSubMenu(const TranslatableString & tName);
|
||
void EndSubMenu();
|
||
|
||
//
|
||
// Accessing
|
||
//
|
||
|
||
wxMenuBar * CurrentMenuBar() const;
|
||
wxMenuBar * GetMenuBar(const wxString & sMenu) const;
|
||
wxMenu * CurrentSubMenu() const;
|
||
public:
|
||
wxMenu * CurrentMenu() const;
|
||
private:
|
||
wxString FormatLabelForMenu(const CommandListEntry *entry) const;
|
||
wxString FormatLabelWithDisabledAccel(const CommandListEntry *entry) const;
|
||
|
||
//
|
||
// Loading/Saving
|
||
//
|
||
|
||
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override;
|
||
void HandleXMLEndTag(const wxChar *tag) override;
|
||
XMLTagHandler *HandleXMLChild(const wxChar *tag) override;
|
||
|
||
private:
|
||
// mMaxList only holds shortcuts that should not be added (by default)
|
||
// and is sorted.
|
||
std::vector<NormalizedKeyString> mMaxListOnly;
|
||
|
||
MenuBarList mMenuBarList;
|
||
SubMenuList mSubMenuList;
|
||
CommandList mCommandList;
|
||
CommandNameHash mCommandNameHash;
|
||
CommandKeyHash mCommandKeyHash;
|
||
CommandNumericIDHash mCommandNumericIDHash;
|
||
int mCurrentID;
|
||
int mXMLKeysRead;
|
||
|
||
bool mbSeparatorAllowed; // false at the start of a menu and immediately after a separator.
|
||
|
||
TranslatableString mCurrentMenuName;
|
||
std::unique_ptr<wxMenu> uCurrentMenu;
|
||
wxMenu *mCurrentMenu {};
|
||
|
||
bool bMakingOccultCommands;
|
||
std::unique_ptr< wxMenuBar > mTempMenuBar;
|
||
};
|
||
|
||
// Define classes and functions that associate parts of the user interface
|
||
// with path names
|
||
namespace Registry {
|
||
// Items in the registry form an unordered tree, but each may also describe a
|
||
// desired insertion point among its peers. The request might not be honored
|
||
// (as when the other name is not found, or when more than one item requests
|
||
// the same ordering), but this is not treated as an error.
|
||
struct OrderingHint
|
||
{
|
||
// The default Unspecified hint is just like End, except that in case the
|
||
// item is delegated to (by a SharedItem, ComputedItem, or nameless
|
||
// transparent group), the delegating item's hint will be used instead
|
||
enum Type : int {
|
||
Before, After,
|
||
Begin, End,
|
||
Unspecified // keep this last
|
||
} type{ Unspecified };
|
||
|
||
// name of some other BaseItem; significant only when type is Before or
|
||
// After:
|
||
Identifier name;
|
||
|
||
OrderingHint() {}
|
||
OrderingHint( Type type_, const wxString &name_ = {} )
|
||
: type(type_), name(name_) {}
|
||
|
||
bool operator == ( const OrderingHint &other ) const
|
||
{ return name == other.name && type == other.type; }
|
||
|
||
bool operator < ( const OrderingHint &other ) const
|
||
{
|
||
// This sorts unspecified placements later
|
||
return std::make_pair( type, name ) <
|
||
std::make_pair( other.type, other.name );
|
||
}
|
||
};
|
||
|
||
// 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
|
||
explicit
|
||
BaseItem( const Identifier &internalName )
|
||
: name{ internalName }
|
||
{}
|
||
virtual ~BaseItem();
|
||
|
||
const Identifier name;
|
||
|
||
OrderingHint orderingHint;
|
||
};
|
||
using BaseItemPtr = std::unique_ptr<BaseItem>;
|
||
using BaseItemSharedPtr = std::shared_ptr<BaseItem>;
|
||
using BaseItemPtrs = std::vector<BaseItemPtr>;
|
||
|
||
struct Visitor;
|
||
|
||
|
||
// An item that delegates to another held in a shared pointer; this allows
|
||
// static tables of items to be computed once and reused
|
||
// The name of the delegate is significant for path calculations, but the
|
||
// SharedItem's ordering hint is used if the delegate has none
|
||
struct SharedItem final : BaseItem {
|
||
explicit SharedItem( const BaseItemSharedPtr &ptr_ )
|
||
: BaseItem{ wxEmptyString }
|
||
, ptr{ ptr_ }
|
||
{}
|
||
~SharedItem() override;
|
||
|
||
BaseItemSharedPtr ptr;
|
||
};
|
||
|
||
// A convenience function
|
||
inline std::unique_ptr<SharedItem> Shared( const BaseItemSharedPtr &ptr )
|
||
{ return std::make_unique<SharedItem>( ptr ); }
|
||
|
||
// An item that computes some other item to substitute for it, each time
|
||
// the ComputedItem is visited
|
||
// The name of the substitute is significant for path calculations, but the
|
||
// ComputedItem's ordering hint is used if the substitute has none
|
||
struct ComputedItem final : BaseItem {
|
||
// The type of functions that generate descriptions of items.
|
||
// 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
|
||
template< typename VisitorType >
|
||
using Factory = std::function< BaseItemSharedPtr( VisitorType & ) >;
|
||
|
||
using DefaultVisitor = Visitor;
|
||
|
||
explicit ComputedItem( const Factory< DefaultVisitor > &factory_ )
|
||
: BaseItem( wxEmptyString )
|
||
, factory{ factory_ }
|
||
{}
|
||
~ComputedItem() override;
|
||
|
||
Factory< DefaultVisitor > factory;
|
||
};
|
||
|
||
// Common abstract base class for items that are not groups
|
||
struct SingleItem : BaseItem {
|
||
using BaseItem::BaseItem;
|
||
~SingleItem() override = 0;
|
||
};
|
||
|
||
// Common abstract base class for items that group other items
|
||
struct GroupItem : BaseItem {
|
||
using BaseItem::BaseItem;
|
||
|
||
// Construction from an internal name and a previously built-up
|
||
// vector of pointers
|
||
GroupItem( const Identifier &internalName, BaseItemPtrs &&items_ )
|
||
: BaseItem{ internalName }, items{ std::move( items_ ) }
|
||
{}
|
||
~GroupItem() override = 0;
|
||
|
||
// Whether the item is non-significant for path naming
|
||
// when it also has an empty name
|
||
virtual bool Transparent() const = 0;
|
||
|
||
BaseItemPtrs items;
|
||
};
|
||
|
||
// GroupItem adding variadic constructor conveniences
|
||
template< typename VisitorType = ComputedItem::DefaultVisitor >
|
||
struct InlineGroupItem : GroupItem {
|
||
using GroupItem::GroupItem;
|
||
// In-line, variadic constructor that doesn't require building a vector
|
||
template< typename... Args >
|
||
InlineGroupItem( const Identifier &internalName, Args&&... args )
|
||
: GroupItem( internalName )
|
||
{ Append( std::forward< Args >( args )... ); }
|
||
|
||
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)... );
|
||
};
|
||
|
||
// Move one unique_ptr to an item into our array
|
||
void AppendOne( BaseItemPtr&& ptr )
|
||
{
|
||
items.push_back( std::move( ptr ) );
|
||
}
|
||
// This overload allows a lambda or function pointer in the variadic
|
||
// argument lists without any other syntactic wrapping, and also
|
||
// allows implicit conversions to type Factory.
|
||
// (Thus, a lambda can return a unique_ptr<BaseItem> rvalue even though
|
||
// Factory's return type is shared_ptr, and the needed conversion is
|
||
// appled implicitly.)
|
||
void AppendOne( const ComputedItem::Factory<VisitorType> &factory )
|
||
{
|
||
auto adaptedFactory = [factory]( Registry::Visitor &visitor ){
|
||
return factory( dynamic_cast< VisitorType& >( visitor ) );
|
||
};
|
||
AppendOne( std::make_unique<ComputedItem>( adaptedFactory ) );
|
||
}
|
||
// This overload lets you supply a shared pointer to an item, directly
|
||
template<typename Subtype>
|
||
void AppendOne( const std::shared_ptr<Subtype> &ptr )
|
||
{ AppendOne( std::make_unique<SharedItem>(ptr) ); }
|
||
};
|
||
|
||
// Inline group item also specifying transparency
|
||
template< bool transparent,
|
||
typename VisitorType = ComputedItem::DefaultVisitor >
|
||
struct ConcreteGroupItem : InlineGroupItem< VisitorType >
|
||
{
|
||
using InlineGroupItem< VisitorType >::InlineGroupItem;
|
||
~ConcreteGroupItem() {}
|
||
bool Transparent() const override { return transparent; }
|
||
};
|
||
|
||
// Concrete subclass of GroupItem that adds nothing else
|
||
// TransparentGroupItem with an empty name is transparent to item path calculations
|
||
// and propagates its ordering hint if subordinates don't specify hints
|
||
template< typename VisitorType = ComputedItem::DefaultVisitor >
|
||
struct TransparentGroupItem final : ConcreteGroupItem< true, VisitorType >
|
||
{
|
||
using ConcreteGroupItem< true, VisitorType >::ConcreteGroupItem;
|
||
~TransparentGroupItem() override {}
|
||
};
|
||
|
||
// The /-separated path is relative to the GroupItem supplied to
|
||
// RegisterItem.
|
||
// For instance, wxT("Transport/Cursor") to locate an item under a sub-menu
|
||
// of a main menu
|
||
struct Placement {
|
||
wxString path;
|
||
OrderingHint hint;
|
||
|
||
Placement( const wxString &path_, const OrderingHint &hint_ = {} )
|
||
: path( path_ ), hint( hint_ )
|
||
{}
|
||
};
|
||
|
||
// registry collects items, before consulting preferences and ordering
|
||
// hints, and applying the merge procedure to them.
|
||
// This function puts one more item into the registry.
|
||
void RegisterItem( GroupItem ®istry, const Placement &placement,
|
||
BaseItemPtr pItem );
|
||
|
||
// Define actions to be done in Visit.
|
||
// Default implementations do nothing
|
||
// The supplied path does not include the name of the item
|
||
class Visitor
|
||
{
|
||
public:
|
||
virtual ~Visitor();
|
||
using Path = std::vector< Identifier >;
|
||
virtual void BeginGroup( GroupItem &item, const Path &path );
|
||
virtual void EndGroup( GroupItem &item, const Path &path );
|
||
virtual void Visit( SingleItem &item, const Path &path );
|
||
};
|
||
|
||
// Top-down visitation of all items and groups in a tree rooted in
|
||
// pTopItem, as merged with pRegistry.
|
||
// The merger of the trees is recomputed in each call, not saved.
|
||
// So neither given tree is modified.
|
||
// But there may be a side effect on preferences to remember the ordering
|
||
// imposed on each node of the unordered tree of registered items; each item
|
||
// seen in the registry for the first time is placed somehere, and that
|
||
// ordering should be kept the same thereafter in later runs (which may add
|
||
// yet other previously unknown items).
|
||
void Visit(
|
||
Visitor &visitor,
|
||
BaseItem *pTopItem,
|
||
GroupItem *pRegistry = nullptr );
|
||
}
|
||
|
||
struct MenuVisitor : Registry::Visitor
|
||
{
|
||
explicit MenuVisitor( AudacityProject &p ) : project{ p } {}
|
||
operator AudacityProject & () const { return project; }
|
||
|
||
AudacityProject &project;
|
||
};
|
||
|
||
// Define items that populate tables that specifically describe menu trees
|
||
namespace MenuTable {
|
||
using namespace Registry;
|
||
|
||
// Describes a main menu in the toolbar, or a sub-menu
|
||
struct MenuItem final : ConcreteGroupItem< false, MenuVisitor > {
|
||
// Construction from an internal name and a previously built-up
|
||
// vector of pointers
|
||
MenuItem( const wxString &internalName,
|
||
const TranslatableString &title_, BaseItemPtrs &&items_ );
|
||
// In-line, variadic constructor that doesn't require building a vector
|
||
template< typename... Args >
|
||
MenuItem( const wxString &internalName,
|
||
const TranslatableString &title_, Args&&... args )
|
||
: ConcreteGroupItem< false, MenuVisitor >{
|
||
internalName, std::forward<Args>(args)... }
|
||
, title{ title_ }
|
||
{}
|
||
~MenuItem() override;
|
||
|
||
TranslatableString title;
|
||
};
|
||
|
||
// Collects other items that are conditionally shown or hidden, but are
|
||
// always available to macro programming
|
||
struct ConditionalGroupItem final : ConcreteGroupItem< false, MenuVisitor > {
|
||
using Condition = std::function< bool() >;
|
||
|
||
// Construction from an internal name and a previously built-up
|
||
// vector of pointers
|
||
ConditionalGroupItem( const wxString &internalName,
|
||
Condition condition_, BaseItemPtrs &&items_ );
|
||
// In-line, variadic constructor that doesn't require building a vector
|
||
template< typename... Args >
|
||
ConditionalGroupItem( const wxString &internalName,
|
||
Condition condition_, Args&&... args )
|
||
: ConcreteGroupItem< false, MenuVisitor >{
|
||
internalName, std::forward<Args>(args)... }
|
||
, condition{ condition_ }
|
||
{}
|
||
~ConditionalGroupItem() override;
|
||
|
||
Condition condition;
|
||
};
|
||
|
||
// usage:
|
||
// auto scope = FinderScope( findCommandHandler );
|
||
// return Items( ... );
|
||
//
|
||
// or:
|
||
// return ( FinderScope( findCommandHandler ), Items( ... ) );
|
||
//
|
||
// where findCommandHandler names a function.
|
||
// This is used before a sequence of many calls to Command() and
|
||
// CommandGroup(), so that the finder argument need not be specified
|
||
// in each call.
|
||
class FinderScope : ValueRestorer< CommandHandlerFinder >
|
||
{
|
||
static CommandHandlerFinder sFinder;
|
||
|
||
public:
|
||
static CommandHandlerFinder DefaultFinder() { return sFinder; }
|
||
|
||
explicit
|
||
FinderScope( CommandHandlerFinder finder )
|
||
: ValueRestorer( sFinder, finder )
|
||
{}
|
||
};
|
||
|
||
// Describes one command in a menu
|
||
struct CommandItem final : SingleItem {
|
||
CommandItem(const CommandID &name_,
|
||
const TranslatableString &label_in_,
|
||
CommandFunctorPointer callback_,
|
||
CommandFlag flags_,
|
||
const CommandManager::Options &options_,
|
||
CommandHandlerFinder finder_);
|
||
|
||
// Takes a pointer to member function directly, and delegates to the
|
||
// previous constructor; useful within the lifetime of a FinderScope
|
||
template< typename Handler >
|
||
CommandItem(const CommandID &name_,
|
||
const TranslatableString &label_in_,
|
||
void (Handler::*pmf)(const CommandContext&),
|
||
CommandFlag flags_,
|
||
const CommandManager::Options &options_,
|
||
CommandHandlerFinder finder = FinderScope::DefaultFinder())
|
||
: CommandItem(name_, label_in_,
|
||
static_cast<CommandFunctorPointer>(pmf),
|
||
flags_, options_, finder)
|
||
{}
|
||
|
||
~CommandItem() override;
|
||
|
||
const TranslatableString label_in;
|
||
CommandHandlerFinder finder;
|
||
CommandFunctorPointer callback;
|
||
CommandFlag flags;
|
||
CommandManager::Options options;
|
||
};
|
||
|
||
// Describes several successive commands in a menu that are closely related
|
||
// and dispatch to one common callback, which will be passed a number
|
||
// in the CommandContext identifying the command
|
||
struct CommandGroupItem final : SingleItem {
|
||
CommandGroupItem(const wxString &name_,
|
||
std::vector< ComponentInterfaceSymbol > items_,
|
||
CommandFunctorPointer callback_,
|
||
CommandFlag flags_,
|
||
bool isEffect_,
|
||
CommandHandlerFinder finder_);
|
||
|
||
// Takes a pointer to member function directly, and delegates to the
|
||
// previous constructor; useful within the lifetime of a FinderScope
|
||
template< typename Handler >
|
||
CommandGroupItem(const wxString &name_,
|
||
std::vector< ComponentInterfaceSymbol > items_,
|
||
void (Handler::*pmf)(const CommandContext&),
|
||
CommandFlag flags_,
|
||
bool isEffect_,
|
||
CommandHandlerFinder finder = FinderScope::DefaultFinder())
|
||
: CommandGroupItem(name_, std::move(items_),
|
||
static_cast<CommandFunctorPointer>(pmf),
|
||
flags_, isEffect_, finder)
|
||
{}
|
||
|
||
~CommandGroupItem() override;
|
||
|
||
const std::vector<ComponentInterfaceSymbol> 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 : SingleItem
|
||
{
|
||
using Appender = std::function< void( AudacityProject&, wxMenu& ) >;
|
||
|
||
explicit SpecialItem( const wxString &internalName, const Appender &fn_ )
|
||
: SingleItem{ internalName }
|
||
, fn{ fn_ }
|
||
{}
|
||
~SpecialItem() override;
|
||
|
||
Appender fn;
|
||
};
|
||
|
||
template< bool Transparent >
|
||
struct MenuPart : ConcreteGroupItem< Transparent, MenuVisitor >
|
||
{
|
||
template< typename... Args >
|
||
MenuPart( const wxString &internalName, Args&&... args )
|
||
: ConcreteGroupItem< Transparent, MenuVisitor >{
|
||
internalName, std::forward< Args >( args )... }
|
||
{}
|
||
};
|
||
using MenuItems = MenuPart< true >;
|
||
using MenuSection = MenuPart< false >;
|
||
|
||
// The following, and Shared(), 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.
|
||
// The name is untranslated and may be empty, to make the group transparent
|
||
// in identification of items by path. Otherwise try to keep the name
|
||
// stable across Audacity versions.
|
||
template< typename... Args >
|
||
inline std::unique_ptr< MenuItems > Items(
|
||
const wxString &internalName, Args&&... args )
|
||
{ return std::make_unique< MenuItems >(
|
||
internalName, std::forward<Args>(args)... ); }
|
||
|
||
// Like Items, but insert a menu separator between the menu section and
|
||
// any other items or sections before or after it in the same (innermost,
|
||
// enclosing) menu.
|
||
// It's not necessary that the sisters of sections be other sections, but it
|
||
// might clarify the logical groupings.
|
||
template< typename... Args >
|
||
inline std::unique_ptr< MenuSection > Section(
|
||
const wxString &internalName, Args&&... args )
|
||
{ return std::make_unique< MenuSection >(
|
||
internalName, std::forward<Args>(args)... ); }
|
||
|
||
// Menu items can be constructed two ways, as for group items
|
||
// Items will appear in a main toolbar menu or in a sub-menu.
|
||
// The name is untranslated. Try to keep the name stable across Audacity
|
||
// versions.
|
||
// If the name of a menu is empty, then subordinate items cannot be located
|
||
// by path.
|
||
template< typename... Args >
|
||
inline std::unique_ptr<MenuItem> Menu(
|
||
const wxString &internalName, const TranslatableString &title, Args&&... args )
|
||
{ return std::make_unique<MenuItem>(
|
||
internalName, title, std::forward<Args>(args)... ); }
|
||
inline std::unique_ptr<MenuItem> Menu(
|
||
const wxString &internalName, const TranslatableString &title, BaseItemPtrs &&items )
|
||
{ return std::make_unique<MenuItem>(
|
||
internalName, 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
|
||
// if the condition evaluates false.
|
||
// The name is untranslated. Try to keep the name stable across Audacity
|
||
// versions.
|
||
// Name for conditional group must be non-empty.
|
||
template< typename... Args >
|
||
inline std::unique_ptr<ConditionalGroupItem> ConditionalItems(
|
||
const wxString &internalName,
|
||
ConditionalGroupItem::Condition condition, Args&&... args )
|
||
{ return std::make_unique<ConditionalGroupItem>(
|
||
internalName, condition, std::forward<Args>(args)... ); }
|
||
inline std::unique_ptr<ConditionalGroupItem> ConditionalItems(
|
||
const wxString &internalName, ConditionalGroupItem::Condition condition,
|
||
BaseItemPtrs &&items )
|
||
{ return std::make_unique<ConditionalGroupItem>(
|
||
internalName, condition, std::move( items ) ); }
|
||
|
||
// Make either a menu item or just a group, depending on the nonemptiness
|
||
// of the title.
|
||
// The name is untranslated and may be empty, to make the group transparent
|
||
// in identification of items by path. Otherwise try to keep the name
|
||
// stable across Audacity versions.
|
||
// If the name of a menu is empty, then subordinate items cannot be located
|
||
// by path.
|
||
template< typename... Args >
|
||
inline BaseItemPtr MenuOrItems(
|
||
const wxString &internalName, const TranslatableString &title, Args&&... args )
|
||
{ if ( title.empty() )
|
||
return Items( internalName, std::forward<Args>(args)... );
|
||
else
|
||
return std::make_unique<MenuItem>(
|
||
internalName, title, std::forward<Args>(args)... ); }
|
||
inline BaseItemPtr MenuOrItems(
|
||
const wxString &internalName,
|
||
const TranslatableString &title, BaseItemPtrs &&items )
|
||
{ if ( title.empty() )
|
||
return Items( internalName, std::move( items ) );
|
||
else
|
||
return std::make_unique<MenuItem>(
|
||
internalName, title, std::move( items ) ); }
|
||
|
||
template< typename Handler >
|
||
inline std::unique_ptr<CommandItem> Command(
|
||
const CommandID &name,
|
||
const TranslatableString &label_in,
|
||
void (Handler::*pmf)(const CommandContext&),
|
||
CommandFlag flags, const CommandManager::Options &options = {},
|
||
CommandHandlerFinder finder = FinderScope::DefaultFinder())
|
||
{
|
||
return std::make_unique<CommandItem>(
|
||
name, label_in, pmf, flags, options, finder
|
||
);
|
||
}
|
||
|
||
template< typename Handler >
|
||
inline std::unique_ptr<CommandGroupItem> CommandGroup(
|
||
const wxString &name,
|
||
std::vector< ComponentInterfaceSymbol > items,
|
||
void (Handler::*pmf)(const CommandContext&),
|
||
CommandFlag flags, bool isEffect = false,
|
||
CommandHandlerFinder finder = FinderScope::DefaultFinder())
|
||
{
|
||
return std::make_unique<CommandGroupItem>(
|
||
name, std::move(items), pmf, flags, isEffect, finder
|
||
);
|
||
}
|
||
|
||
inline std::unique_ptr<SpecialItem> Special(
|
||
const wxString &name, const SpecialItem::Appender &fn )
|
||
{ return std::make_unique<SpecialItem>( name, fn ); }
|
||
|
||
// Typically you make a static object of this type in the .cpp file that
|
||
// also defines the added menu actions.
|
||
// pItem can be specified by an expression using the inline functions above.
|
||
struct AttachedItem final
|
||
{
|
||
AttachedItem( const Placement &placement, BaseItemPtr pItem );
|
||
|
||
AttachedItem( const wxString &path, BaseItemPtr pItem )
|
||
// Delegating constructor
|
||
: AttachedItem( Placement{ path }, std::move( pItem ) )
|
||
{}
|
||
};
|
||
|
||
}
|
||
|
||
#endif
|