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