Audacity 3.2.0
CommandManager.cpp
Go to the documentation of this file.
1/**********************************************************************
2
3 Audacity: A Digital Audio Editor
4
5 CommandManager.cpp
6
7 Brian Gunlogson
8 Dominic Mazzoni
9
10*******************************************************************//****************************************************************//****************************************************************//****************************************************************//****************************************************************//****************************************************************//****************************************************************//****************************************************************//******************************************************************/
77
78
79
80#include "CommandManager.h"
81
82#include "CommandContext.h"
84
85#include <wx/app.h>
86#include <wx/defs.h>
87#include <wx/evtloop.h>
88#include <wx/frame.h>
89#include <wx/hash.h>
90#include <wx/log.h>
91#include <wx/menu.h>
92
93#include "../ActiveProject.h"
94#include "../Journal.h"
95#include "../JournalOutput.h"
96#include "../JournalRegistry.h"
97#include "../Menus.h"
98#include "Project.h"
99#include "ProjectWindows.h"
100#include "../widgets/AudacityMessageBox.h"
101#include "../widgets/HelpSystem.h"
102
103
104// On wxGTK, there may be many many many plugins, but the menus don't automatically
105// allow for scrolling, so we build sub-menus. If the menu gets longer than
106// MAX_MENU_LEN, we put things in submenus that have MAX_SUBMENU_LEN items in them.
107//
108#ifdef __WXGTK__
109#define MAX_MENU_LEN 20
110#define MAX_SUBMENU_LEN 15
111#else
112#define MAX_MENU_LEN 1000
113#define MAX_SUBMENU_LEN 1000
114#endif
115
116#define COMMAND XO("Command")
117
118
120{
121 MenuBarListEntry(const wxString &name_, wxMenuBar *menubar_);
123
124 wxString name;
125 wxWeakRef<wxMenuBar> menubar; // This structure does not assume memory ownership!
126};
127
129{
130 SubMenuListEntry( const TranslatableString &name_ );
133
135 std::unique_ptr<wxMenu> menu;
136};
137
139{
140 int id;
148 wxMenu *menu;
152
153 // type of a function that determines checkmark state
154 using CheckFn = std::function< bool(AudacityProject&) >;
156
157 bool multi;
158 int index;
159 int count;
169 bool useStrictFlags{ false };
170};
171
173
175
177{
178 return true;
179}
180
181MenuBarListEntry::MenuBarListEntry(const wxString &name_, wxMenuBar *menubar_)
182 : name(name_), menubar(menubar_)
183{
184}
185
187{
188}
189
191 : name(name_), menu( std::make_unique< wxMenu >() )
192{
193}
194
196{
197}
198
201 [](AudacityProject&) {
202 return std::make_unique<CommandManager>();
203 }
204};
205
207{
208 return project.AttachedObjects::Get< CommandManager >( key );
209}
210
212{
213 return Get( const_cast< AudacityProject & >( project ) );
214}
215
220 mCurrentID(17000),
221 mCurrentMenuName(COMMAND),
222 bMakingOccultCommands( false )
223{
224 mbSeparatorAllowed = false;
225 SetMaxList();
226 mLastProcessId = 0;
227}
228
233{
234 //WARNING: This removes menubars that could still be assigned to windows!
235 PurgeData();
236}
237
238const std::vector<NormalizedKeyString> &CommandManager::ExcludedList()
239{
240 static const auto list = [] {
241 // These short cuts are for the max list only....
242 const char *const strings[] = {
243 // "Ctrl+I",
244 "Ctrl+Alt+I",
245 "Ctrl+J",
246 "Ctrl+Alt+J",
247 "Ctrl+Alt+V",
248 "Alt+X",
249 "Alt+K",
250 "Shift+Alt+X",
251 "Shift+Alt+K",
252 "Alt+L",
253 "Shift+Alt+C",
254 "Alt+I",
255 "Alt+J",
256 "Shift+Alt+J",
257 "Ctrl+Shift+A",
258 //"Q",
259 //"Shift+J",
260 //"Shift+K",
261 //"Shift+Home",
262 //"Shift+End",
263 "Ctrl+[",
264 "Ctrl+]",
265 "1",
266 "Shift+F5",
267 "Shift+F6",
268 "Shift+F7",
269 "Shift+F8",
270 "Ctrl+Shift+F5",
271 "Ctrl+Shift+F7",
272 "Ctrl+Shift+N",
273 "Ctrl+Shift+M",
274 "Ctrl+Home",
275 "Ctrl+End",
276 "Shift+C",
277 "Alt+Shift+Up",
278 "Alt+Shift+Down",
279 "Shift+P",
280 "Alt+Shift+Left",
281 "Alt+Shift+Right",
282 "Ctrl+Shift+T",
283 //"Command+M",
284 //"Option+Command+M",
285 "Shift+H",
286 "Shift+O",
287 "Shift+I",
288 "Shift+N",
289 "D",
290 "A",
291 "Alt+Shift+F6",
292 "Alt+F6",
293 };
294
295 std::vector<NormalizedKeyString> result(
296 std::begin(strings), std::end(strings)
297 );
298 std::sort( result.begin(), result.end() );
299 return result;
300 }();
301 return list;
302}
303
304// CommandManager needs to know which defaults are standard and which are in the
305// full (max) list.
307{
308
309 // This list is a DUPLICATE of the list in
310 // KeyConfigPrefs::OnImportDefaults(wxCommandEvent & event)
311
312 // TODO: At a later date get rid of the maxList entirely and
313 // instead use flags in the menu entries to indicate whether the default
314 // shortcut is standard or full.
315
316 mMaxListOnly.clear();
317
318 // if the full list, don't exclude any.
319 bool bFull = gPrefs->ReadBool(wxT("/GUI/Shortcuts/FullDefaults"),false);
320 if( bFull )
321 return;
322
324}
325
326
328{
329 // mCommandList contains pointers to CommandListEntrys
330 // mMenuBarList contains MenuBarListEntrys.
331 // mSubMenuList contains SubMenuListEntrys
332 mCommandList.clear();
333 mMenuBarList.clear();
334 mSubMenuList.clear();
335
336 mCommandNameHash.clear();
337 mCommandKeyHash.clear();
338 mCommandNumericIDHash.clear();
339
341 mCurrentID = 17000;
342}
343
344
350std::unique_ptr<wxMenuBar> CommandManager::AddMenuBar(const wxString & sMenu)
351{
352 wxMenuBar *menuBar = GetMenuBar(sMenu);
353 if (menuBar) {
354 wxASSERT(false);
355 return {};
356 }
357
358 auto result = std::make_unique<wxMenuBar>();
359 mMenuBarList.emplace_back(sMenu, result.get());
360
361 return result;
362}
363
364
368wxMenuBar * CommandManager::GetMenuBar(const wxString & sMenu) const
369{
370 for (const auto &entry : mMenuBarList)
371 {
372 if(entry.name == sMenu)
373 return entry.menubar;
374 }
375
376 return NULL;
377}
378
379
384{
385 if(mMenuBarList.empty())
386 return NULL;
387
388 return mMenuBarList.back().menubar;
389}
390
397{
398 auto iter = mMenuBarList.end();
399 if ( iter != mMenuBarList.begin() )
400 mMenuBarList.erase( --iter );
401 else
402 wxASSERT( false );
403}
404
405
410{
411 if ( mCurrentMenu )
412 return BeginSubMenu( tName );
413 else
414 return BeginMainMenu( tName );
415}
416
417
420// and in all cases ends the menu
423{
424 if ( mSubMenuList.empty() )
425 EndMainMenu();
426 else
427 EndSubMenu();
428}
429
430
435{
436 uCurrentMenu = std::make_unique<wxMenu>();
438 mCurrentMenuName = tName;
439 return mCurrentMenu;
440}
441
442
447{
448 // Add the menu to the menubar after all menu items have been
449 // added to the menu to allow OSX to rearrange special menu
450 // items like Preferences, About, and Quit.
451 wxASSERT(uCurrentMenu);
452 CurrentMenuBar()->Append(
454 mCurrentMenu = nullptr;
456}
457
458
463{
464 mSubMenuList.emplace_back( tName );
465 mbSeparatorAllowed = false;
466 return mSubMenuList.back().menu.get();
467}
468
469
475{
476 //Save the submenu's information
477 SubMenuListEntry tmpSubMenu{ std::move( mSubMenuList.back() ) };
478
479 //Pop off the NEW submenu so CurrentMenu returns the parent of the submenu
480 mSubMenuList.pop_back();
481
482 //Add the submenu to the current menu
483 auto name = tmpSubMenu.name.Translation();
484 CurrentMenu()->Append(0, name, tmpSubMenu.menu.release(),
485 name /* help string */ );
486 mbSeparatorAllowed = true;
487}
488
489
494{
495 if(mSubMenuList.empty())
496 return NULL;
497
498 return mSubMenuList.back().menu.get();
499}
500
506{
507 if(!mCurrentMenu)
508 return NULL;
509
510 wxMenu * tmpCurrentSubMenu = CurrentSubMenu();
511
512 if(!tmpCurrentSubMenu)
513 {
514 return mCurrentMenu;
515 }
516
517 return tmpCurrentSubMenu;
518}
519
521{
522 for ( const auto &entry : mCommandList ) {
523 if ( entry->menu && entry->checkmarkFn && !entry->isOccult) {
524 entry->menu->Check( entry->id, entry->checkmarkFn( project ) );
525 }
526 }
527}
528
529
530
532 const CommandID &name,
533 const TranslatableString &label_in,
535 CommandFunctorPointer callback,
536 CommandFlag flags,
537 const Options &options)
538{
539 if (options.global) {
540 //wxASSERT( flags == AlwaysEnabledFlag );
542 name, label_in, finder, callback, options );
543 return;
544 }
545
546 wxASSERT( flags != NoFlagsSpecified );
547
550 label_in,
551 CurrentMenu(), finder, callback,
552 {}, 0, 0,
553 options);
554 entry->useStrictFlags = options.useStrictFlags;
555 int ID = entry->id;
557
558 SetCommandFlags(name, flags);
559
560
561 auto &checker = options.checker;
562 if (checker) {
563 CurrentMenu()->AppendCheckItem(ID, label);
564 CurrentMenu()->Check(ID, checker( project ));
565 }
566 else {
567 CurrentMenu()->Append(ID, label);
568 }
569
570 mbSeparatorAllowed = true;
571}
572
574 const wxString key, bool defaultValue ) -> CheckFn
575{
576 return [=](AudacityProject&){ return gPrefs->ReadBool( key, defaultValue ); };
577}
578
580 const BoolSetting &setting ) -> CheckFn
581{
582 return MakeCheckFn( setting.GetPath(), setting.GetDefault() );
583}
584
592 const ComponentInterfaceSymbol items[],
593 size_t nItems,
595 CommandFunctorPointer callback,
596 CommandFlag flags,
597 bool bIsEffect)
598{
599 for (size_t i = 0, cnt = nItems; i < cnt; i++) {
602 items[i].Msgid(),
603 CurrentMenu(),
604 finder,
605 callback,
606 items[i].Internal(),
607 i,
608 cnt,
609 Options{}
610 .IsEffect(bIsEffect));
611 entry->flags = flags;
612 CurrentMenu()->Append(entry->id, FormatLabelForMenu(entry));
613 mbSeparatorAllowed = true;
614 }
615}
616
618 const TranslatableString &label_in,
620 CommandFunctorPointer callback,
621 const Options &options)
622{
624 NewIdentifier(name, label_in, NULL, finder, callback,
625 {}, 0, 0, options);
626
627 entry->enabled = false;
628 entry->isGlobal = true;
629 entry->flags = AlwaysEnabledFlag;
630}
631
633{
635 CurrentMenu()->AppendSeparator();
636 mbSeparatorAllowed = false; // boolean to prevent too many separators.
637}
638
640{
641 ID++;
642
643 //Skip the reserved identifiers used by wxWidgets
644 if((ID >= wxID_LOWEST) && (ID <= wxID_HIGHEST))
645 ID = wxID_HIGHEST+1;
646
647 return ID;
648}
649
657 wxMenu *menu,
659 CommandFunctorPointer callback,
660 const CommandID &nameSuffix,
661 int index,
662 int count,
663 const Options &options)
664{
665 bool excludeFromMacros =
666 (options.allowInMacros == 0) ||
667 ((options.allowInMacros == -1) && label.MSGID().GET().Contains("..."));
668
669 const wxString & accel = options.accel;
670 bool bIsEffect = options.bIsEffect;
671 CommandID parameter = options.parameter == "" ? nameIn : options.parameter;
672
673 // if empty, new identifier's long label will be same as label, below:
674 const auto &longLabel = options.longName;
675
676 const bool multi = !nameSuffix.empty();
677 auto name = nameIn;
678
679 // If we have the identifier already, reuse it.
681 if (!prev);
682 else if( prev->label != label );
683 else if( multi );
684 else
685 return prev;
686
687 {
688 auto entry = std::make_unique<CommandListEntry>();
689
690 TranslatableString labelPrefix;
691 if (!mSubMenuList.empty())
692 labelPrefix = mSubMenuList.back().name.Stripped();
693
694 // For key bindings for commands with a list, such as align,
695 // the name in prefs is the category name plus the effect name.
696 // This feature is not used for built-in effects.
697 if (multi)
698 name = CommandID{ { name, nameSuffix }, wxT('_') };
699
700 // wxMac 2.5 and higher will do special things with the
701 // Preferences, Exit (Quit), and About menu items,
702 // if we give them the right IDs.
703 // Otherwise we just pick increasing ID numbers for each NEW
704 // command. Note that the name string we are comparing
705 // ("About", "Preferences") is the internal command name
706 // (untranslated), not the label that actually appears in the
707 // menu (which might be translated).
708
710 entry->id = mCurrentID;
711 entry->parameter = parameter;
712
713#if defined(__WXMAC__)
714 // See bug #2642 for some history as to why these items
715 // on Mac have their IDs set explicitly and not others.
716 if (name == wxT("Preferences"))
717 entry->id = wxID_PREFERENCES;
718 else if (name == wxT("Exit"))
719 entry->id = wxID_EXIT;
720 else if (name == wxT("About"))
721 entry->id = wxID_ABOUT;
722#endif
723
724 entry->name = name;
725 entry->label = label;
726
727 // long label is the same as label unless options specified otherwise:
728 entry->longLabel = longLabel.empty() ? label : longLabel;
729
730 entry->excludeFromMacros = excludeFromMacros;
731 entry->key = NormalizedKeyString{ accel.BeforeFirst(wxT('\t')) };
732 entry->defaultKey = entry->key;
733 entry->labelPrefix = labelPrefix;
734 entry->labelTop = mCurrentMenuName.Stripped();
735 entry->menu = menu;
736 entry->finder = finder;
737 entry->callback = callback;
738 entry->isEffect = bIsEffect;
739 entry->multi = multi;
740 entry->index = index;
741 entry->count = count;
742 entry->flags = AlwaysEnabledFlag;
743 entry->enabled = true;
744 entry->skipKeydown = options.skipKeyDown;
745 entry->wantKeyup = options.wantKeyUp || entry->skipKeydown;
746 entry->allowDup = options.allowDup;
747 entry->isGlobal = false;
748 entry->isOccult = bMakingOccultCommands;
749 entry->checkmarkFn = options.checker;
750
751 // Exclude accelerators that are in the MaxList.
752 // Note that the default is unaffected, intentionally so.
753 // There are effectively two levels of default, the full (max) list
754 // and the normal reduced list.
755 if( std::binary_search( mMaxListOnly.begin(), mMaxListOnly.end(),
756 entry->key ) )
757 entry->key = {};
758
759 // Key from preferences overrides the default key given
760 gPrefs->SetPath(wxT("/NewKeys"));
761 // using GET to interpret CommandID as a config path component
762 const auto &path = entry->name.GET();
763 if (gPrefs->HasEntry(path)) {
764 entry->key =
765 NormalizedKeyString{ gPrefs->ReadObject(path, entry->key) };
766 }
767 gPrefs->SetPath(wxT("/"));
768
769 mCommandList.push_back(std::move(entry));
770 // Don't use the variable entry eny more!
771 }
772
773 // New variable
776
777#if defined(_DEBUG)
778 prev = mCommandNameHash[entry->name];
779 if (prev) {
780 // Under Linux it looks as if we may ask for a newID for the same command
781 // more than once. So it's only an error if two different commands
782 // have the exact same name.
783 if( prev->label != entry->label )
784 {
785 wxLogDebug(wxT("Command '%s' defined by '%s' and '%s'"),
786 // using GET in a log message for devs' eyes only
787 entry->name.GET(),
788 prev->label.Debug(),
789 entry->label.Debug());
790 wxFAIL_MSG(wxString::Format(wxT("Command '%s' defined by '%s' and '%s'"),
791 // using GET in an assertion violation message for devs'
792 // eyes only
793 entry->name.GET(),
794 prev->label.Debug(),
795 entry->label.Debug()));
796 }
797 }
798#endif
800
801 if (!entry->key.empty()) {
803 }
804
805 return entry;
806}
807
809 const CommandID &id, const TranslatableString *pLabel) const
810{
811 NormalizedKeyString keyStr;
812 if (auto iter = mCommandNameHash.find(id); iter != mCommandNameHash.end()) {
813 if (auto pEntry = iter->second) {
814 keyStr = pEntry->key;
815 if (!pLabel)
816 pLabel = &pEntry->label;
817 }
818 }
819 if (pLabel)
820 return FormatLabelForMenu(*pLabel, keyStr);
821 return {};
822}
823
825{
826 return FormatLabelForMenu( entry->label, entry->key );
827}
828
830 const TranslatableString &translatableLabel,
831 const NormalizedKeyString &keyStr) const
832{
833 auto label = translatableLabel.Translation();
834 auto key = keyStr.GET();
835 if (!key.empty())
836 {
837 // using GET to compose menu item name for wxWidgets
838 label += wxT("\t") + key;
839 }
840
841 return label;
842}
843
844// A label that may have its accelerator disabled.
845// The problem is that as soon as we show accelerators in the menu, the menu might
846// catch them in normal wxWidgets processing, rather than passing the key presses on
847// to the controls that had the focus. We would like all the menu accelerators to be
848// disabled, in fact.
850{
851 auto label = entry->label.Translation();
852#if 1
853 wxString Accel;
854 do{
855 if (!entry->key.empty())
856 {
857 // Dummy accelerator that looks Ok in menus but is non functional.
858 // Note the space before the key.
859#ifdef __WXMSW__
860 // using GET to compose menu item name for wxWidgets
861 auto key = entry->key.GET();
862 Accel = wxString("\t ") + key;
863 if( key.StartsWith("Left" )) break;
864 if( key.StartsWith("Right")) break;
865 if( key.StartsWith("Up" )) break;
866 if( key.StartsWith("Down")) break;
867 if( key.StartsWith("Return")) break;
868 if( key.StartsWith("Tab")) break;
869 if( key.StartsWith("Shift+Tab")) break;
870 if( key.StartsWith("0")) break;
871 if( key.StartsWith("1")) break;
872 if( key.StartsWith("2")) break;
873 if( key.StartsWith("3")) break;
874 if( key.StartsWith("4")) break;
875 if( key.StartsWith("5")) break;
876 if( key.StartsWith("6")) break;
877 if( key.StartsWith("7")) break;
878 if( key.StartsWith("8")) break;
879 if( key.StartsWith("9")) break;
880 // Uncomment the below so as not to add the illegal accelerators.
881 // Accel = "";
882 //if( entry->key.StartsWith("Space" )) break;
883 // These ones appear to be illegal already and mess up accelerator processing.
884 if( key.StartsWith("NUMPAD_ENTER" )) break;
885 if( key.StartsWith("Backspace" )) break;
886 if( key.StartsWith("Delete" )) break;
887#endif
888 //wxLogDebug("Added Accel:[%s][%s]", entry->label, entry->key );
889 // Normal accelerator.
890 // using GET to compose menu item name for wxWidgets
891 Accel = wxString("\t") + entry->key.GET();
892 }
893 } while (false );
894 label += Accel;
895#endif
896 return label;
897}
904{
905 if (!entry->menu) {
906 entry->enabled = enabled;
907 return;
908 }
909
910 // LL: Refresh from real state as we can get out of sync on the
911 // Mac due to its reluctance to enable menus when in a modal
912 // state.
913 entry->enabled = entry->menu->IsEnabled(entry->id);
914
915 // Only enabled if needed
916 if (entry->enabled != enabled) {
917 entry->menu->Enable(entry->id, enabled);
918 entry->enabled = entry->menu->IsEnabled(entry->id);
919 }
920
921 if (entry->multi) {
922 int i;
923 int ID = entry->id;
924
925 for(i=1; i<entry->count; i++) {
926 ID = NextIdentifier(ID);
927
928 // This menu item is not necessarily in the same menu, because
929 // multi-items can be spread across multiple sub menus
930 CommandListEntry *multiEntry = mCommandNumericIDHash[ID];
931 if (multiEntry) {
932 wxMenuItem *item = multiEntry->menu->FindItem(ID);
933
934 if (item) {
935 item->Enable(enabled);
936 } else {
937 // using GET in a log message for devs' eyes only
938 wxLogDebug(wxT("Warning: Menu entry with id %i in %s not found"),
939 ID, entry->name.GET());
940 }
941 } else {
942 wxLogDebug(wxT("Warning: Menu entry with id %i not in hash"), ID);
943 }
944 }
945 }
946}
947
948void CommandManager::Enable(const wxString &name, bool enabled)
949{
951 if (!entry || !entry->menu) {
952 wxLogDebug(wxT("Warning: Unknown command enabled: '%s'"),
953 (const wxChar*)name);
954 return;
955 }
956
957 Enable(entry, enabled);
958}
959
961 CommandFlag flags, CommandFlag strictFlags)
962{
963 // strictFlags are a subset of flags. strictFlags represent the real
964 // conditions now, but flags are the conditions that could be made true.
965 // Some commands use strict flags only, refusing the chance to fix
966 // conditions
967 wxASSERT( (strictFlags & ~flags).none() );
968
969 for(const auto &entry : mCommandList) {
970 if (entry->multi && entry->index != 0)
971 continue;
972 if( entry->isOccult )
973 continue;
974
975 auto useFlags = entry->useStrictFlags ? strictFlags : flags;
976
977 if (entry->flags.any()) {
978 bool enable = ((useFlags & entry->flags) == entry->flags);
979 Enable(entry.get(), enable);
980 }
981 }
982}
983
985{
987 if (!entry || !entry->menu) {
988 // using GET in a log message for devs' eyes only
989 wxLogDebug(wxT("Warning: command doesn't exist: '%s'"),
990 name.GET());
991 return false;
992 }
993 return entry->enabled;
994}
995
997{
998 return mXMLKeysRead;
999}
1000
1001void CommandManager::Check(const CommandID &name, bool checked)
1002{
1004 if (!entry || !entry->menu || entry->isOccult) {
1005 return;
1006 }
1007 entry->menu->Check(entry->id, checked);
1008}
1009
1011void CommandManager::Modify(const wxString &name, const TranslatableString &newLabel)
1012{
1014 if (entry && entry->menu) {
1015 entry->label = newLabel;
1016 entry->menu->SetLabel(entry->id, FormatLabelForMenu(entry));
1017 }
1018}
1019
1021 const NormalizedKeyString &key)
1022{
1024 if (entry) {
1025 entry->key = key;
1026 }
1027}
1028
1030{
1031 const auto &entry = mCommandList[i];
1032 entry->key = key;
1033}
1034
1036 const ComponentInterfaceSymbol commands[], size_t nCommands) const
1037{
1038 wxString mark;
1039 // This depends on the language setting and may change in-session after
1040 // change of preferences:
1041 bool rtl = (wxLayout_RightToLeft == wxTheApp->GetLayoutDirection());
1042 if (rtl)
1043 mark = wxT("\u200f");
1044
1045 static const wxString &separatorFormat = wxT("%s / %s");
1046 TranslatableString result;
1047 for (size_t ii = 0; ii < nCommands; ++ii) {
1048 const auto &pair = commands[ii];
1049 // If RTL, then the control character forces right-to-left sequencing of
1050 // "/" -separated command names, and puts any "(...)" shortcuts to the
1051 // left, consistently with accelerators in menus (assuming matching
1052 // operating system preferences for language), even if the command name
1053 // was missing from the translation file and defaulted to the English.
1054
1055 // Note: not putting this and other short format strings in the
1056 // translation catalogs
1057 auto piece = Verbatim( wxT("%s%s") )
1058 .Format( mark, pair.Msgid().Stripped() );
1059
1060 auto name = pair.Internal();
1061 if (!name.empty()) {
1062 auto keyStr = GetKeyFromName(name);
1063 if (!keyStr.empty()){
1064 auto keyString = keyStr.Display(true);
1065 auto format = wxT("%s %s(%s)");
1066#ifdef __WXMAC__
1067 // The unicode controls push and pop left-to-right embedding.
1068 // This keeps the directionally weak characters, such as uparrow
1069 // for Shift, left of the key name,
1070 // consistently with how menu accelerators appear, even when the
1071 // system language is RTL.
1072 format = wxT("%s %s(\u202a%s\u202c)");
1073#endif
1074 // The mark makes correctly placed parentheses for RTL, even
1075 // in the case that the piece is untranslated.
1076 piece = Verbatim( format ).Format( piece, mark, keyString );
1077 }
1078 }
1079
1080 if (result.empty())
1081 result = piece;
1082 else
1083 result = Verbatim( separatorFormat ).Format( result, piece );
1084 }
1085 return result;
1086}
1087
1091bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent & evt, bool permit)
1092{
1093 if (!project)
1094 return false;
1095
1096 auto pWindow = FindProjectFrame( project );
1098 if (entry == NULL)
1099 {
1100 return false;
1101 }
1102
1103 int type = evt.GetEventType();
1104
1105 // Global commands aren't tied to any specific project
1106 if (entry->isGlobal && type == wxEVT_KEY_DOWN)
1107 {
1108 // Global commands are always disabled so they do not interfere with the
1109 // rest of the command handling. But, to use the common handler, we
1110 // enable them temporarily and then disable them again after handling.
1111 // LL: Why do they need to be disabled???
1112 entry->enabled = false;
1113 auto cleanup = valueRestorer( entry->enabled, true );
1114 return HandleCommandEntry(*project, entry, NoFlagsSpecified, false, &evt);
1115 }
1116
1117 wxWindow * pFocus = wxWindow::FindFocus();
1118 wxWindow * pParent = wxGetTopLevelParent( pFocus );
1119 bool validTarget = pParent == pWindow;
1120 // Bug 1557. MixerBoard should count as 'destined for project'
1121 // MixerBoard IS a TopLevelWindow, and its parent is the project.
1122 if( pParent && pParent->GetParent() == pWindow ){
1123 if(auto keystrokeHandlingWindow = dynamic_cast< TopLevelKeystrokeHandlingWindow* >( pParent ))
1124 validTarget = keystrokeHandlingWindow->HandleCommandKeystrokes();
1125 }
1126 validTarget = validTarget && wxEventLoop::GetActive()->IsMain();
1127
1128 // Any other keypresses must be destined for this project window
1129 if (!permit && !validTarget )
1130 {
1131 return false;
1132 }
1133
1134 auto flags = MenuManager::Get(*project).GetUpdateFlags();
1135
1136 wxKeyEvent temp = evt;
1137
1138 // Possibly let wxWidgets do its normal key handling IF it is one of
1139 // the standard navigation keys.
1140 if((type == wxEVT_KEY_DOWN) || (type == wxEVT_KEY_UP ))
1141 {
1142 wxWindow * pWnd = wxWindow::FindFocus();
1143 bool bIntercept =
1144 pWnd && !dynamic_cast< NonKeystrokeInterceptingWindow * >( pWnd );
1145
1146 //wxLogDebug("Focus: %p TrackPanel: %p", pWnd, pTrackPanel );
1147 // We allow the keystrokes below to be handled by wxWidgets controls IF we are
1148 // in some sub window rather than in the TrackPanel itself.
1149 // Otherwise they will go to our command handler and if it handles them
1150 // they will NOT be available to wxWidgets.
1151 if( bIntercept ){
1152 switch( evt.GetKeyCode() ){
1153 case WXK_LEFT:
1154 case WXK_RIGHT:
1155 case WXK_UP:
1156 case WXK_DOWN:
1157 // Don't trap WXK_SPACE (Bug 1727 - SPACE not starting/stopping playback
1158 // when cursor is in a time control)
1159 // case WXK_SPACE:
1160 case WXK_TAB:
1161 case WXK_BACK:
1162 case WXK_HOME:
1163 case WXK_END:
1164 case WXK_RETURN:
1165 case WXK_NUMPAD_ENTER:
1166 case WXK_DELETE:
1167 case '0':
1168 case '1':
1169 case '2':
1170 case '3':
1171 case '4':
1172 case '5':
1173 case '6':
1174 case '7':
1175 case '8':
1176 case '9':
1177 return false;
1178 }
1179 }
1180 }
1181
1182 if (type == wxEVT_KEY_DOWN)
1183 {
1184 if (entry->skipKeydown)
1185 {
1186 return true;
1187 }
1188 return HandleCommandEntry(*project, entry, flags, false, &temp);
1189 }
1190
1191 if (type == wxEVT_KEY_UP && entry->wantKeyup)
1192 {
1193 return HandleCommandEntry(*project, entry, flags, false, &temp);
1194 }
1195
1196 return false;
1197}
1198
1199namespace {
1200
1201constexpr auto JournalCode = wxT("CM"); // for CommandManager
1202
1203// Register a callback for the journal
1205[]( const wxArrayStringEx &fields )
1206{
1207 // Expect JournalCode and the command name.
1208 // To do, perhaps, is to include some parameters.
1209 bool handled = false;
1210 if ( fields.size() == 2 ) {
1211 if (auto project = GetActiveProject().lock()) {
1212 auto pManager = &CommandManager::Get( *project );
1213 auto flags = MenuManager::Get( *project ).GetUpdateFlags();
1214 const CommandContext context( *project );
1215 auto &command = fields[1];
1216 handled =
1217 pManager->HandleTextualCommand( command, context, flags, false );
1218 }
1219 }
1220 return handled;
1221}
1222};
1223
1224}
1225
1231 const CommandListEntry * entry,
1232 CommandFlag flags, bool alwaysEnabled, const wxEvent * evt,
1233 const CommandContext *pGivenContext)
1234{
1235 if (!entry )
1236 return false;
1237
1238 if (flags != AlwaysEnabledFlag && !entry->enabled)
1239 return false;
1240
1241 if (!alwaysEnabled && entry->flags.any()) {
1242
1243 const auto NiceName = entry->label.Stripped(
1245 // NB: The call may have the side effect of changing flags.
1246 bool allowed =
1248 NiceName, flags, entry->flags );
1249 // If the function was disallowed, it STILL should count as having been
1250 // handled (by doing nothing or by telling the user of the problem).
1251 // Otherwise we may get other handlers having a go at obeying the command.
1252 if (!allowed)
1253 return true;
1254 mNiceName = NiceName;
1255 }
1256 else {
1257 mNiceName = {};
1258 }
1259
1260 Journal::Output({ JournalCode, entry->name.GET() });
1261
1262 CommandContext context{ project, evt, entry->index, entry->parameter };
1263 if (pGivenContext)
1264 context.temporarySelection = pGivenContext->temporarySelection;
1265 // Discriminate the union entry->callback by entry->finder
1266 if (auto &finder = entry->finder) {
1267 auto &handler = finder(project);
1268 (handler.*(entry->callback.memberFn))(context);
1269 }
1270 else
1271 (entry->callback.nonMemberFn)(context);
1272 mLastProcessId = 0;
1273 return true;
1274}
1275
1276// Called by Contrast and Plot Spectrum Plug-ins to mark them as Last Analzers.
1277// Note that Repeat data has previously been collected
1279 if (mLastProcessId != 0) {
1280 auto& menuManager = MenuManager::Get(context.project);
1281 menuManager.mLastAnalyzerRegistration = MenuCreator::repeattypeunique;
1282 menuManager.mLastAnalyzerRegisteredId = mLastProcessId;
1283 auto lastEffectDesc = XO("Repeat %s").Format(mNiceName);
1284 Modify(wxT("RepeatLastAnalyzer"), lastEffectDesc);
1285 }
1286 return;
1287}
1288
1289// Called by Selected Tools to mark them as Last Tools.
1290// Note that Repeat data has previously been collected
1292 if (mLastProcessId != 0) {
1293 auto& menuManager = MenuManager::Get(context.project);
1294 menuManager.mLastToolRegistration = MenuCreator::repeattypeunique;
1295 menuManager.mLastToolRegisteredId = mLastProcessId;
1296 auto lastEffectDesc = XO("Repeat %s").Format(mNiceName);
1297 Modify(wxT("RepeatLastTool"), lastEffectDesc);
1298 }
1299 return;
1300}
1301
1302// Used to invoke Repeat Last Analyzer Process for built-in, non-nyquist plug-ins.
1304 mLastProcessId = 0; //Don't Process this as repeat
1306 // Discriminate the union entry->callback by entry->finder
1307 if (auto &finder = entry->finder) {
1308 auto &handler = finder(context.project);
1309 (handler.*(entry->callback.memberFn))(context);
1310 }
1311 else
1312 (entry->callback.nonMemberFn)(context);
1313}
1314
1315
1322 AudacityProject &project, int id, CommandFlag flags, bool alwaysEnabled)
1323{
1326
1327 if (GlobalMenuHook::Call(entry->name))
1328 return true;
1329
1330 return HandleCommandEntry( project, entry, flags, alwaysEnabled );
1331}
1332
1338 const CommandContext & context, CommandFlag flags, bool alwaysEnabled)
1339{
1340 if( Str.empty() )
1341 return CommandFailure;
1342 // Linear search for now...
1343 for (const auto &entry : mCommandList)
1344 {
1345 if (!entry->multi)
1346 {
1347 // Testing against labelPrefix too allows us to call Nyquist functions by name.
1348 if( Str == entry->name ||
1349 // PRL: uh oh, mixing internal string (Str) with user-visible
1350 // (labelPrefix, which was initialized from a user-visible
1351 // sub-menu name)
1352 Str == entry->labelPrefix.Translation() )
1353 {
1354 return HandleCommandEntry(
1355 context.project, entry.get(), flags, alwaysEnabled,
1356 nullptr, &context)
1358 }
1359 }
1360 else
1361 {
1362 // Handle multis too...
1363 if( Str == entry->name )
1364 {
1365 return HandleCommandEntry(
1366 context.project, entry.get(), flags, alwaysEnabled,
1367 nullptr, &context)
1369 }
1370 }
1371 }
1372 return CommandNotFound;
1373}
1374
1376{
1378
1379 for (const auto &entry : mCommandList) {
1380 auto &cat = entry->labelTop;
1381 if ( ! make_iterator_range( cats ).contains(cat) ) {
1382 cats.push_back(cat);
1383 }
1384 }
1385#if 0
1386 mCommandList.size(); i++) {
1387 if (includeMultis || !mCommandList[i]->multi)
1388 names.push_back(mCommandList[i]->name);
1389 }
1390
1391 if (p == NULL) {
1392 return;
1393 }
1394
1395 wxMenuBar *bar = p->GetMenuBar();
1396 size_t cnt = bar->GetMenuCount();
1397 for (size_t i = 0; i < cnt; i++) {
1398 cats.push_back(bar->GetMenuLabelText(i));
1399 }
1400
1401 cats.push_back(COMMAND);
1402#endif
1403
1404 return cats;
1405}
1406
1408 bool includeMultis) const
1409{
1410 for(const auto &entry : mCommandList) {
1411 if ( entry->isEffect )
1412 continue;
1413 if (!entry->multi)
1414 names.push_back(entry->name);
1415 else if( includeMultis )
1416 names.push_back(entry->name );// + wxT(":")/*+ mCommandList[i]->label*/);
1417 }
1418}
1419
1421 std::vector<bool> &vExcludeFromMacros,
1422 bool includeMultis) const
1423{
1424 vExcludeFromMacros.clear();
1425 for(const auto &entry : mCommandList) {
1426 // This is fetching commands from the menus, for use as batch commands.
1427 // Until we have properly merged EffectManager and CommandManager
1428 // we explicitly exclude effects, as they are already handled by the
1429 // effects Manager.
1430 if ( entry->isEffect )
1431 continue;
1432 if (!entry->multi)
1433 names.push_back(entry->longLabel), vExcludeFromMacros.push_back(entry->excludeFromMacros);
1434 else if( includeMultis )
1435 names.push_back(entry->longLabel), vExcludeFromMacros.push_back(entry->excludeFromMacros);
1436 }
1437}
1438
1441 std::vector<NormalizedKeyString> &keys,
1442 std::vector<NormalizedKeyString> &default_keys,
1443 TranslatableStrings &labels,
1444 TranslatableStrings &categories,
1445#if defined(EXPERIMENTAL_KEY_VIEW)
1446 TranslatableStrings &prefixes,
1447#endif
1448 bool includeMultis)
1449{
1450 for(const auto &entry : mCommandList) {
1451 // GetAllCommandData is used by KeyConfigPrefs.
1452 // It does need the effects.
1453 //if ( entry->isEffect )
1454 // continue;
1455 if ( !entry->multi || includeMultis )
1456 {
1457 names.push_back(entry->name);
1458 keys.push_back(entry->key);
1459 default_keys.push_back(entry->defaultKey);
1460 labels.push_back(entry->label);
1461 categories.push_back(entry->labelTop);
1462#if defined(EXPERIMENTAL_KEY_VIEW)
1463 prefixes.push_back(entry->labelPrefix);
1464#endif
1465 }
1466 }
1467}
1468
1470{
1472 if (!entry)
1473 return {};
1474 return entry->name;
1475}
1476
1478{
1480 if (!entry)
1481 return {};
1482
1483 return entry->longLabel;
1484}
1485
1487{
1489 if (!entry)
1490 return {};
1491
1492 if (!entry->labelPrefix.empty())
1493 return Verbatim( wxT("%s - %s") )
1494 .Format(entry->labelPrefix, entry->label)
1495 .Stripped();
1496 else
1497 return entry->label.Stripped();
1498}
1499
1501{
1503 if (!entry)
1504 return {};
1505
1506 return entry->labelTop;
1507}
1508
1510{
1512 // May create a NULL entry
1513 const_cast<CommandManager*>(this)->mCommandNameHash[name];
1514 if (!entry)
1515 return {};
1516
1517 return entry->key;
1518}
1519
1521{
1523 if (!entry)
1524 return {};
1525
1526 return entry->defaultKey;
1527}
1528
1529bool CommandManager::HandleXMLTag(const std::string_view& tag, const AttributesList &attrs)
1530{
1531 if (tag == "audacitykeyboard") {
1532 mXMLKeysRead = 0;
1533 }
1534
1535 if (tag == "command") {
1536 wxString name;
1538
1539 for (auto pair : attrs)
1540 {
1541 auto attr = pair.first;
1542 auto value = pair.second;
1543
1544 if (value.IsStringView())
1545 {
1546 const wxString strValue = value.ToWString();
1547
1548 if (attr == "name")
1549 name = strValue;
1550 else if (attr == "key")
1551 key = NormalizedKeyString{ strValue };
1552 }
1553 }
1554
1555 if (mCommandNameHash[name]) {
1556 mCommandNameHash[name]->key = key;
1557 mXMLKeysRead++;
1558 }
1559 }
1560
1561 return true;
1562}
1563
1564// This message is displayed now in KeyConfigPrefs::OnImport()
1565void CommandManager::HandleXMLEndTag(const std::string_view& tag)
1566{
1567 /*
1568 if (tag == "audacitykeyboard") {
1569 AudacityMessageBox(
1570 XO("Loaded %d keyboard shortcuts\n")
1571 .Format( mXMLKeysRead ),
1572 XO("Loading Keyboard Shortcuts"),
1573 wxOK | wxCENTRE);
1574 }
1575 */
1576}
1577
1578XMLTagHandler *CommandManager::HandleXMLChild(const std::string_view& WXUNUSED(tag))
1579{
1580 return this;
1581}
1582
1584// may throw
1585{
1586 xmlFile.StartTag(wxT("audacitykeyboard"));
1587 xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING);
1588
1589 for(const auto &entry : mCommandList) {
1590
1591 xmlFile.StartTag(wxT("command"));
1592 xmlFile.WriteAttr(wxT("name"), entry->name);
1593 xmlFile.WriteAttr(wxT("key"), entry->key);
1594 xmlFile.EndTag(wxT("command"));
1595 }
1596
1597 xmlFile.EndTag(wxT("audacitykeyboard"));
1598}
1599
1601{
1602 // To do: perhaps allow occult item switching at lower levels of the
1603 // menu tree.
1604 wxASSERT( !CurrentMenu() );
1605
1606 // Make a temporary menu bar collecting items added after.
1607 // This bar will be discarded but other side effects on the command
1608 // manager persist.
1609 mTempMenuBar = AddMenuBar(wxT("ext-menu"));
1610 bMakingOccultCommands = true;
1611}
1612
1614{
1615 PopMenuBar();
1616 bMakingOccultCommands = false;
1617 mTempMenuBar.reset();
1618}
1619
1621 CommandFlag flags)
1622{
1624 if (entry)
1625 entry->flags = flags;
1626}
1627
1628#if defined(_DEBUG)
1629void CommandManager::CheckDups()
1630{
1631 int cnt = mCommandList.size();
1632 for (size_t j = 0; (int)j < cnt; j++) {
1633 if (mCommandList[j]->key.empty()) {
1634 continue;
1635 }
1636
1637 if (mCommandList[j]->allowDup)
1638 continue;
1639
1640 for (size_t i = 0; (int)i < cnt; i++) {
1641 if (i == j) {
1642 continue;
1643 }
1644
1645 if (mCommandList[i]->key == mCommandList[j]->key) {
1646 wxString msg;
1647 msg.Printf(wxT("key combo '%s' assigned to '%s' and '%s'"),
1648 // using GET to form debug message
1649 mCommandList[i]->key.GET(),
1650 mCommandList[i]->label.Debug(),
1651 mCommandList[j]->label.Debug());
1652 wxASSERT_MSG(mCommandList[i]->key != mCommandList[j]->key, msg);
1653 }
1654 }
1655 }
1656}
1657
1658#endif
1659
1660// If a default shortcut of a command is introduced or changed, then this
1661// shortcut may be the same shortcut a user has previously assigned to another
1662// command. This function removes such duplicates by removing the shortcut
1663// from the command whose default has changed.
1664// Note that two commands may have the same shortcut if their default shortcuts
1665// are the same. However, in this function default shortcuts are checked against
1666// user assigned shortcuts. Two such commands with the same shortcut
1667// must both be in either the first or the second group, so there is no need
1668// to test for this case.
1669// Note that if a user is using the full set of default shortcuts, and one
1670// of these is changed, then if /GUI/Shortcuts/FullDefaults is not set in audacity.cfg,
1671// because the defaults appear as user assigned shortcuts in audacity.cfg,
1672// the previous default overrides the changed default, and no duplicate can
1673// be introduced.
1675{
1676 TranslatableString disabledShortcuts;
1677
1678 for (auto& entry : mCommandList) {
1679 if (!entry->key.empty() && entry->key != entry->defaultKey) { // user assigned
1680 for (auto& entry2 : mCommandList) {
1681 if (!entry2->key.empty() && entry2->key == entry2->defaultKey) { // default
1682 if (entry2->key == entry->key) {
1683 auto name = wxT("/NewKeys/") + entry2->name.GET();
1684 gPrefs->Write(name, NormalizedKeyString{});
1685
1686 disabledShortcuts +=
1687 XO("\n* %s, because you have assigned the shortcut %s to %s")
1688 .Format(entry2->label.Strip(), entry->key.GET(), entry->label.Strip());
1689 }
1690 }
1691 }
1692 }
1693 }
1694
1695 if (!disabledShortcuts.Translation().empty()) {
1696 TranslatableString message = XO("The following commands have had their shortcuts removed,"
1697 " because their default shortcut is new or changed, and is the same shortcut"
1698 " that you have assigned to another command.")
1699 + disabledShortcuts;
1700 AudacityMessageBox(message, XO("Shortcuts have been removed"), wxOK | wxCENTRE);
1701
1702 gPrefs->Flush();
1704 }
1705}
1706
1707#include "../KeyboardCapture.h"
1708
1709static KeyboardCapture::PreFilter::Scope scope1{
1710[]( wxKeyEvent & ) {
1711 // We must have a project since we will be working with the
1712 // CommandManager, which is tied to individual projects.
1713 auto project = GetActiveProject().lock();
1714 return project && GetProjectFrame( *project ).IsEnabled();
1715} };
1716static KeyboardCapture::PostFilter::Scope scope2{
1717[]( wxKeyEvent &key ) {
1718 // Capture handler window didn't want it, so ask the CommandManager.
1719 if (auto project = GetActiveProject().lock()) {
1720 auto &manager = CommandManager::Get( *project );
1721 return manager.FilterKeyEvent(project.get(), key);
1722 }
1723 else
1724 return false;
1725} };
1726
AUDACITY_DLL_API std::weak_ptr< AudacityProject > GetActiveProject()
wxT("CloseDown"))
@ 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
Definition: CommandFlag.h:34
std::bitset< NCommandFlags > CommandFlag
Definition: CommandFlag.h:30
constexpr CommandFlag NoFlagsSpecified
Definition: CommandFlag.h:35
std::function< CommandHandlerObject &(AudacityProject &) > CommandHandlerFinder
static KeyboardCapture::PostFilter::Scope scope2
static const AudacityProject::AttachedObjects::RegisteredFactory key
#define COMMAND
static KeyboardCapture::PreFilter::Scope scope1
const TranslatableString name
Definition: Distortion.cpp:74
@ none
Definition: Dither.h:20
int format
Definition: ExportPCM.cpp:53
XO("Cut/Copy/Paste")
std::vector< CommandID > CommandIDs
Definition: Identifier.h:233
NormalizedKeyString KeyEventToKeyString(const wxKeyEvent &event)
Definition: Keyboard.cpp:83
ValueRestorer< T > valueRestorer(T &var)
inline functions provide convenient parameter type deduction
Definition: MemoryX.h:234
IteratorRange< Iterator > make_iterator_range(const Iterator &i1, const Iterator &i2)
Definition: MemoryX.h:431
FileConfig * gPrefs
Definition: Prefs.cpp:70
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 label
Definition: TagsEditor.cpp:164
static TranslatableStrings names
Definition: TagsEditor.cpp:152
TranslatableString Verbatim(wxString str)
Require calls to the one-argument constructor to go through this distinct global function name.
std::vector< TranslatableString > TranslatableStrings
int id
std::vector< Attribute > AttributesList
Definition: XMLTagHandler.h:40
The top-level handle to an Audacity project. It serves as a source of events that other objects can b...
Definition: Project.h:90
This specialization of Setting for bool adds a Toggle method to negate the saved value.
Definition: Prefs.h:339
Client code makes static instance from a factory of attachments; passes it to Get or Find as a retrie...
Definition: ClientData.h:266
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
CommandList mCommandList
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
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
MenuBarList mMenuBarList
SubMenuList mSubMenuList
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)
wxMenu * mCurrentMenu
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
Definition: FileConfig.cpp:138
virtual bool Flush(bool bCurrentOnly=false) wxOVERRIDE
Definition: FileConfig.cpp:143
virtual void SetPath(const wxString &strPath) wxOVERRIDE
Definition: FileConfig.cpp:93
static result_type Call(Arguments &&...arguments)
Null check of the installed function is done for you.
bool empty() const
Definition: Identifier.h:61
const wxString & GET() const
Explicit conversion to wxString, meant to be ugly-looking and demanding of a comment why it's correct...
Definition: Identifier.h:66
static void RebuildAllMenuBars()
Definition: Menus.cpp:619
@ repeattypeunique
Definition: Menus.h:62
static MenuManager & Get(AudacityProject &project)
Definition: Menus.cpp:69
bool ReportIfActionNotAllowed(const TranslatableString &Name, CommandFlag &flags, CommandFlag flagsRqd)
Definition: Menus.cpp:637
CommandFlag GetUpdateFlags(bool checkActive=false) const
Definition: Menus.cpp:533
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...
Definition: XMLTagHandler.h:42
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:26
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.
Definition: PackedArray.h:159
auto begin(const Ptr< Type, BaseDeleter > &p)
Enables range-for.
Definition: PackedArray.h:150
STL namespace.
CommandListEntry is a structure used by CommandManager.
bool isGlobal
CommandFlag flags
bool allowDup
CommandID name
bool isOccult
bool multi
int count
wxMenu * menu
TranslatableString longLabel
std::function< bool(AudacityProject &) > CheckFn
int id
CommandParameter parameter
bool wantKeyup
bool isEffect
CommandHandlerFinder finder
int index
bool enabled
TranslatableString label
TranslatableString labelTop
bool skipKeydown
CheckFn checkmarkFn
bool useStrictFlags
NormalizedKeyString defaultKey
bool excludeFromMacros
TranslatableString labelPrefix
NormalizedKeyString key
CommandFunctorPointer callback
TranslatableString longName
static CheckFn MakeCheckFn(const wxString key, bool defaultValue)
Options && IsEffect(bool value=true) &&
CommandParameter parameter
MenuBarListEntry is a structure used by CommandManager.
wxWeakRef< wxMenuBar > menubar
MenuBarListEntry(const wxString &name_, wxMenuBar *menubar_)
~MenuBarListEntry()
wxString name
SubMenuListEntry is a structure used by CommandManager.
std::unique_ptr< wxMenu > menu
SubMenuListEntry(const TranslatableString &name_)
~SubMenuListEntry()
SubMenuListEntry(SubMenuListEntry &&)=default
TranslatableString name