Audacity  2.2.2
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 #include "../AudacityHeaders.h"
79 #include "../Audacity.h"
80 #include "CommandManager.h"
81 #include "CommandContext.h"
82 
83 #include <wx/defs.h>
84 #include <wx/eventfilter.h>
85 #include <wx/hash.h>
86 #include <wx/intl.h>
87 #include <wx/log.h>
88 #include <wx/tokenzr.h>
89 
90 #include "../AudacityException.h"
91 #include "../Prefs.h"
92 #include "../Project.h"
93 
94 // LyricsPanel and MixerBoard both intercept keys, as if they were the TrackPanel.
95 // The mechanism of checking for the type of window here is klunky.
96 #include "../Lyrics.h"
97 #include "../MixerBoard.h"
98 
99 #include "Keyboard.h"
100 #include "../PluginManager.h"
101 #include "../effects/EffectManager.h"
102 #include "../widgets/LinkingHtmlWindow.h"
103 #include "../widgets/ErrorDialog.h"
104 #include "../widgets/HelpSystem.h"
105 
106 
107 // On wxGTK, there may be many many many plugins, but the menus don't automatically
108 // allow for scrolling, so we build sub-menus. If the menu gets longer than
109 // MAX_MENU_LEN, we put things in submenus that have MAX_SUBMENU_LEN items in them.
110 //
111 #ifdef __WXGTK__
112 #define MAX_MENU_LEN 20
113 #define MAX_SUBMENU_LEN 15
114 #else
115 #define MAX_MENU_LEN 1000
116 #define MAX_SUBMENU_LEN 1000
117 #endif
118 
119 #define COMMAND _("Command")
120 
121 #if defined(__WXMAC__)
122 #include <AppKit/AppKit.h>
123 #include <wx/osx/private.h>
124 #elif defined(__WXGTK__)
125 #include <gtk/gtk.h>
126 #endif
127 
128 #include "../Experimental.h"
129 
130 // Shared by all projects
131 static class CommandManagerEventMonitor final : public wxEventFilter
132 {
133 public:
135  : wxEventFilter()
136  {
137 #if defined(__WXMAC__)
138  // In wx3, the menu accelerators take precendence over key event processing
139  // so we won't get wxEVT_CHAR_HOOK events for combinations assigned to menus.
140  // Since we only support OS X 10.6 or greater, we can use an event monitor
141  // to capture the key event before it gets to the normal wx3 processing.
142 
143  // The documentation for addLocalMonitorForEventsMatchingMask implies that
144  // NSKeyUpMask can't be used in 10.6, but testing shows that it can.
145  NSEventMask mask = NSKeyDownMask | NSKeyUpMask;
146 
147  mHandler =
148  [
149  NSEvent addLocalMonitorForEventsMatchingMask:mask handler:^NSEvent *(NSEvent *event)
150  {
151  WXWidget widget = (WXWidget) [[event window] firstResponder];
152  if (widget)
153  {
154  wxWidgetCocoaImpl *impl = (wxWidgetCocoaImpl *)
155  wxWidgetImpl::FindFromWXWidget(widget);
156  if (impl)
157  {
158  mEvent = event;
159 
160  wxKeyEvent wxevent([event type] == NSKeyDown ? wxEVT_KEY_DOWN : wxEVT_KEY_UP);
161  impl->SetupKeyEvent(wxevent, event);
162 
163  NSEvent *result;
164  if ([event type] == NSKeyDown)
165  {
166  wxKeyEvent eventHook(wxEVT_CHAR_HOOK, wxevent);
167  result = FilterEvent(eventHook) == Event_Processed ? nil : event;
168  }
169  else
170  {
171  result = FilterEvent(wxevent) == Event_Processed ? nil : event;
172  }
173 
174  mEvent = nullptr;
175  return result;
176  }
177  }
178 
179  return event;
180  }
181  ];
182 
183  // Bug1252: must also install this filter with wxWidgets, else
184  // we don't intercept command keys when focus is in a combo box.
185  wxEvtHandler::AddFilter(this);
186 #else
187 
188  wxEvtHandler::AddFilter(this);
189 
190 #endif
191  }
192 
194  {
195 #if defined(__WXMAC__)
196  [NSEvent removeMonitor:mHandler];
197 #else
198  wxEvtHandler::RemoveFilter(this);
199 #endif
200  }
201 
202  int FilterEvent(wxEvent& event) override
203  {
204  // Unguarded exception propagation may crash the program, at least
205  // on Mac while in the objective-C closure above
206  return GuardedCall< int > ( [&] {
207  // Quickly bail if this isn't something we want.
208  wxEventType type = event.GetEventType();
209  if (type != wxEVT_CHAR_HOOK && type != wxEVT_KEY_UP)
210  {
211  return Event_Skip;
212  }
213 
214  // We must have a project since we will be working with the Command Manager
215  // and capture handler, both of which are (currently) tied to individual projects.
216  //
217  // Shouldn't they be tied to the application instead???
218  AudacityProject *project = GetActiveProject();
219  if (!project || !project->IsEnabled())
220  {
221  return Event_Skip;
222  }
223 
224  // Make a copy of the event and (possibly) make it look like a key down
225  // event.
226  wxKeyEvent key = (wxKeyEvent &) event;
227  if (type == wxEVT_CHAR_HOOK)
228  {
229  key.SetEventType(wxEVT_KEY_DOWN);
230  }
231 
232  // Give the capture handler first dibs at the event.
233  wxWindow *handler = project->GetKeyboardCaptureHandler();
234  if (handler && HandleCapture(handler, key))
235  {
236  return Event_Processed;
237  }
238 
239  // Capture handler didn't want it, so ask the Command Manager.
240  CommandManager *manager = project->GetCommandManager();
241  if (manager && manager->FilterKeyEvent(project, key))
242  {
243  return Event_Processed;
244  }
245 
246  // Give it back to WX for normal processing.
247  return Event_Skip;
248  }, MakeSimpleGuard( Event_Skip ) );
249  }
250 
251 private:
252 
253  // Returns true if the event was captured and processed
254  bool HandleCapture(wxWindow *target, const wxKeyEvent & event)
255  {
256  if (wxGetTopLevelParent(target) != wxGetTopLevelParent(wxWindow::FindFocus()))
257  {
258  return false;
259  }
260  wxEvtHandler *handler = target->GetEventHandler();
261 
262  // We make a copy of the event because the capture handler may modify it.
263  wxKeyEvent temp = event;
264 
265 #if defined(__WXGTK__)
266  // wxGTK uses the control and alt modifiers to represent ALTGR,
267  // so remove it as it might confuse the capture handlers.
268  if (temp.GetModifiers() == (wxMOD_CONTROL | wxMOD_ALT))
269  {
270  temp.SetControlDown(false);
271  temp.SetAltDown(false);
272  }
273 #endif
274 
275  // Ask the capture handler if the key down/up event is something they it
276  // might be interested in handling.
277  wxCommandEvent e(EVT_CAPTURE_KEY);
278  e.SetEventObject(&temp);
279  e.StopPropagation();
280  if (!handler->ProcessEvent(e))
281  {
282  return false;
283  }
284 
285  // Now, let the handler process the normal key event.
286  bool keyDown = temp.GetEventType() == wxEVT_KEY_DOWN;
287  temp.WasProcessed();
288  temp.StopPropagation();
289  wxEventProcessInHandlerOnly onlyDown(temp, handler);
290  bool processed = handler->ProcessEvent(temp);
291 
292  // Don't go any further if the capture handler didn't process
293  // the key down event.
294  if (!processed && keyDown)
295  {
296  return false;
297  }
298 
299  // At this point the capture handler has either processed a key down event
300  // or we're dealing with a key up event.
301  //
302  // So, only generate the char events for key down events.
303  if (keyDown)
304  {
305  wxString chars = GetUnicodeString(temp);
306  for (size_t i = 0, cnt = chars.Length(); i < cnt; i++)
307  {
308  temp = event;
309  temp.SetEventType(wxEVT_CHAR);
310  temp.WasProcessed();
311  temp.StopPropagation();
312  temp.m_uniChar = chars[i];
313  wxEventProcessInHandlerOnly onlyChar(temp, handler);
314  handler->ProcessEvent(temp);
315  }
316  }
317 
318  // We get here for processed key down events or for key up events, whether
319  // processed or not.
320  return true;
321  }
322 
323  // Convert the key down event to a unicode string.
324  wxString GetUnicodeString(const wxKeyEvent & event)
325  {
326  wxString chars;
327 
328 #if defined(__WXMSW__)
329 
330  BYTE ks[256];
331  GetKeyboardState(ks);
332  WCHAR ucode[256];
333  int res = ToUnicode(event.GetRawKeyCode(), 0, ks, ucode, 256, 0);
334  if (res >= 1)
335  {
336  chars.Append(ucode, res);
337  }
338 
339 #elif defined(__WXGTK__)
340 
341  chars.Append((wxChar) gdk_keyval_to_unicode(event.GetRawKeyCode()));
342 
343 #elif defined(__WXMAC__)
344 
345  if (!mEvent) {
346  // TODO: we got here without getting the NSEvent pointer,
347  // as in the combo box case of bug 1252. We can't compute it!
348  // This makes a difference only when there is a capture handler.
349  // It's never the case yet that there is one.
350  wxASSERT(false);
351  return chars;
352  }
353 
354  NSString *c = [mEvent charactersIgnoringModifiers];
355  if ([c length] == 1)
356  {
357  unichar codepoint = [c characterAtIndex:0];
358  if ((codepoint >= 0xF700 && codepoint <= 0xF8FF) || codepoint == 0x7F)
359  {
360  return chars;
361  }
362  }
363 
364  c = [mEvent characters];
365  chars = [c UTF8String];
366 
367  TISInputSourceRef currentKeyboard = TISCopyCurrentKeyboardInputSource();
368  CFDataRef uchr = (CFDataRef)TISGetInputSourceProperty(currentKeyboard, kTISPropertyUnicodeKeyLayoutData);
369  CFRelease(currentKeyboard);
370  if (uchr == NULL)
371  {
372  return chars;
373  }
374 
375  const UCKeyboardLayout *keyboardLayout = (const UCKeyboardLayout*)CFDataGetBytePtr(uchr);
376  if (keyboardLayout == NULL)
377  {
378  return chars;
379  }
380 
381  const UniCharCount maxStringLength = 255;
382  UniCharCount actualStringLength = 0;
383  UniChar unicodeString[maxStringLength];
384  UInt32 nsflags = [mEvent modifierFlags];
385  UInt16 modifiers = (nsflags & NSAlphaShiftKeyMask ? alphaLock : 0) |
386  (nsflags & NSShiftKeyMask ? shiftKey : 0) |
387  (nsflags & NSControlKeyMask ? controlKey : 0) |
388  (nsflags & NSAlternateKeyMask ? optionKey : 0) |
389  (nsflags & NSCommandKeyMask ? cmdKey : 0);
390 
391  OSStatus status = UCKeyTranslate(keyboardLayout,
392  [mEvent keyCode],
393  kUCKeyActionDown,
394  (modifiers >> 8) & 0xff,
395  LMGetKbdType(),
396  0,
397  &mDeadKeyState,
398  maxStringLength,
399  &actualStringLength,
400  unicodeString);
401 
402  if (status != noErr)
403  {
404  return chars;
405  }
406 
407  chars = [[NSString stringWithCharacters:unicodeString
408  length:(NSInteger)actualStringLength] UTF8String];
409 
410 #endif
411 
412  return chars;
413  }
414 
415 private:
416 
417 #if defined(__WXMAC__)
418  id mHandler;
419  NSEvent *mEvent {};
420  UInt32 mDeadKeyState;
421 #endif
422 
423 }monitor;
424 
429  mCurrentID(17000),
430  mCurrentMenuName(COMMAND),
431  mDefaultFlags(AlwaysEnabledFlag),
432  mDefaultMask(AlwaysEnabledFlag),
433  bMakingOccultCommands( false )
434 {
435  mbSeparatorAllowed = false;
436  mLongNameForItem = "";
437  SetMaxList();
438 }
439 
444 {
445  //WARNING: This removes menubars that could still be assigned to windows!
446  PurgeData();
447 }
448 
449 const std::vector<NormalizedKeyString> &CommandManager::ExcludedList()
450 {
451  static const auto list = [] {
452  // These short cuts are for the max list only....
453  const char *const strings[] = {
454  "Ctrl+I",
455  "Ctrl+Alt+I",
456  "Ctrl+J",
457  "Ctrl+Alt+J",
458  "Ctrl+Alt+V",
459  "Alt+X",
460  "Alt+K",
461  "Shift+Alt+X",
462  "Shift+Alt+K",
463  "Alt+L",
464  "Shift+Alt+C",
465  "Alt+I",
466  "Alt+J",
467  "Shift+Alt+J",
468  "Ctrl+Shift+A",
469  "Q",
470  //"Shift+J",
471  //"Shift+K",
472  //"Shift+Home",
473  //"Shift+End",
474  "Ctrl+[",
475  "Ctrl+]",
476  "1",
477  "Shift+F5",
478  "Shift+F6",
479  "Shift+F7",
480  "Shift+F8",
481  "Ctrl+Shift+F5",
482  "Ctrl+Shift+F7",
483  "Ctrl+Shift+N",
484  "Ctrl+Shift+M",
485  "Ctrl+Home",
486  "Ctrl+End",
487  "Shift+C",
488  "Alt+Shift+Up",
489  "Alt+Shift+Down",
490  "Shift+P",
491  "Alt+Shift+Left",
492  "Alt+Shift+Right",
493  "Ctrl+Shift+T",
494  //"Command+M",
495  //"Option+Command+M",
496  "Shift+H",
497  "Shift+O",
498  "Shift+I",
499  "Shift+N",
500  "D",
501  "A",
502  "Alt+Shift+F6",
503  "Alt+F6",
504  };
505 
506  std::vector<NormalizedKeyString> result(
507  strings, strings + sizeof(strings)/sizeof(*strings) );
508  std::sort( result.begin(), result.end() );
509  return result;
510  }();
511  return list;
512 }
513 
514 // CommandManager needs to know which defaults are standard and which are in the
515 // full (max) list.
517 {
518 
519  // This list is a DUPLICATE of the list in
520  // KeyConfigPrefs::OnImportDefaults(wxCommandEvent & event)
521 
522  // TODO: At a later date get rid of the maxList entirely and
523  // instead use flags in the menu entrys to indicate whether the default
524  // shortcut is standard or full.
525 
526  mMaxListOnly.clear();
527 
528  // if the full list, don't exclude any.
529  bool bFull = gPrefs->ReadBool(wxT("/GUI/Shortcuts/FullDefaults"),false);
530  if( bFull )
531  return;
532 
534 }
535 
536 
538 {
539  // mCommandList contains pointers to CommandListEntrys
540  // mMenuBarList contains MenuBarListEntrys.
541  // mSubMenuList contains SubMenuListEntrys
542  mCommandList.clear();
543  mMenuBarList.clear();
544  mSubMenuList.clear();
545 
546  mCommandNameHash.clear();
547  mCommandKeyHash.clear();
548  mCommandIDHash.clear();
549 
551  mCurrentID = 17000;
552 }
553 
554 
560 std::unique_ptr<wxMenuBar> CommandManager::AddMenuBar(const wxString & sMenu)
561 {
562  wxMenuBar *menuBar = GetMenuBar(sMenu);
563  if (menuBar) {
564  wxASSERT(false);
565  return {};
566  }
567 
568  auto result = std::make_unique<wxMenuBar>();
569  mMenuBarList.emplace_back(sMenu, result.get());
570 
571  return result;
572 }
573 
574 
578 wxMenuBar * CommandManager::GetMenuBar(const wxString & sMenu) const
579 {
580  for (const auto &entry : mMenuBarList)
581  {
582  if(!entry.name.Cmp(sMenu))
583  return entry.menubar;
584  }
585 
586  return NULL;
587 }
588 
589 
594 {
595  if(mMenuBarList.empty())
596  return NULL;
597 
598  return mMenuBarList.back().menubar;
599 }
600 
601 
605 void CommandManager::BeginMenu(const wxString & tName)
606 {
607  uCurrentMenu = std::make_unique<wxMenu>();
608  mCurrentMenu = uCurrentMenu.get();
609  mCurrentMenuName = tName;
610 }
611 
612 
617 {
618  // Add the menu to the menubar after all menu items have been
619  // added to the menu to allow OSX to rearrange special menu
620  // items like Preferences, About, and Quit.
621  wxASSERT(uCurrentMenu);
622  CurrentMenuBar()->Append(uCurrentMenu.release(), mCurrentMenuName);
623  mCurrentMenu = nullptr;
625 }
626 
627 
631 wxMenu* CommandManager::BeginSubMenu(const wxString & tName)
632 {
633  mSubMenuList.push_back
634  (std::make_unique< SubMenuListEntry > ( tName, std::make_unique<wxMenu>() ));
635  mbSeparatorAllowed = false;
636  return mSubMenuList.back()->menu.get();
637 }
638 
639 
645 {
646  //Save the submenu's information
647  SubMenuListEntry tmpSubMenu { std::move( *mSubMenuList.back() ) };
648 
649  //Pop off the NEW submenu so CurrentMenu returns the parent of the submenu
650  mSubMenuList.pop_back();
651 
652  //Add the submenu to the current menu
653  CurrentMenu()->Append
654  (0, tmpSubMenu.name, tmpSubMenu.menu.release(), tmpSubMenu.name);
655  mbSeparatorAllowed = true;
656 }
657 
658 
663 {
664  if(mSubMenuList.empty())
665  return NULL;
666 
667  return mSubMenuList.back()->menu.get();
668 }
669 
675 {
676  if(!mCurrentMenu)
677  return NULL;
678 
679  wxMenu * tmpCurrentSubMenu = CurrentSubMenu();
680 
681  if(!tmpCurrentSubMenu)
682  {
683  return mCurrentMenu;
684  }
685 
686  return tmpCurrentSubMenu;
687 }
688 
690 {
691  // uCurrentMenu ought to be null in correct usage
692  wxASSERT(!uCurrentMenu);
693  // Make sure of it anyway
694  uCurrentMenu.reset();
695 
696  mCurrentMenu = menu;
697 }
698 
700 {
701  // uCurrentMenu ought to be null in correct usage
702  wxASSERT(!uCurrentMenu);
703  // Make sure of it anyway
704  uCurrentMenu.reset();
705 
706  mCurrentMenu = nullptr;
707 }
708 
709 #if 0
710 void CommandManager::InsertItem(const wxString & name,
714  const wxString & label_in,
715  CommandHandlerFinder finder,
716  CommandFunctorPointer callback,
717  const wxString & after,
718  int checkmark)
719 {
720  wxMenuBar *bar = GetActiveProject()->GetMenuBar();
721  wxArrayString names = ::wxStringTokenize(after, wxT(":"));
722  size_t cnt = names.GetCount();
723 
724  if (cnt < 2) {
725  return;
726  }
727 
728  int pos = bar->FindMenu(names[0]);
729  if (pos == wxNOT_FOUND) {
730  return;
731  }
732 
733  wxMenu *menu = bar->GetMenu(pos);
734  wxMenuItem *item = NULL;
735  pos = 0;
736 
737  for (size_t ndx = 1; ndx < cnt; ndx++) {
738  wxMenuItemList list = menu->GetMenuItems();
739  size_t lcnt = list.GetCount();
740  wxString label = wxMenuItem::GetLabelText(names[ndx]);
741 
742  for (size_t lndx = 0; lndx < lcnt; lndx++) {
743  item = list.Item(lndx)->GetData();
744  if (item->GetItemLabelText() == label) {
745  break;
746  }
747  pos++;
748  item = NULL;
749  }
750 
751  if (item == NULL) {
752  return;
753  }
754 
755  if (item->IsSubMenu()) {
756  menu = item->GetSubMenu();
757  item = NULL;
758  continue;
759  }
760 
761  if (ndx + 1 != cnt) {
762  return;
763  }
764  }
765 
766  CommandListEntry *entry = NewIdentifier(name, label_in, false, menu, finder, callback, {}, 0, 0, false);
767  int ID = entry->id;
768  wxString label = GetLabel(entry);
769 
770  if (checkmark >= 0) {
771  menu->InsertCheckItem(pos, ID, label);
772  menu->Check(ID, checkmark != 0);
773  }
774  else {
775  menu->Insert(pos, ID, label);
776  }
777 
778  mbSeparatorAllowed = true;
779 }
780 #endif
781 
782 
783 
784 void CommandManager::AddCheck(const wxChar *name,
785  const wxChar *label,
786  bool hasDialog,
787  CommandHandlerFinder finder,
788  CommandFunctorPointer callback,
789  int checkmark)
790 {
791  AddItem(name, label, hasDialog, finder, callback, wxT(""),
792  NoFlagsSpecifed, NoFlagsSpecifed, checkmark);
793 }
794 
795 void CommandManager::AddCheck(const wxChar *name,
796  const wxChar *label,
797  bool hasDialog,
798  CommandHandlerFinder finder,
799  CommandFunctorPointer callback,
800  int checkmark,
801  CommandFlag flags,
802  CommandMask mask)
803 {
804  AddItem(name, label, hasDialog, finder, callback, wxT(""), flags, mask, checkmark);
805 }
806 
807 void CommandManager::AddItem(const wxChar *name,
808  const wxChar *label,
809  bool hasDialog,
810  CommandHandlerFinder finder,
811  CommandFunctorPointer callback,
812  CommandFlag flags,
813  CommandMask mask,
814  bool bIsEffect,
815  const CommandParameter &parameter)
816 {
817  AddItem(name, label, hasDialog, finder, callback, wxT(""), flags, mask, -1, bIsEffect, parameter);
818 }
819 
820 void CommandManager::AddItem(const wxChar *name,
821  const wxChar *label_in,
822  bool hasDialog,
823  CommandHandlerFinder finder,
824  CommandFunctorPointer callback,
825  const wxChar *accel,
826  CommandFlag flags,
827  CommandMask mask,
828  int checkmark,
829  bool bIsEffect,
830  const CommandParameter &parameter)
831 {
832  wxString cookedParameter;
833  if( parameter == "" )
834  cookedParameter = name;
835  else
836  cookedParameter = parameter;
837  CommandListEntry *entry =
838  NewIdentifier(name,
839  label_in,
841  hasDialog,
842  accel, CurrentMenu(), finder, callback,
843  {}, 0, 0, bIsEffect, cookedParameter);
844  mLongNameForItem = "";
845  int ID = entry->id;
846  wxString label = GetLabelWithDisabledAccel(entry);
847 
848  if (flags != NoFlagsSpecifed || mask != NoFlagsSpecifed) {
849  SetCommandFlags(name, flags, mask);
850  }
851 
852 
853  if (checkmark >= 0) {
854  CurrentMenu()->AppendCheckItem(ID, label);
855  CurrentMenu()->Check(ID, checkmark != 0);
856  }
857  else {
858  CurrentMenu()->Append(ID, label);
859  }
860 
861  mbSeparatorAllowed = true;
862 }
863 
870 void CommandManager::AddItemList(const wxString & name,
871  const TranslatedInternalString items[],
872  size_t nItems,
873  CommandHandlerFinder finder,
874  CommandFunctorPointer callback,
875  bool bIsEffect)
876 {
877  for (size_t i = 0, cnt = nItems; i < cnt; i++) {
878  CommandListEntry *entry = NewIdentifier(name,
879  items[i].TranslatedForMenu(),
880  items[i].TranslatedForMenu(),
881  // No means yet to specify !
882  false,
883  CurrentMenu(),
884  finder,
885  callback,
886  items[i].Internal(),
887  i,
888  cnt,
889  bIsEffect);
890  CurrentMenu()->Append(entry->id, GetLabel(entry));
891  mbSeparatorAllowed = true;
892  }
893 }
894 
898 void CommandManager::AddCommand(const wxChar *name,
899  const wxChar *label,
900  CommandHandlerFinder finder,
901  CommandFunctorPointer callback,
902  CommandFlag flags,
903  CommandMask mask)
904 {
905  AddCommand(name, label, finder, callback, wxT(""), flags, mask);
906 }
907 
908 void CommandManager::AddCommand(const wxChar *name,
909  const wxChar *label_in,
910  CommandHandlerFinder finder,
911  CommandFunctorPointer callback,
912  const wxChar *accel,
913  CommandFlag flags,
914  CommandMask mask)
915 {
916  NewIdentifier(name, label_in, label_in, false, accel, NULL, finder, callback, {}, 0, 0, false, {});
917 
918  if (flags != NoFlagsSpecifed || mask != NoFlagsSpecifed) {
919  SetCommandFlags(name, flags, mask);
920  }
921 }
922 
923 void CommandManager::AddGlobalCommand(const wxChar *name,
924  const wxChar *label_in,
925  bool hasDialog,
926  CommandHandlerFinder finder,
927  CommandFunctorPointer callback,
928  const wxChar *accel)
929 {
930  CommandListEntry *entry =
931  NewIdentifier(name, label_in, label_in, hasDialog, accel, NULL, finder, callback,
932  {}, 0, 0, false, {});
933 
934  entry->enabled = false;
935  entry->isGlobal = true;
936  entry->flags = AlwaysEnabledFlag;
937  entry->mask = AlwaysEnabledFlag;
938 }
939 
941 {
942  if( mbSeparatorAllowed )
943  CurrentMenu()->AppendSeparator();
944  mbSeparatorAllowed = false; // boolean to prevent too many separators.
945 }
946 
948 {
949  ID++;
950 
951  //Skip the reserved identifiers used by wxWidgets
952  if((ID >= wxID_LOWEST) && (ID <= wxID_HIGHEST))
953  ID = wxID_HIGHEST+1;
954 
955  return ID;
956 }
957 
964  const wxString & label,
965  const wxString & longLabel,
966  bool hasDialog,
967  wxMenu *menu,
968  CommandHandlerFinder finder,
969  CommandFunctorPointer callback,
970  const wxString &nameSuffix,
971  int index,
972  int count,
973  bool bIsEffect)
974 {
975  return NewIdentifier(name,
976  label.BeforeFirst(wxT('\t')),
977  longLabel.BeforeFirst(wxT('\t')),
978  hasDialog,
979  label.AfterFirst(wxT('\t')),
980  menu,
981  finder,
982  callback,
983  nameSuffix,
984  index,
985  count,
986  bIsEffect,
987  {});
988 }
989 
991  const wxString & label,
992  const wxString & longLabel,
993  bool hasDialog,
994  const wxString & accel,
995  wxMenu *menu,
996  CommandHandlerFinder finder,
997  CommandFunctorPointer callback,
998  const wxString &nameSuffix,
999  int index,
1000  int count,
1001  bool bIsEffect,
1002  const CommandParameter &parameter)
1003 {
1004  const bool multi = !nameSuffix.empty();
1005  wxString name = nameIn;
1006 
1007  // If we have the identifier already, reuse it.
1009  if (!prev);
1010  else if( prev->label != label );
1011  else if( multi );
1012  else
1013  return prev;
1014 
1015  {
1016  // Make a unique_ptr or shared_ptr as appropriate:
1017  auto entry = std::make_unique<CommandListEntry>();
1018 
1019  wxString labelPrefix;
1020  if (!mSubMenuList.empty()) {
1021  labelPrefix = mSubMenuList.back()->name;
1022  }
1023 
1024  // For key bindings for commands with a list, such as align,
1025  // the name in prefs is the category name plus the effect name.
1026  // This feature is not used for built-in effects.
1027  if (multi) {
1028  // The name needs to be clean for use by automation.
1029  wxString cleanedName = wxString::Format(wxT("%s_%s"), name, nameSuffix);
1030  cleanedName.Replace( "/", "" );
1031  cleanedName.Replace( "&", "" );
1032  cleanedName.Replace( " ", "" );
1033  name = cleanedName;
1034  }
1035 
1036  // wxMac 2.5 and higher will do special things with the
1037  // Preferences, Exit (Quit), and About menu items,
1038  // if we give them the right IDs.
1039  // Otherwise we just pick increasing ID numbers for each NEW
1040  // command. Note that the name string we are comparing
1041  // ("About", "Preferences") is the internal command name
1042  // (untranslated), not the label that actually appears in the
1043  // menu (which might be translated).
1044 
1046  entry->id = mCurrentID;
1047  entry->parameter = parameter;
1048 
1049 #if defined(__WXMAC__)
1050  if (name == wxT("Preferences"))
1051  entry->id = wxID_PREFERENCES;
1052  else if (name == wxT("Exit"))
1053  entry->id = wxID_EXIT;
1054  else if (name == wxT("About"))
1055  entry->id = wxID_ABOUT;
1056 #endif
1057 
1058  entry->name = name;
1059  entry->label = label;
1060  entry->longLabel = longLabel.IsEmpty() ? label : longLabel;
1061  entry->hasDialog = hasDialog;
1062  entry->key = NormalizedKeyString{ accel.BeforeFirst(wxT('\t')) };
1063  entry->defaultKey = entry->key;
1064  entry->labelPrefix = labelPrefix;
1065  entry->labelTop = wxMenuItem::GetLabelText(mCurrentMenuName);
1066  entry->menu = menu;
1067  entry->finder = finder;
1068  entry->callback = callback;
1069  entry->isEffect = bIsEffect;
1070  entry->multi = multi;
1071  entry->index = index;
1072  entry->count = count;
1073  entry->flags = mDefaultFlags;
1074  entry->mask = mDefaultMask;
1075  entry->enabled = true;
1076  entry->skipKeydown = (accel.Find(wxT("\tskipKeydown")) != wxNOT_FOUND);
1077  entry->wantKeyup = (accel.Find(wxT("\twantKeyup")) != wxNOT_FOUND) || entry->skipKeydown;
1078  entry->isGlobal = false;
1080 
1081  // Exclude accelerators that are in the MaxList.
1082  // Note that the default is unaffected, intentionally so.
1083  // There are effectively two levels of default, the full (max) list
1084  // and the normal reduced list.
1085  if( std::binary_search( mMaxListOnly.begin(), mMaxListOnly.end(),
1086  entry->key ) )
1087  entry->key = {};
1088 
1089  // Key from preferences overrides the default key given
1090  gPrefs->SetPath(wxT("/NewKeys"));
1091  if (gPrefs->HasEntry(entry->name)) {
1092  entry->key =
1093  NormalizedKeyString{ gPrefs->Read(entry->name, entry->key.Raw()) };
1094  }
1095  gPrefs->SetPath(wxT("/"));
1096 
1097  mCommandList.push_back(std::move(entry));
1098  // Don't use the variable entry eny more!
1099  }
1100 
1101  // New variable
1102  CommandListEntry *entry = &*mCommandList.back();
1103  mCommandIDHash[entry->id] = entry;
1104 
1105 #if defined(__WXDEBUG__)
1106  prev = mCommandNameHash[entry->name];
1107  if (prev) {
1108  // Under Linux it looks as if we may ask for a newID for the same command
1109  // more than once. So it's only an error if two different commands
1110  // have the exact same name.
1111  if( prev->label != entry->label )
1112  {
1113  wxLogDebug(wxT("Command '%s' defined by '%s' and '%s'"),
1114  entry->name,
1115  prev->label.BeforeFirst(wxT('\t')),
1116  entry->label.BeforeFirst(wxT('\t')));
1117  wxFAIL_MSG(wxString::Format(wxT("Command '%s' defined by '%s' and '%s'"),
1118  entry->name,
1119  prev->label.BeforeFirst(wxT('\t')),
1120  entry->label.BeforeFirst(wxT('\t'))));
1121  }
1122  }
1123 #endif
1124  mCommandNameHash[entry->name] = entry;
1125 
1126  if (!entry->key.empty()) {
1127  mCommandKeyHash[entry->key] = entry;
1128  }
1129 
1130  return entry;
1131 }
1132 
1133 wxString CommandManager::GetLabel(const CommandListEntry *entry) const
1134 {
1135  wxString label = entry->label;
1136  if (!entry->key.empty())
1137  {
1138  label += wxT("\t") + entry->key.Raw();
1139  }
1140 
1141  return label;
1142 }
1143 
1144 // A label that may have its accelerator disabled.
1145 // The problem is that as soon as we show accelerators in the menu, the menu might
1146 // catch them in normal wxWidgets processing, rather than passing the key presses on
1147 // to the controls that had the focus. We would like all the menu accelerators to be
1148 // disabled, in fact.
1150 {
1151  wxString label = entry->label;
1152 #if 1
1153  wxString Accel = "";
1154  do{
1155  if (!entry->key.empty())
1156  {
1157  // Dummy accelerator that looks Ok in menus but is non functional.
1158  // Note the space before the key.
1159 #ifdef __WXMSW__
1160  auto key = entry->key.Raw();
1161  Accel = wxString("\t ") + key;
1162  if( key.StartsWith("Left" )) break;
1163  if( key.StartsWith("Right")) break;
1164  if( key.StartsWith("Up" )) break;
1165  if( key.StartsWith("Down")) break;
1166  if( key.StartsWith("Return")) break;
1167  if( key.StartsWith("Tab")) break;
1168  if( key.StartsWith("Shift+Tab")) break;
1169  if( key.StartsWith("0")) break;
1170  if( key.StartsWith("1")) break;
1171  if( key.StartsWith("2")) break;
1172  if( key.StartsWith("3")) break;
1173  if( key.StartsWith("4")) break;
1174  if( key.StartsWith("5")) break;
1175  if( key.StartsWith("6")) break;
1176  if( key.StartsWith("7")) break;
1177  if( key.StartsWith("8")) break;
1178  if( key.StartsWith("9")) break;
1179  // Uncomment the below so as not to add the illegal accelerators.
1180  // Accel = "";
1181  //if( entry->key.StartsWith("Space" )) break;
1182  // These ones appear to be illegal already and mess up accelerator processing.
1183  if( key.StartsWith("NUMPAD_ENTER" )) break;
1184  if( key.StartsWith("Backspace" )) break;
1185  if( key.StartsWith("Delete" )) break;
1186 #endif
1187  //wxLogDebug("Added Accel:[%s][%s]", entry->label, entry->key );
1188  // Normal accelerator.
1189  Accel = wxString("\t") + entry->key.Raw();
1190  }
1191  } while (false );
1192  label += Accel;
1193 #endif
1194  return label;
1195 }
1201 void CommandManager::Enable(CommandListEntry *entry, bool enabled)
1202 {
1203  if (!entry->menu) {
1204  entry->enabled = enabled;
1205  return;
1206  }
1207 
1208  // LL: Refresh from real state as we can get out of sync on the
1209  // Mac due to its reluctance to enable menus when in a modal
1210  // state.
1211  entry->enabled = entry->menu->IsEnabled(entry->id);
1212 
1213  // Only enabled if needed
1214  if (entry->enabled != enabled) {
1215  entry->menu->Enable(entry->id, enabled);
1216  entry->enabled = entry->menu->IsEnabled(entry->id);
1217  }
1218 
1219  if (entry->multi) {
1220  int i;
1221  int ID = entry->id;
1222 
1223  for(i=1; i<entry->count; i++) {
1224  ID = NextIdentifier(ID);
1225 
1226  // This menu item is not necessarily in the same menu, because
1227  // multi-items can be spread across multiple sub menus
1228  CommandListEntry *multiEntry = mCommandIDHash[ID];
1229  if (multiEntry) {
1230  wxMenuItem *item = multiEntry->menu->FindItem(ID);
1231 
1232  if (item) {
1233  item->Enable(enabled);
1234  } else {
1235  wxLogDebug(wxT("Warning: Menu entry with id %i in %s not found"),
1236  ID, (const wxChar*)entry->name);
1237  }
1238  } else {
1239  wxLogDebug(wxT("Warning: Menu entry with id %i not in hash"), ID);
1240  }
1241  }
1242  }
1243 }
1244 
1245 void CommandManager::Enable(const wxString &name, bool enabled)
1246 {
1248  if (!entry || !entry->menu) {
1249  wxLogDebug(wxT("Warning: Unknown command enabled: '%s'"),
1250  (const wxChar*)name);
1251  return;
1252  }
1253 
1254  Enable(entry, enabled);
1255 }
1256 
1258 {
1259  for(const auto &entry : mCommandList) {
1260  if (entry->multi && entry->index != 0)
1261  continue;
1262  if( entry->isOccult )
1263  continue;
1264 
1265  auto combinedMask = (mask & entry->mask);
1266  if (combinedMask) {
1267  bool enable = ((flags & combinedMask) ==
1268  (entry->flags & combinedMask));
1269  Enable(entry.get(), enable);
1270  }
1271  }
1272 }
1273 
1274 bool CommandManager::GetEnabled(const wxString &name)
1275 {
1277  if (!entry || !entry->menu) {
1278  wxLogDebug(wxT("Warning: command doesn't exist: '%s'"),
1279  (const wxChar*)name);
1280  return false;
1281  }
1282  return entry->enabled;
1283 }
1284 
1285 void CommandManager::Check(const wxString &name, bool checked)
1286 {
1288  if (!entry || !entry->menu || entry->isOccult) {
1289  return;
1290  }
1291  entry->menu->Check(entry->id, checked);
1292 }
1293 
1295 void CommandManager::Modify(const wxString &name, const wxString &newLabel)
1296 {
1298  if (entry && entry->menu) {
1299  entry->label = newLabel;
1300  entry->menu->SetLabel(entry->id, GetLabel(entry));
1301  }
1302 }
1303 
1304 void CommandManager::SetKeyFromName(const wxString &name,
1305  const NormalizedKeyString &key)
1306 {
1308  if (entry) {
1309  entry->key = key;
1310  }
1311 }
1312 
1314 {
1315  const auto &entry = mCommandList[i];
1316  entry->key = key;
1317 }
1318 
1319 void CommandManager::TellUserWhyDisallowed( const wxString & Name, CommandFlag flagsGot, CommandMask flagsRequired )
1320 {
1321  // The default string for 'reason' is a catch all. I hope it won't ever be seen
1322  // and that we will get something more specific.
1323  wxString reason = _("There was a problem with your last action. If you think\nthis is a bug, please tell us exactly where it occurred.");
1324  // The default title string is 'Disallowed'.
1325  wxString title = _("Disallowed");
1326  wxString helpPage ="";
1327 
1328  auto missingFlags = flagsRequired & (~flagsGot );
1329  if( missingFlags & AudioIONotBusyFlag )
1330  // This reason will not be shown, because options that require it will be greyed our.
1331  reason = _("You can only do this when playing and recording are\nstopped. (Pausing is not sufficient.)");
1332  else if( missingFlags & StereoRequiredFlag )
1333  // This reason will not be shown, because the stereo-to-mono is greyed out if not allowed.
1334  reason = _("You must first select some stereo audio to perform this\naction. (You cannot use this with mono.)");
1335  // In reporting the issue with cut or copy, we don't tell the user they could also select some text in a label.
1336  else if(( missingFlags & TimeSelectedFlag ) || (missingFlags &CutCopyAvailableFlag )){
1337  title = _("No Audio Selected");
1338 #ifdef EXPERIMENTAL_DA
1339  // i18n-hint: %s will be replaced by the name of an action, such as Normalize, Cut, Fade.
1340  reason = wxString::Format( _("You must first select some audio for '%s' to act on.\n\nCtrl + A selects all audio."), Name );
1341 #else
1342 #ifdef __WXMAC__
1343  // i18n-hint: %s will be replaced by the name of an action, such as Normalize, Cut, Fade.
1344  reason = wxString::Format( _("Select the audio for %s to use (for example, Cmd + A to Select All) then try again."
1345  // No need to explain what a help button is for.
1346  // "\n\nClick the Help button to learn more about selection methods."
1347  ), Name );
1348 
1349 #else
1350  // i18n-hint: %s will be replaced by the name of an action, such as Normalize, Cut, Fade.
1351  reason = wxString::Format( _("Select the audio for %s to use (for example, Ctrl + A to Select All) then try again."
1352  // No need to explain what a help button is for.
1353  // "\n\nClick the Help button to learn more about selection methods."
1354  ), Name );
1355 #endif
1356 #endif
1357  helpPage = "Selecting_Audio_-_the_basics";
1358  }
1359  else if( missingFlags & WaveTracksSelectedFlag)
1360  reason = _("You must first select some audio to perform this action.\n(Selecting other kinds of track won't work.)");
1361  else if ( missingFlags & TracksSelectedFlag )
1362  // i18n-hint: %s will be replaced by the name of an action, such as "Remove Tracks".
1363  reason = wxString::Format(_("\"%s\" requires one or more tracks to be selected."), Name);
1364  // If the only thing wrong was no tracks, we do nothing and don't report a problem
1365  else if( missingFlags == TracksExistFlag )
1366  return;
1367  // Likewise return if it was just no tracks, and track panel did not have focus. (e.g. up-arrow to move track)
1368  else if( missingFlags == (TracksExistFlag | TrackPanelHasFocus) )
1369  return;
1370  // Likewise as above too...
1371  else if( missingFlags == TrackPanelHasFocus )
1372  return;
1373 
1374  // Does not have the warning icon...
1376  NULL,
1377  title,
1378  reason,
1379  helpPage);
1380 }
1381 
1383 (const TranslatedInternalString commands[], size_t nCommands) const
1384 {
1385  wxString mark;
1386  // This depends on the language setting and may change in-session after
1387  // change of preferences:
1388  bool rtl = (wxLayout_RightToLeft == wxTheApp->GetLayoutDirection());
1389  if (rtl)
1390  mark = wxT("\u200f");
1391 
1392  static const wxString &separatorFormat = wxT("%s / %s");
1393  wxString result;
1394  for (size_t ii = 0; ii < nCommands; ++ii) {
1395  const auto &pair = commands[ii];
1396  // If RTL, then the control character forces right-to-left sequencing of
1397  // "/" -separated command names, and puts any "(...)" shortcuts to the
1398  // left, consistently with accelerators in menus (assuming matching
1399  // operating system prefernces for language), even if the command name
1400  // was missing from the translation file and defaulted to the English.
1401  auto piece = wxString::Format(wxT("%s%s"), mark, pair.Translated());
1402 
1403  wxString name{ pair.Internal() };
1404  if (!name.empty()) {
1405  auto keyStr = GetKeyFromName(name);
1406  if (!keyStr.empty()){
1407  auto keyString = keyStr.Display(true);
1408  auto format = wxT("%s %s(%s)");
1409 #ifdef __WXMAC__
1410  // The unicode controls push and pop left-to-right embedding.
1411  // This keeps the directionally weak characters, such as uparrow
1412  // for Shift, left of the key name,
1413  // consistently with how menu accelerators appear, even when the
1414  // system language is RTL.
1415  format = wxT("%s %s(\u202a%s\u202c)");
1416 #endif
1417  // The mark makes correctly placed parentheses for RTL, even
1418  // in the case that the piece is untranslated.
1419  piece = wxString::Format(format, piece, mark, keyString);
1420  }
1421  }
1422 
1423  if (result.empty())
1424  result = piece;
1425  else
1426  result = wxString::Format(separatorFormat, result, piece);
1427  }
1428  return result;
1429 }
1430 
1434 bool CommandManager::FilterKeyEvent(AudacityProject *project, const wxKeyEvent & evt, bool permit)
1435 {
1437  if (entry == NULL)
1438  {
1439  return false;
1440  }
1441 
1442  int type = evt.GetEventType();
1443 
1444  // Global commands aren't tied to any specific project
1445  if (entry->isGlobal && type == wxEVT_KEY_DOWN)
1446  {
1447  // Global commands are always disabled so they do not interfere with the
1448  // rest of the command handling. But, to use the common handler, we
1449  // enable them temporarily and then disable them again after handling.
1450  // LL: Why do they need to be disabled???
1451  entry->enabled = false;
1452  auto cleanup = valueRestorer( entry->enabled, true );
1453  return HandleCommandEntry(entry, NoFlagsSpecifed, NoFlagsSpecifed, &evt);
1454  }
1455 
1456  wxWindow * pFocus = wxWindow::FindFocus();
1457  wxWindow * pParent = wxGetTopLevelParent( pFocus );
1458  bool validTarget = pParent == project;
1459  // Bug 1557. MixerBoard should count as 'destined for project'
1460  // MixerBoard IS a TopLevelWindow, and its parent is the project.
1461  if( pParent && pParent->GetParent() == project){
1462  if( dynamic_cast<MixerBoardFrame*>( pParent) != NULL )
1463  validTarget = true;
1464  }
1465  validTarget = validTarget && wxEventLoop::GetActive()->IsMain();
1466 
1467  // Any other keypresses must be destined for this project window
1468  if (!permit && !validTarget )
1469  {
1470  return false;
1471  }
1472 
1473  auto flags = project->GetUpdateFlags();
1474 
1475  wxKeyEvent temp = evt;
1476 
1477  // Possibly let wxWidgets do its normal key handling IF it is one of
1478  // the standard navigation keys.
1479  if((type == wxEVT_KEY_DOWN) || (type == wxEVT_KEY_UP ))
1480  {
1481  wxWindow * pWnd = wxWindow::FindFocus();
1482  wxWindow * pTrackPanel = (wxWindow*)GetActiveProject()->GetTrackPanel();
1483  bool bIntercept = pWnd != pTrackPanel;
1484  // Intercept keys from windows that are NOT the Lyrics panel
1485  if( bIntercept ){
1486  bIntercept = pWnd && ( dynamic_cast<LyricsPanel*>(pWnd) == NULL );
1487  }
1488  //wxLogDebug("Focus: %p TrackPanel: %p", pWnd, pTrackPanel );
1489  // We allow the keystrokes below to be handled by wxWidgets controls IF we are
1490  // in some sub window rather than in the TrackPanel itself.
1491  // Otherwise they will go to our command handler and if it handles them
1492  // they will NOT be available to wxWidgets.
1493  if( bIntercept ){
1494  switch( evt.GetKeyCode() ){
1495  case WXK_LEFT:
1496  case WXK_RIGHT:
1497  case WXK_UP:
1498  case WXK_DOWN:
1499  // Don't trap WXK_SPACE (Bug 1727 - SPACE not starting/stopping playback
1500  // when cursor is in a time control)
1501  // case WXK_SPACE:
1502  case WXK_TAB:
1503  case WXK_BACK:
1504  case WXK_HOME:
1505  case WXK_END:
1506  case WXK_RETURN:
1507  case WXK_NUMPAD_ENTER:
1508  case WXK_DELETE:
1509  case '0':
1510  case '1':
1511  case '2':
1512  case '3':
1513  case '4':
1514  case '5':
1515  case '6':
1516  case '7':
1517  case '8':
1518  case '9':
1519  return false;
1520  }
1521  }
1522  }
1523 
1524  if (type == wxEVT_KEY_DOWN)
1525  {
1526  if (entry->skipKeydown)
1527  {
1528  return true;
1529  }
1530  return HandleCommandEntry(entry, flags, NoFlagsSpecifed, &temp);
1531  }
1532 
1533  if (type == wxEVT_KEY_UP && entry->wantKeyup)
1534  {
1535  return HandleCommandEntry(entry, flags, NoFlagsSpecifed, &temp);
1536  }
1537 
1538  return false;
1539 }
1540 
1546  CommandFlag flags, CommandMask mask, const wxEvent * evt)
1547 {
1548  if (!entry || !entry->enabled)
1549  return false;
1550 
1551  auto proj = GetActiveProject();
1552 
1553  auto combinedMask = (mask & entry->mask);
1554  if (combinedMask) {
1555 
1556  wxASSERT( proj );
1557  if( !proj )
1558  return false;
1559 
1560  wxString NiceName = entry->label;
1561  NiceName.Replace("&", "");// remove &
1562  NiceName.Replace(".","");// remove ...
1563  // NB: The call may have the side effect of changing flags.
1564  bool allowed = proj->ReportIfActionNotAllowed(
1565  NiceName, flags, entry->flags, combinedMask );
1566  // If the function was disallowed, it STILL should count as having been
1567  // handled (by doing nothing or by telling the user of the problem).
1568  // Otherwise we may get other handlers having a go at obeying the command.
1569  if (!allowed)
1570  return true;
1571  }
1572 
1573  const CommandContext context{ *proj, evt, entry->index, entry->parameter };
1574  auto &handler = entry->finder(*proj);
1575  (handler.*(entry->callback))(context);
1576 
1577  return true;
1578 }
1579 
1585 #include "../prefs/PrefsDialog.h"
1586 #include "../prefs/KeyConfigPrefs.h"
1588 {
1589  CommandListEntry *entry = mCommandIDHash[id];
1590 
1591 #ifdef EXPERIMENTAL_EASY_CHANGE_KEY_BINDINGS
1592  if (::wxGetMouseState().ShiftDown()) {
1593  // Only want one page of the preferences
1594  KeyConfigPrefsFactory keyConfigPrefsFactory{ entry->name };
1595  PrefsDialog::Factories factories;
1596  factories.push_back(&keyConfigPrefsFactory);
1597  GlobalPrefsDialog dialog(GetActiveProject(), factories);
1598  dialog.ShowModal();
1600  return true;
1601  }
1602 #endif
1603 
1604  return HandleCommandEntry( entry, flags, mask );
1605 }
1606 
1610 bool CommandManager::HandleTextualCommand(const wxString & Str, const CommandContext & context, CommandFlag flags, CommandMask mask)
1611 {
1612  if( Str.IsEmpty() )
1613  return false;
1614  // Linear search for now...
1615  for (const auto &entry : mCommandList)
1616  {
1617  if (!entry->multi)
1618  {
1619  // Testing against labelPrefix too allows us to call Nyquist functions by name.
1620  if( Str.IsSameAs( entry->name, false ) || Str.IsSameAs( entry->labelPrefix, false ))
1621  {
1622  return HandleCommandEntry( entry.get(), flags, mask);
1623  }
1624  }
1625  else
1626  {
1627  // Handle multis too...
1628  if( Str.IsSameAs( entry->name, false ) )
1629  {
1630  return HandleCommandEntry( entry.get(), flags, mask);
1631  }
1632  }
1633  }
1634  // Not one of the singleton commands.
1635  // We could/should try all the list-style commands.
1636  // instead we only try the effects.
1637  AudacityProject * proj = GetActiveProject();
1638  if( !proj )
1639  {
1640  return false;
1641  }
1642 
1646  while (plug)
1647  {
1648  if (em.GetCommandIdentifier(plug->GetID()).IsSameAs(Str, false))
1649  {
1650  return proj->DoEffect(plug->GetID(), context, AudacityProject::OnEffectFlags::kConfigured);
1651  }
1652  plug = pm.GetNextPlugin(PluginTypeEffect);
1653  }
1654 
1655  return false;
1656 }
1657 
1658 void CommandManager::GetCategories(wxArrayString &cats)
1659 {
1660  cats.Clear();
1661 
1662  for (const auto &entry : mCommandList) {
1663  wxString cat = entry->labelTop;
1664  if (cats.Index(cat) == wxNOT_FOUND) {
1665  cats.Add(cat);
1666  }
1667  }
1668 #if 0
1669  mCommandList.GetCount(); i++) {
1670  if (includeMultis || !mCommandList[i]->multi)
1671  names.Add(mCommandList[i]->name);
1672  }
1673 
1675  if (p == NULL) {
1676  return;
1677  }
1678 
1679  wxMenuBar *bar = p->GetMenuBar();
1680  size_t cnt = bar->GetMenuCount();
1681  for (size_t i = 0; i < cnt; i++) {
1682  cats.Add(bar->GetMenuLabelText(i));
1683  }
1684 
1685  cats.Add(COMMAND);
1686 #endif
1687 }
1688 
1689 void CommandManager::GetAllCommandNames(wxArrayString &names,
1690  bool includeMultis) const
1691 {
1692  for(const auto &entry : mCommandList) {
1693  if ( entry->isEffect )
1694  continue;
1695  if (!entry->multi)
1696  names.Add(entry->name);
1697  else if( includeMultis )
1698  names.Add(entry->name );// + wxT(":")/*+ mCommandList[i]->label*/);
1699  }
1700 }
1701 
1702 void CommandManager::GetAllCommandLabels(wxArrayString &names,
1703  std::vector<bool> &vHasDialog,
1704  bool includeMultis) const
1705 {
1706  vHasDialog.clear();
1707  for(const auto &entry : mCommandList) {
1708  // This is fetching commands from the menus, for use as batch commands.
1709  // Until we have properly merged EffectManager and CommandManager
1710  // we explicitly exclude effects, as they are already handled by the
1711  // effects Manager.
1712  if ( entry->isEffect )
1713  continue;
1714  if (!entry->multi)
1715  names.Add(entry->longLabel), vHasDialog.push_back(entry->hasDialog);
1716  else if( includeMultis )
1717  names.Add(entry->longLabel), vHasDialog.push_back(entry->hasDialog);
1718  }
1719 }
1720 
1722  wxArrayString &names,
1723  std::vector<NormalizedKeyString> &keys,
1724  std::vector<NormalizedKeyString> &default_keys,
1725  wxArrayString &labels,
1726  wxArrayString &categories,
1727 #if defined(EXPERIMENTAL_KEY_VIEW)
1728  wxArrayString &prefixes,
1729 #endif
1730  bool includeMultis)
1731 {
1732  for(const auto &entry : mCommandList) {
1733  // GetAllCommandData is used by KeyConfigPrefs.
1734  // It does need the effects.
1735  //if ( entry->isEffect )
1736  // continue;
1737  if (!entry->multi)
1738  {
1739  names.Add(entry->name);
1740  keys.push_back(entry->key);
1741  default_keys.push_back(entry->defaultKey);
1742  labels.Add(entry->label);
1743  categories.Add(entry->labelTop);
1744 #if defined(EXPERIMENTAL_KEY_VIEW)
1745  prefixes.Add(entry->labelPrefix);
1746 #endif
1747  }
1748  else if( includeMultis )
1749  {
1750  names.Add(entry->name);
1751  keys.push_back(entry->key);
1752  default_keys.push_back(entry->defaultKey);
1753  labels.Add(entry->label);
1754  categories.Add(entry->labelTop);
1755 #if defined(EXPERIMENTAL_KEY_VIEW)
1756  prefixes.Add(entry->labelPrefix);
1757 #endif
1758  }
1759  }
1760 }
1761 
1763 {
1764  CommandListEntry *entry = mCommandIDHash[id];
1765  if (!entry)
1766  return wxT("");
1767  return entry->name;
1768 }
1769 
1770 wxString CommandManager::GetLabelFromName(const wxString &name)
1771 {
1773  if (!entry)
1774  return wxT("");
1775 
1776  return entry->longLabel;
1777 }
1778 
1779 wxString CommandManager::GetPrefixedLabelFromName(const wxString &name)
1780 {
1782  if (!entry)
1783  return wxT("");
1784 
1785 #if defined(EXPERIMENTAL_KEY_VIEW)
1786  wxString prefix;
1787  if (!entry->labelPrefix.IsEmpty()) {
1788  prefix = entry->labelPrefix + wxT(" - ");
1789  }
1790  return wxMenuItem::GetLabelText(prefix + entry->label);
1791 #else
1792  return wxString(entry->labelPrefix + wxT(" ") + entry->label).Trim(false).Trim(true);
1793 #endif
1794 }
1795 
1796 wxString CommandManager::GetCategoryFromName(const wxString &name)
1797 {
1799  if (!entry)
1800  return wxT("");
1801 
1802  return entry->labelTop;
1803 }
1804 
1806 {
1807  CommandListEntry *entry =
1808  // May create a NULL entry
1809  const_cast<CommandManager*>(this)->mCommandNameHash[name];
1810  if (!entry)
1811  return {};
1812 
1813  return entry->key;
1814 }
1815 
1817 {
1819  if (!entry)
1820  return {};
1821 
1822  return entry->defaultKey;
1823 }
1824 
1825 bool CommandManager::HandleXMLTag(const wxChar *tag, const wxChar **attrs)
1826 {
1827  if (!wxStrcmp(tag, wxT("audacitykeyboard"))) {
1828  mXMLKeysRead = 0;
1829  }
1830 
1831  if (!wxStrcmp(tag, wxT("command"))) {
1832  wxString name;
1833  NormalizedKeyString key;
1834 
1835  while(*attrs) {
1836  const wxChar *attr = *attrs++;
1837  const wxChar *value = *attrs++;
1838 
1839  if (!value)
1840  break;
1841 
1842  if (!wxStrcmp(attr, wxT("name")) && XMLValueChecker::IsGoodString(value))
1843  name = value;
1844  if (!wxStrcmp(attr, wxT("key")) && XMLValueChecker::IsGoodString(value))
1845  key = NormalizedKeyString{ value };
1846  }
1847 
1848  if (mCommandNameHash[name]) {
1849  if (GetDefaultKeyFromName(name) != key) {
1850  mCommandNameHash[name]->key = key;
1851  mXMLKeysRead++;
1852  }
1853  }
1854  }
1855 
1856  return true;
1857 }
1858 
1859 void CommandManager::HandleXMLEndTag(const wxChar *tag)
1860 {
1861  if (!wxStrcmp(tag, wxT("audacitykeyboard"))) {
1862  AudacityMessageBox(wxString::Format(_("Loaded %d keyboard shortcuts\n"),
1863  mXMLKeysRead),
1864  _("Loading Keyboard Shortcuts"),
1865  wxOK | wxCENTRE);
1866  }
1867 }
1868 
1869 XMLTagHandler *CommandManager::HandleXMLChild(const wxChar * WXUNUSED(tag))
1870 {
1871  return this;
1872 }
1873 
1875 // may throw
1876 {
1877  xmlFile.StartTag(wxT("audacitykeyboard"));
1878  xmlFile.WriteAttr(wxT("audacityversion"), AUDACITY_VERSION_STRING);
1879 
1880  for(const auto &entry : mCommandList) {
1881  wxString label = entry->label;
1882  label = wxMenuItem::GetLabelText(label.BeforeFirst(wxT('\t')));
1883 
1884  xmlFile.StartTag(wxT("command"));
1885  xmlFile.WriteAttr(wxT("name"), entry->name);
1886  xmlFile.WriteAttr(wxT("label"), label);
1887  xmlFile.WriteAttr(wxT("key"), entry->key.Raw());
1888  xmlFile.EndTag(wxT("command"));
1889  }
1890 
1891  xmlFile.EndTag(wxT("audacitykeyboard"));
1892 }
1893 
1895 {
1896  mDefaultFlags = flags;
1897  mDefaultMask = mask;
1898 }
1899 
1901 {
1902  bMakingOccultCommands = bOccult;
1903 }
1904 
1905 void CommandManager::SetCommandFlags(const wxString &name,
1906  CommandFlag flags, CommandMask mask)
1907 {
1909  if (entry) {
1910  entry->flags = flags;
1911  entry->mask = mask;
1912  }
1913 }
1914 
1915 void CommandManager::SetCommandFlags(const wxChar **names,
1916  CommandFlag flags, CommandMask mask)
1917 {
1918  const wxChar **nptr = names;
1919  while(*nptr) {
1920  SetCommandFlags(wxString(*nptr), flags, mask);
1921  nptr++;
1922  }
1923 }
1924 
1926 {
1927  va_list list;
1928  va_start(list, mask);
1929  for(;;) {
1930  const wxChar *name = va_arg(list, const wxChar *);
1931  if (!name)
1932  break;
1933  SetCommandFlags(wxString(name), flags, mask);
1934  }
1935  va_end(list);
1936 }
1937 
1938 #if defined(__WXDEBUG__)
1939 void CommandManager::CheckDups()
1940 {
1941  int cnt = mCommandList.size();
1942  for (size_t j = 0; (int)j < cnt; j++) {
1943  if (mCommandList[j]->key.empty()) {
1944  continue;
1945  }
1946 
1947  if (mCommandList[j]->label.AfterLast(wxT('\t')) == wxT("allowDup")) {
1948  continue;
1949  }
1950 
1951  for (size_t i = 0; (int)i < cnt; i++) {
1952  if (i == j) {
1953  continue;
1954  }
1955 
1956  if (mCommandList[i]->key == mCommandList[j]->key) {
1957  wxString msg;
1958  msg.Printf(wxT("key combo '%s' assigned to '%s' and '%s'"),
1959  mCommandList[i]->key.Raw(),
1960  mCommandList[i]->label.BeforeFirst(wxT('\t')),
1961  mCommandList[j]->label.BeforeFirst(wxT('\t')));
1962  wxASSERT_MSG(mCommandList[i]->key != mCommandList[j]->key, msg);
1963  }
1964  }
1965  }
1966 }
1967 
1968 #endif
void BeginMenu(const wxString &tName)
wxString labelPrefix
bool GetEnabled(const wxString &name)
static wxArrayString names()
Definition: Tags.cpp:697
AudacityPrefs * gPrefs
Definition: Prefs.cpp:73
const PluginDescriptor * GetFirstPlugin(int type)
#define AUDACITY_VERSION_STRING
Definition: Audacity.h:81
static const int kConfigured
Definition: Project.h:489
bool bMakingOccultCommands
NormalizedKeyString GetDefaultKeyFromName(const wxString &name)
int id
wxMenu * BeginSubMenu(const wxString &tName)
void AddCommand(const wxChar *name, const wxChar *label, CommandHandlerFinder finder, CommandFunctorPointer callback, CommandFlag flags=NoFlagsSpecifed, CommandMask mask=NoFlagsSpecifed)
wxString GetLabelWithDisabledAccel(const CommandListEntry *entry) const
bool HandleMenuID(int id, CommandFlag flags, CommandMask mask)
bool FilterKeyEvent(AudacityProject *project, const wxKeyEvent &evt, bool permit=false)
CommandMask mask
wxString label
std::unique_ptr< wxMenuBar > AddMenuBar(const wxString &sMenu)
CommandList mCommandList
bool DoEffect(const PluginID &ID, const CommandContext &context, int flags)
Definition: Menus.cpp:4512
void SetCurrentMenu(wxMenu *menu)
int count
wxString label
Definition: Tags.cpp:727
std::unique_ptr< wxMenu > uCurrentMenu
void GetAllCommandLabels(wxArrayString &labels, std::vector< bool > &vHasDialog, bool includeMultis) const
wxString GetNameFromID(int id)
bool enabled
void EnableUsingFlags(CommandFlag flags, CommandMask mask)
void WriteXML(XMLWriter &xmlFile) const
static void RebuildAllMenuBars()
Definition: Menus.cpp:4653
NormalizedKeyString KeyEventToKeyString(const wxKeyEvent &event)
Definition: Keyboard.cpp:78
bool HandleTextualCommand(const wxString &Str, const CommandContext &context, CommandFlag flags, CommandMask mask)
void AddItem(const wxChar *name, const wxChar *label, bool hasDialog, CommandHandlerFinder finder, CommandFunctorPointer callback, CommandFlag flags=NoFlagsSpecifed, CommandMask mask=NoFlagsSpecifed, bool bIsEffect=false, const CommandParameter &parameter=CommandParameter{})
wxString DescribeCommandsAndShortcuts(const TranslatedInternalString commands[], size_t nCommands) const
const PluginDescriptor * GetNextPlugin(int type)
int AudacityMessageBox(const wxString &message, const wxString &caption=AudacityMessageBoxCaptionStr(), long style=wxOK|wxCENTRE, wxWindow *parent=NULL, int x=wxDefaultCoord, int y=wxDefaultCoord)
Definition: ErrorDialog.h:92
wxMenuBar * GetMenuBar(const wxString &sMenu) const
SubMenuList mSubMenuList
CommandContext provides addiitonal information to an 'Apply()' command. It provides the project...
bool HandleXMLTag(const wxChar *tag, const wxChar **attrs) override
CommandManager * GetCommandManager()
Definition: Project.h:346
virtual ~CommandManager()
int NextIdentifier(int ID)
void ShowErrorDialog(wxWindow *parent, const wxString &dlogTitle, const wxString &message, const wxString &helpPage, const bool Close)
Displays an error dialog with a button that offers help.
CommandKeyHash mCommandKeyHash
void SetCommandFlags(const wxString &name, CommandFlag flags, CommandMask mask)
wxString mCurrentMenuName
const wxString & Internal() const
Definition: Internat.h:210
void AddCheck(const wxChar *name, const wxChar *label, bool hasDialog, CommandHandlerFinder finder, CommandFunctorPointer callback, int checkmark=0)
std::vector< NormalizedKeyString > mMaxListOnly
XMLTagHandler * HandleXMLChild(const wxChar *tag) override
SimpleGuard< R > MakeSimpleGuard(R value)
void GetAllCommandData(wxArrayString &names, std::vector< NormalizedKeyString > &keys, std::vector< NormalizedKeyString > &default_keys, wxArrayString &labels, wxArrayString &categories, wxArrayString &prefixes, bool includeMultis)
int FilterEvent(wxEvent &event) override
void(CommandHandlerObject::*)(const CommandContext &context) CommandFunctorPointer
void SetKeyFromIndex(int i, const NormalizedKeyString &key)
static const std::vector< NormalizedKeyString > & ExcludedList()
int index
wxMenu * menu
AudacityProject provides the main window, with tools and tracks contained within it.
Definition: Project.h:176
CommandParameter parameter
wxString GetPrefixedLabelFromName(const wxString &name)
int format
Definition: ExportPCM.cpp:56
CommandNameHash mCommandNameHash
static bool IsGoodString(const wxString &str)
void Enable(const wxString &name, bool enabled)
void Check(const wxString &name, bool checked)
wxString GetUnicodeString(const wxKeyEvent &event)
void Modify(const wxString &name, const wxString &newLabel)
Changes the label text of a menu item.
CommandFunctorPointer callback
wxString GetCategoryFromName(const wxString &name)
wxMenu * CurrentMenu() const
CommandListEntry is a structure used by CommandManager.
std::vector< PrefsNode > Factories
Definition: PrefsDialog.h:50
wxMenuBar * CurrentMenuBar() const
bool skipKeydown
wxMenu * mCurrentMenu
wxString GetLabel(const CommandListEntry *entry) const
CommandListEntry * NewIdentifier(const wxString &name, const wxString &label, const wxString &longLabel, bool hasDialog, wxMenu *menu, CommandHandlerFinder finder, CommandFunctorPointer callback, const wxString &nameSuffix, int index, int count, bool bIsEffect)
CommandManager implements a system for organizing all user-callable commands.
CommandHandlerFinder finder
void AddGlobalCommand(const wxChar *name, const wxChar *label, bool hasDialog, CommandHandlerFinder finder, CommandFunctorPointer callback, const wxChar *accel)
This class is an interface which should be implemented by classes which wish to be able to load and s...
Definition: XMLTagHandler.h:70
wxString GetLabelFromName(const wxString &name)
EffectManager is the class that handles effects and effect categories.
Definition: EffectManager.h:45
bool isEffect
bool isGlobal
bool HandleCommandEntry(const CommandListEntry *entry, CommandFlag flags, CommandMask mask, const wxEvent *evt=NULL)
static EffectManager & Get()
bool multi
bool wantKeyup
#define EXPERIMENTAL_KEY_VIEW
Definition: Project.h:47
wxString name
void SetDefaultFlags(CommandFlag flags, CommandMask mask)
LyricsPanel is a panel that paints the bouncing ball and the lyrics text.
Definition: Lyrics.h:71
PluginManager maintains a list of all plug ins. That covers modules, effects, generators, analysis-effects, commands. It also has functions for shared and private configs - which need to move out.
CommandManagerEventMonitor monitor
CommandMask mDefaultMask
MenuBarList mMenuBarList
_("Move Track &Down")+wxT("\t")+(GetActiveProject() -> GetCommandManager() ->GetKeyFromName(wxT("TrackMoveDown")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveTopID, _("Move Track to &Top")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveTop")).Raw()), OnMoveTrack) POPUP_MENU_ITEM(OnMoveBottomID, _("Move Track to &Bottom")+wxT("\t")+(GetActiveProject() ->GetCommandManager() ->GetKeyFromName(wxT("TrackMoveBottom")).Raw()), OnMoveTrack)#define SET_TRACK_NAME_PLUGIN_SYMBOLclass SetTrackNameCommand:public AudacityCommand
ValueRestorer< T > valueRestorer(T &var)
Definition: MemoryX.h:494
CommandFlag
Definition: CommandFlag.h:16
void SetOccultCommands(bool bOccult)
int ShowModal() override
const wxChar * name
Definition: Distortion.cpp:94
void AddItemList(const wxString &name, const TranslatedInternalString items[], size_t nItems, CommandHandlerFinder finder, CommandFunctorPointer callback, bool bIsEffect=false)
const wxString & GetID() const
AUDACITY_DLL_API AudacityProject * GetActiveProject()
Definition: Project.cpp:308
wxMenu * CurrentSubMenu() const
void HandleXMLEndTag(const wxChar *tag) override
wxString GetCommandIdentifier(const PluginID &ID)
NormalizedKeyString defaultKey
CommandFlag mDefaultFlags
wxString labelTop
TrackPanel * GetTrackPanel()
Definition: Project.h:307
wxString longLabel
void SetKeyFromName(const wxString &name, const NormalizedKeyString &key)
void GetCategories(wxArrayString &cats)
wxString mLongNameForItem
NormalizedKeyString key
NormalizedKeyString GetKeyFromName(const wxString &name) const
CommandHandlerObject &(*)(AudacityProject &) CommandHandlerFinder
Base class for XMLFileWriter and XMLStringWriter that provides the general functionality for creating...
Definition: XMLWriter.h:22
const wxString & Raw() const
Definition: Keyboard.h:27
static PluginManager & Get()
#define COMMAND
void TellUserWhyDisallowed(const wxString &Name, CommandFlag flagsGot, CommandFlag flagsRequired)
CommandIDHash mCommandIDHash
CommandFlag flags
bool hasDialog
bool HandleCapture(wxWindow *target, const wxKeyEvent &event)
SubMenuListEntry is a structure used by CommandManager.
void GetAllCommandNames(wxArrayString &names, bool includeMultis) const
CommandFlag GetUpdateFlags(bool checkActive=false)
Definition: Menus.cpp:2174
bool isOccult
wxString CommandParameter
static wxWindow * GetKeyboardCaptureHandler()
Definition: Project.cpp:5942