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