87#include <wx/evtloop.h>
93#include <wx/tokenzr.h>
95#include "../ActiveProject.h"
96#include "../Journal.h"
97#include "../JournalOutput.h"
98#include "../JournalRegistry.h"
102#include "../widgets/AudacityMessageBox.h"
103#include "../widgets/HelpSystem.h"
111#define MAX_MENU_LEN 20
112#define MAX_SUBMENU_LEN 15
114#define MAX_MENU_LEN 1000
115#define MAX_SUBMENU_LEN 1000
118#define COMMAND XO("Command")
183 :
name(name_), menubar(menubar_)
192 :
name(name_), menu(
std::make_unique< wxMenu >() )
203 return std::make_unique<CommandManager>();
223 bMakingOccultCommands( false )
241 static const auto list = [] {
243 const char *
const strings[] = {
296 std::vector<NormalizedKeyString> result(
299 std::sort( result.begin(), result.end() );
320 bool bFull =
gPrefs->ReadBool(wxT(
"/GUI/Shortcuts/FullDefaults"),
false);
359 auto result = std::make_unique<wxMenuBar>();
373 if(
entry.name == sMenu)
374 return entry.menubar;
513 if(!tmpCurrentSubMenu)
518 return tmpCurrentSubMenu;
543 name, label_in, finder, callback, options );
562 auto &checker = options.
checker;
575 const wxString
key,
bool defaultValue ) ->
CheckFn
583 return MakeCheckFn( setting.GetPath(), setting.GetDefault() );
600 for (
size_t i = 0, cnt = nItems; i < cnt; i++) {
612 entry->flags = flags;
628 entry->enabled =
false;
629 entry->isGlobal =
true;
645 if((ID >= wxID_LOWEST) && (ID <= wxID_HIGHEST))
666 bool excludeFromMacros =
670 const wxString & accel = options.
accel;
675 const auto &longLabel = options.
longName;
677 const bool multi = !nameSuffix.
empty();
689 auto entry = std::make_unique<CommandListEntry>();
712 entry->parameter = parameter;
714#if defined(__WXMAC__)
717 if (
name == wxT(
"Preferences"))
718 entry->id = wxID_PREFERENCES;
719 else if (
name == wxT(
"Exit"))
720 entry->id = wxID_EXIT;
721 else if (
name == wxT(
"About"))
722 entry->id = wxID_ABOUT;
729 entry->longLabel = longLabel.empty() ?
label : longLabel;
731 entry->excludeFromMacros = excludeFromMacros;
734 entry->labelPrefix = labelPrefix;
737 entry->finder = finder;
738 entry->callback = callback;
739 entry->isEffect = bIsEffect;
740 entry->multi = multi;
741 entry->index = index;
742 entry->count = count;
744 entry->enabled =
true;
748 entry->isGlobal =
false;
763 const auto &path =
entry->name.GET();
786 wxLogDebug(wxT(
"Command '%s' defined by '%s' and '%s'"),
790 entry->label.Debug());
791 wxFAIL_MSG(wxString::Format(wxT(
"Command '%s' defined by '%s' and '%s'"),
796 entry->label.Debug()));
802 if (!
entry->key.empty()) {
814 if (
auto pEntry = iter->second) {
815 keyStr = pEntry->key;
817 pLabel = &pEntry->label;
856 if (!
entry->key.empty())
863 Accel = wxString(
"\t ") +
key;
864 if(
key.StartsWith(
"Left" ))
break;
865 if(
key.StartsWith(
"Right"))
break;
866 if(
key.StartsWith(
"Up" ))
break;
867 if(
key.StartsWith(
"Down"))
break;
868 if(
key.StartsWith(
"Return"))
break;
869 if(
key.StartsWith(
"Tab"))
break;
870 if(
key.StartsWith(
"Shift+Tab"))
break;
871 if(
key.StartsWith(
"0"))
break;
872 if(
key.StartsWith(
"1"))
break;
873 if(
key.StartsWith(
"2"))
break;
874 if(
key.StartsWith(
"3"))
break;
875 if(
key.StartsWith(
"4"))
break;
876 if(
key.StartsWith(
"5"))
break;
877 if(
key.StartsWith(
"6"))
break;
878 if(
key.StartsWith(
"7"))
break;
879 if(
key.StartsWith(
"8"))
break;
880 if(
key.StartsWith(
"9"))
break;
885 if(
key.StartsWith(
"NUMPAD_ENTER" ))
break;
886 if(
key.StartsWith(
"Backspace" ))
break;
887 if(
key.StartsWith(
"Delete" ))
break;
892 Accel = wxString(
"\t") +
entry->key.GET();
907 entry->enabled = enabled;
917 if (
entry->enabled != enabled) {
926 for(i=1; i<
entry->count; i++) {
933 wxMenuItem *item = multiEntry->
menu->FindItem(ID);
936 item->Enable(enabled);
939 wxLogDebug(wxT(
"Warning: Menu entry with id %i in %s not found"),
940 ID,
entry->name.GET());
943 wxLogDebug(wxT(
"Warning: Menu entry with id %i not in hash"), ID);
953 wxLogDebug(wxT(
"Warning: Unknown command enabled: '%s'"),
954 (
const wxChar*)
name);
968 wxASSERT( (strictFlags & ~flags).
none() );
973 if(
entry->isOccult )
976 auto useFlags =
entry->useStrictFlags ? strictFlags : flags;
978 if (
entry->flags.any()) {
979 bool enable = ((useFlags &
entry->flags) ==
entry->flags);
990 wxLogDebug(wxT(
"Warning: command doesn't exist: '%s'"),
994 return entry->enabled;
1016 entry->label = newLabel;
1042 bool rtl = (wxLayout_RightToLeft == wxTheApp->GetLayoutDirection());
1044 mark = wxT(
"\u200f");
1046 static const wxString &separatorFormat = wxT(
"%s / %s");
1048 for (
size_t ii = 0; ii < nCommands; ++ii) {
1049 const auto &pair = commands[ii];
1058 auto piece =
Verbatim( wxT(
"%s%s") )
1059 .
Format( mark, pair.Msgid().Stripped() );
1061 auto name = pair.Internal();
1064 if (!keyStr.empty()){
1065 auto keyString = keyStr.Display(
true);
1066 auto format = wxT(
"%s %s(%s)");
1073 format = wxT(
"%s %s(\u202a%s\u202c)");
1104 int type = evt.GetEventType();
1107 if (
entry->isGlobal && type == wxEVT_KEY_DOWN)
1113 entry->enabled =
false;
1118 wxWindow * pFocus = wxWindow::FindFocus();
1119 wxWindow * pParent = wxGetTopLevelParent( pFocus );
1120 bool validTarget = pParent == pWindow;
1123 if( pParent && pParent->GetParent() == pWindow ){
1127 validTarget = validTarget && wxEventLoop::GetActive()->IsMain();
1130 if (!permit && !validTarget )
1137 wxKeyEvent temp = evt;
1141 if((type == wxEVT_KEY_DOWN) || (type == wxEVT_KEY_UP ))
1143 wxWindow * pWnd = wxWindow::FindFocus();
1153 switch( evt.GetKeyCode() ){
1166 case WXK_NUMPAD_ENTER:
1183 if (type == wxEVT_KEY_DOWN)
1185 if (
entry->skipKeydown)
1192 if (type == wxEVT_KEY_UP &&
entry->wantKeyup)
1210 bool handled =
false;
1211 if ( fields.size() == 2 ) {
1216 auto &command = fields[1];
1218 pManager->HandleTextualCommand( command, context, flags,
false );
1233 CommandFlag flags,
bool alwaysEnabled,
const wxEvent * evt,
1242 if (!alwaysEnabled &&
entry->flags.any()) {
1244 const auto NiceName =
entry->label.Stripped(
1249 NiceName, flags,
entry->flags );
1266 auto &handler =
entry->finder(project);
1267 (handler.*(
entry->callback))(context);
1279 auto lastEffectDesc =
XO(
"Repeat %s").Format(
mNiceName);
1280 Modify(wxT(
"RepeatLastAnalyzer"), lastEffectDesc);
1292 auto lastEffectDesc =
XO(
"Repeat %s").Format(
mNiceName);
1293 Modify(wxT(
"RepeatLastTool"), lastEffectDesc);
1303 (handler.*(
entry->callback))(context);
1339 if( Str ==
entry->name ||
1343 Str ==
entry->labelPrefix.Translation() )
1354 if( Str ==
entry->name )
1371 auto &cat =
entry->labelTop;
1373 cats.push_back(cat);
1386 wxMenuBar *bar = p->GetMenuBar();
1387 size_t cnt = bar->GetMenuCount();
1388 for (
size_t i = 0; i < cnt; i++) {
1389 cats.push_back(bar->GetMenuLabelText(i));
1399 bool includeMultis)
const
1402 if (
entry->isEffect )
1406 else if( includeMultis )
1412 std::vector<bool> &vExcludeFromMacros,
1413 bool includeMultis)
const
1415 vExcludeFromMacros.clear();
1421 if (
entry->isEffect )
1424 names.push_back(
entry->longLabel), vExcludeFromMacros.push_back(
entry->excludeFromMacros);
1425 else if( includeMultis )
1426 names.push_back(
entry->longLabel), vExcludeFromMacros.push_back(
entry->excludeFromMacros);
1432 std::vector<NormalizedKeyString> &keys,
1433 std::vector<NormalizedKeyString> &default_keys,
1436#
if defined(EXPERIMENTAL_KEY_VIEW)
1446 if ( !
entry->multi || includeMultis )
1449 keys.push_back(
entry->key);
1450 default_keys.push_back(
entry->defaultKey);
1451 labels.push_back(
entry->label);
1452 categories.push_back(
entry->labelTop);
1453#if defined(EXPERIMENTAL_KEY_VIEW)
1454 prefixes.push_back(
entry->labelPrefix);
1474 return entry->longLabel;
1483 if (!
entry->labelPrefix.empty())
1488 return entry->label.Stripped();
1497 return entry->labelTop;
1517 return entry->defaultKey;
1522 if (tag ==
"audacitykeyboard") {
1526 if (tag ==
"command") {
1530 for (
auto pair : attrs)
1532 auto attr = pair.first;
1533 auto value = pair.second;
1535 if (value.IsStringView())
1537 const wxString strValue = value.ToWString();
1541 else if (attr ==
"key")
1577 xmlFile.StartTag(wxT(
"audacitykeyboard"));
1578 xmlFile.WriteAttr(wxT(
"audacityversion"), AUDACITY_VERSION_STRING);
1580 for(
const auto &
entry : mCommandList) {
1582 xmlFile.StartTag(wxT(
"command"));
1583 xmlFile.WriteAttr(wxT(
"name"),
entry->name);
1584 xmlFile.WriteAttr(wxT(
"key"),
entry->key);
1585 xmlFile.EndTag(wxT(
"command"));
1588 xmlFile.EndTag(wxT(
"audacitykeyboard"));
1616 entry->flags = flags;
1620void CommandManager::CheckDups()
1623 for (
size_t j = 0; (int)j < cnt; j++) {
1631 for (
size_t i = 0; (int)i < cnt; i++) {
1638 msg.Printf(wxT(
"key combo '%s' assigned to '%s' and '%s'"),
1672 if (!entry2->key.empty() && entry2->key == entry2->defaultKey) {
1673 if (entry2->key ==
entry->key) {
1674 auto name = wxT(
"/NewKeys/") + entry2->name.GET();
1677 disabledShortcuts +=
1678 XO(
"\n* %s, because you have assigned the shortcut %s to %s")
1679 .Format(entry2->label.Strip(),
entry->key.GET(),
entry->label.Strip());
1688 " because their default shortcut is new or changed, and is the same shortcut"
1689 " that you have assigned to another command.")
1690 + disabledShortcuts;
1698#include "../KeyboardCapture.h"
1700static KeyboardCapture::PreFilter::Scope
scope1{
1707static KeyboardCapture::PostFilter::Scope
scope2{
1708[]( wxKeyEvent &
key ) {
1712 return manager.FilterKeyEvent(project.get(),
key);
AUDACITY_DLL_API std::weak_ptr< AudacityProject > GetActiveProject()
@ Internal
Indicates internal failure from Audacity.
int AudacityMessageBox(const TranslatableString &message, const TranslatableString &caption, long style, wxWindow *parent, int x, int y)
constexpr CommandFlag AlwaysEnabledFlag
std::bitset< NCommandFlags > CommandFlag
constexpr CommandFlag NoFlagsSpecified
void(CommandHandlerObject::*)(const CommandContext &context) CommandFunctorPointer
std::function< CommandHandlerObject &(AudacityProject &) > CommandHandlerFinder
static KeyboardCapture::PostFilter::Scope scope2
static const AudacityProject::AttachedObjects::RegisteredFactory key
static KeyboardCapture::PreFilter::Scope scope1
const TranslatableString name
std::vector< CommandID > CommandIDs
NormalizedKeyString KeyEventToKeyString(const wxKeyEvent &event)
ValueRestorer< T > valueRestorer(T &var)
inline functions provide convenient parameter type deduction
IteratorRange< Iterator > make_iterator_range(const Iterator &i1, const Iterator &i2)
static ProjectFileIORegistry::AttributeWriterEntry entry
wxFrame * FindProjectFrame(AudacityProject *project)
Get a pointer to the window associated with a project, or null if the given pointer is null,...
AUDACITY_DLL_API wxFrame & GetProjectFrame(AudacityProject &project)
Get the top-level window associated with the project (as a wxFrame only, when you do not need to use ...
accessors for certain important windows associated with each project
static const AttachedProjectObjects::RegisteredFactory manager
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
std::vector< Attribute > AttributesList
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
CommandContext provides additional information to an 'Apply()' command. It provides the project,...
TemporarySelection temporarySelection
AudacityProject & project
CommandManager implements a system for organizing all user-callable commands.
void WriteXML(XMLWriter &xmlFile) const
void Enable(const wxString &name, bool enabled)
void HandleXMLEndTag(const std::string_view &tag) override
void AddItemList(const CommandID &name, const ComponentInterfaceSymbol items[], size_t nItems, CommandHandlerFinder finder, CommandFunctorPointer callback, CommandFlag flags, bool bIsEffect=false)
void AddGlobalCommand(const CommandID &name, const TranslatableString &label, CommandHandlerFinder finder, CommandFunctorPointer callback, const Options &options={})
void RegisterLastTool(const CommandContext &context)
wxMenu * CurrentSubMenu() const
CommandListEntry * NewIdentifier(const CommandID &name, const TranslatableString &label, wxMenu *menu, CommandHandlerFinder finder, CommandFunctorPointer callback, const CommandID &nameSuffix, int index, int count, const Options &options)
wxMenu * BeginMenu(const TranslatableString &tName)
wxString FormatLabelForMenu(const CommandID &id, const TranslatableString *pLabel) const
Format a string appropriate for insertion in a menu.
CommandNameHash mCommandNameHash
void BeginOccultCommands()
wxString FormatLabelWithDisabledAccel(const CommandListEntry *entry) const
void UpdateCheckmarks(AudacityProject &project)
bool HandleMenuID(AudacityProject &project, int id, CommandFlag flags, bool alwaysEnabled)
TranslatableStrings GetCategories(AudacityProject &)
std::unique_ptr< wxMenuBar > mTempMenuBar
TranslatableString mCurrentMenuName
TranslatableString DescribeCommandsAndShortcuts(const ComponentInterfaceSymbol commands[], size_t nCommands) const
bool FilterKeyEvent(AudacityProject *project, const wxKeyEvent &evt, bool permit=false)
wxMenu * CurrentMenu() const
void DoRepeatProcess(const CommandContext &context, int)
XMLTagHandler * HandleXMLChild(const std::string_view &tag) override
std::vector< NormalizedKeyString > mMaxListOnly
wxMenuBar * CurrentMenuBar() const
void Modify(const wxString &name, const TranslatableString &newLabel)
Changes the label text of a menu item.
void RegisterLastAnalyzer(const CommandContext &context)
static CommandManager & Get(AudacityProject &project)
std::unique_ptr< wxMenu > uCurrentMenu
void EndMenu()
This attaches a menu, if it's main, to the menubar.
wxMenu * BeginSubMenu(const TranslatableString &tName)
void RemoveDuplicateShortcuts()
void GetAllCommandLabels(TranslatableStrings &labels, std::vector< bool > &vExcludeFromMacros, bool includeMultis) const
void AddItem(AudacityProject &project, const CommandID &name, const TranslatableString &label_in, CommandHandlerFinder finder, CommandFunctorPointer callback, CommandFlag flags, const Options &options={})
TextualCommandResult HandleTextualCommand(const CommandID &Str, const CommandContext &context, CommandFlag flags, bool alwaysEnabled)
wxMenu * BeginMainMenu(const TranslatableString &tName)
TranslatableString GetLabelFromName(const CommandID &name)
bool bMakingOccultCommands
int NextIdentifier(int ID)
TranslatableString GetCategoryFromName(const CommandID &name)
void GetAllCommandData(CommandIDs &names, std::vector< NormalizedKeyString > &keys, std::vector< NormalizedKeyString > &default_keys, TranslatableStrings &labels, TranslatableStrings &categories, bool includeMultis)
static const std::vector< NormalizedKeyString > & ExcludedList()
CommandKeyHash mCommandKeyHash
std::function< bool(AudacityProject &) > CheckFn
void SetCommandFlags(const CommandID &name, CommandFlag flags)
bool GetEnabled(const CommandID &name)
void SetKeyFromName(const CommandID &name, const NormalizedKeyString &key)
NormalizedKeyString GetKeyFromName(const CommandID &name) const
void Check(const CommandID &name, bool checked)
bool HandleXMLTag(const std::string_view &tag, const AttributesList &attrs) override
wxMenuBar * GetMenuBar(const wxString &sMenu) const
CommandID GetNameFromNumericID(int id)
void GetAllCommandNames(CommandIDs &names, bool includeMultis) const
void EnableUsingFlags(CommandFlag flags, CommandFlag strictFlags)
NormalizedKeyString GetDefaultKeyFromName(const CommandID &name)
bool HandleCommandEntry(AudacityProject &project, const CommandListEntry *entry, CommandFlag flags, bool alwaysEnabled, const wxEvent *evt=nullptr, const CommandContext *pGivenContext=nullptr)
virtual ~CommandManager()
void SetKeyFromIndex(int i, const NormalizedKeyString &key)
int GetNumberOfKeysRead() const
std::unique_ptr< wxMenuBar > AddMenuBar(const wxString &sMenu)
TranslatableString GetPrefixedLabelFromName(const CommandID &name)
TranslatableString mNiceName
CommandNumericIDHash mCommandNumericIDHash
ComponentInterfaceSymbol pairs a persistent string identifier used internally with an optional,...
virtual bool HasEntry(const wxString &strName) const wxOVERRIDE
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
virtual void SetPath(const wxString &strPath) wxOVERRIDE
static result_type Call(Arguments &&...arguments)
Null check of the installed function is done for you.
const wxString & GET() const
Explicit conversion to wxString, meant to be ugly-looking and demanding of a comment why it's correct...
Holds a msgid for the translation catalog; may also bind format arguments.
Identifier MSGID() const
MSGID is the English lookup key in the catalog, not necessarily for user's eyes if locale is some oth...
wxString Translation() const
TranslatableString & Format(Args &&...args) &
Capture variadic format arguments (by copy) when there is no plural.
wxString Debug() const
Format as an English string for debugging logs and developers' eyes, not for end users.
TranslatableString Stripped(unsigned options=MenuCodes) const
non-mutating, constructs another TranslatableString object
This class is an interface which should be implemented by classes which wish to be able to load and s...
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Extend wxArrayString with move operations and construction and insertion fromstd::initializer_list.
void Output(const wxString &string)
auto end(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
constexpr auto JournalCode
Journal::RegisteredCommand sCommand
CommandListEntry is a structure used by CommandManager.
TranslatableString longLabel
std::function< bool(AudacityProject &) > CheckFn
CommandParameter parameter
CommandHandlerFinder finder
TranslatableString labelTop
NormalizedKeyString defaultKey
TranslatableString labelPrefix
CommandFunctorPointer callback
TranslatableString longName
static CheckFn MakeCheckFn(const wxString key, bool defaultValue)
Options && IsEffect(bool value=true) &&
CommandParameter parameter
virtual ~NonKeystrokeInterceptingWindow()
virtual ~TopLevelKeystrokeHandlingWindow()