/********************************************************************** 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 "../Prefs.h" #include "../Registry.h" #include #include "../xml/XMLTagHandler.h" #include "audacity/Types.h" #include class wxMenu; class wxMenuBar; using CommandParameter = CommandID; struct MenuBarListEntry; struct SubMenuListEntry; struct CommandListEntry; using MenuBarList = std::vector < MenuBarListEntry >; using SubMenuList = std::vector < 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>; using CommandKeyHash = std::unordered_map; using CommandNameHash = std::unordered_map; using CommandNumericIDHash = std::unordered_map; 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 AddMenuBar(const wxString & sMenu); wxMenu *BeginMenu(const TranslatableString & tName); void EndMenu(); // type of a function that determines checkmark state using CheckFn = std::function< bool(AudacityProject&) >; // For specifying unusual arguments in AddItem struct Options { 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 &&WantKeyUp () && { wantKeyUp = true; return std::move(*this); } Options &&SkipKeyDown () && { skipKeyDown = true; return std::move(*this); } // This option affects debugging only: Options &&AllowDup () && { allowDup = true; return std::move(*this); } Options &&AllowInMacros ( int value = 1 ) && { allowInMacros = value; 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 }; bool wantKeyUp{ false }; bool skipKeyDown{ false }; bool allowDup{ 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); void RegisterLastAnalyzer(const CommandContext& context); void RegisterLastTool(const CommandContext& context); void DoRepeatProcess(const CommandContext& context, int); enum TextualCommandResult { CommandFailure, CommandSuccess, CommandNotFound }; TextualCommandResult HandleTextualCommand(const CommandID & Str, const CommandContext & context, CommandFlag flags, bool alwaysEnabled); // // Accessing // TranslatableStrings GetCategories( AudacityProject& ); void GetAllCommandNames(CommandIDs &names, bool includeMultis) const; void GetAllCommandLabels( TranslatableStrings &labels, std::vector &vExcludeFromMacros, bool includeMultis) const; void GetAllCommandData( CommandIDs &names, std::vector &keys, std::vector &default_keys, TranslatableStrings &labels, TranslatableStrings &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); TranslatableString 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(_DEBUG) void CheckDups(); #endif void RemoveDuplicateShortcuts(); // // 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 excluded from the standard defaults static const std::vector &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; void UpdateCheckmarks( AudacityProject &project ); 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 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; TranslatableString mNiceName; int mLastProcessId; std::unique_ptr uCurrentMenu; wxMenu *mCurrentMenu {}; bool bMakingOccultCommands; std::unique_ptr< wxMenuBar > mTempMenuBar; }; struct MenuVisitor : Registry::Visitor { // final overrides void BeginGroup( Registry::GroupItem &item, const Path &path ) final; void EndGroup( Registry::GroupItem &item, const Path& ) final; void Visit( Registry::SingleItem &item, const Path &path ) final; // added virtuals virtual void DoBeginGroup( Registry::GroupItem &item, const Path &path ); virtual void DoEndGroup( Registry::GroupItem &item, const Path &path ); virtual void DoVisit( Registry::SingleItem &item, const Path &path ); virtual void DoSeparator(); private: void MaybeDoSeparator(); std::vector firstItem; std::vector needSeparator; }; struct ToolbarMenuVisitor : MenuVisitor { explicit ToolbarMenuVisitor( 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; // These are found by dynamic_cast struct MenuSection { virtual ~MenuSection(); }; struct WholeMenu { WholeMenu( bool extend = false ) : extension{ extend } {} virtual ~WholeMenu(); bool extension; }; // Describes a main menu in the toolbar, or a sub-menu struct MenuItem final : ConcreteGroupItem< false, ToolbarMenuVisitor > , WholeMenu { // Construction from an internal name and a previously built-up // vector of pointers MenuItem( const Identifier &internalName, const TranslatableString &title_, BaseItemPtrs &&items_ ); // In-line, variadic constructor that doesn't require building a vector template< typename... Args > MenuItem( const Identifier &internalName, const TranslatableString &title_, Args&&... args ) : ConcreteGroupItem< false, ToolbarMenuVisitor >{ internalName, std::forward(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, ToolbarMenuVisitor > { using Condition = std::function< bool() >; // Construction from an internal name and a previously built-up // vector of pointers ConditionalGroupItem( const Identifier &internalName, Condition condition_, BaseItemPtrs &&items_ ); // In-line, variadic constructor that doesn't require building a vector template< typename... Args > ConditionalGroupItem( const Identifier &internalName, Condition condition_, Args&&... args ) : ConcreteGroupItem< false, ToolbarMenuVisitor >{ internalName, std::forward(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(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 Identifier &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 Identifier &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(pmf), flags_, isEffect_, finder) {} ~CommandGroupItem() override; const std::vector 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 Identifier &internalName, const Appender &fn_ ) : SingleItem{ internalName } , fn{ fn_ } {} ~SpecialItem() override; Appender fn; }; struct MenuPart : ConcreteGroupItem< false, ToolbarMenuVisitor >, MenuSection { template< typename... Args > explicit MenuPart( const Identifier &internalName, Args&&... args ) : ConcreteGroupItem< false, ToolbarMenuVisitor >{ internalName, std::forward< Args >( args )... } {} }; using MenuItems = ConcreteGroupItem< true, ToolbarMenuVisitor >; // 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 Identifier &internalName, Args&&... args ) { return std::make_unique< MenuItems >( internalName, std::forward(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< MenuPart > Section( const Identifier &internalName, Args&&... args ) { return std::make_unique< MenuPart >( internalName, std::forward(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 Menu( const Identifier &internalName, const TranslatableString &title, Args&&... args ) { return std::make_unique( internalName, title, std::forward(args)... ); } inline std::unique_ptr Menu( const Identifier &internalName, const TranslatableString &title, BaseItemPtrs &&items ) { return std::make_unique( 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 ConditionalItems( const Identifier &internalName, ConditionalGroupItem::Condition condition, Args&&... args ) { return std::make_unique( internalName, condition, std::forward(args)... ); } inline std::unique_ptr ConditionalItems( const Identifier &internalName, ConditionalGroupItem::Condition condition, BaseItemPtrs &&items ) { return std::make_unique( 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 Identifier &internalName, const TranslatableString &title, Args&&... args ) { if ( title.empty() ) return Items( internalName, std::forward(args)... ); else return std::make_unique( internalName, title, std::forward(args)... ); } inline BaseItemPtr MenuOrItems( const Identifier &internalName, const TranslatableString &title, BaseItemPtrs &&items ) { if ( title.empty() ) return Items( internalName, std::move( items ) ); else return std::make_unique( internalName, title, std::move( items ) ); } template< typename Handler > inline std::unique_ptr 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( name, label_in, pmf, flags, options, finder ); } template< typename Handler > inline std::unique_ptr CommandGroup( const Identifier &name, std::vector< ComponentInterfaceSymbol > items, void (Handler::*pmf)(const CommandContext&), CommandFlag flags, bool isEffect = false, CommandHandlerFinder finder = FinderScope::DefaultFinder()) { return std::make_unique( name, std::move(items), pmf, flags, isEffect, finder ); } inline std::unique_ptr Special( const Identifier &name, const SpecialItem::Appender &fn ) { return std::make_unique( 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 ) ) {} }; void DestroyRegistry(); } #endif